├── lib ├── capistrano.rb ├── capistrano │ ├── console.rb │ ├── version.rb │ ├── install.rb │ ├── dotfile.rb │ ├── framework.rb │ ├── deploy.rb │ ├── configuration │ │ ├── empty_filter.rb │ │ ├── null_filter.rb │ │ ├── role_filter.rb │ │ ├── host_filter.rb │ │ ├── filter.rb │ │ ├── plugin_installer.rb │ │ ├── question.rb │ │ └── servers.rb │ ├── upload_task.rb │ ├── doctor.rb │ ├── all.rb │ ├── scm │ │ ├── plugin.rb │ │ ├── tasks │ │ │ ├── hg.rake │ │ │ ├── svn.rake │ │ │ └── git.rake │ │ ├── hg.rb │ │ └── svn.rb │ ├── proc_helpers.rb │ ├── doctor │ │ ├── environment_doctor.rb │ │ ├── gems_doctor.rb │ │ ├── variables_doctor.rb │ │ ├── output_helpers.rb │ │ └── servers_doctor.rb │ ├── tasks │ │ ├── console.rake │ │ ├── doctor.rake │ │ ├── install.rake │ │ └── framework.rake │ ├── version_validator.rb │ ├── dsl │ │ ├── stages.rb │ │ ├── env.rb │ │ ├── task_enhancements.rb │ │ └── paths.rb │ ├── immutable_task.rb │ ├── setup.rb │ ├── templates │ │ ├── Capfile │ │ ├── deploy.rb.erb │ │ └── stage.rb.erb │ ├── defaults.rb │ ├── i18n.rb │ ├── plugin.rb │ └── scm.rb └── Capfile ├── docs ├── CNAME ├── assets │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── mstile-150x150-min.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── images │ │ ├── CapistranoLogo.png │ │ ├── BashStartupFiles1.png │ │ ├── CapistranoAvatar.png │ │ ├── CapistranoGraphic.png │ │ ├── airbrussh-screenshot.png │ │ ├── CapistranoGraphicWireframe.png │ │ ├── git-check-example-screenshot.png │ │ ├── github-personal-api-token-page.png │ │ ├── successful-git-check-example-screenshot.png │ │ └── capistrano-logo-harrow-logo-c-primary-darker-w640.png │ ├── fonts │ │ ├── social_foundicons.eot │ │ ├── social_foundicons.ttf │ │ ├── social_foundicons.woff │ │ └── social_foundicons.svg │ ├── browserconfig.xml │ ├── site.webmanifest │ ├── js │ │ └── foundation │ │ │ ├── foundation.alerts.js │ │ │ └── foundation.cookie.js │ ├── css │ │ ├── github.css │ │ ├── capistrano.css │ │ ├── dreamweaver.css │ │ └── social_foundicons.css │ └── safari-pinned-tab.svg ├── _layouts │ ├── post.html │ └── default.html ├── _includes │ ├── carbon.html │ ├── header.html │ ├── google_tag_manager.html │ ├── metrics.html │ └── footer.html ├── _config.yml ├── Gemfile ├── documentation │ ├── faq │ │ ├── how-can-i-check-for-existing-remote-file │ │ │ └── index.markdown │ │ ├── how-can-i-get-capistrano-to-prompt-for-a-password │ │ │ └── index.markdown │ │ ├── how-can-i-set-capistrano-configuration-paths │ │ │ └── index.markdown │ │ └── how-can-i-access-stage-configuration-variables │ │ │ └── index.markdown │ ├── advanced-features │ │ ├── validation-of-variables │ │ │ └── index.markdown │ │ ├── remote-file │ │ │ └── index.markdown │ │ ├── ignoring │ │ │ └── index.markdown │ │ ├── ssh-kit │ │ │ └── index.markdown │ │ ├── console │ │ │ └── index.markdown │ │ ├── ptys │ │ │ └── index.markdown │ │ ├── overriding-capistrano-tasks │ │ │ └── index.markdown │ │ ├── property-filtering │ │ │ └── index.markdown │ │ ├── role-filtering │ │ │ └── index.markdown │ │ ├── host-filtering │ │ │ └── index.markdown │ │ ├── filtering │ │ │ └── index.markdown │ │ └── custom-filters │ │ │ └── index.markdown │ ├── tasks │ │ ├── intro │ │ │ └── index.markdown │ │ └── rails │ │ │ └── index.markdown │ ├── getting-started │ │ ├── local-tasks │ │ │ └── index.markdown │ │ ├── before-after │ │ │ └── index.markdown │ │ ├── tasks │ │ │ └── index.markdown │ │ ├── user-input │ │ │ └── index.markdown │ │ ├── version-locking │ │ │ └── index.markdown │ │ ├── structure │ │ │ └── index.markdown │ │ └── flow │ │ │ └── index.markdown │ ├── plugins │ │ └── index.markdown │ ├── harrow │ │ └── index.markdown │ ├── third-party-plugins │ │ └── index.markdown │ └── overview │ │ └── what-is-capistrano │ │ └── index.markdown ├── index.markdown └── README.md ├── features ├── support │ ├── env.rb │ ├── remote_command_helpers.rb │ ├── remote_ssh_helpers.rb │ └── docker_gateway.rb ├── stage_failure.feature ├── doctor.feature ├── subdirectory.feature ├── sshconnect.feature ├── deploy_failure.feature ├── step_definitions │ ├── cap_commands.rb │ └── setup.rb ├── installation.feature └── configuration.feature ├── bin ├── cap └── capify ├── CHANGELOG.md ├── spec ├── integration_spec_helper.rb ├── lib │ ├── capistrano_spec.rb │ └── capistrano │ │ ├── configuration │ │ ├── empty_filter_spec.rb │ │ ├── null_filter_spec.rb │ │ ├── scm_resolver_spec.rb │ │ ├── host_filter_spec.rb │ │ ├── role_filter_spec.rb │ │ ├── plugin_installer_spec.rb │ │ └── question_spec.rb │ │ ├── upload_task_spec.rb │ │ ├── immutable_task_spec.rb │ │ ├── doctor │ │ ├── environment_doctor_spec.rb │ │ ├── output_helpers_spec.rb │ │ ├── gems_doctor_spec.rb │ │ ├── servers_doctor_spec.rb │ │ └── variables_doctor_spec.rb │ │ ├── application_spec.rb │ │ ├── plugin_spec.rb │ │ └── scm_spec.rb ├── support │ ├── matchers.rb │ └── tasks │ │ ├── failed.rake │ │ ├── plugin.rake │ │ ├── fail.rake │ │ ├── root.rake │ │ └── database.rake └── spec_helper.rb ├── docker-compose.yml ├── .docker ├── Dockerfile ├── ubuntu_setup.sh └── ssh_key_rsa.pub ├── .gitignore ├── .github ├── workflows │ ├── release-drafter.yml │ └── ci.yml ├── release-drafter.yml ├── pull_request_template.md └── issue_template.md ├── Rakefile ├── RELEASING.md ├── LICENSE.txt ├── .rubocop.yml ├── capistrano.gemspec ├── Gemfile └── UPGRADING-3.7.md /lib/capistrano.rb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | capistranorb.com 2 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require_relative "../../spec/support/test_app" 2 | -------------------------------------------------------------------------------- /lib/capistrano/console.rb: -------------------------------------------------------------------------------- 1 | load File.expand_path("../tasks/console.rake", __FILE__) 2 | -------------------------------------------------------------------------------- /lib/capistrano/version.rb: -------------------------------------------------------------------------------- 1 | module Capistrano 2 | VERSION = "3.18.1".freeze 3 | end 4 | -------------------------------------------------------------------------------- /bin/cap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "capistrano/all" 3 | Capistrano::Application.new.run 4 | -------------------------------------------------------------------------------- /lib/Capfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env cap 2 | include Capistrano::DSL 3 | require "capistrano/install" 4 | -------------------------------------------------------------------------------- /docs/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/favicon.ico -------------------------------------------------------------------------------- /lib/capistrano/install.rb: -------------------------------------------------------------------------------- 1 | load File.expand_path(File.join(File.dirname(__FILE__), "tasks/install.rake")) 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Release notes for this project are kept here: https://github.com/capistrano/capistrano/releases 2 | -------------------------------------------------------------------------------- /docs/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/favicon-16x16.png -------------------------------------------------------------------------------- /docs/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/favicon-32x32.png -------------------------------------------------------------------------------- /lib/capistrano/dotfile.rb: -------------------------------------------------------------------------------- 1 | dotfile = Pathname.new(File.join(Dir.home, ".capfile")) 2 | load dotfile if dotfile.file? 3 | -------------------------------------------------------------------------------- /lib/capistrano/framework.rb: -------------------------------------------------------------------------------- 1 | load File.expand_path("../tasks/framework.rake", __FILE__) 2 | require "capistrano/install" 3 | -------------------------------------------------------------------------------- /docs/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /lib/capistrano/deploy.rb: -------------------------------------------------------------------------------- 1 | require "capistrano/framework" 2 | 3 | load File.expand_path("../tasks/deploy.rake", __FILE__) 4 | -------------------------------------------------------------------------------- /docs/assets/mstile-150x150-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/mstile-150x150-min.png -------------------------------------------------------------------------------- /docs/assets/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/assets/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/assets/images/CapistranoLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/images/CapistranoLogo.png -------------------------------------------------------------------------------- /docs/assets/fonts/social_foundicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/fonts/social_foundicons.eot -------------------------------------------------------------------------------- /docs/assets/fonts/social_foundicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/fonts/social_foundicons.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/social_foundicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/fonts/social_foundicons.woff -------------------------------------------------------------------------------- /docs/assets/images/BashStartupFiles1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/images/BashStartupFiles1.png -------------------------------------------------------------------------------- /docs/assets/images/CapistranoAvatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/images/CapistranoAvatar.png -------------------------------------------------------------------------------- /docs/assets/images/CapistranoGraphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/images/CapistranoGraphic.png -------------------------------------------------------------------------------- /docs/assets/images/airbrussh-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/images/airbrussh-screenshot.png -------------------------------------------------------------------------------- /spec/integration_spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "support/test_app" 3 | require "support/matchers" 4 | 5 | include TestApp 6 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: capistrano 2 | 3 | services: 4 | ssh_server: 5 | build: 6 | context: .docker 7 | ports: 8 | - "2022:22" 9 | -------------------------------------------------------------------------------- /docs/assets/images/CapistranoGraphicWireframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/images/CapistranoGraphicWireframe.png -------------------------------------------------------------------------------- /docs/assets/images/git-check-example-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/images/git-check-example-screenshot.png -------------------------------------------------------------------------------- /docs/assets/images/github-personal-api-token-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/images/github-personal-api-token-page.png -------------------------------------------------------------------------------- /spec/lib/capistrano_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Capistrano 4 | describe Application do 5 | let(:app) { Application.new } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/support/matchers.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :be_a_symlink_to do |expected| 2 | match do |actual| 3 | File.identical?(expected, actual) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/tasks/failed.rake: -------------------------------------------------------------------------------- 1 | after "deploy:failed", :custom_failed do 2 | on roles :all do 3 | execute :touch, shared_path.join("failed") 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /docs/_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |

{{ page.date | date_to_string }}

