statistrano 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/changelog.md +161 -0
- data/doc/config/file-permissions.md +33 -0
- data/doc/config/log-files.md +32 -0
- data/doc/config/task-definitions.md +88 -0
- data/doc/getting-started.md +96 -0
- data/doc/strategies/base.md +38 -0
- data/doc/strategies/branches.md +82 -0
- data/doc/strategies/releases.md +110 -0
- data/doc/strategies.md +17 -0
- data/lib/statistrano/config/configurable.rb +53 -0
- data/lib/statistrano/config/rake_task_with_context_creation.rb +43 -0
- data/lib/statistrano/config.rb +52 -0
- data/lib/statistrano/deployment/log_file.rb +44 -0
- data/lib/statistrano/deployment/manifest.rb +88 -0
- data/lib/statistrano/deployment/rake_tasks.rb +74 -0
- data/lib/statistrano/deployment/registerable.rb +11 -0
- data/lib/statistrano/deployment/releaser/revisions.rb +163 -0
- data/lib/statistrano/deployment/releaser/single.rb +48 -0
- data/lib/statistrano/deployment/releaser.rb +2 -0
- data/lib/statistrano/deployment/strategy/base.rb +132 -0
- data/lib/statistrano/deployment/strategy/branches/index/template.html.erb +78 -0
- data/lib/statistrano/deployment/strategy/branches/index.rb +40 -0
- data/lib/statistrano/deployment/strategy/branches/release.rb +73 -0
- data/lib/statistrano/deployment/strategy/branches.rb +198 -0
- data/lib/statistrano/deployment/strategy/check_git.rb +43 -0
- data/lib/statistrano/deployment/strategy/invoke_tasks.rb +58 -0
- data/lib/statistrano/deployment/strategy/releases.rb +76 -0
- data/lib/statistrano/deployment/strategy.rb +37 -0
- data/lib/statistrano/deployment.rb +10 -0
- data/lib/statistrano/log/default_logger.rb +105 -0
- data/lib/statistrano/log.rb +33 -0
- data/lib/statistrano/remote/file.rb +79 -0
- data/lib/statistrano/remote.rb +111 -0
- data/lib/statistrano/shell.rb +17 -0
- data/lib/statistrano/util/file_permissions.rb +34 -0
- data/lib/statistrano/util.rb +27 -0
- data/lib/statistrano/version.rb +3 -0
- data/lib/statistrano.rb +55 -0
- data/readme.md +247 -0
- data/spec/integration_tests/base_integration_spec.rb +103 -0
- data/spec/integration_tests/branches_integration_spec.rb +189 -0
- data/spec/integration_tests/releases/deploy_integration_spec.rb +116 -0
- data/spec/integration_tests/releases/list_releases_integration_spec.rb +38 -0
- data/spec/integration_tests/releases/prune_releases_integration_spec.rb +86 -0
- data/spec/integration_tests/releases/rollback_release_integration_spec.rb +46 -0
- data/spec/lib/statistrano/config/configurable_spec.rb +88 -0
- data/spec/lib/statistrano/config/rake_task_with_context_creation_spec.rb +73 -0
- data/spec/lib/statistrano/config_spec.rb +34 -0
- data/spec/lib/statistrano/deployment/log_file_spec.rb +75 -0
- data/spec/lib/statistrano/deployment/manifest_spec.rb +171 -0
- data/spec/lib/statistrano/deployment/rake_tasks_spec.rb +107 -0
- data/spec/lib/statistrano/deployment/registerable_spec.rb +19 -0
- data/spec/lib/statistrano/deployment/releaser/revisions_spec.rb +486 -0
- data/spec/lib/statistrano/deployment/releaser/single_spec.rb +59 -0
- data/spec/lib/statistrano/deployment/strategy/base_spec.rb +158 -0
- data/spec/lib/statistrano/deployment/strategy/branches_spec.rb +19 -0
- data/spec/lib/statistrano/deployment/strategy/check_git_spec.rb +39 -0
- data/spec/lib/statistrano/deployment/strategy/invoke_tasks_spec.rb +66 -0
- data/spec/lib/statistrano/deployment/strategy/releases_spec.rb +257 -0
- data/spec/lib/statistrano/deployment/strategy_spec.rb +76 -0
- data/spec/lib/statistrano/deployment_spec.rb +4 -0
- data/spec/lib/statistrano/log/default_logger_spec.rb +172 -0
- data/spec/lib/statistrano/log_spec.rb +36 -0
- data/spec/lib/statistrano/remote/file_spec.rb +166 -0
- data/spec/lib/statistrano/remote_spec.rb +226 -0
- data/spec/lib/statistrano/util/file_permissions_spec.rb +25 -0
- data/spec/lib/statistrano/util_spec.rb +23 -0
- data/spec/lib/statistrano_spec.rb +52 -0
- data/spec/spec_helper.rb +86 -0
- data/spec/support/given.rb +39 -0
- metadata +223 -0
| @@ -0,0 +1,132 @@ | |
| 1 | 
            +
            module Statistrano
         | 
