legion-data 1.10.28 → 1.11.7
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/.github/workflows/ci.yml +2 -2
- data/AGENTS.md +35 -2
- data/CHANGELOG.md +50 -0
- data/CLAUDE.md +1 -30
- data/Gemfile +3 -0
- data/README.md +92 -25
- data/legion-data.gemspec +1 -1
- data/lib/legion/data/archival/policy.rb +1 -16
- data/lib/legion/data/archival.rb +1 -1
- data/lib/legion/data/archiver.rb +2 -1
- data/lib/legion/data/audit_record.rb +18 -27
- data/lib/legion/data/connection.rb +40 -73
- data/lib/legion/data/database_lock.rb +3 -2
- data/lib/legion/data/event_store.rb +12 -13
- data/lib/legion/data/extract.rb +5 -7
- data/lib/legion/data/helper.rb +1 -1
- data/lib/legion/data/local.rb +9 -21
- data/lib/legion/data/migration.rb +24 -0
- data/lib/legion/data/migrations/139_create_task_logs.rb +27 -0
- data/lib/legion/data/model.rb +2 -21
- data/lib/legion/data/models/append_claim.rb +10 -0
- data/lib/legion/data/models/data_archive.rb +10 -0
- data/lib/legion/data/models/governance_event.rb +10 -0
- data/lib/legion/data/models/task.rb +5 -5
- data/lib/legion/data/models/task_log.rb +16 -0
- data/lib/legion/data/settings.rb +6 -9
- data/lib/legion/data/storage_tiers.rb +26 -20
- data/lib/legion/data/task/status.rb +36 -0
- data/lib/legion/data/version.rb +1 -1
- data/lib/legion/data.rb +4 -1
- metadata +8 -4
- data/lib/legion/data/connection_pool.rb +0 -83
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require 'legion/logging/helper'
|
|
4
4
|
require 'legion/data/auth_failure_handler'
|
|
5
|
-
require 'legion/data/connection_pool'
|
|
6
5
|
|
|
7
6
|
require 'fileutils'
|
|
8
7
|
require 'sequel'
|
|
@@ -166,52 +165,43 @@ module Legion
|
|
|
166
165
|
class << self
|
|
167
166
|
include Legion::Logging::Helper
|
|
168
167
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def sequel=(database)
|
|
172
|
-
if database
|
|
173
|
-
pool = database.pool
|
|
174
|
-
pool.extend(Legion::Data::ConnectionPool) unless pool.is_a?(Legion::Data::ConnectionPool)
|
|
175
|
-
end
|
|
176
|
-
Legion::Data.reset_privileges!
|
|
177
|
-
@sequel = database
|
|
178
|
-
end
|
|
168
|
+
attr_accessor :sequel
|
|
179
169
|
|
|
180
170
|
def adapter
|
|
181
|
-
@adapter
|
|
171
|
+
@adapter || Legion::Settings[:data][:adapter].to_sym
|
|
182
172
|
end
|
|
183
173
|
|
|
184
174
|
def setup
|
|
185
175
|
Legion::Settings[:data][:connected] = false
|
|
186
176
|
@sequel&.disconnect
|
|
187
177
|
@sequel = nil
|
|
188
|
-
@adapter = Legion::Settings[:data][:adapter]
|
|
178
|
+
@adapter = Legion::Settings[:data][:adapter].to_sym
|
|
189
179
|
opts = sequel_opts
|
|
190
180
|
log.info("Legion::Data::Connection setup adapter=#{adapter}")
|
|
191
181
|
@fallback_active = false
|
|
192
182
|
@replica_servers = []
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
183
|
+
@sequel = if adapter == :sqlite
|
|
184
|
+
::Sequel.connect(opts.merge(adapter: :sqlite, database: sqlite_path))
|
|
185
|
+
else
|
|
186
|
+
attempted_adapter = adapter
|
|
187
|
+
begin
|
|
188
|
+
::Sequel.connect(connection_opts_for(adapter: attempted_adapter, opts: opts))
|
|
189
|
+
rescue UnresolvedCredentialError => e
|
|
190
|
+
handle_exception(e, level: :fatal, handled: false, operation: :data_connect)
|
|
191
|
+
raise
|
|
192
|
+
rescue StandardError => e
|
|
193
|
+
raise unless dev_fallback?
|
|
194
|
+
|
|
195
|
+
log.error("Legion::Data FALLING BACK TO SQLITE — #{attempted_adapter} network DB connection failed: #{e.message}")
|
|
196
|
+
log.error("Legion::Data WARNING: Data written to SQLite will NOT be visible when #{attempted_adapter} reconnects. " \
|
|
197
|
+
'Apollo knowledge, audit logs, and other DB-backed services will use a local-only store.')
|
|
198
|
+
handle_exception(e, level: :error, handled: true, operation: :shared_connect, fallback: :sqlite)
|
|
199
|
+
@adapter = :sqlite
|
|
200
|
+
@fallback_active = true
|
|
201
|
+
sqlite_opts = sequel_opts
|
|
202
|
+
::Sequel.connect(sqlite_opts.merge(adapter: :sqlite, database: sqlite_path))
|
|
203
|
+
end
|
|
204
|
+
end
|
|
215
205
|
@replica_servers = @sequel.opts.fetch(:servers, {}).keys
|
|
216
206
|
log_connection_info
|
|
217
207
|
configure_extensions
|
|
@@ -230,8 +220,7 @@ module Legion
|
|
|
230
220
|
adapter: adapter,
|
|
231
221
|
connected: Legion::Settings[:data][:connected],
|
|
232
222
|
fallback_active: @fallback_active || false,
|
|
233
|
-
configured_adapter: Legion::Settings[:data][:adapter]
|
|
234
|
-
shutdown: @sequel&.pool&.shutdown_status,
|
|
223
|
+
configured_adapter: Legion::Settings[:data][:adapter].to_sym,
|
|
235
224
|
sequel_alive: (begin
|
|
236
225
|
!@sequel&.test_connection.nil?
|
|
237
226
|
rescue StandardError => e
|
|
@@ -298,26 +287,14 @@ module Legion
|
|
|
298
287
|
end
|
|
299
288
|
|
|
300
289
|
def shutdown
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
Legion::Settings[:data][:connected] = false
|
|
310
|
-
end
|
|
311
|
-
log.info 'Legion::Data connection closed'
|
|
312
|
-
end
|
|
313
|
-
unless database
|
|
314
|
-
on_closed.call
|
|
315
|
-
return true
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
closed = database.pool.shutdown(timeout: Legion::Settings[:data][:shutdown_timeout], &on_closed)
|
|
319
|
-
log.warn("Legion::Data shutdown incomplete: #{database.pool.shutdown_status}") unless closed
|
|
320
|
-
closed
|
|
290
|
+
@sequel&.disconnect
|
|
291
|
+
@query_file_logger&.close
|
|
292
|
+
@query_file_logger = nil
|
|
293
|
+
@fallback_active = false
|
|
294
|
+
@replica_servers = []
|
|
295
|
+
Legion::Settings[:data][:connected] = false
|
|
296
|
+
log.info 'Legion::Data connection closed'
|
|
297
|
+
true
|
|
321
298
|
end
|
|
322
299
|
|
|
323
300
|
def reconnect_with_fresh_creds
|
|
@@ -376,7 +353,7 @@ module Legion
|
|
|
376
353
|
return creds if adapter == :sqlite
|
|
377
354
|
return creds unless defined?(Legion::Crypt::TLS)
|
|
378
355
|
|
|
379
|
-
tls_settings =
|
|
356
|
+
tls_settings = Legion::Settings[:data][:tls]
|
|
380
357
|
return creds unless tls_settings[:enabled] == true
|
|
381
358
|
|
|
382
359
|
tls = Legion::Crypt::TLS.resolve(tls_settings, port: port)
|
|
@@ -424,15 +401,6 @@ module Legion
|
|
|
424
401
|
|
|
425
402
|
private
|
|
426
403
|
|
|
427
|
-
def data_tls_settings
|
|
428
|
-
return {} unless defined?(Legion::Settings)
|
|
429
|
-
|
|
430
|
-
Legion::Settings[:data][:tls] || {}
|
|
431
|
-
rescue StandardError => e
|
|
432
|
-
handle_exception(e, level: :warn, handled: true, operation: :data_tls_settings)
|
|
433
|
-
{}
|
|
434
|
-
end
|
|
435
|
-
|
|
436
404
|
def log_connection_info
|
|
437
405
|
if adapter == :sqlite
|
|
438
406
|
log.info "Connected to SQLite at #{sqlite_path}"
|
|
@@ -464,7 +432,7 @@ module Legion
|
|
|
464
432
|
end
|
|
465
433
|
|
|
466
434
|
def sqlite_path
|
|
467
|
-
path = Legion::Settings[:data][:creds][:database]
|
|
435
|
+
path = Legion::Settings[:data][:creds][:database]
|
|
468
436
|
return path if File.absolute_path?(path)
|
|
469
437
|
|
|
470
438
|
base_dir = File.expand_path('~/.legionio/data')
|
|
@@ -610,7 +578,7 @@ module Legion
|
|
|
610
578
|
stats[pragma.to_sym] = val unless val.nil?
|
|
611
579
|
end
|
|
612
580
|
|
|
613
|
-
db_path = Legion::Settings[:data][:creds][:database]
|
|
581
|
+
db_path = Legion::Settings[:data][:creds][:database]
|
|
614
582
|
stats[:file_size] = File.size(db_path) if File.exist?(db_path)
|
|
615
583
|
stats[:database_size_bytes] = (stats[:page_size].to_i * stats[:page_count].to_i) if stats[:page_size] && stats[:page_count]
|
|
616
584
|
stats
|
|
@@ -698,15 +666,14 @@ module Legion
|
|
|
698
666
|
|
|
699
667
|
install_auth_failure_hook
|
|
700
668
|
rescue StandardError => e
|
|
701
|
-
handle_exception(e, level: :
|
|
702
|
-
raise
|
|
669
|
+
handle_exception(e, level: :warn, handled: true, operation: :configure_extensions, adapter: adapter)
|
|
670
|
+
raise e
|
|
703
671
|
end
|
|
704
672
|
|
|
705
673
|
def install_auth_failure_hook
|
|
706
674
|
Legion::Data::AuthFailureHandler.install(@sequel)
|
|
707
675
|
rescue StandardError => e
|
|
708
|
-
handle_exception(e, level: :
|
|
709
|
-
raise
|
|
676
|
+
handle_exception(e, level: :warn, handled: true, operation: :install_auth_failure_hook)
|
|
710
677
|
end
|
|
711
678
|
|
|
712
679
|
def build_data_logger
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
module Legion
|
|
4
4
|
module Data
|
|
5
5
|
module DatabaseLock
|
|
6
|
-
def self.with_claim(
|
|
6
|
+
def self.with_claim(key:)
|
|
7
|
+
database = Legion::Data::Model::AppendClaim.db
|
|
7
8
|
options = { savepoint: true }
|
|
8
9
|
options[:mode] = :immediate if database.database_type == :sqlite
|
|
9
10
|
database.transaction(**options) do
|
|
10
|
-
claim =
|
|
11
|
+
claim = Legion::Data::Model::AppendClaim.where(key)
|
|
11
12
|
# InnoDB needs exclusive duplicate-key locks to avoid shared-lock upgrade deadlocks.
|
|
12
13
|
claim = claim.on_duplicate_key_update(*key.keys) if database.database_type == :mysql
|
|
13
14
|
claim.insert(key)
|
|
@@ -21,11 +21,10 @@ module Legion
|
|
|
21
21
|
def append(stream:, type:, data: {}, metadata: {})
|
|
22
22
|
return { error: 'db unavailable' } unless db_ready?
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
Legion::Data::DatabaseLock.with_claim(
|
|
26
|
-
|
|
27
|
-
records =
|
|
28
|
-
records = records.for_update unless conn.database_type == :sqlite
|
|
24
|
+
database = Legion::Data::Model::GovernanceEvent.db
|
|
25
|
+
Legion::Data::DatabaseLock.with_claim(key: { kind: 'event', stream_id: stream }) do
|
|
26
|
+
records = Legion::Data::Model::GovernanceEvent.where(stream_id: stream).order(Sequel.desc(:sequence_number))
|
|
27
|
+
records = records.for_update unless database.database_type == :sqlite
|
|
29
28
|
last = records.first
|
|
30
29
|
|
|
31
30
|
seq = (last&.[](:sequence_number) || 0) + 1
|
|
@@ -35,7 +34,7 @@ module Legion
|
|
|
35
34
|
metadata_json = Legion::JSON.dump(metadata)
|
|
36
35
|
event_hash = compute_hash(stream, seq, type, data_json, metadata_json, prev_hash)
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
Legion::Data::Model::GovernanceEvent.create(
|
|
39
38
|
stream_id: stream,
|
|
40
39
|
event_type: type,
|
|
41
40
|
sequence_number: seq,
|
|
@@ -54,7 +53,7 @@ module Legion
|
|
|
54
53
|
def read_stream(stream, since: nil)
|
|
55
54
|
return [] unless db_ready?
|
|
56
55
|
|
|
57
|
-
ds = Legion::Data.
|
|
56
|
+
ds = Legion::Data::Model::GovernanceEvent.where(stream_id: stream)
|
|
58
57
|
ds = ds.where { created_at >= since } if since
|
|
59
58
|
ds.order(:sequence_number).all.map { |e| deserialize(e) }
|
|
60
59
|
end
|
|
@@ -62,7 +61,7 @@ module Legion
|
|
|
62
61
|
def read_by_type(type, since: nil, limit: 100)
|
|
63
62
|
return [] unless db_ready?
|
|
64
63
|
|
|
65
|
-
ds = Legion::Data.
|
|
64
|
+
ds = Legion::Data::Model::GovernanceEvent.where(event_type: type)
|
|
66
65
|
ds = ds.where { created_at >= since } if since
|
|
67
66
|
ds.order(Sequel.desc(:created_at)).limit(limit).all.map { |e| deserialize(e) }
|
|
68
67
|
end
|
|
@@ -70,10 +69,10 @@ module Legion
|
|
|
70
69
|
def verify_chain(stream)
|
|
71
70
|
return { valid: false, error: 'db unavailable' } unless db_ready?
|
|
72
71
|
|
|
73
|
-
events = Legion::Data
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
events = Legion::Data::Model::GovernanceEvent
|
|
73
|
+
.where(stream_id: stream)
|
|
74
|
+
.order(:sequence_number)
|
|
75
|
+
.all
|
|
77
76
|
|
|
78
77
|
prev_hash = '0' * 64
|
|
79
78
|
legacy_hashes = 0
|
|
@@ -129,7 +128,7 @@ module Legion
|
|
|
129
128
|
end
|
|
130
129
|
|
|
131
130
|
def db_ready?
|
|
132
|
-
|
|
131
|
+
Legion::Data.connected? && Legion::Data::Model::GovernanceEvent.db.table_exists?(:governance_events)
|
|
133
132
|
rescue StandardError => e
|
|
134
133
|
handle_exception(e, level: :warn, handled: true, operation: :event_store_db_ready?)
|
|
135
134
|
false
|
data/lib/legion/data/extract.rb
CHANGED
|
@@ -102,16 +102,14 @@ module Legion
|
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
def persist_step_timings(extract_id, timings)
|
|
105
|
-
return unless
|
|
105
|
+
return unless Legion::Data.connected?
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
existing_steps = Legion::Data::Model::ExtractStepTiming.where(extract_id: extract_id).select_map(:name)
|
|
108
|
+
timings.each do |timing|
|
|
109
|
+
next if existing_steps.include?(timing[:name])
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
rows = timings.reject { |timing| existing_steps.include?(timing[:name]) }.map do |timing|
|
|
112
|
-
timing.merge(extract_id: extract_id)
|
|
111
|
+
Legion::Data::Model::ExtractStepTiming.create(timing.merge(extract_id: extract_id))
|
|
113
112
|
end
|
|
114
|
-
connection[:extract_step_timings].multi_insert(rows) unless rows.empty?
|
|
115
113
|
rescue StandardError => e
|
|
116
114
|
handle_exception(e, level: :warn, handled: true, operation: :persist_extract_step_timings,
|
|
117
115
|
extract_id: extract_id)
|
data/lib/legion/data/helper.rb
CHANGED
data/lib/legion/data/local.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Legion
|
|
|
17
17
|
def setup(database: nil, **)
|
|
18
18
|
return if @connected
|
|
19
19
|
|
|
20
|
-
db_file = database ||
|
|
20
|
+
db_file = database || Legion::Settings[:data][:local][:database]
|
|
21
21
|
unless File.absolute_path?(db_file)
|
|
22
22
|
base_dir = File.expand_path('~/.legionio')
|
|
23
23
|
FileUtils.mkdir_p(base_dir)
|
|
@@ -26,19 +26,19 @@ module Legion
|
|
|
26
26
|
@db_path = db_file
|
|
27
27
|
|
|
28
28
|
sqlite_defaults = Legion::Data::Connection::ADAPTER_DEFAULTS.fetch(:sqlite, {})
|
|
29
|
-
data =
|
|
29
|
+
data = Legion::Settings[:data]
|
|
30
30
|
opts = { adapter: :sqlite, database: db_file }
|
|
31
31
|
Legion::Data::Connection::ADAPTER_KEYS.fetch(:sqlite, []).each do |key|
|
|
32
32
|
val = data.key?(key) && !data[key].nil? ? data[key] : sqlite_defaults[key]
|
|
33
33
|
opts[key] = val unless val.nil?
|
|
34
34
|
end
|
|
35
|
-
opts[:timeout] = data[:timeout]
|
|
35
|
+
opts[:timeout] = data[:timeout].nil? ? 30_000 : data[:timeout]
|
|
36
36
|
opts[:after_connect] = proc do |connection|
|
|
37
37
|
Legion::Data::Connection.configure_sqlite_busy_handler(connection: connection, timeout: opts[:timeout])
|
|
38
38
|
connection.execute_batch('PRAGMA synchronous=NORMAL; PRAGMA cache_size=-20000; PRAGMA mmap_size=268435456')
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
if
|
|
41
|
+
if Legion::Settings[:data][:local][:query_log]
|
|
42
42
|
log_path = File.join(Legion::Data::Connection::QUERY_LOG_DIR, 'data-local-query.log')
|
|
43
43
|
@query_file_logger = Legion::Data::Connection::QueryFileLogger.new(log_path)
|
|
44
44
|
opts[:logger] = @query_file_logger
|
|
@@ -99,7 +99,7 @@ module Legion
|
|
|
99
99
|
connected: true,
|
|
100
100
|
adapter: :sqlite,
|
|
101
101
|
path: @db_path,
|
|
102
|
-
query_log:
|
|
102
|
+
query_log: Legion::Settings[:data][:local][:query_log],
|
|
103
103
|
query_log_path: @query_file_logger&.path,
|
|
104
104
|
registered_migrations: registered_migrations.keys
|
|
105
105
|
}
|
|
@@ -135,7 +135,7 @@ module Legion
|
|
|
135
135
|
private
|
|
136
136
|
|
|
137
137
|
def run_migrations
|
|
138
|
-
return unless
|
|
138
|
+
return unless Legion::Settings[:data][:local][:migrations][:auto_migrate]
|
|
139
139
|
|
|
140
140
|
registered_migrations.each do |name, path|
|
|
141
141
|
run_single_migration(name, path)
|
|
@@ -143,7 +143,7 @@ module Legion
|
|
|
143
143
|
end
|
|
144
144
|
|
|
145
145
|
def run_single_migration(name, path)
|
|
146
|
-
return unless
|
|
146
|
+
return unless Legion::Settings[:data][:local][:migrations][:auto_migrate]
|
|
147
147
|
return unless File.directory?(path)
|
|
148
148
|
|
|
149
149
|
table = :"schema_migrations_#{name}"
|
|
@@ -153,12 +153,6 @@ module Legion
|
|
|
153
153
|
raise
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
-
def local_settings
|
|
157
|
-
return {} unless defined?(Legion::Settings)
|
|
158
|
-
|
|
159
|
-
Legion::Settings[:data]&.dig(:local) || {}
|
|
160
|
-
end
|
|
161
|
-
|
|
162
156
|
def build_local_logger
|
|
163
157
|
tagged = if defined?(Legion::Logging::TaggedLogger) && respond_to?(:tagged_logger_settings, true)
|
|
164
158
|
Legion::Logging::TaggedLogger.new(
|
|
@@ -181,17 +175,11 @@ module Legion
|
|
|
181
175
|
end
|
|
182
176
|
|
|
183
177
|
def resolved_sql_log_level
|
|
184
|
-
|
|
185
|
-
rescue StandardError => e
|
|
186
|
-
handle_exception(e, level: :warn, handled: true, operation: :resolved_sql_log_level)
|
|
187
|
-
:debug
|
|
178
|
+
Legion::Settings[:data][:local].fetch(:sql_log_level, Legion::Settings[:data][:sql_log_level]).to_sym
|
|
188
179
|
end
|
|
189
180
|
|
|
190
181
|
def resolved_log_warn_duration
|
|
191
|
-
|
|
192
|
-
rescue StandardError => e
|
|
193
|
-
handle_exception(e, level: :warn, handled: true, operation: :resolved_log_warn_duration)
|
|
194
|
-
nil
|
|
182
|
+
Legion::Settings[:data][:local].fetch(:log_warn_duration, Legion::Settings[:data][:log_warn_duration])
|
|
195
183
|
end
|
|
196
184
|
end
|
|
197
185
|
end
|
|
@@ -16,6 +16,30 @@ module Legion
|
|
|
16
16
|
return
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
if connection.database_type == :postgres
|
|
20
|
+
role_state = connection.fetch(<<~SQL).first
|
|
21
|
+
SELECT session_user,
|
|
22
|
+
current_user,
|
|
23
|
+
current_setting('role') AS active_role,
|
|
24
|
+
to_regrole('legionio_migrator')::text AS migrator_role,
|
|
25
|
+
CASE
|
|
26
|
+
WHEN to_regrole('legionio_migrator') IS NULL THEN NULL
|
|
27
|
+
ELSE pg_has_role(session_user, 'legionio_migrator', 'MEMBER')
|
|
28
|
+
END AS migrator_member,
|
|
29
|
+
CASE
|
|
30
|
+
WHEN to_regrole('legionio_migrator') IS NULL THEN NULL
|
|
31
|
+
ELSE pg_has_role(session_user, 'legionio_migrator', 'SET')
|
|
32
|
+
END AS migrator_set,
|
|
33
|
+
has_schema_privilege(session_user, 'public', 'CREATE') AS session_create,
|
|
34
|
+
has_schema_privilege(current_user, 'public', 'CREATE') AS current_create,
|
|
35
|
+
CASE
|
|
36
|
+
WHEN to_regrole('legionio_migrator') IS NULL THEN NULL
|
|
37
|
+
ELSE has_schema_privilege('legionio_migrator', 'public', 'CREATE')
|
|
38
|
+
END AS migrator_create
|
|
39
|
+
SQL
|
|
40
|
+
Legion::Logging.unknown "migration role state=#{role_state.inspect}"
|
|
41
|
+
end
|
|
42
|
+
|
|
19
43
|
Legion::Settings[:data][:migrations][:version] = Sequel::Migrator.run(connection, path, **)
|
|
20
44
|
log.info("Legion::Data::Migration ran successfully to version #{Legion::Settings[:data][:migrations][:version]}")
|
|
21
45
|
Legion::Settings[:data][:migrations][:ran] = true
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
change do
|
|
5
|
+
create_table(:task_logs) do
|
|
6
|
+
primary_key :id
|
|
7
|
+
String :uuid, size: 36, null: false, unique: true
|
|
8
|
+
String :access_scope, size: 20, null: false, default: 'global', index: true
|
|
9
|
+
foreign_key :identity_principal_id, :identity_principals, null: true, on_delete: :set_null, on_update: :cascade
|
|
10
|
+
foreign_key :identity_id, :identities, null: true, on_delete: :set_null, on_update: :cascade
|
|
11
|
+
String :identity_canonical_name, size: 255, null: true, index: true
|
|
12
|
+
foreign_key :task_id, :tasks, null: true, on_delete: :set_null, on_update: :cascade
|
|
13
|
+
foreign_key :node_id, :nodes, null: true, on_delete: :set_null, on_update: :cascade
|
|
14
|
+
foreign_key :function_id, :functions, null: true, on_delete: :set_null, on_update: :cascade
|
|
15
|
+
String :entry, text: true, null: false
|
|
16
|
+
DateTime :created, null: false, default: Sequel::CURRENT_TIMESTAMP
|
|
17
|
+
DateTime :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
|
18
|
+
DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
|
19
|
+
DateTime :updated_at, null: true
|
|
20
|
+
index :identity_principal_id
|
|
21
|
+
index :task_id
|
|
22
|
+
index :node_id
|
|
23
|
+
index :function_id
|
|
24
|
+
index :created
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/legion/data/model.rb
CHANGED
|
@@ -11,9 +11,9 @@ module Legion
|
|
|
11
11
|
attr_reader :loaded_models
|
|
12
12
|
|
|
13
13
|
def models
|
|
14
|
-
%w[extension function relationship chain task runner node setting digital_worker
|
|
14
|
+
%w[extension function relationship chain task task_log runner node setting digital_worker
|
|
15
15
|
apollo_entry apollo_relation apollo_expertise apollo_access_log audit_log
|
|
16
|
-
audit_record extract_step_timing
|
|
16
|
+
audit_record governance_event append_claim data_archive extract_step_timing
|
|
17
17
|
identity/identity identity/principal identity/providers identity/group
|
|
18
18
|
identity/group_memberships identity/audit_log
|
|
19
19
|
apollo/entries apollo/relation apollo/access_log apollo/expertise
|
|
@@ -30,10 +30,7 @@ module Legion
|
|
|
30
30
|
def load
|
|
31
31
|
log.info 'Loading Legion::Data::Models'
|
|
32
32
|
@loaded_models ||= []
|
|
33
|
-
database = Legion::Data::Connection.sequel
|
|
34
|
-
Sequel::Model.db = database
|
|
35
33
|
require_sequel_models(models)
|
|
36
|
-
bind_database(namespace: Legion::Data::Model, database: database)
|
|
37
34
|
Legion::Settings[:data][:models][:loaded] = true
|
|
38
35
|
end
|
|
39
36
|
|
|
@@ -52,22 +49,6 @@ module Legion
|
|
|
52
49
|
handle_exception(e, level: :fatal, operation: :load_sequel_model, model: model)
|
|
53
50
|
raise e unless Legion::Settings[:data][:models][:continue_on_load_fail]
|
|
54
51
|
end
|
|
55
|
-
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
def bind_database(namespace:, database:)
|
|
59
|
-
namespace.constants(false).each do |name|
|
|
60
|
-
value = namespace.const_get(name, false)
|
|
61
|
-
if value.is_a?(Class) && value < Sequel::Model
|
|
62
|
-
next if value.db.equal?(database)
|
|
63
|
-
|
|
64
|
-
value.set_dataset(database[value.table_name])
|
|
65
|
-
value.association_reflections.each_value { |reflection| reflection[:cache]&.clear }
|
|
66
|
-
elsif value.is_a?(Module)
|
|
67
|
-
bind_database(namespace: value, database: database)
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
52
|
end
|
|
72
53
|
end
|
|
73
54
|
end
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
require 'digest'
|
|
4
4
|
require 'legion/json'
|
|
5
|
+
require 'legion/data/task/status'
|
|
5
6
|
require 'time'
|
|
6
7
|
|
|
7
8
|
module Legion
|
|
8
9
|
module Data
|
|
9
10
|
module Model
|
|
10
11
|
class Task < Sequel::Model
|
|
11
|
-
TERMINAL_STATUSES =
|
|
12
|
-
|
|
13
|
-
].freeze
|
|
12
|
+
TERMINAL_STATUSES = (Legion::Data::Task::Status::TERMINAL_STATUSES +
|
|
13
|
+
Legion::Data::Task::Status::LEGACY_TERMINAL_STATUSES).freeze
|
|
14
14
|
|
|
15
15
|
many_to_one :function
|
|
16
16
|
many_to_one :relationship
|
|
@@ -60,9 +60,9 @@ module Legion
|
|
|
60
60
|
when Array
|
|
61
61
|
value.map { |item| canonical_payload(item) }
|
|
62
62
|
when Time
|
|
63
|
-
value.
|
|
63
|
+
value.getutc.iso8601(6)
|
|
64
64
|
when DateTime
|
|
65
|
-
value.to_time.
|
|
65
|
+
value.to_time.getutc.iso8601(6)
|
|
66
66
|
else
|
|
67
67
|
value
|
|
68
68
|
end
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Data
|
|
5
7
|
module Model
|
|
6
8
|
class TaskLog < Sequel::Model
|
|
7
9
|
many_to_one :task
|
|
8
10
|
many_to_one :node
|
|
11
|
+
many_to_one :function
|
|
12
|
+
|
|
13
|
+
def self.insert(values)
|
|
14
|
+
create(values).id
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def before_create
|
|
18
|
+
self[:uuid] ||= SecureRandom.uuid
|
|
19
|
+
self[:created] ||= Time.now
|
|
20
|
+
self[:created] = self[:created].getlocal if self[:created].is_a?(Time)
|
|
21
|
+
self[:created_at] ||= self[:created]
|
|
22
|
+
self[:created_at] = self[:created_at].getlocal if self[:created_at].is_a?(Time)
|
|
23
|
+
super
|
|
24
|
+
end
|
|
9
25
|
end
|
|
10
26
|
end
|
|
11
27
|
end
|
data/lib/legion/data/settings.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'legion/logging/helper'
|
|
4
|
+
require 'legion/settings'
|
|
4
5
|
|
|
5
6
|
module Legion
|
|
6
7
|
module Data
|
|
@@ -76,10 +77,6 @@ module Legion
|
|
|
76
77
|
keepalives_count: 3,
|
|
77
78
|
tcp_user_timeout: 15_000,
|
|
78
79
|
|
|
79
|
-
# Shutdown: max seconds to wait for checked-out connections to return before
|
|
80
|
-
# force-disconnecting them.
|
|
81
|
-
shutdown_timeout: 5,
|
|
82
|
-
|
|
83
80
|
# Adapter-specific (nil = use adapter built-in default)
|
|
84
81
|
connect_timeout: nil,
|
|
85
82
|
read_timeout: nil,
|
|
@@ -95,6 +92,7 @@ module Legion
|
|
|
95
92
|
|
|
96
93
|
# Grouped settings
|
|
97
94
|
creds: creds,
|
|
95
|
+
tls: { enabled: false },
|
|
98
96
|
cache: cache,
|
|
99
97
|
migrations: migrations,
|
|
100
98
|
models: models,
|
|
@@ -141,7 +139,10 @@ module Legion
|
|
|
141
139
|
def self.archival
|
|
142
140
|
{
|
|
143
141
|
retention_days: 90,
|
|
142
|
+
warm_after_days: 7,
|
|
143
|
+
cold_after_days: 90,
|
|
144
144
|
batch_size: 1000,
|
|
145
|
+
tables: %w[tasks metering_records],
|
|
145
146
|
storage_backend: nil
|
|
146
147
|
}
|
|
147
148
|
end
|
|
@@ -158,8 +159,4 @@ module Legion
|
|
|
158
159
|
end
|
|
159
160
|
end
|
|
160
161
|
|
|
161
|
-
|
|
162
|
-
Legion::Settings.merge_settings('data', Legion::Data::Settings.default) if Legion.const_defined?('Settings', false)
|
|
163
|
-
rescue StandardError => e
|
|
164
|
-
Legion::Data::Settings.handle_exception(e, level: :fatal, operation: :merge_settings)
|
|
165
|
-
end
|
|
162
|
+
Legion::Settings.register_library(:data, Legion::Data::Settings.default)
|