ops_backups 0.1.11 → 0.1.13

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2fef2adc42b4749261dd1a0eb29142b0e55c84dd0465a519bdfdea0e3b86314
4
- data.tar.gz: 4bf5d15d8584abfb624fc8071da8cb5afa578830e1bc4b8754b8e716692e67d6
3
+ metadata.gz: 9fd477dfd14deed2d6a2781ed4da42ccd0acd90e1c72111d1b577d7392f5cda5
4
+ data.tar.gz: c881e6740abefb06b6367a855be6a46b3231def32529c6773b1e471d57705d25
5
5
  SHA512:
6
- metadata.gz: 7e70cc33d5f7d23e4f9cff8b22b0a6bcaa32d0830d4ea232c5d3e41db2f9adaca62ad484423c96ffbc9ec85d38fb2202094f3ff55c28b5a24c29c320a4a4caf3
7
- data.tar.gz: 277e00a4449260ff40a4558df7bf7cd624e20a73ca72fe696d7bebd257529bbf2b175e813351401e0756d6f135ad2646b1139bdc0f35e68cf2e49a747247d55d
6
+ metadata.gz: a5d2218b533240662da7fa48af937ea24b7f5c7e669278c0dd352b59ba28dde091a03ebdda9202045867bbbe2d82f911b3214a16d66a343f7951b99a3a26ad6b
7
+ data.tar.gz: 8211a1acf5bffbb596c709e9dbc1f857fade276d017d83cd246ba03c6b084378deea6afa1ac3c9753348724ca082d41a675648ce685f8e2dce7b26f2064599c7
@@ -13,3 +13,6 @@
13
13
  *= require_tree .
14
14
  *= require_self
15
15
  */
16
+ html {
17
+ font-family: 'Courier New', Courier, monospace;
18
+ }
@@ -0,0 +1,40 @@
1
+ module OpsBackups
2
+ class BackupsController < ApplicationController
3
+ def index
4
+ @backups = OpsBackups::Backup.all.order(updated_at: :desc)
5
+ @jobs = jobs
6
+ end
7
+
8
+ def download
9
+ backup = OpsBackups::Backup.find(params[:id])
10
+ redirect_to backup.backup_file.url(disposition: :attachment), allow_other_host: true
11
+ end
12
+
13
+ def destroy
14
+ backup = OpsBackups::Backup.find(params[:id])
15
+ backup.destroy
16
+ redirect_to backups_url, notice: "#{backup} destroyed."
17
+ end
18
+
19
+ def trigger_job
20
+ job_name = params[:job_name]
21
+ job_config = jobs[job_name]
22
+
23
+ job_class = job_config["class"].constantize
24
+ args = job_config["args"] || []
25
+
26
+ Rails.logger.info "Triggering job: #{job_name} with args: #{args.reduce({}, :merge).symbolize_keys}"
27
+
28
+ job_class.perform_later(args.reduce({}, :merge).symbolize_keys)
29
+
30
+ redirect_to backups_url, notice: "#{job_name} triggered successfully."
31
+ end
32
+
33
+ private
34
+
35
+ def jobs
36
+ jobs = YAML.load_file(Rails.root.join("config", "recurring.yml"))[Rails.env]
37
+ jobs&.select { |k, v| v["class"].start_with?("OpsBackups") }
38
+ end
39
+ end
40
+ end
@@ -1,4 +1,4 @@
1
1
  module OpsBackups
2
- module ApplicationHelper
2
+ module BackupsHelper
3
3
  end
4
4
  end
@@ -12,6 +12,7 @@ class OpsBackups::BackupDbJob < ApplicationJob
12
12
  exclude_tables = options[:exclude_tables] || []
13
13
  tag = options[:tag] || (exclude_tables.empty? ? "db_pg_full" : "db_pg_partial")
14
14
  cleanup = options[:cleanup]
15
+ Rails.logger.info "Performing backup with tag: #{tag} and exclude_tables: #{exclude_tables}"
15
16
  OpsBackups::Backup.new.db_pg_backup(exclude_tables:, tag:)
16
17
  OpsBackups::Backup.send("#{cleanup}_cleanup_policy", tag: tag) if cleanup.present?
17
18
  end
@@ -1,16 +1,31 @@
1
1
  module OpsBackups
2
2
  class Backup < ActiveRecord::Base