| 2 | 
            +
              module Deployment
         | 
| 3 | 
            +
                module Strategy
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  # A Base Deployment Instance
         | 
| 6 | 
            +
                  # it holds the common methods needed
         | 
| 7 | 
            +
                  # to create a deployment
         | 
| 8 | 
            +
                  class Base
         | 
| 9 | 
            +
                    extend Deployment::Registerable
         | 
| 10 | 
            +
                    extend Config::Configurable
         | 
| 11 | 
            +
                    include InvokeTasks
         | 
| 12 | 
            +
                    include CheckGit
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    attr_reader :name
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    register_strategy :base
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    options :remote_dir, :local_dir,
         | 
| 19 | 
            +
                            :hostname, :user, :password, :keys, :forward_agent,
         | 
| 20 | 
            +
                            :build_task, :post_deploy_task,
         | 
| 21 | 
            +
                            :check_git, :git_branch, :repo_url,
         | 
| 22 | 
            +
                            :dir_permissions, :file_permissions, :rsync_flags,
         | 
| 23 | 
            +
                            :log_file_path, :log_file_entry
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    option  :remotes, []
         | 
| 26 | 
            +
                    option  :dir_permissions, 755
         | 
| 27 | 
            +
                    option  :file_permissions, 644
         | 
| 28 | 
            +
                    option  :rsync_flags, '-aqz --delete-after'
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    option  :verbose, false
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    task :deploy,      :deploy,                  "Deploy to remote"
         | 
| 33 | 
            +
                    task :build,       :invoke_build_task,       "Run build task"
         | 
| 34 | 
            +
                    task :post_deploy, :invoke_post_deploy_task, "Run post deploy task"
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    # create a new deployment instance
         | 
| 37 | 
            +
                    # @param name [String]
         | 
| 38 | 
            +
                    # @return [Void]
         | 
| 39 | 
            +
                    def initialize name
         | 
| 40 | 
            +
                      @name = name
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    # Standard deployment flow
         | 
| 44 | 
            +
                    # @return [Void]
         | 
| 45 | 
            +
                    def deploy
         | 
| 46 | 
            +
                      unless safe_to_deploy?
         | 
| 47 | 
            +
                        Log.error "exiting due to git check failing"
         | 
| 48 | 
            +
                        abort()
         | 
| 49 | 
            +
                      end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                      build_data = invoke_build_task
         | 
| 52 | 
            +
                      build_data = ensure_data_is_hash build_data
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                      unless safe_to_deploy?
         | 
| 55 | 
            +
                        Log.error "exiting due to git check failing",
         | 
| 56 | 
            +
                                  "your build task modified checked in files"
         | 
| 57 | 
            +
                        abort()
         | 
| 58 | 
            +
                      end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                      remotes.each do |remote|
         | 
| 61 | 
            +
                        persisted_releaser.create_release remote, build_data
         | 
| 62 | 
            +
                      end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                      post_deploy_data = invoke_post_deploy_task
         | 
| 65 | 
            +
                      post_deploy_data = ensure_data_is_hash post_deploy_data
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                      if config.log_file_path
         | 
| 68 | 
            +
                        log_entry = config.log_file_entry.call self, persisted_releaser,
         | 
| 69 | 
            +
                                                               build_data, post_deploy_data
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                        remotes.each do |remote|
         | 
| 72 | 
            +
                          log_file(remote).append! log_entry
         | 
| 73 | 
            +
                        end
         | 
| 74 | 
            +
                      end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                      flush_persisted_releaser!
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                    def register_tasks
         | 
| 80 | 
            +
                      RakeTasks.register self
         | 
| 81 | 
            +
                    end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                    def remotes
         | 
| 84 | 
            +
                      return @_remotes if @_remotes
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                      @_remotes = config.options[:remotes].map do |remote_options|
         | 
| 87 | 
            +
                        Remote.new Config.new( config.options.dup.merge(remote_options) )
         | 
| 88 | 
            +
                      end
         | 
| 89 | 
            +
                      @_remotes.push Remote.new(config) if @_remotes.empty?
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                      return @_remotes
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                    def log_file remote=remotes.first
         | 
| 95 | 
            +
                      Deployment::LogFile.new config.log_file_path, remote
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                    def persisted_releaser
         | 
| 99 | 
            +
                      @_persisted_releaser ||= releaser
         | 
