capistrano-net_storage 0.5.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,169 +1,177 @@
1
- require 'capistrano/net_storage/error'
2
- require 'capistrano/net_storage/bundler'
3
- require 'capistrano/net_storage/archiver/zip'
4
- require 'capistrano/net_storage/scm/git'
1
+ require 'pathname'
2
+
3
+ require 'capistrano/net_storage/cleaner'
4
+ require 'capistrano/net_storage/bundler/default'
5
+ require 'capistrano/net_storage/bundler/null'
6
+ require 'capistrano/net_storage/config_uploader'
5
7
 
6
8
  module Capistrano
7
9
  module NetStorage
8
10
  class Config
9
- def executor_class(type)
10
- @executor_classes ||= {}
11
- @executor_classes[type] ||= fetch(:"net_storage_#{type}")
12
- @executor_classes[type] ||= begin
13
- case type
14
- when :archiver
15
- Capistrano::NetStorage::Archiver::Zip
16
- when :scm
17
- Capistrano::NetStorage::SCM::Git
18
- when :cleaner
19
- Capistrano::NetStorage::Cleaner
20
- when :bundler
21
- Capistrano::NetStorage::Bundler
22
- when :transport
23
- msg = 'You have to set :net_storage_transport because no default transport class!'
24
- raise Capistrano::NetStorage::Error, msg
25
- else
26
- raise "Unknown type! #{type}"
27
- end
28
- end
11
+
12
+ # Settings for delegate classes
13
+
14
+ def transport_class
15
+ fetch(:net_storage_transport)
29
16
  end
30
17
 
31
- # Servers to deploy
32
- def servers
33
- fetch(:net_storage_servers, -> { release_roles(:all) })
18
+ def archiver_class
19
+ fetch(:net_storage_archiver)
34
20
  end
35
21
 
36
- def max_parallels
37
- @max_parallels ||= fetch(:net_storage_max_parallels, servers.size)
22
+ def scm_class
23
+ fetch(:net_storage_scm)
38
24
  end
39
25
 
40
- # Application configuration files to be deployed with
41
- # @return [Array<String, Pathname>]
42
- def config_files
43
- @config_files ||= fetch(:net_storage_config_files)
26
+ def cleaner_class
27
+ Capistrano::NetStorage::Cleaner
44
28
  end
45
29
 
46
- # If +true+, skip to bundle gems bundled with target app.
47
- # Defaults to +true+
48
- def skip_bundle?
49
- @has_checked_skip_bundle ||= begin
50
- @skip_bundle = !fetch(:net_storage_with_bundle)
51
- true
30
+ def bundler_class
31
+ if skip_bundle?
32
+ Capistrano::NetStorage::Bundler::Null
33
+ else
34
+ Capistrano::NetStorage::Bundler::Default
52
35
  end
53
- @skip_bundle
54
36
  end
55
37
 
56
- # If +true+, create archive ONLY when it's not found on remote storage.
57
- # Otherwise, create archive ALWAYS.
58
- # Defaults to +true+
59
- def archive_on_missing?
60
- @has_checked_archive_on_missing ||= begin
61
- @archive_on_missing = fetch(:net_storage_archive_on_missing, true)
62
- true
63
- end
64
- @archive_on_missing
38
+ def config_uploader_class
39
+ Capistrano::NetStorage::ConfigUploader
40
+ end
41
+
42
+ # Settings for syncing config
43
+
44
+ def config_files
45
+ fetch(:net_storage_config_files)
65
46
  end
66
47
 
67
- # If +true+, use +rsync+ to sync config files.
68
- # Otherwise, use +upload!+ by sshkit.
69
- # Defaults to +false+
70
- # @see #rsync_options
71
48
  def upload_files_by_rsync?
72
- @upload_files_by_rsync ||= fetch(:net_storage_upload_files_by_rsync, false)
49
+ fetch(:net_storage_upload_files_by_rsync)
73
50
  end
74
51
 
75
- # You can set +:user+, +:keys+, +:port+ as ssh options for +rsync+ command to sync configs
76
- # when +:net_storage_upload_files_by_rsync+ is set +true+.
77
- # @see #upload_files_by_rsync?
78
52
  def rsync_options
79
- @rsync_options ||= fetch(:net_storage_rsync_options, fetch(:ssh_options, {}))
53
+ fetch(:net_storage_rsync_options)
54
+ end
55
+
56
+ # Settings for tuning performance
57
+
58
+ def max_parallels
59
+ fetch(:net_storage_max_parallels)
60
+ end
61
+
62
+ def reuse_archive?
63
+ fetch(:net_storage_reuse_archive)
64
+ end
65
+
66
+ def keep_remote_archives
67
+ fetch(:net_storage_keep_remote_archives)
68
+ end
69
+
70
+ # Settings for behavioral changes
71
+
72
+ def skip_bundle?
73
+ fetch(:net_storage_skip_bundle)
74
+ end
75
+
76
+ def multi_app_mode?
77
+ fetch(:net_storage_multi_app_mode)
80
78
  end
