textus 0.18.0 → 0.20.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 +43 -48
- data/CHANGELOG.md +173 -0
- data/lib/textus/application/context.rb +20 -58
- data/lib/textus/application/policy/predicates/human_accept.rb +30 -0
- data/lib/textus/{domain → application}/policy/predicates/schema_valid.rb +5 -5
- data/lib/textus/{domain → application}/policy/promotion.rb +20 -3
- data/lib/textus/application/projection.rb +91 -0
- data/lib/textus/application/reads/audit.rb +4 -4
- data/lib/textus/application/reads/blame.rb +9 -8
- data/lib/textus/application/reads/deps.rb +14 -3
- data/lib/textus/application/reads/freshness.rb +10 -8
- data/lib/textus/application/reads/get.rb +10 -8
- data/lib/textus/application/reads/get_or_refresh.rb +3 -3
- data/lib/textus/application/reads/list.rb +3 -3
- data/lib/textus/application/reads/policy_explain.rb +3 -3
- data/lib/textus/application/reads/published.rb +5 -3
- data/lib/textus/application/reads/rdeps.rb +15 -3
- data/lib/textus/application/reads/schema_envelope.rb +5 -4
- data/lib/textus/application/reads/stale.rb +3 -3
- data/lib/textus/application/reads/uid.rb +11 -3
- data/lib/textus/application/reads/validate_all.rb +10 -6
- data/lib/textus/application/reads/validator.rb +2 -2
- data/lib/textus/application/reads/where.rb +3 -3
- data/lib/textus/application/refresh/all.rb +15 -11
- data/lib/textus/application/refresh/orchestrator.rb +9 -8
- data/lib/textus/application/refresh/worker.rb +56 -32
- data/lib/textus/application/tools/migrate_keys.rb +191 -0
- data/lib/textus/application/tools/migrate_manifest_to_kinds.rb +31 -0
- data/lib/textus/application/writes/accept.rb +38 -15
- data/lib/textus/application/writes/delete.rb +13 -10
- data/lib/textus/application/writes/envelope_io.rb +64 -4
- data/lib/textus/application/writes/materializer.rb +50 -0
- data/lib/textus/application/writes/mv.rb +57 -94
- data/lib/textus/application/writes/publish.rb +132 -26
- data/lib/textus/application/writes/put.rb +15 -14
- data/lib/textus/application/writes/reject.rb +20 -11
- data/lib/textus/builder/pipeline.rb +21 -15
- data/lib/textus/builder/renderer/json.rb +4 -1
- data/lib/textus/builder/renderer/markdown.rb +7 -1
- data/lib/textus/builder/renderer/yaml.rb +4 -1
- data/lib/textus/cli/verb/build.rb +2 -5
- data/lib/textus/cli/verb/get.rb +1 -1
- data/lib/textus/cli/verb/hook_run.rb +3 -4
- data/lib/textus/cli/verb/hooks.rb +5 -5
- data/lib/textus/cli/verb/key_normalize.rb +32 -3
- data/lib/textus/cli/verb/put.rb +2 -3
- data/lib/textus/cli/verb/refresh_stale.rb +1 -2
- data/lib/textus/doctor/check/handler_allowlist.rb +3 -2
- data/lib/textus/doctor/check/hooks.rb +2 -2
- data/lib/textus/doctor/check/illegal_keys.rb +6 -5
- data/lib/textus/doctor/check/intake_registration.rb +2 -2
- data/lib/textus/doctor/check/manifest_files.rb +1 -1
- data/lib/textus/doctor/check/protocol_version.rb +2 -2
- data/lib/textus/doctor/check/templates.rb +4 -3
- data/lib/textus/doctor.rb +3 -4
- data/lib/textus/domain/authorizer.rb +37 -0
- data/lib/textus/domain/staleness/generator_check.rb +8 -7
- data/lib/textus/domain/staleness/intake_check.rb +2 -2
- data/lib/textus/hooks/builtin.rb +6 -6
- data/lib/textus/hooks/bus.rb +155 -0
- data/lib/textus/hooks/context.rb +38 -0
- data/lib/textus/hooks/fire_report.rb +23 -0
- data/lib/textus/hooks/loader.rb +3 -3
- data/lib/textus/infra/audit_subscriber.rb +4 -4
- data/lib/textus/infra/event_bus.rb +3 -3
- data/lib/textus/infra/refresh/detached.rb +1 -1
- data/lib/textus/init.rb +3 -2
- data/lib/textus/intro.rb +7 -7
- data/lib/textus/manifest/entry/base.rb +38 -0
- data/lib/textus/manifest/entry/derived.rb +25 -0
- data/lib/textus/manifest/entry/intake.rb +19 -0
- data/lib/textus/manifest/entry/leaf.rb +16 -0
- data/lib/textus/manifest/entry/nested.rb +39 -0
- data/lib/textus/manifest/entry/parser.rb +64 -31
- data/lib/textus/manifest/entry/validators/events.rb +3 -2
- data/lib/textus/manifest/entry/validators/format_matrix.rb +5 -3
- data/lib/textus/manifest/entry/validators/index_filename.rb +11 -10
- data/lib/textus/manifest/entry/validators/inject_intro.rb +5 -2
- data/lib/textus/manifest/entry/validators/publish_each.rb +9 -6
- data/lib/textus/manifest/entry.rb +0 -72
- data/lib/textus/manifest/resolver.rb +109 -0
- data/lib/textus/manifest/schema.rb +1 -1
- data/lib/textus/manifest.rb +3 -100
- data/lib/textus/operations.rb +131 -74
- data/lib/textus/schema/tools.rb +2 -2
- data/lib/textus/store.rb +6 -6
- data/lib/textus/version.rb +1 -1
- metadata +18 -11
- data/lib/textus/application/writes/build.rb +0 -78
- data/lib/textus/dependencies.rb +0 -23
- data/lib/textus/domain/policy/predicates/human_accept.rb +0 -31
- data/lib/textus/hooks/dispatcher.rb +0 -71
- data/lib/textus/hooks/registry.rb +0 -85
- data/lib/textus/migrate_keys.rb +0 -187
- data/lib/textus/projection.rb +0 -89
- data/lib/textus/refresh.rb +0 -39
|
@@ -8,12 +8,13 @@ module Textus
|
|
|
8
8
|
# row. Falls back to `git => nil` when not in a git repo or when the
|
|
9
9
|
# file is untracked.
|
|
10
10
|
class Blame
|
|
11
|
-
def initialize(
|
|
12
|
-
@
|
|
11
|
+
def initialize(manifest:, root:)
|
|
12
|
+
@manifest = manifest
|
|
13
|
+
@root = root
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def call(key:, limit: nil)
|
|
16
|
-
audit_rows = Textus::Application::Reads::Audit.new(
|
|
17
|
+
audit_rows = Textus::Application::Reads::Audit.new(manifest: @manifest, root: @root).call(key: key, limit: limit)
|
|
17
18
|
path = resolve_path(key)
|
|
18
19
|
return audit_rows.map { |r| r.merge("git" => nil) } unless git_tracked?(path)
|
|
19
20
|
|
|
@@ -23,13 +24,13 @@ module Textus
|
|
|
23
24
|
private
|
|
24
25
|
|
|
25
26
|
def resolve_path(key)
|
|
26
|
-
res = @
|
|
27
|
+
res = @manifest.resolver.resolve(key)
|
|
27
28
|
mentry = res.entry
|
|
28
29
|
path = res.path
|
|
29
30
|
# Nested entries resolve to a file under the entry path; leaf entries
|
|
30
31
|
# already have a fully-resolved path. Either way `path` is what git
|
|
31
32
|
# needs to know about.
|
|
32
|
-
path || Textus::Key::Path.resolve(@
|
|
33
|
+
path || Textus::Key::Path.resolve(@manifest, mentry)
|
|
33
34
|
rescue Textus::Error
|
|
34
35
|
nil
|
|
35
36
|
end
|
|
@@ -41,7 +42,7 @@ module Textus
|
|
|
41
42
|
|
|
42
43
|
_out, _err, status = Open3.capture3(
|
|
43
44
|
"git", "ls-files", "--error-unmatch", path,
|
|
44
|
-
chdir: @
|
|
45
|
+
chdir: @root
|
|
45
46
|
)
|
|
46
47
|
status.success?
|
|
47
48
|
rescue Errno::ENOENT
|
|
@@ -50,7 +51,7 @@ module Textus
|
|
|
50
51
|
|
|
51
52
|
def git_repo?
|
|
52
53
|
# Walk up from store root to find a .git directory.
|
|
53
|
-
dir = @
|
|
54
|
+
dir = @root
|
|
54
55
|
loop do
|
|
55
56
|
return true if File.directory?(File.join(dir, ".git"))
|
|
56
57
|
|
|
@@ -65,7 +66,7 @@ module Textus
|
|
|
65
66
|
args = ["git", "log", "-1"]
|
|
66
67
|
args << "--before=#{timestamp}" if timestamp
|
|
67
68
|
args += ["--format=%H%x09%an%x09%aI%x09%s", "--", path]
|
|
68
|
-
out, _err, status = Open3.capture3(*args, chdir: @
|
|
69
|
+
out, _err, status = Open3.capture3(*args, chdir: @root)
|
|
69
70
|
return nil unless status.success?
|
|
70
71
|
|
|
71
72
|
sha, author, date, subject = out.strip.split("\t", 4)
|
|
@@ -2,12 +2,23 @@ module Textus
|
|
|
2
2
|
module Application
|
|
3
3
|
module Reads
|
|
4
4
|
class Deps
|
|
5
|
-
def initialize(
|
|
6
|
-
@
|
|
5
|
+
def initialize(manifest:)
|
|
6
|
+
@manifest = manifest
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def call(key)
|
|
10
|
-
|
|
10
|
+
entry = @manifest.entries.find { |e| e.key == key } or return []
|
|
11
|
+
return [] unless entry.is_a?(Textus::Manifest::Entry::Derived)
|
|
12
|
+
|
|
13
|
+
src = entry.source
|
|
14
|
+
result = if src.is_a?(Textus::Manifest::Entry::Derived::Projection)
|
|
15
|
+
Array(src.select).compact
|
|
16
|
+
elsif src.is_a?(Textus::Manifest::Entry::Derived::External)
|
|
17
|
+
Array(src.sources).compact
|
|
18
|
+
else
|
|
19
|
+
[]
|
|
20
|
+
end
|
|
21
|
+
result.uniq
|
|
11
22
|
end
|
|
12
23
|
end
|
|
13
24
|
end
|
|
@@ -8,14 +8,16 @@ module Textus
|
|
|
8
8
|
# current status. Status is one of :fresh, :stale, :never_refreshed, or
|
|
9
9
|
# :no_policy.
|
|
10
10
|
class Freshness
|
|
11
|
-
def initialize(ctx:, evaluator: Textus::Domain::Freshness::Evaluator)
|
|
12
|
-
@ctx
|
|
13
|
-
@
|
|
11
|
+
def initialize(ctx:, manifest:, file_store:, evaluator: Textus::Domain::Freshness::Evaluator)
|
|
12
|
+
@ctx = ctx
|
|
13
|
+
@manifest = manifest
|
|
14
|
+
@file_store = file_store
|
|
15
|
+
@evaluator = evaluator
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
def call(prefix: nil, zone: nil)
|
|
17
19
|
rows = []
|
|
18
|
-
@
|
|
20
|
+
@manifest.entries.each do |mentry|
|
|
19
21
|
next if prefix && !mentry.key.start_with?(prefix)
|
|
20
22
|
next if zone && mentry.zone != zone
|
|
21
23
|
|
|
@@ -27,7 +29,7 @@ module Textus
|
|
|
27
29
|
private
|
|
28
30
|
|
|
29
31
|
def row_for(mentry)
|
|
30
|
-
set = @
|
|
32
|
+
set = @manifest.rules_for(mentry.key)
|
|
31
33
|
refresh = set.refresh
|
|
32
34
|
envelope = safe_get(mentry.key)
|
|
33
35
|
last = envelope&.meta&.dig("last_refreshed_at")
|
|
@@ -61,10 +63,10 @@ module Textus
|
|
|
61
63
|
# Returns the raw envelope or nil. Nested entries (mentry.key is a
|
|
62
64
|
# prefix, not a leaf) and missing files both resolve to nil.
|
|
63
65
|
def safe_get(key)
|
|
64
|
-
res = @
|
|
65
|
-
return nil unless @
|
|
66
|
+
res = @manifest.resolver.resolve(key)
|
|
67
|
+
return nil unless @file_store.exists?(res.path)
|
|
66
68
|
|
|
67
|
-
raw = @
|
|
69
|
+
raw = @file_store.read(res.path)
|
|
68
70
|
parsed = Entry.for_format(res.entry.format).parse(raw, path: res.path)
|
|
69
71
|
Envelope.build(
|
|
70
72
|
key: key, mentry: res.entry, path: res.path,
|
|
@@ -7,16 +7,18 @@ module Textus
|
|
|
7
7
|
# For interactive reads that want refresh-on-stale, use
|
|
8
8
|
# `Reads::GetOrRefresh`, which composes this with the orchestrator.
|
|
9
9
|
class Get
|
|
10
|
-
def initialize(ctx:, evaluator: Textus::Domain::Freshness::Evaluator)
|
|
11
|
-
@ctx
|
|
12
|
-
@
|
|
10
|
+
def initialize(ctx:, manifest:, file_store:, evaluator: Textus::Domain::Freshness::Evaluator)
|
|
11
|
+
@ctx = ctx
|
|
12
|
+
@manifest = manifest
|
|
13
|
+
@file_store = file_store
|
|
14
|
+
@evaluator = evaluator
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def call(key)
|
|
16
18
|
envelope = read_raw_envelope(key)
|
|
17
19
|
return nil if envelope.nil?
|
|
18
20
|
|
|
19
|
-
policy_set = @
|
|
21
|
+
policy_set = @manifest.rules_for(key)
|
|
20
22
|
refresh_policy = policy_set.refresh
|
|
21
23
|
return annotate_fresh(envelope) if refresh_policy.nil?
|
|
22
24
|
|
|
@@ -34,18 +36,18 @@ module Textus
|
|
|
34
36
|
# Used by consumers (e.g. Validator) that need to distinguish absence
|
|
35
37
|
# from emptiness.
|
|
36
38
|
def get(key)
|
|
37
|
-
call(key) || raise(UnknownKey.new(key, suggestions: @
|
|
39
|
+
call(key) || raise(UnknownKey.new(key, suggestions: @manifest.resolver.suggestions_for(key)))
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
private
|
|
41
43
|
|
|
42
44
|
def read_raw_envelope(key)
|
|
43
|
-
res = @
|
|
45
|
+
res = @manifest.resolver.resolve(key)
|
|
44
46
|
mentry = res.entry
|
|
45
47
|
path = res.path
|
|
46
|
-
return nil unless @
|
|
48
|
+
return nil unless @file_store.exists?(path)
|
|
47
49
|
|
|
48
|
-
raw = @
|
|
50
|
+
raw = @file_store.read(path)
|
|
49
51
|
parsed = Entry.for_format(mentry.format).parse(raw, path: path)
|
|
50
52
|
Envelope.build(
|
|
51
53
|
key: key, mentry: mentry, path: path,
|
|
@@ -10,8 +10,8 @@ module Textus
|
|
|
10
10
|
# Pure reads (build, projection, schema tooling) should use
|
|
11
11
|
# `Reads::Get` directly; it has no orchestrator dependency.
|
|
12
12
|
class GetOrRefresh
|
|
13
|
-
def initialize(
|
|
14
|
-
@
|
|
13
|
+
def initialize(manifest:, get:, orchestrator:)
|
|
14
|
+
@manifest = manifest
|
|
15
15
|
@get = get
|
|
16
16
|
@orchestrator = orchestrator
|
|
17
17
|
end
|
|
@@ -21,7 +21,7 @@ module Textus
|
|
|
21
21
|
return nil if envelope.nil?
|
|
22
22
|
return envelope unless envelope.freshness&.stale
|
|
23
23
|
|
|
24
|
-
policy_set = @
|
|
24
|
+
policy_set = @manifest.rules_for(key)
|
|
25
25
|
refresh_policy = policy_set.refresh
|
|
26
26
|
return envelope if refresh_policy.nil?
|
|
27
27
|
|
|
@@ -2,12 +2,12 @@ module Textus
|
|
|
2
2
|
module Application
|
|
3
3
|
module Reads
|
|
4
4
|
class List
|
|
5
|
-
def initialize(
|
|
6
|
-
@
|
|
5
|
+
def initialize(manifest:)
|
|
6
|
+
@manifest = manifest
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def call(prefix: nil, zone: nil)
|
|
10
|
-
rows = @
|
|
10
|
+
rows = @manifest.resolver.enumerate(prefix: prefix)
|
|
11
11
|
rows = rows.select { |r| r[:manifest_entry].zone == zone } if zone
|
|
12
12
|
rows.map { |row| { "key" => row[:key], "zone" => row[:manifest_entry].zone, "path" => row[:path] } }
|
|
13
13
|
end
|
|
@@ -4,12 +4,12 @@ module Textus
|
|
|
4
4
|
# For one key, surface every matching policy block along with the
|
|
5
5
|
# per-slot effective value (which loses ties win-by-specificity).
|
|
6
6
|
class PolicyExplain
|
|
7
|
-
def initialize(
|
|
8
|
-
@
|
|
7
|
+
def initialize(manifest:)
|
|
8
|
+
@manifest = manifest
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def call(key:)
|
|
12
|
-
policies = @
|
|
12
|
+
policies = @manifest.rules
|
|
13
13
|
matching = policies.explain(key)
|
|
14
14
|
winners = policies.for(key)
|
|
15
15
|
|
|
@@ -2,12 +2,14 @@ module Textus
|
|
|
2
2
|
module Application
|
|
3
3
|
module Reads
|
|
4
4
|
class Published
|
|
5
|
-
def initialize(
|
|
6
|
-
@
|
|
5
|
+
def initialize(manifest:)
|
|
6
|
+
@manifest = manifest
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def call
|
|
10
|
-
|
|
10
|
+
@manifest.entries.reject { |e| e.publish_to.empty? }.map do |e|
|
|
11
|
+
{ "key" => e.key, "publish_to" => e.publish_to }
|
|
12
|
+
end
|
|
11
13
|
end
|
|
12
14
|
end
|
|
13
15
|
end
|
|
@@ -2,12 +2,24 @@ module Textus
|
|
|
2
2
|
module Application
|
|
3
3
|
module Reads
|
|
4
4
|
class Rdeps
|
|
5
|
-
def initialize(
|
|
6
|
-
@
|
|
5
|
+
def initialize(manifest:)
|
|
6
|
+
@manifest = manifest
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def call(key)
|
|
10
|
-
|
|
10
|
+
@manifest.entries.each_with_object([]) do |e, acc|
|
|
11
|
+
next unless e.is_a?(Textus::Manifest::Entry::Derived)
|
|
12
|
+
|
|
13
|
+
src = e.source
|
|
14
|
+
sources = if src.is_a?(Textus::Manifest::Entry::Derived::Projection)
|
|
15
|
+
Array(src.select).compact
|
|
16
|
+
elsif src.is_a?(Textus::Manifest::Entry::Derived::External)
|
|
17
|
+
Array(src.sources).compact
|
|
18
|
+
else
|
|
19
|
+
[]
|
|
20
|
+
end
|
|
21
|
+
acc << e.key if sources.any? { |s| s == key || key.start_with?("#{s}.") }
|
|
22
|
+
end
|
|
11
23
|
end
|
|
12
24
|
end
|
|
13
25
|
end
|
|
@@ -2,13 +2,14 @@ module Textus
|
|
|
2
2
|
module Application
|
|
3
3
|
module Reads
|
|
4
4
|
class SchemaEnvelope
|
|
5
|
-
def initialize(
|
|
6
|
-
@
|
|
5
|
+
def initialize(manifest:, schemas:)
|
|
6
|
+
@manifest = manifest
|
|
7
|
+
@schemas = schemas
|
|
7
8
|
end
|
|
8
9
|
|
|
9
10
|
def call(key)
|
|
10
|
-
mentry = @
|
|
11
|
-
schema = @
|
|
11
|
+
mentry = @manifest.resolver.resolve(key).entry
|
|
12
|
+
schema = @schemas.fetch_or_nil(mentry.schema)
|
|
12
13
|
{ "protocol" => PROTOCOL, "key" => key, "schema_ref" => mentry.schema, "schema" => schema&.to_h }
|
|
13
14
|
end
|
|
14
15
|
end
|
|
@@ -2,12 +2,12 @@ module Textus
|
|
|
2
2
|
module Application
|
|
3
3
|
module Reads
|
|
4
4
|
class Stale
|
|
5
|
-
def initialize(
|
|
6
|
-
@
|
|
5
|
+
def initialize(manifest:)
|
|
6
|
+
@manifest = manifest
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def call(prefix: nil, zone: nil)
|
|
10
|
-
Textus::Domain::Staleness.new(manifest: @
|
|
10
|
+
Textus::Domain::Staleness.new(manifest: @manifest).call(prefix: prefix, zone: zone)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
end
|
|
@@ -2,12 +2,20 @@ module Textus
|
|
|
2
2
|
module Application
|
|
3
3
|
module Reads
|
|
4
4
|
class Uid
|
|
5
|
-
def initialize(ctx:)
|
|
6
|
-
@ctx
|
|
5
|
+
def initialize(ctx:, manifest:, file_store:)
|
|
6
|
+
@ctx = ctx
|
|
7
|
+
@manifest = manifest
|
|
8
|
+
@file_store = file_store
|
|
7
9
|
end
|
|
8
10
|
|
|
9
11
|
def call(key)
|
|
10
|
-
|
|
12
|
+
get.get(key).uid
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def get
|
|
18
|
+
@get ||= Get.new(ctx: @ctx, manifest: @manifest, file_store: @file_store)
|
|
11
19
|
end
|
|
12
20
|
end
|
|
13
21
|
end
|
|
@@ -2,16 +2,20 @@ module Textus
|
|
|
2
2
|
module Application
|
|
3
3
|
module Reads
|
|
4
4
|
class ValidateAll
|
|
5
|
-
def initialize(ctx:)
|
|
6
|
-
@ctx
|
|
5
|
+
def initialize(ctx:, manifest:, file_store:, schemas:, audit_log:)
|
|
6
|
+
@ctx = ctx
|
|
7
|
+
@manifest = manifest
|
|
8
|
+
@file_store = file_store
|
|
9
|
+
@schemas = schemas
|
|
10
|
+
@audit_log = audit_log
|
|
7
11
|
end
|
|
8
12
|
|
|
9
13
|
def call
|
|
10
14
|
Validator.new(
|
|
11
|
-
reader: Get.new(ctx: @ctx),
|
|
12
|
-
manifest: @
|
|
13
|
-
audit_log: @
|
|
14
|
-
schema_for: ->(name) { @
|
|
15
|
+
reader: Get.new(ctx: @ctx, manifest: @manifest, file_store: @file_store),
|
|
16
|
+
manifest: @manifest,
|
|
17
|
+
audit_log: @audit_log,
|
|
18
|
+
schema_for: ->(name) { @schemas.fetch_or_nil(name) },
|
|
15
19
|
).call
|
|
16
20
|
end
|
|
17
21
|
end
|
|
@@ -19,7 +19,7 @@ module Textus
|
|
|
19
19
|
private
|
|
20
20
|
|
|
21
21
|
def check_content_violations(violations)
|
|
22
|
-
@manifest.enumerate.each do |row|
|
|
22
|
+
@manifest.resolver.enumerate.each do |row|
|
|
23
23
|
key = row[:key]
|
|
24
24
|
mentry = row[:manifest_entry]
|
|
25
25
|
env = fetch_envelope(key, violations) or next
|
|
@@ -35,7 +35,7 @@ module Textus
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def check_role_authority_violations(violations)
|
|
38
|
-
@manifest.enumerate.each do |row|
|
|
38
|
+
@manifest.resolver.enumerate.each do |row|
|
|
39
39
|
mentry = row[:manifest_entry]
|
|
40
40
|
next unless mentry.schema
|
|
41
41
|
|
|
@@ -2,12 +2,12 @@ module Textus
|
|
|
2
2
|
module Application
|
|
3
3
|
module Reads
|
|
4
4
|
class Where
|
|
5
|
-
def initialize(
|
|
6
|
-
@
|
|
5
|
+
def initialize(manifest:)
|
|
6
|
+
@manifest = manifest
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def call(key)
|
|
10
|
-
res = @
|
|
10
|
+
res = @manifest.resolver.resolve(key)
|
|
11
11
|
mentry = res.entry
|
|
12
12
|
path = res.path
|
|
13
13
|
{ "protocol" => PROTOCOL, "key" => key, "zone" => mentry.zone, "owner" => mentry.owner, "path" => path }
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
module Textus
|
|
2
2
|
module Application
|
|
3
3
|
module Refresh
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
class All
|
|
5
|
+
def initialize(ctx:, manifest:, envelope_io:, bus:, store:, authorizer:, hook_context:) # rubocop:disable Metrics/ParameterLists
|
|
6
|
+
@ctx = ctx
|
|
7
|
+
@manifest = manifest
|
|
8
|
+
@envelope_io = envelope_io
|
|
9
|
+
@bus = bus
|
|
10
|
+
@store = store
|
|
11
|
+
@authorizer = authorizer
|
|
12
|
+
@hook_context = hook_context
|
|
13
|
+
end
|
|
6
14
|
|
|
7
|
-
def call(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
schemas: ctx.schemas,
|
|
12
|
-
audit_log: ctx.audit_log,
|
|
13
|
-
ctx: ctx,
|
|
15
|
+
def call(prefix: nil, zone: nil)
|
|
16
|
+
worker = Textus::Application::Refresh::Worker.new(
|
|
17
|
+
ctx: @ctx, manifest: @manifest, envelope_io: @envelope_io, bus: @bus,
|
|
18
|
+
store: @store, authorizer: @authorizer, hook_context: @hook_context
|
|
14
19
|
)
|
|
15
|
-
worker = Textus::Application::Refresh::Worker.new(ctx: ctx, envelope_io: envelope_io)
|
|
16
20
|
|
|
17
|
-
stale_rows = Textus::Application::Reads::Stale.new(
|
|
21
|
+
stale_rows = Textus::Application::Reads::Stale.new(manifest: @manifest).call(prefix: prefix, zone: zone)
|
|
18
22
|
refreshed = []
|
|
19
23
|
failed = []
|
|
20
24
|
skipped = []
|
|
@@ -2,11 +2,13 @@ module Textus
|
|
|
2
2
|
module Application
|
|
3
3
|
module Refresh
|
|
4
4
|
class Orchestrator
|
|
5
|
-
def initialize(worker:, store_root:, store: nil,
|
|
6
|
-
@worker
|
|
7
|
-
@store_root
|
|
8
|
-
@
|
|
9
|
-
@
|
|
5
|
+
def initialize(worker:, store_root:, bus: nil, store: nil, ctx: nil, hook_context: nil, detached_spawner: nil) # rubocop:disable Metrics/ParameterLists
|
|
6
|
+
@worker = worker
|
|
7
|
+
@store_root = store_root
|
|
8
|
+
@bus = bus
|
|
9
|
+
@store = store
|
|
10
|
+
@ctx = ctx
|
|
11
|
+
@hook_context = hook_context
|
|
10
12
|
@detached_spawner = detached_spawner || default_spawner
|
|
11
13
|
end
|
|
12
14
|
|
|
@@ -55,10 +57,9 @@ module Textus
|
|
|
55
57
|
|
|
56
58
|
probe.release
|
|
57
59
|
|
|
58
|
-
store_view = @store ? Textus::Application::Context.new(store: @store, role: @role) : nil
|
|
59
60
|
payload = { key: key, started_at: Time.now.utc.iso8601, budget_ms: budget_ms }
|
|
60
|
-
payload[:
|
|
61
|
-
@
|
|
61
|
+
payload[:ctx] = @hook_context if @hook_context
|
|
62
|
+
@bus&.publish(:refresh_backgrounded, **payload)
|
|
62
63
|
@detached_spawner.call(store_root: @store_root, key: key)
|
|
63
64
|
Textus::Domain::Outcome::Detached.new
|
|
64
65
|
elsif result.is_a?(Textus::Error)
|
|
@@ -6,17 +6,22 @@ module Textus
|
|
|
6
6
|
class Worker
|
|
7
7
|
FETCH_TIMEOUT_SECONDS = 30
|
|
8
8
|
|
|
9
|
-
def initialize(ctx:, envelope_io:)
|
|
10
|
-
@ctx
|
|
11
|
-
@
|
|
9
|
+
def initialize(ctx:, manifest:, envelope_io:, bus:, store:, authorizer:, hook_context:) # rubocop:disable Metrics/ParameterLists
|
|
10
|
+
@ctx = ctx
|
|
11
|
+
@manifest = manifest
|
|
12
|
+
@envelope_io = envelope_io
|
|
13
|
+
@bus = bus
|
|
14
|
+
@store = store
|
|
15
|
+
@authorizer = authorizer
|
|
16
|
+
@hook_context = hook_context
|
|
12
17
|
end
|
|
13
18
|
|
|
14
19
|
def run(key)
|
|
15
|
-
res = @
|
|
20
|
+
res = @manifest.resolver.resolve(key)
|
|
16
21
|
mentry = res.entry
|
|
17
22
|
path = res.path
|
|
18
23
|
remaining = res.remaining
|
|
19
|
-
raise UsageError.new("no intake declared for '#{key}'") unless mentry.
|
|
24
|
+
raise UsageError.new("no intake declared for '#{key}'") unless mentry.is_a?(Textus::Manifest::Entry::Intake)
|
|
20
25
|
|
|
21
26
|
before_etag = File.exist?(path) ? Etag.for_file(path) : nil
|
|
22
27
|
result = fetch_with_bus(key, mentry, remaining)
|
|
@@ -25,19 +30,14 @@ module Textus
|
|
|
25
30
|
|
|
26
31
|
private
|
|
27
32
|
|
|
28
|
-
def read_view
|
|
29
|
-
Application::Context.new(store: @ctx.store, role: @ctx.role)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
33
|
def fetch_timeout_for(key)
|
|
33
|
-
rule = @
|
|
34
|
+
rule = @manifest.rules_for(key)
|
|
34
35
|
rule&.refresh&.fetch_timeout_seconds || FETCH_TIMEOUT_SECONDS
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def fetch_with_bus(key, mentry, remaining)
|
|
38
|
-
callable = @
|
|
39
|
-
@
|
|
40
|
-
correlation_id: @ctx.correlation_id)
|
|
39
|
+
callable = @bus.rpc_callable(:resolve_intake, mentry.handler)
|
|
40
|
+
@bus.publish(:refresh_started, ctx: @hook_context, key: key, mode: :sync)
|
|
41
41
|
call_intake(key, mentry, callable, remaining)
|
|
42
42
|
end
|
|
43
43
|
|
|
@@ -45,38 +45,38 @@ module Textus
|
|
|
45
45
|
timeout = fetch_timeout_for(key)
|
|
46
46
|
Timeout.timeout(timeout) do
|
|
47
47
|
callable.call(
|
|
48
|
-
store: @
|
|
49
|
-
config: mentry.
|
|
48
|
+
store: @store,
|
|
49
|
+
config: mentry.config,
|
|
50
50
|
args: { trigger_key: key, leaf_segments: remaining || [] },
|
|
51
51
|
)
|
|
52
52
|
end
|
|
53
53
|
rescue Timeout::Error
|
|
54
|
-
@
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
raise UsageError.new("intake '#{mentry.
|
|
54
|
+
@bus.publish(:refresh_failed, ctx: @hook_context, key: key,
|
|
55
|
+
error_class: "Timeout::Error",
|
|
56
|
+
error_message: "intake '#{mentry.handler}' exceeded #{timeout}s")
|
|
57
|
+
raise UsageError.new("intake '#{mentry.handler}' exceeded #{timeout}s timeout")
|
|
58
58
|
rescue Textus::Error => e
|
|
59
|
-
@
|
|
60
|
-
|
|
59
|
+
@bus.publish(:refresh_failed, ctx: @hook_context, key: key, error_class: e.class.name,
|
|
60
|
+
error_message: e.message)
|
|
61
61
|
raise
|
|
62
62
|
rescue StandardError => e
|
|
63
|
-
@
|
|
64
|
-
|
|
65
|
-
raise UsageError.new("intake '#{mentry.
|
|
63
|
+
@bus.publish(:refresh_failed, ctx: @hook_context, key: key, error_class: e.class.name,
|
|
64
|
+
error_message: e.message)
|
|
65
|
+
raise UsageError.new("intake '#{mentry.handler}' raised: #{e.class}: #{e.message}")
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def persist_and_notify(key, mentry, result, before_etag)
|
|
69
|
-
normalized =
|
|
70
|
-
|
|
69
|
+
normalized = self.class.send(:normalize_action_result, result, format: mentry.format)
|
|
70
|
+
@authorizer.authorize_write!(mentry, role: @ctx.role)
|
|
71
|
+
envelope = @envelope_io.write(
|
|
71
72
|
key,
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
mentry: mentry,
|
|
74
|
+
payload: Textus::Application::Writes::EnvelopeIO::Payload.new(
|
|
75
|
+
meta: normalized[:meta], body: normalized[:body], content: normalized[:content],
|
|
76
|
+
),
|
|
74
77
|
)
|
|
75
78
|
change = detect_change(before_etag, envelope)
|
|
76
|
-
unless change == :unchanged
|
|
77
|
-
@ctx.bus.publish(:entry_refreshed, store: read_view, key: key, envelope: envelope, change: change,
|
|
78
|
-
correlation_id: @ctx.correlation_id)
|
|
79
|
-
end
|
|
79
|
+
@bus.publish(:entry_refreshed, ctx: @hook_context, key: key, envelope: envelope, change: change) unless change == :unchanged
|
|
80
80
|
envelope
|
|
81
81
|
end
|
|
82
82
|
|
|
@@ -86,6 +86,30 @@ module Textus
|
|
|
86
86
|
else :updated
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
|
+
|
|
90
|
+
def self.normalize_action_result(res, format:)
|
|
91
|
+
res = res.transform_keys(&:to_s) if res.is_a?(Hash)
|
|
92
|
+
res ||= {}
|
|
93
|
+
meta_val = res["_meta"]
|
|
94
|
+
body = res["body"]
|
|
95
|
+
content = res["content"]
|
|
96
|
+
|
|
97
|
+
case format
|
|
98
|
+
when "markdown" then { meta: meta_val || {}, body: body.to_s, content: nil }
|
|
99
|
+
when "text" then { meta: {}, body: body.to_s, content: nil }
|
|
100
|
+
when "json", "yaml"
|
|
101
|
+
if !content.nil?
|
|
102
|
+
{ meta: meta_val || {}, body: nil, content: content }
|
|
103
|
+
elsif !body.nil?
|
|
104
|
+
{ meta: {}, body: body.to_s, content: nil }
|
|
105
|
+
else
|
|
106
|
+
raise Textus::UsageError.new("intake for #{format} returned neither content nor body")
|
|
107
|
+
end
|
|
108
|
+
else
|
|
109
|
+
raise Textus::UsageError.new("unknown format #{format.inspect}")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
private_class_method :normalize_action_result
|
|
89
113
|
end
|
|
90
114
|
end
|
|
91
115
|
end
|