container-backup 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: 4de3792fee3087a90470ba80687e0e86856aad8b93b1d73bf3e25fd416b3e7ef
4
+ data.tar.gz: ba68d2b16662e8f42aa6db0e49bc2f7e2e51e05565e2ba74128ed298d9497a7a
5
+ SHA512:
6
+ metadata.gz: 1936a531cdd2026972ccbe517bc79570650e57938b799f17775e3efb3a0f0ed0bb4177290614aab8109b16e6b47b00daa7be56c4b9a2703410947d600dbd01a9
7
+ data.tar.gz: a63df0b83e5f50ef5d7e8285b00b9a52fb9e44b7839fc5751146fc4f7c3cbf28f29d10b987d43328423b2f46977e207809efcd1b3696ddbc601995f084d7bf4d
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ /.idea/
14
+ /.byebug_history
15
+ /backup/
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.0
6
+ before_install: gem install bundler -v 2.1.2
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at mpantel@aegean.gr. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in container-backup.gemspec
4
+ gemspec
5
+
@@ -0,0 +1,42 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ container-backup (0.1.0)
5
+ rake (~> 12.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ byebug (3.5.1)
11
+ columnize (~> 0.8)
12
+ debugger-linecache (~> 1.2)
13
+ slop (~> 3.6)
14
+ columnize (0.9.0)
15
+ debugger-linecache (1.2.0)
16
+ diff-lcs (1.3)
17
+ rake (12.3.3)
18
+ rspec (3.8.0)
19
+ rspec-core (~> 3.8.0)
20
+ rspec-expectations (~> 3.8.0)
21
+ rspec-mocks (~> 3.8.0)
22
+ rspec-core (3.8.2)
23
+ rspec-support (~> 3.8.0)
24
+ rspec-expectations (3.8.4)
25
+ diff-lcs (>= 1.2.0, < 2.0)
26
+ rspec-support (~> 3.8.0)
27
+ rspec-mocks (3.8.1)
28
+ diff-lcs (>= 1.2.0, < 2.0)
29
+ rspec-support (~> 3.8.0)
30
+ rspec-support (3.8.2)
31
+ slop (3.6.0)
32
+
33
+ PLATFORMS
34
+ ruby
35
+
36
+ DEPENDENCIES
37
+ byebug (~> 3.0)
38
+ container-backup!
39
+ rspec (~> 3.0)
40
+
41
+ BUNDLED WITH
42
+ 2.1.4
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Michail Pantelelis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,107 @@
1
+ # Container::Backup
2
+
3
+ *Caution:* **Early Prerelease**
4
+
5
+ ## Description
6
+
7
+ Uses docker-compose backup labels for each container to backup/restore specified assets
8
+ ```
9
+ Supported assets include:
10
+ - databases
11
+ - mssql
12
+ - mysql
13
+ - mongodb
14
+ - influxdb
15
+ - chronograf
16
+ - volumes
17
+ - directories
18
+ ```
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'container-backup'
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ $ bundle install
30
+
31
+ Or install it yourself as:
32
+
33
+ $ gem install container-backup
34
+
35
+ ## Usage
36
+
37
+ Usage: container-backup [options] container_names
38
+
39
+ Back up or restore container assets base on docker-compose label configuration
40
+
41
+ available backup types include:
42
+
43
+ * volumes
44
+ * databases
45
+ * mapped directories
46
+
47
+ ```
48
+ -f, --file=DOCKERFILE Docker file with backup/restore configuration
49
+ -b, --backup Backup
50
+ -d, --directory=BACKUP_DIRECTORY Backup
51
+ -r, --restore Restore
52
+ --review Review backup/restore actions
53
+ --details Review backup/restore actions with commands to be executed
54
+ -h, --help Prints this help
55
+ ```
56
+ ## Assumptions
57
+
58
+ Every container has a mapped directory where backups are externally stored:
59
+
60
+ ```YML
61
+ volumes:
62
+ - "mysql_data:/var/lib/mysql"
63
+ - "./backups:/root/backups"
64
+ - "./backup:/backup"
65
+ ```
66
+
67
+ Include the backup label in your docker compose files as follows:
68
+
69
+ ```YML
70
+ labels:
71
+ - "backup={volumes: [mongo_data],databases: [mongo: {user: ${MONGO_INITDB_ROOT_USERNAME}, password: ${MONGO_INITDB_ROOT_PASSWORD}}]}"
72
+ - "backup.1={volumes: [influxdb_data],databases: [influxdb: {user: ${INFLUXDB_ADMIN_USER},password: ${INFLUXDB_ADMIN_PASSWORD}}]}"
73
+ - "backup.chronograf={volumes: [chronograf_data],databases: [chronograf]}"
74
+ - "backup.2={directories: [/var/www/html/libraries, /var/www/html/modules, /var/www/html/profiles, /var/www/html/themes, /var/www/html/sites]}"
75
+ - "backup.drupal={volumes: [drupal_mysql_data],databases: [mysql: {db: ${MYSQL_DATABASE},password: ${MYSQL_ROOT_PASSWORD},user: root}]}"
76
+ ```
77
+
78
+ In the above example env variables are getting replaced during backup execution, as below:
79
+
80
+
81
+ {volumes: [mongo_data],databases: [mongo: {user: ${MONGO_INITDB_ROOT_USERNAME}, password: ${MONGO_INITDB_ROOT_PASSWORD}}]}
82
+
83
+ becomes
84
+
85
+ {volumes: [mongo_data],databases: [mongo: {user: 'username', password: 'password']}}]}
86
+
87
+ replacing contents of with actual value environmental value
88
+
89
+ ## Development
90
+
91
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
92
+
93
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
94
+
95
+ ## Contributing
96
+
97
+ Bug reports and pull requests are welcome on GitHub at https://github.com/mpantel/container-backup. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/container-backup/blob/master/CODE_OF_CONDUCT.md).
98
+
99
+ ## License
100
+
101
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
102
+
103
+ ## Code of Conduct
104
+
105
+ Everyone interacting in the Container::Backup project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/container-backup/blob/master/CODE_OF_CONDUCT.md).
106
+
107
+
@@ -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 "container/backup"
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,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,41 @@
1
+ require_relative 'lib/container/backup/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "container-backup"
5
+ spec.version = Container::Backup::VERSION
6
+ spec.authors = ["Michail Pantelelis"]
7
+ spec.email = ["mpantel@aegean.gr"]
8
+
9
+ spec.summary = %q{Uses docker compose backup lable for each container to drive backup and restore}
10
+ spec.description = %q{labels:
11
+ - "backup={volumes: [mongo_data],databases: [mongo: {user: ${MONGO_INITDB_ROOT_USERNAME}, password: ${MONGO_INITDB_ROOT_PASSWORD}}]}"
12
+ - "backup={volumes: [influxdb_data],databases: [influxdb: {user: ${INFLUXDB_ADMIN_USER},password: ${INFLUXDB_ADMIN_PASSWORD}}]}"
13
+ - "backup={volumes: [chronograf_data],databases: [chronograf]}"
14
+ - "backup={directories: [/var/www/html/libraries, /var/www/html/modules, /var/www/html/profiles, /var/www/html/themes, /var/www/html/sites]}"
15
+ - "backup={volumes: [drupal_mysql_data],databases: [mysql: {db: ${MYSQL_DATABASE},password: ${MYSQL_ROOT_PASSWORD},user: root}]}"
16
+ }
17
+ spec.homepage = "https://github.com/mpantel/container-backup"
18
+ spec.license = "MIT"
19
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
20
+
21
+ #spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
22
+
23
+ spec.metadata["homepage_uri"] = spec.homepage
24
+ spec.metadata["source_code_uri"] = "https://github.com/mpantel/container-backup"
25
+ spec.metadata["changelog_uri"] = "https://github.com/mpantel/container-backup"
26
+
27
+ # Specify which files should be added to the gem when it is released.
28
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
29
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
30
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
31
+ end
32
+ spec.bindir = "exe"
33
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
+ spec.require_paths = ["lib"]
35
+
36
+ spec.add_runtime_dependency 'rake', '~> 12.0'
37
+
38
+ spec.add_development_dependency 'rspec', '~> 3.0'
39
+ spec.add_development_dependency 'byebug', '~> 3.0'
40
+
41
+ end
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "container/backup"
@@ -0,0 +1,41 @@
1
+ require "container/docker_compose"
2
+ require "container/parameter"
3
+ require "container/step_factory"
4
+ require "container/step"
5
+ require "container/steps/directories"
6
+ require "container/steps/volumes"
7
+ require "container/steps/databases"
8
+ require 'byebug' rescue nil
9
+
10
+ module Container
11
+ module Backup
12
+ class Action
13
+ def self.perform
14
+ parameters = Parameter.parse(ARGV)
15
+ pp parameters if parameters[:review]
16
+ DockerCompose.file = parameters[:filename]
17
+ backups = DockerCompose.instance.with_label
18
+ pp backups if parameters[:review]
19
+ parameters[:containers] = backups.keys if parameters[:containers].size == 0
20
+ containers = parameters[:containers] - ( parameters[:containers]- backups.keys.map(&:keys).flatten)
21
+ backups.keys.select{|k| containers.include?(k.keys.first)}.each do |container_info|
22
+ backup = :details if parameters[:details]
23
+ backup ||= parameters[:backup] && !parameters[:restore]
24
+ Action.new(container_info,parameters[:directory],backup,backups[container_info]).perform
25
+ end
26
+ rescue Errno::ENOENT => e
27
+ puts "File not found: #{e.message}"
28
+ end
29
+
30
+ def perform
31
+ @steps.each(&:perform)
32
+ end
33
+ def initialize(container_info,directory,backup,actions)
34
+ @container_info = container_info
35
+ @directory = directory
36
+ @backup = backup
37
+ @steps = StepFactory.generate(@container_info, @directory, @backup,actions)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,11 @@
1
+ require "container/backup/version"
2
+ require "container/action"
3
+
4
+ module Container
5
+ module Backup
6
+ class Error < StandardError; end
7
+
8
+ Action.perform
9
+
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module Container
2
+ module Backup
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,45 @@
1
+ require 'yaml'
2
+ require 'singleton'
3
+ # http://www.albertoalmagro.com/en/ruby-methods-defined-in-rake-tasks/
4
+ module Container
5
+ class DockerCompose
6
+ include Singleton
7
+ @@file = ""
8
+ def self.file= composer_file
9
+ @@file = composer_file
10
+ end
11
+ def initialize
12
+ raise "Container::DockerCompose.file not initialized" if @@file.size == 0
13
+ @file = @@file
14
+ @config = load_config(@file)
15
+ end
16
+
17
+ def load_config(file)
18
+ YAML.load_file(file)
19
+ end
20
+
21
+ def self.docker_compose
22
+ "docker-compose -f #{@@file}"
23
+ end
24
+ def services
25
+ @services ||= @config['services']
26
+ end
27
+
28
+ def parse_env(string = nil)
29
+ #addapted from https://www.shakacode.com/blog/masking-pii-with-ruby-gsub-with-regular-expression-named-match-groups/
30
+ # and https://cmsdk.com/javascript/regex-to-match-string-with-contains-closed-brackets.html
31
+ # replace { ${XXX} ${YYYY}} => { ENV['XXX'] ENV['YYYY'] }
32
+ string.gsub(/(?<match>\$\{(?<variable>\g<match>|[^${}]++)*\})/) { |match| ENV[$~[:variable]] } if string
33
+ end
34
+
35
+ def with_label(label_key: 'backup')
36
+ services.inject({}) do |h, (k, v)|
37
+ image = parse_env(v.dig('image'))
38
+ values = (v.dig('labels')&.
39
+ map { |l| l.split('=') }&.
40
+ select { |l| l.first.split('.').first == label_key.to_s })
41
+ (values&.size || 0) > 0 ? h.merge({{k => image} => values.map{|value| YAML.load(parse_env(value.last))}}) : h
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,57 @@
1
+ require 'optparse'
2
+
3
+ class Parameter
4
+ def self.parse(options)
5
+ args = {}
6
+
7
+ opt_parser = OptionParser.new do |opts|
8
+
9
+ opts.banner = <<BANNER
10
+
11
+ Usage: #{File.basename($0)} [options] container_names
12
+
13
+ Back up or restore container assets base on docker-compose label configuration
14
+ Handles:
15
+ * volumes
16
+ * databases
17
+ * mapped directories
18
+
19
+ BANNER
20
+
21
+ args[:filename] = 'docker-compose.yml'
22
+ opts.on("-f", "--file=DOCKERFILE", "Docker file with backup/restore configuration") do |n|
23
+ args[:filename] = n
24
+ end
25
+ args[:backup] = true
26
+ opts.on("-b", "--backup", "Backup") do |n|
27
+ args[:backup] = n
28
+ end
29
+ args[:directory] = "backup/backup_#{Time.now.strftime('%Y%m%dT%H%M%S')}"
30
+ opts.on("-d", "--directory=BACKUP_DIRECTORY", "Backup") do |n|
31
+ args[:directory] = n
32
+ end
33
+ opts.on("-r", "--restore", "Restore") do |n|
34
+ args[:restore] = n
35
+ end
36
+ opts.on("--review", "Review backup/restore actions") do |n|
37
+ args[:review] = n
38
+ end
39
+ opts.on("--details", "Review backup/restore actions with commands to be executed") do |n|
40
+ args[:details] = n
41
+ end
42
+
43
+ opts.on("-h", "--help", "Prints this help") do
44
+ puts opts
45
+ exit
46
+ end
47
+ end
48
+
49
+ opt_parser.parse!(options)
50
+ return args.merge({containers: ARGV})
51
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
52
+
53
+ puts ["\nError:\n------\n", e.message, "\n------\n"]
54
+ opt_parser.parse!(['-h'])
55
+
56
+ end
57
+ end
@@ -0,0 +1,48 @@
1
+ require 'rake'
2
+ require 'rake/file_utils_ext'
3
+
4
+ module Container
5
+ module Backup
6
+ class Step
7
+ include Rake::FileUtilsExt
8
+
9
+ def initialize(container_info, directory, backup, params_hash)
10
+ @container_info = container_info
11
+ @directory = directory
12
+ @backup = backup
13
+ @params = params_hash
14
+ end
15
+
16
+ def params
17
+ @params
18
+ end
19
+
20
+ def container
21
+ @container_info.keys.first
22
+ end
23
+
24
+ def image
25
+ @container_info.values.first
26
+ end
27
+
28
+ def backup_path
29
+ [@directory, container, self.class.name.split('::').last.downcase].join('/')
30
+ end
31
+
32
+ def perform
33
+ @backup ? backup : restore
34
+ end
35
+
36
+ def backup
37
+ puts "Backup path: #{backup_path}"
38
+ puts "Backup: #{self.class} container: #{container} params: #{@params}"
39
+ end
40
+
41
+ def restore
42
+ puts "Backup path: #{backup_path}"
43
+ puts "Restore: #{self.class} container: #{container} params: #{@params}"
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,37 @@
1
+ module Container
2
+ module Backup
3
+ class StepFactory
4
+ def self.get_class(type)
5
+ klass = Object.const_get(['Container', 'Backup', type.capitalize].join('::'))
6
+ if klass.ancestors.include? Container::Backup::Step
7
+ klass
8
+ else
9
+ raise 'Unknown step for #{klass}'
10
+ end
11
+ end
12
+
13
+ def self.generate(container, directory, backup, actions)
14
+ actions.map do |a|
15
+ a.map do |type, steps|
16
+ steps.map do |param|
17
+ if Object.const_get(['Container', 'Backup', type.capitalize].join('::')).superclass == Container::Backup::StepFactory
18
+ (param.is_a?(String) ? {param => {}} : param).map do |type, param|
19
+ StepFactory.build(container, directory, backup, type, param)
20
+ end
21
+ else
22
+ StepFactory.build(container, directory, backup, type, param)
23
+ end
24
+ end
25
+ end
26
+ end.flatten
27
+ end
28
+
29
+ def self.build(container, directory, backup, type, params)
30
+ StepFactory.get_class(type).new(container, directory, backup, params)
31
+ end
32
+
33
+ end
34
+
35
+ class Databases < StepFactory ; end
36
+ end
37
+ end
@@ -0,0 +1,124 @@
1
+ module Container
2
+ module Backup
3
+ class Mysql < Directories
4
+
5
+ def backup
6
+ # eval "ssh ru-dm.aegean.gr 'mysqldump --force --routines -h localhost -u root -p$mysql_root_password ele > ele_dev.sql'"
7
+ #eval "ssh ru-vm2.aegean.gr 'scp ru-dm.ru.aegean.gr:~/ele_dev.sql /tmp/ele_dev.sql'"
8
+ mkdir_p(backup_path)
9
+ sh "docker exec #{container} sh -c 'mysqldump --force --routines -h localhost -u root -p#{params['password']} #{params['db']} > /#{backup_path}/#{params['db']}.sql'"
10
+ end
11
+ def restore
12
+ # user == db
13
+ sh "docker exec #{container} sh -c 'echo \"DROP USER \'#{params['db']}\'@'%\' ;DROP DATABASE #{params['db']};\" | mysql -u root -p#{params['password']}'"
14
+ sh "docker exec #{container} sh -c 'echo \"CREATE DATABASE #{params['db']};CREATE USER \'#{params['user']}\'@\'%\' IDENTIFIED BY \'#{params['password']}\';GRANT ALL PRIVILEGES ON *.* TO \'#{params['user']}\'@\'%\';FLUSH PRIVILEGES;\" | mysql -u root -p#{params['password']}'"
15
+ sh "docker exec #{container} sh -c 'mysql -u root -p#{params['password']} #{params['db']} < /#{backup_path}/#{params['db']}.sql'"
16
+ end
17
+ end
18
+
19
+ class Mssql < Step
20
+ # - "backup={volumes: [drupal_mysql_data],databases: [mysql: {db: ${MYSQL_DATABASE},password: ${MYSQL_ROOT_PASSWORD},user: root}]}"
21
+ # - "backup.1={databases: [mysql: {db: ${MYSQL_DATABASE}2,password: ${MYSQL_ROOT_PASSWORD},user: root}]}"
22
+
23
+ def backup
24
+ raise "not yet implemented #{self.class.name} backup step"
25
+ # docker exec -it mssql /opt/mssql-tools/bin/sqlcmd -S ru-db.aegean.gr -U sa -P $mssql_root_password -d master -i /root/mssql/backup_mssql_dbs.sql
26
+ #echo 'Press any key to copy backups to staging...'; #read -s -n1
27
+ #docker exec -it mssql /opt/mssql-tools/bin/sqlcmd -S ru-db.aegean.gr -U sa -P $mssql_root_password -d master -i /root/mssql/copy_mssql_dbs_to_staging.sql
28
+
29
+ end
30
+ def restore
31
+ raise "not yet implemented #{self.class.name} backup step"
32
+ #stop
33
+ #sh "docker exec -it mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P %SA_PASSWORD% -d master -i /root/mssql/init.sql"
34
+ #docker exec -it mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P %SA_PASSWORD% -d master -i /root/mssql/map_logins.sql
35
+ #start
36
+ end
37
+
38
+ end
39
+
40
+ class Mongo < Step
41
+ # https://docs.mongodb.com/database-tools/mongodump/#bin.mongodump
42
+ # https://docs.mongodb.com/database-tools/mongorestore/#bin.mongorestore
43
+ #
44
+ # - "backup={volumes: [mongo_data],databases: [mongo: {user: ${MONGO_INITDB_ROOT_USERNAME}, password: ${MONGO_INITDB_ROOT_PASSWORD}}]}"
45
+ #
46
+ def backup
47
+ # mongodump --host="mongodb0.example.com" --port=27017 [additional options]
48
+ stop
49
+ sh "docker run -it --rm --volumes-from #{container} #{image} bash -c 'mongodump -v --host=#{params['host'] || 'localhost'} --port=#{params['port'] || 27017} --out=/#{backup_path}'"
50
+ start
51
+ end
52
+ def restore
53
+ # mongorestore --username joe --password secret1 --host=mongodb0.example.com --port=27017
54
+ # docker run -it --rm --link mongo:mongo -v /tmp/mongodump:/tmp mongo bash -c 'mongorestore -v --host $MONGO_PORT_27017_TCP_ADDR:$MONGO_PORT_27017_TCP_PORT /tmp'
55
+ stop
56
+ sh "docker run -it --rm --volumes-from #{container} #{image} bash -c 'mongorestore -v --host=#{params['host'] || 'localhost'} --port=#{params['port'] || 27017} /#{backup_path}'"
57
+ start
58
+ end
59
+ end
60
+
61
+ class Chronograf < Step
62
+ # https://www.influxdata.com/blog/chronograf-dashboard-definitions/
63
+ #
64
+ # - "backup={volumes: [chronograf_data],databases: [chronograf]}"
65
+ #
66
+ def get_dashboard_ids(save=true)
67
+ sh "docker exec #{container} sh -c 'curl -i X GET http://127.0.0.1:8888/chronograf/v1/dashboards > /#{backup_path}/dashboards.json'" if save
68
+ JSON.parse(File.open("/#{backup_path}/dashboards.json"))['dashboards'].map{|d| d['id']}
69
+ end
70
+
71
+ def backup
72
+ get_dashboard_ids(false).each do |i|
73
+ sh "docker exec #{container} sh -c 'curl -i -X GET http://127.0.0.1:8888/chronograf/v1/dashboards/#{i} > /#{backup_path}/i.json'"
74
+ end
75
+ end
76
+
77
+ def restore
78
+ get_dashboard_ids.each do |i|
79
+ sh "docker exec #{container} sh -c 'curl -i -X POST -H \"Content-Type: application/json\" http://127.0.0.1:8888/chronograf/v1/dashboards -d @/#{i}.json'"
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+ class Influxdb < Step
86
+ # https://www.influxdata.com/blog/new-features-in-open-source-backup-and-restore/
87
+ #
88
+ # - "backup={volumes: [influxdb_data],databases: [influxdb: {user: ${INFLUXDB_ADMIN_USER},password: ${INFLUXDB_ADMIN_PASSWORD}}]}"
89
+ #
90
+ #
91
+ def backup
92
+ # influxd backup -portable [options] <path-to-backup>
93
+ #
94
+ # Backup Options
95
+ #
96
+ # -host <host:port> – The host to connect to and perform a snapshot of. Defaults to 127.0.0.1:8088.
97
+ # -database <name> – The database to backup. Optional. If not given, all databases are backed up.
98
+ # -retention <name> – The retention policy to backup. Optional.
99
+ # -shard <id> – The shard id to backup. Optional. If specified, -retention is required.
100
+ # -since <2015-12-24T08:12:13Z> – Do a file-level backup since the given time. The time needs to be in the RFC3339 format. Optional.
101
+ # -start <2015-12-24T08:12:23Z> – All points earlier than this timestamp will be excluded from the export. Not compatible with -since.
102
+ # -end <2015-12-24T08:12:23Z> – All points later than this time stamp will be excluded from the export. Not compatible with -since.
103
+ # -portable – Generate backup files in the format used for InfluxDB Enterprise.
104
+
105
+ sh "docker exec #{container} sh -c 'influxd backup -protable /#{backup_path}'"
106
+
107
+ end
108
+ def restore
109
+ # influxd restore -portable [options] <path-to-backup>
110
+ # Regardless of whether you have existing backup automation that supports the legacy format, or you are a new user, you may wish to test the new online feature for legacy to gain the advantages described above. It is activated by using either the -portable or -online flags. The flags indicate that the input is in either the new portable backup format (which is the same format that Enterprise InfluxDB uses), or the legacy backup format, respectively. It has the following options:
111
+ # -host <host:port> – The host to connect to and perform a snapshot of. Defaults to 127.0.0.1:8088.
112
+ # -db <name> – Identifies the database from the backup that will be restored.
113
+ # -newdb <name> – The name of the database into which the archived data will be imported on the target system. If not given, then the value of -db is used. The new database name must be unique to the target system.
114
+ # -rp <name> – Identifies the retention policy from the backup that will be restored. Requires that -db is set.
115
+ # -newrp <name> – The name of the retention policy that will be created on the target system. Requires that -rp is set. If not given, the value of -rp is used.
116
+ # -shard <id> – Optional. If given, -db and -rp are required. Will restore the single shard’s data.
117
+
118
+ sh "docker exec #{container} sh -c 'influxd restore -protable /#{backup_path}'"
119
+
120
+ end
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,48 @@
1
+ module Container
2
+ module Backup
3
+ class Directories < Step
4
+ # - "backup={directories: [/var/www/html/libraries, /var/www/html/modules, /var/www/html/profiles, /var/www/html/themes, /var/www/html/sites]}"
5
+ def stop
6
+ sh "#{DockerCompose.docker_compose} stop #{container}"
7
+ end
8
+
9
+ def start
10
+ sh "#{DockerCompose.docker_compose} up -d #{container}"
11
+ end
12
+
13
+ def tar_volume(option)
14
+ raise "Invalid tar option #{option}" unless option =~ /\A[cx]\z/
15
+ sh "docker run --rm --volumes-from #{container} -v #{backup_path}:/backup ubuntu bash -c \"cd #{volume} && tar #{option}vf /backup/#{volume}.tar #{option == 'c' ? ' .' : ''}\""
16
+ end
17
+ def backup
18
+ stop
19
+ mkdir_p(backup_path)
20
+ backup_volume
21
+ start
22
+ end
23
+
24
+ def restore
25
+ stop
26
+ remove_volume
27
+ recover_volume
28
+ start
29
+ end
30
+ def backup_volume
31
+ tar_volume('c')
32
+ end
33
+
34
+ def recover_volume
35
+ tar_volume('x')
36
+ end
37
+
38
+ def remove_volume
39
+ puts "Remove all files from #{volume} (y/n)?"
40
+ if gets.chomp == 'y'
41
+ sh "docker run --rm --volumes-from #{container} ubuntu bash -c \"rm -rf #{volume}\""
42
+ else
43
+ exit
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,47 @@
1
+ module Container
2
+ module Backup
3
+ class Volumes < Directories
4
+ # - "backup={volumes: [influxdb_data],databases: [influxdb: {user: ${INFLUXDB_ADMIN_USER},password: ${INFLUXDB_ADMIN_PASSWORD}}]}"
5
+ def remove_container
6
+ puts "Remove container #{container} (y/n)?"
7
+ if gets.chomp == 'y'
8
+ sh "docker rm -f #{container}"
9
+ else
10
+ exit
11
+ end
12
+ end
13
+
14
+ def volume
15
+ @params
16
+ end
17
+
18
+ def remove_volume
19
+ puts "Remove volume #{volume} (y/n)?"
20
+ if gets.chomp == 'y'
21
+ sh "docker volume rm #{volume}"
22
+ else
23
+ exit
24
+ end
25
+ end
26
+
27
+ def create_volume
28
+ sh "docker volume create #{volume}"
29
+ end
30
+
31
+ # https://blog.ssdnodes.com/blog/docker-backup-volumes/
32
+
33
+ def tar_volume(option)
34
+ raise "Invalid tar option #{option}" unless option =~ /\A[cx]\z/
35
+ sh "docker run --rm -v #{volume}:/temp -v #{backup_path}:/backup ubuntu bash -c \"cd /temp && tar #{option}vf /backup/#{volume}.tar #{option == 'c' ? ' .' : ''}\""
36
+ end
37
+
38
+ def restore
39
+ stop
40
+ remove_container
41
+ remove_volume
42
+ recover_volume
43
+ start
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,4 @@
1
+ PRINT "Backup #{db_name} db"
2
+ go
3
+ BACKUP DATABASE #{db_name} TO DISK = 'G:\MSSQL\Backup\#{db_name}.bak' WITH FORMAT,COMPRESSION;
4
+ go
@@ -0,0 +1,28 @@
1
+ --RESTORE FILELISTONLY FROM DISK = 'G:\MSSQL\Backup\#{db_name}.bak' ;
2
+ --"BACKUP LOG [demodb] TO DISK = N'var/opt/mssql/data/demodb_LogBackup_2016-11-14_18-09-53.bak' WITH NOFORMAT, NOINIT, NAME = N'demodb_LogBackup_2016-11-14_18-09-53', NOSKIP, NOREWIND, NOUNLOAD, NORECOVERY , STATS = 5"
3
+ -- for #{db_name}Dev
4
+
5
+
6
+ -- differential
7
+ -- docker exec -it mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SA_PASSWORD -d master
8
+ -- RESTORE DATABASE [#{db_name}Dev] FROM DISK = N'/root/backups/1.bak' WITH FILE = 1, NORECOVERY, REPLACE;
9
+ -- RESTORE DATABASE [#{db_name}Dev] FROM DISK = N'/root/backups/2.bak' WITH FILE = 1, RECOVERY;
10
+
11
+ IF ( EXISTs (SELECT name FROM master.dbo.sysdatabases WHERE name = '$(DB_NAME)2Dev'))
12
+ drop DATABASE $(DB_NAME)2Dev
13
+ create DATABASE $(DB_NAME)2Dev
14
+
15
+ ALTER DATABASE $(DB_NAME)2Dev
16
+ SET SINGLE_USER WITH ROLLBACK AFTER 5 --this will give your current connections 60 seconds to complete
17
+ go
18
+ RESTORE DATABASE [$(DB_NAME)2Dev] FROM DISK = N'/root/backups/$(DB_NAME)2Dev.bak' WITH FILE = 1, NOUNLOAD, REPLACE, STATS = 5;
19
+ GO
20
+ ALTER DATABASE $(DB_NAME)2Dev SET MULTI_USER
21
+ GO
22
+ USE master ;
23
+ ALTER DATABASE $(DB_NAME)2Dev SET RECOVERY SIMPLE;
24
+ GO
25
+ DBCC SHRINKDATABASE ($(DB_NAME)2Dev, TRUNCATEONLY);
26
+ --GO
27
+ --DBCC SHRINKDATABASE ($(DB_NAME)2Dev, 10);
28
+ GO
@@ -0,0 +1,13 @@
1
+
2
+ use $(DB_NAME)2Dev
3
+ go
4
+ IF NOT EXISTS (SELECT name FROM master.sys.server_principals WHERE name = '#{user_name}')
5
+ create login #{user_name} with password ='$(SA_PASSWORD)',DEFAULT_database = [$(DB_NAME)2Dev]
6
+
7
+ SET QUOTED_IDENTIFIER ON
8
+ go
9
+
10
+ EXEC sp_change_users_login 'Auto_Fix', '#{username}'
11
+
12
+
13
+ go
@@ -0,0 +1,19 @@
1
+
2
+ use master;
3
+ go
4
+ print "Restore dev db"
5
+ go
6
+ IF ( EXISTs (SELECT name FROM master.dbo.sysdatabases WHERE name = '$(DB_NAME)2Dev'))
7
+ drop DATABASE $(DB_NAME)2Dev
8
+ go
9
+ create DATABASE $(DB_NAME)2Dev
10
+ go
11
+ ALTER DATABASE $(DB_NAME)2Dev
12
+ SET SINGLE_USER WITH
13
+ ROLLBACK AFTER 5 --this will give your current connections 60 seconds to complete
14
+ go
15
+ RESTORE DATABASE $(DB_NAME)2Dev FROM DISK = '/root/backups/$(DB_NAME)2Dev.bak'
16
+ WITH REPLACE;
17
+ GO
18
+ ALTER DATABASE $(DB_NAME)2Dev SET MULTI_USER
19
+ GO
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: container-backup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michail Pantelelis
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-10-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '12.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '12.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: |
56
+ labels:
57
+ - "backup={volumes: [mongo_data],databases: [mongo: {user: ${MONGO_INITDB_ROOT_USERNAME}, password: ${MONGO_INITDB_ROOT_PASSWORD}}]}"
58
+ - "backup={volumes: [influxdb_data],databases: [influxdb: {user: ${INFLUXDB_ADMIN_USER},password: ${INFLUXDB_ADMIN_PASSWORD}}]}"
59
+ - "backup={volumes: [chronograf_data],databases: [chronograf]}"
60
+ - "backup={directories: [/var/www/html/libraries, /var/www/html/modules, /var/www/html/profiles, /var/www/html/themes, /var/www/html/sites]}"
61
+ - "backup={volumes: [drupal_mysql_data],databases: [mysql: {db: ${MYSQL_DATABASE},password: ${MYSQL_ROOT_PASSWORD},user: root}]}"
62
+ email:
63
+ - mpantel@aegean.gr
64
+ executables:
65
+ - container-backup
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - ".gitignore"
70
+ - ".rspec"
71
+ - ".travis.yml"
72
+ - CODE_OF_CONDUCT.md
73
+ - Gemfile
74
+ - Gemfile.lock
75
+ - LICENSE
76
+ - README.md
77
+ - Rakefile
78
+ - bin/console
79
+ - bin/setup
80
+ - container-backup.gemspec
81
+ - exe/container-backup
82
+ - lib/container/action.rb
83
+ - lib/container/backup.rb
84
+ - lib/container/backup/version.rb
85
+ - lib/container/docker_compose.rb
86
+ - lib/container/parameter.rb
87
+ - lib/container/step.rb
88
+ - lib/container/step_factory.rb
89
+ - lib/container/steps/databases.rb
90
+ - lib/container/steps/directories.rb
91
+ - lib/container/steps/volumes.rb
92
+ - scripts/mssql/backup_mssql_dbs.sql
93
+ - scripts/mssql/init.sql
94
+ - scripts/mssql/map_logins.sql
95
+ - scripts/mssql/restore_aegean2dev.sql
96
+ homepage: https://github.com/mpantel/container-backup
97
+ licenses:
98
+ - MIT
99
+ metadata:
100
+ homepage_uri: https://github.com/mpantel/container-backup
101
+ source_code_uri: https://github.com/mpantel/container-backup
102
+ changelog_uri: https://github.com/mpantel/container-backup
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 2.3.0
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubygems_version: 3.1.4
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Uses docker compose backup lable for each container to drive backup and restore
122
+ test_files: []