| 100 | 
            +
                    end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                    def flush_persisted_releaser!
         | 
| 103 | 
            +
                      @_persisted_releaser = nil
         | 
| 104 | 
            +
                    end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    private
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                      def resolve_log_file_path
         | 
| 109 | 
            +
                        if config.log_file_path.start_with? '/'
         | 
| 110 | 
            +
                          config.log_file_path
         | 
| 111 | 
            +
                        else
         | 
| 112 | 
            +
                          File.join( config.remote_dir, config.log_file_path )
         | 
| 113 | 
            +
                        end
         | 
| 114 | 
            +
                      end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                      def ensure_data_is_hash data
         | 
| 117 | 
            +
                        if data.respond_to? :to_hash
         | 
| 118 | 
            +
                          data = data.to_hash
         | 
| 119 | 
            +
                        else
         | 
| 120 | 
            +
                          data = {}
         | 
| 121 | 
            +
                        end
         | 
| 122 | 
            +
                      end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                      def releaser
         | 
| 125 | 
            +
                        Releaser::Single.new
         | 
| 126 | 
            +
                      end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                  end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                end
         | 
| 131 | 
            +
              end
         | 
| 132 | 
            +
            end
         | 
| @@ -0,0 +1,78 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            <!DOCTYPE html>
         | 
| 3 | 
            +
            <html role="document">
         | 
| 4 | 
            +
              <head>
         | 
| 5 | 
            +
                <meta charset=utf-8>
         | 
| 6 | 
            +
                <meta content=en name=language>
         | 
| 7 | 
            +
                <meta content=IE=edge,chrome=1 http-equiv=X-UA-Compatible>
         | 
| 8 | 
            +
                <meta content=width=device-width name=viewport>
         | 
| 9 | 
            +
                <style type=text/css>
         | 
| 10 | 
            +
                  body {
         | 
| 11 | 
            +
                    background: #f9f9f9;
         | 
| 12 | 
            +
                    color: #5f5f5f;
         | 
| 13 | 
            +
                    font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
         | 
| 14 | 
            +
                    -webkit-font-smoothing: antialiased;
         | 
| 15 | 
            +
                    margin: 0;
         | 
| 16 | 
            +
                    padding: 0;
         | 
| 17 | 
            +
                  }
         | 
| 18 | 
            +
                  h1 {
         | 
| 19 | 
            +
                    font-size: 3em;
         | 
| 20 | 
            +
                    margin: .5em 0;
         | 
| 21 | 
            +
                    line-height: 1;
         | 
| 22 | 
            +
                  }
         | 
| 23 | 
            +
                  .content {
         | 
| 24 | 
            +
                    max-width: 640px;
         | 
| 25 | 
            +
                    padding: 3em 1em;
         | 
| 26 | 
            +
                    margin: 0 auto;
         | 
| 27 | 
            +
                    text-align: center;
         | 
| 28 | 
            +
                  }
         | 
| 29 | 
            +
                  ul {
         | 
| 30 | 
            +
                    padding: 0;
         | 
| 31 | 
            +
                    outline: 1px solid #a6a6a6;
         | 
| 32 | 
            +
                    list-style: none;
         | 
| 33 | 
            +
                    display: inline-block;
         | 
| 34 | 
            +
                  }
         | 
| 35 | 
            +
                  li {
         | 
| 36 | 
            +
                    padding: 1em 1.5em;
         | 
| 37 | 
            +
                    border-bottom: 1px solid #c2c2c2;
         | 
| 38 | 
            +
                    text-align: left;
         | 
| 39 | 
            +
                  }
         | 
| 40 | 
            +
                    li:last-child {
         | 
| 41 | 
            +
                      border-bottom: none;
         | 
| 42 | 
            +
                    }
         | 
| 43 | 
            +
                  li a {
         | 
| 44 | 
            +
                    margin: 0;
         | 
| 45 | 
            +
                    padding: 0 1em 0 0;
         | 
| 46 | 
            +
                    display: inline-block;
         | 
| 47 | 
            +
                    font-weight: bold;
         | 
| 48 | 
            +
                    text-decoration: none;
         | 
| 49 | 
            +
                    color: inherit;
         | 
| 50 | 
            +
                         -o-transition: color .2s ease;
         | 
| 51 | 
            +
                       -moz-transition: color .2s ease;
         | 
| 52 | 
            +
                    -webkit-transition: color .2s ease;
         | 
| 53 | 
            +
                            transition: color .2s ease;
         | 
| 54 | 
            +
                  }
         | 
| 55 | 
            +
                    li a:hover {
         | 
| 56 | 
            +
                      color: #449A88;
         | 
| 57 | 
            +
                    }
         | 