81
79
 
82
- #
83
80
  # Path settings
84
- #
85
81
 
86
- # Path of base directory on local
82
+ # Path to application of remote release_path
83
+ # @return [Pathname]
84
+ def release_app_path
85
+ if multi_app_mode?
86
+ release_path.join(fetch(:application))
87
+ else
88
+ release_path
89
+ end
90
+ end
91
+
92
+ # Path to base directory on local
87
93
  # @return [Pathname]
88
94
  def local_base_path
89
- @local_base_path ||= pathname(fetch(:net_storage_local_base_path, "#{Dir.pwd}/.local_repo"))
95
+ Pathname.new(fetch(:net_storage_local_base_path))
90
96
  end
91
97
 
92
- # Path to clone repository on local
98
+ # Path to a mirror repository on local
93
99
  # @return [Pathname]
94
100
  def local_mirror_path
95
- @local_mirror_path ||= pathname(fetch(:net_storage_local_mirror_path))
96
- @local_mirror_path ||= local_base_path.join('mirror')
101
+ local_base_path.join('mirror')
97
102
  end
98
103
 
99
104
  # Path to keep release directories on local
100
105
  # @return [Pathname]
101
106
  def local_releases_path
102
- @local_releases_path ||= pathname(fetch(:net_storage_local_releases_path))
103
- @local_releases_path ||= local_base_path.join('releases')
107
+ local_base_path.join('releases')
104
108
  end
105
109
 
106
- # Path to take a snapshot of repository for release on local
110
+ # Path to local release directory, where a release is to be prepared
107
111
  # @return [Pathname]
108
112
  def local_release_path
109
- @local_release_path ||= local_releases_path.join(release_timestamp)
113
+ local_releases_path.join(release_timestamp)
110
114
  end
111
115
 
112
- # Shared directory to install gems on local
116
+ # Path to application of local release_path
117
+ # @return [Pathname]
118
+ def local_release_app_path
119
+ if multi_app_mode?
120
+ local_release_path.join(fetch(:application))
121
+ else
122
+ local_release_path
123
+ end
124
+ end
125
+
126
+ # Shared cache directory to speed up installing gems on local
113
127
  # @return [Pathname]
114
128
  def local_bundle_path
115
- @local_bundle_path ||= pathname(fetch(:net_storage_local_bundle_path))
116
- @local_bundle_path ||= local_base_path.join('bundle')
129
+ local_base_path.join('bundle')
117
130
  end
118
131
 
119
132
  # Path of archive directories on local
120
133
  # @return [Pathname]
121
134
  def local_archives_path
122
- @local_archives_path ||= pathname(fetch(:net_storage_local_archives_path))
123
- @local_archives_path ||= local_base_path.join('archives')
135
+ local_base_path.join('archives')
124
136
  end
125
137
 
126
138
  # Destination path to archive application on local
127
139
  # @return [Pathname]
128
140
  def local_archive_path
129
- @local_archive_path ||= local_archives_path.join("#{release_timestamp}.#{archive_suffix}")
141
+ local_archives_path.join("#{release_timestamp}.#{archive_file_extension}")
130
142
  end
131
143
 
132
144
  # Path of archive directories on remote servers
133
145
  # @return [Pathname]
134
146
  def archives_path
135
- @archives_path ||= pathname(fetch(:net_storage_archives_path))
136
- @archives_path ||= deploy_path.join('net_storage_archives')
147
+ Pathname.new(fetch(:net_storage_archives_path))
137
148
  end
138
149
 
139
150
  # Path of archive file to be downloaded on remote servers
140
151
  # @return [Pathname]
141
152
  def archive_path
142
- @archive_path ||= archives_path.join("#{release_timestamp}.#{archive_suffix}")
153
+ archives_path.join("#{release_timestamp}.#{archive_file_extension}")
143
154
  end
144
155
 
145
156
  # Suffix of archive file
146
157
  # @return [String]
147
- def archive_suffix
148
- case Capistrano::NetStorage.archiver
149
- when Capistrano::NetStorage::Archiver::Zip
150
- 'zip'
151
- when Capistrano::NetStorage::Archiver::TarGzip
152
- 'tar.gz'
153
- else
154
- 'archive'
155
- end
158
+ def archive_file_extension
159
+ archiver_class.file_extension
156
160
  end