3
- # has_one_attached :backup_file, service: :backup_storage
4
- has_one_attached :backup_file, service: :backups
3
+ include ActionView::Helpers::NumberHelper
4
+
5
5
  self.table_name = "ops_backups"
6
6
 
7
+ has_one_attached :backup_file, service: :backups
8
+
7
9
  default_scope { order(updated_at: :desc) }
8
10
 
9
11
  def self.ransackable_attributes(auth_object = nil)
10
12
  [ "created_at", "id", "name", "new_id", "tag", "updated_at" ]
11
13
  end
12
14
 
15
+ def size
16
+ backup_file.attached? ? number_to_human_size(backup_file.byte_size) : "N/A"
17
+ end
18
+
19
+ def duration
20
+ Time.at((updated_at - created_at)).utc.strftime("%H:%M:%S")
21
+ end
22
+
13
23
  def db_pg_backup(tag: nil, exclude_tables: [])
24
+ if ENV["DATABASE_URL"].blank?
25
+ Rails.logger.error("Failed to backup database: DATABASE_URL is not set")
26
+ return
27
+ end
28
+
14
29
  db_url = ENV["DATABASE_URL"]
15
30
  tag ||= exclude_tables.empty? ? "db_pg_full" : "db_pg_partial" # if tag.empty?
16
31
  self.tag = tag
@@ -1,13 +1,13 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title>Ops backups</title>
4
+ <title>Ops Backups</title>
5
5
  <%= csrf_meta_tags %>
6
6
  <%= csp_meta_tag %>
7
7
 
8
8
  <%= yield :head %>
9
9
 
10
- <%= stylesheet_link_tag "ops_backups/application", media: "all" %>
10
+ <%= stylesheet_link_tag "ops_backups/application", media: "all" %>
11
11
  </head>
12
12
  <body>
13
13
 
@@ -0,0 +1,111 @@
1
+ <style>
2
+ @keyframes fadeOut {
3
+ to {
4
+ opacity: 0;
5
+ }
6
+ }
7
+ /* all elements have a border */
8
+ ops-backups * {
9
+ /* border: 1px solid red; */
10
+ }
11
+ ops-backups {
12
+ display: flex;
13
+ padding: 0.5em;
14
+ flex-direction: column;
15
+ & h1 {
16
+ margin: 0;
17
+ margin-bottom: 0.25em;
18
+ }
19
+ & ops-backup {
20
+ display: grid;
21
+ grid-template-columns: 3em 1fr 1fr 6em 8em 16em 8em;
22
+ gap: 0.5em;
23
+ padding: 0.25em;
24
+ & ops-backup-index,
25
+ ops-backup-size,
26
+ ops-backup-duration,
27
+ ops-backup-created_at,
28
+ ops-backup-actions {
29
+ text-align: right;
30
+ }
31
+ & ops-backup-actions {
32
+ display: flex;
33
+ flex-direction: row;
34
+ justify-content: flex-end;
35
+ gap: 0.5em;
36
+ }
37
+ }
38
+ & ops-backup.header {
39
+ font-weight: bold;
40
+ border-bottom: 1px solid black;
41
+ margin-bottom: 0.25em;
42
+ }
43
+ & ops-backup-jobs {
44
+ display: flex;
45
+ gap: 0.5em;
46
+ margin-bottom: 0.5em;
47
+ /* padding: 0.5em; */
48
+ }
49
+ }
50
+ .alerts {
51
+ /* position these alerts at the bottom right */
52
+ position: fixed;
53
+ bottom: 1em;
54
+ right: 1em;
55
+ animation: fadeOut 3s forwards 3s;
56
+ display: flex;
57
+ flex-direction: column;
58
+ align-items: flex-end;
59
+ gap: 0.5em;
60
+ & .alert {
61
+ padding: 0.5em;
62
+ border-radius: 0.25em;
63
+ }
64
+ & .alert-notice {
65
+ background-color: lightgreen;
66
+ }
67
+ & .alert-danger {
68
+ background-color: lightcoral;
69
+ }
70
+ }
71
+ </style>
72
+ <ops-backups>
73
+ <h1>Backups</h1>
74
+ <% if @jobs.present? %>
75
+ <ops-backup-jobs>
76
+ <% @jobs.each do |name, config| %>
77
+ <%= button_to name, trigger_job_path(job_name: name), method: :post %>
78
+ <% end %>
79
+ </ops-backup-jobs>
80
+ <% end %>
81
+ <ops-backup class="header">
82
+ <ops-backup-index>Index</ops-backup-index>
83
+ <ops-backup-name>Name</ops-backup-name>
84
+ <ops-backup-tag>Tag</ops-backup-tag>
85
+ <ops-backup-size>Size</ops-backup-size>
86
+ <ops-backup-duration>Duration</ops-backup-duration>
87
+ <ops-backup-created_at>Created At</ops-backup-created_at>
88
+ <ops-backup-actions>Actions</ops-backup-actions>
89
+ </ops-backup>
90
+ <% @backups.each.with_index do |backup, i| %>
91
+ <ops-backup id="ops-backup-<%= i %>">
92
+ <ops-backup-index><%= i %></ops-backup-index>
93
+ <ops-backup-name><%= backup.name %></ops-backup-name>
94
+ <ops-backup-tag><%= backup.name %></ops-backup-tag>
95
+ <ops-backup-size><%= backup.size %></ops-backup-size>
96
+ <ops-backup-duration><%= backup.duration %></ops-backup-duration>
97
+ <ops-backup-created_at><%= backup.created_at %></ops-backup-created_at>
98
+ <ops-backup-actions>
99
+ <% if backup.backup_file.attached? %>
100
+ <%= button_to "📎", download_backup_path(backup), method: :get %>
101
+ <% end %>
102
+ <%= button_to "🗑️", backup_path(backup), method: :delete %>
103
+ </ops-backup-actions>
104
+ </ops-backup>
105
+ <% end %>
106
+ </ops-backups>
107
+ <div class="alerts">
108
+ <% flash.each do |key, value| %>
109
+ <div class="alert alert-<%= key %>"><%= value %></div>
110
+ <% end %>
111
+ </div>
data/config/routes.rb CHANGED
@@ -1,2 +1,6 @@
1
1
  OpsBackups::Engine.routes.draw do
