bkp 1.0.0 → 1.0.2

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.
Files changed (7) hide show
  1. checksums.yaml +4 -4
  2. data/Onafile +159 -0
  3. data/bin/bkp +1 -1
  4. data/lib/bkp.rb +0 -0
  5. data/lib/helpers.rb +233 -0
  6. data/lib/validations.rb +80 -0
  7. metadata +5 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e35406050d99e452d6cd2f9a780f345ad5139ccefc93bb248f0eaf6ad46871ec
4
- data.tar.gz: 0d53ae0985b1a2acf3dc5ea9c735e6a62db12bcd75977c47c1cd9b91d6999007
3
+ metadata.gz: faf235ebd2ca4d89012682be1bd15ef5d69fc2cffaaf7d2caf6b7c97c3239e07
4
+ data.tar.gz: 1e11efcc09b0a6ce1b3e4e2a395776b5d8dc193c16ef16592ec81d21728e0a29
5
5
  SHA512:
6
- metadata.gz: 6a9fb68afa5d77be1440fe1ef69a9c1271e78bb3954b91b0ddab61d92330da85870ba91d17cf8c6fda562f481b9fa777981f1f03a93afdb437294a3e9b2f2717
7
- data.tar.gz: 24d81d917b11e79a76cbfe09395d84619dc459506c9a3f89abe8c1ca42c9b9b873028f60dd5ed018bf235a5b921f912e54244ecb17dc8737b7b4eba1fd5275cc
6
+ metadata.gz: 593a9d038c49107a8c93749e3755428ffd6f74736111425a6b06636102aba0e3f2ceee8c3f470bc641538bc749800cb12a00ad1b0a46e9cd30c6cea42ec6783b
7
+ data.tar.gz: e546d9dd408cb290be0d768183e90e79e6edece0bac533d841cd4ab08715a528b354fdb8366052c75b32400cacf880cc928fd95000e772461d18b697e954faaa
data/Onafile CHANGED
@@ -1,3 +1,162 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'yaml'
4
+ require 'time'
5
+ require 'tmpdir'
6
+ require 'json'
7
+ require 'fileutils'
8
+ require './lib/validations.rb'
9
+ require './lib/helpers.rb'
10
+
11
+ if ENV['OLDPWD']
12
+ Dir.chdir(ENV['OLDPWD'])
13
+ end
14
+
15
+ config_check
16
+
3
17
  Ona.prompt = 'bkp'
