calevipoeg-zeus 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6820060f9e8309528aac997df89e9724c21c8a9533d2bac39b3172b05ca6d24a
4
+ data.tar.gz: 99a4d6699a253c6c8047a477294d0e2e179ce3226295051a7606c4af79aecb6b
5
+ SHA512:
6
+ metadata.gz: b6642017621bf5afe8ff790282081cab1bafac9c8973c95b5a960a542093f96c6572fbb26bdef08ab3ef632356ff80ea461546dd1c997e026bb3c02ec5e17123
7
+ data.tar.gz: 8d884d1f4fc000b212fad4bcc625d091f09e1987fb151ad7723917a9ef930b7c0911f03a963beda42ef0e2aa80c8c55ae869f58e6103b5464340c3df38b3d22b
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .rspec_status
10
+ /*.gem
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.1
6
+ before_install: gem install bundler -v 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in zeus.gemspec
4
+ gemspec
5
+
6
+ gem 'rake'
7
+ gem 'rspec'
@@ -0,0 +1,34 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ zeus (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.3)
10
+ rake (13.0.1)
11
+ rspec (3.9.0)
12
+ rspec-core (~> 3.9.0)
13
+ rspec-expectations (~> 3.9.0)
14
+ rspec-mocks (~> 3.9.0)
15
+ rspec-core (3.9.2)
16
+ rspec-support (~> 3.9.3)
17
+ rspec-expectations (3.9.2)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.9.0)
20
+ rspec-mocks (3.9.1)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.9.0)
23
+ rspec-support (3.9.3)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ rake
30
+ rspec
31
+ zeus!
32
+
33
+ BUNDLED WITH
34
+ 2.1.4
@@ -0,0 +1,61 @@
1
+ # Zeus
2
+
3
+ This is a helper package for my own projects
4
+
5
+ ## Installation
6
+
7
+ ```ruby
8
+ gem 'calevipoeg-zeus', require: 'zeus'
9
+ ```
10
+
11
+ ## SSH Tunneling
12
+
13
+ ```ruby
14
+ Zeus::SshTunnel.link('root@db-01', '5555:localhost:5432') do
15
+ system('telnet localhost 5555')
16
+ end
17
+ ```
18
+
19
+ ### S3 IO
20
+
21
+ This class represents an IO object (File, StringIO) which can be
22
+ uploaded to the S3-compatible storage with PUT or multipart requests
23
+ S3Iterate
24
+ ```ruby
25
+ io = Zeus::S3Io.new(File.open('movie.mp4'))
26
+ s3 = Aws::S3::Client.new
27
+
28
+ io.upload(s3, bucket: 'bucket', key: 'movies/movie.mp4') do |progress|
29
+ puts "[#{progress.to_i}%] Uploading #{key}"
30
+ end
31
+ ```
32
+
33
+ ### S3 iterable
34
+
35
+ ```ruby
36
+ s3 = Aws::S3::Client.new
37
+
38
+ Zeus::S3Iterate.new(s3).delete_if(bucket: 'bucket', prefix: 'movies/') do |file|
39
+ break(false) unless 2.years.ago >= file.last_modified
40
+
41
+ puts "[-] removing (last mod #{file.last_modified}) #{file.key}"
42
+ end
43
+ ```
44
+
45
+ ### PostgreSQL Base Backup
46
+
47
+ ```ruby
48
+ begin
49
+ s3 = Aws::S3::Client.new
50
+ pg = Zeus::PgBaseBackup.new(s3: s3, bucket: 'backup')
51
+
52
+ Zeus::SshTunnel.link('root@db', '5555:localhost:5432') do
53
+ pg.dump(port: '5555', user: 'replica', password: 'password')
54
+ end
55
+
56
+ pg.upload
57
+ pg.clear(12.days.ago)
58
+ ensure
59
+ pg.unlink
60
+ end
61
+ ```
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'zeus'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require'irb'
14
+ IRB.start(__FILE__)
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+
3
+ rm ./*.gem
4
+ gem build zeus.gemspec
5
+ gem push calevipoeg-zeus-*
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zeus/version'
4
+
5
+ module Zeus
6
+ autoload :PgBaseBackup, 'zeus/pg_base_backup'
7
+ autoload :S3Io, 'zeus/s3_io'
8
+ autoload :S3Iterate, 'zeus/s3_iterate'
9
+ autoload :SshTunnel, 'zeus/ssh_tunnel'
10
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zeus
4
+ class PgBaseBackup
5
+ attr_reader :s3, :bucket, :dir, :prefix
6
+
7
+ # @param s3 [Aws::Client::S3]
8
+ def initialize(s3:, bucket:, prefix: 'postgresql')
9
+ @dir = Dir.mktmpdir('pg-base-backup')
10
+ @s3 = s3
11
+ @bucket = bucket
12
+ @prefix = prefix
13
+ end
14
+
15
+ def dump(user:, password: nil, port: '5432', host: 'localhost', rate: '10M')
16
+ system <<~CMD.tr("\n", ' '), exception: true
17
+ PG_COLOR=always PGPASSWORD=#{Shellwords.escape password} pg_basebackup
18
+ -D #{dir.pathmap} -Ft -z -Xs
19
+ --host #{host}
20
+ --port #{port}
21
+ --username #{user}
22
+ --max-rate="#{rate}"
23
+ --write-recovery-conf
24
+ --progress
25
+ --verbose
26
+ CMD
27
+ end
28
+
29
+ def upload
30
+ Dir.entries(dir.pathmap).each do |filename|
31
+ next if filename.start_with?('.')
32
+
33
+ key = File.join(folder, filename)
34
+ file = File.open(File.join(dir, filename), 'rb')
35
+ io = Zeus::S3Io.new(file)
36
+
37
+ io.upload(s3, bucket: bucket, key: key) do |progress|
38
+ puts "[#{progress.to_i}%] Uploading #{key}"
39
+ end
40
+ end
41
+ end
42
+
43
+ # clear previously sored backup
44
+ # @param [Time]
45
+ def clear(past_time)
46
+ Zeus::S3Iterate.new(s3).delete_if(bucket: bucket, prefix: prefix) do |file|
47
+ break(false) unless past_time >= file.last_modified
48
+
49
+ puts "[-] removing (last mod #{file.last_modified}) #{file.key}"
50
+ end
51
+ end
52
+
53
+ def unlink
54
+ FileUtils.remove_entry(dir.pathmap) if File.directory?(dir.pathmap)
55
+ end
56
+
57
+ private
58
+
59
+ def folder
60
+ "#{prefix}/#{timestamp}"
61
+ end
62
+
63
+ def timestamp
64
+ @timestamp ||= Time.current.strftime('%Y-%m-%d-%H-%M')
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-s3'
4
+
5
+ # === S3 decorator for the IO object
6
+ module Zeus
7
+ class S3Io
8
+ # * Размер каждой части, кроме последней, должен быть не менее 5MB
9
+ # * Номер — это целое число в промежутке от 1 до 10000 включительно
10
+ DEFAULT_PART_SIZE = 1024 * 1024 * 50 # 50MB
11
+
12
+ attr_reader :io, :part_size
13
+
14
+ # @param [File, StringIO, IO]
15
+ def initialize(io, part_size: DEFAULT_PART_SIZE)
16
+ @io = io
17
+ @part_size = part_size
18
+ end
19
+
20
+ # @param s3 [Aws::S3::Client]
21
+ def upload(s3, bucket:, key:, **extra, &block)
22
+ if multipart?
23
+ upload_multipart(s3, bucket: bucket, key: key, **extra, &block)
24
+ else
25
+ s3.put_object(bucket: bucket, key: key, body: io.read, **extra)
26
+ yield(100) if block_given?
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def upload_multipart(s3, bucket:, key:, **extra)
33
+ multipart = s3.create_multipart_upload(bucket: bucket, key: key, **extra)
34
+ parts = []
35
+
36
+ each_part do |part, num|
37
+ parts << s3.upload_part(
38
+ body: part,
39
+ bucket: bucket,
40
+ content_length: part.bytesize,
41
+ key: key,
42
+ part_number: num,
43
+ upload_id: multipart.upload_id
44
+ )
45
+
46
+ progress = (num.fdiv(total_parts) * 100).round
47
+ progress = 100 if progress > 100
48
+
49
+ yield(progress) if block_given?
50
+ end
51
+
52
+ s3.complete_multipart_upload(
53
+ bucket: bucket,
54
+ key: key,
55
+ upload_id: multipart.upload_id,
56
+ multipart_upload: {
57
+ parts: parts.map.with_index do |part, ix|
58
+ {
59
+ etag: part.etag,
60
+ part_number: ix.next
61
+ }
62
+ end
63
+ }
64
+ )
65
+ end
66
+
67
+ # yields parts bytes and number of a page
68
+ def each_part(num: 1)
69
+ until io.eof?
70
+ yield(io.read(part_size), num)
71
+ num += 1
72
+ end
73
+ end
74
+
75
+ def multipart?
76
+ total_parts.nonzero?
77
+ end
78
+
79
+ def total_parts
80
+ io.size.fdiv(part_size).ceil
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-s3'
4
+
5
+ # Clean S3 files base on file#lost_mod
6
+ module Zeus
7
+ class S3Iterate
8
+ attr_reader :s3
9
+
10
+ def initialize(s3)
11
+ @s3 = s3
12
+ end
13
+
14
+ def delete_if(bucket:, prefix:)
15
+ each_file(bucket: bucket, prefix: prefix) do |file|
16
+ next unless yield(file)
17
+
18
+ s3.delete_object(bucket: bucket, key: file.key)
19
+ end
20
+ end
21
+
22
+ def each_file(bucket:, prefix:)
23
+ s3.list_objects_v2(bucket: bucket, prefix: prefix).each do |page|
24
+ iterate(page) do |list|
25
+ list.contents.each do |file|
26
+ yield(file)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ # @param page [Seahorse::Client::Response]
35
+ def iterate(page, &block)
36
+ yield(page)
37
+ iterate(page.next_page, &block) if page.next_page?
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # === Example
4
+ # Zeus::SshTunnel.link('root@db-01', '5555:localhost:5432') do
5
+ # system('telnet localhost 5555')
6
+ # end
7
+ module Zeus
8
+ class SshTunnel
9
+ def self.link(remote, destination)
10
+ IO.popen("ssh -N #{remote} -L #{destination}") do |pipe|
11
+ pipe.read # just like wait
12
+ yield
13
+ Process.kill('QUIT', pipe.pid)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zeus
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'lib/zeus/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'calevipoeg-zeus'
5
+ spec.version = Zeus::VERSION
6
+ spec.authors = ['Aliaksandr Shylau']
7
+ spec.email = %w[alex.shilov.by@gmail.com]
8
+
9
+ spec.summary = 'Helper package for own projects'
10
+ spec.description = 'Helper package for own projects'
11
+ spec.homepage = 'https://github.com/calevipoeg/zeus'
12
+ spec.license = 'MIT'
13
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
14
+
15
+ spec.metadata['homepage_uri'] = spec.homepage
16
+ spec.metadata['source_code_uri'] = 'https://github.com/calevipoeg/zeus'
17
+ spec.metadata['changelog_uri'] = 'https://github.com/calevipoeg/zeus'
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = 'exe'
25
+ # @refs
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = %w[lib]
28
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: calevipoeg-zeus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Aliaksandr Shylau
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-06-02 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Helper package for own projects
14
+ email:
15
+ - alex.shilov.by@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rspec"
22
+ - ".travis.yml"
23
+ - Gemfile
24
+ - Gemfile.lock
25
+ - README.md
26
+ - Rakefile
27
+ - bin/console
28
+ - bin/release.sh
29
+ - bin/setup
30
+ - lib/zeus.rb
31
+ - lib/zeus/pg_base_backup.rb
32
+ - lib/zeus/s3_io.rb
33
+ - lib/zeus/s3_iterate.rb
34
+ - lib/zeus/ssh_tunnel.rb
35
+ - lib/zeus/version.rb
36
+ - zeus.gemspec
37
+ homepage: https://github.com/calevipoeg/zeus
38
+ licenses:
39
+ - MIT
40
+ metadata:
41
+ homepage_uri: https://github.com/calevipoeg/zeus
42
+ source_code_uri: https://github.com/calevipoeg/zeus
43
+ changelog_uri: https://github.com/calevipoeg/zeus
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 2.3.0
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 3.1.2
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: Helper package for own projects
63
+ test_files: []