textus 0.14.4 → 0.18.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/ARCHITECTURE.md +14 -14
- data/CHANGELOG.md +378 -0
- data/README.md +13 -9
- data/SPEC.md +13 -10
- data/docs/conventions.md +11 -0
- data/lib/textus/application/context.rb +25 -7
- data/lib/textus/application/reads/audit.rb +1 -1
- data/lib/textus/application/reads/blame.rb +3 -1
- data/lib/textus/application/reads/deps.rb +1 -1
- data/lib/textus/application/reads/freshness.rb +12 -3
- data/lib/textus/application/reads/get.rb +38 -33
- data/lib/textus/application/reads/get_or_refresh.rb +51 -0
- data/lib/textus/application/reads/list.rb +3 -1
- data/lib/textus/application/reads/published.rb +1 -1
- data/lib/textus/application/reads/rdeps.rb +1 -1
- data/lib/textus/application/reads/schema_envelope.rb +3 -1
- data/lib/textus/application/reads/stale.rb +1 -1
- data/lib/textus/application/reads/uid.rb +1 -1
- data/lib/textus/application/reads/validate_all.rb +6 -1
- data/lib/textus/application/reads/validator.rb +84 -0
- data/lib/textus/application/reads/where.rb +4 -1
- data/lib/textus/application/refresh/all.rb +8 -1
- data/lib/textus/application/refresh/orchestrator.rb +11 -3
- data/lib/textus/application/refresh/worker.rb +27 -20
- data/lib/textus/application/writes/accept.rb +12 -12
- data/lib/textus/application/writes/build.rb +3 -4
- data/lib/textus/application/writes/delete.rb +10 -15
- data/lib/textus/application/writes/envelope_io.rb +106 -0
- data/lib/textus/application/writes/mv.rb +25 -27
- data/lib/textus/application/writes/publish.rb +8 -9
- data/lib/textus/application/writes/put.rb +12 -16
- data/lib/textus/application/writes/reject.rb +10 -10
- data/lib/textus/builder/pipeline.rb +8 -1
- data/lib/textus/cli/group/hook.rb +1 -3
- data/lib/textus/cli/group/key.rb +1 -4
- data/lib/textus/cli/group/refresh.rb +1 -2
- data/lib/textus/cli/group/rule.rb +1 -3
- data/lib/textus/cli/group/schema.rb +1 -5
- data/lib/textus/cli/group.rb +12 -16
- data/lib/textus/cli/verb/accept.rb +3 -1
- data/lib/textus/cli/verb/audit.rb +3 -1
- data/lib/textus/cli/verb/blame.rb +3 -1
- data/lib/textus/cli/verb/build.rb +4 -2
- data/lib/textus/cli/verb/delete.rb +3 -1
- data/lib/textus/cli/verb/deps.rb +3 -1
- data/lib/textus/cli/verb/doctor.rb +2 -0
- data/lib/textus/cli/verb/freshness.rb +3 -1
- data/lib/textus/cli/verb/get.rb +3 -1
- data/lib/textus/cli/verb/hook_run.rb +3 -0
- data/lib/textus/cli/verb/hooks.rb +3 -0
- data/lib/textus/cli/verb/init.rb +2 -0
- data/lib/textus/cli/verb/intro.rb +2 -0
- data/lib/textus/cli/verb/key_normalize.rb +3 -0
- data/lib/textus/cli/verb/list.rb +3 -1
- data/lib/textus/cli/verb/mv.rb +4 -1
- data/lib/textus/cli/verb/published.rb +3 -1
- data/lib/textus/cli/verb/put.rb +3 -1
- data/lib/textus/cli/verb/rdeps.rb +3 -1
- data/lib/textus/cli/verb/refresh.rb +1 -1
- data/lib/textus/cli/verb/refresh_stale.rb +4 -1
- data/lib/textus/cli/verb/reject.rb +3 -1
- data/lib/textus/cli/verb/rule_explain.rb +4 -1
- data/lib/textus/cli/verb/rule_list.rb +3 -0
- data/lib/textus/cli/verb/schema.rb +4 -1
- data/lib/textus/cli/verb/schema_diff.rb +3 -0
- data/lib/textus/cli/verb/schema_init.rb +3 -0
- data/lib/textus/cli/verb/schema_migrate.rb +3 -0
- data/lib/textus/cli/verb/uid.rb +4 -1
- data/lib/textus/cli/verb/where.rb +3 -1
- data/lib/textus/cli/verb.rb +30 -0
- data/lib/textus/cli.rb +40 -35
- data/lib/textus/doctor/check/audit_log.rb +1 -1
- data/lib/textus/doctor/check/hooks.rb +3 -1
- data/lib/textus/doctor/check/intake_registration.rb +3 -3
- data/lib/textus/doctor/check/schema_violations.rb +1 -1
- data/lib/textus/doctor/check/sentinels.rb +2 -2
- data/lib/textus/domain/freshness/evaluator.rb +1 -1
- data/lib/textus/domain/freshness/policy.rb +1 -1
- data/lib/textus/domain/freshness/verdict.rb +1 -1
- data/lib/textus/domain/freshness.rb +40 -0
- data/lib/textus/domain/policy/predicates/schema_valid.rb +2 -2
- data/lib/textus/{store → domain}/sentinel.rb +1 -1
- data/lib/textus/{store → domain}/staleness/generator_check.rb +1 -1
- data/lib/textus/{store → domain}/staleness/intake_check.rb +1 -1
- data/lib/textus/{store → domain}/staleness.rb +1 -1
- data/lib/textus/entry/json.rb +1 -1
- data/lib/textus/entry/markdown.rb +1 -1
- data/lib/textus/entry/yaml.rb +1 -1
- data/lib/textus/envelope.rb +7 -3
- data/lib/textus/errors.rb +19 -0
- data/lib/textus/hooks/builtin.rb +6 -6
- data/lib/textus/hooks/dispatcher.rb +17 -9
- data/lib/textus/hooks/loader.rb +20 -17
- data/lib/textus/hooks/registry.rb +4 -0
- data/lib/textus/{store → infra}/audit_log.rb +1 -1
- data/lib/textus/infra/audit_subscriber.rb +43 -0
- data/lib/textus/infra/publisher.rb +3 -3
- data/lib/textus/infra/storage/file_store.rb +26 -0
- data/lib/textus/init.rb +11 -9
- data/lib/textus/manifest/resolution.rb +5 -0
- data/lib/textus/manifest.rb +4 -3
- data/lib/textus/migrate_keys.rb +1 -1
- data/lib/textus/operations.rb +84 -17
- data/lib/textus/projection.rb +16 -11
- data/lib/textus/refresh.rb +1 -1
- data/lib/textus/schema/tools.rb +5 -5
- data/lib/textus/schemas.rb +46 -0
- data/lib/textus/store.rb +12 -49
- data/lib/textus/uid.rb +18 -0
- data/lib/textus/version.rb +1 -1
- data/lib/textus.rb +17 -1
- metadata +15 -13
- data/lib/textus/hooks/dsl.rb +0 -11
- data/lib/textus/operations/reads.rb +0 -39
- data/lib/textus/operations/refresh.rb +0 -27
- data/lib/textus/operations/writes.rb +0 -21
- data/lib/textus/store/reader.rb +0 -69
- data/lib/textus/store/validator.rb +0 -82
- data/lib/textus/store/writer.rb +0 -102
data/lib/textus/store.rb
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
require "fileutils"
|
|
2
|
-
require "securerandom"
|
|
3
2
|
|
|
4
3
|
module Textus
|
|
5
4
|
class Store
|
|
6
|
-
attr_reader :root, :manifest, :
|
|
7
|
-
|
|
8
|
-
# A Textus UID: 16 lowercase hex chars (SecureRandom.hex(8)). Not a UUID —
|
|
9
|
-
# short on purpose. Random enough for collision-never-in-practice within a
|
|
10
|
-
# single store.
|
|
11
|
-
def self.mint_uid
|
|
12
|
-
SecureRandom.hex(8)
|
|
13
|
-
end
|
|
5
|
+
attr_reader :root, :manifest, :schemas, :file_store, :audit_log, :bus, :registry
|
|
14
6
|
|
|
15
7
|
def self.discover(start_dir = Dir.pwd, root: nil)
|
|
16
8
|
explicit = root || ENV.fetch("TEXTUS_ROOT", nil)
|
|
@@ -37,46 +29,17 @@ module Textus
|
|
|
37
29
|
end
|
|
38
30
|
|
|
39
31
|
def initialize(root)
|
|
40
|
-
@root
|
|
41
|
-
@manifest
|
|
42
|
-
@
|
|
43
|
-
@
|
|
44
|
-
@
|
|
45
|
-
|
|
46
|
-
@
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def load_hooks
|
|
52
|
-
Textus.with_registry(@registry) do
|
|
53
|
-
Hooks::Builtin.register_all
|
|
54
|
-
dir = File.join(@root, "hooks")
|
|
55
|
-
return unless File.directory?(dir)
|
|
56
|
-
|
|
57
|
-
Dir.glob(File.join(dir, "**/*.rb")).sort.each do |f| # rubocop:disable Lint/RedundantDirGlobSort
|
|
58
|
-
begin
|
|
59
|
-
load(f)
|
|
60
|
-
rescue StandardError, ScriptError => e
|
|
61
|
-
raise UsageError.new("failed loading hook #{File.basename(f)}: #{e.class}: #{e.message}")
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def schema_for(name)
|
|
68
|
-
return nil if name.nil?
|
|
69
|
-
|
|
70
|
-
@schemas[name] ||= begin
|
|
71
|
-
sp = File.join(@root, "schemas", "#{name}.yaml")
|
|
72
|
-
raise IoError.new("schema not found: #{sp}") unless File.exist?(sp)
|
|
73
|
-
|
|
74
|
-
Schema.load(sp)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def audit_log
|
|
79
|
-
@audit_log ||= Store::AuditLog.new(@root)
|
|
32
|
+
@root = File.expand_path(root)
|
|
33
|
+
@manifest = Manifest.load(@root)
|
|
34
|
+
@schemas = Schemas.new(File.join(@root, "schemas"))
|
|
35
|
+
@file_store = Infra::Storage::FileStore.new
|
|
36
|
+
@audit_log = Infra::AuditLog.new(@root)
|
|
37
|
+
@bus = Hooks::Dispatcher.new
|
|
38
|
+
@registry = Hooks::Registry.new(dispatcher: @bus)
|
|
39
|
+
Infra::AuditSubscriber.new(@audit_log).attach(@bus)
|
|
40
|
+
Hooks::Builtin.register_all(@registry)
|
|
41
|
+
Hooks::Loader.new(registry: @registry).load_dir(File.join(@root, "hooks"))
|
|
42
|
+
@bus.publish(:store_loaded, store: Application::Context.system(self))
|
|
80
43
|
end
|
|
81
44
|
end
|
|
82
45
|
end
|
data/lib/textus/uid.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "securerandom"
|
|
2
|
+
|
|
3
|
+
module Textus
|
|
4
|
+
# A Textus UID: 16 lowercase hex chars (SecureRandom.hex(8)). Not a UUID —
|
|
5
|
+
# short on purpose. Random enough for collision-never-in-practice within a
|
|
6
|
+
# single store.
|
|
7
|
+
module Uid
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def mint
|
|
11
|
+
SecureRandom.hex(8)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def valid?(str)
|
|
15
|
+
str.is_a?(String) && str.match?(/\A[0-9a-f]{16}\z/)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/textus/version.rb
CHANGED
data/lib/textus.rb
CHANGED
|
@@ -8,11 +8,27 @@ loader.inflector.inflect(
|
|
|
8
8
|
"json" => "Json",
|
|
9
9
|
"yaml" => "Yaml",
|
|
10
10
|
"hook_dsl_scanner" => "HookDSLScanner",
|
|
11
|
+
"envelope_io" => "EnvelopeIO",
|
|
11
12
|
)
|
|
12
13
|
loader.ignore(File.expand_path("textus/errors.rb", __dir__))
|
|
13
14
|
loader.setup
|
|
14
15
|
loader.eager_load
|
|
15
16
|
|
|
16
17
|
module Textus
|
|
17
|
-
|
|
18
|
+
@hook_mutex = Mutex.new
|
|
19
|
+
@hook_blocks = []
|
|
20
|
+
|
|
21
|
+
def self.hook(&blk)
|
|
22
|
+
raise UsageError.new("hook block required") unless blk
|
|
23
|
+
|
|
24
|
+
@hook_mutex.synchronize { @hook_blocks << blk }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.drain_hook_blocks
|
|
28
|
+
@hook_mutex.synchronize do
|
|
29
|
+
blocks = @hook_blocks
|
|
30
|
+
@hook_blocks = []
|
|
31
|
+
blocks
|
|
32
|
+
end
|
|
33
|
+
end
|
|
18
34
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: textus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.18.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrick
|
|
@@ -115,6 +115,7 @@ files:
|
|
|
115
115
|
- lib/textus/application/reads/deps.rb
|
|
116
116
|
- lib/textus/application/reads/freshness.rb
|
|
117
117
|
- lib/textus/application/reads/get.rb
|
|
118
|
+
- lib/textus/application/reads/get_or_refresh.rb
|
|
118
119
|
- lib/textus/application/reads/list.rb
|
|
119
120
|
- lib/textus/application/reads/policy_explain.rb
|
|
120
121
|
- lib/textus/application/reads/published.rb
|
|
@@ -123,6 +124,7 @@ files:
|
|
|
123
124
|
- lib/textus/application/reads/stale.rb
|
|
124
125
|
- lib/textus/application/reads/uid.rb
|
|
125
126
|
- lib/textus/application/reads/validate_all.rb
|
|
127
|
+
- lib/textus/application/reads/validator.rb
|
|
126
128
|
- lib/textus/application/reads/where.rb
|
|
127
129
|
- lib/textus/application/refresh/all.rb
|
|
128
130
|
- lib/textus/application/refresh/orchestrator.rb
|
|
@@ -130,6 +132,7 @@ files:
|
|
|
130
132
|
- lib/textus/application/writes/accept.rb
|
|
131
133
|
- lib/textus/application/writes/build.rb
|
|
132
134
|
- lib/textus/application/writes/delete.rb
|
|
135
|
+
- lib/textus/application/writes/envelope_io.rb
|
|
133
136
|
- lib/textus/application/writes/mv.rb
|
|
134
137
|
- lib/textus/application/writes/publish.rb
|
|
135
138
|
- lib/textus/application/writes/put.rb
|
|
@@ -197,6 +200,7 @@ files:
|
|
|
197
200
|
- lib/textus/doctor/check/templates.rb
|
|
198
201
|
- lib/textus/doctor/check/unowned_schema_fields.rb
|
|
199
202
|
- lib/textus/domain/action.rb
|
|
203
|
+
- lib/textus/domain/freshness.rb
|
|
200
204
|
- lib/textus/domain/freshness/evaluator.rb
|
|
201
205
|
- lib/textus/domain/freshness/policy.rb
|
|
202
206
|
- lib/textus/domain/freshness/verdict.rb
|
|
@@ -210,6 +214,10 @@ files:
|
|
|
210
214
|
- lib/textus/domain/policy/promote.rb
|
|
211
215
|
- lib/textus/domain/policy/promotion.rb
|
|
212
216
|
- lib/textus/domain/policy/refresh.rb
|
|
217
|
+
- lib/textus/domain/sentinel.rb
|
|
218
|
+
- lib/textus/domain/staleness.rb
|
|
219
|
+
- lib/textus/domain/staleness/generator_check.rb
|
|
220
|
+
- lib/textus/domain/staleness/intake_check.rb
|
|
213
221
|
- lib/textus/entry.rb
|
|
214
222
|
- lib/textus/entry/base.rb
|
|
215
223
|
- lib/textus/entry/json.rb
|
|
@@ -221,15 +229,17 @@ files:
|
|
|
221
229
|
- lib/textus/etag.rb
|
|
222
230
|
- lib/textus/hooks/builtin.rb
|
|
223
231
|
- lib/textus/hooks/dispatcher.rb
|
|
224
|
-
- lib/textus/hooks/dsl.rb
|
|
225
232
|
- lib/textus/hooks/loader.rb
|
|
226
233
|
- lib/textus/hooks/registry.rb
|
|
234
|
+
- lib/textus/infra/audit_log.rb
|
|
235
|
+
- lib/textus/infra/audit_subscriber.rb
|
|
227
236
|
- lib/textus/infra/build_lock.rb
|
|
228
237
|
- lib/textus/infra/clock.rb
|
|
229
238
|
- lib/textus/infra/event_bus.rb
|
|
230
239
|
- lib/textus/infra/publisher.rb
|
|
231
240
|
- lib/textus/infra/refresh/detached.rb
|
|
232
241
|
- lib/textus/infra/refresh/lock.rb
|
|
242
|
+
- lib/textus/infra/storage/file_store.rb
|
|
233
243
|
- lib/textus/init.rb
|
|
234
244
|
- lib/textus/intro.rb
|
|
235
245
|
- lib/textus/key/distance.rb
|
|
@@ -244,28 +254,20 @@ files:
|
|
|
244
254
|
- lib/textus/manifest/entry/validators/index_filename.rb
|
|
245
255
|
- lib/textus/manifest/entry/validators/inject_intro.rb
|
|
246
256
|
- lib/textus/manifest/entry/validators/publish_each.rb
|
|
257
|
+
- lib/textus/manifest/resolution.rb
|
|
247
258
|
- lib/textus/manifest/rules.rb
|
|
248
259
|
- lib/textus/manifest/schema.rb
|
|
249
260
|
- lib/textus/migrate_keys.rb
|
|
250
261
|
- lib/textus/mustache.rb
|
|
251
262
|
- lib/textus/operations.rb
|
|
252
|
-
- lib/textus/operations/reads.rb
|
|
253
|
-
- lib/textus/operations/refresh.rb
|
|
254
|
-
- lib/textus/operations/writes.rb
|
|
255
263
|
- lib/textus/projection.rb
|
|
256
264
|
- lib/textus/refresh.rb
|
|
257
265
|
- lib/textus/role.rb
|
|
258
266
|
- lib/textus/schema.rb
|
|
259
267
|
- lib/textus/schema/tools.rb
|
|
268
|
+
- lib/textus/schemas.rb
|
|
260
269
|
- lib/textus/store.rb
|
|
261
|
-
- lib/textus/
|
|
262
|
-
- lib/textus/store/reader.rb
|
|
263
|
-
- lib/textus/store/sentinel.rb
|
|
264
|
-
- lib/textus/store/staleness.rb
|
|
265
|
-
- lib/textus/store/staleness/generator_check.rb
|
|
266
|
-
- lib/textus/store/staleness/intake_check.rb
|
|
267
|
-
- lib/textus/store/validator.rb
|
|
268
|
-
- lib/textus/store/writer.rb
|
|
270
|
+
- lib/textus/uid.rb
|
|
269
271
|
- lib/textus/version.rb
|
|
270
272
|
homepage: https://github.com/patrick204nqh/textus
|
|
271
273
|
licenses:
|
data/lib/textus/hooks/dsl.rb
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Operations
|
|
3
|
-
class Reads
|
|
4
|
-
def initialize(ctx)
|
|
5
|
-
@ctx = ctx
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def get
|
|
9
|
-
Application::Reads::Get.new(ctx: @ctx, orchestrator: orchestrator)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def freshness = Application::Reads::Freshness.new(ctx: @ctx)
|
|
13
|
-
def audit = Application::Reads::Audit.new(ctx: @ctx)
|
|
14
|
-
def blame = Application::Reads::Blame.new(ctx: @ctx)
|
|
15
|
-
def policy_explain = Application::Reads::PolicyExplain.new(ctx: @ctx)
|
|
16
|
-
def list = Application::Reads::List.new(ctx: @ctx)
|
|
17
|
-
def where = Application::Reads::Where.new(ctx: @ctx)
|
|
18
|
-
def uid = Application::Reads::Uid.new(ctx: @ctx)
|
|
19
|
-
def schema_envelope = Application::Reads::SchemaEnvelope.new(ctx: @ctx)
|
|
20
|
-
def deps = Application::Reads::Deps.new(ctx: @ctx)
|
|
21
|
-
def rdeps = Application::Reads::Rdeps.new(ctx: @ctx)
|
|
22
|
-
def published = Application::Reads::Published.new(ctx: @ctx)
|
|
23
|
-
def stale = Application::Reads::Stale.new(ctx: @ctx)
|
|
24
|
-
def validate_all = Application::Reads::ValidateAll.new(ctx: @ctx)
|
|
25
|
-
|
|
26
|
-
private
|
|
27
|
-
|
|
28
|
-
def orchestrator
|
|
29
|
-
Application::Refresh::Orchestrator.new(
|
|
30
|
-
worker: Application::Refresh::Worker.new(ctx: @ctx, bus: @ctx.store.bus),
|
|
31
|
-
bus: @ctx.store.bus,
|
|
32
|
-
store_root: @ctx.store.root,
|
|
33
|
-
store: @ctx.store,
|
|
34
|
-
role: @ctx.role,
|
|
35
|
-
)
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Operations
|
|
3
|
-
class Refresh
|
|
4
|
-
def initialize(ctx)
|
|
5
|
-
@ctx = ctx
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def worker
|
|
9
|
-
Application::Refresh::Worker.new(ctx: @ctx, bus: @ctx.store.bus)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def orchestrator
|
|
13
|
-
Application::Refresh::Orchestrator.new(
|
|
14
|
-
worker: worker,
|
|
15
|
-
bus: @ctx.store.bus,
|
|
16
|
-
store_root: @ctx.store.root,
|
|
17
|
-
store: @ctx.store,
|
|
18
|
-
role: @ctx.role,
|
|
19
|
-
)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def all
|
|
23
|
-
Application::Refresh::All.new(ctx: @ctx, bus: @ctx.store.bus)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Operations
|
|
3
|
-
class Writes
|
|
4
|
-
def initialize(ctx)
|
|
5
|
-
@ctx = ctx
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def put = Application::Writes::Put.new(ctx: @ctx, bus: bus)
|
|
9
|
-
def delete = Application::Writes::Delete.new(ctx: @ctx, bus: bus)
|
|
10
|
-
def mv = Application::Writes::Mv.new(ctx: @ctx, bus: bus)
|
|
11
|
-
def accept = Application::Writes::Accept.new(ctx: @ctx, bus: bus)
|
|
12
|
-
def build = Application::Writes::Build.new(ctx: @ctx, bus: bus)
|
|
13
|
-
def publish = Application::Writes::Publish.new(ctx: @ctx, bus: bus)
|
|
14
|
-
def reject = Application::Writes::Reject.new(ctx: @ctx, bus: bus)
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
def bus = @ctx.store.bus
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
data/lib/textus/store/reader.rb
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Store
|
|
3
|
-
class Reader
|
|
4
|
-
def initialize(store)
|
|
5
|
-
@store = store
|
|
6
|
-
@manifest = store.manifest
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def get(key)
|
|
10
|
-
read_raw_envelope(key) || raise(UnknownKey.new(key, suggestions: @manifest.suggestions_for(key)))
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
# Reads the current on-disk state of key as a bare envelope, skipping
|
|
14
|
-
# freshness annotation to avoid recursion. Used by Freshness.refresh_sync
|
|
15
|
-
# after a sync refresh completes.
|
|
16
|
-
def read_raw_envelope(key)
|
|
17
|
-
mentry, path, = @manifest.resolve(key)
|
|
18
|
-
return nil unless File.exist?(path)
|
|
19
|
-
|
|
20
|
-
raw = File.binread(path)
|
|
21
|
-
parsed = Entry.for_format(mentry.format).parse(raw, path: path)
|
|
22
|
-
Envelope.build(
|
|
23
|
-
key: key, mentry: mentry, path: path,
|
|
24
|
-
meta: parsed["_meta"], body: parsed["body"],
|
|
25
|
-
etag: Etag.for_bytes(raw), content: parsed["content"]
|
|
26
|
-
)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def list(prefix: nil, zone: nil)
|
|
30
|
-
rows = @manifest.enumerate(prefix: prefix)
|
|
31
|
-
rows = rows.select { |r| r[:manifest_entry].zone == zone } if zone
|
|
32
|
-
rows.map { |row| { "key" => row[:key], "zone" => row[:manifest_entry].zone, "path" => row[:path] } }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def where(key)
|
|
36
|
-
mentry, path, = @manifest.resolve(key)
|
|
37
|
-
{ "protocol" => PROTOCOL, "key" => key, "zone" => mentry.zone, "owner" => mentry.owner, "path" => path }
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def schema_envelope(key)
|
|
41
|
-
mentry, = @manifest.resolve(key)
|
|
42
|
-
schema = @store.schema_for(mentry.schema)
|
|
43
|
-
{ "protocol" => PROTOCOL, "key" => key, "schema_ref" => mentry.schema, "schema" => schema&.to_h }
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Returns the Textus UID for a key (or nil if the entry has none yet).
|
|
47
|
-
# Raises UnknownKey if the key doesn't resolve to a real file.
|
|
48
|
-
def uid(key)
|
|
49
|
-
get(key).uid
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def deps(key) = Dependencies.deps_of(@manifest, key)
|
|
53
|
-
def rdeps(key) = Dependencies.rdeps_of(@manifest, key)
|
|
54
|
-
def published = Dependencies.published_of(@manifest)
|
|
55
|
-
|
|
56
|
-
def stale(prefix: nil, zone: nil)
|
|
57
|
-
Staleness.new(manifest: @manifest).call(prefix: prefix, zone: zone)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def validate_all
|
|
61
|
-
Validator.new(
|
|
62
|
-
reader: self, manifest: @manifest,
|
|
63
|
-
audit_log: @store.audit_log,
|
|
64
|
-
schema_for: ->(name) { @store.schema_for(name) }
|
|
65
|
-
).call
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Store
|
|
3
|
-
class Validator
|
|
4
|
-
def initialize(reader:, manifest:, audit_log:, schema_for:)
|
|
5
|
-
@reader = reader
|
|
6
|
-
@manifest = manifest
|
|
7
|
-
@audit_log = audit_log
|
|
8
|
-
@schema_for = schema_for
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def call
|
|
12
|
-
violations = []
|
|
13
|
-
check_content_violations(violations)
|
|
14
|
-
check_role_authority_violations(violations)
|
|
15
|
-
{ "protocol" => PROTOCOL, "ok" => violations.empty?, "violations" => violations }
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
private
|
|
19
|
-
|
|
20
|
-
def check_content_violations(violations)
|
|
21
|
-
@manifest.enumerate.each do |row|
|
|
22
|
-
key = row[:key]
|
|
23
|
-
mentry = row[:manifest_entry]
|
|
24
|
-
env = fetch_envelope(key, violations) or next
|
|
25
|
-
schema = mentry.schema && @schema_for.call(mentry.schema)
|
|
26
|
-
next unless schema
|
|
27
|
-
|
|
28
|
-
begin
|
|
29
|
-
validate_schema!(schema, env, mentry.format)
|
|
30
|
-
rescue Textus::Error => e
|
|
31
|
-
violations << { "key" => key, "code" => e.code, "message" => e.message }
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def check_role_authority_violations(violations)
|
|
37
|
-
@manifest.enumerate.each do |row|
|
|
38
|
-
mentry = row[:manifest_entry]
|
|
39
|
-
next unless mentry.schema
|
|
40
|
-
|
|
41
|
-
schema = @schema_for.call(mentry.schema)
|
|
42
|
-
next unless schema
|
|
43
|
-
|
|
44
|
-
env = begin
|
|
45
|
-
@reader.get(row[:key])
|
|
46
|
-
rescue StandardError
|
|
47
|
-
next
|
|
48
|
-
end
|
|
49
|
-
append_authority_violations(violations, row[:key], env, schema)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def append_authority_violations(violations, key, env, schema)
|
|
54
|
-
last_writer = @audit_log.last_writer_for(key)
|
|
55
|
-
return if last_writer.nil?
|
|
56
|
-
|
|
57
|
-
env.meta.each_key do |field|
|
|
58
|
-
owner = schema.maintained_by(field)
|
|
59
|
-
next if owner.nil? || last_writer == owner || last_writer == "human"
|
|
60
|
-
|
|
61
|
-
violations << { "key" => key, "code" => "role_authority",
|
|
62
|
-
"field" => field, "expected" => owner, "last_writer" => last_writer }
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def fetch_envelope(key, violations)
|
|
67
|
-
@reader.get(key)
|
|
68
|
-
rescue Textus::Error => e
|
|
69
|
-
violations << { "key" => key, "code" => e.code, "message" => e.message }
|
|
70
|
-
nil
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def validate_schema!(schema, envelope, format)
|
|
74
|
-
payload = case format
|
|
75
|
-
when "json", "yaml" then envelope.content || {}
|
|
76
|
-
else envelope.meta || {}
|
|
77
|
-
end
|
|
78
|
-
schema.validate!(payload)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
data/lib/textus/store/writer.rb
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
require "fileutils"
|
|
2
|
-
|
|
3
|
-
module Textus
|
|
4
|
-
class Store
|
|
5
|
-
class Writer
|
|
6
|
-
Payload = Data.define(:meta, :body, :content)
|
|
7
|
-
|
|
8
|
-
def initialize(store)
|
|
9
|
-
@store = store
|
|
10
|
-
@manifest = store.manifest
|
|
11
|
-
@reader = store.reader
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# Pure I/O: validate, serialize, etag-check, write to disk, audit. No
|
|
15
|
-
# permission check and no event firing — those are handled by the caller
|
|
16
|
-
# (Application::Writes::Put).
|
|
17
|
-
def write_envelope_to_disk(key, mentry:, payload:, ctx:, if_etag: nil)
|
|
18
|
-
_, path, = @manifest.resolve(key)
|
|
19
|
-
|
|
20
|
-
meta = payload.meta || {}
|
|
21
|
-
strategy = Entry.for_format(mentry.format)
|
|
22
|
-
|
|
23
|
-
existing_uid = existing_uid_for(mentry, path)
|
|
24
|
-
meta, content = ensure_uid(mentry.format, meta, payload.content, existing_uid)
|
|
25
|
-
|
|
26
|
-
bytes, eff_meta, eff_body, eff_content = serialize_for_put(
|
|
27
|
-
mentry: mentry, path: path, strategy: strategy,
|
|
28
|
-
meta: meta, body: payload.body, content: content
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
enforce_name_match!(path, eff_meta, mentry.format)
|
|
32
|
-
|
|
33
|
-
schema = @store.schema_for(mentry.schema)
|
|
34
|
-
if schema
|
|
35
|
-
Entry.for_format(mentry.format).validate_against(
|
|
36
|
-
schema,
|
|
37
|
-
{ "_meta" => eff_meta, "content" => eff_content },
|
|
38
|
-
)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
etag_before = File.exist?(path) ? Etag.for_file(path) : nil
|
|
42
|
-
raise EtagMismatch.new(key, if_etag, etag_before) if if_etag && (etag_before != if_etag)
|
|
43
|
-
|
|
44
|
-
FileUtils.mkdir_p(File.dirname(path))
|
|
45
|
-
File.binwrite(path, bytes)
|
|
46
|
-
etag_after = Etag.for_bytes(bytes)
|
|
47
|
-
@store.audit_log.append(
|
|
48
|
-
role: ctx.role, verb: "put", key: key,
|
|
49
|
-
etag_before: etag_before, etag_after: etag_after,
|
|
50
|
-
extras: ctx.correlation_id ? { "correlation_id" => ctx.correlation_id } : nil
|
|
51
|
-
)
|
|
52
|
-
Envelope.build(
|
|
53
|
-
key: key, mentry: mentry, path: path,
|
|
54
|
-
meta: eff_meta, body: eff_body, etag: etag_after, content: eff_content
|
|
55
|
-
)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def existing_uid_for(mentry, path)
|
|
59
|
-
return nil unless File.exist?(path)
|
|
60
|
-
|
|
61
|
-
raw = File.binread(path)
|
|
62
|
-
parsed = Entry.for_format(mentry.format).parse(raw, path: path)
|
|
63
|
-
Envelope.extract_uid(parsed["_meta"])
|
|
64
|
-
rescue StandardError
|
|
65
|
-
nil
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def ensure_uid(format, meta, content, existing_uid)
|
|
69
|
-
Textus::Entry.for_format(format).inject_uid(meta, content, existing_uid)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def enforce_name_match!(path, meta, format)
|
|
73
|
-
Textus::Entry.for_format(format).enforce_name_match!(path, meta)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def serialize_for_put(mentry:, path:, strategy:, meta:, body:, content:)
|
|
77
|
-
_ = strategy
|
|
78
|
-
Textus::Entry.for_format(mentry.format).serialize_for_put(
|
|
79
|
-
meta: meta, body: body, content: content, path: path,
|
|
80
|
-
)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Pure I/O: resolve path, validate etag, delete from disk, audit. No
|
|
84
|
-
# permission check and no event firing — those are handled by the caller
|
|
85
|
-
# (Application::Writes::Delete).
|
|
86
|
-
def delete_envelope_from_disk(key, ctx:, if_etag: nil)
|
|
87
|
-
_, path, = @manifest.resolve(key)
|
|
88
|
-
raise UnknownKey.new(key, suggestions: @manifest.suggestions_for(key)) unless File.exist?(path)
|
|
89
|
-
|
|
90
|
-
etag_before = Etag.for_file(path)
|
|
91
|
-
raise EtagMismatch.new(key, if_etag, etag_before) if if_etag && if_etag != etag_before
|
|
92
|
-
|
|
93
|
-
File.delete(path)
|
|
94
|
-
@store.audit_log.append(
|
|
95
|
-
role: ctx.role, verb: "delete", key: key,
|
|
96
|
-
etag_before: etag_before, etag_after: nil,
|
|
97
|
-
extras: ctx.correlation_id ? { "correlation_id" => ctx.correlation_id } : nil
|
|
98
|
-
)
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|