| 58 | 
            +
                  li small {
         | 
| 59 | 
            +
                    margin: .25em 0 0 0;
         | 
| 60 | 
            +
                    padding: 0;
         | 
| 61 | 
            +
                    font-size: .75em;
         | 
| 62 | 
            +
                    display: inline-block;
         | 
| 63 | 
            +
                    color: #999;
         | 
| 64 | 
            +
                  }
         | 
| 65 | 
            +
                </style>
         | 
| 66 | 
            +
              </head>
         | 
| 67 | 
            +
              <body>
         | 
| 68 | 
            +
                <section class="content">
         | 
| 69 | 
            +
                  <h1>Branch Index</h1>
         | 
| 70 | 
            +
                  <ul><% releases.each do |release| %>
         | 
| 71 | 
            +
                    <li>
         | 
| 72 | 
            +
                      <a href="<%= release.href %>"><%= release.name %></a>
         | 
| 73 | 
            +
                      <small>updated: <%= Time.at(release.time).strftime('%A %b %d, %Y at %l:%M %P') %></small>
         | 
| 74 | 
            +
                    </li>
         | 
| 75 | 
            +
                  <% end %></ul>
         | 
| 76 | 
            +
                </section>
         | 
| 77 | 
            +
              </body>
         | 
| 78 | 
            +
            </html>
         | 
| @@ -0,0 +1,40 @@ | |
| 1 | 
            +
            require 'erb'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Statistrano
         | 
| 4 | 
            +
              module Deployment
         | 
| 5 | 
            +
                module Strategy
         | 
| 6 | 
            +
                  class Branches
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                    class Index
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                      attr_reader :template, :releases
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                      def initialize releases, template_path=nil
         | 
| 13 | 
            +
                        @releases = releases
         | 
| 14 | 
            +
                        @template = IO.read(template_path || File.expand_path("../index/template.html.erb", __FILE__))
         | 
| 15 | 
            +
                      end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                      def to_html
         | 
| 18 | 
            +
                        ERB.new(template).result(ERBContext.new(releases).get_binding)
         | 
| 19 | 
            +
                      end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                      class ERBContext
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                        attr_reader :releases
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                        def initialize releases
         | 
| 26 | 
            +
                          @releases = releases
         | 
| 27 | 
            +
                        end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                        def get_binding
         | 
| 30 | 
            +
                          binding
         | 
| 31 | 
            +
                        end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
            end
         | 
| @@ -0,0 +1,73 @@ | |
| 1 | 
            +
            module Statistrano
         | 
| 2 | 
            +
              module Deployment
         | 
| 3 | 
            +
                module Strategy
         | 
| 4 | 
            +
                  class Branches
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                    #
         | 
| 7 | 
            +
                    # Manages the state of a single release for the manifest
         | 
| 8 | 
            +
                    #
         | 
| 9 | 
            +
                    class Release
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                      attr_reader :name, :config, :options
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                      # init a release
         | 
| 14 | 
            +
                      # @param name [String] name of the release
         | 
| 15 | 
            +
                      # @param config [Obj] the config object
         | 
| 16 | 
            +
                      # @param options [Hash] :time, :commit, & :link || :repo_url
         | 
| 17 | 
            +
                      def initialize name, config, options={}
         | 
| 18 | 
            +
                        @name    = name
         | 
| 19 | 
            +
                        @config  = config
         | 
| 20 | 
            +
                        @options = Util.symbolize_hash_keys options
         | 
| 21 | 
            +
                      end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                      def time
         | 
| 24 | 
            +
                        @_time ||= options.fetch(:time) { Time.now.to_i }
         | 
| 25 | 
            +
                      end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                      def commit
         | 
| 28 | 
            +
                        @_commit ||= options.fetch(:commit) { Asgit.current_commit }
         | 
| 29 | 
            +
                      end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      def link
         | 
| 32 | 
            +
                        @_link ||= options.fetch(:link) { (options[:repo_url]) ? "#{options[:repo_url]}/tree/#{commit}" : nil }
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                      def href
         | 
| 36 | 
            +
                        "http://#{name}.#{config.base_domain}"
         | 
| 37 | 
            +
                      end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                      # # convert the release to an li element
         | 
| 40 | 
            +
                      # # @return [String]
         | 
| 41 | 
            +
                      # def as_li
         | 
| 42 | 
            +
                      #   "<li>" +
         | 
| 43 | 
            +
                      #   "<a href=\"http://#{name}.#{config.base_domain}\">#{name}</a>" +
         | 
| 44 | 
            +
                      #   "<small>updated: #{Time.at(time).strftime('%A %b %d, %Y at %l:%M %P')}</small>" +
         | 
| 45 | 
            +
                      #   "</li>"
         | 