157
161
 
158
- private
162
+ def archive_suffix
163
+ warn <<~WARN
164
+ ######### DEPRECATION WARNING #########
159
165
 
160
- def pathname(path)
161
- case path
162
- when String
163
- Pathname.new(path)
164
- else
165
- path
166
- end
166
+ `Capistrano::NetStorage.config.archive_suffix` is no longer available
167
+ at #{caller[0]}
168
+
169
+ Use following method instead.
170
+ `Capistrano::NetStorage.config.archive_file_extension`
171
+
172
+ WARN
173
+
174
+ archive_file_extension
167
175
  end
168
176
  end
169
177
  end
@@ -0,0 +1,58 @@
1
+ module Capistrano
2
+ module NetStorage
3
+ class ConfigUploader
4
+ # These values are intentionally separated from config and fixed.
5
+ # If you have trouble with these defaults for 10^2 ~ 10^3 servers, please contact us on GitHub.
6
+ # Ideally, you can upload files to 10000 servers in 50 seconds. (2.0 second jitter + 0.5 second execution time)
7
+ MAX_PARALLEL_TO_UPLOAD = 500
8
+ JITTER_DURATION_TO_UPLOAD = 2.0
9
+
10
+ # Check for prerequisites.
11
+ # Same interface to delegated class
12
+ def check
13
+ end
14
+
15
+ def upload_config_files
16
+ config = Capistrano::NetStorage.config
17
+
18
+ on release_roles(:all), in: :groups, limit: MAX_PARALLEL_TO_UPLOAD do |host|
19
+ if config.upload_files_by_rsync?
20
+ rsh_option = Capistrano::NetStorage::ConfigUploader.build_ssh_command(host)
21
+ run_locally do
22
+ sleep Random.rand(JITTER_DURATION_TO_UPLOAD)
23
+ execute :rsync, '-az', "--rsh='#{rsh_option}'", *config.config_files, "#{host.hostname}:#{config.release_app_path.join('config')}"
24
+ end
25
+ else
26
+ # slow and not recommended.
27
+ files.each do |src|
28
+ upload! src, dest_dir
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ # Build ssh command with options for rsync
35
+ def self.build_ssh_command(host)
36
+ user_opt = ''
37
+ key_opt = ''
38
+ port_opt = ''
39
+ ssh_options = Capistrano::NetStorage.config.rsync_options
40
+
41
+ if user = host.user || ssh_options[:user]
42
+ user_opt = " -l #{user}"
43
+ end
44
+
45
+ if keys = (host.keys.empty? ? ssh_options[:keys] : host.keys)
46
+ keys = keys.is_a?(Array) ? keys : [keys]
47
+ key_opt = keys.map { |key| " -i #{key}" }.join('')
48
+ end
49
+
50
+ if port = host.port || ssh_options[:port]
51
+ port_opt = " -p #{port}"
52
+ end
53
+
54
+ "ssh#{user_opt}#{key_opt}#{port_opt}"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,14 +1,52 @@
1
+ require 'pathname'
2
+
1
3
  require 'capistrano/scm/plugin'
2
- require 'capistrano/net_storage'
4
+
5
+ require 'capistrano/net_storage/archiver/tar_gzip'
6
+ require 'capistrano/net_storage/scm/git'
3
7
 
4
8
  module Capistrano
5
9
  module NetStorage
6
10
  class Plugin < ::Capistrano::SCM::Plugin
11
+ # See README.md for details of settings
12
+ def set_defaults
13
+ set_if_empty :net_storage_transport, -> { raise ArgumentError, 'You have to `set(:net_storage_transport, Capistrano::NetStorage::S3)` # or your custom class' }
14
+ set_if_empty :net_storage_archiver, Capistrano::NetStorage::Archiver::TarGzip
15
+ set_if_empty :net_storage_scm, Capistrano::NetStorage::SCM::Git
16
+
17
+ set_if_empty :net_storage_config_files, []
18
+ set_if_empty :net_storage_upload_files_by_rsync, true
19
+ set_if_empty :net_storage_rsync_options, -> { fetch(:ssh_options, {}) }
20
+
21
+ set_if_empty :net_storage_max_parallels, 1000
22
+ set_if_empty :net_storage_reuse_archive, true
23
+
24
+ set_if_empty :net_storage_local_base_path, Pathname.new("#{Dir.pwd}/.local_net_storage")
25
+ set_if_empty :net_storage_archives_path, -> { deploy_path.join('net_storage_archives') }
26
+
27
+ set_if_empty :net_storage_skip_bundle, false
28
+ set_if_empty :net_storage_multi_app_mode, false
29
+
30
+ set_if_empty :net_storage_keep_remote_archives, 10
31
+ end
32
+
33
+ def define_tasks
34
+ eval_rakefile File.expand_path("../tasks/net_storage.rake", __dir__)
35
+ end
36
+
7
37
  def register_hooks