5 | 6 |
7 | {{ content }} 8 |
9 | -------------------------------------------------------------------------------- /docs/_includes/carbon.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/assets/images/successful-git-check-example-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/images/successful-git-check-example-screenshot.png -------------------------------------------------------------------------------- /spec/support/tasks/plugin.rake: -------------------------------------------------------------------------------- 1 | # This rake file is used by plugin_spec.rb. 2 | 3 | task :plugin_test do 4 | # Example of invoking a helper method provided by the plugin 5 | hello 6 | end 7 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | name: Capistrano 2 | kramdown: 3 | input: GFM 4 | hard_wrap: false 5 | highlighter: rouge 6 | safe: true 7 | lsi: false 8 | exclude: ["Gemfile", "Gemfile.lock", "README.md", "vendor"] 9 | -------------------------------------------------------------------------------- /docs/assets/images/capistrano-logo-harrow-logo-c-primary-darker-w640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbrictson/capistrano/master/docs/assets/images/capistrano-logo-harrow-logo-c-primary-darker-w640.png -------------------------------------------------------------------------------- /lib/capistrano/configuration/empty_filter.rb: -------------------------------------------------------------------------------- 1 | module Capistrano 2 | class Configuration 3 | class EmptyFilter 4 | def filter(_servers) 5 | [] 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /.docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | WORKDIR /provision 3 | COPY ./ssh_key_rsa.pub /root/.ssh/authorized_keys 4 | COPY ./ubuntu_setup.sh ./ 5 | RUN ./ubuntu_setup.sh 6 | EXPOSE 22 7 | CMD ["/usr/sbin/sshd", "-D"] 8 | -------------------------------------------------------------------------------- /lib/capistrano/configuration/null_filter.rb: -------------------------------------------------------------------------------- 1 | module Capistrano 2 | class Configuration 3 | class NullFilter 4 | def filter(servers) 5 | servers 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # keep versions up-to-date with the actual GitHub Pages setup 4 | # https://pages.github.com/versions/ 5 | 6 | # just do `bundle update` to get the latest version. 7 | 8 | gem "github-pages" 9 | -------------------------------------------------------------------------------- /spec/support/tasks/fail.rake: -------------------------------------------------------------------------------- 1 | set :fail, proc { raise } 2 | before "deploy:starting", :fail do 3 | on roles :all do 4 | execute :mkdir, "-p", shared_path 5 | execute :touch, shared_path.join("fail") 6 | end 7 | fetch(:fail) 8 | end 9 | -------------------------------------------------------------------------------- /lib/capistrano/upload_task.rb: -------------------------------------------------------------------------------- 1 | require "rake/file_creation_task" 2 | 3 | module Capistrano 4 | class UploadTask < Rake::FileCreationTask 5 | def needed? 6 | true # always needed because we can't check remote hosts 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /features/stage_failure.feature: -------------------------------------------------------------------------------- 1 | Feature: Stage failure 2 | 3 | Background: 4 | Given a test app with the default configuration 5 | And a stage file named deploy.rb 6 | 7 | Scenario: Running a task 8 | When I run cap "doctor" 9 | Then the task fails 10 | -------------------------------------------------------------------------------- /lib/capistrano/doctor.rb: -------------------------------------------------------------------------------- 1 | require "capistrano/doctor/environment_doctor" 2 | require "capistrano/doctor/gems_doctor" 3 | require "capistrano/doctor/variables_doctor" 4 | require "capistrano/doctor/servers_doctor" 5 | 6 | load File.expand_path("../tasks/doctor.rake", __FILE__) 7 | -------------------------------------------------------------------------------- /bin/capify: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | puts "-" * 80 3 | puts "Capistrano 3.x is incompatible with Capistrano 2.x. " 4 | puts 5 | puts "This command has become `cap install` in Capistrano 3.x" 6 | puts 7 | puts "For more information see http://www.capistranorb.com/" 8 | puts "-" * 80 9 | -------------------------------------------------------------------------------- /spec/support/tasks/root.rake: -------------------------------------------------------------------------------- 1 | task :am_i_root do 2 | on roles(:all) do |host| 3 | host.user = "root" 4 | ident = capture :id, "-a" 5 | info "I am #{ident}" 6 | end 7 | on roles(:all) do |_host| 8 | ident = capture :id, "-a" 9 | info "I am #{ident}" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /docs/assets/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #1c1b39 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /spec/support/tasks/database.rake: -------------------------------------------------------------------------------- 1 | namespace :deploy do 2 | namespace :check do 3 | task linked_files: "config/database.yml" 4 | end 5 | end 6 | 7 | remote_file "config/database.yml" => "/tmp/database.yml", :roles => :all 8 | 9 | file "/tmp/database.yml" do |t| 10 | sh "touch #{t.name}" 11 | end 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | *.swp 4 | .bundle 5 | .config 6 | .rspec 7 | .rspec-local 8 | .ruby-version 9 | .yardoc 10 | Gemfile.lock 11 | InstalledFiles 12 | _site 13 | _yardoc 14 | coverage 15 | doc/ 16 | lib/bundler/man 17 | pkg 18 | rdoc 19 | spec/reports 20 | tags 21 | test/tmp 22 | test/version_tmp 23 | tmp 24 | /docs/_site/ 25 | vendor/ 26 | -------------------------------------------------------------------------------- /docs/documentation/faq/how-can-i-check-for-existing-remote-file/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: How can I check for existing remote file? 3 | layout: default 4 | --- 5 | 6 | The `test` method is best used for file checking with bash conditionals 7 | 8 | ```ruby 9 | if test("[ -f /tmp/foo ]") 10 | # do stuff 11 | end 12 | ``` 13 | 14 | -------------------------------------------------------------------------------- /features/doctor.feature: -------------------------------------------------------------------------------- 1 | Feature: Doctor 2 | 3 | Background: 4 | Given a test app with the default configuration 5 | 6 | Scenario: Running the doctor task 7 | When I run cap "doctor" 8 | Then the task is successful 9 | And contains "Environment" in the output 10 | And contains "Gems" in the output 11 | And contains "Variables" in the output 12 | -------------------------------------------------------------------------------- /features/subdirectory.feature: -------------------------------------------------------------------------------- 1 | Feature: cap can be run from a subdirectory, and will still find the Capfile 2 | 3 | Background: 4 | Given a test app with the default configuration 5 | And servers with the roles app and web 6 | 7 | Scenario: Running cap from a subdirectory 8 | When I run cap "git:check" within the "config" directory 9 | Then the task is successful 10 | -------------------------------------------------------------------------------- /docs/_includes/header.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | capistrano logo 6 | 7 |
8 | 9 | 10 | 11 |
12 |
13 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: read 11 | 12 | jobs: 13 | update_release_draft: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: release-drafter/release-drafter@v5 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /lib/capistrano/all.rb: -------------------------------------------------------------------------------- 1 | require "rake" 2 | require "sshkit" 3 | 4 | require "io/console" 5 | 6 | Rake.application.options.trace = true 7 | 8 | require "capistrano/version" 9 | require "capistrano/version_validator" 10 | require "capistrano/i18n" 11 | require "capistrano/dsl" 12 | require "capistrano/application" 13 | require "capistrano/configuration" 14 | require "capistrano/configuration/scm_resolver" 15 | 16 | module Capistrano 17 | end 18 | -------------------------------------------------------------------------------- /features/sshconnect.feature: -------------------------------------------------------------------------------- 1 | Feature: SSH Connection 2 | 3 | Background: 4 | Given a test app with the default configuration 5 | And servers with the roles app and web 6 | And a task which executes as root 7 | 8 | Scenario: Switching from default user to root and back again 9 | When I run cap "am_i_root" 10 | Then the task is successful 11 | And the output matches "I am uid=0\(root\)" followed by "I am uid=\d+\(deployer\)" 12 | -------------------------------------------------------------------------------- /spec/lib/capistrano/configuration/empty_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Capistrano 4 | class Configuration 5 | describe EmptyFilter do 6 | subject(:empty_filter) { EmptyFilter.new } 7 | 8 | describe "#filter" do 9 | let(:servers) { mock("servers") } 10 | 11 | it "returns an empty array" do 12 | expect(empty_filter.filter(servers)).to eq([]) 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/lib/capistrano/configuration/null_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Capistrano 4 | class Configuration 5 | describe NullFilter do 6 | subject(:null_filter) { NullFilter.new } 7 | 8 | describe "#filter" do 9 | let(:servers) { mock("servers") } 10 | 11 | it "returns the servers passed in as arguments" do 12 | expect(null_filter.filter(servers)).to eq(servers) 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/capistrano/scm/plugin.rb: -------------------------------------------------------------------------------- 1 | require "capistrano/plugin" 2 | require "capistrano/scm" 3 | 4 | # Base class for all built-in and third-party SCM plugins. Notice that this 5 | # class doesn't really do anything other than provide an `scm?` predicate. This 6 | # tells Capistrano that the plugin provides SCM functionality. All other plugin 7 | # features are inherited from Capistrano::Plugin. 8 | # 9 | class Capistrano::SCM::Plugin < Capistrano::Plugin 10 | def scm? 11 | true 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/capistrano/proc_helpers.rb: -------------------------------------------------------------------------------- 1 | module Capistrano 2 | module ProcHelpers 3 | module_function 4 | 5 | # Tests whether the given object appears to respond to `call` with 6 | # zero parameters. In Capistrano, such a proc is used to represent a 7 | # "deferred value". That is, a value that is resolved by invoking `call` at 8 | # the time it is first needed. 9 | def callable_without_parameters?(x) 10 | x.respond_to?(:call) && (!x.respond_to?(:arity) || x.arity.zero?) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /docs/documentation/advanced-features/validation-of-variables/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Validation of variables 4 | --- 5 | 6 | To validate a variable, each time before it is set, define a validation: 7 | 8 | ```ruby 9 | validate :some_key do |key, value| 10 | if value.length < 5 11 | raise Capistrano::ValidationError, "Length of #{key} is too short!" 12 | end 13 | end 14 | ``` 15 | 16 | Multiple validations can be assigned to a single key. Validations will be executed in the order of registration. 17 | -------------------------------------------------------------------------------- /docs/documentation/faq/how-can-i-get-capistrano-to-prompt-for-a-password/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: How can I get Capistrano to prompt for a password? 3 | layout: default 4 | --- 5 | 6 | Password authentication can be done via `ask` in your deploy environment file (e.g.: config/environments/production.rb) 7 | 8 | ```ruby 9 | # Capistrano > 3.2.0 supports echo: false 10 | ask(:password, nil, echo: false) 11 | server 'server.domain.com', user: 'ssh_user_name', port: 22, password: fetch(:password), roles: %w{web app db} 12 | ``` 13 | -------------------------------------------------------------------------------- /docs/assets/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Capistrano", 3 | "short_name": "Capistrano", 4 | "icons": [ 5 | { 6 | "src": "android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#1c1b39", 17 | "background_color": "#1c1b39", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "cucumber/rake/task" 3 | require "rspec/core/rake_task" 4 | 5 | begin 6 | require "rubocop/rake_task" 7 | desc "Run RuboCop checks" 8 | RuboCop::RakeTask.new 9 | task default: %i(spec rubocop) 10 | rescue LoadError 11 | task default: :spec 12 | end 13 | 14 | RSpec::Core::RakeTask.new 15 | Cucumber::Rake::Task.new(:features) 16 | 17 | Rake::Task["release"].enhance do 18 | puts "Don't forget to publish the release on GitHub!" 19 | system "open https://github.com/capistrano/capistrano/releases" 20 | end 21 | -------------------------------------------------------------------------------- /lib/capistrano/doctor/environment_doctor.rb: -------------------------------------------------------------------------------- 1 | require "capistrano/doctor/output_helpers" 2 | 3 | module Capistrano 4 | module Doctor 5 | class EnvironmentDoctor 6 | include Capistrano::Doctor::OutputHelpers 7 | 8 | def call 9 | title("Environment") 10 | puts <<-OUT.gsub(/^\s+/, "") 11 | Ruby #{RUBY_DESCRIPTION} 12 | Rubygems #{Gem::VERSION} 13 | Bundler #{defined?(Bundler::VERSION) ? Bundler::VERSION : 'N/A'} 14 | Command #{$PROGRAM_NAME} #{ARGV.join(' ')} 15 | OUT 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /docs/_includes/google_tag_manager.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /features/deploy_failure.feature: -------------------------------------------------------------------------------- 1 | Feature: Deploy failure 2 | 3 | Background: 4 | Given a test app with the default configuration 5 | And a custom task that will simulate a failure 6 | And a custom task to run in the event of a failure 7 | And servers with the roles app and web 8 | 9 | Scenario: Triggering the custom task 10 | When I run cap "deploy:starting" 11 | But an error is raised 12 | Then the failure task will not run 13 | 14 | Scenario: Triggering the custom task 15 | When I run cap "deploy" 16 | But an error is raised 17 | Then the failure task will run 18 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/console.rake: -------------------------------------------------------------------------------- 1 | desc "Execute remote commands" 2 | task :console do 3 | stage = fetch(:stage) 4 | puts I18n.t("console.welcome", scope: :capistrano, stage: stage) 5 | loop do 6 | print "#{stage}> " 7 | 8 | command = (input = $stdin.gets) ? input.chomp : "exit" 9 | 10 | next if command.empty? 11 | 12 | if %w{quit exit q}.include? command 13 | puts t("console.bye") 14 | break 15 | else 16 | begin 17 | on roles :all do 18 | execute command 19 | end 20 | rescue => e 21 | puts e 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /docs/documentation/tasks/intro/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Task Cookbook 3 | layout: default 4 | --- 5 | 6 | These pages document common custom tasks for specific use cases. It is hoped that these will be copied and modified for your use case, and also provide a basis for understanding how to extend Capistrano for your own usage. 7 | 8 | You can also look in most Capistrano repositories (including core) for rake tasks to see further example of how it works. 9 | 10 | Feel free to contribute more via a Pull Request. 11 | 12 | ### Pages 13 | 14 | 15 | * [Rails related tasks](/documentation/tasks/rails/) 16 | -------------------------------------------------------------------------------- /.docker/ubuntu_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | export DEBIAN_FRONTEND=noninteractive 6 | 7 | # Create `deployer` user 8 | adduser --disabled-password deployer < /dev/null 9 | mkdir -p /home/deployer/.ssh 10 | cp /root/.ssh/authorized_keys /home/deployer/.ssh 11 | chown -R deployer:deployer /home/deployer/.ssh 12 | chmod 600 /home/deployer/.ssh/authorized_keys 13 | 14 | # Install and configure sshd 15 | apt -y update 16 | apt-get -y install openssh-server git 17 | { 18 | echo "Port 22" 19 | echo "PasswordAuthentication no" 20 | echo "ChallengeResponseAuthentication no" 21 | } >> /etc/ssh/sshd_config 22 | mkdir /var/run/sshd 23 | chmod 0755 /var/run/sshd 24 | -------------------------------------------------------------------------------- /spec/lib/capistrano/upload_task_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Capistrano::UploadTask do 4 | let(:app) { Rake.application = Rake::Application.new } 5 | 6 | subject(:upload_task) { described_class.define_task("path/file.yml") } 7 | 8 | it { is_expected.to be_a(Rake::FileCreationTask) } 9 | it { is_expected.to be_needed } 10 | 11 | context "inside namespace" do 12 | let(:normal_task) { Rake::Task.define_task("path/other_file.yml") } 13 | 14 | around { |ex| app.in_namespace("namespace", &ex) } 15 | 16 | it { expect(upload_task.name).to eq("path/file.yml") } 17 | it { expect(upload_task.scope.path).to eq("namespace") } 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /features/support/remote_command_helpers.rb: -------------------------------------------------------------------------------- 1 | module RemoteCommandHelpers 2 | def test_dir_exists(path) 3 | exists?("d", path) 4 | end 5 | 6 | def test_symlink_exists(path) 7 | exists?("L", path) 8 | end 9 | 10 | def test_file_exists(path) 11 | exists?("f", path) 12 | end 13 | 14 | def exists?(type, path) 15 | %Q{[[ -#{type} "#{path}" ]]} 16 | end 17 | 18 | def symlinked?(symlink_path, target_path) 19 | "[ #{symlink_path} -ef #{target_path} ]" 20 | end 21 | 22 | def safely_remove_file(_path) 23 | run_remote_ssh_command("rm #{test_file}") 24 | rescue 25 | RemoteSSHHelpers::RemoteSSHCommandError 26 | end 27 | end 28 | 29 | World(RemoteCommandHelpers) 30 | -------------------------------------------------------------------------------- /lib/capistrano/version_validator.rb: -------------------------------------------------------------------------------- 1 | module Capistrano 2 | class VersionValidator 3 | def initialize(version) 4 | @version = version 5 | end 6 | 7 | def verify 8 | return self if match? 9 | raise "Capfile locked at #{version}, but #{current_version} is loaded" 10 | end 11 | 12 | private 13 | 14 | attr_reader :version 15 | 16 | def match? 17 | available =~ requested 18 | end 19 | 20 | def current_version 21 | VERSION 22 | end 23 | 24 | def available 25 | Gem::Dependency.new("cap", version) 26 | end 27 | 28 | def requested 29 | Gem::Dependency.new("cap", current_version) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /docs/assets/fonts/social_foundicons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /features/step_definitions/cap_commands.rb: -------------------------------------------------------------------------------- 1 | When(/^I run cap "(.*?)"$/) do |task| 2 | @success, @output = TestApp.cap(task) 3 | end 4 | 5 | When(/^I run cap "(.*?)" within the "(.*?)" directory$/) do |task, directory| 6 | @success, @output = TestApp.cap(task, directory) 7 | end 8 | 9 | When(/^I run cap "(.*?)" as part of a release$/) do |task| 10 | TestApp.cap("deploy:new_release_path #{task}") 11 | end 12 | 13 | When(/^I run "(.*?)"$/) do |command| 14 | @success, @output = TestApp.run(command) 15 | end 16 | 17 | When(/^I rollback to a specific release$/) do 18 | @rollback_release = @release_paths.first.split("/").last 19 | 20 | step %Q{I run cap "deploy:rollback ROLLBACK_RELEASE=#{@rollback_release}"} 21 | end 22 | -------------------------------------------------------------------------------- /features/installation.feature: -------------------------------------------------------------------------------- 1 | Feature: Installation 2 | 3 | Background: 4 | Given a test app without any configuration 5 | 6 | Scenario: The "install" task documentation can be viewed 7 | When I run "cap -T" 8 | Then the task is successful 9 | And contains "cap install" in the output 10 | 11 | Scenario: With default stages 12 | When I run "cap install" 13 | Then the deploy.rb file is created 14 | And the default stage files are created 15 | And the tasks folder is created 16 | 17 | Scenario: With specified stages 18 | When I run "cap install STAGES=qa,production" 19 | Then the deploy.rb file is created 20 | And the specified stage files are created 21 | And the tasks folder is created 22 | -------------------------------------------------------------------------------- /.docker/ssh_key_rsa.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC6wVCoBOGYS5qfXZGiKjuh7RWExlzrQ1Gml8Z3DDCggSEibh1WXJU9+rNKTqHmVoGy36iarPfjsGWw3IbiWiMOXD7crg3BCyRbksAdrVV/MIq6BiKqnGaQvOV9AE5RbA6W4/prkEKL/tdnpVaWpMbtthEo6H/IO3Ih+iXomni4NkUBjDjJf6sr8B/vI4aIcp0I5/uXNI3zIWVhQ6yBZ4hORs8jPBMpe7jMoFb5z8t6AUUfmIc66JiLmLX2hqYEreIB7rzlPOsDsOByADubyA/r3zYaA5kZxLbHT+sgQitjb4TATdoCxX6+utxVQeRZK4YrCR3D/aWzhmOgl5OHxO7ArWgICXC18icrvDdUACxOWKRaSe0WXw0nX1L0ZteKuYsvFAf0t0nNPStnWkxzRA4snzJCiptuWcjx8p7WY0Ph267ERq9rNFgMDmSsHaYMOnJAXr8WF00zFcVgAW6b3MBJSmBEtcz45RvMDv4XiMvzZiP/1+we+JXI1f5Y8LhyVtkeME5J7u6LrNUI5bvBMyDodMkmOonRdpL4iwOUXwpETX7i8fsr7+8YFw+JtXQ0iePq4V2PmmPNXThljhkhConwVnGsfV4qfuGaGJgMKJfK/E6hF8W9ejEH9BvmX7l75fyAK0zMq+n6ohOr4M4wVcz9G+s+5vSZ5JNwYTyVrAthtw== test@example.com 2 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: "$RESOLVED_VERSION" 2 | tag-template: "v$RESOLVED_VERSION" 3 | categories: 4 | - title: "⚠️ Breaking Changes" 5 | label: "⚠️ Breaking" 6 | - title: "✨ New Features" 7 | label: "✨ Feature" 8 | - title: "🐛 Bug Fixes" 9 | label: "🐛 Bug Fix" 10 | - title: "📚 Documentation" 11 | label: "📚 Docs" 12 | - title: "🏠 Housekeeping" 13 | label: "🏠 Housekeeping" 14 | version-resolver: 15 | minor: 16 | labels: 17 | - "⚠️ Breaking" 18 | - "✨ Feature" 19 | default: patch 20 | change-template: "- $TITLE (#$NUMBER) @$AUTHOR" 21 | no-changes-template: "- No changes" 22 | template: | 23 | $CHANGES 24 | 25 | **Full Changelog:** https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION 26 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/doctor.rake: -------------------------------------------------------------------------------- 1 | desc "Display a Capistrano troubleshooting report (all doctor: tasks)" 2 | task doctor: ["doctor:environment", "doctor:gems", "doctor:variables", "doctor:servers"] 3 | 4 | namespace :doctor do 5 | desc "Display Ruby environment details" 6 | task :environment do 7 | Capistrano::Doctor::EnvironmentDoctor.new.call 8 | end 9 | 10 | desc "Display Capistrano gem versions" 11 | task :gems do 12 | Capistrano::Doctor::GemsDoctor.new.call 13 | end 14 | 15 | desc "Display the values of all Capistrano variables" 16 | task :variables do 17 | Capistrano::Doctor::VariablesDoctor.new.call 18 | end 19 | 20 | desc "Display the effective servers configuration" 21 | task :servers do 22 | Capistrano::Doctor::ServersDoctor.new.call 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/capistrano/configuration/role_filter.rb: -------------------------------------------------------------------------------- 1 | module Capistrano 2 | class Configuration 3 | class RoleFilter 4 | def initialize(values) 5 | av = Array(values).dup 6 | av = av.flat_map { |v| v.is_a?(String) ? v.split(",") : v } 7 | @rex = regex_matcher(av) 8 | end 9 | 10 | def filter(servers) 11 | Array(servers).select { |s| s.is_a?(String) ? false : s.roles.any? { |r| @rex.match r } } 12 | end 13 | 14 | private 15 | 16 | def regex_matcher(values) 17 | values.map! do |v| 18 | case v 19 | when Regexp then v 20 | else 21 | vs = v.to_s 22 | vs =~ %r{^/(.+)/$} ? Regexp.new($1) : /^#{Regexp.quote(vs)}$/ 23 | end 24 | end 25 | Regexp.union values 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | (Guidelines for creating a bug report are available 4 | here: https://github.com/capistrano/capistrano/blob/master/DEVELOPMENT.md) 5 | 6 | Provide a general description of the code changes in your pull 7 | request... were there any bugs you had fixed? If so, mention them. If 8 | these bugs have open GitHub issues, be sure to tag them here as well, 9 | to keep the conversation linked together. 10 | 11 | ### Short checklist 12 | 13 | - [ ] Did you run `bundle exec rubocop -a` to fix linter issues? 14 | - [ ] If relevant, did you create a test? 15 | - [ ] Did you confirm that the RSpec tests pass? 16 | 17 | ### Other Information 18 | 19 | If there's anything else that's important and relevant to your pull 20 | request, mention that information here. 21 | 22 | Thanks for helping improve Capistrano! 23 | -------------------------------------------------------------------------------- /lib/capistrano/configuration/host_filter.rb: -------------------------------------------------------------------------------- 1 | module Capistrano 2 | class Configuration 3 | class HostFilter 4 | def initialize(values) 5 | av = Array(values).dup 6 | av = av.flat_map { |v| v.is_a?(String) && v =~ /^(?[-A-Za-z0-9.]+)(,\g)*$/ ? v.split(",") : v } 7 | @rex = regex_matcher(av) 8 | end 9 | 10 | def filter(servers) 11 | Array(servers).select { |s| @rex.match s.to_s } 12 | end 13 | 14 | private 15 | 16 | def regex_matcher(values) 17 | values.map! do |v| 18 | case v 19 | when Regexp then v 20 | else 21 | vs = v.to_s 22 | vs =~ /^[-A-Za-z0-9.]+$/ ? /^#{Regexp.quote(vs)}$/ : Regexp.new(vs) 23 | end 24 | end 25 | Regexp.union values 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /docs/documentation/getting-started/local-tasks/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Local Tasks 3 | layout: default 4 | --- 5 | 6 | Local tasks can be run by replacing `on` with `run_locally`: 7 | 8 | ```ruby 9 | desc 'Notify service of deployment' 10 | task :notify do 11 | run_locally do 12 | with rails_env: :development do 13 | rake 'service:notify' 14 | end 15 | end 16 | end 17 | ``` 18 | 19 | Of course, you can always just use standard ruby syntax to run things locally: 20 | 21 | ```ruby 22 | desc 'Notify service of deployment' 23 | task :notify do 24 | %x(RAILS_ENV=development bundle exec rake "service:notify") 25 | end 26 | ``` 27 | 28 | Alternatively you could use the rake syntax: 29 | 30 | ```ruby 31 | desc "Notify service of deployment" 32 | task :notify do 33 | sh 'RAILS_ENV=development bundle exec rake "service:notify"' 34 | end 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: A remote server automation and deployment tool written in Ruby. 4 | --- 5 | 6 | ### A Simple Task 7 | 8 | ```ruby 9 | role :demo, %w{example.com example.org example.net} 10 | task :uptime do 11 | on roles(:demo), in: :parallel do |host| 12 | uptime = capture(:uptime) 13 | puts "#{host.hostname} reports: #{uptime}" 14 | end 15 | end 16 | ``` 17 | 18 | Capistrano extends the *Rake* DSL with methods specific to running commands 19 | `on()` servers. 20 | 21 | ### For Any Language 22 | 23 | Capistrano is written in Ruby, but it can easily be used to deploy any 24 | language. 25 | 26 | If your language or framework has special deployment requirements, Capistrano can easily be 27 | extended to support them. 28 | 29 | ### Source Code 30 | 31 |
32 | -------------------------------------------------------------------------------- /lib/capistrano/dsl/stages.rb: -------------------------------------------------------------------------------- 1 | module Capistrano 2 | module DSL 3 | module Stages 4 | RESERVED_NAMES = %w(deploy doctor install).freeze 5 | private_constant :RESERVED_NAMES 6 | 7 | def stages 8 | names = Dir[stage_definitions].map { |f| File.basename(f, ".rb") } 9 | assert_valid_stage_names(names) 10 | names 11 | end 12 | 13 | def stage_definitions 14 | stage_config_path.join("*.rb") 15 | end 16 | 17 | def stage_set? 18 | !!fetch(:stage, false) 19 | end 20 | 21 | private 22 | 23 | def assert_valid_stage_names(names) 24 | invalid = names.find { |n| RESERVED_NAMES.include?(n) } 25 | return if invalid.nil? 26 | 27 | raise t("error.invalid_stage_name", name: invalid, path: stage_config_path.join("#{invalid}.rb")) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /features/support/remote_ssh_helpers.rb: -------------------------------------------------------------------------------- 1 | require "open3" 2 | require "socket" 3 | require_relative "docker_gateway" 4 | 5 | module RemoteSSHHelpers 6 | extend self 7 | 8 | class RemoteSSHCommandError < RuntimeError; end 9 | 10 | def start_ssh_server 11 | docker_gateway.start 12 | end 13 | 14 | def wait_for_ssh_server(retries=3) 15 | Socket.tcp("localhost", 2022, connect_timeout: 1).close 16 | rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT 17 | retries -= 1 18 | sleep(2) && retry if retries.positive? 19 | raise 20 | end 21 | 22 | def run_remote_ssh_command(command) 23 | stdout, stderr, status = docker_gateway.run_shell_command(command) 24 | return [stdout, stderr] if status.success? 25 | raise RemoteSSHCommandError, status 26 | end 27 | 28 | def docker_gateway 29 | @docker_gateway ||= DockerGateway.new(method(:log)) 30 | end 31 | end 32 | 33 | World(RemoteSSHHelpers) 34 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # capistranorb.com 2 | 3 | This `docs/` directory generates the [capistranorb.com](https://capistranorb.com/) site. Feel free to send pull requests to make improvements to Capistrano's documentation! 4 | 5 | ### Quick start 6 | 7 | This is a GitHub Pages project, which means it is built using Jekyll. To run the site locally, you'll need a functioning Ruby environment. 8 | 9 | After checking out the capistrano repository, run: 10 | 11 | ``` 12 | cd docs 13 | bundle install 14 | ``` 15 | 16 | Then start the Jekyll server with: 17 | 18 | ``` 19 | bundle exec jekyll serve 20 | ``` 21 | 22 | You should now be able to preview the site on `http://localhost:4000`. After making any changes to markdown or HTML files, just refresh your browser to see the results. You do not have to restart the Jekyll process. 23 | 24 | More information: [Using Jekyll with Pages](https://help.github.com/articles/using-jekyll-with-pages/). 25 | -------------------------------------------------------------------------------- /docs/documentation/advanced-features/remote-file/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remote file task 3 | layout: default 4 | --- 5 | 6 | **Warning: `remote_file` is deprecated and was removed in Capistrano 3.7.0** 7 | 8 | The `remote_file` task is allowing the existence of a remote file to be set as a prerequisite. These tasks can in turn depend on local files if required. In this implementation, the fact that we're dealing with a file in the shared path is assumed. 9 | 10 | As an example, this task can be used to ensure that files to be linked exist 11 | before running the check:linked_files task: 12 | 13 | ```ruby 14 | namespace :deploy do 15 | namespace :check do 16 | task :linked_files => 'config/newrelic.yml' 17 | end 18 | end 19 | 20 | remote_file 'config/newrelic.yml' => '/tmp/newrelic.yml', roles: :app 21 | 22 | file '/tmp/newrelic.yml' do |t| 23 | sh "curl -o #{t.name} https://rpm.newrelic.com/accounts/xx/newrelic.yml" 24 | end 25 | ``` 26 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib")) 2 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 3 | require "capistrano/all" 4 | require "rspec" 5 | require "mocha/api" 6 | require "time" 7 | 8 | # Requires supporting files with custom matchers and macros, etc, 9 | # in ./support/ and its subdirectories. 10 | Dir['#{File.dirname(__FILE__)}/support/**/*.rb'].each { |f| require f } 11 | 12 | RSpec.configure do |config| 13 | config.raise_errors_for_deprecations! 14 | config.mock_framework = :mocha 15 | config.order = "random" 16 | 17 | config.around(:example, capture_io: true) do |example| 18 | begin 19 | Rake.application.options.trace_output = StringIO.new 20 | $stdout = StringIO.new 21 | $stderr = StringIO.new 22 | example.run 23 | ensure 24 | Rake.application.options.trace_output = STDERR 25 | $stdout = STDOUT 26 | $stderr = STDERR 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/lib/capistrano/immutable_task_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "rake" 3 | require "capistrano/immutable_task" 4 | 5 | module Capistrano 6 | describe ImmutableTask do 7 | after do 8 | # Ensure that any tasks we create in these tests don't pollute other tests 9 | Rake::Task.clear 10 | end 11 | 12 | it "prints warning and raises when task is enhanced" do 13 | extend(Rake::DSL) 14 | 15 | load_defaults = Rake::Task.define_task("load:defaults") 16 | load_defaults.extend(Capistrano::ImmutableTask) 17 | 18 | $stderr.expects(:puts).with do |message| 19 | message =~ /^ERROR: load:defaults has already been invoked/ 20 | end 21 | 22 | expect do 23 | namespace :load do 24 | task :defaults do 25 | # Never reached since load_defaults is frozen and can't be enhanced 26 | end 27 | end 28 | end.to raise_error(/frozen/i) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/capistrano/configuration/filter.rb: -------------------------------------------------------------------------------- 1 | require "capistrano/configuration" 2 | require "capistrano/configuration/empty_filter" 3 | require "capistrano/configuration/host_filter" 4 | require "capistrano/configuration/null_filter" 5 | require "capistrano/configuration/role_filter" 6 | 7 | module Capistrano 8 | class Configuration 9 | class Filter 10 | def initialize(type, values=nil) 11 | raise "Invalid filter type #{type}" unless %i(host role).include? type 12 | av = Array(values) 13 | @strategy = if av.empty? then EmptyFilter.new 14 | elsif av.include?(:all) || av.include?("all") then NullFilter.new 15 | elsif type == :host then HostFilter.new(values) 16 | elsif type == :role then RoleFilter.new(values) 17 | else NullFilter.new 18 | end 19 | end 20 | 21 | def filter(servers) 22 | @strategy.filter servers 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /docs/documentation/plugins/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Official Plugins 3 | layout: default 4 | --- 5 | 6 | This is the list of official Capistrano Plugins. 7 | 8 | 21 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | ## Prerequisites 4 | 5 | * You must have commit rights to the Capistrano repository. 6 | * You must have push rights for the capistrano gem on rubygems.org. 7 | 8 | ## How to release 9 | 10 | 1. Run `bundle install` to make sure that you have all the gems necessary for testing and releasing. 11 | 2. **Ensure all tests are passing by running `rake spec` and `rake features`.** 12 | 3. Determine which would be the correct next version number according to [semver](http://semver.org/). 13 | 4. Update the version in `./lib/capistrano/version.rb`. 14 | 5. Update the version in the `./README.md` Gemfile example (`gem "capistrano", "~> X.Y"`). 15 | 6. Commit the `version.rb` and `README.md` changes in a single commit, the message should be "Release vX.Y.Z" 16 | 7. Run `rake release`; this will tag, push to GitHub, and publish to rubygems.org. 17 | 8. Update the draft release on the [GitHub releases page](https://github.com/capistrano/capistrano/releases) to point to the new tag and publish the release 18 | -------------------------------------------------------------------------------- /docs/documentation/advanced-features/ignoring/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ignoring 3 | layout: default 4 | --- 5 | 6 | Files committed to version control (i.e. not in .gitignore) can still be ignored when deploying. To ignore these files or directories, simply add them to .gitattributes: 7 | 8 | ```bash 9 | config/deploy/deploy.rb export-ignore 10 | config/deploy/ export-ignore 11 | ``` 12 | 13 | These files will be kept in version control but not deployed to the server. 14 | 15 | *Note:* This feature is probably unnecessary unless the root of your repository is also your web server's docroot. For example, in a Rails application, the docroot is the `public/` folder. Since all of the Capistrano configuration lives above or beside this folder, it cannot be served and is not a security risk. If the docroot is indeed at the base of the repository, consider changing that by moving the code at the repository base to a subdirectory such as public_html instead of using this feature. Note that this feature is very specific to Git and will not work on other SCMs. 16 | -------------------------------------------------------------------------------- /lib/capistrano/immutable_task.rb: -------------------------------------------------------------------------------- 1 | module Capistrano 2 | # This module extends a Rake::Task to freeze it to prevent it from being 3 | # enhanced. This is used to prevent users from enhancing a task at the wrong 4 | # point of Capistrano's boot process, which can happen if a Capistrano plugin 5 | # is loaded in deploy.rb by mistake (instead of in the Capfile). 6 | # 7 | # Usage: 8 | # 9 | # task = Rake.application["load:defaults"] 10 | # task.invoke 11 | # task.extend(Capistrano::ImmutableTask) # prevent further modifications 12 | # 13 | module ImmutableTask 14 | def self.extended(task) 15 | task.freeze 16 | end 17 | 18 | def enhance(*args, &block) 19 | $stderr.puts <<-MESSAGE 20 | ERROR: #{name} has already been invoked and can no longer be modified. 21 | Check that you haven't loaded a Capistrano plugin in deploy.rb or a stage 22 | (e.g. deploy/production.rb) by mistake. 23 | Plugins must be loaded in the Capfile to initialize properly. 24 | MESSAGE 25 | 26 | # This will raise a frozen object error 27 | super(*args, &block) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/capistrano/setup.rb: -------------------------------------------------------------------------------- 1 | require "capistrano/doctor" 2 | require "capistrano/immutable_task" 3 | include Capistrano::DSL 4 | 5 | namespace :load do 6 | task :defaults do 7 | load "capistrano/defaults.rb" 8 | end 9 | end 10 | 11 | require "airbrussh/capistrano" 12 | # We don't need to show the "using Airbrussh" banner announcement since 13 | # Airbrussh is now the built-in formatter. Also enable command output by 14 | # default; hiding the output might be confusing to users new to Capistrano. 15 | Airbrussh.configure do |airbrussh| 16 | airbrussh.banner = false 17 | airbrussh.command_output = true 18 | end 19 | 20 | stages.each do |stage| 21 | Rake::Task.define_task(stage) do 22 | set(:stage, stage.to_sym) 23 | 24 | invoke "load:defaults" 25 | Rake.application["load:defaults"].extend(Capistrano::ImmutableTask) 26 | env.variables.untrusted! do 27 | load deploy_config_path 28 | load stage_config_path.join("#{stage}.rb") 29 | end 30 | configure_scm 31 | I18n.locale = fetch(:locale, :en) 32 | configure_backend 33 | end 34 | end 35 | 36 | require "capistrano/dotfile" 37 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | **Important:** GitHub issues are for feature requests or bug reports. The Capistrano team recommends you use [Stack Overflow](http://stackoverflow.com/questions/tagged/capistrano) for general questions. For more details, please see our [contribution policy](https://github.com/capistrano/capistrano/blob/master/CONTRIBUTING.md). 2 | 3 | --- 4 | 5 | ### Steps to reproduce 6 | If reasonable, you can help by creating a Capistrano skeleton example project which reproduces the issue you are seeing. You can then upload the individual files to a GitHub Gist or a GitHub project. Others can simply modify the configuration to point at a test server/repository of their own. Often times, an issue is resolved simply by making this test case. 7 | 8 | An example test case is here: https://gist.github.com/will-in-wi/527327e31af30b3eae2068e2965be05b 9 | 10 | ### Expected behavior 11 | Tell us what should happen 12 | 13 | ### Actual behavior 14 | Tell us what happens instead 15 | 16 | ### System configuration 17 | Please link to the output of `cap doctor` in a GitHub Gist. 18 | 19 | Thanks for helping improve Capistrano! 20 | -------------------------------------------------------------------------------- /docs/documentation/advanced-features/ssh-kit/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remote commands with SSH Kit 3 | layout: default 4 | --- 5 | 6 | Capistrano executes commands on remote servers using [**SSHKit**](https://github.com/capistrano/sshkit). 7 | 8 | An example setting a working directory, user and environment variable: 9 | 10 | ```ruby 11 | on roles(:app), in: :sequence, wait: 5 do 12 | within "/opt/sites/example.com" do 13 | # commands in this block execute in the 14 | # directory: /opt/sites/example.com 15 | as :deploy do 16 | # commands in this block execute as the "deploy" user. 17 | with rails_env: :production do 18 | # commands in this block execute with the environment 19 | # variable RAILS_ENV=production 20 | rake "assets:precompile" 21 | runner "S3::Sync.notify" 22 | end 23 | end 24 | end 25 | end 26 | ``` 27 | 28 | For more examples, see the EXAMPLES.md file in the [**SSHKit**](https://github.com/capistrano/sshkit) project: 29 | 30 | [https://github.com/capistrano/sshkit/blob/master/EXAMPLES.md](https://github.com/capistrano/sshkit/blob/master/EXAMPLES.md) 31 | -------------------------------------------------------------------------------- /features/configuration.feature: -------------------------------------------------------------------------------- 1 | Feature: The path to the configuration can be changed, removing the need to 2 | follow Ruby/Rails conventions 3 | 4 | Background: 5 | Given a test app with the default configuration 6 | And servers with the roles app and web 7 | 8 | Scenario: Deploying with configuration in default location 9 | When I run "cap test" 10 | Then the task is successful 11 | 12 | Scenario: Deploying with configuration in a custom location 13 | But the configuration is in a custom location 14 | When I run "cap test" 15 | Then the task is successful 16 | 17 | Scenario: Show install task with configuration in default location 18 | When I run "cap -T" 19 | Then the task is successful 20 | And contains "install" in the output 21 | 22 | Scenario: Hide install task with configuration in a custom location 23 | And config stage file has line "desc 'Special Task'" 24 | And config stage file has line "task :special_stage_task" 25 | But the configuration is in a custom location 26 | When I run "cap -T" 27 | Then the task is successful 28 | And doesn't contain "special_stage_task" in the output 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License (MIT) 2 | 3 | Copyright (c) 2012-2020 Tom Clements, Lee Hambley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/capistrano/dsl/env.rb: -------------------------------------------------------------------------------- 1 | require "forwardable" 2 | 3 | module Capistrano 4 | module DSL 5 | module Env 6 | extend Forwardable 7 | def_delegators :env, 8 | :configure_backend, :fetch, :set, :set_if_empty, :delete, 9 | :ask, :role, :server, :primary, :validate, :append, 10 | :remove, :dry_run?, :install_plugin, :any?, :is_question?, 11 | :configure_scm, :scm_plugin_installed? 12 | 13 | def roles(*names) 14 | env.roles_for(names.flatten) 15 | end 16 | 17 | def role_properties(*names, &block) 18 | env.role_properties_for(names, &block) 19 | end 20 | 21 | def release_roles(*names) 22 | if names.last.is_a? Hash 23 | names.last[:exclude] = :no_release 24 | else 25 | names << { exclude: :no_release } 26 | end 27 | roles(*names) 28 | end 29 | 30 | def env 31 | Configuration.env 32 | end 33 | 34 | def release_timestamp 35 | env.timestamp.strftime("%Y%m%d%H%M%S") 36 | end 37 | 38 | def asset_timestamp 39 | env.timestamp.strftime("%Y%m%d%H%M.%S") 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/capistrano/templates/Capfile: -------------------------------------------------------------------------------- 1 | # Load DSL and set up stages 2 | require "capistrano/setup" 3 | 4 | # Include default deployment tasks 5 | require "capistrano/deploy" 6 | 7 | # Load the SCM plugin appropriate to your project: 8 | # 9 | # require "capistrano/scm/hg" 10 | # install_plugin Capistrano::SCM::Hg 11 | # or 12 | # require "capistrano/scm/svn" 13 | # install_plugin Capistrano::SCM::Svn 14 | # or 15 | require "capistrano/scm/git" 16 | install_plugin Capistrano::SCM::Git 17 | 18 | # Include tasks from other gems included in your Gemfile 19 | # 20 | # For documentation on these, see for example: 21 | # 22 | # https://github.com/capistrano/rvm 23 | # https://github.com/capistrano/rbenv 24 | # https://github.com/capistrano/chruby 25 | # https://github.com/capistrano/bundler 26 | # https://github.com/capistrano/rails 27 | # https://github.com/capistrano/passenger 28 | # 29 | # require "capistrano/rvm" 30 | # require "capistrano/rbenv" 31 | # require "capistrano/chruby" 32 | # require "capistrano/bundler" 33 | # require "capistrano/rails/assets" 34 | # require "capistrano/rails/migrations" 35 | # require "capistrano/passenger" 36 | 37 | # Load custom tasks from `lib/capistrano/tasks` if you have any defined 38 | Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r } 39 | -------------------------------------------------------------------------------- /docs/documentation/getting-started/before-after/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Before / After Hooks 3 | layout: default 4 | --- 5 | 6 | Where calling on the same task name, executed in order of inclusion 7 | 8 | ```ruby 9 | # call an existing task 10 | before :starting, :ensure_user 11 | 12 | after :finishing, :notify 13 | 14 | 15 | # or define in block 16 | namespace :deploy do 17 | before :starting, :ensure_user do 18 | # 19 | end 20 | 21 | after :finishing, :notify do 22 | # 23 | end 24 | end 25 | ``` 26 | 27 | If it makes sense for your use case (often, that means *generating a file*) 28 | the Rake prerequisite mechanism can be used: 29 | 30 | ```ruby 31 | desc "Create Important File" 32 | file 'important.txt' do |t| 33 | sh "touch #{t.name}" 34 | end 35 | desc "Upload Important File" 36 | task :upload => 'important.txt' do |t| 37 | on roles(:all) do 38 | upload!(t.prerequisites.first, '/tmp') 39 | end 40 | end 41 | ``` 42 | 43 | The final way to call out to other tasks is to simply `invoke()` them: 44 | 45 | ```ruby 46 | namespace :example do 47 | task :one do 48 | on roles(:all) { info "One" } 49 | end 50 | task :two do 51 | invoke "example:one" 52 | on roles(:all) { info "Two" } 53 | end 54 | end 55 | ``` 56 | 57 | This method is widely used. 58 | -------------------------------------------------------------------------------- /docs/documentation/advanced-features/console/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Console 3 | layout: default 4 | --- 5 | 6 | **Note:** Here be dragons. The console is very immature, but it's much more 7 | cleanly architected than previous incarnations and it'll only get better from 8 | here on in. 9 | 10 | Execute arbitrary remote commands, to use this simply add 11 | `require 'capistrano/console'` which will add the necessary tasks to your 12 | environment: 13 | 14 | ```bash 15 | $ bundle exec cap staging console 16 | ``` 17 | 18 | Then, after setting up the server connections, this is how that might look: 19 | 20 | ```bash 21 | $ bundle exec cap production console 22 | capistrano console - enter command to execute on production 23 | production> uptime 24 | INFO [94db8027] Running /usr/bin/env uptime on leehambley@example.com:22 25 | DEBUG [94db8027] Command: /usr/bin/env uptime 26 | DEBUG [94db8027] 17:11:17 up 50 days, 22:31, 1 user, load average: 0.02, 0.02, 0.05 27 | INFO [94db8027] Finished in 0.435 seconds command successful. 28 | production> who 29 | INFO [9ce34809] Running /usr/bin/env who on leehambley@example.com:22 30 | DEBUG [9ce34809] Command: /usr/bin/env who 31 | DEBUG [9ce34809] leehambley pts/0 2013-06-13 17:11 (port-11262.pppoe.wtnet.de) 32 | INFO [9ce34809] Finished in 0.420 seconds command successful. 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/_includes/metrics.html: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | 22 | 31 | -------------------------------------------------------------------------------- /docs/documentation/advanced-features/ptys/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: PTYs 3 | layout: default 4 | --- 5 | 6 | There is a configuration option which asks the backend driver to ask the 7 | remote host to assign the connection a *pty*. A *pty* is a pseudo-terminal, 8 | which in effect means *tell the backend that this is an __interactive__ 9 | session*. This is normally a bad idea. 10 | 11 | Most of the differences are best explained by [this 12 | page](https://github.com/sstephenson/rbenv/wiki/Unix-shell-initialization) 13 | from the author of *rbenv*. 14 | 15 | **When Capistrano makes a connection it is a *non-login*, *non-interactive* 16 | shell. This was not an accident!** 17 | 18 | It's often used as a band aid to cure issues related to RVM and rbenv not 19 | loading login and shell initialisation scripts. In these scenarios RVM and 20 | rbenv are the tools at fault, or at least they are being used incorrectly. 21 | 22 | Whilst, especially in the case of language runtimes (Ruby, Node, Python and 23 | friends in particular) there is a temptation to run multiple versions in 24 | parallel on a single server and to switch between them using environmental 25 | variables, this is an anti-pattern, and symptomatic of bad design (e.g. you're 26 | testing a second version of Ruby in production because your company lacks the 27 | infrastructure to test this in a staging environment). 28 | -------------------------------------------------------------------------------- /docs/documentation/faq/how-can-i-set-capistrano-configuration-paths/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: How can I set Capistrano configuration paths? 3 | layout: default 4 | --- 5 | 6 | Capistrano `config` and `tasks` paths can be explicitly defined, like so: 7 | 8 | Capfile 9 | 10 | ```ruby 11 | # default deploy_config_path is 'config/deploy.rb' 12 | set :deploy_config_path, 'cap/deploy.rb' 13 | # default stage_config_path is 'config/deploy' 14 | set :stage_config_path, 'cap/stages' 15 | 16 | # previous variables MUST be set before 'capistrano/setup' 17 | require 'capistrano/setup' 18 | 19 | # default tasks path is `lib/capistrano/tasks/*.rake` 20 | # (note that you can also change the file extensions) 21 | Dir.glob('cap/tasks/*.rb').each { |r| import r } 22 | ``` 23 | 24 | Here is the corresponding capistrano configuration structure: 25 | 26 | ```bash 27 | ├── Capfile 28 | └── cap 29 | ├── stages 30 | │ ├── production.rb 31 | │ └── staging.rb 32 | ├── tasks 33 | │ └── custom_tasks.rb 34 | └── deploy.rb 35 | ``` 36 | 37 |

