sacback 0.0.3

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 83bc1c9dd28613bc0b8d1cea52f5b184dcb3f973
4
+ data.tar.gz: 46b2464c5306818f8b6652c2e4bd7ac3ea31196c
5
+ SHA512:
6
+ metadata.gz: a4b0938d093e2eef1fa4d06d8e771285d5f59c4919f7ee94cd0bda09bcc786fa8e8e73ff32b506a0c9f59b1fffcaad74556808bc8fe7484d64180912b62efb42
7
+ data.tar.gz: c109d33c5978ec2398b36604cf5f4eac339ffd2934aeeb13375872856bbadd0d4b1a219cb1552d3c3df4dc503430c90f95e58a485d84e47bac2ba202407d5c1d
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+
16
+ vendor/bundle/
17
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sacback.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Eiichi Shimotori
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # Sacback
2
+
3
+ Sacbackはさくらのクラウドのディスクをローカルホストにバックアップするツールです。 (Sakura Cloud Backup tool)
4
+
5
+ 以下の一連の処理を一気に実行します。また、それぞれの処理を個別に実行することもできます。
6
+
7
+ 1. さくらのクラウド上でディスクのアーカイブを作成
8
+ 1. アーカイブをローカルホストにダウンロード
9
+ 1. さくらのクラウド上のアーカイブを削除
10
+ 1. ローカルのアーカイブファイルを圧縮
11
+
12
+
13
+ ## セットアップ
14
+
15
+ まだgem化していないため、プロジェクトをクローンしてください。
16
+
17
+ ```
18
+ $ git clone https://github.com/shimotori/sacback.git
19
+ $ cd sacback
20
+ $ bundle install
21
+ ```
22
+
23
+ さくらのクラウドの認証情報とゾーン名を以下の環境変数にセットします。
24
+
25
+ * SACLOUD_TOKEN: アクセス・トークン
26
+
27
+ * SACLOUD_SECRET: アクセス・トークン・シークレット
28
+
29
+ * SACLOUD_ZONE: ゾーン名(石狩第1: is1a, 石狩第2: is1b)
30
+
31
+
32
+
33
+ ## 使い方
34
+
35
+ ```
36
+ $ bundle exec bin/sacback
37
+ Commands:
38
+ sacback a DISK_NAME [LOCAL_DIR_PATH] # Run all through with the disk (def...
39
+ sacback c DISK_NAME # Create an archive from the disk
40
+ sacback g ARCHIVE_ID [LOCAL_DIR_PATH] # Get the archive (default path: /tmp)
41
+ sacback help [COMMAND] # Describe available commands or one...
42
+ sacback r ARCHIVE_ID # Remove the archive
43
+ sacback z LOCAL_DIR [FILE_NAME] # Compress the local file (default f...
44
+
45
+ Options:
46
+ -s, [--silent], [--no-silent] # Not write log messages
47
+ [--curl], [--no-curl] # Use curl command for downloading
48
+ [--ftpblocksize=N] # Block size on FTP transfer (KB) (not used on curl mode)
49
+ # Default: 512
50
+ ```
51
+
52
+ まだgem化していないため、直接 `sacback` コマンドを使うことはできません。
53
+
54
+ 実行する時にはプロジェクトディレクトリーに移動して、`bundle exec bin/sacback` で実行してください。
55
+
56
+
57
+
58
+ #### コマンド
59
+
60
+ * a: すべての処理を一気に実行します。(以降のc, g, r, z)
61
+
62
+ 例: `bundle exec bin/sacback a disk1 /backup`
63
+
64
+ * c: さくらのクラウド上でディスクからアーカイブを作成します。
65
+
66
+ アーカイブ名:{ディスク名}-YYMMDD-HHMM
67
+
68
+ * g: アーカイブをローカルホストにダウンロードします。
69
+
70
+ デフォルトファイル名:archive.img
71
+
72
+ * r: さくらのクラウド上でアーカイブを削除します。
73
+
74
+ * z: ローカルホストのファイルを圧縮します。(gzip)
75
+
76
+ 圧縮ファイル名:{アーカイブ名}.gz
77
+
78
+
79
+
80
+ #### オプション
81
+
82
+ -s, --silent: ログメッセージを表示しません。cronなどで実行する場合はこのオプションを指定してください。
83
+
84
+ --curl: アーカイブのダウンロードをcurlで行います。デフォルトでは [DoubleBagFTPS](https://github.com/bnix/double-bag-ftps) を使用しますが非常に遅いため、curlが使える環境ではこのオプションの使用をお勧めします。
85
+
86
+ --ftpblocksize=N: アーカイブダウンロード時のブロックサイズを指定します(KB単位)。curlモードの場合は影響しません。
87
+
88
+
89
+
90
+ #### 注意事項
91
+
92
+ a, cコマンドのパラメーターで指定する「ディスク名」は部分一致となります。複数のディスク名が該当した場合は最初のものが使用されるため、ユニークに決まる名称を指定した方が良いでしょう。
93
+
94
+ アーカイブのダウンロード時に、さくらのクラウドのコンソール上では、ステータスが「アップロード」と表示されます。今のところ原因がわかりませんが、動作上は問題ありません。
95
+
96
+ さくらのクラウド側でたまに `HttpServiceUnavailableException` が発生することがあります。aコマンドで実行していた場合は、失敗した処理から個別に実行して継続することができます。
97
+
98
+
99
+
100
+ ## Contributing
101
+
102
+ 1. Fork it ( https://github.com/shimotori/sacback/fork )
103
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
104
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
105
+ 4. Push to the branch (`git push origin my-new-feature`)
106
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/sacback ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sacback'
4
+
5
+ Sacback::CLI.start
data/lib/sacback.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "sacback/version"
2
+ require "sacback/cli"
3
+
4
+ module Sacback
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,189 @@
1
+ # coding: utf-8
2
+
3
+ require 'thor'
4
+ require 'date'
5
+ require 'zlib'
6
+ require 'double_bag_ftps'
7
+ require 'saklient/cloud/api'
8
+ require 'sacback'
9
+ require 'sacback/sacloud_request'
10
+
11
+
12
+ module Sacback
13
+ class CLI < Thor
14
+
15
+ ARCHIVE_IMAGE_NAME = 'archive.img'
16
+ DEFAULT_LOCAL_DIR_PATH = '/tmp'
17
+ FTP_PORT = 21
18
+ SACLOUD_API_VERSION = '1.1'
19
+
20
+ class_option :silent, type: :boolean, default: false, desc: 'Not write log messages', aliases: '-s'
21
+ class_option :curl, type: :boolean, default: false, desc: 'Use curl command for downloading'
22
+ class_option :ftpblocksize, type: :numeric, default: 512, desc: 'Block size on FTP transfer (KB) (not used on curl mode)'
23
+
24
+
25
+ desc "a DISK_NAME [LOCAL_DIR_PATH]", "Run all through with the disk (default path: #{DEFAULT_LOCAL_DIR_PATH})"
26
+ def a(disk_name, local_dir_path = DEFAULT_LOCAL_DIR_PATH)
27
+ log "a: Run all through: disk=#{disk_name}, local dir=#{local_dir_path}"
28
+ log
29
+ archive = invoke :c, [disk_name]
30
+ # log "archive: #{archive.id} #{archive.name}"
31
+ invoke :g, [archive.id, local_dir_path]
32
+ invoke :r, [archive.id]
33
+ compressed_file_name = archive.name + '.gz'
34
+ invoke :z, [local_dir_path, ARCHIVE_IMAGE_NAME, compressed_file_name]
35
+ end
36
+
37
+
38
+ desc "c DISK_NAME", "Create an archive from the disk"
39
+ def c(disk_name)
40
+ log "c: Create an archive: disk=#{disk_name}"
41
+ @api = authorize
42
+ disk = get_disk disk_name
43
+ # say "disk: #{disk.id}"
44
+ archive = create_archive disk
45
+ log "The archive was created: id=#{archive.id}, name=#{archive.name}"
46
+ log
47
+ archive
48
+ end
49
+
50
+
51
+ desc "g ARCHIVE_ID [LOCAL_DIR_PATH]", "Get the archive (default path: #{DEFAULT_LOCAL_DIR_PATH})"
52
+ def g(archive_id, local_dir_path = DEFAULT_LOCAL_DIR_PATH)
53
+ log "g: Get the archive: archive id=#{archive_id}, local dir=#{local_dir_path}"
54
+ @api = authorize
55
+ archive = @api.archive.get_by_id archive_id
56
+ log "archive: #{archive.id} #{archive.name}"
57
+
58
+ archive.open_ftp
59
+ log 'ftp opened'
60
+ begin
61
+ ftp_info = archive.ftp_info
62
+ # log "ftp: #{ftp_info.user},#{ftp_info.password},#{ftp_info.host_name}"
63
+
64
+ local_file = File.join local_dir_path, ARCHIVE_IMAGE_NAME
65
+ if options[:curl]
66
+ download_with_curl ftp_info, local_file
67
+ else
68
+ download_with_doublebugftps ftp_info, local_file
69
+ end
70
+
71
+ ensure
72
+ archive.close_ftp
73
+ log 'ftp closed'
74
+ end
75
+ log
76
+ end
77
+
78
+
79
+ desc "r ARCHIVE_ID", "Remove the archive"
80
+ def r(archive_id)
81
+ log "r: Remove the archive: archive id=#{archive_id}"
82
+ request = create_rest_api_request
83
+ result = request.send :delete, "/archive/#{archive_id}"
84
+ if result['Success'] && result['is_ok']
85
+ log "The archive #{archive_id} was removed."
86
+ else
87
+ abort "Error: removing the archive #{archive_id} failed"
88
+ end
89
+ log
90
+ end
91
+
92
+
93
+ desc "z LOCAL_DIR FILE_NAME COMPRESSED_NAME", "Compress the local file (default file name: #{ARCHIVE_IMAGE_NAME})"
94
+ def z(dir, name_orig, name_new)
95
+ log "z: Compress the local file: local dir=#{dir}, target file=#{name_orig}, compressed file=#{name_new}"
96
+ path_orig = File.join dir, name_orig
97
+ path_new = File.join dir, name_new
98
+ log "Start compressing #{path_orig} to #{path_new}"
99
+ # Zlib::GzipWriter.open(path_new, Zlib::BEST_COMPRESSION) do |gz|
100
+ # gz.mtime = File.mtime(path_orig)
101
+ # gz.orig_name = name_orig
102
+ # File.open(path_orig, 'rb') do |fp|
103
+ # while chunk = fp.read(1024 * 1024) do
104
+ # gz.puts chunk
105
+ # end
106
+ # end
107
+ # end
108
+ command = "(gzip -c #{path_orig} > #{path_new}) && rm #{path_orig}"
109
+ `#{command}`
110
+ log "Compression succeeded: #{path_new}"
111
+ log
112
+ end
113
+
114
+
115
+
116
+
117
+ private
118
+
119
+ def log(text = '')
120
+ say text unless options[:silent]
121
+ end
122
+
123
+
124
+ def authorize
125
+ Saklient::Cloud::API::authorize ENV['SACLOUD_TOKEN'], ENV['SACLOUD_SECRET'], ENV['SACLOUD_ZONE']
126
+ end
127
+
128
+
129
+ # Get the disk data whose name includes the specific name
130
+ def get_disk(name)
131
+ disks = @api.disk.with_name_like(name).limit(1).find()
132
+ abort "Error: disk is not found: #{name}" if disks.count == 0
133
+
134
+ disks[0]
135
+ end
136
+
137
+
138
+ def create_archive(disk)
139
+ datetime = DateTime.now.strftime "%Y%m%d-%H%M"
140
+ archive_name = "#{disk.name}-#{datetime}"
141
+ log "Creating a new archive: #{archive_name}"
142
+
143
+ archive = @api.archive.create
144
+ archive.name = archive_name
145
+ archive.source = disk
146
+ archive.save
147
+ result = archive.sleep_while_copying
148
+ abort "Creating the archive failed: #{archive_name}" unless result
149
+
150
+ log "Creating the archive succeeded: #{archive_name}"
151
+ archive
152
+ end
153
+
154
+
155
+ def create_rest_api_request
156
+ Sacback::SacloudRequest.new(ENV['SACLOUD_TOKEN'], ENV['SACLOUD_SECRET'], ENV['SACLOUD_ZONE'], SACLOUD_API_VERSION)
157
+ end
158
+
159
+
160
+ def download_with_doublebugftps(ftp_info, local_file)
161
+ BasicSocket.do_not_reverse_lookup = true
162
+ ftps = DoubleBagFTPS.new
163
+ ftps.ftps_mode = DoubleBagFTPS::EXPLICIT
164
+ ftps.passive = true
165
+ # ftps.debug_mode = true
166
+ block_size = options[:ftpblocksize] * 1024
167
+ ftps.connect ftp_info.host_name, FTP_PORT
168
+ begin
169
+ ftps.login ftp_info.user, ftp_info.password
170
+ log "Start downloading (DoubleBagFTPS): remote file=#{ARCHIVE_IMAGE_NAME}, local file=#{local_file}, block size=#{block_size} bytes"
171
+ ftps.getbinaryfile ARCHIVE_IMAGE_NAME, local_file, block_size
172
+ log "Finished downloading: #{local_file}"
173
+ ensure
174
+ ftps.close unless ftps.closed?
175
+ end
176
+ end
177
+
178
+
179
+ def download_with_curl(ftp_info, local_file)
180
+ url = "ftp://#{ftp_info.host_name}/#{ARCHIVE_IMAGE_NAME}"
181
+ auth = "-u #{ftp_info.user}:#{ftp_info.password}"
182
+ silent = options[:silent] ? '-s -S' : '' # Show errors only
183
+ log "Start downloading (curl): remote file=#{ARCHIVE_IMAGE_NAME}, local file=#{local_file}"
184
+ command = "curl --ftp-ssl --ftp-pasv #{silent} #{auth} -o #{local_file} #{url}"
185
+ `#{command}`
186
+ log "Finished downloading: #{local_file}"
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,53 @@
1
+ # coding: utf-8
2
+
3
+ require 'net/https'
4
+ require 'uri'
5
+
6
+
7
+ module Sacback
8
+ class SacloudRequest
9
+
10
+ def initialize(token, secret, zone, api_version = '1.1')
11
+ @token = token
12
+ @secret = secret
13
+ @base_url = "https://secure.sakura.ad.jp/cloud/zone/#{zone}/api/cloud/#{api_version}"
14
+ end
15
+
16
+
17
+ # Send a request
18
+ def send(method, path, params = {})
19
+ uri = URI.parse(@base_url + path)
20
+
21
+ # HTTPS setting
22
+ https = Net::HTTP.new(uri.host, uri.port)
23
+ https.use_ssl = true
24
+ https.verify_mode = OpenSSL::SSL::VERIFY_NONE
25
+
26
+ # Set the request class
27
+ request_class = case method
28
+ when :get then Net::HTTP::Get
29
+ when :post then Net::HTTP::Post
30
+ when :delete then Net::HTTP::Delete
31
+ when :put then Net::HTTP::Put
32
+ else
33
+ raise ArgumentError, 'invalid method'
34
+ end
35
+
36
+ # Basic Auth setting
37
+ request = request_class.new(uri.path)
38
+ request.basic_auth @token, @secret
39
+
40
+ # Parameter setting
41
+ request.body = JSON.generate(params) if (method == :post) || (method == :put)
42
+
43
+ # Send the request
44
+ response = https.start do |x|
45
+ x.request request
46
+ end
47
+
48
+ # Return the body as JSON
49
+ JSON.parse response.body
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Sacback
2
+ VERSION = "0.0.3"
3
+ end
data/sacback.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sacback/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sacback"
8
+ spec.version = Sacback::VERSION
9
+ spec.authors = ["Eiichi Shimotori"]
10
+ spec.email = ["eiichi.shimotori@gmail.com"]
11
+ spec.summary = %q{Sakura Cloud disk backup tool}
12
+ spec.description = %q{Command line tool to backup a disk on Sakura Cloud}
13
+ spec.homepage = "https://github.com/shimotori/sacback"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+
24
+ spec.add_dependency "thor"
25
+ spec.add_dependency "saklient"
26
+ spec.add_dependency "double-bag-ftps"
27
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sacback
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Eiichi Shimotori
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: saklient
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: double-bag-ftps
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Command line tool to backup a disk on Sakura Cloud
84
+ email:
85
+ - eiichi.shimotori@gmail.com
86
+ executables:
87
+ - sacback
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - bin/sacback
97
+ - lib/sacback.rb
98
+ - lib/sacback/cli.rb
99
+ - lib/sacback/sacloud_request.rb
100
+ - lib/sacback/version.rb
101
+ - sacback.gemspec
102
+ homepage: https://github.com/shimotori/sacback
103
+ licenses:
104
+ - MIT
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.2.2
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Sakura Cloud disk backup tool
126
+ test_files: []