2
+ get "backups", to: "backups#index"
3
+ get "backups/:id/download", to: "backups#download", as: "download_backup"
4
+ delete "backups/:id", to: "backups#destroy", as: "backup"
5
+ post "backups/trigger_job", to: "backups#trigger_job", as: "trigger_job"
2
6
  end
@@ -39,7 +39,7 @@ class OpsBackups::InstallGenerator < Rails::Generators::Base
39
39
  recurring_config = <<~YAML.lines.map { |line| " #{line}" }.join
40
40
  backup_db:
41
41
  class: OpsBackups::BackupDbJob
42
- args: [tag: "db_pg_backup", cleanup: "retain_tiered_cleanup_policy"]
42
+ args: [tag: "db_pg_backup", cleanup: "retain_tiered"]
43
43
  schedule: every hour
44
44
  YAML
45
45
 
@@ -1,3 +1,3 @@
1
1
  module OpsBackups
2
- VERSION = "0.1.11"
2
+ VERSION = "0.1.13"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ops_backups
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.11
4
+ version: 0.1.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koen Handekyn
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-28 00:00:00.000000000 Z
11
+ date: 2024-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -38,15 +38,16 @@ files:
38
38
  - Rakefile
39
39
  - app/assets/stylesheets/ops_backups/application.css
40
40
  - app/controllers/ops_backups/application_controller.rb
41
- - app/helpers/ops_backups/application_helper.rb
41
+ - app/controllers/ops_backups/backups_controller.rb
42
+ - app/helpers/ops_backups/backups_helper.rb
42
43
  - app/jobs/ops_backups/application_job.rb
43
44
  - app/jobs/ops_backups/backup_db_job.rb
44
45
  - app/jobs/ops_backups/cleanup_limit_job.rb
45
46
  - app/jobs/ops_backups/cleanup_tiered_job.rb
46
- - app/mailers/ops_backups/application_mailer.rb
47
47
  - app/models/ops_backups/application_record.rb
48
48
  - app/models/ops_backups/backup.rb
49
49
  - app/views/layouts/ops_backups/application.html.erb
50
+ - app/views/ops_backups/backups/index.html.erb
50
51
  - bin/pgdb
51
52
  - bin/pgdb.sh
52
53
  - config/locales/en.yml
@@ -1,6 +0,0 @@
1
- module OpsBackups
2
- class ApplicationMailer < ActionMailer::Base
3
- default from: "from@example.com"
4
- layout "mailer"
5
- end
6
- end