bard-api 0.5.0 → 0.6.0
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.
- checksums.yaml +4 -4
- data/README.md +9 -8
- data/lib/bard/api/app.rb +84 -7
- data/lib/bard/api/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ee48d71ea32a1a05c4a540cdab7b8127f94e9366cfcc259a829f2843d0ce8fe2
|
|
4
|
+
data.tar.gz: f3baa8c04d7acf644692c9cc5dbd6dc143504c22d1e4b8380134d4cb29217728
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0ca7e1d49a6dfac34bf2c55e01f6e2dbb4d287ca003301a371a092ae57e3ab4e62110d694ee8d9863053fe32fbff97bd2834338128c8b8afd47fafe88d4e36c7
|
|
7
|
+
data.tar.gz: 5b1167e219a79526de91876267bf769ad895c6aaf8cea1df2e5c6fe316307a05e33b29da3f5c39748037e5027b978d82b4da7e2647f10a6c301b535414fbae55
|
data/README.md
CHANGED
|
@@ -11,19 +11,20 @@ The bard-api gem enables BARD Tracker to manage Rails applications through a RES
|
|
|
11
11
|
|
|
12
12
|
## Usage
|
|
13
13
|
|
|
14
|
-
### Mounting
|
|
14
|
+
### Mounting
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
With `bard-rails` this mounts automatically at `/bard` — nothing to do. Otherwise add to your
|
|
17
|
+
`config/routes.rb`:
|
|
17
18
|
|
|
18
19
|
```ruby
|
|
19
|
-
mount Bard::Api::App.new => "/bard
|
|
20
|
+
mount Bard::Api::App.new => "/bard"
|
|
20
21
|
```
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
Either way the API is served at `/bard/*`.
|
|
23
24
|
|
|
24
25
|
### Endpoints
|
|
25
26
|
|
|
26
|
-
#### GET /bard
|
|
27
|
+
#### GET /bard/health
|
|
27
28
|
|
|
28
29
|
Health check endpoint (no authentication required).
|
|
29
30
|
|
|
@@ -34,7 +35,7 @@ Health check endpoint (no authentication required).
|
|
|
34
35
|
}
|
|
35
36
|
```
|
|
36
37
|
|
|
37
|
-
#### POST /bard
|
|
38
|
+
#### POST /bard/backups
|
|
38
39
|
|
|
39
40
|
Trigger a backup (requires JWT authentication).
|
|
40
41
|
|
|
@@ -67,7 +68,7 @@ Authorization: Bearer <jwt-token>
|
|
|
67
68
|
}
|
|
68
69
|
```
|
|
69
70
|
|
|
70
|
-
#### GET /bard
|
|
71
|
+
#### GET /bard/backups/latest
|
|
71
72
|
|
|
72
73
|
Get status of most recent backup (requires JWT authentication).
|
|
73
74
|
|
|
@@ -98,7 +99,7 @@ Authorization: Bearer <jwt-token>
|
|
|
98
99
|
}
|
|
99
100
|
```
|
|
100
101
|
|
|
101
|
-
#### GET /bard
|
|
102
|
+
#### GET /bard/config
|
|
102
103
|
|
|
103
104
|
Read the project's live backup and uptime configuration (requires JWT authentication). Used by axis to discover what each project actually has deployed.
|
|
104
105
|
|
data/lib/bard/api/app.rb
CHANGED
|
@@ -8,6 +8,35 @@ require "bard/backup"
|
|
|
8
8
|
module Bard
|
|
9
9
|
module Api
|
|
10
10
|
class App
|
|
11
|
+
class << self
|
|
12
|
+
attr_writer :backup_runner, :deploy_runner
|
|
13
|
+
|
|
14
|
+
# Runs the backup task out-of-band so the request returns immediately.
|
|
15
|
+
# Override to wire backups into a project's own job queue.
|
|
16
|
+
def backup_runner
|
|
17
|
+
@backup_runner ||= ->(task) { Thread.new { task.call } }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# bin/setup restarts Puma via `procsd restart` → `systemctl --user restart`, which
|
|
21
|
+
# SIGKILLs the app unit's whole cgroup. So the deploy can't run in this worker (or any
|
|
22
|
+
# plain child of it) — it'd be killed mid-deploy. We run it in its own systemd --user
|
|
23
|
+
# scope: a separate cgroup that survives the restart.
|
|
24
|
+
def deploy_runner
|
|
25
|
+
@deploy_runner ||= method(:spawn_detached_deploy)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def spawn_detached_deploy(command)
|
|
29
|
+
pid = Process.spawn(
|
|
30
|
+
"systemd-run", "--user", "--scope", "--collect", "--quiet", "bash", "-lc", command,
|
|
31
|
+
:in => "/dev/null", %i[out err] => ["log/bard-deploy.log", "a"],
|
|
32
|
+
)
|
|
33
|
+
Process.detach(pid)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
DEPLOY_LOCK = "tmp/bard-deploy.lock"
|
|
38
|
+
DEPLOY_COMMAND = "flock -n #{DEPLOY_LOCK} -c 'git pull --ff-only origin master && bin/setup'"
|
|
39
|
+
|
|
11
40
|
def call(env)
|
|
12
41
|
request = Rack::Request.new(env)
|
|
13
42
|
method = request.request_method
|
|
@@ -22,6 +51,8 @@ module Bard
|
|
|
22
51
|
latest_backup(request)
|
|
23
52
|
when ["GET", "/config"]
|
|
24
53
|
config(request)
|
|
54
|
+
when ["POST", "/deploy"]
|
|
55
|
+
create_deploy(request)
|
|
25
56
|
else
|
|
26
57
|
not_found
|
|
27
58
|
end
|
|
@@ -40,13 +71,30 @@ module Bard
|
|
|
40
71
|
s3 = payload["s3"].transform_keys(&:to_sym)
|
|
41
72
|
project_name = Bard::Config.current.project_name
|
|
42
73
|
|
|
43
|
-
|
|
44
|
-
type: :s3,
|
|
45
|
-
path: "bard-backup/#{project_name}",
|
|
46
|
-
**s3,
|
|
47
|
-
)
|
|
74
|
+
self.class.backup_runner.call(-> { run_backup(project_name, s3) })
|
|
48
75
|
|
|
49
|
-
json_response(
|
|
76
|
+
json_response(202, { status: "started" })
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def run_backup(project_name, s3)
|
|
81
|
+
Bard::Backup.create!(
|
|
82
|
+
type: :s3,
|
|
83
|
+
path: "bard-backup/#{project_name}",
|
|
84
|
+
**s3,
|
|
85
|
+
)
|
|
86
|
+
rescue => e
|
|
87
|
+
log_backup_error(e)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def log_backup_error(error)
|
|
91
|
+
message = "[bard-api] backup failed: #{error.class}: #{error.message}"
|
|
92
|
+
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
93
|
+
Rails.logger.error(message)
|
|
94
|
+
Rails.logger.error(error.backtrace.join("\n")) if error.backtrace
|
|
95
|
+
else
|
|
96
|
+
warn message
|
|
97
|
+
warn error.backtrace.join("\n") if error.backtrace
|
|
50
98
|
end
|
|
51
99
|
end
|
|
52
100
|
|
|
@@ -65,6 +113,35 @@ module Bard
|
|
|
65
113
|
end
|
|
66
114
|
end
|
|
67
115
|
|
|
116
|
+
# Unauthenticated on purpose: it can only fast-forward prod to origin/master (master IS prod)
|
|
117
|
+
# and is a no-op when already there.
|
|
118
|
+
#
|
|
119
|
+
# bin/setup restarts Puma, so we can't run the deploy in this worker thread — the restart
|
|
120
|
+
# would kill it mid-flight. We hand off to an out-of-band process (its own systemd scope,
|
|
121
|
+
# see .deploy_runner), serialize concurrent deploys with an flock, and return 202
|
|
122
|
+
# immediately. The caller polls until HEAD == sha.
|
|
123
|
+
def create_deploy(request)
|
|
124
|
+
target = JSON.parse(request.body.read)["sha"]
|
|
125
|
+
sha = current_sha
|
|
126
|
+
return json_response(200, { status: "noop", sha: sha }) if target == sha
|
|
127
|
+
return json_response(409, { status: "deploying", sha: sha }) if deploy_in_progress?
|
|
128
|
+
|
|
129
|
+
self.class.deploy_runner.call(DEPLOY_COMMAND)
|
|
130
|
+
json_response(202, { status: "deploying", sha: sha })
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def current_sha
|
|
134
|
+
`git rev-parse HEAD`.chomp
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def deploy_in_progress?
|
|
138
|
+
File.open(DEPLOY_LOCK, File::RDWR | File::CREAT, 0o644) do |lock|
|
|
139
|
+
acquired = lock.flock(File::LOCK_EX | File::LOCK_NB)
|
|
140
|
+
lock.flock(File::LOCK_UN) if acquired
|
|
141
|
+
!acquired
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
68
145
|
def serialize_config(bard_config)
|
|
69
146
|
backup = bard_config.backup
|
|
70
147
|
production = bard_config.targets[:production]
|
|
@@ -74,7 +151,7 @@ module Bard
|
|
|
74
151
|
enabled: backup.enabled?,
|
|
75
152
|
bard_managed: backup.bard?,
|
|
76
153
|
self_managed: backup.self_managed?,
|
|
77
|
-
encryption_enabled: !!
|
|
154
|
+
encryption_enabled: !!backup.encrypt,
|
|
78
155
|
destinations: backup.destinations.map { |d| { name: d[:name], type: d[:type] } },
|
|
79
156
|
},
|
|
80
157
|
servers: production ? { production: { pings: production.ping } } : {},
|
data/lib/bard/api/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bard-api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Micah Geisel
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-07-09 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: jwt
|
|
@@ -71,14 +71,14 @@ dependencies:
|
|
|
71
71
|
requirements:
|
|
72
72
|
- - ">="
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: '0.
|
|
74
|
+
version: '0.11'
|
|
75
75
|
type: :runtime
|
|
76
76
|
prerelease: false
|
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: '0.
|
|
81
|
+
version: '0.11'
|
|
82
82
|
- !ruby/object:Gem::Dependency
|
|
83
83
|
name: rack-test
|
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|