8
38
  after 'deploy:new_release_path', 'net_storage:create_release'
9
39
  before 'deploy:check', 'net_storage:check'
10
- before 'deploy:set_current_revision', 'net_storage:set_current_revision'
40
+ after 'deploy:cleanup', 'net_storage:cleanup'
11
41
  end
12
42
  end
13
43
  end
14
44
  end
45
+
46
+ # initialization code to access global settings via Capistrano::NetStorage
47
+
48
+ require 'capistrano/net_storage'
49
+ require 'capistrano/net_storage/config'
50
+
51
+ config = Capistrano::NetStorage::Config.new
52
+ Capistrano::NetStorage.setup!(config: Capistrano::NetStorage::Config.new)
@@ -1,47 +1,26 @@
1
- require 'capistrano/net_storage/utils'
2
-
3
1
  module Capistrano
4
2
  module NetStorage
5
3
  module SCM
6
- # Base internal SCM class of Capistrano::Netstrage
7
- # @abstract
4
+ # Abstract class to handle SCM between local_mirror_path and local_release_path
8
5
  class Base
9
- include Capistrano::NetStorage::Utils
10
-
11
- # Check SCM prerequisites
12
- # @abstract
13
6
  def check
14
- raise NotImplementedError
7
+ raise NotImplementedError, "Implement `#{self.class}#{__method__}` to check prerequisites for SCM"
15
8
  end
16
9
 
17
- # Clone repository to local
18
- # @abstract
19
10
  def clone
20
- raise NotImplementedError
11
+ raise NotImplementedError, "Implement `#{self.class}#{__method__}` to clone repository to `Capistrano::NetStorage.config.local_mirror_path`"
21
12
  end
22
13
 
23
- # Update local repository
24
- # @abstract
25
14
  def update
26
- raise NotImplementedError
15
+ raise NotImplementedError, "Implement `#{self.class}#{__method__}` to update repository in `Capistrano::NetStorage.config.local_mirror_path`"
27
16
  end
28
17
 
29
- # Set current revision to be deployed of the repository
30
- # @abstract
31
18
  def set_current_revision
32
- raise NotImplementedError
19
+ raise NotImplementedError, "Implement `#{self.class}#{__method__}` to set current revision by `set :current_revision, revision`"
33
20
  end
34
21
 
35
- # Prepare snapshot of repository to be archived for release
36
- # @abstract
37
22
  def prepare_archive
38
- raise NotImplementedError
39
- end
40
-
41
- # Copy local config files to servers
42
- def sync_config
43
- return unless config.config_files
44
- upload_files(config.config_files, release_path.join('config'))
23
+ raise NotImplementedError, "Implement `#{self.class}#{__method__}` to extract and prepare release into `Capistrano::NetStorage.config.local_release_path`"
45
24
  end
46
25
  end
47
26
  end
@@ -1,6 +1,5 @@
1
1
  require 'capistrano/net_storage/scm/base'
2
2
 
3
- # Internal SCM class for Git repository
4
3
  class Capistrano::NetStorage::SCM::Git < Capistrano::NetStorage::SCM::Base
5
4
  def check
6
5
  run_locally do
@@ -9,51 +8,54 @@ class Capistrano::NetStorage::SCM::Git < Capistrano::NetStorage::SCM::Base
9
8
  end
10
9
 
11
10
  def clone
12
- c = config
11
+ config = Capistrano::NetStorage.config
12
+
13
13
  run_locally do
14
- if File.exist?("#{c.local_mirror_path}/HEAD")
15
- info t(:mirror_exists, at: c.local_mirror_path)
14
+ if test "[ -f #{config.local_mirror_path}/HEAD ]"
15
+ info t(:mirror_exists, at: config.local_mirror_path)
16
16
  else
17
- execute :git, :clone, '--mirror', repo_url, c.local_mirror_path
17
+ execute :git, :clone, '--mirror', repo_url, config.local_mirror_path
18
18
  end
19
19
  end
20
20
  end
21
21
 
22
22
  def update
23
- c = config
23
+ config = Capistrano::NetStorage.config
24
+
24
25
  run_locally do
25
- within c.local_mirror_path do
26
+ within config.local_mirror_path do
26
27
  execute :git, :remote, :update
27
28
  end
28
29
  end
29
30
  end
30
31
 
31
32
  def set_current_revision
