calevipoeg-zeus 0.1.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.
@@ -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: []