| 46 | 
            +
                      # end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                      # def get_binding
         | 
| 49 | 
            +
                      #   binding
         | 
| 50 | 
            +
                      # end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                      def to_hash
         | 
| 53 | 
            +
                        hash = {
         | 
| 54 | 
            +
                          name: name,
         | 
| 55 | 
            +
                          time: time,
         | 
| 56 | 
            +
                          commit: commit
         | 
| 57 | 
            +
                        }
         | 
| 58 | 
            +
                        hash.merge({ link: link }) if link
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                        return hash
         | 
| 61 | 
            +
                      end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                      # convert the release to a json object
         | 
| 64 | 
            +
                      # @return [String]
         | 
| 65 | 
            +
                      def to_json
         | 
| 66 | 
            +
                        return to_hash.to_json
         | 
| 67 | 
            +
                      end
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
            end
         | 
| @@ -0,0 +1,198 @@ | |
| 1 | 
            +
            module Statistrano
         | 
| 2 | 
            +
              module Deployment
         | 
| 3 | 
            +
                module Strategy
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  #
         | 
| 6 | 
            +
                  # Branches is for deployments that depend upon the
         | 
| 7 | 
            +
                  # current git branch, eg. doing feature branch deployments
         | 
| 8 | 
            +
                  #
         | 
| 9 | 
            +
                  class Branches < Base
         | 
| 10 | 
            +
                    register_strategy :branches
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    option :base_domain
         | 
| 13 | 
            +
                    option :public_dir, :call, Proc.new { Asgit.current_branch.to_slug }
         | 
| 14 | 
            +
                    option :post_deploy_task,  Proc.new { |d|
         | 
| 15 | 
            +
                      d.push_current_release_to_manifest
         | 
| 16 | 
            +
                      d.generate_index
         | 
| 17 | 
            +
                    }
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    task :list,           :list_releases,  "List branches"
         | 
| 20 | 
            +
                    task :prune,          :prune_releases, "Prune a branch"
         | 
| 21 | 
            +
                    task :generate_index, :generate_index, "Generate a branch index"
         | 
| 22 | 
            +
                    task :open,           :open_url,       "Open the current branch URL"
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    # output a list of the releases in manifest
         | 
| 25 | 
            +
                    # @return [Void]
         | 
| 26 | 
            +
                    def list_releases
         | 
| 27 | 
            +
                      remotes.each do |remote|
         | 
| 28 | 
            +
                        releases_data = sorted_release_data(remote)
         | 
| 29 | 
            +
                        releases_data.map! do |release|
         | 
| 30 | 
            +
                          "#{release[:name]} created at #{Time.at(release[:time]).strftime('%a %b %d, %Y at %l:%M %P')}"
         | 
| 31 | 
            +
                        end
         | 
| 32 | 
            +
                        Log.info remote.config.hostname.to_sym, *releases_data
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    # trim releases not in the manifest,
         | 
| 37 | 
            +
                    # get user input for removal of other releases
         | 
| 38 | 
            +
                    # @return [Void]
         | 
| 39 | 
            +
                    def prune_releases
         | 
| 40 | 
            +
                      candidate_releases = []
         | 
| 41 | 
            +
                      remotes.each do |remote|
         | 
| 42 | 
            +
                        prune_untracked_releases(remote)
         | 
| 43 | 
            +
                        candidate_releases.push *get_releases(remote)
         | 
| 44 | 
            +
                        candidate_releases.uniq!
         | 
| 45 | 
            +
                      end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                      if candidate_releases.length > 0
         | 
| 48 | 
            +
                        pick_and_remove_release candidate_releases
         | 
| 49 | 
            +
                      else
         | 
| 50 | 
            +
                        Log.warn "no releases to prune"
         | 
| 51 | 
            +
                      end
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    # generate an index file for releases in the manifest
         | 
| 55 | 
            +
                    # @return [Void]
         | 
| 56 | 
            +
                    def generate_index
         | 
| 57 | 
            +
                      remotes.each do |remote|
         | 
| 58 | 
            +
                        index_dir  = File.join( remote.config.remote_dir, "index" )
         | 
| 59 | 
            +
                        index_path = File.join( index_dir, "index.html" )
         | 
| 60 | 
            +
                        remote.create_remote_dir index_dir
         | 
| 61 | 
            +
                        remote.run "touch #{index_path} && echo '#{release_list_html(remote)}' > #{index_path}"
         | 
| 62 | 
            +
                      end
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    # push the current release into the manifest
         | 
| 66 | 
            +
                    # @return [Void]
         | 
| 67 | 
            +
                    def push_current_release_to_manifest
         | 
| 68 | 
            +
                      remotes.each do |remote|
         | 
| 69 | 
            +
                        mnfst = manifest(remote)
         | 
