s3streambackup 0.1.0 → 0.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.
- data/README.md +16 -3
- data/VERSION +1 -1
- data/bin/s3streambackup +38 -100
- data/bin/s3streamrestore +66 -0
- data/lib/s3streambackup.rb +137 -0
- data/lib/s3streambackup/units.rb +19 -0
- data/s3streambackup.gemspec +6 -3
- metadata +7 -3
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# S3 Stream Backup
|
2
2
|
|
3
3
|
Stores data from STDIN in S3 object using multipart upload and removes oldest backups to keep maximum desired backup object count.
|
4
|
+
Restore tool included.
|
4
5
|
|
5
6
|
## Installing
|
6
7
|
|
@@ -15,12 +16,20 @@ gem install s3streambackup
|
|
15
16
|
## Usage
|
16
17
|
|
17
18
|
```bash
|
18
|
-
|
19
|
+
# store some-backup.file in mybucket bucket and name it my-backup
|
20
|
+
s3streambackup mybucket my-backup < some-backup.file
|
21
|
+
|
22
|
+
# list available backups of my-backup
|
23
|
+
s3streamrestore mybucket my-backup
|
24
|
+
|
25
|
+
# restore my-backup backup from 2013-08-06 09:03:17 UTC
|
26
|
+
s3streamrestore mybucket my-backup 130806_090317 > some-backup.file
|
19
27
|
```
|
20
28
|
|
21
|
-
Note that you should have your S3 key and secret set in environment `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` or you can specify them in command line with `--key` and `--secret
|
29
|
+
Note that you should have your S3 key and secret set in environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` or you can specify them in command line with `--key` and `--secret` options.
|
22
30
|
|
23
31
|
You can store your backup object within prefix by using `--prefix` option e.g.: `--prefix backups/server1/`.
|
32
|
+
Additionally you can postfix your backup objects by using `--postfix` option e.g: `--postfix .sql.gz`.
|
24
33
|
|
25
34
|
By default two backup copies will be kept. You can change this number by using `--keep` options.
|
26
35
|
|
@@ -29,7 +38,11 @@ For other usage information use `--help`.
|
|
29
38
|
### PostgreSQL backup example
|
30
39
|
|
31
40
|
```bash
|
32
|
-
|
41
|
+
# backup to S3
|
42
|
+
su - postgres -c 'pg_dumpall' | xz -2 | s3streambackup --keep 7 --prefix backups/zabbix/ mybucket postgress-all
|
43
|
+
|
44
|
+
# restore could look like this
|
45
|
+
s3streambackup --prefix backups/zabbix/ mybucket postgress-all 130806_090317 | xz -d | su - postgres -c 'psql'
|
33
46
|
```
|
34
47
|
|
35
48
|
## Contributing to S3 Stream Backup
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/bin/s3streambackup
CHANGED
@@ -1,117 +1,55 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
require '
|
5
|
-
require 'logger'
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
require 's3streambackup'
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
class BucketCollection
|
11
|
-
def [](name)
|
12
|
-
# if name is DNS compatible we still cannot use it for writes if it does contain dots
|
13
|
-
return S3::Bucket.new(name.to_s, :owner => nil, :config => config) if client.dns_compatible_bucket_name?(name) and not name.include? '.'
|
14
|
-
|
15
|
-
# save region mapping for bucket for futher requests
|
16
|
-
@@location_cache = {} unless defined? @@location_cache
|
17
|
-
# if we have it cased use it; else try to fetch it and if it is nil bucket is in standard region
|
18
|
-
region = @@location_cache[name] || @@location_cache[name] = S3::Bucket.new(name.to_s, :owner => nil, :config => config).location_constraint || @@location_cache[name] = :standard
|
19
|
-
|
20
|
-
# no need to specify region if bucket is in standard region
|
21
|
-
return S3::Bucket.new(name.to_s, :owner => nil, :config => config) if region == :standard
|
22
|
-
|
23
|
-
# use same config but with region specified for buckets that are not DNS compatible or have dots and are not in standard region
|
24
|
-
S3::Bucket.new(name.to_s, :owner => nil, :config => config.with(region: region))
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
settings = CLI.new do
|
6
|
+
S3StreamBackup.new do
|
7
|
+
cli do
|
8
|
+
description 'Store backup from STDIN to S3'
|
31
9
|
stdin :data
|
32
|
-
option :key,
|
33
|
-
short: :k,
|
34
|
-
description: 'S3 key',
|
35
|
-
default: ENV['AWS_ACCESS_KEY_ID']
|
36
|
-
option :secret,
|
37
|
-
short: :s,
|
38
|
-
description: 'S3 key secret',
|
39
|
-
default_label: '<secret>',
|
40
|
-
default: ENV['AWS_SECRET_ACCESS_KEY']
|
41
|
-
option :prefix,
|
42
|
-
short: :p,
|
43
|
-
description: 'prefix under which the objects will be kept',
|
44
|
-
default: ''
|
45
10
|
option :keep,
|
46
11
|
short: :K,
|
47
12
|
description: 'how many backup file to keep',
|
48
13
|
cast: Integer,
|
49
14
|
default: 2
|
50
|
-
option :
|
51
|
-
short: :
|
52
|
-
description: '
|
53
|
-
|
54
|
-
description: 'use plain connections instead of SSL to S3'
|
55
|
-
switch :debug,
|
56
|
-
description: 'log debug messages'
|
57
|
-
argument :bucket,
|
58
|
-
description: 'name of bucket to upload data to'
|
59
|
-
argument :name,
|
60
|
-
description: 'name under which the object will be stored'
|
61
|
-
end.parse! do |settings|
|
62
|
-
fail 'AWS_ACCESS_KEY_ID environment not set and --key not used' unless settings.key
|
63
|
-
fail 'AWS_SECRET_ACCESS_KEY environment not set and --secret not used' unless settings.secret
|
64
|
-
end
|
65
|
-
|
66
|
-
log = Logger.new(settings.log_file ? settings.log_file : STDOUT)
|
67
|
-
log.formatter = proc do |severity, datetime, progname, msg|
|
68
|
-
"[#{datetime.utc.strftime "%Y-%m-%d %H:%M:%S.%6N %Z"}] [#{$$}] #{severity}: #{msg.strip}\n"
|
69
|
-
end
|
70
|
-
|
71
|
-
log.level = Logger::INFO
|
72
|
-
log.level = Logger::DEBUG if settings.debug
|
73
|
-
|
74
|
-
begin
|
75
|
-
s3 = AWS::S3.new(
|
76
|
-
access_key_id: settings.key,
|
77
|
-
secret_access_key: settings.secret,
|
78
|
-
logger: log,
|
79
|
-
log_level: :debug,
|
80
|
-
use_ssl: ! settings.plain
|
81
|
-
)
|
82
|
-
|
83
|
-
upload_date = Time.now.utc.strftime "%y%m%d_%H%M%S"
|
84
|
-
prefix = "#{settings.prefix}#{settings.name}.backup."
|
85
|
-
path = "#{prefix}#{upload_date}"
|
86
|
-
|
87
|
-
bucket = s3.buckets[settings.bucket]
|
88
|
-
backup = bucket.objects[path]
|
89
|
-
log.info "writting to: #{path}"
|
90
|
-
|
91
|
-
# make sure we use multipart upload
|
92
|
-
total_bytes = 0
|
93
|
-
backup.write(estimated_content_length: 10 * 1024 ** 3) do |buffer, bytes|
|
94
|
-
log.info "#{total_bytes} bytes written..."
|
95
|
-
data = settings.stdin.read(bytes)
|
96
|
-
total_bytes += data.bytesize
|
97
|
-
buffer.write data
|
15
|
+
option :postfix,
|
16
|
+
short: :P,
|
17
|
+
description: 'postfix which is appended to backup objects',
|
18
|
+
default: ''
|
98
19
|
end
|
99
|
-
log.info "total upload size: #{total_bytes}"
|
100
20
|
|
101
|
-
|
21
|
+
main do |settings, log, s3|
|
22
|
+
upload_date = Time.now.utc.strftime "%y%m%d_%H%M%S"
|
23
|
+
prefix = "#{settings.prefix}#{settings.name}.backup."
|
24
|
+
path = "#{prefix}#{upload_date}#{settings.postfix}"
|
25
|
+
|
26
|
+
bucket = s3.buckets[settings.bucket]
|
27
|
+
backup = bucket.objects[path]
|
28
|
+
log.info "writting to: #{path}"
|
29
|
+
|
30
|
+
# make sure we use multipart upload
|
31
|
+
total_bytes = ProgeressLogger.new
|
32
|
+
backup.write(
|
33
|
+
content_type: 'application/octet-stream',
|
34
|
+
estimated_content_length: 10 * 1024 ** 3
|
35
|
+
) do |buffer, bytes|
|
36
|
+
total_bytes.log(log, ' written...')
|
37
|
+
data = settings.stdin.read(bytes)
|
38
|
+
total_bytes << data.bytesize
|
39
|
+
buffer.write data
|
40
|
+
end
|
41
|
+
log.info "total upload size: #{total_bytes.in_bytes_auto}"
|
102
42
|
|
103
|
-
|
43
|
+
backups = bucket.objects.with_prefix(prefix).to_a
|
104
44
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
backup
|
45
|
+
log.info "keeping maximum #{settings.keep} latest buckups of #{backups.length} storred"
|
46
|
+
|
47
|
+
if backups.length > settings.keep
|
48
|
+
backups.take(backups.length - settings.keep).each do |backup|
|
49
|
+
log.info "removing oldest backup: #{backup.key}"
|
50
|
+
backup.delete
|
51
|
+
end
|
109
52
|
end
|
110
53
|
end
|
111
|
-
rescue => error
|
112
|
-
msg = "#{error.class.name}: #{error.message}\n#{error.backtrace.join("\n")}"
|
113
|
-
log.error msg
|
114
|
-
STDERR.write msg
|
115
|
-
exit 10
|
116
54
|
end
|
117
55
|
|
data/bin/s3streamrestore
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
require 's3streambackup'
|
5
|
+
|
6
|
+
S3StreamBackup.new do
|
7
|
+
cli do
|
8
|
+
description 'Restore backup from S3 to STDOUT'
|
9
|
+
option :keep,
|
10
|
+
short: :K,
|
11
|
+
description: 'how many backup file to keep',
|
12
|
+
cast: Integer,
|
13
|
+
default: 2
|
14
|
+
option :postfix,
|
15
|
+
short: :P,
|
16
|
+
description: 'ignored',
|
17
|
+
default: ''
|
18
|
+
argument :date,
|
19
|
+
description: 'date of the backup to restore e.g 130806_090317; if not specified list available backups',
|
20
|
+
required: false
|
21
|
+
end
|
22
|
+
|
23
|
+
main do |settings, log, s3|
|
24
|
+
prefix = "#{settings.prefix}#{settings.name}.backup."
|
25
|
+
|
26
|
+
bucket = s3.buckets[settings.bucket]
|
27
|
+
backups = bucket.objects.with_prefix(prefix).to_a
|
28
|
+
if not settings.date
|
29
|
+
dates = backups.map do |backup|
|
30
|
+
backup.key.to_s.match(/.backup.([0-9]{6}_[0-9]{6})/).captures.first
|
31
|
+
end
|
32
|
+
dates.each do |date|
|
33
|
+
puts "#{date} (#{Time.parse(date + ' +0000')})"
|
34
|
+
end
|
35
|
+
exit 0
|
36
|
+
end
|
37
|
+
|
38
|
+
backup = backups.select do |backup|
|
39
|
+
backup.key =~ /.backup.#{settings.date}/
|
40
|
+
end.first
|
41
|
+
|
42
|
+
fail "backup from date #{settings.date} not found" unless backup
|
43
|
+
|
44
|
+
length = backup.content_length
|
45
|
+
log.info "sourcing from: #{backup.key} length: #{length}"
|
46
|
+
|
47
|
+
total_bytes = ProgeressLogger.new
|
48
|
+
backup.read do |data|
|
49
|
+
total_bytes.log log, ' read...'
|
50
|
+
total_bytes << data.bytesize
|
51
|
+
begin
|
52
|
+
STDOUT.write data
|
53
|
+
rescue Errno::EPIPE
|
54
|
+
log.warn "STDOUT closed prematurely"
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
if total_bytes != length
|
59
|
+
log.warn "got differnet amount of data (#{total_bytes} bytes) than expected (#{length} bytes)"
|
60
|
+
exit 1
|
61
|
+
else
|
62
|
+
log.info "total restore size: #{total_bytes.in_bytes_auto}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'cli'
|
2
|
+
require 'aws-sdk'
|
3
|
+
require 'logger'
|
4
|
+
require 's3streambackup/units'
|
5
|
+
|
6
|
+
## HACK: Auto select region based on location_constraint
|
7
|
+
module AWS
|
8
|
+
class S3
|
9
|
+
class BucketCollection
|
10
|
+
def [](name)
|
11
|
+
# if name is DNS compatible we still cannot use it for writes if it does contain dots
|
12
|
+
return S3::Bucket.new(name.to_s, :owner => nil, :config => config) if client.dns_compatible_bucket_name?(name) and not name.include? '.'
|
13
|
+
|
14
|
+
# save region mapping for bucket for futher requests
|
15
|
+
@@location_cache = {} unless defined? @@location_cache
|
16
|
+
# if we have it cased use it; else try to fetch it and if it is nil bucket is in standard region
|
17
|
+
region = @@location_cache[name] || @@location_cache[name] = S3::Bucket.new(name.to_s, :owner => nil, :config => config).location_constraint || @@location_cache[name] = :standard
|
18
|
+
|
19
|
+
# no need to specify region if bucket is in standard region
|
20
|
+
return S3::Bucket.new(name.to_s, :owner => nil, :config => config) if region == :standard
|
21
|
+
|
22
|
+
# use same config but with region specified for buckets that are not DNS compatible or have dots and are not in standard region
|
23
|
+
S3::Bucket.new(name.to_s, :owner => nil, :config => config.with(region: region))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class S3StreamBackup
|
30
|
+
def initialize(&block)
|
31
|
+
instance_eval &block
|
32
|
+
cli_setup = @cli_setup
|
33
|
+
cli_verify_setup = @cli_verify_setup
|
34
|
+
settings = CLI.new do
|
35
|
+
option :key,
|
36
|
+
short: :k,
|
37
|
+
description: 'S3 key',
|
38
|
+
default: ENV['AWS_ACCESS_KEY_ID']
|
39
|
+
option :secret,
|
40
|
+
short: :s,
|
41
|
+
description: 'S3 key secret',
|
42
|
+
default_label: '<secret>',
|
43
|
+
default: ENV['AWS_SECRET_ACCESS_KEY']
|
44
|
+
option :prefix,
|
45
|
+
short: :p,
|
46
|
+
description: 'prefix under which the backup objects are kept',
|
47
|
+
default: ''
|
48
|
+
option :log_file,
|
49
|
+
short: :l,
|
50
|
+
description: 'location of log file; if not specifed log to STDERR'
|
51
|
+
switch :plain,
|
52
|
+
description: 'use plain connections instead of SSL to S3'
|
53
|
+
switch :verbose,
|
54
|
+
short: :v,
|
55
|
+
description: 'log debug messages'
|
56
|
+
switch :debug,
|
57
|
+
short: :d,
|
58
|
+
description: 'log AWS SDK debug messages'
|
59
|
+
argument :bucket,
|
60
|
+
description: 'name of bucket to upload data to'
|
61
|
+
argument :name,
|
62
|
+
description: 'name under which the object will be stored'
|
63
|
+
instance_eval &cli_setup if cli_setup
|
64
|
+
end.parse! do |settings|
|
65
|
+
fail 'AWS_ACCESS_KEY_ID environment not set and --key not used' unless settings.key
|
66
|
+
fail 'AWS_SECRET_ACCESS_KEY environment not set and --secret not used' unless settings.secret
|
67
|
+
instance_eval &cli_verify_setup if cli_verify_setup
|
68
|
+
end
|
69
|
+
|
70
|
+
log = Logger.new(settings.log_file ? settings.log_file : STDERR)
|
71
|
+
log.formatter = proc do |severity, datetime, progname, msg|
|
72
|
+
"[#{datetime.utc.strftime "%Y-%m-%d %H:%M:%S.%6N %Z"}] [#{$$}] #{severity}: #{msg.strip}\n"
|
73
|
+
end
|
74
|
+
|
75
|
+
log.level = Logger::INFO
|
76
|
+
log.level = Logger::DEBUG if settings.verbose or settings.debug
|
77
|
+
|
78
|
+
begin
|
79
|
+
s3 = AWS::S3.new(
|
80
|
+
access_key_id: settings.key,
|
81
|
+
secret_access_key: settings.secret,
|
82
|
+
logger: settings.debug ? log : nil,
|
83
|
+
log_level: :debug,
|
84
|
+
use_ssl: ! settings.plain
|
85
|
+
)
|
86
|
+
@main.call(settings, log, s3)
|
87
|
+
rescue => error
|
88
|
+
msg = "#{error.class.name}: #{error.message}\n#{error.backtrace.join("\n")}"
|
89
|
+
log.error msg
|
90
|
+
STDERR.write msg
|
91
|
+
exit 10
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def cli(&block)
|
96
|
+
@cli_setup = block
|
97
|
+
end
|
98
|
+
|
99
|
+
def cli_verify(&block)
|
100
|
+
@cli_verify_setup = block
|
101
|
+
end
|
102
|
+
|
103
|
+
def main(&block)
|
104
|
+
@main = block
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class ProgeressLogger
|
109
|
+
def initialize
|
110
|
+
@bytes = 0
|
111
|
+
end
|
112
|
+
|
113
|
+
def log(logger, postfix)
|
114
|
+
if logger.debug?
|
115
|
+
was, @output_bytes = @output_bytes || '', @bytes.in_bytes_auto
|
116
|
+
logger.debug "#{@output_bytes}#{postfix}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def <<(bytes)
|
121
|
+
@bytes += bytes
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_s
|
125
|
+
@bytes.to_s
|
126
|
+
end
|
127
|
+
|
128
|
+
include Comparable
|
129
|
+
def <=>(value)
|
130
|
+
@bytes <=> value
|
131
|
+
end
|
132
|
+
|
133
|
+
def in_bytes_auto
|
134
|
+
@bytes.in_bytes_auto
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Numeric
|
2
|
+
UNITS = %w{bytes KiB MiB GiB TiB PiB EiB}
|
3
|
+
def in_bytes_auto
|
4
|
+
size = self.to_f
|
5
|
+
units = UNITS.dup
|
6
|
+
|
7
|
+
while size > 999
|
8
|
+
size /= 1024
|
9
|
+
units.shift
|
10
|
+
end
|
11
|
+
|
12
|
+
if units.length == UNITS.length
|
13
|
+
"#{'%d' % size} #{units.first}"
|
14
|
+
else
|
15
|
+
"#{'%.1f' % size} #{units.first}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
data/s3streambackup.gemspec
CHANGED
@@ -5,14 +5,14 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "s3streambackup"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jakub Pastuszek"]
|
12
|
-
s.date = "2013-08-
|
12
|
+
s.date = "2013-08-06"
|
13
13
|
s.description = "Stores data from STDIN in S3 object using multipart upload and removes oldest backups to keep maximum desired backup object count."
|
14
14
|
s.email = "jpastuszek@gmail.com"
|
15
|
-
s.executables = ["s3streambackup"]
|
15
|
+
s.executables = ["s3streambackup", "s3streamrestore"]
|
16
16
|
s.extra_rdoc_files = [
|
17
17
|
"LICENSE.txt",
|
18
18
|
"README.md"
|
@@ -27,9 +27,12 @@ Gem::Specification.new do |s|
|
|
27
27
|
"Rakefile",
|
28
28
|
"VERSION",
|
29
29
|
"bin/s3streambackup",
|
30
|
+
"bin/s3streamrestore",
|
30
31
|
"features/s3streambackup.feature",
|
31
32
|
"features/step_definitions/s3streambackup_steps.rb",
|
32
33
|
"features/support/env.rb",
|
34
|
+
"lib/s3streambackup.rb",
|
35
|
+
"lib/s3streambackup/units.rb",
|
33
36
|
"s3streambackup.gemspec",
|
34
37
|
"spec/s3streambackup_spec.rb",
|
35
38
|
"spec/spec_helper.rb"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: s3streambackup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-08-
|
12
|
+
date: 2013-08-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: cli
|
@@ -144,6 +144,7 @@ description: Stores data from STDIN in S3 object using multipart upload and remo
|
|
144
144
|
email: jpastuszek@gmail.com
|
145
145
|
executables:
|
146
146
|
- s3streambackup
|
147
|
+
- s3streamrestore
|
147
148
|
extensions: []
|
148
149
|
extra_rdoc_files:
|
149
150
|
- LICENSE.txt
|
@@ -158,9 +159,12 @@ files:
|
|
158
159
|
- Rakefile
|
159
160
|
- VERSION
|
160
161
|
- bin/s3streambackup
|
162
|
+
- bin/s3streamrestore
|
161
163
|
- features/s3streambackup.feature
|
162
164
|
- features/step_definitions/s3streambackup_steps.rb
|
163
165
|
- features/support/env.rb
|
166
|
+
- lib/s3streambackup.rb
|
167
|
+
- lib/s3streambackup/units.rb
|
164
168
|
- s3streambackup.gemspec
|
165
169
|
- spec/s3streambackup_spec.rb
|
166
170
|
- spec/spec_helper.rb
|
@@ -179,7 +183,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
179
183
|
version: '0'
|
180
184
|
segments:
|
181
185
|
- 0
|
182
|
-
hash:
|
186
|
+
hash: 4107987020335858105
|
183
187
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
184
188
|
none: false
|
185
189
|
requirements:
|