18
+
19
+ Ona.resource(:backup, [
20
+ :background,
21
+ :bucket,
22
+ :date,
23
+ :name,
24
+ :owner,
25
+ :summary,
26
+ :ticket,
27
+ :path,
28
+ :directory
29
+ ])
30
+
31
+ reload_manifests
32
+
33
+ Ona.action(
34
+ :regex => /(^)(ls)($)/,
35
+ :resource => :backup,
36
+ :text => "List backups.",
37
+ :example => "ls"
38
+ ) do |items, command, regex|
39
+ items.sort do |a, b|
40
+ Time.parse(a.date) <=> Time.parse(b.date)
41
+ end.each_with_index do |item, id|
42
+ puts pretty_backup_list(id, item)
43
+ end
44
+ end
45
+
46
+ Ona.action(
47
+ :regex => /(^)(new)($)/,
48
+ :resource => :backup,
49
+ :text => "Creates a new backup config file.",
50
+ :example => "new"
51
+ ) do |items, command, regex|
52
+ generate_template
53
+ end
54
+
55
+ Ona.action(
56
+ :regex => /(^)(check)(\s+)(.*)($)/,
57
+ :resource => :backup,
58
+ :text => "Validates a backup config file.",
59
+ :example => "check [FILENAME]"
60
+ ) do |items, command, regex|
61
+ file = command.scan(regex)[0][3]
62
+ file.gsub!(/(\s+$)/, '')
63
+ load_file(file)
64
+ end
65
+
66
+ Ona.action(
67
+ :regex => /(^)(upload)(\s+)(.*)($)/,
68
+ :resource => :backup,
69
+ :text => "Uploads backups from config file.",
70
+ :example => "upload [FILENAME]"
71
+ ) do |items, command, regex|
72
+ file = command.scan(regex)[0][3]
73
+ file.gsub!(/(\s+$)/, '')
74
+ backups = load_file(file)
75
+ unless Ona.confirm('Are you sure you want to upload these backups?', 'yes')
76
+ next
77
+ end
78
+ backups.each do |backup|
79
+ create_backup(backup)
80
+ end
81
+ end
82
+
83
+ Ona.action(
84
+ :regex => /(^)(sync)($)/,
85
+ :resource => :backup,
86
+ :text => "Reads remote backups so they can be evaluated locally.",
87
+ :example => "sync"
88
+ ) do |items, command, regex|
89
+ download_manifests
90
+ reload_manifests
91
+ end
92
+
93
+ Ona.action(
94
+ :regex => /(^)(show)(\s+)(.*)($)/,
95
+ :resource => :backup,
96
+ :text => "Shows a backup config file.",
97
+ :example => "show [NUMBER]",
98
+ ) do |items, command, regex|
99
+ queried_id = command.scan(regex)[0][3]
100
+ if queried_id =~ /\d+/
101
+ queried_id = queried_id.to_i
102
+ else
103
+ next
104
+ end
105
+ items.sort do |a, b|
106
+ Time.parse(a.date) <=> Time.parse(b.date)
107
+ end.each_with_index do |backup, id|
108
+ if id == queried_id
109
+ pretty_backup_body(id, backup)
110
+ end
111
+ end
112
+ end
113
+
114
+ Ona.action(
115
+ :regex => /(^)(download)(\s+)(.*)($)/,
116
+ :resource => :backup,
117
+ :text => 'Download a remote backup',
118
+ :example => "download [NUMBER]",
119
+ ) do |items, command, regex|
120
+ queried_id = command.scan(regex)[0][3]
121
+ if queried_id =~ /\d+/
122
+ queried_id = queried_id.to_i
123
+ else
124
+ next
125
+ end
126
+ items.sort do |a, b|
127
+ Time.parse(a.date) <=> Time.parse(b.date)
128
+ end.each_with_index do |backup, id|
129
+ if id == queried_id
130
+ run_command('aws s3 cp ' + s3_path(backup) + ' .' )
131
+ end
132
+ end
133
+ end
134
+
135
+ Ona.action(
136
+ :regex => /(^)(s)(\s+)(.*)($)/,
137
+ :resource => :backup,
138
+ :text => 'Search in local manifests',
139
+ :example => "s [REGEX]",
140
+ ) do |items, command, regex|
141
+ query = command.scan(regex)[0][3]
142
+ search = Regexp.new(query, Regexp::IGNORECASE)
143
+ items.sort do |a, b|
144
+ Time.parse(a.date) <=> Time.parse(b.date)
145
+ end.each_with_index do |backup, id|
146
+ search_in = [
147
+ backup.name,
148
+ backup.directory,
149
+ backup.path,
150
+ backup.summary,
151
+ backup.ticket,
152
+ backup.owner,
153
+ backup.date
154
+ ]
155
+ next unless search_in.any? { |s| s =~ search }
156
+ s = pretty_backup_list(id, backup)
157
+ r = s.gsub(search) do |match|
158
+ match.to_s.to_ansi.red.to_s
159
+ end
160
+ puts r
161
+ end
162
+ end
data/bin/bkp CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- system "cd #{__dir__}/../ && ona #{ARGV.join(' ')}"
3
+ system "cd #{__dir__}/../ && OLDPWD=#{Dir.pwd} ona #{ARGV.join(' ')}"
4
4
 