32
- return if fetch(:current_revision)
33
- c = config
33
+ config = Capistrano::NetStorage.config
34
+
34
35
  run_locally do
35
- within c.local_mirror_path do
36
+ within config.local_mirror_path do
36
37
  set :current_revision, capture(:git, "rev-list --max-count=1 #{fetch(:branch)}")
37
38
  end
38
39
  end
39
40
  end
40
41
 
41
42
  def prepare_archive
42
- c = config
43
+ config = Capistrano::NetStorage.config
44
+
43
45
  run_locally do
44
- execute :mkdir, '-p', c.local_release_path
46
+ execute :mkdir, '-p', config.local_release_path
45
47
 
46
- within c.local_mirror_path do
48
+ within config.local_mirror_path do
47
49
  if tree = fetch(:repo_tree)
48
50
  stripped = tree.slice %r{^/?(.*?)/?$}, 1 # strip both side /
49
51
  num_components = stripped.count('/')
50
52
  execute(
51
53
  :git, :archive, fetch(:branch), tree,
52
54
  "| tar -x --strip-components #{num_components} -f - -C ",
53
- c.local_release_path
55
+ config.local_release_path
54
56
  )
55
57
  else
56
- execute :git, :archive, fetch(:branch), '| tar -x -C', c.local_release_path
58
+ execute :git, :archive, fetch(:branch), '| tar -x -C', config.local_release_path
57
59
  end
58
60
  end
59
61
  end
@@ -1,39 +1,27 @@
1
1
  module Capistrano
2
2
  module NetStorage
3
3
  module Transport
4
- # Abstract class to transport archive from/to remote storage
5
- # @abstract
4
+ # Abstract class to upload archive from local to storage and download from storage to remote
6
5
  class Base
7
- # Check prerequisites for transport
8
- # @abstract
9
6
  def check
10
- raise NotImplementedError
7
+ raise NotImplementedError, "Implement `#{self.class}#{__method__}` to check prerequisites for Transport"
11
8
  end
12
9
 
13
- # Find uploaded archive to be deployed and set +:net_storage_uploaded_archive+ its address
14
- # @abstract
15
- def find_uploaded
16
- raise NotImplementedError
10
+ def archive_exists?
11
+ raise NotImplementedError, "Implement `#{self.class}#{__method__}` to test archive on storage corresponding to `fetch(:current_revision) + Config#archive_file_extension`"
17
12
  end
18
13
 
19
- # Upload archive onto remote storage
20
- # @abstract
21
14
  def upload
22
- raise NotImplementedError
15
+ raise NotImplementedError, "Implement `#{self.class}#{__method__}` to upload archive from `Capistrano::NetStorage.config.local_archive_path` to remote storage"
23
16
  end
24
17
 
25
- # Download archive from remote storage to servers.
26
- # Archive file should be placed at +config.archive_path+
27
- # @abstract
28
18
  def download
29
- raise NotImplementedError
19
+ raise NotImplementedError, "Implement `#{self.class}#{__method__}` to download archive from remote storage to `Capistrano::NetStorage.config.archive_path`"
30
20
  end
31
21
 
32
- # Clean up old archives on remote storage
33
- # @abstract
34
- # def cleanup
35
- # raise NotImplementedError
36
- # end
22
+ def cleanup
23
+ raise NotImplementedError, "Implement `#{self.class}#{__method__}` to clean archives on remote storage to `Capistrano::NetStorage.config.keep_remote_archives`"
24
+ end
37
25
  end
38
26
  end
39
27
  end
@@ -1,5 +1,5 @@
1
1
  module Capistrano
2
2
  module NetStorage
3
- VERSION = '0.5.0'
3
+ VERSION = '1.0.0'
4
4
  end
5
5
  end
@@ -1,2 +1,35 @@
1
- require 'capistrano/net_storage/all'
2
- load File.expand_path('../tasks/net_storage.rake', __FILE__)
1
+ module Capistrano
2
+ module NetStorage
3
+ class << self
4
+ attr_reader :config
5
+
6
+ def setup!(config:)
7
+ @config = config
8
+ end
9
+
10
+ def transport
11
+ config.transport_class.new
12
+ end
13
+
14
+ def archiver
15
+ config.archiver_class.new
16
+ end
17
+
18
+ def scm
19
+ config.scm_class.new
20
+ end
21
+
22
+ def cleaner
23
+ config.cleaner_class.new
24
+ end
25
+
26
+ def bundler
27
+ config.bundler_class.new
28
+ end
29
+
30
+ def config_uploader
31
+ config.config_uploader_class.new
32
+ end
33
+ end
34
+ end
35
+ end