| 70 | 
            +
                        mnfst.put Release.new( config.public_dir, config ).to_hash, :name
         | 
| 71 | 
            +
                        mnfst.save!
         | 
| 72 | 
            +
                      end
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                    private
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                      def manifest remote
         | 
| 78 | 
            +
                        @_manifests ||= {}
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                        @_manifests.fetch( remote ) do
         | 
| 81 | 
            +
                          Deployment::Manifest.new remote.config.remote_dir, remote
         | 
| 82 | 
            +
                        end
         | 
| 83 | 
            +
                      end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                      def pick_and_remove_release candidate_releases
         | 
| 86 | 
            +
                        picked_release = pick_release_to_remove candidate_releases
         | 
| 87 | 
            +
                        if picked_release
         | 
| 88 | 
            +
                          remotes.each do |remote|
         | 
| 89 | 
            +
                            if get_releases(remote).include? picked_release
         | 
| 90 | 
            +
                              remove_release(remote, picked_release)
         | 
| 91 | 
            +
                            end
         | 
| 92 | 
            +
                          end
         | 
| 93 | 
            +
                          generate_index
         | 
| 94 | 
            +
                        else
         | 
| 95 | 
            +
                          Log.warn "sorry, that isn't one of the releases"
         | 
| 96 | 
            +
                        end
         | 
| 97 | 
            +
                      end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                      def pick_release_to_remove candidate_releases
         | 
| 100 | 
            +
                        list_releases_with_index candidate_releases
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                        picked_release = Shell.get_input("select a release to remove: ").gsub(/[^0-9]/, '')
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                        if !picked_release.empty? && picked_release.to_i < candidate_releases.length
         | 
| 105 | 
            +
                          return candidate_releases[picked_release.to_i]
         | 
| 106 | 
            +
                        else
         | 
| 107 | 
            +
                          return false
         | 
| 108 | 
            +
                        end
         | 
| 109 | 
            +
                      end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                      def list_releases_with_index releases
         | 
| 112 | 
            +
                        releases.each_with_index do |release,idx|
         | 
| 113 | 
            +
                          Log.info :"[#{idx}]", "#{release}"
         | 
| 114 | 
            +
                        end
         | 
| 115 | 
            +
                      end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                      # removes releases that are on the remote but not in the manifest
         | 
| 118 | 
            +
                      # @return [Void]
         | 
| 119 | 
            +
                      def prune_untracked_releases remote
         | 
| 120 | 
            +
                        get_actual_releases(remote).each do |release|
         | 
| 121 | 
            +
                          remove_release(remote, release) unless get_releases(remote).include? release
         | 
| 122 | 
            +
                        end
         | 
| 123 | 
            +
                      end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                      def release_list_html remote
         | 
| 126 | 
            +
                        releases = sorted_release_data(remote).map do |r|
         | 
| 127 | 
            +
                          name = r.fetch(:name)
         | 
| 128 | 
            +
                          r.merge({ repo_url: config.repo_url }) if config.repo_url
         | 
| 129 | 
            +
                          Release.new( name, config, r )
         | 
| 130 | 
            +
                        end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                        Index.new( releases ).to_html
         | 
| 133 | 
            +
                      end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                      def sorted_release_data remote
         | 
| 136 | 
            +
                        manifest(remote).data.sort_by do |r|
         | 
| 137 | 
            +
                          r[:time]
         | 
| 138 | 
            +
                        end.reverse
         | 
| 139 | 
            +
                      end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                      # remove a release
         | 
| 142 | 
            +
                      # @param name [String]
         | 
| 143 | 
            +
                      # @return [Void]
         | 
| 144 | 
            +
                      def remove_release remote, name
         | 
| 145 | 
            +
                        Log.info remote.config.hostname.to_sym, "Removing release '#{name}'"
         | 
| 146 | 
            +
                        remote.run "rm -rf #{release_path(name, remote)}"
         | 
| 147 | 
            +
                        manifest(remote).remove_if do |r|
         | 
| 148 | 
            +
                          r[:name] == name
         | 
| 149 | 
            +
                        end
         | 
| 150 | 
            +
                        manifest(remote).save!
         | 
| 151 | 
            +
                      end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                      # return array of releases from the manifest
         | 
| 154 | 
            +
                      # @return [Array]
         | 
| 155 | 
            +
                      def get_releases remote
         | 
| 156 | 
            +
                       sorted_release_data(remote).map { |r| r[:name] }
         | 
| 157 | 
            +
                      end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                      # return array of releases on the remote
         | 
| 160 | 
            +
                      # @return [Array]
         | 
| 161 | 
            +
                      def get_actual_releases remote
         | 