38 | Be aware that you will have to provide an absolute path, if you want your "deploy_config_path" to be "capistrano/deploy.rb". 39 | See this issue for more explanations and how to get an absolute path in Ruby. 40 |

41 | -------------------------------------------------------------------------------- /lib/capistrano/defaults.rb: -------------------------------------------------------------------------------- 1 | validate :application do |_key, value| 2 | changed_value = value.gsub(/[^A-Z0-9\.\-]/i, "_") 3 | if value != changed_value 4 | warn %Q(The :application value "#{value}" is invalid!) 5 | warn "Use only letters, numbers, hyphens, dots, and underscores. For example:" 6 | warn " set :application, '#{changed_value}'" 7 | raise Capistrano::ValidationError 8 | end 9 | end 10 | 11 | %i(git_strategy hg_strategy svn_strategy).each do |strategy| 12 | validate(strategy) do |key, _value| 13 | warn( 14 | "[Deprecation Warning] #{key} is deprecated and will be removed in "\ 15 | "Capistrano 3.7.0.\n"\ 16 | "https://github.com/capistrano/capistrano/blob/master/UPGRADING-3.7.md" 17 | ) 18 | end 19 | end 20 | 21 | # We use a special :_default_git value so that SCMResolver can tell whether the 22 | # default has been replaced by the user via `set`. 23 | set_if_empty :scm, Capistrano::Configuration::SCMResolver::DEFAULT_GIT 24 | set_if_empty :branch, "master" 25 | set_if_empty :deploy_to, -> { "/var/www/#{fetch(:application)}" } 26 | set_if_empty :tmp_dir, "/tmp" 27 | 28 | set_if_empty :default_env, {} 29 | set_if_empty :keep_releases, 5 30 | 31 | set_if_empty :format, :airbrussh 32 | set_if_empty :log_level, :debug 33 | 34 | set_if_empty :pty, false 35 | 36 | set_if_empty :local_user, -> { ENV["USER"] || ENV["LOGNAME"] || ENV["USERNAME"] } 37 | -------------------------------------------------------------------------------- /lib/capistrano/doctor/gems_doctor.rb: -------------------------------------------------------------------------------- 1 | require "capistrano/doctor/output_helpers" 2 | 3 | module Capistrano 4 | module Doctor 5 | # Prints table of all Capistrano-related gems and their version numbers. If 6 | # there is a newer version of a gem available, call attention to it. 7 | class GemsDoctor 8 | include Capistrano::Doctor::OutputHelpers 9 | 10 | def call 11 | title("Gems") 12 | table(all_gem_names) do |gem, row| 13 | row.yellow if update_available?(gem) 14 | row << gem 15 | row << installed_gem_version(gem) 16 | row << "(update available)" if update_available?(gem) 17 | end 18 | end 19 | 20 | private 21 | 22 | def installed_gem_version(gem_name) 23 | Gem.loaded_specs[gem_name].version 24 | end 25 | 26 | def update_available?(gem_name) 27 | latest = Gem.latest_version_for(gem_name) 28 | return false if latest.nil? 29 | latest > installed_gem_version(gem_name) 30 | end 31 | 32 | def all_gem_names 33 | core_gem_names + plugin_gem_names 34 | end 35 | 36 | def core_gem_names 37 | %w(capistrano airbrussh rake sshkit net-ssh) & Gem.loaded_specs.keys 38 | end 39 | 40 | def plugin_gem_names 41 | (Gem.loaded_specs.keys - ["capistrano"]).grep(/capistrano/).sort 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/capistrano/scm/tasks/hg.rake: -------------------------------------------------------------------------------- 1 | # TODO: this is nearly identical to git.rake. DRY up? 2 | 3 | # This trick lets us access the Hg plugin within `on` blocks. 4 | hg_plugin = self 5 | 6 | namespace :hg do 7 | desc "Check that the repo is reachable" 8 | task :check do 9 | on release_roles :all do 10 | hg_plugin.check_repo_is_reachable 11 | end 12 | end 13 | 14 | desc "Clone the repo to the cache" 15 | task :clone do 16 | on release_roles :all do 17 | if hg_plugin.repo_mirror_exists? 18 | info t(:mirror_exists, at: repo_path) 19 | else 20 | within deploy_path do 21 | hg_plugin.clone_repo 22 | end 23 | end 24 | end 25 | end 26 | 27 | desc "Pull changes from the remote repo" 28 | task update: :'hg:clone' do 29 | on release_roles :all do 30 | within repo_path do 31 | hg_plugin.update_mirror 32 | end 33 | end 34 | end 35 | 36 | desc "Copy repo to releases" 37 | task create_release: :'hg:update' do 38 | on release_roles :all do 39 | within repo_path do 40 | hg_plugin.archive_to_release_path 41 | end 42 | end 43 | end 44 | 45 | desc "Determine the revision that will be deployed" 46 | task :set_current_revision do 47 | on release_roles :all do 48 | within repo_path do 49 | set :current_revision, hg_plugin.fetch_revision 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/capistrano/scm/tasks/svn.rake: -------------------------------------------------------------------------------- 1 | # TODO: this is nearly identical to git.rake. DRY up? 2 | 3 | # This trick lets us access the Svn plugin within `on` blocks. 4 | svn_plugin = self 5 | 6 | namespace :svn do 7 | desc "Check that the repo is reachable" 8 | task :check do 9 | on release_roles :all do 10 | svn_plugin.check_repo_is_reachable 11 | end 12 | end 13 | 14 | desc "Clone the repo to the cache" 15 | task :clone do 16 | on release_roles :all do 17 | if svn_plugin.repo_mirror_exists? 18 | info t(:mirror_exists, at: repo_path) 19 | else 20 | within deploy_path do 21 | svn_plugin.clone_repo 22 | end 23 | end 24 | end 25 | end 26 | 27 | desc "Pull changes from the remote repo" 28 | task update: :'svn:clone' do 29 | on release_roles :all do 30 | within repo_path do 31 | svn_plugin.update_mirror 32 | end 33 | end 34 | end 35 | 36 | desc "Copy repo to releases" 37 | task create_release: :'svn:update' do 38 | on release_roles :all do 39 | within repo_path do 40 | svn_plugin.archive_to_release_path 41 | end 42 | end 43 | end 44 | 45 | desc "Determine the revision that will be deployed" 46 | task :set_current_revision do 47 | on release_roles :all do 48 | within repo_path do 49 | set :current_revision, svn_plugin.fetch_revision 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /docs/assets/js/foundation/foundation.alerts.js: -------------------------------------------------------------------------------- 1 | /*jslint unparam: true, browser: true, indent: 2 */ 2 | 3 | ;(function ($, window, document, undefined) { 4 | 'use strict'; 5 | 6 | Foundation.libs.alerts = { 7 | name : 'alerts', 8 | 9 | version : '4.2.2', 10 | 11 | settings : { 12 | speed: 300, // fade out speed 13 | callback: function (){} 14 | }, 15 | 16 | init : function (scope, method, options) { 17 | this.scope = scope || this.scope; 18 | 19 | if (typeof method === 'object') { 20 | $.extend(true, this.settings, method); 21 | } 22 | 23 | if (typeof method !== 'string') { 24 | if (!this.settings.init) { this.events(); } 25 | 26 | return this.settings.init; 27 | } else { 28 | return this[method].call(this, options); 29 | } 30 | }, 31 | 32 | events : function () { 33 | var self = this; 34 | 35 | $(this.scope).on('click.fndtn.alerts', '[data-alert] a.close', function (e) { 36 | e.preventDefault(); 37 | $(this).closest("[data-alert]").fadeOut(self.speed, function () { 38 | $(this).remove(); 39 | self.settings.callback(); 40 | }); 41 | }); 42 | 43 | this.settings.init = true; 44 | }, 45 | 46 | off : function () { 47 | $(this.scope).off('.fndtn.alerts'); 48 | }, 49 | 50 | reflow : function () {} 51 | }; 52 | }(Foundation.zj, this, this.document)); 53 | -------------------------------------------------------------------------------- /spec/lib/capistrano/doctor/environment_doctor_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "capistrano/doctor/environment_doctor" 3 | 4 | module Capistrano 5 | module Doctor 6 | describe EnvironmentDoctor do 7 | let(:doc) { EnvironmentDoctor.new } 8 | 9 | it "prints using 4-space indentation" do 10 | expect { doc.call }.to output(/^ {4}/).to_stdout 11 | end 12 | 13 | it "prints the Ruby version" do 14 | expect { doc.call }.to\ 15 | output(/#{Regexp.quote(RUBY_DESCRIPTION)}/).to_stdout 16 | end 17 | 18 | it "prints the Rubygems version" do 19 | expect { doc.call }.to output(/#{Regexp.quote(Gem::VERSION)}/).to_stdout 20 | end 21 | 22 | describe "Rake" do 23 | before do 24 | load File.expand_path("../../../../../lib/capistrano/doctor.rb", 25 | __FILE__) 26 | end 27 | 28 | after do 29 | Rake::Task.clear 30 | end 31 | 32 | it "has an doctor:environment task that calls EnvironmentDoctor", capture_io: true do 33 | EnvironmentDoctor.any_instance.expects(:call) 34 | Rake::Task["doctor:environment"].invoke 35 | end 36 | 37 | it "has a doctor task that depends on doctor:environment" do 38 | expect(Rake::Task["doctor"].prerequisites).to \ 39 | include("doctor:environment") 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /features/support/docker_gateway.rb: -------------------------------------------------------------------------------- 1 | # Ensure Docker container is completely stopped when Ruby exits. 2 | at_exit do 3 | DockerGateway.new.stop 4 | end 5 | 6 | # Manages the Docker-based SSH server that is declared in docker-compose.yml. 7 | class DockerGateway 8 | def initialize(log_proc=$stderr.method(:puts)) 9 | @log_proc = log_proc 10 | end 11 | 12 | def start 13 | run_compose_command("up -d") 14 | end 15 | 16 | def stop 17 | run_compose_command("down") 18 | end 19 | 20 | def run_shell_command(command) 21 | run_compose_command("exec ssh_server /bin/bash -c #{command.shellescape}") 22 | end 23 | 24 | private 25 | 26 | def run_compose_command(command) 27 | log "[docker compose] #{command}" 28 | stdout, stderr, status = Open3.popen3("docker compose #{command}") do |stdin, stdout, stderr, wait_threads| 29 | stdin << "" 30 | stdin.close 31 | out = Thread.new { read_lines(stdout, &$stdout.method(:puts)) } 32 | err = Thread.new { stderr.read } 33 | [out.value, err.value.to_s, wait_threads.value] 34 | end 35 | 36 | (stdout + stderr).each_line { |line| log "[docker compose] #{line}" } 37 | 38 | [stdout, stderr, status] 39 | end 40 | 41 | def read_lines(io) 42 | buffer = + "" 43 | while (line = io.gets) 44 | buffer << line 45 | yield line 46 | end 47 | buffer 48 | end 49 | 50 | def log(message) 51 | @log_proc.call(message) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /docs/documentation/getting-started/tasks/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tasks 3 | layout: default 4 | --- 5 | 6 | ```ruby 7 | server 'example.com', roles: [:web, :app] 8 | server 'example.org', roles: [:db, :workers] 9 | desc "Report Uptimes" 10 | task :uptime do 11 | on roles(:all) do |host| 12 | execute :any_command, "with args", :here, "and here" 13 | info "Host #{host} (#{host.roles.to_a.join(', ')}):\t#{capture(:uptime)}" 14 | end 15 | end 16 | ``` 17 | 18 | **Note**: 19 | 20 | **tl;dr**: `execute(:bundle, :install)` and `execute('bundle install')` don't behave identically! 21 | 22 | `execute()` has a subtle behaviour. When calling `within './directory' { execute(:bundle, :install) }` for example, the first argument to `execute()` is a *Stringish* with ***no whitespace***. This allows the command to pass through the [SSHKit::CommandMap](https://github.com/capistrano/sshkit#the-command-map) which enables a number of powerful features. 23 | 24 | When the first argument to `execute()` contains whitespace, for example `within './directory' { execute('bundle install') }` (or when using a heredoc), neither Capistrano, nor SSHKit can reliably predict how it should be shell escaped, and thus cannot perform any context, or command mapping, that means that the `within(){}` (as well as `with()`, `as()`, etc) have no effect. There have been a few attempts to resolve this, but we don't consider it a bug although we acknowledge that it might be a little counter intuitive. 25 | -------------------------------------------------------------------------------- /docs/_includes/footer.html: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/install.rake: -------------------------------------------------------------------------------- 1 | require "erb" 2 | require "pathname" 3 | desc "Install Capistrano, cap install STAGES=staging,production" 4 | task :install do 5 | envs = ENV["STAGES"] || "staging,production" 6 | 7 | tasks_dir = Pathname.new("lib/capistrano/tasks") 8 | config_dir = Pathname.new("config") 9 | deploy_dir = config_dir.join("deploy") 10 | 11 | deploy_rb = File.expand_path("../../templates/deploy.rb.erb", __FILE__) 12 | stage_rb = File.expand_path("../../templates/stage.rb.erb", __FILE__) 13 | capfile = File.expand_path("../../templates/Capfile", __FILE__) 14 | 15 | mkdir_p deploy_dir 16 | 17 | entries = [{ template: deploy_rb, file: config_dir.join("deploy.rb") }] 18 | entries += envs.split(",").map { |stage| { template: stage_rb, file: deploy_dir.join("#{stage}.rb") } } 19 | 20 | entries.each do |entry| 21 | if File.exist?(entry[:file]) 22 | warn "[skip] #{entry[:file]} already exists" 23 | else 24 | File.open(entry[:file], "w+") do |f| 25 | f.write(ERB.new(File.read(entry[:template])).result(binding)) 26 | puts I18n.t(:written_file, scope: :capistrano, file: entry[:file]) 27 | end 28 | end 29 | end 30 | 31 | mkdir_p tasks_dir 32 | 33 | if File.exist?("Capfile") 34 | warn "[skip] Capfile already exists" 35 | else 36 | FileUtils.cp(capfile, "Capfile") 37 | puts I18n.t(:written_file, scope: :capistrano, file: "Capfile") 38 | end 39 | 40 | puts I18n.t :capified, scope: :capistrano 41 | end 42 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/framework.rake: -------------------------------------------------------------------------------- 1 | namespace :deploy do 2 | desc "Start a deployment, make sure server(s) ready." 3 | task :starting do 4 | end 5 | 6 | desc "Started" 7 | task :started do 8 | end 9 | 10 | desc "Update server(s) by setting up a new release." 11 | task :updating do 12 | end 13 | 14 | desc "Updated" 15 | task :updated do 16 | end 17 | 18 | desc "Revert server(s) to previous release." 19 | task :reverting do 20 | end 21 | 22 | desc "Reverted" 23 | task :reverted do 24 | end 25 | 26 | desc "Publish the release." 27 | task :publishing do 28 | end 29 | 30 | desc "Published" 31 | task :published do 32 | end 33 | 34 | desc "Finish the deployment, clean up server(s)." 35 | task :finishing do 36 | end 37 | 38 | desc "Finish the rollback, clean up server(s)." 39 | task :finishing_rollback do 40 | end 41 | 42 | desc "Finished" 43 | task :finished do 44 | end 45 | 46 | desc "Rollback to previous release." 47 | task :rollback do 48 | %w{ starting started 49 | reverting reverted 50 | publishing published 51 | finishing_rollback finished }.each do |task| 52 | invoke "deploy:#{task}" 53 | end 54 | end 55 | end 56 | 57 | desc "Deploy a new release." 58 | task :deploy do 59 | set(:deploying, true) 60 | %w{ starting started 61 | updating updated 62 | publishing published 63 | finishing finished }.each do |task| 64 | invoke "deploy:#{task}" 65 | end 66 | end 67 | task default: :deploy 68 | -------------------------------------------------------------------------------- /lib/capistrano/templates/deploy.rb.erb: -------------------------------------------------------------------------------- 1 | # config valid for current version and patch releases of Capistrano 2 | lock "~> <%= Capistrano::VERSION %>" 3 | 4 | set :application, "my_app_name" 5 | set :repo_url, "git@example.com:me/my_repo.git" 6 | 7 | # Default branch is :master 8 | # ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp 9 | 10 | # Default deploy_to directory is /var/www/my_app_name 11 | # set :deploy_to, "/var/www/my_app_name" 12 | 13 | # Default value for :format is :airbrussh. 14 | # set :format, :airbrussh 15 | 16 | # You can configure the Airbrussh format using :format_options. 17 | # These are the defaults. 18 | # set :format_options, command_output: true, log_file: "log/capistrano.log", color: :auto, truncate: :auto 19 | 20 | # Default value for :pty is false 21 | # set :pty, true 22 | 23 | # Default value for :linked_files is [] 24 | # append :linked_files, "config/database.yml", 'config/master.key' 25 | 26 | # Default value for linked_dirs is [] 27 | # append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system", "vendor", "storage" 28 | 29 | # Default value for default_env is {} 30 | # set :default_env, { path: "/opt/ruby/bin:$PATH" } 31 | 32 | # Default value for local_user is ENV['USER'] 33 | # set :local_user, -> { `git config user.name`.chomp } 34 | 35 | # Default value for keep_releases is 5 36 | # set :keep_releases, 5 37 | 38 | # Uncomment the following to require manually verifying the host key before first deploy. 39 | # set :ssh_options, verify_host_key: :secure 40 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | DisplayCopNames: true 3 | DisplayStyleGuide: true 4 | TargetRubyVersion: 2.0 5 | 6 | Lint/AmbiguousBlockAssociation: 7 | Enabled: 8 | false 9 | Metrics/BlockLength: 10 | Exclude: 11 | - "*.gemspec" 12 | - "spec/**/*" 13 | - "lib/**/*.rake" 14 | Style/BarePercentLiterals: 15 | EnforcedStyle: percent_q 16 | Style/BracesAroundHashParameters: 17 | Exclude: 18 | - spec/integration/dsl_spec.rb 19 | Style/ClassAndModuleChildren: 20 | Enabled: false 21 | Style/DoubleNegation: 22 | Enabled: false 23 | Style/IndentHeredoc: 24 | Enabled: false 25 | Style/SpaceAroundEqualsInParameterDefault: 26 | EnforcedStyle: no_space 27 | Style/StringLiterals: 28 | EnforcedStyle: double_quotes 29 | Style/TrivialAccessors: 30 | AllowPredicates: true 31 | Style/PercentLiteralDelimiters: 32 | Enabled: false 33 | Style/SingleLineBlockParams: 34 | Enabled: false 35 | Style/ModuleFunction: 36 | Enabled: false 37 | 38 | # Enable someday 39 | Style/Documentation: 40 | Enabled: false 41 | 42 | # Needs refactors 43 | Metrics/PerceivedComplexity: 44 | Enabled: false 45 | Metrics/CyclomaticComplexity: 46 | Enabled: false 47 | Metrics/MethodLength: 48 | Enabled: false 49 | Style/PredicateName: 50 | Enabled: false 51 | Metrics/LineLength: 52 | Enabled: false 53 | Metrics/AbcSize: 54 | Enabled: false 55 | Style/PerlBackrefs: 56 | Enabled: false 57 | Metrics/ClassLength: 58 | Enabled: false 59 | Metrics/ModuleLength: 60 | Enabled: false 61 | Style/AccessorMethodName: 62 | Enabled: false 63 | -------------------------------------------------------------------------------- /capistrano.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | lib = File.expand_path("../lib", __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require "capistrano/version" 6 | 7 | Gem::Specification.new do |gem| 8 | gem.name = "capistrano" 9 | gem.version = Capistrano::VERSION 10 | gem.authors = ["Tom Clements", "Lee Hambley"] 11 | gem.email = ["seenmyfate@gmail.com", "lee.hambley@gmail.com"] 12 | gem.description = "Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH." 13 | gem.summary = "Capistrano - Welcome to easy deployment with Ruby over SSH" 14 | gem.homepage = "https://capistranorb.com/" 15 | gem.metadata = { 16 | "bug_tracker_uri" => "https://github.com/capistrano/capistrano/issues", 17 | "changelog_uri" => "https://github.com/capistrano/capistrano/releases", 18 | "source_code_uri" => "https://github.com/capistrano/capistrano", 19 | "homepage_uri" => "https://capistranorb.com/", 20 | "documentation_uri" => "https://capistranorb.com/" 21 | } 22 | gem.files = `git ls-files -z`.split("\x0").reject { |f| f =~ /^docs/ } 23 | gem.executables = %w(cap capify) 24 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 25 | gem.require_paths = ["lib"] 26 | 27 | gem.licenses = ["MIT"] 28 | 29 | gem.required_ruby_version = ">= 2.0" 30 | gem.add_dependency "airbrussh", ">= 1.0.0" 31 | gem.add_dependency "i18n" 32 | gem.add_dependency "rake", ">= 10.0.0" 33 | gem.add_dependency "sshkit", ">= 1.9.0" 34 | end 35 | -------------------------------------------------------------------------------- /spec/lib/capistrano/doctor/output_helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "capistrano/doctor/output_helpers" 3 | 4 | module Capistrano 5 | module Doctor 6 | describe OutputHelpers do 7 | include OutputHelpers 8 | 9 | # Force color for the purpose of these tests 10 | before { ENV.stubs(:[]).with("SSHKIT_COLOR").returns("1") } 11 | 12 | it "prints titles in blue with newlines and without indentation" do 13 | expect { title("Hello!") }.to\ 14 | output("\e[0;34;49m\nHello!\n\e[0m\n").to_stdout 15 | end 16 | 17 | it "prints warnings in yellow with 4-space indentation" do 18 | expect { warning("Yikes!") }.to\ 19 | output(" \e[0;33;49mYikes!\e[0m\n").to_stdout 20 | end 21 | 22 | it "overrides puts to indent 4 spaces per line" do 23 | expect { puts("one\ntwo") }.to output(" one\n two\n").to_stdout 24 | end 25 | 26 | it "formats tables with indent, aligned columns and per-row color" do 27 | data = [ 28 | ["one", ".", "1"], 29 | ["two", "..", "2"], 30 | ["three", "...", "3"] 31 | ] 32 | block = proc do |record, row| 33 | row.yellow if record.first == "two" 34 | row << record[0] 35 | row << record[1] 36 | row << record[2] 37 | end 38 | expected_output = <<-OUT 39 | one . 1 40 | \e[0;33;49mtwo .. 2\e[0m 41 | three ... 3 42 | OUT 43 | expect { table(data, &block) }.to output(expected_output).to_stdout 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Specify your gem's dependencies in capistrano.gemspec 4 | gemspec 5 | 6 | gem "mocha" 7 | gem "rspec" 8 | gem "rspec-core", "~> 3.4.4" 9 | 10 | group :cucumber do 11 | # Latest versions of cucumber don't support Ruby < 2.1 12 | # rubocop:disable Bundler/DuplicatedGem 13 | if Gem::Requirement.new("< 2.1").satisfied_by?(Gem::Version.new(RUBY_VERSION)) 14 | gem "cucumber", "< 3.0.1" 15 | else 16 | gem "cucumber" 17 | end 18 | # rubocop:enable Bundler/DuplicatedGem 19 | end 20 | 21 | # Latest versions of net-ssh don't support Ruby < 2.2.6 22 | if Gem::Requirement.new("< 2.2.6").satisfied_by?(Gem::Version.new(RUBY_VERSION)) 23 | gem "net-ssh", "< 5.0.0" 24 | end 25 | 26 | # Latest versions of public_suffix don't support Ruby < 2.1 27 | if Gem::Requirement.new("< 2.1").satisfied_by?(Gem::Version.new(RUBY_VERSION)) 28 | gem "public_suffix", "< 3.0.0" 29 | end 30 | 31 | # Latest versions of i18n don't support Ruby < 2.4 32 | if Gem::Requirement.new("< 2.4").satisfied_by?(Gem::Version.new(RUBY_VERSION)) 33 | gem "i18n", "< 1.3.0" 34 | end 35 | 36 | # Latest versions of rake don't support Ruby < 2.2 37 | if Gem::Requirement.new("< 2.2").satisfied_by?(Gem::Version.new(RUBY_VERSION)) 38 | gem "rake", "< 13.0.0" 39 | end 40 | 41 | # We only run rubocop and its dependencies on a new-ish ruby; no need to install them otherwise 42 | if Gem::Requirement.new("> 2.4").satisfied_by?(Gem::Version.new(RUBY_VERSION)) 43 | gem "base64" 44 | gem "psych", "< 4" # Ensures rubocop works on Ruby 3.1 45 | gem "racc" 46 | gem "rubocop", "0.48.1" 47 | end 48 | -------------------------------------------------------------------------------- /lib/capistrano/scm/hg.rb: -------------------------------------------------------------------------------- 1 | require "capistrano/scm/plugin" 2 | require "securerandom" 3 | 4 | class Capistrano::SCM::Hg < Capistrano::SCM::Plugin 5 | def register_hooks 6 | after "deploy:new_release_path", "hg:create_release" 7 | before "deploy:check", "hg:check" 8 | before "deploy:set_current_revision", "hg:set_current_revision" 9 | end 10 | 11 | def define_tasks 12 | eval_rakefile File.expand_path("../tasks/hg.rake", __FILE__) 13 | end 14 | 15 | def hg(*args) 16 | args.unshift(:hg) 17 | backend.execute(*args) 18 | end 19 | 20 | def repo_mirror_exists? 21 | backend.test " [ -d #{repo_path}/.hg ] " 22 | end 23 | 24 | def check_repo_is_reachable 25 | hg "id", repo_url 26 | end 27 | 28 | def clone_repo 29 | hg "clone", "--noupdate", repo_url, repo_path.to_s 30 | end 31 | 32 | def update_mirror 33 | hg "pull" 34 | end 35 | 36 | def archive_to_release_path 37 | if (tree = fetch(:repo_tree)) 38 | tree = tree.slice %r#^/?(.*?)/?$#, 1 39 | components = tree.split("/").size 40 | temp_tar = "#{fetch(:tmp_dir)}/#{SecureRandom.hex(10)}.tar" 41 | 42 | hg "archive -p . -I", tree, "--rev", fetch(:branch), temp_tar 43 | 44 | backend.execute :mkdir, "-p", release_path 45 | backend.execute :tar, "-x --strip-components #{components} -f", temp_tar, "-C", release_path 46 | backend.execute :rm, temp_tar 47 | else 48 | hg "archive", release_path, "--rev", fetch(:branch) 49 | end 50 | end 51 | 52 | def fetch_revision 53 | backend.capture(:hg, "log --rev #{fetch(:branch)} --template \"{node}\n\"") 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /docs/documentation/tasks/rails/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Custom Rails Tasks 3 | layout: default 4 | --- 5 | 6 | Many of these tasks probably require [Capistrano::Rails](https://github.com/capistrano/rails). 7 | 8 | ### Run arbitrary rake tasks from environment variables 9 | 10 | From [Capistrano/Rails PR #209](https://github.com/capistrano/rails/pull/209) 11 | 12 | ```ruby 13 | namespace :deploy do 14 | desc 'Runs any rake task, cap deploy:rake task=db:rollback' 15 | task rake: [:set_rails_env] do 16 | on release_roles([:db]) do 17 | within release_path do 18 | with rails_env: fetch(:rails_env) do 19 | execute :rake, ENV['task'] 20 | end 21 | end 22 | end 23 | end 24 | end 25 | ``` 26 | 27 | Passes in the rake task to be run via an environment variable. Also a simple example of running a rake task on the server. 28 | 29 | ```bash 30 | bundle exec cap production deploy:rake task=db:seed 31 | ``` 32 | 33 | 34 | ### Conditional migrations 35 | 36 | Arising from [Capistrano/Rails issue #199](https://github.com/capistrano/rails/issues/199) 37 | 38 | A frequent issue on deploy are slow migrations which involve downtime. In this case, you often want to run the migrations conditionally, where the main deploy doesn't run them, but you can do so manually at a better point. To do so, you could put the following in your `Capfile`: 39 | 40 | ```ruby 41 | require 'capistrano/rails/migrations' if ENV['RUN_MIGRATIONS'] 42 | ``` 43 | 44 | Now the migrations do not run by default, but they will run with the following command: 45 | 46 | ```bash 47 | RUN_MIGRATIONS=1 bundle exec cap production deploy:migrate 48 | ``` 49 | -------------------------------------------------------------------------------- /spec/lib/capistrano/configuration/scm_resolver_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "capistrano/scm" 3 | 4 | module Capistrano 5 | class Configuration 6 | describe SCMResolver do 7 | include Capistrano::DSL 8 | 9 | let(:resolver) { SCMResolver.new } 10 | 11 | before do 12 | Rake::Task.define_task("deploy:check") 13 | Rake::Task.define_task("deploy:new_release_path") 14 | Rake::Task.define_task("deploy:set_current_revision") 15 | Rake::Task.define_task("deploy:set_current_revision_time") 16 | set :scm, SCMResolver::DEFAULT_GIT 17 | end 18 | 19 | after do 20 | Rake::Task.clear 21 | Capistrano::Configuration.reset! 22 | end 23 | 24 | context "default scm, no plugin installed" do 25 | it "emits a warning" do 26 | expect { resolver.resolve }.to output(/will not load the git scm/i).to_stderr 27 | end 28 | 29 | it "activates the git scm", capture_io: true do 30 | resolver.resolve 31 | expect(Rake::Task["git:wrapper"]).not_to be_nil 32 | end 33 | 34 | it "sets :scm to :git", capture_io: true do 35 | resolver.resolve 36 | expect(fetch(:scm)).to eq(:git) 37 | end 38 | end 39 | 40 | context "default scm, git plugin installed" do 41 | before do 42 | install_plugin Capistrano::SCM::Git 43 | end 44 | 45 | it "emits no warning" do 46 | expect { resolver.resolve }.not_to output.to_stderr 47 | end 48 | 49 | it "deletes :scm" do 50 | resolver.resolve 51 | expect(fetch(:scm)).to be_nil 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /docs/documentation/advanced-features/overriding-capistrano-tasks/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overriding Capistrano tasks 3 | layout: default 4 | --- 5 | 6 | When re-defining a task in Capistrano v2, the original task was replaced. The 7 | Rake DSL on which Capistrano v3 is built is additive however, which means that 8 | given the following definitions 9 | 10 | ```ruby 11 | task :foo do 12 | puts "foo" 13 | end 14 | 15 | task :foo do 16 | puts "bar" 17 | end 18 | ``` 19 | 20 | Will print both `foo` and `bar`. 21 | 22 | But it is also possible to completely clear a task and then re-defining it 23 | from scratch. A `Rake::Task` provides the `clear` method for this, which 24 | internally performs three separate actions: 25 | 26 | - `clear_prerequisites` 27 | - `clear_actions` 28 | - `clear_comments` 29 | 30 | Clearing the prerequisites (i.e. any dependencies that may have been defined 31 | for a task) is probably not what you want, though. Let's say, for example, 32 | that you want to re-define the `deploy:revert_release` task, which is defined 33 | as follows: 34 | 35 | ```ruby 36 | task :revert_release => :rollback_release_path do 37 | # ... 38 | end 39 | ``` 40 | 41 | Calling `clear` on this task and then re-defining it results in 42 | `rollback_release_path` never being called, thus breaking rollback behavior. 43 | 44 | Under most circumstances, you will simply want to use `clear_actions`, which 45 | removes the specified task's behaviour, but does not alter it's dependencies 46 | or comments: 47 | 48 | ```ruby 49 | task :init do 50 | puts "init" 51 | end 52 | 53 | task :foo => :init do 54 | puts "foo" 55 | end 56 | 57 | Rake::Task["foo"].clear_actions 58 | task :foo do 59 | puts "bar" 60 | end 61 | ``` 62 | 63 | Running the `foo` task will print 64 | 65 | ```ruby 66 | init 67 | bar 68 | ``` 69 | 70 | --- 71 | -------------------------------------------------------------------------------- /lib/capistrano/scm/svn.rb: -------------------------------------------------------------------------------- 1 | require "capistrano/scm/plugin" 2 | 3 | class Capistrano::SCM::Svn < Capistrano::SCM::Plugin 4 | def register_hooks 5 | after "deploy:new_release_path", "svn:create_release" 6 | before "deploy:check", "svn:check" 7 | before "deploy:set_current_revision", "svn:set_current_revision" 8 | end 9 | 10 | def define_tasks 11 | eval_rakefile File.expand_path("../tasks/svn.rake", __FILE__) 12 | end 13 | 14 | def svn(*args) 15 | args.unshift(:svn) 16 | args.push "--username #{fetch(:svn_username)}" if fetch(:svn_username) 17 | args.push "--password #{fetch(:svn_password)}" if fetch(:svn_password) 18 | args.push "--revision #{fetch(:svn_revision)}" if fetch(:svn_revision) 19 | backend.execute(*args) 20 | end 21 | 22 | def repo_mirror_exists? 23 | backend.test " [ -d #{repo_path}/.svn ] " 24 | end 25 | 26 | def check_repo_is_reachable 27 | svn_username = fetch(:svn_username) ? "--username #{fetch(:svn_username)}" : "" 28 | svn_password = fetch(:svn_password) ? "--password #{fetch(:svn_password)}" : "" 29 | backend.test :svn, :info, repo_url, svn_username, svn_password 30 | end 31 | 32 | def clone_repo 33 | svn :checkout, repo_url, repo_path.to_s 34 | end 35 | 36 | def update_mirror 37 | # Switch the repository URL if necessary. 38 | repo_mirror_url = fetch_repo_mirror_url 39 | svn :switch, repo_url unless repo_mirror_url == repo_url 40 | svn :update 41 | end 42 | 43 | def archive_to_release_path 44 | svn :export, "--force", ".", release_path 45 | end 46 | 47 | def fetch_revision 48 | backend.capture(:svnversion, repo_path.to_s) 49 | end 50 | 51 | def fetch_repo_mirror_url 52 | backend.capture(:svn, :info, repo_path.to_s).each_line do |line| 53 | return $1 if /\AURL: (.*)\n\z/ =~ line 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/capistrano/configuration/plugin_installer.rb: -------------------------------------------------------------------------------- 1 | # Encapsulates the logic for installing plugins into Capistrano. Plugins must 2 | # simply conform to a basic API; the PluginInstaller takes care of invoking the 3 | # API at appropriate times. 4 | # 5 | # This class is not used directly; instead it is typically accessed via the 6 | # `install_plugin` method of the Capistrano DSL. 7 | # 8 | module Capistrano 9 | class Configuration 10 | class PluginInstaller 11 | # "Installs" a Plugin into Capistrano by loading its tasks, hooks, and 12 | # defaults at the appropriate time. The hooks in particular can be 13 | # skipped, if you want full control over when and how the plugin's tasks 14 | # are executed. Simply pass `load_hooks:false` to opt out. 15 | # 16 | # The plugin class or instance may be provided. These are equivalent: 17 | # 18 | # install(Capistrano::SCM::Git) 19 | # install(Capistrano::SCM::Git.new) 20 | # 21 | # Note that the :load_immediately flag is for internal use only and will 22 | # be removed in an upcoming release. 23 | # 24 | def install(plugin, load_hooks: true, load_immediately: false) 25 | plugin = plugin.is_a?(Class) ? plugin.new : plugin 26 | 27 | plugin.define_tasks 28 | plugin.register_hooks if load_hooks 29 | @scm_installed ||= provides_scm?(plugin) 30 | 31 | if load_immediately 32 | plugin.set_defaults 33 | else 34 | Rake::Task.define_task("load:defaults") do 35 | plugin.set_defaults 36 | end 37 | end 38 | end 39 | 40 | def scm_installed? 41 | @scm_installed 42 | end 43 | 44 | private 45 | 46 | def provides_scm?(plugin) 47 | plugin.respond_to?(:scm?) && plugin.scm? 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/capistrano/dsl/task_enhancements.rb: -------------------------------------------------------------------------------- 1 | require "capistrano/upload_task" 2 | 3 | module Capistrano 4 | module TaskEnhancements 5 | def before(task, prerequisite, *args, &block) 6 | prerequisite = Rake::Task.define_task(prerequisite, *args, &block) if block_given? 7 | Rake::Task[task].enhance [prerequisite] 8 | end 9 | 10 | def after(task, post_task, *args, &block) 11 | Rake::Task.define_task(post_task, *args, &block) if block_given? 12 | task = Rake::Task[task] 13 | task.enhance do 14 | post = Rake.application.lookup(post_task, task.scope) 15 | raise ArgumentError, "Task #{post_task.inspect} not found" unless post 16 | post.invoke 17 | end 18 | end 19 | 20 | def define_remote_file_task(task, target_roles) 21 | Capistrano::UploadTask.define_task(task) do |t| 22 | prerequisite_file = t.prerequisites.first 23 | file = shared_path.join(t.name) 24 | 25 | on roles(target_roles) do 26 | unless test "[ -f #{file.to_s.shellescape} ]" 27 | info "Uploading #{prerequisite_file} to #{file}" 28 | upload! File.open(prerequisite_file), file 29 | end 30 | end 31 | end 32 | end 33 | 34 | def ensure_stage 35 | Rake::Task.define_task(:ensure_stage) do 36 | unless stage_set? 37 | puts t(:stage_not_set) 38 | exit 1 39 | end 40 | end 41 | end 42 | 43 | def tasks_without_stage_dependency 44 | stages + default_tasks 45 | end 46 | 47 | def default_tasks 48 | %w{install} 49 | end 50 | 51 | def exit_deploy_because_of_exception(ex) 52 | warn t(:deploy_failed, ex: ex.message) 53 | invoke "deploy:failed" 54 | exit(false) 55 | end 56 | 57 | def deploying? 58 | fetch(:deploying, false) 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /docs/assets/css/github.css: -------------------------------------------------------------------------------- 1 | /** 2 | * GitHub theme 3 | * 4 | * @author Craig Campbell 5 | * @version 1.0.4 6 | */ 7 | pre { 8 | border: 1px solid #ccc; 9 | word-wrap: break-word; 10 | padding: 6px 10px; 11 | line-height: 19px; 12 | margin-bottom: 20px; 13 | } 14 | 15 | code { 16 | border: 1px solid #eaeaea; 17 | margin: 0px 2px; 18 | padding: 0px 5px; 19 | font-size: 12px; 20 | } 21 | 22 | pre code { 23 | border: 0px; 24 | padding: 0px; 25 | margin: 0px; 26 | -moz-border-radius: 0px; 27 | -webkit-border-radius: 0px; 28 | border-radius: 0px; 29 | } 30 | 31 | pre, code { 32 | font-family: Consolas, 'Liberation Mono', Courier, monospace; 33 | color: #333; 34 | background: #f8f8f8; 35 | -moz-border-radius: 3px; 36 | -webkit-border-radius: 3px; 37 | border-radius: 3px; 38 | } 39 | 40 | pre, pre code { 41 | font-size: 13px; 42 | } 43 | 44 | pre .comment { 45 | color: #998; 46 | } 47 | 48 | pre .support { 49 | color: #0086B3; 50 | } 51 | 52 | pre .tag, pre .tag-name { 53 | color: navy; 54 | } 55 | 56 | pre .keyword, pre .css-property, pre .vendor-prefix, pre .sass, pre .class, pre .id, pre .css-value, pre .entity.function, pre .storage.function { 57 | font-weight: bold; 58 | } 59 | 60 | pre .css-property, pre .css-value, pre .vendor-prefix, pre .support.namespace { 61 | color: #333; 62 | } 63 | 64 | pre .constant.numeric, pre .keyword.unit, pre .hex-color { 65 | font-weight: normal; 66 | color: #099; 67 | } 68 | 69 | pre .entity.class { 70 | color: #458; 71 | } 72 | 73 | pre .entity.id, pre .entity.function { 74 | color: #900; 75 | } 76 | 77 | pre .attribute, pre .variable { 78 | color: teal; 79 | } 80 | 81 | pre .string, pre .support.value { 82 | font-weight: normal; 83 | color: #d14; 84 | } 85 | 86 | pre .regexp { 87 | color: #009926; 88 | } 89 | -------------------------------------------------------------------------------- /docs/documentation/getting-started/user-input/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: User Input 3 | layout: default 4 | --- 5 | 6 | User input can be required in a task or during configuration: 7 | 8 | ```ruby 9 | # used in a configuration 10 | ask(:database_name, "default_database_name") 11 | 12 | # used in a task 13 | desc "Ask about breakfast" 14 | task :breakfast do 15 | ask(:breakfast, "pancakes") 16 | on roles(:all) do |h| 17 | execute "echo \"$(whoami) wants #{fetch(:breakfast)} for breakfast!\"" 18 | end 19 | end 20 | ``` 21 | 22 | When using `ask` to get user input, you can pass `echo: false` to prevent the 23 | input from being displayed. This option should be used to ask the user for 24 | passwords and other sensitive data during a deploy run. 25 | 26 | ```ruby 27 | ask(:database_password, 'default_password', echo: false) 28 | ``` 29 | 30 | 31 | The symbol passed as a parameter will be printed as text for the user and the 32 | input will be saved to this variable: 33 | 34 | ```ruby 35 | ask(:database_encoding, 'UTF-8') 36 | # Please enter :database_encoding (UTF-8): 37 | 38 | fetch(:database_encoding) 39 | # => contains the user input (or the default) 40 | # once the above line got executed 41 | ``` 42 | 43 | 44 | You can use `ask` to set a server- or role-specific configuration variable. 45 | 46 | ```ruby 47 | ask(:password, nil) 48 | server 'example.com', user: 'ssh_user_name', port: 22, password: fetch(:password), roles: %w{web app db} 49 | ``` 50 | 51 | 52 | You can also show your own message by using `prompt` option: 53 | 54 | ```ruby 55 | ask(:breakfast, "pancakes", prompt: "What's for breakfast?") 56 | ``` 57 | 58 | **Important!** `ask` will not prompt the user immediately. The question is 59 | deferred until the first time `fetch` is used to obtain the setting. That means 60 | you can `ask` for many variables, but only the variables used by your task(s) 61 | will actually prompt the user for input. 62 | -------------------------------------------------------------------------------- /lib/capistrano/configuration/question.rb: -------------------------------------------------------------------------------- 1 | module Capistrano 2 | class Configuration 3 | class Question 4 | def initialize(key, default, options={}) 5 | @key = key 6 | @default = default 7 | @options = options 8 | end 9 | 10 | def call 11 | ask_question 12 | value_or_default 13 | end 14 | 15 | private 16 | 17 | attr_reader :key, :default, :options 18 | 19 | def ask_question 20 | $stdout.print question 21 | $stdout.flush 22 | end 23 | 24 | def value_or_default 25 | if response.empty? 26 | default 27 | else 28 | response 29 | end 30 | end 31 | 32 | def response 33 | return @response if defined? @response 34 | 35 | @response = (gets || "").chomp 36 | end 37 | 38 | def gets 39 | return unless stdin.tty? 40 | 41 | if echo? 42 | stdin.gets 43 | else 44 | stdin.noecho(&:gets).tap { $stdout.print "\n" } 45 | end 46 | rescue Errno::EIO 47 | # when stdio gets closed 48 | return 49 | end 50 | 51 | def question 52 | if prompt && default.nil? 53 | I18n.t(:question_prompt, key: prompt, scope: :capistrano) 54 | elsif prompt 55 | I18n.t(:question_prompt_default, key: prompt, default_value: default, scope: :capistrano) 56 | elsif default.nil? 57 | I18n.t(:question, key: key, scope: :capistrano) 58 | else 59 | I18n.t(:question_default, key: key, default_value: default, scope: :capistrano) 60 | end 61 | end 62 | 63 | def echo? 64 | (options || {}).fetch(:echo, true) 65 | end 66 | 67 | def stdin 68 | (options || {}).fetch(:stdin, $stdin) 69 | end 70 | 71 | def prompt 72 | (options || {}).fetch(:prompt, nil) 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /docs/documentation/getting-started/version-locking/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Version Locking 3 | layout: default 4 | --- 5 | 6 | Capistrano will, by default, include a `lock` command at the top of `deploy.rb`. This checks that the version of Capistrano running the configuration is the same as was intended to run it. 7 | 8 | The reasoning for this is that, in a pre-Bundler world, or when Bundler is not being used, Capistrano could behave in an unexpected and unclear manner with an incompatible configuration. Even today, it is easy to run Capistrano without `bundle exec` or a binstub (`bin/cap`, obtained through `bundle binstub capistrano`), resulting in unexpected behavior. 9 | 10 | The syntax for the lock is the same as that used by Bundler in a Gemfile (see the Implementation section below). 11 | 12 | The simplest form is: `lock '3.9.0'`. This locks the configuration to the exact version given. 13 | 14 | The most useful form uses the pessimistic operator: `~> 3.9.0`. This allows the version of the last segment to be increased, and all prior segments are locked. For example, if you used `lock '~> 3.9.2'`, version `3.9.3` would be allowed, but `3.9.1`, `3.10.0`, and `4.0.0` would not. Generally, you will want to lock to the `major.minor` revision. This means that the major version cannot increase, but the minor version can, which is consistent with semantic versioning (which Capistrano follows, [loosely](https://github.com/capistrano/capistrano/pull/1894/files)). 15 | 16 | You can also use `>`, `<`, `<=`, `>=`, and `=` before the version, as in `lock '>= 3.9.0'`. These are useful if you want to lock to a specific set of rules. 17 | 18 | For more complex usage, you can combine operators. For example, you can write `lock ['>= 3.9.0', '< 3.9.10']`, which would allow everything from 3.9.0 to 3.9.9, but not 3.9.10 or greater. 19 | 20 | ## Implementation 21 | 22 | The code reuses RubyGems core [version comparison logic](https://ruby-doc.org/stdlib-2.4.2/libdoc/rubygems/rdoc/Gem/Dependency.html#method-i-3D-7E). So anything you can do in RubyGems, you can do here. 23 | -------------------------------------------------------------------------------- /docs/assets/css/capistrano.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "proxima-nova",sans-serif; 3 | } 4 | 5 | #carbonads img { 6 | float: left; 7 | margin-right: 1em; 8 | } 9 | 10 | #carbonads .carbon-text { 11 | display: block; 12 | font-size: 0.9em; 13 | color: silver; 14 | } 15 | 16 | #carbonads .carbon-poweredby { 17 | font-size: 0.75em; 18 | display: block; 19 | color: silver 20 | } 21 | 22 | .header { 23 | margin-bottom: 30px; 24 | padding-top: 20px; 25 | background-color: #1C1B39; 26 | min-height: 170px; 27 | } 28 | 29 | h1, h2, h3, h4, h5, h6 { 30 | font-weight: 400; 31 | font-family: 'Enriqueta', serif; 32 | } 33 | 34 | .highlighter-rouge { 35 | margin-bottom: 1.25em; 36 | } 37 | 38 | .alert-box { 39 | font-weight: normal; 40 | color: black; 41 | 42 | background-color: #d7ecfa; 43 | border: none; 44 | padding-left: 20px; 45 | } 46 | 47 | .alert-box.alert { 48 | background-color: #f7e4e1; 49 | color: black; 50 | } 51 | 52 | .alert-box.success { 53 | background-color: #e1faea; 54 | color: black; 55 | } 56 | 57 | .alert-box.secondary { 58 | background-color: #eaeaea; 59 | color: black; 60 | } 61 | 62 | /*p code, li code { 63 | padding: 3px; 64 | background-color: #E6E6E6; 65 | font-family: "droid-sans-mono", Consolas, Monaco, 'Andale Mono', monospace; 66 | font-size: 0.9em; 67 | color: #222; 68 | border-radius: 3px; 69 | }*/ 70 | 71 | footer { 72 | padding: 1em; 73 | background-color: #222; 74 | color: #fff; 75 | } 76 | 77 | footer a, footer a:hover { 78 | color: #fff; 79 | } 80 | 81 | 82 | footer ul.social.icons { 83 | list-style-type: none; 84 | } 85 | 86 | li { 87 | margin-left: 2em; 88 | } 89 | 90 | footer ul.social.icons li.foundicon { 91 | float: left; 92 | margin-left: 10px; 93 | font-size: 5em; 94 | } 95 | 96 | footer ul.social.icons li.thanks-dnsimple { 97 | clear: left; 98 | } 99 | 100 | .github-widget { 101 | margin-bottom: 1em; 102 | } 103 | 104 | .github-box .github-box-download { 105 | height: 44px !important; 106 | } 107 | 108 | /*pre code { 109 | color: #fff; 110 | }*/ 111 | -------------------------------------------------------------------------------- /spec/lib/capistrano/application_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Capistrano::Application do 4 | it "provides a --trace option which enables SSHKit/NetSSH trace output" 5 | 6 | it "provides a --format option which enables the choice of output formatting" 7 | 8 | it "displays documentation URL as help banner", capture_io: true do 9 | flags "--help", "-h" 10 | expect($stdout.string.each_line.first).to match(/capistranorb.com/) 11 | end 12 | 13 | %w(quiet silent verbose).each do |switch| 14 | it "doesn't include --#{switch} in help", capture_io: true do 15 | flags "--help", "-h" 16 | expect($stdout.string).not_to match(/--#{switch}/) 17 | end 18 | end 19 | 20 | it "overrides the rake method, but still prints the rake version", capture_io: true do 21 | flags "--version", "-V" 22 | out = $stdout.string 23 | expect(out).to match(/\bCapistrano Version\b/) 24 | expect(out).to match(/\b#{Capistrano::VERSION}\b/) 25 | expect(out).to match(/\bRake Version\b/) 26 | expect(out).to match(/\b#{Rake::VERSION}\b/) 27 | end 28 | 29 | it "overrides the rake method, and sets the sshkit_backend to SSHKit::Backend::Printer", capture_io: true do 30 | flags "--dry-run", "-n" 31 | sshkit_backend = Capistrano::Configuration.fetch(:sshkit_backend) 32 | expect(sshkit_backend).to eq(SSHKit::Backend::Printer) 33 | end 34 | 35 | it "enables printing all config variables on command line parameter", capture_io: true do 36 | begin 37 | flags "--print-config-variables", "-p" 38 | expect(Capistrano::Configuration.fetch(:print_config_variables)).to be true 39 | ensure 40 | Capistrano::Configuration.reset! 41 | end 42 | end 43 | 44 | def flags(*sets) 45 | sets.each do |set| 46 | ARGV.clear 47 | @exit = catch(:system_exit) { command_line(*set) } 48 | end 49 | yield(subject.options) if block_given? 50 | end 51 | 52 | def command_line(*options) 53 | options.each { |opt| ARGV << opt } 54 | subject.define_singleton_method(:exit) do |*_args| 55 | throw(:system_exit, :exit) 56 | end 57 | subject.run 58 | subject.options 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /docs/documentation/advanced-features/property-filtering/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Property Filtering 3 | layout: default 4 | --- 5 | 6 | Options may be passed to the `roles()` method (and implicitly in methods like 7 | `release_roles()` and `primary()`) that affect the set of servers returned. These options 8 | take the form of a Hash passed as the last parameter. Each of the key/value pairs in the 9 | hash are evaluated in the sequence they are declared and if all are true for a specific 10 | server then the server will be returned. The keys must always be symbols which have the 11 | following meaning: 12 | 13 | * `:filter`, or `:select`: The value is either a property keyname or a lambda which is 14 | called with the server as parameter. The value must return true for the server to be 15 | included. 16 | 17 | * `:exclude`: As above but the value must return false for the server to be included. 18 | 19 | * Any other symbol is taken as a server property name whose value must equal the given value. 20 | A lambda will not be called if one is supplied! 21 | 22 | ### Examples 23 | 24 | ```ruby 25 | server 'example1.com', roles: %w{web}, active: true 26 | server 'example2.com', roles: %w{web} 27 | server 'example3.com', roles: %w{app web}, active: true 28 | server 'example4.com', roles: %w{app}, primary: true 29 | server 'example5.com', roles: %w{db}, no_release: true, active: true 30 | 31 | task :demo do 32 | puts "All active release roles: 1,3" 33 | release_roles(:all, filter: :active).each do |r| 34 | puts "#{r.hostname}" 35 | end 36 | puts "All active roles: 1,3,5" 37 | roles(:all, active: true).each do |r| 38 | puts "#{r.hostname}" 39 | end 40 | puts "All web and db roles with selected names: 2,3" 41 | roles(:web, :db, select: ->(s){ s.hostname =~ /[234]/}).each do |r| 42 | puts "#{r.hostname}" 43 | end 44 | puts "All with no active property: 2,4" 45 | roles(:all, active: nil).each do |r| 46 | puts "#{r.hostname}" 47 | end 48 | puts "All except active: 2,4" 49 | roles(:all, exclude: :active).each do |r| 50 | puts "#{r.hostname}" 51 | end 52 | puts "All primary: 4" 53 | roles(:all, select: :primary).each do |r| 54 | puts "#{r.hostname}" 55 | end 56 | end 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/documentation/faq/how-can-i-access-stage-configuration-variables/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: How can I access stage configuration variables? 3 | layout: default 4 | --- 5 | 6 | Configuration variables are accessed with the fetch method, like so: 7 | 8 | ```ruby 9 | local = fetch(:configuration_variable, _default_value_) 10 | ``` 11 | 12 | This works fine when accessing configuration variables defined within the same file. For example accessing a previously set configuration variable defined in deploy.rb or accessing a set configuration variable in a stage file. 13 | 14 | The deploy.rb configuration is executed first and then the stage file(s) from config/deploy/*.rb are executed next. This means that the configuration variables set in deploy.rb are available to the stage files, but configuration variables created in a stage file are not available in deploy.rb. To access them they must be lazily loaded in deploy.rb. This works because all configuration variables (from both deploy.rb and the current stage file) have been defined by the time the tasks run and access the variables. 15 | 16 | For example, let's create a configuration variable in the production and staging files and access the current one from deploy.rb. 17 | 18 | config/deploy/production.rb 19 | 20 | ```ruby 21 | set :app_domain, "www.my_application.com" 22 | ``` 23 | 24 | config/deploy/staging.rb 25 | 26 | ```ruby 27 | set :app_domain, "stage.application_test.com" 28 | ``` 29 | 30 | These variables are not available in deploy.rb using `fetch(:nginx_port)` or `fetch(:app_domain)` because they are not defined when deploy.rb is executed. They can, however, be lazily loaded using a lambda in deploy.rb like this: 31 | 32 | config/deploy.rb 33 | 34 | ```ruby 35 | set :nginx_server_name, ->{ fetch(:app_domain) } 36 | set :puma_bind, ->{ "unix:/tmp/#{fetch(:app_domain)}.sock" } 37 | ``` 38 | 39 | Now the `:nginx_server_name` and `:puma_bind` variables will be lazily assigned the values set in which ever stage file was used to deploy. 40 | 41 | If you need to create nested hashes, you might find `do/end` syntax more readable: 42 | 43 | ```ruby 44 | set :database_yml, -> do 45 | { 46 | production: { 47 | host: 'localhost' 48 | } 49 | } 50 | end 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/documentation/harrow/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: What is Harrow? 3 | layout: default 4 | --- 5 | 6 | ### Harrow is a web-based platform for continuous integration and deployment built by the Capistrano team. 7 | 8 | There are many continuous integration tools in the world already, Harrow is 9 | ours. It is designed to "feel" familiar to Capistrano users. 10 | 11 | ![Harrow, web-based Capistrano](/assets/images/capistrano-logo-harrow-logo-c-primary-darker-w640.png) 12 | 13 | Although Harrow is designed to work well for Capistrano-style use-cases, it is 14 | by no means limited to only being used for Capistrano, or even for deployment. 15 | 16 | Some of the features which make Harrow ideal for automating tools such as 17 | Capistrano: 18 | 19 | * A discrete concept of scripts and environments, allowing reuse of scripts in 20 | different settings using different configurations 21 | * A pure JSON-HAL API allowing integrations and tools 22 | * Powerful triggers and notifications allowing the construction of pipelines 23 | starting with git changes 24 | 25 | Harrow, much like Capistrano can also be used to: 26 | 27 | * To automate common tasks in software teams 28 | * To drive infrastructure provisioning tools such as *chef-solo*, *Ansible* or similar 29 | 30 | Again, like Capistrano, Harrow is also *very* scriptable, and can be integrated 31 | with any other software to form part of a larger tool. 32 | 33 | #### How To Use Harrow 34 | 35 | 1. Sign up for a Harrow [account](https://www.app.harrow.io/#/a/signin) 36 | 2. Connect your Git repository using our setup wizard 37 | 3. Choose "Capistrano" as the project template 38 | 39 | #### What does it cost, and how does that affect Capistrano 40 | 41 | Harrow has very reasonable [pricing](https://harrow.io/pricing/). As a 42 | comparison with other continuous integration tools, some of our customers have 43 | cut their monthly outgoing by a factor of 5 or more. 44 | 45 | For individual users, it's free to use forever. To work with collaborators in 46 | your projects, paid plans start at just $29/mo. 47 | 48 | Capistrano is unaffected by Harrow. Capistrano will remain liberally licensed 49 | (currently MIT) and will include discrete hooks offering Harrow to users 50 | without being intrusive. 51 | -------------------------------------------------------------------------------- /lib/capistrano/i18n.rb: -------------------------------------------------------------------------------- 1 | require "i18n" 2 | 3 | en = { 4 | starting: "Starting", 5 | capified: "Capified", 6 | start: "Start", 7 | update: "Update", 8 | finalize: "Finalise", 9 | finishing: "Finishing", 10 | finished: "Finished", 11 | stage_not_set: "Stage not set, please call something such as `cap production deploy`, where production is a stage you have defined.", 12 | written_file: "create %{file}", 13 | question: "Please enter %{key}: ", 14 | question_default: "Please enter %{key} (%{default_value}): ", 15 | question_prompt: "%{key}: ", 16 | question_prompt_default: "%{key} (%{default_value}): ", 17 | keeping_releases: "Keeping %{keep_releases} of %{releases} deployed releases on %{host}", 18 | skip_cleanup: "Skipping cleanup of invalid releases on %{host}; unexpected foldername found (should be timestamp)", 19 | wont_delete_current_release: "Current release was marked for being removed but it's going to be skipped on %{host}", 20 | no_current_release: "There is no current release present on %{host}", 21 | no_old_releases: "No old releases (keeping newest %{keep_releases}) on %{host}", 22 | linked_file_does_not_exist: "linked file %{file} does not exist on %{host}", 23 | cannot_rollback: "There are no older releases to rollback to", 24 | cannot_found_rollback_release: "Cannot rollback because release %{release} does not exist", 25 | mirror_exists: "The repository mirror is at %{at}", 26 | revision_log_message: "Branch %{branch} (at %{sha}) deployed as release %{release} by %{user}", 27 | rollback_log_message: "%{user} rolled back to release %{release}", 28 | deploy_failed: "The deploy has failed with an error: %{ex}", 29 | console: { 30 | welcome: "capistrano console - enter command to execute on %{stage}", 31 | bye: "bye" 32 | }, 33 | error: { 34 | invalid_stage_name: '"%{name}" is a reserved word and cannot be used as a stage. Rename "%{path}" to something else.', 35 | user: { 36 | does_not_exist: "User %{user} does not exists", 37 | cannot_switch: "Cannot switch to user %{user}" 38 | } 39 | } 40 | } 41 | 42 | I18n.backend.store_translations(:en, capistrano: en) 43 | 44 | if I18n.respond_to?(:enforce_available_locales=) 45 | I18n.enforce_available_locales = true 46 | end 47 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | jobs: 7 | spec: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | ruby: 12 | [ 13 | "2.3", 14 | "2.4", 15 | "2.5", 16 | "2.6", 17 | "2.7", 18 | "3.0", 19 | "3.1", 20 | "3.2", 21 | "3.3", 22 | "head", 23 | ] 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Set up Ruby 27 | uses: ruby/setup-ruby@v1 28 | with: 29 | ruby-version: ${{ matrix.ruby }} 30 | bundler-cache: true 31 | - name: rake spec 32 | run: bundle exec rake spec 33 | spec-legacy: 34 | runs-on: ubuntu-20.04 35 | strategy: 36 | matrix: 37 | ruby: ["2.0", "2.1", "2.2"] 38 | steps: 39 | - uses: actions/checkout@v4 40 | - name: Set up Ruby 41 | uses: ruby/setup-ruby@v1 42 | with: 43 | ruby-version: ${{ matrix.ruby }} 44 | bundler-cache: true 45 | - name: rake spec 46 | run: bundle exec rake spec 47 | spec-all: 48 | runs-on: ubuntu-latest 49 | needs: [spec, spec-legacy] 50 | if: always() 51 | steps: 52 | - name: All tests ok 53 | if: ${{ !(contains(needs.*.result, 'failure')) }} 54 | run: exit 0 55 | - name: Some tests failed 56 | if: ${{ contains(needs.*.result, 'failure') }} 57 | run: exit 1 58 | rubocop: 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v4 62 | - name: Set up Ruby 63 | uses: ruby/setup-ruby@v1 64 | with: 65 | ruby-version: "ruby" # latest-stable 66 | bundler-cache: true 67 | - name: rake rubocop 68 | run: bundle exec rake rubocop 69 | features: 70 | needs: [spec, spec-legacy] 71 | runs-on: ubuntu-latest 72 | steps: 73 | - uses: actions/checkout@v4 74 | - name: Set up Ruby 75 | uses: ruby/setup-ruby@v1 76 | with: 77 | ruby-version: "ruby" # latest-stable 78 | bundler-cache: true 79 | - name: rake features 80 | run: bundle exec rake features 81 | -------------------------------------------------------------------------------- /docs/documentation/getting-started/structure/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Structure 3 | layout: default 4 | --- 5 | 6 | Capistrano uses a strictly defined directory hierarchy on each remote server to organise the source code and other deployment-related data. The root path of this structure can be defined with the configuration variable `:deploy_to`. 7 | 8 | Assuming your `config/deploy.rb` contains this: 9 | 10 | ```ruby 11 | set :deploy_to, '/var/www/my_app_name' 12 | ``` 13 | 14 | 15 | Then inspecting the directories inside `/var/www/my_app_name` looks like this: 16 | 17 | ```bash 18 | ├── current -> /var/www/my_app_name/releases/20150120114500/ 19 | ├── releases 20 | │   ├── 20150080072500 21 | │   ├── 20150090083000 22 | │   ├── 20150100093500 23 | │   ├── 20150110104000 24 | │   └── 20150120114500 25 | ├── repo 26 | │ └── 27 | ├── revisions.log 28 | └── shared 29 | └── 30 | ``` 31 | 32 | 33 | * `current` is a symlink pointing to the latest release. This symlink is 34 | updated at the end of a successful deployment. If the deployment fails in any 35 | step the `current` symlink still points to the old release. 36 | 37 | * `releases` holds all deployments in a timestamped folder. These folders are 38 | the target of the `current` symlink. 39 | 40 | * `repo` holds the version control system configured. In case of a git 41 | repository the content will be a raw git repository (e.g. objects, refs, 42 | etc.). 43 | 44 | * `revisions.log` is used to log every deploy or rollback. Each entry is 45 | timestamped and the executing user (`:local_user`, defaulting to the local 46 | username) is listed. Depending on your VCS data like branch names or revision 47 | numbers are listed as well. 48 | 49 | * `shared` contains the `linked_files` and `linked_dirs` which are symlinked 50 | into each release. This data persists across deployments and releases. It 51 | should be used for things like database configuration files and static and 52 | persistent user storage handed over from one release to the next. 53 | 54 | The application is completely contained within the path of `:deploy_to`. If 55 | you plan on deploying multiple applications to the same server, simply choose 56 | a different `:deploy_to` path. 57 | -------------------------------------------------------------------------------- /lib/capistrano/doctor/variables_doctor.rb: -------------------------------------------------------------------------------- 1 | require "capistrano/doctor/output_helpers" 2 | 3 | module Capistrano 4 | module Doctor 5 | # Prints a table of all Capistrano variables and their current values. If 6 | # there are unrecognized variables, print warnings for them. 7 | class VariablesDoctor 8 | # These are keys that are recognized by Capistrano, but do not have values 9 | # set by default. 10 | WHITELIST = %i( 11 | application 12 | current_directory 13 | linked_dirs 14 | linked_files 15 | releases_directory 16 | repo_url 17 | repo_tree 18 | shared_directory 19 | ).freeze 20 | private_constant :WHITELIST 21 | 22 | include Capistrano::Doctor::OutputHelpers 23 | 24 | def initialize(env=Capistrano::Configuration.env) 25 | @env = env 26 | end 27 | 28 | def call 29 | title("Variables") 30 | values = inspect_all_values 31 | 32 | table(variables.keys.sort_by(&:to_s)) do |key, row| 33 | row.yellow if suspicious_keys.include?(key) 34 | row << key.inspect 35 | row << values[key] 36 | end 37 | 38 | puts if suspicious_keys.any? 39 | 40 | suspicious_keys.sort_by(&:to_s).each do |key| 41 | warning("#{key.inspect} is not a recognized Capistrano setting "\ 42 | "(#{location(key)})") 43 | end 44 | end 45 | 46 | private 47 | 48 | attr_reader :env 49 | 50 | def variables 51 | env.variables 52 | end 53 | 54 | def inspect_all_values 55 | variables.keys.each_with_object({}) do |key, inspected| 56 | inspected[key] = if env.is_question?(key) 57 | "" 58 | else 59 | variables.peek(key).inspect 60 | end 61 | end 62 | end 63 | 64 | def suspicious_keys 65 | (variables.untrusted_keys & variables.unused_keys) - WHITELIST 66 | end 67 | 68 | def location(key) 69 | loc = variables.source_locations(key).first 70 | loc && loc.sub(/^#{Regexp.quote(Dir.pwd)}/, "").sub(/:in.*/, "") 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/lib/capistrano/doctor/gems_doctor_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "capistrano/doctor/gems_doctor" 3 | require "airbrussh/version" 4 | require "sshkit/version" 5 | require "net/ssh/version" 6 | 7 | module Capistrano 8 | module Doctor 9 | describe GemsDoctor do 10 | let(:doc) { GemsDoctor.new } 11 | 12 | it "prints using 4-space indentation" do 13 | expect { doc.call }.to output(/^ {4}/).to_stdout 14 | end 15 | 16 | it "prints the Capistrano version" do 17 | expect { doc.call }.to\ 18 | output(/capistrano\s+#{Regexp.quote(Capistrano::VERSION)}/).to_stdout 19 | end 20 | 21 | it "prints the Rake version" do 22 | expect { doc.call }.to\ 23 | output(/rake\s+#{Regexp.quote(Rake::VERSION)}/).to_stdout 24 | end 25 | 26 | it "prints the SSHKit version" do 27 | expect { doc.call }.to\ 28 | output(/sshkit\s+#{Regexp.quote(SSHKit::VERSION)}/).to_stdout 29 | end 30 | 31 | it "prints the Airbrussh version" do 32 | expect { doc.call }.to\ 33 | output(/airbrussh\s+#{Regexp.quote(Airbrussh::VERSION)}/).to_stdout 34 | end 35 | 36 | it "prints the net-ssh version" do 37 | expect { doc.call }.to\ 38 | output(/net-ssh\s+#{Regexp.quote(Net::SSH::Version::STRING)}/).to_stdout 39 | end 40 | 41 | it "warns that new version is available" do 42 | Gem.stubs(:latest_version_for).returns(Gem::Version.new("99.0.0")) 43 | expect { doc.call }.to output(/\(update available\)/).to_stdout 44 | end 45 | 46 | describe "Rake" do 47 | before do 48 | load File.expand_path("../../../../../lib/capistrano/doctor.rb", 49 | __FILE__) 50 | end 51 | 52 | after do 53 | Rake::Task.clear 54 | end 55 | 56 | it "has an doctor:gems task that calls GemsDoctor", capture_io: true do 57 | GemsDoctor.any_instance.expects(:call) 58 | Rake::Task["doctor:gems"].invoke 59 | end 60 | 61 | it "has a doctor task that depends on doctor:gems" do 62 | expect(Rake::Task["doctor"].prerequisites).to include("doctor:gems") 63 | end 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ page.title }} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% include metrics.html %} 28 | 29 | {% include header.html %} 30 | 31 |
32 |
33 | {% include navigation.html %} 34 |
35 |
36 |
37 |