data/lib/bkp.rb ADDED
File without changes
data/lib/helpers.rb ADDED
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ TEMPLATE = "---
4
+ -
5
+ # A short one line easy to understand summary of what the backup is for.
6
+ summary:
7
+
8
+ # Some context about the backup (why are we doing this?)
9
+ background: |-
10
+ This is a backup of the database for the website.
11
+ You can find the website at http://www.example.com
12
+
13
+ It is important to backup the database because it contains all the information for the website.
14
+
15
+ # The local diretory you want to backup
16
+ directory: ./mydata
17
+
18
+ # The date of the backup
19
+ date: '%<date>s'
20
+
21
+ # Who created the backup. (usually your name)
22
+ owner:
23
+
24
+ # The ticket url associated with the backup
25
+ ticket: ''
26
+
27
+ # The bucket where the backup will be stored
28
+ # (probably okay to keep as is)
29
+ bucket: %<bucket>s
30
+
31
+ # The path where all the backups are stored in the bucket
32
+ # Think about it this way: bucket/path/[backups live here]
33
+ # (probably okay to keep as is)
34
+ path: %<path>s
35
+ "
36
+
37
+ CONFIG_FILE = "#{ENV['HOME']}/.bkp/config.yml"
38
+
39
+ DEFAULT_CONFIG = "---
40
+ # The default bucket where all backups will be stored
41
+ bucket: ''
42
+
43
+ # The default path where all backups will be stored
44
+ # Think about it this way: bucket/path/[backups live here]
45
+ path: ''
46
+ "
47
+
48
+ def to_hyphen(string)
49
+ string = string.chomp
50
+ string.gsub!(/\W+/, ' ')
51
+ string.gsub!(/\s+/, '-')
52
+ string.gsub!(/^[-]+|[-]+$/, '')
53
+ string.downcase
54
+ end
55
+
56
+ def create_backup(backup)
57
+ Dir.mktmpdir do |dir|
58
+ # we keep the original directory when we tar and gzip the directory
59
+ run_command "cd #{backup['directory']} && cd .. && tar -czf #{dir}/#{backup['name']}.tar.gz #{backup['directory'].split('/').last}"
60
+ run_command "aws s3 cp #{dir}/#{backup['name']}.tar.gz s3://#{backup['bucket']}/#{backup['path']}/#{backup['name']}/#{backup['name']}.tar.gz"
61
+
62
+ manifest = "#{dir}/manifest.json"
63
+
64
+ File.open(manifest, 'w+') do |file|
65
+ file.write(backup.to_json)
66
+ end
67
+
68
+ run_command "aws s3 cp #{manifest} s3://#{backup['bucket']}/#{backup['path']}/#{backup['name']}/manifest.json"
69
+ end
70
+ end
71
+
72
+ def load_file(file)
73
+ unless File.exist?(file)
74
+ $stderr.puts "Backup File #{file.inspect} does not exist."
75
+ exit 1
76
+ end
77
+
78
+ backups = YAML.load_file(file)
79
+
80
+ backups.each_with_index do |backup, index|
81
+ validate_summary(backup['summary'], index)
82
+ validate_background(backup['background'], index)
83
+ validate_directory(backup['directory'], index)
84
+ validate_date(backup['date'], index)
85
+ validate_owner(backup['owner'], index)
86
+ validate_ticket(backup['ticket'], index)
87
+ validate_bucket(backup['bucket'], index)
88
+ validate_path(backup['path'], index)
89
+ date = Time.parse(backup['date'], index)
90
+ date = date.strftime('%Y-%m-%d')
91
+ backup['name'] = date + '-' + to_hyphen(backup['summary'])
92
+ end
93
+ end
94
+
95
+ def backup_list
96
+ config = YAML.load_file(CONFIG_FILE)
97
+ list = File.expand_path("~/.bkp/list.txt")
98
+ run_command "aws s3 ls s3://#{config['bucket']}/#{config['path']}/ > #{list}"
99
+ files = []
100
+ File.read(list).each_line do |line|
101
+ files << line.split(' ').last
102
+ end
103
+ files
104
+ end
105
+
106
+ def download_manifests
107
+ config = YAML.load_file(CONFIG_FILE)
108
+ backups = backup_list
109
+ FileUtils.mkdir_p(File.expand_path('~/.bkp/manifests'))
110
+ FileUtils.rm(Dir.glob(File.expand_path('~/.bkp/manifests/*.json')))
111
+ backups.each do |backup|
112
+ backup = backup.split('/').first
113
+ run_command "aws s3 cp s3://#{config['bucket']}/#{config['path']}/#{backup}/manifest.json ~/.bkp/manifests/#{backup}.json"
114
+ end
115
+ reload_manifests
116
+ end
117
+
118
+ def load_manifests
119
+ Dir.glob(File.expand_path('~/.bkp/manifests/*.json')).map do |file|
120
+ object = JSON.parse(File.read(file))
121
+ Ona.register(:backup) do |backup|
122
+ backup.name = object['name']
123
+ backup.summary = object['summary']
124
+ backup.background = object['background']
125
+ backup.directory = object['directory']
126
+ backup.date = object['date']
127
+ backup.owner = object['owner']
128
+ backup.ticket = object['ticket']
129
+ backup.bucket = object['bucket']
130
+ backup.path = object['path']
131
+ end
132
+ end
133
+ end
134
+
135
+ def unload_manifests
136
+ Ona.class_eval do
137
+ puts @resources[:backup][:entries] = []
138
+ end
139
+ end
140
+
141
+ def reload_manifests
142
+ unload_manifests
143
+ load_manifests
144
+ end
145
+
146
+ def config_check
147
+ return if File.exist?(CONFIG_FILE)
148
+
149
+ puts "Config file #{CONFIG_FILE.inspect} does not exist."
150
+
151
+ unless Ona.confirm('Okay to create a new config file in ~/.bkp/config.yml?', 'yes')
152
+ $stderr.puts 'Will exit this program now.'
153
+ exit 1
154
+ end
155
+
156
+ puts 'What is the name of the bucket where you want to store your backups?'
157
+ print 'S3 Bucket name> '
158
+ bucket = gets.chomp
159
+
160
+ puts "What is the 'path' where you want to store your backups?"
161
+ print 'S3 Bucket path> '
162
+ path = gets.chomp
163
+
164
+ config = YAML.load(DEFAULT_CONFIG)
165
+ config['bucket'] = bucket
166
+ config['path'] = path
167
+ FileUtils.mkdir_p(File.expand_path('~/.bkp'))
168
+ File.open(CONFIG_FILE, 'w+') do |file|
169
+ file.write(config.to_yaml)
170
+ end
171
+ end
172
+
173
+ # we use gsub because older rubies format method works differently.
174
+ def generate_template
175
+ config = YAML.load_file(CONFIG_FILE)
176
+ template = TEMPLATE.dup
177
+ template.gsub!('%<date>s', Time.now.to_s)
178
+ template.gsub!('%<bucket>s', config['bucket'])
179
+ template.gsub!('%<path>s', config['path'])
180
+ puts template
181
+ end
182
+
183
+ def run_command(command)
184
+ puts ''
185
+ puts "# Command: #{command.to_ansi.yellow.to_s}"
186
+ puts "# Executed at: #{Time.now.to_s}"
187
+ puts "# #{('=' * 76).to_ansi.cyan.to_s}"
188
+ system command
189
+ puts ''
190
+ end
191
+
192
+ def pretty_backup_list(id, backup)
193
+ pretty_id = id.to_s.to_s.rjust(5, ' ').to_ansi.cyan.to_s
194
+ pretty_name = 'name'.to_ansi.green.to_s
195
+ pretty_date = backup.date.to_ansi.yellow.to_s
196
+ "#{pretty_id} - [#{pretty_date}] #{pretty_name}: #{backup.name}"
197
+ end
198
+
199
+ def pretty_backup_body(id, backup)
200
+ puts ''
201
+ puts 'Summary:'.to_ansi.green.to_s
202
+ puts backup.summary.to_ansi.cyan.to_s
203
+ puts ''
204
+ puts 'Background:'.to_ansi.green.to_s
205
+ puts backup.background.to_ansi.cyan.to_s
206
+ puts ''
207
+ puts 'Date:'.to_ansi.green.to_s
208
+ puts backup.date.to_ansi.cyan.to_s
209
+ puts ''
210
+ puts 'Owner:'.to_ansi.green.to_s
211
+ puts backup.owner.to_ansi.cyan.to_s
212
+ puts ''
213
+ puts 'Ticket:'.to_ansi.green.to_s
214
+ puts backup.ticket.to_ansi.cyan.to_s
215
+ puts ''
216
+ puts 'Bucket:'.to_ansi.green.to_s
217
+ puts backup.bucket.to_ansi.cyan.to_s
218
+ puts ''
219
+ puts 'Path:'.to_ansi.green.to_s
220
+ puts backup.path.to_ansi.cyan.to_s
221
+ puts ''
222
+ puts 'Directory:'.to_ansi.green.to_s
223
+ puts backup.directory.to_ansi.cyan.to_s
224
+ puts ''
225
+ puts 'S3 Path:'.to_ansi.green.to_s
226
+ puts s3_path(backup).to_ansi.cyan.to_s
227
+ puts ''
228
+
229
+ end
230
+
231
+ def s3_path(backup)
232
+ 's3://' + backup.bucket + '/' + backup.path + '/' + backup.name + '/' + backup.name + '.tar.gz'
233
+ end
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ def validate_summary(summary, index)
4
+ if summary.nil? || summary.empty?
5
+ $stderr.puts "Backup(#{index}) summary is empty: #{summary.inspect}"
6
+ exit 1
7
+ end
8
+
9
+ if summary.size > 80
10
+ $stderr.puts "Backup(#{index}) summary is too long: #{summary.size} characters"
11
+ exit 1
12
+ end
13
+ end
14
+
15
+ def validate_background(background, index)
16
+ if background.nil? || background.empty?
17
+ $stderr.puts "Backup(#{index}) background is empty: #{background.inspect}"
18
+ exit 1
19
+ end
20
+
21
+ if background.size < 80
22
+ $stderr.puts "Backup(#{index}) background is too short: #{background.size} characters"
23
+ exit 1
24
+ end
25
+ end
26
+
27
+ def validate_directory(directory, index)
28
+ if directory.nil? || directory.empty?
29
+ $stderr.puts "Backup(#{index}) directory is empty: #{directory.inspect}"
30
+ exit 1
31
+ end
32
+
33
+ unless File.directory?(directory)
34
+ $stderr.puts "Backup(#{index}) directory does not exist: #{directory.inspect}"
35
+ exit 1
36
+ end
37
+ end
38
+
39
+ def validate_date(date, index)
40
+ if date.nil? || date.empty?
41
+ $stderr.puts "Backup(#{index}) date is empty: #{date.inspect}"
42
+ exit 1
43
+ end
44
+
45
+ begin
46
+ Time.parse(date)
47
+ rescue ArgumentError
48
+ $stderr.puts "Backup(#{index}) date is not a valid date: #{date.inspect}"
49
+ exit 1
50
+ end
51
+ end
52
+
53
+ def validate_owner(owner, index)
54
+ if owner.nil? || owner.empty?
55
+ $stderr.puts "Backup(#{index}) owner is empty: #{owner.inspect}"
56
+ exit 1
57
+ end
58
+ end
59
+
60
+ def validate_ticket(ticket, index)
61
+ if ticket.nil? || ticket.empty?
62
+ $stderr.puts "Backup(#{index}) ticket is empty: #{ticket.inspect}"
63
+ exit 1
64
+ end
65
+ end
66
+
67
+ def validate_bucket(bucket, index)
68
+ if bucket.nil? || bucket.empty?
69
+ $stderr.puts "Backup(#{index}) bucket is empty: #{bucket.inspect}"
70
+ exit 1
71
+ end
72
+ end
73
+
74
+ def validate_path(path, index)
75
+ if path.nil? || path.empty?
76
+ $stderr.puts "Backup(#{index}) path is empty: #{path.inspect}"
77
+ exit 1
78
+ end
79
+ end
80
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bkp
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kazuyoshi Tlacaelel
@@ -48,6 +48,9 @@ files:
48
48
  - Gemfile
49
49
  - Onafile
50
50
  - bin/bkp
51
+ - lib/bkp.rb
52
+ - lib/helpers.rb
53
+ - lib/validations.rb
51
54
  homepage: https://github.com/ktlacaelel/bkp
52
55
  licenses:
53
56
  - MIT
@@ -70,5 +73,5 @@ requirements: []
70
73
  rubygems_version: 3.3.7
71
74
  signing_key:
72
75
  specification_version: 4
73
- summary: bkp - Backup Management Tool
76
+ summary: bkp - Shell Backup Management Tool (for AWS S3 Buckets)
74
77
  test_files: []