backups-cli 1.0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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