sg_tiny_backup 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.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +51 -0
- data/.gitignore +14 -0
- data/.octocov.yml +15 -0
- data/.rspec +3 -0
- data/.rubocop.yml +70 -0
- data/.ruby-version +1 -0
- data/.simplecov +14 -0
- data/CHANGELOG.md +73 -0
- data/Gemfile +23 -0
- data/Gemfile.lock +175 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/database.ci.yml +15 -0
- data/lib/sg_tiny_backup/commands/aws_cli.rb +32 -0
- data/lib/sg_tiny_backup/commands/base.rb +23 -0
- data/lib/sg_tiny_backup/commands/gzip.rb +20 -0
- data/lib/sg_tiny_backup/commands/mysql_dump.rb +38 -0
- data/lib/sg_tiny_backup/commands/openssl.rb +40 -0
- data/lib/sg_tiny_backup/commands/pg_dump.rb +38 -0
- data/lib/sg_tiny_backup/commands/tar.rb +68 -0
- data/lib/sg_tiny_backup/commands.rb +8 -0
- data/lib/sg_tiny_backup/config.rb +71 -0
- data/lib/sg_tiny_backup/error.rb +25 -0
- data/lib/sg_tiny_backup/pipeline.rb +62 -0
- data/lib/sg_tiny_backup/pipeline_builders/base.rb +37 -0
- data/lib/sg_tiny_backup/pipeline_builders/db.rb +70 -0
- data/lib/sg_tiny_backup/pipeline_builders/log.rb +36 -0
- data/lib/sg_tiny_backup/pipeline_builders/s3.rb +26 -0
- data/lib/sg_tiny_backup/railtie.rb +9 -0
- data/lib/sg_tiny_backup/runner.rb +77 -0
- data/lib/sg_tiny_backup/spawner.rb +113 -0
- data/lib/sg_tiny_backup/templates/sg_tiny_backup.yml +43 -0
- data/lib/sg_tiny_backup/utils.rb +34 -0
- data/lib/sg_tiny_backup/version.rb +5 -0
- data/lib/sg_tiny_backup.rb +21 -0
- data/lib/tasks/sg_tiny_backup.rake +78 -0
- data/sg_tiny_backup.gemspec +38 -0
- metadata +87 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SgTinyBackup
|
|
4
|
+
module Commands
|
|
5
|
+
class Base
|
|
6
|
+
def command
|
|
7
|
+
raise NotImplementedError
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def env
|
|
11
|
+
{}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def success_codes
|
|
15
|
+
[0]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def warning_message
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module SgTinyBackup
|
|
6
|
+
module Commands
|
|
7
|
+
class Gzip < Base
|
|
8
|
+
def initialize(level: nil)
|
|
9
|
+
super()
|
|
10
|
+
@level = level
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def command
|
|
14
|
+
parts = ["gzip"]
|
|
15
|
+
parts << "-#{@level}" if @level
|
|
16
|
+
parts.join(" ")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module SgTinyBackup
|
|
6
|
+
module Commands
|
|
7
|
+
class MysqlDump < Base
|
|
8
|
+
attr_reader :user, :host, :port, :password, :database, :extra_options
|
|
9
|
+
|
|
10
|
+
def initialize(database:, host: nil, port: nil, user: nil, password: nil, extra_options: nil) # rubocop:disable Metrics/ParameterLists
|
|
11
|
+
super()
|
|
12
|
+
@user = user
|
|
13
|
+
@host = host
|
|
14
|
+
@port = port
|
|
15
|
+
@password = password
|
|
16
|
+
@database = database
|
|
17
|
+
@extra_options = extra_options
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def command
|
|
21
|
+
parts = []
|
|
22
|
+
parts << "mysqldump"
|
|
23
|
+
parts << extra_options if extra_options
|
|
24
|
+
parts << "--user=#{Shellwords.escape(@user)}" if @user
|
|
25
|
+
parts << "--host=#{@host}" if @host
|
|
26
|
+
parts << "--port=#{@port}" if @port
|
|
27
|
+
parts << @database
|
|
28
|
+
parts.join(" ")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def env
|
|
32
|
+
{
|
|
33
|
+
"MYSQL_PWD" => @password,
|
|
34
|
+
}.compact
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module SgTinyBackup
|
|
6
|
+
module Commands
|
|
7
|
+
class Openssl < Base
|
|
8
|
+
CIPHER = "aes-256-cbc"
|
|
9
|
+
ITER = 10_000
|
|
10
|
+
|
|
11
|
+
def initialize(password:, filename: nil)
|
|
12
|
+
super()
|
|
13
|
+
@password = password
|
|
14
|
+
@filename = filename
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def command
|
|
18
|
+
parts = ["openssl enc -#{CIPHER} -pbkdf2 -iter #{ITER}"]
|
|
19
|
+
parts << "-pass env:SG_TINY_BACKUP_ENCRYPTION_KEY"
|
|
20
|
+
parts << "-out #{@filename}" if @filename
|
|
21
|
+
parts.join(" ")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def env
|
|
25
|
+
{
|
|
26
|
+
"SG_TINY_BACKUP_ENCRYPTION_KEY" => @password,
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
def decryption_command
|
|
32
|
+
parts = ["openssl enc -d -#{CIPHER} -pbkdf2 -iter #{ITER}"]
|
|
33
|
+
parts << "-pass pass:ENCRYPTION_KEY"
|
|
34
|
+
parts << "-in INPUTFILE -out OUTPUTFILE"
|
|
35
|
+
parts.join(" ")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module SgTinyBackup
|
|
6
|
+
module Commands
|
|
7
|
+
class PgDump < Base
|
|
8
|
+
attr_reader :user, :host, :port, :password, :database, :extra_options
|
|
9
|
+
|
|
10
|
+
def initialize(database:, host: nil, port: nil, user: nil, password: nil, extra_options: nil) # rubocop:disable Metrics/ParameterLists
|
|
11
|
+
super()
|
|
12
|
+
@user = user
|
|
13
|
+
@host = host
|
|
14
|
+
@port = port
|
|
15
|
+
@password = password
|
|
16
|
+
@database = database
|
|
17
|
+
@extra_options = extra_options
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def command
|
|
21
|
+
parts = []
|
|
22
|
+
parts << "pg_dump"
|
|
23
|
+
parts << extra_options if extra_options
|
|
24
|
+
parts << "--username=#{Shellwords.escape(@user)}" if @user
|
|
25
|
+
parts << "--host=#{@host}" if @host
|
|
26
|
+
parts << "--port=#{@port}" if @port
|
|
27
|
+
parts << @database
|
|
28
|
+
parts.join(" ")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def env
|
|
32
|
+
{
|
|
33
|
+
"PGPASSWORD" => @password,
|
|
34
|
+
}.compact
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shellwords"
|
|
4
|
+
require "open3"
|
|
5
|
+
require_relative "base"
|
|
6
|
+
|
|
7
|
+
module SgTinyBackup
|
|
8
|
+
module Commands
|
|
9
|
+
class Tar < Base
|
|
10
|
+
def initialize(paths: [], optional_paths: [])
|
|
11
|
+
super()
|
|
12
|
+
@paths = paths
|
|
13
|
+
@optional_paths = optional_paths
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def command
|
|
17
|
+
if target_file_paths.empty?
|
|
18
|
+
# Create empty tar archive.
|
|
19
|
+
"tar -c -T /dev/null"
|
|
20
|
+
else
|
|
21
|
+
cmd = ["tar -c"]
|
|
22
|
+
target_file_paths.map do |path|
|
|
23
|
+
cmd << Shellwords.escape(path)
|
|
24
|
+
end
|
|
25
|
+
cmd.join(" ")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def success_codes
|
|
30
|
+
if self.class.gnu_tar?
|
|
31
|
+
# GNU tar's exit code 1 means that some files were changed while being archived.
|
|
32
|
+
# See https://www.gnu.org/software/tar/manual/html_section/Synopsis.html
|
|
33
|
+
[0, 1]
|
|
34
|
+
else
|
|
35
|
+
[0]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def warning_message
|
|
40
|
+
"tar: missing files: #{missing_optinal_file_paths.join(", ")}" unless missing_optinal_file_paths.empty?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def existing_optional_file_paths
|
|
46
|
+
@existing_optional_file_paths ||= @optional_paths.select { |path| File.file?(path) || File.directory?(path) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def missing_optinal_file_paths
|
|
50
|
+
@missing_optinal_file_paths ||= @optional_paths - existing_optional_file_paths
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def target_file_paths
|
|
54
|
+
@target_file_paths ||= @paths + existing_optional_file_paths
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class << self
|
|
58
|
+
def gnu_tar?
|
|
59
|
+
unless defined?(@gnu_tar)
|
|
60
|
+
out, _err, status = Open3.capture3("tar --version")
|
|
61
|
+
@gnu_tar = status.success? && out.match?(/GNU/)
|
|
62
|
+
end
|
|
63
|
+
@gnu_tar
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sg_tiny_backup/commands/aws_cli"
|
|
4
|
+
require "sg_tiny_backup/commands/gzip"
|
|
5
|
+
require "sg_tiny_backup/commands/mysql_dump"
|
|
6
|
+
require "sg_tiny_backup/commands/openssl"
|
|
7
|
+
require "sg_tiny_backup/commands/pg_dump"
|
|
8
|
+
require "sg_tiny_backup/commands/tar"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SgTinyBackup
|
|
4
|
+
class Config
|
|
5
|
+
KEY_PG_DUMP = "pg_dump"
|
|
6
|
+
KEY_MYSQLDUMP = "mysqldump"
|
|
7
|
+
KEY_S3 = "s3"
|
|
8
|
+
KEY_DB = "db"
|
|
9
|
+
KEY_ENCRYPTION_KEY = "encryption_key"
|
|
10
|
+
KEY_EXPECTED_UPLOAD_SIZE = "expected_upload_size"
|
|
11
|
+
KEY_LOG = "log"
|
|
12
|
+
KEY_FILES = "files"
|
|
13
|
+
KEY_OPTIONAL_FILES = "optional_files"
|
|
14
|
+
KEY_GZIP = "gzip"
|
|
15
|
+
|
|
16
|
+
attr_reader :s3, :encryption_key, :pg_dump, :mysqldump, :db, :gzip
|
|
17
|
+
|
|
18
|
+
def initialize(s3:, encryption_key:, pg_dump: nil, mysqldump: nil, db: nil, log: nil, gzip: nil) # rubocop:disable Metrics/ParameterLists
|
|
19
|
+
@s3 = s3
|
|
20
|
+
@encryption_key = encryption_key
|
|
21
|
+
@pg_dump = pg_dump || {}
|
|
22
|
+
@mysqldump = mysqldump
|
|
23
|
+
@db = db || self.class.rails_db_config
|
|
24
|
+
@log = log || {}
|
|
25
|
+
@gzip = gzip || {}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def log_file_paths
|
|
29
|
+
@log[KEY_FILES] || []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def optional_log_file_paths
|
|
33
|
+
@log[KEY_OPTIONAL_FILES] || []
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class << self
|
|
37
|
+
def read(io)
|
|
38
|
+
yaml = Utils.load_yaml_with_erb(io)
|
|
39
|
+
Config.new(
|
|
40
|
+
s3: yaml[KEY_S3],
|
|
41
|
+
encryption_key: yaml[KEY_ENCRYPTION_KEY],
|
|
42
|
+
db: yaml[KEY_DB],
|
|
43
|
+
pg_dump: yaml[KEY_PG_DUMP],
|
|
44
|
+
mysqldump: yaml[KEY_MYSQLDUMP],
|
|
45
|
+
log: yaml[KEY_LOG],
|
|
46
|
+
gzip: yaml[KEY_GZIP]
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def read_file(path)
|
|
51
|
+
File.open(path, "r") do |f|
|
|
52
|
+
read(f)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def generate_template_file(path)
|
|
57
|
+
template_path = File.expand_path("./templates/sg_tiny_backup.yml", __dir__)
|
|
58
|
+
FileUtils.cp(template_path, path)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def rails_db_config
|
|
62
|
+
return unless defined?(Rails)
|
|
63
|
+
|
|
64
|
+
db_config = File.open(Rails.root.join("config", "database.yml"), "r") do |f|
|
|
65
|
+
Utils.load_yaml_with_erb(f)
|
|
66
|
+
end
|
|
67
|
+
db_config[Rails.env]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SgTinyBackup
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class BackupFailed < Error
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class BackupWarning < Error
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class SpawnError < Error
|
|
14
|
+
def initialize(msg, inner_error)
|
|
15
|
+
inner_message = inner_error.message
|
|
16
|
+
inner_class_name = inner_error.class.name
|
|
17
|
+
|
|
18
|
+
message = msg.dup
|
|
19
|
+
message << "\n--- Inner error ---\n"
|
|
20
|
+
message << "#{inner_class_name}: " unless inner_message.start_with?(inner_class_name)
|
|
21
|
+
message << inner_message
|
|
22
|
+
super(message)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
require "sg_tiny_backup/spawner"
|
|
5
|
+
|
|
6
|
+
module SgTinyBackup
|
|
7
|
+
class Pipeline
|
|
8
|
+
extend Forwardable
|
|
9
|
+
|
|
10
|
+
delegate stdout: :@spawner
|
|
11
|
+
delegate stderr: :@spawner
|
|
12
|
+
delegate succeeded?: :@spawner
|
|
13
|
+
|
|
14
|
+
def initialize(output_path: nil)
|
|
15
|
+
@commands = []
|
|
16
|
+
@spawner = build_spawner
|
|
17
|
+
@output_path = output_path
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def <<(command)
|
|
21
|
+
@commands << command
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def run
|
|
26
|
+
@spawner = build_spawner
|
|
27
|
+
@spawner.spawn_and_wait
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def plain_commands
|
|
31
|
+
@commands.map(&:command)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def env
|
|
35
|
+
@commands.map(&:env).reduce({}, &:merge)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def failed?
|
|
39
|
+
!succeeded?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def error_message
|
|
43
|
+
[@spawner.stderr_message, @spawner.exit_code_error_message].join
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def warning_message
|
|
47
|
+
messages = [commands_warning_message]
|
|
48
|
+
messages << @spawner.stderr_message if succeeded? && !@spawner.stderr_message.empty?
|
|
49
|
+
messages.reject { |str| str.nil? || str.empty? }.join("\n")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def commands_warning_message
|
|
55
|
+
@commands.filter_map(&:warning_message).join("\n")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def build_spawner
|
|
59
|
+
Spawner.new(commands: @commands, env: env, output_path: @output_path)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "s3"
|
|
4
|
+
|
|
5
|
+
module SgTinyBackup
|
|
6
|
+
module PipelineBuilders
|
|
7
|
+
class Base
|
|
8
|
+
attr_reader :s3_config
|
|
9
|
+
|
|
10
|
+
def initialize(config:, basename:, local:, s3_config:, extension:)
|
|
11
|
+
@config = config
|
|
12
|
+
@basename = basename
|
|
13
|
+
@local = local
|
|
14
|
+
@s3_config = s3_config
|
|
15
|
+
@extension = extension
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def base_filename
|
|
19
|
+
"#{@basename}.#{@extension}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def destination_url
|
|
23
|
+
S3.build_s3_url(s3_config: s3_config, base_filename: base_filename)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def local?
|
|
27
|
+
@local
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def aws_cli_command
|
|
33
|
+
S3.build_aws_cli_command(s3_config: s3_config, destination_url: destination_url)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sg_tiny_backup/commands"
|
|
4
|
+
require "sg_tiny_backup/pipeline"
|
|
5
|
+
require_relative "base"
|
|
6
|
+
|
|
7
|
+
module SgTinyBackup
|
|
8
|
+
module PipelineBuilders
|
|
9
|
+
class Db < Base
|
|
10
|
+
def initialize(config:, basename:, local:)
|
|
11
|
+
super(
|
|
12
|
+
config: config,
|
|
13
|
+
basename: basename,
|
|
14
|
+
local: local,
|
|
15
|
+
s3_config: config.s3["db"],
|
|
16
|
+
extension: "sql.gz.enc"
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build
|
|
21
|
+
output_path = base_filename if local?
|
|
22
|
+
pl = Pipeline.new(output_path: output_path)
|
|
23
|
+
pl << db_dump_command
|
|
24
|
+
pl << Commands::Gzip.new(level: @config.gzip["level"])
|
|
25
|
+
pl << Commands::Openssl.new(password: @config.encryption_key)
|
|
26
|
+
pl << aws_cli_command unless local?
|
|
27
|
+
pl
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def db_dump_command
|
|
33
|
+
db_config = @config.db
|
|
34
|
+
adapter = db_config["adapter"]
|
|
35
|
+
raise "database adapter is not specified in your config." if adapter.nil?
|
|
36
|
+
|
|
37
|
+
case adapter
|
|
38
|
+
when "postgresql"
|
|
39
|
+
pg_dump_command(db_config)
|
|
40
|
+
when "mysql2"
|
|
41
|
+
mysql_dump_command(db_config)
|
|
42
|
+
else
|
|
43
|
+
raise "database adapter `#{adapter}` is not supported."
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def pg_dump_command(db_config)
|
|
48
|
+
Commands::PgDump.new(
|
|
49
|
+
database: db_config["database"],
|
|
50
|
+
host: db_config["host"],
|
|
51
|
+
port: db_config["port"],
|
|
52
|
+
user: db_config["username"] || db_config["user"],
|
|
53
|
+
password: db_config["password"],
|
|
54
|
+
extra_options: @config.pg_dump["extra_options"]
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def mysql_dump_command(db_config)
|
|
59
|
+
Commands::MysqlDump.new(
|
|
60
|
+
database: db_config["database"],
|
|
61
|
+
host: db_config["host"],
|
|
62
|
+
port: db_config["port"],
|
|
63
|
+
user: db_config["username"] || db_config["user"],
|
|
64
|
+
password: db_config["password"],
|
|
65
|
+
extra_options: @config.mysqldump["extra_options"]
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sg_tiny_backup/commands"
|
|
4
|
+
require "sg_tiny_backup/pipeline"
|
|
5
|
+
require_relative "base"
|
|
6
|
+
|
|
7
|
+
module SgTinyBackup
|
|
8
|
+
module PipelineBuilders
|
|
9
|
+
class Log < Base
|
|
10
|
+
def initialize(config:, basename:, local:)
|
|
11
|
+
super(
|
|
12
|
+
config: config,
|
|
13
|
+
basename: basename,
|
|
14
|
+
local: local,
|
|
15
|
+
s3_config: config.s3["log"],
|
|
16
|
+
extension: "tar.gz"
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build
|
|
21
|
+
output_path = base_filename if local?
|
|
22
|
+
pl = Pipeline.new(output_path: output_path)
|
|
23
|
+
pl << Commands::Tar.new(paths: @config.log_file_paths, optional_paths: @config.optional_log_file_paths)
|
|
24
|
+
pl << Commands::Gzip.new(level: @config.gzip["level"])
|
|
25
|
+
pl << aws_cli_command unless local?
|
|
26
|
+
pl
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def extension
|
|
32
|
+
"tar.gz"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sg_tiny_backup/commands/aws_cli"
|
|
4
|
+
|
|
5
|
+
module SgTinyBackup
|
|
6
|
+
module PipelineBuilders
|
|
7
|
+
module S3
|
|
8
|
+
class << self
|
|
9
|
+
def build_aws_cli_command(s3_config:, destination_url:)
|
|
10
|
+
expected_size = s3_config["expected_upload_size"].to_i if s3_config["expected_upload_size"]
|
|
11
|
+
Commands::AwsCli.new(
|
|
12
|
+
destination_url: destination_url,
|
|
13
|
+
access_key_id: s3_config["access_key_id"],
|
|
14
|
+
secret_access_key: s3_config["secret_access_key"],
|
|
15
|
+
expected_size: expected_size
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def build_s3_url(s3_config:, base_filename:)
|
|
20
|
+
object_path = [s3_config["prefix"], base_filename].join("_")
|
|
21
|
+
"s3://#{File.join(s3_config["bucket"], object_path)}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sg_tiny_backup/pipeline"
|
|
4
|
+
require "sg_tiny_backup/error"
|
|
5
|
+
require "sg_tiny_backup/pipeline_builders/db"
|
|
6
|
+
require "sg_tiny_backup/pipeline_builders/log"
|
|
7
|
+
|
|
8
|
+
module SgTinyBackup
|
|
9
|
+
class Runner
|
|
10
|
+
attr_reader :target
|
|
11
|
+
|
|
12
|
+
TARGET_DB = "db"
|
|
13
|
+
TARGET_LOG = "log"
|
|
14
|
+
|
|
15
|
+
def initialize(config:, basename:, target: "db", local: false, pipeline_builder: nil)
|
|
16
|
+
@config = config
|
|
17
|
+
@basename = basename
|
|
18
|
+
@target = target
|
|
19
|
+
@local = local
|
|
20
|
+
@pipeline_builder = pipeline_builder
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def run # rubocop:disable Naming/PredicateMethod
|
|
24
|
+
pipeline.run
|
|
25
|
+
handle_warning(pipeline.warning_message)
|
|
26
|
+
handle_error(pipeline.error_message) if pipeline.failed?
|
|
27
|
+
pipeline.succeeded?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def plain_commands
|
|
31
|
+
pipeline.plain_commands
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def piped_command
|
|
35
|
+
plain_commands.join(" | ")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def env
|
|
39
|
+
pipeline.env
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def s3_destination_url
|
|
43
|
+
pipeline_builder.destination_url
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def base_filename
|
|
47
|
+
pipeline_builder.base_filename
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def pipeline
|
|
53
|
+
@pipeline ||= pipeline_builder.build
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def pipeline_builder
|
|
57
|
+
@pipeline_builder ||=
|
|
58
|
+
case target
|
|
59
|
+
when TARGET_DB
|
|
60
|
+
SgTinyBackup::PipelineBuilders::Db.new(config: @config, basename: @basename, local: @local)
|
|
61
|
+
when TARGET_LOG
|
|
62
|
+
SgTinyBackup::PipelineBuilders::Log.new(config: @config, basename: @basename, local: @local)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def handle_warning(message)
|
|
67
|
+
return if message.empty?
|
|
68
|
+
|
|
69
|
+
SgTinyBackup.logger.warn message
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def handle_error(message)
|
|
73
|
+
SgTinyBackup.logger.error message
|
|
74
|
+
raise BackupFailed, message if SgTinyBackup.raise_on_error
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|