| 162 | 
            +
                        remote.run("ls -mp #{remote.config.remote_dir}")
         | 
| 163 | 
            +
                              .stdout.strip.split(',')
         | 
| 164 | 
            +
                              .keep_if { |release| /\/$/.match(release) }
         | 
| 165 | 
            +
                              .map     { |release| release.strip.sub(/(\/$)/, '') }
         | 
| 166 | 
            +
                              .keep_if { |release| release != "index" }
         | 
| 167 | 
            +
                      end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                      # path to the current release
         | 
| 170 | 
            +
                      # this is based on the git branch
         | 
| 171 | 
            +
                      # @return [String]
         | 
| 172 | 
            +
                      def current_release_path
         | 
| 173 | 
            +
                        File.join( config.remote_dir, config.public_dir )
         | 
| 174 | 
            +
                      end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                      # path to a specific release
         | 
| 177 | 
            +
                      # @return [String]
         | 
| 178 | 
            +
                      def release_path name, remote
         | 
| 179 | 
            +
                        File.join( remote.config.remote_dir, name )
         | 
| 180 | 
            +
                      end
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                      # open the current checked out branch
         | 
| 183 | 
            +
                      # @return [Void]
         | 
| 184 | 
            +
                      def open_url
         | 
| 185 | 
            +
                        if config.base_domain
         | 
| 186 | 
            +
                          url = "http://#{config.public_dir}.#{config.base_domain}"
         | 
| 187 | 
            +
                          system "open #{url}"
         | 
| 188 | 
            +
                        end
         | 
| 189 | 
            +
                      end
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                  end
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                end
         | 
| 194 | 
            +
              end
         | 
| 195 | 
            +
            end
         | 
| 196 | 
            +
             | 
| 197 | 
            +
            require_relative 'branches/index'
         | 
| 198 | 
            +
            require_relative 'branches/release'
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            module Statistrano
         | 
| 2 | 
            +
              module Deployment
         | 
| 3 | 
            +
                module Strategy
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  module CheckGit
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                    # Check if things are safe to deploy
         | 
| 8 | 
            +
                    # @return [Boolean]
         | 
| 9 | 
            +
                    def safe_to_deploy?
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                      # if we don't want to check git
         | 
| 12 | 
            +
                      # we're good to go
         | 
| 13 | 
            +
                      if !config.check_git
         | 
| 14 | 
            +
                        return true
         | 
| 15 | 
            +
                      end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                      # are there any uncommited changes?
         | 
| 18 | 
            +
                      if !Asgit.working_tree_clean?
         | 
| 19 | 
            +
                        Log.warn "You need to commit or stash your changes before deploying"
         | 
| 20 | 
            +
                        return false
         | 
| 21 | 
            +
                      end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                      # make sure you're on the branch selected to check against
         | 
| 24 | 
            +
                      if Asgit.current_branch != config.git_branch
         | 
| 25 | 
            +
                        Log.warn "You shouldn't deploy from any branch but #{config.git_branch}"
         | 
| 26 | 
            +
                        return false
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                      # make sure you're up to date
         | 
| 30 | 
            +
                      if !Asgit.remote_up_to_date?
         | 
| 31 | 
            +
                        Log.warn "You need to update or push your changes before deploying"
         | 
| 32 | 
            +
                        return false
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                      # we passed all the checks
         | 
| 36 | 
            +
                      return true
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
| @@ -0,0 +1,58 @@ | |
| 1 | 
            +
            module Statistrano
         | 
| 2 | 
            +
              module Deployment
         | 
| 3 | 
            +
                module Strategy
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  module InvokeTasks
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                    # Run the post_deploy_task
         | 
| 8 | 
            +
                    # return [Void]
         | 
| 9 | 
            +
                    def invoke_post_deploy_task
         | 
| 10 | 
            +
                      if config.post_deploy_task
         | 
| 11 | 
            +
                        Log.info :post_deploy, "Running the post deploy task"
         | 
| 12 | 
            +
                        call_or_invoke_task config.post_deploy_task
         | 
| 13 | 
            +
                      end
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    # Run the build_task supplied
         | 
| 17 | 
            +
                    # return [Void]
         | 
| 18 | 
            +
                    def invoke_build_task
         | 
| 19 | 
            +
                      Log.info :build, "Running the build task"
         | 
| 20 | 
            +
                      call_or_invoke_task config.build_task
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    # Run the pre_symlink_task if supplied
         | 
| 24 | 
            +
                    # return [Void]
         | 
| 25 | 
            +
                    def invoke_pre_symlink_task remote
         | 
| 26 | 
            +
                      if !remote.config.pre_symlink_task.nil?
         | 
| 27 | 
            +
                        Log.info :pre_symlink, "Running the pre_symlink task"
         | 