{{ page.title }}

38 | {{ content }} 39 |
40 |
41 |
42 | 43 | {% include footer.html %} 44 | 45 | 46 | 47 | 48 | Fork me on GitHub 49 | 50 | {% include google_tag_manager.html %} 51 | 52 | 53 | -------------------------------------------------------------------------------- /lib/capistrano/dsl/paths.rb: -------------------------------------------------------------------------------- 1 | require "pathname" 2 | module Capistrano 3 | module DSL 4 | module Paths 5 | def deploy_to 6 | fetch(:deploy_to) 7 | end 8 | 9 | def deploy_path 10 | Pathname.new(deploy_to) 11 | end 12 | 13 | def current_path 14 | deploy_path.join(fetch(:current_directory, "current")) 15 | end 16 | 17 | def releases_path 18 | deploy_path.join(fetch(:releases_directory, "releases")) 19 | end 20 | 21 | def release_path 22 | fetch(:release_path) { current_path } 23 | end 24 | 25 | def set_release_path(timestamp=now) 26 | set(:release_timestamp, timestamp) 27 | set(:release_path, releases_path.join(timestamp)) 28 | end 29 | 30 | def stage_config_path 31 | Pathname.new fetch(:stage_config_path, "config/deploy") 32 | end 33 | 34 | def deploy_config_path 35 | Pathname.new fetch(:deploy_config_path, "config/deploy.rb") 36 | end 37 | 38 | def repo_url 39 | fetch(:repo_url) 40 | end 41 | 42 | def repo_path 43 | Pathname.new(fetch(:repo_path, ->() { deploy_path.join("repo") })) 44 | end 45 | 46 | def shared_path 47 | deploy_path.join(fetch(:shared_directory, "shared")) 48 | end 49 | 50 | def revision_log 51 | deploy_path.join("revisions.log") 52 | end 53 | 54 | def now 55 | env.timestamp.strftime("%Y%m%d%H%M%S") 56 | end 57 | 58 | def asset_timestamp 59 | env.timestamp.strftime("%Y%m%d%H%M.%S") 60 | end 61 | 62 | def linked_dirs(parent) 63 | paths = fetch(:linked_dirs) 64 | join_paths(parent, paths) 65 | end 66 | 67 | def linked_files(parent) 68 | paths = fetch(:linked_files) 69 | join_paths(parent, paths) 70 | end 71 | 72 | def linked_file_dirs(parent) 73 | map_dirnames(linked_files(parent)) 74 | end 75 | 76 | def linked_dir_parents(parent) 77 | map_dirnames(linked_dirs(parent)) 78 | end 79 | 80 | def join_paths(parent, paths) 81 | paths.map { |path| parent.join(path) } 82 | end 83 | 84 | def map_dirnames(paths) 85 | paths.map(&:dirname).uniq 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /docs/assets/js/foundation/foundation.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Cookie Plugin v1.3 3 | * https://github.com/carhartl/jquery-cookie 4 | * 5 | * Copyright 2011, Klaus Hartl 6 | * Dual licensed under the MIT or GPL Version 2 licenses. 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * http://www.opensource.org/licenses/GPL-2.0 9 | * 10 | * Modified to work with Zepto.js by ZURB 11 | */ 12 | (function ($, document, undefined) { 13 | 14 | var pluses = /\+/g; 15 | 16 | function raw(s) { 17 | return s; 18 | } 19 | 20 | function decoded(s) { 21 | return decodeURIComponent(s.replace(pluses, ' ')); 22 | } 23 | 24 | var config = $.cookie = function (key, value, options) { 25 | 26 | // write 27 | if (value !== undefined) { 28 | options = $.extend({}, config.defaults, options); 29 | 30 | if (value === null) { 31 | options.expires = -1; 32 | } 33 | 34 | if (typeof options.expires === 'number') { 35 | var days = options.expires, t = options.expires = new Date(); 36 | t.setDate(t.getDate() + days); 37 | } 38 | 39 | value = config.json ? JSON.stringify(value) : String(value); 40 | 41 | return (document.cookie = [ 42 | encodeURIComponent(key), '=', config.raw ? value : encodeURIComponent(value), 43 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE 44 | options.path ? '; path=' + options.path : '', 45 | options.domain ? '; domain=' + options.domain : '', 46 | options.secure ? '; secure' : '' 47 | ].join('')); 48 | } 49 | 50 | // read 51 | var decode = config.raw ? raw : decoded; 52 | var cookies = document.cookie.split('; '); 53 | for (var i = 0, l = cookies.length; i < l; i++) { 54 | var parts = cookies[i].split('='); 55 | if (decode(parts.shift()) === key) { 56 | var cookie = decode(parts.join('=')); 57 | return config.json ? JSON.parse(cookie) : cookie; 58 | } 59 | } 60 | 61 | return null; 62 | }; 63 | 64 | config.defaults = {}; 65 | 66 | $.removeCookie = function (key, options) { 67 | if ($.cookie(key) !== null) { 68 | $.cookie(key, null, options); 69 | return true; 70 | } 71 | return false; 72 | }; 73 | 74 | })(Foundation.zj, document); 75 | -------------------------------------------------------------------------------- /lib/capistrano/templates/stage.rb.erb: -------------------------------------------------------------------------------- 1 | # server-based syntax 2 | # ====================== 3 | # Defines a single server with a list of roles and multiple properties. 4 | # You can define all roles on a single server, or split them: 5 | 6 | # server "example.com", user: "deploy", roles: %w{app db web}, my_property: :my_value 7 | # server "example.com", user: "deploy", roles: %w{app web}, other_property: :other_value 8 | # server "db.example.com", user: "deploy", roles: %w{db} 9 | 10 | 11 | 12 | # role-based syntax 13 | # ================== 14 | 15 | # Defines a role with one or multiple servers. The primary server in each 16 | # group is considered to be the first unless any hosts have the primary 17 | # property set. Specify the username and a domain or IP for the server. 18 | # Don't use `:all`, it's a meta role. 19 | 20 | # role :app, %w{deploy@example.com}, my_property: :my_value 21 | # role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value 22 | # role :db, %w{deploy@example.com} 23 | 24 | 25 | 26 | # Configuration 27 | # ============= 28 | # You can set any configuration variable like in config/deploy.rb 29 | # These variables are then only loaded and set in this stage. 30 | # For available Capistrano configuration variables see the documentation page. 31 | # http://capistranorb.com/documentation/getting-started/configuration/ 32 | # Feel free to add new variables to customise your setup. 33 | 34 | 35 | 36 | # Custom SSH Options 37 | # ================== 38 | # You may pass any option but keep in mind that net/ssh understands a 39 | # limited set of options, consult the Net::SSH documentation. 40 | # http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start 41 | # 42 | # Global options 43 | # -------------- 44 | # set :ssh_options, { 45 | # keys: %w(/home/user_name/.ssh/id_rsa), 46 | # forward_agent: false, 47 | # auth_methods: %w(password) 48 | # } 49 | # 50 | # The server-based syntax can be used to override options: 51 | # ------------------------------------ 52 | # server "example.com", 53 | # user: "user_name", 54 | # roles: %w{web app}, 55 | # ssh_options: { 56 | # user: "user_name", # overrides user setting above 57 | # keys: %w(/home/user_name/.ssh/id_rsa), 58 | # forward_agent: false, 59 | # auth_methods: %w(publickey password) 60 | # # password: "please use keys" 61 | # } 62 | -------------------------------------------------------------------------------- /docs/assets/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/documentation/advanced-features/role-filtering/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Role filtering 3 | layout: default 4 | --- 5 | 6 | You may have situations where you only want to deploy to servers matching 7 | a single role. For example, you may have changed some aspect of how the web 8 | role works, but don't want to trigger a deployment to your database servers. 9 | 10 | You can use the *role filter* to restrict Capistrano tasks to only servers 11 | match a given role or roles. 12 | 13 | If the filter matches no servers, no actions will be taken. 14 | 15 | If you specify a filter, it will match any servers that have that role, and 16 | it will run _all_ tasks for each of the roles that server has. For example, 17 | if you filtered for servers with the `web` role, and a server had both the 18 | `web` and `db` role, both the `web` and `db` role tasks would be executed on it. 19 | 20 | ### Specifying a role filter 21 | 22 | There are three ways to specify the role filter. 23 | 24 | #### Environment variable 25 | 26 | Capistrano will read the role filter from the environment variable `ROLES` 27 | if it is set. You can set it inline: 28 | 29 | ```bash 30 | ROLES=app,web cap production deploy 31 | ``` 32 | 33 | Specify multiple roles by separating them with a comma. 34 | 35 | #### In configuration 36 | 37 | You can set the role filter inside your deploy configuration. For example, 38 | you can set the following inside `config/deploy.rb`: 39 | 40 | ```ruby 41 | set :filter, :roles => %w{app web} 42 | ``` 43 | 44 | Note that you specify the filter as an array rather than as a comma-separated 45 | list of roles when using this method. 46 | 47 | Note that the keyname `:role` is also supported. 48 | 49 | #### On the command line 50 | 51 | In a similar way to using the environment variable, you can set the role 52 | filter by specifying it as a command line argument to `cap`: 53 | 54 | ```bash 55 | cap --roles=app,web production deploy 56 | ``` 57 | 58 | Like the environment variable method, specify multiple roles by separating them 59 | with a comma. 60 | 61 | ### Using Regular Expressions 62 | 63 | Since role names are Ruby symbols they can legitimately contain any characters. However to 64 | allow multiple of them to be specified on one line we use the comma as a separator. 65 | 66 | To use a regular expression for a role filter begin and end the string with '/'. Because 67 | of the above these regular expressions may not contain a comma. 68 | -------------------------------------------------------------------------------- /lib/capistrano/configuration/servers.rb: -------------------------------------------------------------------------------- 1 | require "set" 2 | require "capistrano/configuration" 3 | require "capistrano/configuration/filter" 4 | 5 | module Capistrano 6 | class Configuration 7 | class Servers 8 | include Enumerable 9 | 10 | def add_host(host, properties={}) 11 | new_host = Server[host] 12 | new_host.port = properties[:port] if properties.key?(:port) 13 | # This matching logic must stay in sync with `Server#matches?`. 14 | key = ServerKey.new(new_host.hostname, new_host.port) 15 | existing = servers_by_key[key] 16 | if existing 17 | existing.user = new_host.user if new_host.user 18 | existing.with(properties) 19 | else 20 | servers_by_key[key] = new_host.with(properties) 21 | end 22 | end 23 | 24 | # rubocop:disable Security/MarshalLoad 25 | def add_role(role, hosts, options={}) 26 | options_deepcopy = Marshal.dump(options.merge(roles: role)) 27 | Array(hosts).each { |host| add_host(host, Marshal.load(options_deepcopy)) } 28 | end 29 | # rubocop:enable Security/MarshalLoad 30 | 31 | def roles_for(names) 32 | options = extract_options(names) 33 | s = Filter.new(:role, names).filter(servers_by_key.values) 34 | s.select { |server| server.select?(options) } 35 | end 36 | 37 | def role_properties_for(rolenames) 38 | roles = rolenames.to_set 39 | rps = Set.new unless block_given? 40 | roles_for(rolenames).each do |host| 41 | host.roles.intersection(roles).each do |role| 42 | [host.properties.fetch(role)].flatten(1).each do |props| 43 | if block_given? 44 | yield host, role, props 45 | else 46 | rps << (props || {}).merge(role: role, hostname: host.hostname) 47 | end 48 | end 49 | end 50 | end 51 | block_given? ? nil : rps 52 | end 53 | 54 | def fetch_primary(role) 55 | hosts = roles_for([role]) 56 | hosts.find(&:primary) || hosts.first 57 | end 58 | 59 | def each 60 | servers_by_key.values.each { |server| yield server } 61 | end 62 | 63 | private 64 | 65 | ServerKey = Struct.new(:hostname, :port) 66 | 67 | def servers_by_key 68 | @servers_by_key ||= {} 69 | end 70 | 71 | def extract_options(array) 72 | array.last.is_a?(::Hash) ? array.pop : {} 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /docs/documentation/advanced-features/host-filtering/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Host filtering 3 | layout: default 4 | --- 5 | 6 | You may encounter situations where you only want to deploy to a subset of 7 | the servers defined in your configuration. For example, a single server or 8 | set of servers may be misbehaving, and you want to re-deploy to just these 9 | servers without deploying to every server. 10 | 11 | You can use the *host filter* to restrict Capistrano tasks to only servers 12 | that match a given set of hostnames. 13 | 14 | If the filter matches no servers, no actions will be taken. 15 | 16 | If you specify a filter, it will match servers that have the listed hostnames, 17 | and it will run *all* the roles for each server. In other words, it only affects 18 | the servers the task runs on, not what tasks are run on a server. 19 | 20 | ### Specifying a host filter 21 | 22 | There are three ways to specify the host filter. 23 | 24 | #### Environment variable 25 | 26 | Capistrano will read the host filter from the environment variable `HOSTS` 27 | if it is set. You can set it inline: 28 | 29 | ```bash 30 | HOSTS=server1,server2 cap production deploy 31 | ``` 32 | 33 | Specify multiple hosts by separating them with a comma. 34 | 35 | #### In configuration 36 | 37 | You can set the host filter inside your deploy configuration. For example, 38 | you can set the following inside `config/deploy.rb`: 39 | 40 | ```ruby 41 | set :filter, :hosts => %w{server1 server2} 42 | ``` 43 | 44 | Note that you specify the filter as an array rather than as a comma-separated 45 | list of servers when using this method. 46 | 47 | Note that the keyname `:host` is also supported. 48 | 49 | #### On the command line 50 | 51 | In a similar way to using the environment variable, you can set the role 52 | filter by specifying it as a command line argument to `cap`: 53 | 54 | ```bash 55 | cap --hosts=server1,server2 production deploy 56 | ``` 57 | 58 | Like the environment variable method, specify multiple servers by separating 59 | them with a comma. 60 | 61 | ### Using Regular Expressions 62 | 63 | If the host name in a filter doesn't match the set of valid characters for a DNS name 64 | (Given by the regular expression `/^[-A-Za-z0-9.]+$/`) then it's assumed to be a regular 65 | expression in standard Ruby syntax. 66 | 67 | For example, if you had three servers named localrubyserver1, localrubyserver2, and amazonrubyserver1, but only wanted to deploy to localrubyserver*, you call Capistrano with a regex: 68 | 69 | ```bash 70 | cap --hosts=^localrubyserver production deploy 71 | ``` 72 | -------------------------------------------------------------------------------- /spec/lib/capistrano/configuration/host_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Capistrano 4 | class Configuration 5 | describe HostFilter do 6 | subject(:host_filter) { HostFilter.new(values) } 7 | 8 | let(:available) do 9 | [Server.new("server1"), 10 | Server.new("server2"), 11 | Server.new("server3"), 12 | Server.new("server4"), 13 | Server.new("server10")] 14 | end 15 | 16 | shared_examples "it filters hosts correctly" do |expected| 17 | it "filters correctly" do 18 | set = host_filter.filter(available) 19 | expect(set.map(&:hostname)).to eq(expected) 20 | end 21 | end 22 | 23 | describe "#filter" do 24 | context "with a string" do 25 | let(:values) { "server1" } 26 | it_behaves_like "it filters hosts correctly", %w{server1} 27 | 28 | context "and a single server" do 29 | let(:available) { Server.new("server1") } 30 | it_behaves_like "it filters hosts correctly", %w{server1} 31 | end 32 | end 33 | 34 | context "with a comma separated string" do 35 | let(:values) { "server1,server10" } 36 | it_behaves_like "it filters hosts correctly", %w{server1 server10} 37 | end 38 | 39 | context "with an array of strings" do 40 | let(:values) { %w{server1 server3} } 41 | it_behaves_like "it filters hosts correctly", %w{server1 server3} 42 | end 43 | 44 | context "with mixed splittable and unsplittable strings" do 45 | let(:values) { %w{server1 server2,server3} } 46 | it_behaves_like "it filters hosts correctly", %w{server1 server2 server3} 47 | end 48 | 49 | context "with a regexp" do 50 | let(:values) { "server[13]$" } 51 | it_behaves_like "it filters hosts correctly", %w{server1 server3} 52 | end 53 | 54 | context "with a regexp with line boundaries" do 55 | let(:values) { "^server" } 56 | it_behaves_like "it filters hosts correctly", %w{server1 server2 server3 server4 server10} 57 | end 58 | 59 | context "with a regexp with a comma" do 60 | let(:values) { 'server\d{1,3}$' } 61 | it_behaves_like "it filters hosts correctly", %w{server1 server2 server3 server4 server10} 62 | end 63 | 64 | context "without number" do 65 | let(:values) { "server" } 66 | it_behaves_like "it filters hosts correctly", %w{} 67 | end 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/lib/capistrano/plugin_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "capistrano/plugin" 3 | 4 | module Capistrano 5 | describe Plugin do 6 | include Rake::DSL 7 | include Capistrano::DSL 8 | 9 | class DummyPlugin < Capistrano::Plugin 10 | def define_tasks 11 | task :hello do 12 | end 13 | end 14 | 15 | def register_hooks 16 | before "deploy:published", "hello" 17 | end 18 | end 19 | 20 | class ExternalTasksPlugin < Capistrano::Plugin 21 | def define_tasks 22 | eval_rakefile( 23 | File.expand_path("../../../support/tasks/plugin.rake", __FILE__) 24 | ) 25 | end 26 | 27 | # Called from plugin.rake to demonstrate that helper methods work 28 | def hello 29 | set :plugin_result, "hello" 30 | end 31 | end 32 | 33 | before do 34 | # Define an example task to allow testing hooks 35 | task "deploy:published" 36 | end 37 | 38 | after do 39 | # Clean up any tasks or variables we created during the tests 40 | Rake::Task.clear 41 | Capistrano::Configuration.reset! 42 | end 43 | 44 | it "defines tasks when constructed" do 45 | install_plugin(DummyPlugin) 46 | expect(Rake::Task["hello"]).not_to be_nil 47 | end 48 | 49 | it "registers hooks when constructed" do 50 | install_plugin(DummyPlugin) 51 | expect(Rake::Task["deploy:published"].prerequisites).to include("hello") 52 | end 53 | 54 | it "skips registering hooks if load_hooks: false" do 55 | install_plugin(DummyPlugin, load_hooks: false) 56 | expect(Rake::Task["deploy:published"].prerequisites).to be_empty 57 | end 58 | 59 | it "doesn't call set_defaults immediately" do 60 | dummy = DummyPlugin.new 61 | install_plugin(dummy) 62 | dummy.expects(:set_defaults).never 63 | end 64 | 65 | it "calls set_defaults during load:defaults", capture_io: true do 66 | dummy = DummyPlugin.new 67 | dummy.expects(:set_defaults).once 68 | install_plugin(dummy) 69 | Rake::Task["load:defaults"].invoke 70 | end 71 | 72 | it "is able to load tasks from a .rake file", capture_io: true do 73 | install_plugin(ExternalTasksPlugin) 74 | Rake::Task["plugin_test"].invoke 75 | expect(fetch(:plugin_result)).to eq("hello") 76 | end 77 | 78 | it "exposes the SSHKit backend to subclasses" do 79 | SSHKit::Backend.expects(:current).returns(:backend) 80 | plugin = DummyPlugin.new 81 | expect(plugin.send(:backend)).to eq(:backend) 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /docs/documentation/third-party-plugins/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3rd Party Plugins 3 | layout: default 4 | --- 5 | 6 | Here are some Capistrano plugins you might find useful. 7 | This list is neither complete nor audited in any way. 8 | 9 | You can help us expanding this list by sending us a pull request on 10 | GitHub. 11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 |
23 | 24 |
25 | 26 |
27 | 28 |
29 | 30 |
31 | 32 |
33 | 34 |
35 | 36 |
37 | 38 |
39 | 40 |
41 | 42 |
43 | 44 |
45 | 46 |
47 | 48 |
49 | 50 |
51 | 52 |
53 | 54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 | 62 |
63 | -------------------------------------------------------------------------------- /lib/capistrano/doctor/output_helpers.rb: -------------------------------------------------------------------------------- 1 | module Capistrano 2 | module Doctor 3 | # Helper methods for pretty-printing doctor output to stdout. All output 4 | # (other than `title`) is indented by four spaces to facilitate copying and 5 | # pasting this output into e.g. GitHub or Stack Overflow to achieve code 6 | # formatting. 7 | module OutputHelpers 8 | class Row 9 | attr_reader :color 10 | attr_reader :values 11 | 12 | def initialize 13 | @values = [] 14 | end 15 | 16 | def <<(value) 17 | values << value 18 | end 19 | 20 | def yellow 21 | @color = :yellow 22 | end 23 | end 24 | 25 | # Prints a table for a given array of records. For each record, the block 26 | # is yielded two arguments: the record and a Row object. To print values 27 | # for that record, add values using `row << "some value"`. A row can 28 | # optionally be highlighted in yellow using `row.yellow`. 29 | def table(records, &block) 30 | return if records.empty? 31 | rows = collect_rows(records, &block) 32 | col_widths = calculate_column_widths(rows) 33 | 34 | rows.each do |row| 35 | line = row.values.each_with_index.map do |value, col| 36 | value.to_s.ljust(col_widths[col]) 37 | end.join(" ").rstrip 38 | line = color.colorize(line, row.color) if row.color 39 | puts line 40 | end 41 | end 42 | 43 | # Prints a title in blue with surrounding newlines. 44 | def title(text) 45 | # Use $stdout directly to bypass the indentation that our `puts` does. 46 | $stdout.puts(color.colorize("\n#{text}\n", :blue)) 47 | end 48 | 49 | # Prints text in yellow. 50 | def warning(text) 51 | puts color.colorize(text, :yellow) 52 | end 53 | 54 | # Override `Kernel#puts` to prepend four spaces to each line. 55 | def puts(string=nil) 56 | $stdout.puts(string.to_s.gsub(/^/, " ")) 57 | end 58 | 59 | private 60 | 61 | def collect_rows(records) 62 | records.map do |rec| 63 | Row.new.tap { |row| yield(rec, row) } 64 | end 65 | end 66 | 67 | def calculate_column_widths(rows) 68 | num_columns = rows.map { |row| row.values.length }.max 69 | Array.new(num_columns) do |col| 70 | rows.map { |row| row.values[col].to_s.length }.max 71 | end 72 | end 73 | 74 | def color 75 | @color ||= SSHKit::Color.new($stdout) 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /docs/assets/css/dreamweaver.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Dreamweaver theme 3 | * 4 | * @author Sean Coker 5 | * @url http://seancoker.com 6 | * @version 1.0 7 | */ 8 | 9 | pre { 10 | /* original is white background with no border */ 11 | background-color: #fff; 12 | word-wrap: break-word; 13 | margin: 0; 14 | padding: 10px; 15 | color: #000; 16 | font-size: 13px; 17 | line-height: 16px; 18 | margin-bottom: 20px 19 | } 20 | 21 | pre, code { 22 | font-family: monospace; 23 | } 24 | 25 | pre .comment { 26 | color: #888; 27 | } 28 | 29 | pre .support { 30 | color: #cd57d5; 31 | } 32 | 33 | pre .constant.numeric, pre .php.embedded { 34 | color: #fa0002; 35 | font-weight: bold; 36 | } 37 | 38 | pre .keyword, pre .constant.language { 39 | color: #000789; 40 | font-weight: bold; 41 | } 42 | 43 | pre .selector, pre .support.property, pre .entity.name.function { 44 | color: #000; 45 | } 46 | 47 | pre .storage.function, pre .variable.self, pre .support.function, pre .constant.language { 48 | color: #000; 49 | font-weight: bold; 50 | } 51 | 52 | pre .string { 53 | color: #0d43fa; 54 | font-weight: normal; 55 | } 56 | 57 | pre .css-property + span, pre .keyword.unit, pre .support.css-value { 58 | color: #0d43fa !important; 59 | font-weight: normal !important; 60 | } 61 | 62 | pre .entity.tag.style + .string, pre .php.embedded .constant.language, pre .php.embedded .keyword { 63 | color: #37a348 !important; 64 | } 65 | 66 | pre .support.method { 67 | color: #2bd5bb; 68 | } 69 | 70 | pre .entity.name { 71 | color: #fd74e0; 72 | } 73 | 74 | pre .support.css-property, pre .support.tag-name, pre .support.tag, pre .support.attribute, pre .support.attribute + .operator { 75 | color: #000789; 76 | } 77 | 78 | pre .storage.module, pre .storage.class { 79 | color: #122573; 80 | font-weight: bold; 81 | } 82 | 83 | pre .css.embedded .support.tag, pre .css.embedded .style.tag { 84 | color: #cd57d5; 85 | } 86 | 87 | pre .keyword.operator { 88 | color: #2852eb; 89 | font-weight: normal; 90 | } 91 | 92 | pre .php.embedded .variable, pre .php.embedded .storage.function { 93 | color: #0d43fa; 94 | font-weight: normal; 95 | } 96 | 97 | pre .php.embedded .string, pre .js.embedded .tag.script { 98 | color: #c4001e; 99 | } 100 | 101 | pre .php.embedded .comment { 102 | color: #f4b441; 103 | font-weight: normal; 104 | } 105 | 106 | pre .php.embedded .function.name { 107 | color: #000; 108 | font-weight: normal; 109 | } 110 | -------------------------------------------------------------------------------- /UPGRADING-3.7.md: -------------------------------------------------------------------------------- 1 | # Capistrano 3.7.0 upgrade guide 2 | 3 | ## The :scm variable is deprecated 4 | 5 | Up until now, Capistrano's SCM was configured using the `:scm` variable: 6 | 7 | ```ruby 8 | # This is now deprecated 9 | set :scm, :svn 10 | ``` 11 | 12 | To avoid deprecation warnings: 13 | 14 | 1. Remove `set :scm, ...` from your Capistrano configuration. 15 | 2. Add *one* of the following SCM declarations to your `Capfile` after `require "capistrano/deploy"`: 16 | 17 | ```ruby 18 | # To use Git 19 | require "capistrano/scm/git" 20 | install_plugin Capistrano::SCM::Git 21 | 22 | # To use Mercurial 23 | require "capistrano/scm/hg" 24 | install_plugin Capistrano::SCM::Hg 25 | 26 | # To use Subversion 27 | require "capistrano/scm/svn" 28 | install_plugin Capistrano::SCM::Svn 29 | ``` 30 | 31 | ## This is the last release where Git is the automatic default 32 | 33 | If you do not specify an SCM, Capistrano assumes Git. However this behavior is 34 | now deprecated. Add this to your Capfile to avoid deprecation warnings: 35 | 36 | ```ruby 37 | require "capistrano/scm/git" 38 | install_plugin Capistrano::SCM::Git 39 | ``` 40 | 41 | ## :git_strategy, :hg_strategy, and :svn_strategy are removed 42 | 43 | Capistrano 3.7.0 has a rewritten SCM system that relies on "plugins". This 44 | system is more flexible than the old "strategy" system that only allowed certain 45 | parts of SCM tasks to be customized. 46 | 47 | If your deployment relies on a custom SCM strategy, you will need to rewrite 48 | that strategy to be a full-fledged SCM plugin instead. There is a fairly 49 | straightforward migration path: write your plugin to be a subclass of the 50 | built-in SCM that you want to customize. For example: 51 | 52 | ```ruby 53 | require "capistrano/scm/git" 54 | 55 | class MyCustomGit < Capistrano::SCM::Git 56 | # Override the methods you wish to customize, e.g.: 57 | def clone_repo 58 | # ... 59 | end 60 | end 61 | ``` 62 | 63 | Then use your plugin in by loading it in the Capfile: 64 | 65 | ```ruby 66 | require_relative "path/to/my_custom_git.rb" 67 | install_plugin MyCustomGit 68 | ``` 69 | 70 | ## Existing third-party SCMs are deprecated 71 | 72 | If you are using a third-party SCM, you can continue using it without 73 | changes, but you will see deprecation warnings. Contact the maintainer of the 74 | third-party SCM gem and ask them about modifying the gem to work with the new 75 | Capistrano 3.7.0 SCM plugin system. 76 | 77 | ## remote_file is removed 78 | 79 | The `remote_file` method is no longer in Capistrano 3.7.0. You can read the 80 | discussion that led to its removal here: 81 | [issue 762](https://github.com/capistrano/capistrano/issues/762). 82 | 83 | There is no direct replacement. To migrate to 3.7.0, you will need to rewrite 84 | any parts of your deployment that use `remote_file` to use a different 85 | mechanism for uploading files. Consider using the `upload!` method directly in 86 | a procedural fashion instead. 87 | -------------------------------------------------------------------------------- /spec/lib/capistrano/configuration/role_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Capistrano 4 | class Configuration 5 | describe RoleFilter do 6 | subject(:role_filter) { RoleFilter.new(values) } 7 | 8 | let(:available) do 9 | [ 10 | Server.new("server1").add_roles(%i(web db)), 11 | Server.new("server2").add_role(:web), 12 | Server.new("server3").add_role(:redis), 13 | Server.new("server4").add_role(:db), 14 | Server.new("server5").add_role(:stageweb), 15 | Server.new("server6").add_role(:"db.new") 16 | ] 17 | end 18 | 19 | shared_examples "it filters roles correctly" do |expected_size, expected| 20 | it "filters correctly" do 21 | set = role_filter.filter(available) 22 | expect(set.size).to eq(expected_size) 23 | expect(set.map(&:hostname)).to eq(expected) 24 | end 25 | end 26 | 27 | describe "#filter" do 28 | context "with a single role string" do 29 | let(:values) { "web" } 30 | it_behaves_like "it filters roles correctly", 2, %w{server1 server2} 31 | end 32 | 33 | context "with a single role" do 34 | let(:values) { [:web] } 35 | it_behaves_like "it filters roles correctly", 2, %w{server1 server2} 36 | end 37 | 38 | context "with multiple roles in a string" do 39 | let(:values) { "web,db" } 40 | it_behaves_like "it filters roles correctly", 3, %w{server1 server2 server4} 41 | end 42 | 43 | context "with multiple roles" do 44 | let(:values) { %i(web db) } 45 | it_behaves_like "it filters roles correctly", 3, %w{server1 server2 server4} 46 | end 47 | 48 | context "with a regex" do 49 | let(:values) { /red/ } 50 | it_behaves_like "it filters roles correctly", 1, %w{server3} 51 | end 52 | 53 | context "with a regex string" do 54 | let(:values) { "/red|web/" } 55 | it_behaves_like "it filters roles correctly", 4, %w{server1 server2 server3 server5} 56 | end 57 | 58 | context "with both a string and regex" do 59 | let(:values) { "db,/red/" } 60 | it_behaves_like "it filters roles correctly", 3, %w{server1 server3 server4} 61 | end 62 | 63 | context "with a dot wildcard" do 64 | let(:values) { "db.*" } 65 | it_behaves_like "it filters roles correctly", 0, %w{} 66 | end 67 | 68 | context "with a dot" do 69 | let(:values) { "db.new" } 70 | it_behaves_like "it filters roles correctly", 1, %w{server6} 71 | end 72 | 73 | context "with a dot wildcard regex" do 74 | let(:values) { "/db.*/" } 75 | it_behaves_like "it filters roles correctly", 3, %w{server1 server4 server6} 76 | end 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/lib/capistrano/configuration/plugin_installer_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "capistrano/plugin" 3 | require "capistrano/scm/plugin" 4 | 5 | module Capistrano 6 | class Configuration 7 | class ExamplePlugin < Capistrano::Plugin 8 | def set_defaults 9 | set_if_empty :example_variable, "foo" 10 | end 11 | 12 | def define_tasks 13 | task :example 14 | task :example_prerequisite 15 | end 16 | 17 | def register_hooks 18 | before :example, :example_prerequisite 19 | end 20 | end 21 | 22 | class ExampleSCMPlugin < Capistrano::SCM::Plugin 23 | end 24 | 25 | describe PluginInstaller do 26 | include Capistrano::DSL 27 | 28 | let(:installer) { PluginInstaller.new } 29 | let(:options) { {} } 30 | let(:plugin) { ExamplePlugin.new } 31 | 32 | before do 33 | installer.install(plugin, **options) 34 | end 35 | 36 | after do 37 | Rake::Task.clear 38 | Capistrano::Configuration.reset! 39 | end 40 | 41 | context "installing plugin" do 42 | it "defines tasks" do 43 | expect(Rake::Task[:example]).to_not be_nil 44 | expect(Rake::Task[:example_prerequisite]).to_not be_nil 45 | end 46 | 47 | it "registers hooks" do 48 | task = Rake::Task[:example] 49 | expect(task.prerequisites).to eq([:example_prerequisite]) 50 | end 51 | 52 | it "sets defaults when load:defaults is invoked", capture_io: true do 53 | expect(fetch(:example_variable)).to be_nil 54 | invoke "load:defaults" 55 | expect(fetch(:example_variable)).to eq("foo") 56 | end 57 | 58 | it "doesn't say an SCM is installed" do 59 | expect(installer.scm_installed?).to be_falsey 60 | end 61 | end 62 | 63 | context "installing plugin class" do 64 | let(:plugin) { ExamplePlugin } 65 | 66 | it "defines tasks" do 67 | expect(Rake::Task[:example]).to_not be_nil 68 | expect(Rake::Task[:example_prerequisite]).to_not be_nil 69 | end 70 | end 71 | 72 | context "installing plugin without hooks" do 73 | let(:options) { { load_hooks: false } } 74 | 75 | it "doesn't register hooks" do 76 | task = Rake::Task[:example] 77 | expect(task.prerequisites).to be_empty 78 | end 79 | end 80 | 81 | context "installing plugin and loading immediately" do 82 | let(:options) { { load_immediately: true } } 83 | 84 | it "sets defaults immediately" do 85 | expect(fetch(:example_variable)).to eq("foo") 86 | end 87 | end 88 | 89 | context "installing an SCM plugin" do 90 | let(:plugin) { ExampleSCMPlugin } 91 | 92 | it "says an SCM is installed" do 93 | expect(installer.scm_installed?).to be_truthy 94 | end 95 | end 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /spec/lib/capistrano/scm_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | require "capistrano/scm" 4 | 5 | module RaiseNotImplementedMacro 6 | def raise_not_implemented_on(method) 7 | it "should raise NotImplemented on #{method}" do 8 | expect do 9 | subject.send(method) 10 | end.to raise_error(NotImplementedError) 11 | end 12 | end 13 | end 14 | 15 | RSpec.configure do 16 | include RaiseNotImplementedMacro 17 | end 18 | 19 | module DummyStrategy 20 | def test 21 | test!("you dummy!") 22 | end 23 | end 24 | 25 | module BlindStrategy; end 26 | 27 | module Capistrano 28 | describe SCM do 29 | let(:context) { mock } 30 | 31 | describe "#initialize" do 32 | subject { Capistrano::SCM.new(context, DummyStrategy) } 33 | 34 | it "should load the provided strategy" do 35 | context.expects(:test).with("you dummy!") 36 | subject.test 37 | end 38 | end 39 | 40 | describe "Convenience methods" do 41 | subject { Capistrano::SCM.new(context, BlindStrategy) } 42 | 43 | describe "#test!" do 44 | it "should return call test on the context" do 45 | context.expects(:test).with(:x) 46 | subject.test!(:x) 47 | end 48 | end 49 | 50 | describe "#repo_url" do 51 | it "should return the repo url according to the context" do 52 | context.expects(:repo_url).returns(:url) 53 | expect(subject.repo_url).to eq(:url) 54 | end 55 | end 56 | 57 | describe "#repo_path" do 58 | it "should return the repo path according to the context" do 59 | context.expects(:repo_path).returns(:path) 60 | expect(subject.repo_path).to eq(:path) 61 | end 62 | end 63 | 64 | describe "#release_path" do 65 | it "should return the release path according to the context" do 66 | context.expects(:release_path).returns("/path/to/nowhere") 67 | expect(subject.release_path).to eq("/path/to/nowhere") 68 | end 69 | end 70 | 71 | describe "#fetch" do 72 | it "should call fetch on the context" do 73 | context.expects(:fetch) 74 | subject.fetch(:branch) 75 | end 76 | end 77 | end 78 | 79 | describe "With a 'blind' strategy" do 80 | subject { Capistrano::SCM.new(context, BlindStrategy) } 81 | 82 | describe "#test" do 83 | raise_not_implemented_on(:test) 84 | end 85 | 86 | describe "#check" do 87 | raise_not_implemented_on(:check) 88 | end 89 | 90 | describe "#clone" do 91 | raise_not_implemented_on(:clone) 92 | end 93 | 94 | describe "#update" do 95 | raise_not_implemented_on(:update) 96 | end 97 | 98 | describe "#release" do 99 | raise_not_implemented_on(:release) 100 | end 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /spec/lib/capistrano/doctor/servers_doctor_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "capistrano/doctor/servers_doctor" 3 | 4 | module Capistrano 5 | module Doctor 6 | describe ServersDoctor do 7 | include Capistrano::DSL 8 | let(:doc) { ServersDoctor.new } 9 | 10 | before { Capistrano::Configuration.reset! } 11 | after { Capistrano::Configuration.reset! } 12 | 13 | it "prints using 4-space indentation" do 14 | expect { doc.call }.to output(/^ {4}/).to_stdout 15 | end 16 | 17 | it "prints the number of defined servers" do 18 | role :app, %w(example.com) 19 | server "www@example.com:22" 20 | 21 | expect { doc.call }.to output(/Servers \(2\)/).to_stdout 22 | end 23 | 24 | describe "prints the server's details" do 25 | it "including username" do 26 | server "www@example.com" 27 | expect { doc.call }.to output(/www@example.com/).to_stdout 28 | end 29 | 30 | it "including port" do 31 | server "www@example.com:22" 32 | expect { doc.call }.to output(/www@example.com:22/).to_stdout 33 | end 34 | 35 | it "including roles" do 36 | role :app, %w(example.com) 37 | expect { doc.call }.to output(/example.com\s+\[:app\]/).to_stdout 38 | end 39 | 40 | it "including empty roles" do 41 | server "example.com" 42 | expect { doc.call }.to output(/example.com\s+\[\]/).to_stdout 43 | end 44 | 45 | it "including properties" do 46 | server "example.com", roles: %w(app db), primary: true 47 | expect { doc.call }.to \ 48 | output(/example.com\s+\[:app, :db\]\s+\{ :primary => true \}/).to_stdout 49 | end 50 | 51 | it "including misleading role name alert" do 52 | server "example.com", roles: ["web app db"] 53 | warning_msg = 'Whitespace detected in role(s) :"web app db". ' \ 54 | 'This might be a result of a mistyped "%w()" array literal' 55 | 56 | expect { doc.call }.to output(/#{Regexp.escape(warning_msg)}/).to_stdout 57 | end 58 | end 59 | 60 | it "doesn't fail for no servers" do 61 | expect { doc.call }.to output("\nServers (0)\n \n").to_stdout 62 | end 63 | 64 | describe "Rake" do 65 | before do 66 | load File.expand_path("../../../../../lib/capistrano/doctor.rb", 67 | __FILE__) 68 | end 69 | 70 | after do 71 | Rake::Task.clear 72 | end 73 | 74 | it "has an doctor:servers task that calls ServersDoctor", capture_io: true do 75 | ServersDoctor.any_instance.expects(:call) 76 | Rake::Task["doctor:servers"].invoke 77 | end 78 | 79 | it "has a doctor task that depends on doctor:servers" do 80 | expect(Rake::Task["doctor"].prerequisites).to \ 81 | include("doctor:servers") 82 | end 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/capistrano/doctor/servers_doctor.rb: -------------------------------------------------------------------------------- 1 | require "capistrano/doctor/output_helpers" 2 | 3 | module Capistrano 4 | module Doctor 5 | class ServersDoctor 6 | include Capistrano::Doctor::OutputHelpers 7 | 8 | def initialize(env=Capistrano::Configuration.env) 9 | @servers = env.servers.to_a 10 | end 11 | 12 | def call 13 | title("Servers (#{servers.size})") 14 | rwc = RoleWhitespaceChecker.new(servers) 15 | 16 | table(servers) do |server, row| 17 | sd = ServerDecorator.new(server) 18 | 19 | row << sd.uri_form 20 | row << sd.roles 21 | row << sd.properties 22 | row.yellow if rwc.any_has_whitespace?(server.roles) 23 | end 24 | 25 | if rwc.whitespace_roles.any? 26 | warning "\nWhitespace detected in role(s) #{rwc.whitespace_roles_decorated}. " \ 27 | "This might be a result of a mistyped \"%w()\" array literal." 28 | end 29 | puts 30 | end 31 | 32 | private 33 | 34 | attr_reader :servers 35 | 36 | class RoleWhitespaceChecker 37 | attr_reader :whitespace_roles, :servers 38 | 39 | def initialize(servers) 40 | @servers = servers 41 | @whitespace_roles = find_whitespace_roles 42 | end 43 | 44 | def any_has_whitespace?(roles) 45 | roles.any? { |role| include_whitespace?(role) } 46 | end 47 | 48 | def include_whitespace?(role) 49 | role =~ /\s/ 50 | end 51 | 52 | def whitespace_roles_decorated 53 | whitespace_roles.map(&:inspect).join(", ") 54 | end 55 | 56 | private 57 | 58 | def find_whitespace_roles 59 | servers.map(&:roles).flat_map(&:to_a).uniq 60 | .select { |role| include_whitespace?(role) } 61 | end 62 | end 63 | 64 | class ServerDecorator 65 | def initialize(server) 66 | @server = server 67 | end 68 | 69 | def uri_form 70 | [ 71 | server.user, 72 | server.user && "@", 73 | server.hostname, 74 | server.port && ":", 75 | server.port 76 | ].compact.join 77 | end 78 | 79 | def roles 80 | server.roles.to_a.inspect 81 | end 82 | 83 | def properties 84 | return "" unless server.properties.keys.any? 85 | pretty_inspect(server.properties.to_h) 86 | end 87 | 88 | private 89 | 90 | attr_reader :server 91 | 92 | # Hashes with proper padding 93 | def pretty_inspect(element) 94 | return element.inspect unless element.is_a?(Hash) 95 | 96 | pairs_string = element.keys.map do |key| 97 | [pretty_inspect(key), pretty_inspect(element.fetch(key))].join(" => ") 98 | end.join(", ") 99 | 100 | "{ #{pairs_string} }" 101 | end 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /features/step_definitions/setup.rb: -------------------------------------------------------------------------------- 1 | Given(/^a test app with the default configuration$/) do 2 | TestApp.install 3 | end 4 | 5 | Given(/^a test app without any configuration$/) do 6 | TestApp.create_test_app 7 | end 8 | 9 | Given(/^servers with the roles app and web$/) do 10 | start_ssh_server 11 | wait_for_ssh_server 12 | end 13 | 14 | Given(/^a linked file "(.*?)"$/) do |file| 15 | # ignoring other linked files 16 | TestApp.append_to_deploy_file("set :linked_files, ['#{file}']") 17 | end 18 | 19 | Given(/^file "(.*?)" exists in shared path$/) do |file| 20 | file_shared_path = TestApp.shared_path.join(file) 21 | run_remote_ssh_command("mkdir -p #{file_shared_path.dirname}") 22 | run_remote_ssh_command("touch #{file_shared_path}") 23 | end 24 | 25 | Given(/^all linked files exists in shared path$/) do 26 | TestApp.linked_files.each do |linked_file| 27 | step %Q{file "#{linked_file}" exists in shared path} 28 | end 29 | end 30 | 31 | Given(/^file "(.*?)" does not exist in shared path$/) do |file| 32 | file_shared_path = TestApp.shared_path.join(file) 33 | run_remote_ssh_command("mkdir -p #{TestApp.shared_path}") 34 | run_remote_ssh_command("touch #{file_shared_path} && rm #{file_shared_path}") 35 | end 36 | 37 | Given(/^a custom task to generate a file$/) do 38 | TestApp.copy_task_to_test_app("spec/support/tasks/database.rake") 39 | end 40 | 41 | Given(/^a task which executes as root$/) do 42 | TestApp.copy_task_to_test_app("spec/support/tasks/root.rake") 43 | end 44 | 45 | Given(/config stage file has line "(.*?)"/) do |line| 46 | TestApp.append_to_deploy_file(line) 47 | end 48 | 49 | Given(/^the configuration is in a custom location$/) do 50 | TestApp.move_configuration_to_custom_location("app") 51 | end 52 | 53 | Given(/^a custom task that will simulate a failure$/) do 54 | safely_remove_file(TestApp.shared_path.join("failed")) 55 | TestApp.copy_task_to_test_app("spec/support/tasks/fail.rake") 56 | end 57 | 58 | Given(/^a custom task to run in the event of a failure$/) do 59 | safely_remove_file(TestApp.shared_path.join("failed")) 60 | TestApp.copy_task_to_test_app("spec/support/tasks/failed.rake") 61 | end 62 | 63 | Given(/^a stage file named (.+)$/) do |filename| 64 | TestApp.write_local_stage_file(filename) 65 | end 66 | 67 | Given(/^I make (\d+) deployments?$/) do |count| 68 | step "all linked files exists in shared path" 69 | 70 | @release_paths = (1..count.to_i).map do 71 | TestApp.cap("deploy") 72 | stdout, _stderr = run_remote_ssh_command("readlink #{TestApp.current_path}") 73 | 74 | stdout.strip 75 | end 76 | end 77 | 78 | Given(/^(\d+) valid existing releases$/) do |num| 79 | a_day = 86_400 # in seconds 80 | (1...num).each_slice(100) do |num_batch| 81 | dirs = num_batch.map do |i| 82 | offset = -(a_day * i) 83 | TestApp.release_path(TestApp.timestamp(offset)) 84 | end 85 | run_remote_ssh_command("mkdir -p #{dirs.join(' ')}") 86 | end 87 | end 88 | 89 | Given(/^an invalid release named "(.+)"$/) do |filename| 90 | run_remote_ssh_command("mkdir -p #{TestApp.release_path(filename)}") 91 | end 92 | -------------------------------------------------------------------------------- /docs/assets/css/social_foundicons.css: -------------------------------------------------------------------------------- 1 | /* font-face */ 2 | @font-face { 3 | font-family: "SocialFoundicons"; 4 | src: url("../fonts/social_foundicons.eot"); 5 | src: url("../fonts/social_foundicons.eot?#iefix") format("embedded-opentype"), url("../fonts/social_foundicons.woff") format("woff"), url("../fonts/social_foundicons.ttf") format("truetype"), url("../fonts/social_foundicons.svg#SocialFoundicons") format("svg"); 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* global foundicon styles */ 11 | [class*="foundicon-"] { 12 | display: inline; 13 | width: auto; 14 | height: auto; 15 | line-height: inherit; 16 | vertical-align: baseline; 17 | background-image: none; 18 | background-position: 0 0; 19 | background-repeat: repeat; 20 | } 21 | 22 | [class*="foundicon-"]:before { 23 | font-family: "SocialFoundicons"; 24 | font-weight: normal; 25 | font-style: normal; 26 | text-decoration: inherit; 27 | } 28 | 29 | /* icons */ 30 | .foundicon-thumb-up:before { 31 | content: "\f000"; 32 | } 33 | 34 | .foundicon-thumb-down:before { 35 | content: "\f001"; 36 | } 37 | 38 | .foundicon-rss:before { 39 | content: "\f002"; 40 | } 41 | 42 | .foundicon-facebook:before { 43 | content: "\f003"; 44 | } 45 | 46 | .foundicon-twitter:before { 47 | content: "\f004"; 48 | } 49 | 50 | .foundicon-pinterest:before { 51 | content: "\f005"; 52 | } 53 | 54 | .foundicon-github:before { 55 | content: "\f006"; 56 | } 57 | 58 | .foundicon-path:before { 59 | content: "\f007"; 60 | } 61 | 62 | .foundicon-linkedin:before { 63 | content: "\f008"; 64 | } 65 | 66 | .foundicon-dribbble:before { 67 | content: "\f009"; 68 | } 69 | 70 | .foundicon-stumble-upon:before { 71 | content: "\f00a"; 72 | } 73 | 74 | .foundicon-behance:before { 75 | content: "\f00b"; 76 | } 77 | 78 | .foundicon-reddit:before { 79 | content: "\f00c"; 80 | } 81 | 82 | .foundicon-google-plus:before { 83 | content: "\f00d"; 84 | } 85 | 86 | .foundicon-youtube:before { 87 | content: "\f00e"; 88 | } 89 | 90 | .foundicon-vimeo:before { 91 | content: "\f00f"; 92 | } 93 | 94 | .foundicon-flickr:before { 95 | content: "\f010"; 96 | } 97 | 98 | .foundicon-slideshare:before { 99 | content: "\f011"; 100 | } 101 | 102 | .foundicon-picassa:before { 103 | content: "\f012"; 104 | } 105 | 106 | .foundicon-skype:before { 107 | content: "\f013"; 108 | } 109 | 110 | .foundicon-steam:before { 111 | content: "\f014"; 112 | } 113 | 114 | .foundicon-instagram:before { 115 | content: "\f015"; 116 | } 117 | 118 | .foundicon-foursquare:before { 119 | content: "\f016"; 120 | } 121 | 122 | .foundicon-delicious:before { 123 | content: "\f017"; 124 | } 125 | 126 | .foundicon-chat:before { 127 | content: "\f018"; 128 | } 129 | 130 | .foundicon-torso:before { 131 | content: "\f019"; 132 | } 133 | 134 | .foundicon-tumblr:before { 135 | content: "\f01a"; 136 | } 137 | 138 | .foundicon-video-chat:before { 139 | content: "\f01b"; 140 | } 141 | 142 | .foundicon-digg:before { 143 | content: "\f01c"; 144 | } 145 | 146 | .foundicon-wordpress:before { 147 | content: "\f01d"; 148 | } 149 | -------------------------------------------------------------------------------- /docs/documentation/overview/what-is-capistrano/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: What is Capistrano? 3 | layout: default 4 | --- 5 | 6 | ### Capistrano is a remote server automation tool. 7 | 8 | It supports the scripting and execution of arbitrary tasks, and includes a set of sane-default deployment workflows. 9 | 10 | Capistrano can be used to: 11 | 12 | * Reliably deploy web application to any number of machines simultaneously, 13 | in sequence or as a rolling set 14 | * To automate audits of any number of machines (checking login logs, 15 | enumerating uptimes, and/or applying security patches) 16 | * To script arbitrary workflows over SSH 17 | * To automate common tasks in software teams. 18 | * To drive infrastructure provisioning tools such as *chef-solo*, *Ansible* or similar. 19 | 20 | Capistrano is also *very* scriptable, and can be integrated with any other 21 | Ruby software to form part of a larger tool. 22 | 23 | #### What does it look like? 24 | 25 | ![Capistrano 3.5 / Airbrussh formatter screenshot](/assets/images/airbrussh-screenshot.png) 26 | 27 | 28 | #### What else is in the box? 29 | 30 | There's lots of cool stuff in the Capistrano toy box: 31 | 32 | * Interchangeable output formatters (progress, pretty, html, etc) 33 | * Easy to add support for other source control management software. 34 | * A rudimentary multi-console for running Capistrano interactively. 35 | * Host and Role filters for partial deploys, or partial-cluster maintenance. 36 | * Recipes for the Rails asset pipelines, and database migrations. 37 | * Support for complex environments. 38 | * A sane, expressive API: 39 | 40 | ```ruby 41 | desc "Show off the API" 42 | task :ditty do 43 | 44 | on roles(:all) do |host| 45 | # Capture output from the remote host, and re-use it 46 | # we can reflect on the `host` object passed to the block 47 | # and use the `info` logger method to benefit from the 48 | # output formatter that is selected. 49 | uptime = capture('uptime') 50 | if host.roles.include?(:web) 51 | info "Your webserver #{host} has uptime: #{uptime}" 52 | end 53 | end 54 | 55 | on roles(:app) do 56 | # We can set environmental variables for the duration of a block 57 | # and move the process into a directory, executing arbitrary tasks 58 | # such as letting Rails do some heavy lifting. 59 | with({:rails_env => :production}) do 60 | within('/var/www/my/rails/app') do 61 | execute :rails, :runner, 'MyModel.something' 62 | end 63 | end 64 | end 65 | 66 | on roles(:db) do 67 | # We can even switch users, provided we have support on the remote 68 | # server for switching to that user without being prompted for a 69 | # passphrase. 70 | as 'postgres' do 71 | widgets = capture "echo 'SELECT * FROM widgets;' | psql my_database" 72 | if widgets.to_i < 50 73 | warn "There are fewer than 50 widgets in the database on #{host}!" 74 | end 75 | end 76 | end 77 | 78 | on roles(:all) do 79 | # We can even use `test` the way the Unix gods intended 80 | if test("[ -d /some/directory ]") 81 | info "Phew, it's ok, the directory exists!" 82 | end 83 | end 84 | end 85 | ``` 86 | -------------------------------------------------------------------------------- /spec/lib/capistrano/doctor/variables_doctor_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "capistrano/doctor/variables_doctor" 3 | 4 | module Capistrano 5 | module Doctor 6 | describe VariablesDoctor do 7 | include Capistrano::DSL 8 | 9 | let(:doc) { VariablesDoctor.new } 10 | 11 | before do 12 | set :branch, "master" 13 | set :pty, false 14 | 15 | env.variables.untrusted! do 16 | set :application, "my_app" 17 | set :repo_tree, "public" 18 | set :repo_url, ".git" 19 | set :copy_strategy, :scp 20 | set :custom_setting, "hello" 21 | set "string_setting", "hello" 22 | ask :secret 23 | end 24 | 25 | fetch :custom_setting 26 | end 27 | 28 | after { Capistrano::Configuration.reset! } 29 | 30 | it "prints using 4-space indentation" do 31 | expect { doc.call }.to output(/^ {4}/).to_stdout 32 | end 33 | 34 | it "prints variable names and values" do 35 | expect { doc.call }.to output(/:branch\s+"master"$/).to_stdout 36 | expect { doc.call }.to output(/:pty\s+false$/).to_stdout 37 | expect { doc.call }.to output(/:application\s+"my_app"$/).to_stdout 38 | expect { doc.call }.to output(/:repo_url\s+".git"$/).to_stdout 39 | expect { doc.call }.to output(/:repo_tree\s+"public"$/).to_stdout 40 | expect { doc.call }.to output(/:copy_strategy\s+:scp$/).to_stdout 41 | expect { doc.call }.to output(/:custom_setting\s+"hello"$/).to_stdout 42 | expect { doc.call }.to output(/"string_setting"\s+"hello"$/).to_stdout 43 | end 44 | 45 | it "prints unanswered question variable as " do 46 | expect { doc.call }.to output(/:secret\s+$/).to_stdout 47 | end 48 | 49 | it "prints warning for unrecognized variable" do 50 | expect { doc.call }.to \ 51 | output(/:copy_strategy is not a recognized Capistrano setting/)\ 52 | .to_stdout 53 | end 54 | 55 | it "does not print warning for unrecognized variable that is fetched" do 56 | expect { doc.call }.not_to \ 57 | output(/:custom_setting is not a recognized Capistrano setting/)\ 58 | .to_stdout 59 | end 60 | 61 | it "does not print warning for whitelisted variable" do 62 | expect { doc.call }.not_to \ 63 | output(/:repo_tree is not a recognized Capistrano setting/)\ 64 | .to_stdout 65 | end 66 | 67 | describe "Rake" do 68 | before do 69 | load File.expand_path("../../../../../lib/capistrano/doctor.rb", 70 | __FILE__) 71 | end 72 | 73 | after do 74 | Rake::Task.clear 75 | end 76 | 77 | it "has an doctor:variables task that calls VariablesDoctor", capture_io: true do 78 | VariablesDoctor.any_instance.expects(:call) 79 | Rake::Task["doctor:variables"].invoke 80 | end 81 | 82 | it "has a doctor task that depends on doctor:variables" do 83 | expect(Rake::Task["doctor"].prerequisites).to \ 84 | include("doctor:variables") 85 | end 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /docs/documentation/getting-started/flow/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Flow 3 | layout: default 4 | --- 5 | 6 | Capistrano v3 provides a default **deploy flow** and a **rollback flow**: 7 | 8 | ### Deploy flow 9 | 10 | When you run `cap production deploy`, it invokes the following tasks in 11 | sequence: 12 | 13 | ```ruby 14 | deploy:starting - start a deployment, make sure everything is ready 15 | deploy:started - started hook (for custom tasks) 16 | deploy:updating - update server(s) with a new release 17 | deploy:updated - updated hook 18 | deploy:publishing - publish the new release 19 | deploy:published - published hook 20 | deploy:finishing - finish the deployment, clean up everything 21 | deploy:finished - finished hook 22 | ``` 23 | 24 | Notice there are several hook tasks e.g. `:started`, `:updated` for 25 | you to hook up custom tasks into the flow using `after()` and `before()`. 26 | 27 | ### Rollback flow 28 | 29 | When you run `cap production deploy:rollback`, it invokes the following 30 | tasks in sequence: 31 | 32 | ```ruby 33 | deploy:starting 34 | deploy:started 35 | deploy:reverting - revert server(s) to previous release 36 | deploy:reverted - reverted hook 37 | deploy:publishing 38 | deploy:published 39 | deploy:finishing_rollback - finish the rollback, clean up everything 40 | deploy:finished 41 | ``` 42 | 43 | As you can see, rollback flow shares many tasks with deploy flow. But note 44 | that, rollback flow runs its own `:finishing_rollback` task because its 45 | cleanup process is usually different from deploy flow. 46 | 47 | ### Flow examples 48 | 49 | Assume you require the following files in `Capfile`, 50 | 51 | ```ruby 52 | # Capfile 53 | require 'capistrano/setup' 54 | require 'capistrano/deploy' 55 | require 'capistrano/bundler' 56 | require 'capistrano/rails/assets' 57 | require 'capistrano/rails/migrations' 58 | ``` 59 | 60 | When you run `cap production deploy`, it runs these tasks: 61 | 62 | ```ruby 63 | deploy 64 | deploy:starting 65 | [before] 66 | deploy:ensure_stage 67 | deploy:set_shared_assets 68 | deploy:check 69 | deploy:started 70 | deploy:updating 71 | git:create_release 72 | deploy:symlink:shared 73 | deploy:updated 74 | [before] 75 | deploy:bundle 76 | [after] 77 | deploy:migrate 78 | deploy:compile_assets 79 | deploy:normalize_assets 80 | deploy:publishing 81 | deploy:symlink:release 82 | deploy:published 83 | deploy:finishing 84 | deploy:cleanup 85 | deploy:finished 86 | deploy:log_revision 87 | ``` 88 | 89 | For `cap production deploy:rollback`, it runs these tasks: 90 | 91 | ```ruby 92 | deploy 93 | deploy:starting 94 | [before] 95 | deploy:ensure_stage 96 | deploy:set_shared_assets 97 | deploy:check 98 | deploy:started 99 | deploy:reverting 100 | deploy:revert_release 101 | deploy:reverted 102 | [after] 103 | deploy:rollback_assets 104 | deploy:publishing 105 | deploy:symlink:release 106 | deploy:published 107 | deploy:finishing_rollback 108 | deploy:cleanup_rollback 109 | deploy:finished 110 | deploy:log_revision 111 | ``` 112 | -------------------------------------------------------------------------------- /lib/capistrano/plugin.rb: -------------------------------------------------------------------------------- 1 | require "capistrano/all" 2 | require "rake/tasklib" 3 | 4 | # IMPORTANT: The Capistrano::Plugin system is not yet considered a stable, 5 | # public API, and is subject to change without notice. Eventually it will be 6 | # officially documented and supported, but for now, use it at your own risk. 7 | # 8 | # Base class for Capistrano plugins. Makes building a Capistrano plugin as easy 9 | # as writing a `Capistrano::Plugin` subclass and overriding any or all of its 10 | # three template methods: 11 | # 12 | # * set_defaults 13 | # * register_hooks 14 | # * define_tasks 15 | # 16 | # Within the plugin you can use any methods of the Rake or Capistrano DSLs, like 17 | # `fetch`, `invoke`, etc. In cases when you need to use SSHKit's backend outside 18 | # of an `on` block, use the `backend` convenience method. E.g. `backend.test`, 19 | # `backend.execute`, or `backend.capture`. 20 | # 21 | # Package up and distribute your plugin class as a gem and you're good to go! 22 | # 23 | # To use a plugin, all a user has to do is install it in the Capfile, like this: 24 | # 25 | # # Capfile 26 | # require "capistrano/superfancy" 27 | # install_plugin Capistrano::Superfancy 28 | # 29 | # Or, to install the plugin without its hooks: 30 | # 31 | # # Capfile 32 | # require "capistrano/superfancy" 33 | # install_plugin Capistrano::Superfancy, load_hooks: false 34 | # 35 | class Capistrano::Plugin < Rake::TaskLib 36 | include Capistrano::DSL 37 | 38 | # Implemented by subclasses to provide default values for settings needed by 39 | # this plugin. Typically done using the `set_if_empty` Capistrano DSL method. 40 | # 41 | # Example: 42 | # 43 | # def set_defaults 44 | # set_if_empty :my_plugin_option, true 45 | # end 46 | # 47 | def set_defaults; end 48 | 49 | # Implemented by subclasses to hook into Capistrano's deployment flow using 50 | # using the `before` and `after` DSL methods. Note that `register_hooks` will 51 | # not be called if the user has opted-out of hooks when installing the plugin. 52 | # 53 | # Example: 54 | # 55 | # def register_hooks 56 | # after "deploy:updated", "my_plugin:do_something" 57 | # end 58 | # 59 | def register_hooks; end 60 | 61 | # Implemented by subclasses to define Rake tasks. Typically a plugin will call 62 | # `eval_rakefile` to load Rake tasks from a separate .rake file. 63 | # 64 | # Example: 65 | # 66 | # def define_tasks 67 | # eval_rakefile File.expand_path("../tasks.rake", __FILE__) 68 | # end 69 | # 70 | # For simple tasks, you can define them inline. No need for a separate file. 71 | # 72 | # def define_tasks 73 | # desc "Do something fantastic." 74 | # task "my_plugin:fantastic" do 75 | # ... 76 | # end 77 | # end 78 | # 79 | def define_tasks; end 80 | 81 | private 82 | 83 | # Read and eval a .rake file in such a way that `self` within the .rake file 84 | # refers to this plugin instance. This gives the tasks in the file access to 85 | # helper methods defined by the plugin. 86 | def eval_rakefile(path) 87 | contents = IO.read(path) 88 | instance_eval(contents, path, 1) 89 | end 90 | 91 | # Convenience to access the current SSHKit backend outside of an `on` block. 92 | def backend 93 | SSHKit::Backend.current 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /docs/documentation/advanced-features/filtering/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Filtering 3 | layout: default 4 | --- 5 | 6 | Filtering is the term given to reducing the entire set of servers declared in a stage file 7 | to a smaller set. There are three types of filters used in Capistrano (Host, Role and 8 | Property) and they take effect in two quite different ways because of the two distinct 9 | uses to which the declarations of servers, roles and properties are put in tasks: 10 | 11 | * To determine _configurations_: typically by using the `roles()`, `release_roles()` and 12 | `primary()` methods. Typically these are used outside the scope of the `on()` method. 13 | 14 | * To _interact_ with remote hosts using the `on()` method 15 | 16 | An illustration of this would be to create a `/etc/krb5.conf` file containing the list of 17 | available KDC's by using the list of servers returned by `roles(:kdc)` and then uploading 18 | it to all client machines using `on(roles(:all)) do upload!(file) end` 19 | 20 | A problem with this arises when _filters_ are used. Filters are designed to limit the 21 | actual set of hosts that are used to a subset of those in the overall stage, but how 22 | should that apply in the above case? 23 | 24 | If the filter applies to both the _interaction_ and _configuration_ aspects, any configuration 25 | files deployed will not be the same as those on the hosts excluded by the filters. This is 26 | almost certainly not what is wanted, the filters should apply only to the _interactions_ 27 | ensuring that any configuration files deployed will be identical across the stage. 28 | 29 | So we define two different categories of filter, the interaction ones which are called _On-Filters_ 30 | and the configuration ones which are _Property-Filters_ 31 | 32 | ### On-Filtering 33 | 34 | On-filters apply only to the `on()` method that invokes SSH. There are two default types: 35 | 36 | * [Host Filters](/documentation/advanced-features/host-filtering/), and 37 | 38 | * [Role Filters](/documentation/advanced-features/role-filtering/) 39 | 40 | 41 | In both the above cases, when filters are specified using comma separated lists, the final 42 | filter is the _union_ of all of the components. However when multiple filters are declared 43 | the result is the _intersection_. 44 | 45 | This means that you can filter by both role and host but you will get the _intersection_ 46 | of the servers. For example, lets say you filtered by the role `app`, then by 47 | the hostnames `server1` and `server2`. Capistrano would first filter the 48 | available servers to only those with the role `app`, then filter them 49 | to look for servers with the hostname `server1` or `server2`. If only `server2` 50 | had the role `app` (`server1` has some other role), then in this situation your 51 | task would only run on `server2`. 52 | 53 | Custom filters may also be added; see 54 | [Custom Filters](/documentation/advanced-features/custom-filters/). 55 | 56 | ### Property-Filtering 57 | 58 | Property-filters select servers based on the value of their properties alone and 59 | are specified by options passed to the `roles()` method (and implicitly in methods 60 | like `release_roles()` and `primary()`) 61 | 62 | An example of that is the 'no_release' property and it's use in the `release_roles()` method. 63 | 64 | See the [documentation](/documentation/advanced-features/property-filtering/) for 65 | details 66 | -------------------------------------------------------------------------------- /docs/documentation/advanced-features/custom-filters/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Custom Filters 3 | layout: default 4 | --- 5 | 6 | Custom filters (specifically, Custom On-Filters) limit the hosts that are being 7 | deployed to, in the same way as 8 | [Host](/documentation/advanced-features/host-filtering/) and 9 | [Role](/documentation/advanced-features/role-filtering/) filters, but the exact 10 | method used to filter servers is up to the user of Capistrano. 11 | 12 | Filters may be added to Capistrano's list of filters by using the 13 | `Configuration#add_filter` method. Filters must respond to a `filter` method, 14 | which will be given an array of Servers, and should return a subset of that 15 | array (the servers which passed the filter). 16 | 17 | `Configuration#add_filter` may also take a block, in which case the block is 18 | expected to be unary. The block will be passed an array of servers, and is 19 | expected to return a subset of that array. 20 | 21 | Either a block or object may be passed to `add_filter`, but not both. 22 | 23 | ### Example 24 | 25 | You may have a large group of servers that are partitioned into separate regions 26 | that correspond to actual geographic regions. Usually, you deploy to all of 27 | them, but there are cases where you want to deploy to a specific region. 28 | 29 | Capistrano recognizes the concept of a server's *role* and *hostname*, but has 30 | no concept of a *region*. In this case, you can construct your own filter that 31 | selects servers based on their region. When defining servers, you may provide 32 | them with a `region` property, and use that property in your filter. 33 | 34 | 35 | The filter could look like this: 36 | 37 | `config/deploy.rb` 38 | 39 | ```ruby 40 | class RegionFilter 41 | 42 | def initialize(regions) 43 | @regions = Array(regions) 44 | end 45 | 46 | def filter(servers) 47 | servers.select {|server| 48 | region = server.fetch(:region) 49 | region && @regions.include?(region) 50 | } 51 | end 52 | 53 | end 54 | ``` 55 | 56 | You would add servers like this: 57 | 58 | `config/deploy/production.rb` 59 | 60 | ```ruby 61 | server('123.123.123.123', region: 'north-east') 62 | server('12.12.12.12', region: 'south-west') 63 | server('4.5.6.7', region: 'mid-west') 64 | ``` 65 | 66 | To tell Capistrano to use this filter, you would use the 67 | `Configuration#add_filter` method. In this example, we look at the `REGIONS` 68 | environment variable, and take it to be a comma-separated list of regions that 69 | we're interested in: 70 | 71 | `config/deploy.rb` 72 | 73 | ```ruby 74 | if ENV['REGIONS'] 75 | regions = ENV['REGIONS'].split(',') 76 | filter = RegionFilter.new(regions) 77 | Capistrano::Configuration.env.add_filter(filter) 78 | end 79 | ``` 80 | 81 | We obtain a list of regions to deploy to from the environment variable, 82 | construct a new filter with those regions, and add it to Capistrano's list of 83 | filters. 84 | 85 | Of course, we're not limited to regions. Any time you can classify or partition 86 | a list of servers in a way that you only want to deploy to some of them, you can 87 | use a custom filter. For another example, you might arbitrarily assign your 88 | servers to either an *A* group or a *B* group, and deploy a new version only to 89 | the *B* group as a simple variant of A/B Testing. 90 | -------------------------------------------------------------------------------- /lib/capistrano/scm.rb: -------------------------------------------------------------------------------- 1 | module Capistrano 2 | # Base class for SCM strategy providers. 3 | # 4 | # @abstract 5 | # 6 | # @attr_reader [Rake] context 7 | # 8 | # @author Hartog de Mik 9 | # 10 | class SCM 11 | attr_reader :context 12 | 13 | # Provide a wrapper for the SCM that loads a strategy for the user. 14 | # 15 | # @param [Rake] context The context in which the strategy should run 16 | # @param [Module] strategy A module to include into the SCM instance. The 17 | # module should provide the abstract methods of Capistrano::SCM 18 | # 19 | def initialize(context, strategy) 20 | @context = context 21 | singleton = class << self; self; end 22 | singleton.send(:include, strategy) 23 | end 24 | 25 | # Call test in context 26 | def test!(*args) 27 | context.test(*args) 28 | end 29 | 30 | # The repository URL according to the context 31 | def repo_url 32 | context.repo_url 33 | end 34 | 35 | # The repository path according to the context 36 | def repo_path 37 | context.repo_path 38 | end 39 | 40 | # The release path according to the context 41 | def release_path 42 | context.release_path 43 | end 44 | 45 | # Fetch a var from the context 46 | # @param [Symbol] variable The variable to fetch 47 | # @param [Object] default The default value if not found 48 | # 49 | def fetch(*args) 50 | context.fetch(*args) 51 | end 52 | 53 | # @abstract 54 | # 55 | # Your implementation should check the existence of a cache repository on 56 | # the deployment target 57 | # 58 | # @return [Boolean] 59 | # 60 | def test 61 | raise NotImplementedError, "Your SCM strategy module should provide a #test method" 62 | end 63 | 64 | # @abstract 65 | # 66 | # Your implementation should check if the specified remote-repository is 67 | # available. 68 | # 69 | # @return [Boolean] 70 | # 71 | def check 72 | raise NotImplementedError, "Your SCM strategy module should provide a #check method" 73 | end 74 | 75 | # @abstract 76 | # 77 | # Create a (new) clone of the remote-repository on the deployment target 78 | # 79 | # @return void 80 | # 81 | def clone 82 | raise NotImplementedError, "Your SCM strategy module should provide a #clone method" 83 | end 84 | 85 | # @abstract 86 | # 87 | # Update the clone on the deployment target 88 | # 89 | # @return void 90 | # 91 | def update 92 | raise NotImplementedError, "Your SCM strategy module should provide a #update method" 93 | end 94 | 95 | # @abstract 96 | # 97 | # Copy the contents of the cache-repository onto the release path 98 | # 99 | # @return void 100 | # 101 | def release 102 | raise NotImplementedError, "Your SCM strategy module should provide a #release method" 103 | end 104 | 105 | # @abstract 106 | # 107 | # Identify the SHA of the commit that will be deployed. This will most likely involve SshKit's capture method. 108 | # 109 | # @return void 110 | # 111 | def fetch_revision 112 | raise NotImplementedError, "Your SCM strategy module should provide a #fetch_revision method" 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /lib/capistrano/scm/tasks/git.rake: -------------------------------------------------------------------------------- 1 | # This trick lets us access the Git plugin within `on` blocks. 2 | git_plugin = self 3 | 4 | namespace :git do 5 | desc "Upload the git wrapper script, this script guarantees that we can script git without getting an interactive prompt" 6 | task :wrapper do 7 | on release_roles(:all), in: :groups, limit: fetch(:git_max_concurrent_connections), wait: fetch(:git_wait_interval) do 8 | execute :mkdir, "-p", File.dirname(fetch(:git_wrapper_path)).shellescape 9 | upload! StringIO.new("#!/bin/sh -e\nexec /usr/bin/env ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no \"$@\"\n"), fetch(:git_wrapper_path) 10 | execute :chmod, "700", fetch(:git_wrapper_path).shellescape 11 | end 12 | end 13 | 14 | desc "Check that the repository is reachable" 15 | task check: :'git:wrapper' do 16 | fetch(:branch) 17 | on release_roles(:all), in: :groups, limit: fetch(:git_max_concurrent_connections), wait: fetch(:git_wait_interval) do 18 | with fetch(:git_environmental_variables) do 19 | git_plugin.check_repo_is_reachable 20 | end 21 | end 22 | end 23 | 24 | desc "Clone the repo to the cache" 25 | task clone: :'git:wrapper' do 26 | on release_roles(:all), in: :groups, limit: fetch(:git_max_concurrent_connections), wait: fetch(:git_wait_interval) do 27 | if git_plugin.repo_mirror_exists? 28 | info t(:mirror_exists, at: repo_path) 29 | else 30 | within deploy_path do 31 | with fetch(:git_environmental_variables) do 32 | git_plugin.clone_repo 33 | end 34 | end 35 | end 36 | end 37 | end 38 | 39 | desc "Update the repo mirror to reflect the origin state" 40 | task update: :'git:clone' do 41 | on release_roles(:all), in: :groups, limit: fetch(:git_max_concurrent_connections), wait: fetch(:git_wait_interval) do 42 | within repo_path do 43 | with fetch(:git_environmental_variables) do 44 | git_plugin.update_mirror 45 | git_plugin.verify_commit if fetch(:git_verify_commit) 46 | end 47 | end 48 | end 49 | end 50 | 51 | desc "Copy repo to releases" 52 | task create_release: :'git:update' do 53 | on release_roles(:all), in: :groups, limit: fetch(:git_max_concurrent_connections), wait: fetch(:git_wait_interval) do 54 | with fetch(:git_environmental_variables) do 55 | within repo_path do 56 | execute :mkdir, "-p", release_path 57 | git_plugin.archive_to_release_path 58 | end 59 | end 60 | end 61 | end 62 | 63 | desc "Determine the revision that will be deployed" 64 | task :set_current_revision do 65 | on release_roles(:all), in: :groups, limit: fetch(:git_max_concurrent_connections), wait: fetch(:git_wait_interval) do 66 | within repo_path do 67 | with fetch(:git_environmental_variables) do 68 | set :current_revision, git_plugin.fetch_revision 69 | end 70 | end 71 | end 72 | end 73 | 74 | desc "Determine the unix timestamp that the revision that will be deployed was created" 75 | task :set_current_revision_time do 76 | on release_roles(:all), in: :groups, limit: fetch(:git_max_concurrent_connections), wait: fetch(:git_wait_interval) do 77 | within repo_path do 78 | with fetch(:git_environmental_variables) do 79 | set :current_revision_time, git_plugin.fetch_revision_time 80 | end 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /spec/lib/capistrano/configuration/question_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Capistrano 4 | class Configuration 5 | describe Question do 6 | let(:question) { Question.new(key, default, stdin: stdin) } 7 | let(:question_without_echo) { Question.new(key, default, echo: false, stdin: stdin) } 8 | let(:question_without_default) { Question.new(key, nil, stdin: stdin) } 9 | let(:question_prompt) { Question.new(key, default, stdin: stdin, prompt: "Your favorite branch") } 10 | let(:question_prompt_without_default) { Question.new(key, nil, stdin: stdin, prompt: "Your favorite branch") } 11 | let(:default) { :default } 12 | let(:key) { :branch } 13 | let(:stdin) { stub(tty?: true) } 14 | 15 | describe ".new" do 16 | it "takes a key, default, options" do 17 | question 18 | end 19 | end 20 | 21 | describe "#call" do 22 | context "value is entered" do 23 | let(:branch) { "branch" } 24 | 25 | it "returns the echoed value" do 26 | $stdout.expects(:print).with("Please enter branch (default): ") 27 | stdin.expects(:gets).returns(branch) 28 | stdin.expects(:noecho).never 29 | 30 | expect(question.call).to eq(branch) 31 | end 32 | 33 | it "returns the value but does not echo it" do 34 | $stdout.expects(:print).with("Please enter branch (default): ") 35 | stdin.expects(:noecho).returns(branch) 36 | $stdout.expects(:print).with("\n") 37 | 38 | expect(question_without_echo.call).to eq(branch) 39 | end 40 | 41 | it "returns the value but has no default between parenthesis" do 42 | $stdout.expects(:print).with("Please enter branch: ") 43 | stdin.expects(:gets).returns(branch) 44 | stdin.expects(:noecho).never 45 | 46 | expect(question_without_default.call).to eq(branch) 47 | end 48 | 49 | it "uses prompt and returns the value" do 50 | $stdout.expects(:print).with("Your favorite branch (default): ") 51 | stdin.expects(:gets).returns(branch) 52 | stdin.expects(:noecho).never 53 | 54 | expect(question_prompt.call).to eq(branch) 55 | end 56 | 57 | it "uses prompt and returns the value but has no default between parenthesis" do 58 | $stdout.expects(:print).with("Your favorite branch: ") 59 | stdin.expects(:gets).returns(branch) 60 | stdin.expects(:noecho).never 61 | 62 | expect(question_prompt_without_default.call).to eq(branch) 63 | end 64 | end 65 | 66 | context "value is not entered" do 67 | let(:branch) { default } 68 | 69 | before do 70 | $stdout.expects(:print).with("Please enter branch (default): ") 71 | stdin.expects(:gets).returns("") 72 | end 73 | 74 | it "returns the default as the value" do 75 | expect(question.call).to eq(branch) 76 | end 77 | end 78 | 79 | context "tty unavailable", capture_io: true do 80 | before do 81 | stdin.expects(:gets).never 82 | stdin.expects(:tty?).returns(false) 83 | end 84 | 85 | it "returns the default as the value" do 86 | expect(question.call).to eq(default) 87 | end 88 | end 89 | end 90 | end 91 | end 92 | end 93 | --------------------------------------------------------------------------------