backups-cli 1.0.9

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,70 @@
1
+ module Backups
2
+ class Base
3
+
4
+ ALL_DATABASES = "all-databases"
5
+
6
+ include System
7
+ include Loader
8
+
9
+ def initialize config
10
+ @config = config
11
+ end
12
+
13
+ def get_timestamp
14
+ now = Time.new
15
+ now = now.utc if now.utc?
16
+ now.iso8601.gsub(':', '-')
17
+ end
18
+
19
+ def get_date_path
20
+ Time.now.strftime('%Y/%m/%d')
21
+ end
22
+
23
+ def get_prefix
24
+ return @options['s3']['prefix'] if @options['s3']['prefix']
25
+ return @config["_name"]
26
+ end
27
+
28
+ def compress source, dest, secret
29
+ if source.kind_of? Array
30
+ dir = File.dirname(source[0])
31
+ base = source.map {|v| File.basename(v)}.join(' ')
32
+ else
33
+ dir = File.dirname(source)
34
+ base = File.basename(source)
35
+ end
36
+
37
+ commands = []
38
+ commands << "cd #{dir} && zip"
39
+ commands << "--password #{secret}" if secret
40
+ commands << "-r #{dest} #{base}"
41
+
42
+ exec commands.join(' ')
43
+ end
44
+
45
+ def send_to_s3 bucket, path, filename, options = nil
46
+ dest = "s3://#{bucket}/#{path}"
47
+ $LOGGER.info "Sending to #{dest}"
48
+ exec "aws s3 cp #{filename} #{dest}/"
49
+
50
+ return unless options
51
+ tags = options.fetch('tags', {})
52
+
53
+ return unless tags.size > 0
54
+ key = "#{path}/#{File.basename(filename)}"
55
+ tag_s3_object bucket, key, tags
56
+ end
57
+
58
+ def tag_s3_object bucket, key, tags
59
+ tagSet = tags.map{ |k, v| "{Key=#{k},Value=#{v}}" }.join(",")
60
+ $LOGGER.info "Tagging s3://#{bucket}/#{key}"
61
+ $LOGGER.debug "Tagset: #{tagSet}"
62
+
63
+ exec "aws s3api put-object-tagging \
64
+ --bucket #{bucket} \
65
+ --key #{key} \
66
+ --tagging 'TagSet=[#{tagSet}]'"
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,51 @@
1
+ require "thor"
2
+
3
+ module Backups
4
+ class Cli < Thor
5
+
6
+ desc "version", "Show current version"
7
+ def version
8
+ puts "v#{VERSION}"
9
+ end
10
+
11
+ desc "ls", "Lists all the configured jobs"
12
+ def ls
13
+ Crontab.new.list()
14
+ end
15
+
16
+ desc "show [JOB]", "Shows the merged config (for a JOB or them all)"
17
+ def show job = nil
18
+ data = Runner.new.show(job)
19
+ puts data.to_json
20
+ end
21
+
22
+ desc "start [JOB]", "Starts a backup JOB or all of them"
23
+ def start job = nil
24
+ if job
25
+ Runner.new.start job
26
+ else
27
+ Runner.new.start_all
28
+ end
29
+ end
30
+
31
+ desc "verify [JOB]", "Restores and verifies a backup JOB or all of them"
32
+ def verify job = nil
33
+ if job
34
+ Runner.new.verify job
35
+ else
36
+ Runner.new.verify_all
37
+ end
38
+ end
39
+
40
+ desc "install", "Sets up the crontab for all jobs"
41
+ def install
42
+ Crontab.new.install
43
+ end
44
+
45
+ desc "crontab", "Shows the crontab config"
46
+ def crontab
47
+ puts Crontab.new.show()
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,200 @@
1
+ require "tablelize"
2
+
3
+ module Backups
4
+ class Crontab
5
+
6
+ include System
7
+ include Loader
8
+
9
+ PADDING = 10
10
+
11
+ def initialize
12
+ load_configs
13
+ @base = File.expand_path('../../..', __FILE__)
14
+ end
15
+
16
+ def list
17
+ paddings = {
18
+ 'job' => 20,
19
+ 'backup' => 10,
20
+ 'verify' => 10,
21
+ }
22
+
23
+ rows = []
24
+ rows << ["JOB", "CRONTAB", "INSTALL", "ENABLED"]
25
+
26
+ $GLOBAL["jobs"].each do |job, config|
27
+ backup = config.fetch('backup', {})
28
+ crontab = backup.fetch('crontab', {})
29
+ crontime = get_crontime(crontab)
30
+ install = crontab.fetch('install', true)
31
+ enabled = config.fetch('enabled', true)
32
+
33
+ rows << [job, crontime, install, enabled]
34
+ end
35
+
36
+ Tablelize::table rows
37
+ end
38
+
39
+ def show
40
+ get_install_lines()
41
+ end
42
+
43
+ def install
44
+ last_log_active = $LOG_ACTIVE
45
+ $LOG_ACTIVE = 0
46
+
47
+ install_mysql_groups
48
+
49
+ content = get_install_lines()
50
+ cronfile = "/tmp/crontab.new"
51
+ write cronfile, content
52
+ exec "crontab #{cronfile}"
53
+ exec "rm #{cronfile}"
54
+ exec "crontab -l"
55
+
56
+ $LOG_ACTIVE = last_log_active
57
+ end
58
+
59
+ private
60
+ def get_install_lines
61
+ # We dont want passwords in the main log and write does that
62
+
63
+ start = "# BEGIN SECTION Backups\n"
64
+ finish = "# END SECTION Backups\n"
65
+ previous = `crontab -l 2>/dev/null`
66
+
67
+ backups = $GLOBAL.fetch("backups", {})
68
+ crontab = backups.fetch("crontab", {})
69
+ header = crontab.fetch("header", "")
70
+ backup = crontab.fetch("backup", {})
71
+ verify = crontab.fetch("verify", {})
72
+
73
+ backup_all = backup.fetch("run-all", false)
74
+ verify_all = verify.fetch("run-all", false)
75
+
76
+ content = "#{header}\n#{previous}" \
77
+ if header and not previous.include? header
78
+
79
+ content << start
80
+ content << "# Generated at #{Time.now}\n"
81
+
82
+ if backup_all
83
+ content << get_all_backup(backup) + "\n"
84
+ else
85
+ content << get_jobs_crontab() + "\n"
86
+ end
87
+
88
+ if verify_all
89
+ content << get_all_verify(verify) + "\n"
90
+ end
91
+
92
+ content << finish
93
+ end
94
+
95
+ def get_all_backup crontab
96
+ line = "#{@base}/bin/backups start"
97
+ minute = crontab.fetch("minute", 0)
98
+ hour = crontab.fetch("hour", "*")
99
+ crontime = get_crontime(crontab)
100
+ prefix = crontab.fetch("prefix", "")
101
+ postfix = crontab.fetch("postfix", "")
102
+
103
+ line = "#{prefix} #{line}" if prefix
104
+ line = "#{line} #{postfix}" if postfix
105
+
106
+ "#{crontime} #{line}"
107
+ end
108
+
109
+ def get_all_verify crontab
110
+ line = "#{@base}/bin/backups verify"
111
+ minute = crontab.fetch("minute", 0)
112
+ hour = crontab.fetch("hour", "*")
113
+ crontime = get_crontime(crontab)
114
+ prefix = crontab.fetch("prefix", "")
115
+ postfix = crontab.fetch("postfix", "")
116
+
117
+ line = "#{prefix} #{line}" if prefix
118
+ line = "#{line} #{postfix}" if postfix
119
+
120
+ "#{crontime} #{line}"
121
+ end
122
+
123
+ def get_jobs_crontab
124
+ contents = []
125
+
126
+ $GLOBAL['jobs'].each do |job, config|
127
+ backup = config.fetch('backup', {})
128
+ crontab = backup.fetch('crontab', {})
129
+ crontime = get_cronjob(job, 'start', crontab)
130
+ contents << crontime
131
+ end
132
+
133
+ contents.join("\n")
134
+ end
135
+
136
+ def get_cronjob job, command, crontab
137
+ return unless crontab.fetch('install', true)
138
+ line = "#{@base}/bin/backups #{command} #{job}"
139
+ time = get_crontime(crontab)
140
+
141
+ prefix = crontab.fetch("prefix", "")
142
+ postfix = crontab.fetch("postfix", "")
143
+ line = "#{prefix} #{line}" if prefix
144
+ line = "#{line} #{postfix}" if postfix
145
+
146
+ "#{time} #{line}" if not time.nil?
147
+ end
148
+
149
+ def get_crontime crontab
150
+ time = "#{crontab.fetch('minute', '0')}"
151
+ time << " #{crontab.fetch('hour', '4')}"
152
+ time << " #{crontab.fetch('day', '*')}"
153
+ time << " #{crontab.fetch('month', '*')}"
154
+ time << " #{crontab.fetch('weekday', '*')}"
155
+
156
+ time.strip()
157
+ end
158
+
159
+ def install_mysql_groups
160
+ $GLOBAL["jobs"].each do |job, config|
161
+ install_mysql_group job, config if config["type"].downcase === "mysql"
162
+ end
163
+ end
164
+
165
+ def install_mysql_group job, config
166
+ group = "client_" + job.gsub("-", "_")
167
+ username = config["backup"]["connection"]["username"]
168
+ password = config["backup"]["connection"]["password"]
169
+
170
+ contents = []
171
+ contents << "[" + group + "]"
172
+ contents << "user = " + username if username
173
+ contents << "password = " + password if password
174
+ replace_mysql_group group, contents.join("\n")
175
+ end
176
+
177
+ def replace_mysql_group group, content
178
+ path = File.expand_path("~/.my.cnf")
179
+ lines = []
180
+ if File.exist? path
181
+ found = false
182
+ File.readlines(path).each do |line|
183
+ if line === "[#{group}]\n"
184
+ found = true
185
+ elsif line.match(/^\[.*\]\n/)
186
+ found = false
187
+ end
188
+ lines << line if not found
189
+ end
190
+ end
191
+
192
+ contents = lines.join()
193
+ contents += "\n" if contents.size > 0 and contents[contents.size-1..-1] != "\n"
194
+ contents += content
195
+
196
+ write path, contents
197
+ end
198
+
199
+ end
200
+ end
@@ -0,0 +1,35 @@
1
+ require "mysql2"
2
+
3
+ module Backups
4
+ module Driver
5
+ module Mysql
6
+
7
+ def connect
8
+ @connection[flags: Mysql2::Client::MULTI_STATEMENTS]
9
+ @mysql = Mysql2::Client.new(@connection)
10
+ end
11
+
12
+ def exec_query(sql)
13
+ return puts sql if $dry_run
14
+ connect if not @mysql
15
+ @mysql.query sql
16
+ end
17
+
18
+ def get_results(sql)
19
+ return [] if not rset = exec_query(sql)
20
+ rows = []
21
+ rset.each do |row|
22
+ rows << row
23
+ end
24
+
25
+ return rows
26
+ end
27
+
28
+ def get_result(sql)
29
+ rows = get_results(sql)
30
+ return rows[0]
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,40 @@
1
+ module Backups
2
+ class Events
3
+
4
+ @@events = {}
5
+
6
+ def self.on *events, &block
7
+ names = []
8
+ if events.kind_of? Array
9
+ names = events
10
+ else
11
+ names << events
12
+ end
13
+
14
+ names.each do |name|
15
+ @@events[name] ||= []
16
+ @@events[name] << block
17
+ end
18
+ end
19
+
20
+ def self.fire event, params
21
+ params[:event] = event
22
+ names = []
23
+ if event.kind_of? Array
24
+ names = event
25
+ else
26
+ names << event
27
+ end
28
+
29
+ names.each do |name|
30
+ if @@events.has_key? name
31
+ @@events[name].each do |cb|
32
+ res = cb.call(params)
33
+ break if res == false
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ unless Fixnum.method_defined?(:european)
2
+ class ::Fixnum
3
+ def european
4
+ self.to_s.reverse.gsub(/...(?=.)/,'\& ').reverse
5
+ end
6
+ end
7
+ end
8
+
9
+ unless Fixnum.method_defined?(:to_filesize)
10
+ class ::Fixnum
11
+ def to_filesize
12
+ {
13
+ 'B' => 1024,
14
+ 'KB' => 1024 * 1024,
15
+ 'MB' => 1024 * 1024 * 1024,
16
+ 'GB' => 1024 * 1024 * 1024 * 1024,
17
+ 'TB' => 1024 * 1024 * 1024 * 1024 * 1024
18
+ }.each_pair {|e, s| return "#{(self.to_f / (s/1024)).round(2)} #{e}" if self < s }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ unless Hash.method_defined?(:deep_merge)
2
+ class ::Hash
3
+ def deep_merge(second)
4
+ merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
5
+ self.merge(second, &merger)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ unless NilClass.method_defined?(:[])
2
+ class ::NilClass
3
+ def [](*args)
4
+ nil
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ unless BSON::OrderedHash.method_defined?(:to_json)
2
+ class BSON::OrderedHash
3
+ def to_h
4
+ inject({}) { |acc, element| k,v = element; acc[k] = (if v.class == BSON::OrderedHash then v.to_h else v end); acc }
5
+ end
6
+
7
+ def to_json
8
+ to_h.to_json
9
+ end
10
+ end
11
+ end