capistrano-net_storage 0.4.0 → 1.0.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.
@@ -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.4.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