| 28 | 
            +
                        resp = call_or_invoke_task remote.config.pre_symlink_task, remote
         | 
| 29 | 
            +
                        if resp == false
         | 
| 30 | 
            +
                          Log.error :pre_symlink, "exiting due to falsy return"
         | 
| 31 | 
            +
                          abort()
         | 
| 32 | 
            +
                        end
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    def call_or_invoke_task task, arg=nil
         | 
| 37 | 
            +
                      if task.respond_to? :call
         | 
| 38 | 
            +
                        if arg && task.arity == 2
         | 
| 39 | 
            +
                          task.call self, arg
         | 
| 40 | 
            +
                        elsif task.arity == 1
         | 
| 41 | 
            +
                          task.call self
         | 
| 42 | 
            +
                        else
         | 
| 43 | 
            +
                          task.call
         | 
| 44 | 
            +
                        end
         | 
| 45 | 
            +
                      else
         | 
| 46 | 
            +
                        Rake::Task[task].invoke
         | 
| 47 | 
            +
                      end
         | 
| 48 | 
            +
                    rescue Exception => e
         | 
| 49 | 
            +
                      Log.error "exiting due to error in task",
         | 
| 50 | 
            +
                                "#{e.class}: #{e}"
         | 
| 51 | 
            +
                      abort()
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
            end
         | 
| @@ -0,0 +1,76 @@ | |
| 1 | 
            +
            module Statistrano
         | 
| 2 | 
            +
              module Deployment
         | 
| 3 | 
            +
                module Strategy
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  # deployment type for running a releases deployment
         | 
| 6 | 
            +
                  # accross multiple remotes
         | 
| 7 | 
            +
                  #
         | 
| 8 | 
            +
                  # @example:
         | 
| 9 | 
            +
                  #
         | 
| 10 | 
            +
                  #   define_deployment "multi", :releases do
         | 
| 11 | 
            +
                  #     build_task 'deploy:build'
         | 
| 12 | 
            +
                  #     local_dir  'build'
         | 
| 13 | 
            +
                  #     remote_dir '/var/www/proj'
         | 
| 14 | 
            +
                  #
         | 
| 15 | 
            +
                  #     check_git  true
         | 
| 16 | 
            +
                  #     git_branch 'master'
         | 
| 17 | 
            +
                  #
         | 
| 18 | 
            +
                  #     remotes [
         | 
| 19 | 
            +
                  #       { hostname: 'web01' },
         | 
| 20 | 
            +
                  #       { hostname: 'web02' }
         | 
| 21 | 
            +
                  #     ]
         | 
| 22 | 
            +
                  #
         | 
| 23 | 
            +
                  #     # each remote gets merged with the global
         | 
| 24 | 
            +
                  #     # configs and deployed to individually
         | 
| 25 | 
            +
                  #     #
         | 
| 26 | 
            +
                  #   end
         | 
| 27 | 
            +
                  #
         | 
| 28 | 
            +
                  class Releases < Base
         | 
| 29 | 
            +
                    register_strategy :releases
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    option :pre_symlink_task, nil
         | 
| 32 | 
            +
                    option :release_count,    5
         | 
| 33 | 
            +
                    option :release_dir,      "releases"
         | 
| 34 | 
            +
                    option :public_dir,       "current"
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    option :remotes, []
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    task :deploy,   :deploy,           "Deploy to all remotes"
         | 
| 39 | 
            +
                    task :rollback, :rollback_release, "Rollback to the previous release"
         | 
| 40 | 
            +
                    task :prune,    :prune_releases,   "Prune releases to release count"
         | 
| 41 | 
            +
                    task :list,     :list_releases,    "List releases"
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    def initialize name
         | 
| 44 | 
            +
                      @name = name
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    def rollback_release
         | 
| 48 | 
            +
                      remotes.each do |remote|
         | 
| 49 | 
            +
                        releaser.rollback_release remote
         | 
| 50 | 
            +
                      end
         | 
| 51 | 
            +
                    end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    def prune_releases
         | 
| 54 | 
            +
                      remotes.each do |remote|
         | 
| 55 | 
            +
                        releaser.prune_releases remote
         | 
| 56 | 
            +
                      end
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    def list_releases
         | 
| 60 | 
            +
                      remotes.each do |remote|
         | 
| 61 | 
            +
                        releases = releaser.list_releases(remote).map { |rel| rel[:release] }
         | 
| 62 | 
            +
                        Log.info :"#{remote.config.hostname}", releases
         | 
| 63 | 
            +
                      end
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                    private
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                      def releaser
         | 
| 69 | 
            +
                        Releaser::Revisions.new
         | 
| 70 | 
            +
                      end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
            end
         |