textus 0.10.5 → 0.14.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 +60 -40
- data/CHANGELOG.md +318 -3
- data/README.md +34 -27
- data/SPEC.md +226 -145
- data/docs/conventions.md +8 -8
- data/lib/textus/application/context.rb +4 -0
- data/lib/textus/application/reads/blame.rb +1 -1
- data/lib/textus/application/reads/deps.rb +15 -0
- data/lib/textus/application/reads/freshness.rb +4 -4
- data/lib/textus/application/reads/get.rb +9 -12
- data/lib/textus/application/reads/list.rb +15 -0
- data/lib/textus/application/reads/policy_explain.rb +2 -2
- data/lib/textus/application/reads/published.rb +15 -0
- data/lib/textus/application/reads/rdeps.rb +15 -0
- data/lib/textus/application/reads/schema_envelope.rb +15 -0
- data/lib/textus/application/reads/stale.rb +15 -0
- data/lib/textus/application/reads/uid.rb +15 -0
- data/lib/textus/application/reads/validate_all.rb +15 -0
- data/lib/textus/application/reads/where.rb +15 -0
- data/lib/textus/application/refresh/all.rb +2 -2
- data/lib/textus/application/refresh/orchestrator.rb +1 -1
- data/lib/textus/application/refresh/worker.rb +8 -8
- data/lib/textus/application/writes/accept.rb +26 -8
- data/lib/textus/application/writes/build.rb +12 -49
- data/lib/textus/application/writes/delete.rb +1 -1
- data/lib/textus/application/writes/mv.rb +144 -0
- data/lib/textus/application/writes/publish.rb +42 -10
- data/lib/textus/application/writes/put.rb +1 -1
- data/lib/textus/application/writes/reject.rb +37 -0
- data/lib/textus/builder/pipeline.rb +1 -1
- data/lib/textus/builder/renderer/json.rb +1 -1
- data/lib/textus/builder/renderer/yaml.rb +1 -1
- data/lib/textus/cli/group/key.rb +1 -1
- data/lib/textus/cli/group/refresh.rb +21 -0
- data/lib/textus/cli/group/rule.rb +11 -0
- data/lib/textus/cli/verb/accept.rb +1 -2
- data/lib/textus/cli/verb/audit.rb +3 -3
- data/lib/textus/cli/verb/blame.rb +1 -2
- data/lib/textus/cli/verb/build.rb +6 -2
- data/lib/textus/cli/verb/delete.rb +1 -2
- data/lib/textus/cli/verb/deps.rb +1 -1
- data/lib/textus/cli/verb/freshness.rb +1 -2
- data/lib/textus/cli/verb/get.rb +2 -3
- data/lib/textus/cli/verb/hook_run.rb +3 -2
- data/lib/textus/cli/verb/hooks.rb +1 -1
- data/lib/textus/cli/verb/{migrate_keys.rb → key_normalize.rb} +1 -1
- data/lib/textus/cli/verb/list.rb +1 -1
- data/lib/textus/cli/verb/mv.rb +1 -1
- data/lib/textus/cli/verb/published.rb +1 -1
- data/lib/textus/cli/verb/put.rb +3 -3
- data/lib/textus/cli/verb/rdeps.rb +1 -1
- data/lib/textus/cli/verb/refresh.rb +1 -2
- data/lib/textus/cli/verb/reject.rb +1 -1
- data/lib/textus/cli/verb/{policy_explain.rb → rule_explain.rb} +2 -3
- data/lib/textus/cli/verb/{policy_list.rb → rule_list.rb} +3 -3
- data/lib/textus/cli/verb/schema.rb +1 -1
- data/lib/textus/cli/verb/uid.rb +1 -1
- data/lib/textus/cli/verb/where.rb +1 -1
- data/lib/textus/cli/verb.rb +9 -3
- data/lib/textus/cli.rb +6 -6
- data/lib/textus/doctor/check/handler_allowlist.rb +1 -1
- data/lib/textus/doctor/check/illegal_keys.rb +39 -16
- data/lib/textus/doctor/check/intake_registration.rb +4 -4
- data/lib/textus/doctor/check/protocol_version.rb +47 -0
- data/lib/textus/doctor/check/{policy_ambiguity.rb → rule_ambiguity.rb} +6 -6
- data/lib/textus/doctor/check/schema_violations.rb +1 -1
- data/lib/textus/doctor.rb +6 -5
- data/lib/textus/domain/freshness/evaluator.rb +1 -1
- data/lib/textus/domain/permission.rb +4 -4
- data/lib/textus/domain/policy/predicates/human_accept.rb +31 -0
- data/lib/textus/domain/policy/predicates/schema_valid.rb +50 -0
- data/lib/textus/domain/policy/promotion.rb +45 -0
- data/lib/textus/entry/base.rb +28 -0
- data/lib/textus/entry/json.rb +59 -0
- data/lib/textus/entry/markdown.rb +46 -0
- data/lib/textus/entry/text.rb +35 -0
- data/lib/textus/entry/yaml.rb +59 -0
- data/lib/textus/entry.rb +16 -0
- data/lib/textus/envelope.rb +44 -14
- data/lib/textus/errors.rb +24 -5
- data/lib/textus/hooks/builtin.rb +5 -5
- data/lib/textus/hooks/dispatcher.rb +1 -1
- data/lib/textus/hooks/dsl.rb +3 -10
- data/lib/textus/hooks/loader.rb +1 -2
- data/lib/textus/hooks/registry.rb +22 -21
- data/lib/textus/infra/refresh/detached.rb +1 -1
- data/lib/textus/init.rb +25 -34
- data/lib/textus/intro.rb +65 -9
- data/lib/textus/manifest/entry/parser.rb +84 -0
- data/lib/textus/manifest/entry/validators/events.rb +21 -0
- data/lib/textus/manifest/entry/validators/format_matrix.rb +26 -0
- data/lib/textus/manifest/entry/validators/index_filename.rb +45 -0
- data/lib/textus/manifest/entry/validators/inject_intro.rb +18 -0
- data/lib/textus/manifest/entry/validators/publish_each.rb +37 -0
- data/lib/textus/manifest/entry/validators.rb +20 -0
- data/lib/textus/manifest/entry.rb +38 -189
- data/lib/textus/manifest/{policies.rb → rules.rb} +12 -10
- data/lib/textus/manifest/schema.rb +49 -0
- data/lib/textus/manifest.rb +50 -24
- data/lib/textus/migrate_keys.rb +1 -1
- data/lib/textus/operations/reads.rb +39 -0
- data/lib/textus/operations/refresh.rb +27 -0
- data/lib/textus/operations/writes.rb +21 -0
- data/lib/textus/operations.rb +44 -0
- data/lib/textus/projection.rb +9 -8
- data/lib/textus/refresh.rb +4 -5
- data/lib/textus/schema/tools.rb +8 -7
- data/lib/textus/store/reader.rb +1 -1
- data/lib/textus/store/staleness/intake_check.rb +1 -1
- data/lib/textus/store/validator.rb +3 -3
- data/lib/textus/store/writer.rb +5 -74
- data/lib/textus/store.rb +1 -55
- data/lib/textus/version.rb +2 -2
- data/lib/textus.rb +1 -0
- metadata +35 -10
- data/lib/textus/cli/group/policy.rb +0 -11
- data/lib/textus/composition.rb +0 -72
- data/lib/textus/proposal.rb +0 -10
- data/lib/textus/store/mover.rb +0 -167
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
|
|
3
|
+
module Textus
|
|
4
|
+
module Application
|
|
5
|
+
module Writes
|
|
6
|
+
class Mv
|
|
7
|
+
MovePlan = Data.define(
|
|
8
|
+
:old_key, :new_key, :old_path, :new_path,
|
|
9
|
+
:new_mentry, :uid, :etag_before
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
def initialize(ctx:, bus:)
|
|
13
|
+
@ctx = ctx
|
|
14
|
+
@bus = bus
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call(old_key, new_key, dry_run: false)
|
|
18
|
+
plan, pre_env = prepare_plan(old_key, new_key)
|
|
19
|
+
return dry_run_result(plan) if dry_run
|
|
20
|
+
|
|
21
|
+
plan = ensure_uid!(plan, pre_env: pre_env)
|
|
22
|
+
etag_after = perform_move!(plan)
|
|
23
|
+
new_envelope = record_move(plan, etag_after: etag_after)
|
|
24
|
+
success_result(plan, new_envelope: new_envelope)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def manifest = @ctx.store.manifest
|
|
30
|
+
def reader = @ctx.store.reader
|
|
31
|
+
|
|
32
|
+
def prepare_plan(old_key, new_key)
|
|
33
|
+
manifest.validate_key!(old_key)
|
|
34
|
+
manifest.validate_key!(new_key)
|
|
35
|
+
raise UsageError.new("mv: old and new keys are identical") if old_key == new_key
|
|
36
|
+
|
|
37
|
+
old_mentry, old_path, = manifest.resolve(old_key)
|
|
38
|
+
raise UnknownKey.new(old_key) unless File.exist?(old_path)
|
|
39
|
+
|
|
40
|
+
new_mentry, new_path, = manifest.resolve(new_key)
|
|
41
|
+
validate_zone_and_format!(old_mentry, new_mentry)
|
|
42
|
+
validate_writer!(old_mentry, old_key)
|
|
43
|
+
raise UsageError.new("mv: target '#{new_key}' already exists at #{new_path}") if File.exist?(new_path)
|
|
44
|
+
|
|
45
|
+
pre_env = reader.get(old_key)
|
|
46
|
+
plan = MovePlan.new(
|
|
47
|
+
old_key: old_key, new_key: new_key,
|
|
48
|
+
old_path: old_path, new_path: new_path,
|
|
49
|
+
new_mentry: new_mentry,
|
|
50
|
+
uid: pre_env.uid, etag_before: pre_env.etag
|
|
51
|
+
)
|
|
52
|
+
[plan, pre_env]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def validate_zone_and_format!(old_mentry, new_mentry)
|
|
56
|
+
if old_mentry.zone != new_mentry.zone
|
|
57
|
+
raise UsageError.new(
|
|
58
|
+
"mv: cross-zone move refused (#{old_mentry.zone} → #{new_mentry.zone}). " \
|
|
59
|
+
"Use put+delete for cross-zone moves.",
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
return if old_mentry.format == new_mentry.format
|
|
63
|
+
|
|
64
|
+
raise UsageError.new("mv: format mismatch (#{old_mentry.format} → #{new_mentry.format}); refusing.")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def validate_writer!(mentry, key)
|
|
68
|
+
writers = manifest.zone_writers(mentry.zone)
|
|
69
|
+
return if writers.include?(@ctx.role)
|
|
70
|
+
|
|
71
|
+
raise WriteForbidden.new(key, mentry.zone, writers: writers)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def ensure_uid!(plan, pre_env:)
|
|
75
|
+
return plan if plan.uid
|
|
76
|
+
|
|
77
|
+
env = Textus::Application::Writes::Put.new(ctx: @ctx, bus: @bus).call(
|
|
78
|
+
plan.old_key,
|
|
79
|
+
meta: pre_env.meta,
|
|
80
|
+
body: pre_env.body,
|
|
81
|
+
content: pre_env.content,
|
|
82
|
+
suppress_events: true,
|
|
83
|
+
)
|
|
84
|
+
plan.with(uid: env.uid, etag_before: env.etag)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def perform_move!(plan)
|
|
88
|
+
FileUtils.mkdir_p(File.dirname(plan.new_path))
|
|
89
|
+
FileUtils.mv(plan.old_path, plan.new_path)
|
|
90
|
+
rewrite_name_for_mv!(plan.new_mentry, plan.new_path, plan.new_key)
|
|
91
|
+
Etag.for_file(plan.new_path)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def record_move(plan, etag_after:)
|
|
95
|
+
extras = {
|
|
96
|
+
"from_key" => plan.old_key, "to_key" => plan.new_key,
|
|
97
|
+
"from_path" => plan.old_path, "to_path" => plan.new_path,
|
|
98
|
+
"uid" => plan.uid
|
|
99
|
+
}
|
|
100
|
+
extras["correlation_id"] = @ctx.correlation_id if @ctx.correlation_id
|
|
101
|
+
|
|
102
|
+
@ctx.store.audit_log.append(
|
|
103
|
+
role: @ctx.role, verb: "mv", key: plan.new_key,
|
|
104
|
+
etag_before: plan.etag_before, etag_after: etag_after,
|
|
105
|
+
extras: extras
|
|
106
|
+
)
|
|
107
|
+
new_envelope = reader.get(plan.new_key)
|
|
108
|
+
@bus.publish(:entry_renamed,
|
|
109
|
+
store: @ctx.with_role(@ctx.role),
|
|
110
|
+
key: plan.new_key,
|
|
111
|
+
from_key: plan.old_key,
|
|
112
|
+
to_key: plan.new_key,
|
|
113
|
+
envelope: new_envelope,
|
|
114
|
+
correlation_id: @ctx.correlation_id)
|
|
115
|
+
new_envelope
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def dry_run_result(plan)
|
|
119
|
+
{
|
|
120
|
+
"protocol" => PROTOCOL, "ok" => true, "dry_run" => true,
|
|
121
|
+
"from_key" => plan.old_key, "to_key" => plan.new_key,
|
|
122
|
+
"from_path" => plan.old_path, "to_path" => plan.new_path,
|
|
123
|
+
"uid" => plan.uid
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def success_result(plan, new_envelope:)
|
|
128
|
+
{
|
|
129
|
+
"protocol" => PROTOCOL, "ok" => true,
|
|
130
|
+
"from_key" => plan.old_key, "to_key" => plan.new_key,
|
|
131
|
+
"from_path" => plan.old_path, "to_path" => plan.new_path,
|
|
132
|
+
"uid" => plan.uid,
|
|
133
|
+
"envelope" => new_envelope.to_h_for_wire
|
|
134
|
+
}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def rewrite_name_for_mv!(mentry, new_path, new_key)
|
|
138
|
+
basename = new_key.split(".").last
|
|
139
|
+
Entry.for_format(mentry.format).rewrite_name(new_path, basename)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -1,23 +1,55 @@
|
|
|
1
1
|
module Textus
|
|
2
2
|
module Application
|
|
3
3
|
module Writes
|
|
4
|
+
# Copies nested-leaf entries to their `publish_each:` targets. Fires
|
|
5
|
+
# `:file_published` for each copy. Mirror of `Build` for the publish
|
|
6
|
+
# half — split out from the old Build per ADR 0007.
|
|
4
7
|
class Publish
|
|
5
8
|
def initialize(ctx:, bus:)
|
|
6
9
|
@ctx = ctx
|
|
7
10
|
@bus = bus
|
|
8
11
|
end
|
|
9
12
|
|
|
10
|
-
def call(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
def call(prefix: nil)
|
|
14
|
+
repo_root = File.dirname(store.root)
|
|
15
|
+
out = []
|
|
16
|
+
manifest.entries.each do |mentry|
|
|
17
|
+
next unless mentry.nested && mentry.publish_each
|
|
18
|
+
next if prefix && !mentry.key.start_with?(prefix) && !prefix.start_with?("#{mentry.key}.")
|
|
19
|
+
|
|
20
|
+
manifest.enumerate(prefix: mentry.key).each do |row|
|
|
21
|
+
next unless row[:manifest_entry].equal?(mentry)
|
|
22
|
+
next if prefix && !row[:key].start_with?(prefix) && row[:key] != prefix
|
|
23
|
+
|
|
24
|
+
out << publish_leaf(mentry, row, repo_root)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
{ "protocol" => Textus::PROTOCOL, "published_leaves" => out }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def store = @ctx.store
|
|
33
|
+
def manifest = store.manifest
|
|
34
|
+
|
|
35
|
+
def publish_leaf(mentry, row, repo_root)
|
|
36
|
+
target_rel = mentry.publish_target_for(row[:key])
|
|
37
|
+
target_abs = File.expand_path(File.join(repo_root, target_rel))
|
|
38
|
+
unless target_abs.start_with?(File.expand_path(repo_root) + File::SEPARATOR)
|
|
39
|
+
raise PublishError.new(
|
|
40
|
+
"entry '#{mentry.key}': publish_each target '#{target_rel}' for key '#{row[:key]}' escapes repo root",
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
Textus::Infra::Publisher.publish(source: row[:path], target: target_abs, store_root: store.root)
|
|
45
|
+
@bus.publish(:file_published,
|
|
46
|
+
store: @ctx.with_role(@ctx.role),
|
|
47
|
+
key: row[:key],
|
|
48
|
+
envelope: store.reader.get(row[:key]),
|
|
49
|
+
source: row[:path],
|
|
50
|
+
target: target_abs,
|
|
20
51
|
correlation_id: @ctx.correlation_id)
|
|
52
|
+
{ "key" => row[:key], "source" => row[:path], "target" => target_abs }
|
|
21
53
|
end
|
|
22
54
|
end
|
|
23
55
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Application
|
|
3
|
+
module Writes
|
|
4
|
+
class Reject
|
|
5
|
+
def initialize(ctx:, bus:)
|
|
6
|
+
@ctx = ctx
|
|
7
|
+
@bus = bus
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call(pending_key)
|
|
11
|
+
raise ProposalError.new("only human role can reject proposals; got '#{@ctx.role}'") unless @ctx.role == "human"
|
|
12
|
+
|
|
13
|
+
mentry, = @ctx.store.manifest.resolve(pending_key)
|
|
14
|
+
unless mentry.in_proposal_zone?
|
|
15
|
+
raise ProposalError.new("reject: '#{pending_key}' is not in a proposal zone (zone=#{mentry.zone})")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
env = @ctx.store.reader.get(pending_key)
|
|
19
|
+
proposal = env.meta&.dig("proposal") or
|
|
20
|
+
raise ProposalError.new("entry has no proposal block: #{pending_key}")
|
|
21
|
+
target_key = proposal["target_key"] or
|
|
22
|
+
raise ProposalError.new("proposal missing target_key")
|
|
23
|
+
|
|
24
|
+
Textus::Application::Writes::Delete.new(ctx: @ctx, bus: @bus).call(pending_key, suppress_events: true)
|
|
25
|
+
|
|
26
|
+
@bus.publish(:proposal_rejected,
|
|
27
|
+
store: @ctx.with_role(@ctx.role),
|
|
28
|
+
key: pending_key,
|
|
29
|
+
target_key: target_key,
|
|
30
|
+
correlation_id: @ctx.correlation_id)
|
|
31
|
+
|
|
32
|
+
{ "protocol" => PROTOCOL, "rejected" => pending_key, "target_key" => target_key }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -10,7 +10,7 @@ module Textus
|
|
|
10
10
|
from = Array(mentry.projection&.fetch("select", nil)).compact
|
|
11
11
|
meta["from"] = from unless from.empty?
|
|
12
12
|
meta["template"] = mentry.template if mentry.template
|
|
13
|
-
reduce = mentry.projection&.dig("
|
|
13
|
+
reduce = mentry.projection&.dig("transform")
|
|
14
14
|
meta["reduce"] = reduce if reduce
|
|
15
15
|
|
|
16
16
|
out = { "_meta" => meta }
|
|
@@ -28,7 +28,7 @@ module Textus
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def default_shape(mentry, data)
|
|
31
|
-
if mentry.projection && mentry.projection["
|
|
31
|
+
if mentry.projection && mentry.projection["transform"] && data.is_a?(Hash) && !data.key?("entries")
|
|
32
32
|
data
|
|
33
33
|
elsif data.is_a?(Hash) && data["entries"].is_a?(Array)
|
|
34
34
|
{ "entries" => data["entries"] }
|
|
@@ -28,7 +28,7 @@ module Textus
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def default_shape(mentry, data)
|
|
31
|
-
if mentry.projection && mentry.projection["
|
|
31
|
+
if mentry.projection && mentry.projection["transform"] && data.is_a?(Hash) && !data.key?("entries")
|
|
32
32
|
data
|
|
33
33
|
elsif data.is_a?(Hash) && data["entries"].is_a?(Array)
|
|
34
34
|
{ "entries" => data["entries"] }
|
data/lib/textus/cli/group/key.rb
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
class CLI
|
|
3
|
+
class Group
|
|
4
|
+
class Refresh < Group
|
|
5
|
+
self.cli_name = "refresh"
|
|
6
|
+
subcommands["stale"] = Verb::RefreshStale
|
|
7
|
+
|
|
8
|
+
def parse(argv)
|
|
9
|
+
if argv.first == "stale"
|
|
10
|
+
argv.shift
|
|
11
|
+
@sub_klass = Verb::RefreshStale
|
|
12
|
+
else
|
|
13
|
+
@sub_klass = Verb::Refresh
|
|
14
|
+
end
|
|
15
|
+
@sub = @sub_klass.new(stdin: @stdin, stdout: @stdout, stderr: @stderr, cwd: @cwd)
|
|
16
|
+
@sub.parse(argv)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -6,8 +6,7 @@ module Textus
|
|
|
6
6
|
|
|
7
7
|
def call(store)
|
|
8
8
|
key = positional.shift or raise UsageError.new("accept requires a key")
|
|
9
|
-
|
|
10
|
-
emit(Textus::Composition.writes_accept(ctx).call(key))
|
|
9
|
+
emit(operations_for(store).writes.accept.call(key))
|
|
11
10
|
end
|
|
12
11
|
end
|
|
13
12
|
end
|
|
@@ -11,9 +11,9 @@ module Textus
|
|
|
11
11
|
option :limit, "--limit=N"
|
|
12
12
|
|
|
13
13
|
def call(store)
|
|
14
|
-
|
|
15
|
-
since_time = since && Textus::Application::Reads::Audit.parse_since(since, now: ctx.now)
|
|
16
|
-
rows =
|
|
14
|
+
ops = operations_for(store)
|
|
15
|
+
since_time = since && Textus::Application::Reads::Audit.parse_since(since, now: ops.ctx.now)
|
|
16
|
+
rows = ops.reads.audit.call(
|
|
17
17
|
key: key_filter,
|
|
18
18
|
zone: zone,
|
|
19
19
|
role: role_filter,
|
|
@@ -6,8 +6,7 @@ module Textus
|
|
|
6
6
|
|
|
7
7
|
def call(store)
|
|
8
8
|
key = positional.shift or raise UsageError.new("blame requires a key")
|
|
9
|
-
|
|
10
|
-
rows = Textus::Composition.blame(ctx).call(key: key, limit: limit&.to_i)
|
|
9
|
+
rows = operations_for(store).reads.blame.call(key: key, limit: limit&.to_i)
|
|
11
10
|
emit({ "verb" => "blame", "key" => key, "rows" => rows })
|
|
12
11
|
end
|
|
13
12
|
end
|
|
@@ -5,8 +5,12 @@ module Textus
|
|
|
5
5
|
option :prefix, "--prefix=K"
|
|
6
6
|
|
|
7
7
|
def call(store)
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
ops = Textus::Operations.for(store, role: "builder")
|
|
9
|
+
build_res = ops.writes.build.call(prefix: prefix)
|
|
10
|
+
publish_res = ops.writes.publish.call(prefix: prefix)
|
|
11
|
+
emit({ "protocol" => Textus::PROTOCOL,
|
|
12
|
+
"built" => build_res["built"],
|
|
13
|
+
"published_leaves" => publish_res["published_leaves"] })
|
|
10
14
|
end
|
|
11
15
|
end
|
|
12
16
|
end
|
|
@@ -7,8 +7,7 @@ module Textus
|
|
|
7
7
|
|
|
8
8
|
def call(store)
|
|
9
9
|
key = positional.shift or raise UsageError.new("delete requires a key")
|
|
10
|
-
|
|
11
|
-
emit(Textus::Composition.writes_delete(ctx).call(key, if_etag: if_etag))
|
|
10
|
+
emit(operations_for(store).writes.delete.call(key, if_etag: if_etag))
|
|
12
11
|
end
|
|
13
12
|
end
|
|
14
13
|
end
|
data/lib/textus/cli/verb/deps.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Textus
|
|
|
4
4
|
class Deps < Verb
|
|
5
5
|
def call(store)
|
|
6
6
|
key = positional.shift or raise UsageError.new("deps requires a key")
|
|
7
|
-
emit({ "key" => key, "deps" => store.deps(key) })
|
|
7
|
+
emit({ "key" => key, "deps" => operations_for(store).reads.deps.call(key) })
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
end
|
|
@@ -6,8 +6,7 @@ module Textus
|
|
|
6
6
|
option :zone, "--zone=Z"
|
|
7
7
|
|
|
8
8
|
def call(store)
|
|
9
|
-
|
|
10
|
-
rows = Textus::Composition.freshness(ctx).call(prefix: prefix, zone: zone)
|
|
9
|
+
rows = operations_for(store).reads.freshness.call(prefix: prefix, zone: zone)
|
|
11
10
|
emit({ "verb" => "freshness", "rows" => rows })
|
|
12
11
|
end
|
|
13
12
|
end
|
data/lib/textus/cli/verb/get.rb
CHANGED
|
@@ -6,11 +6,10 @@ module Textus
|
|
|
6
6
|
|
|
7
7
|
def call(store)
|
|
8
8
|
key = positional.shift or raise UsageError.new("get requires a key")
|
|
9
|
-
|
|
10
|
-
result = Textus::Composition.reads_get(ctx).call(key)
|
|
9
|
+
result = operations_for(store).reads.get.call(key)
|
|
11
10
|
raise Textus::UnknownKey.new(key, suggestions: store.manifest.suggestions_for(key)) if result.nil?
|
|
12
11
|
|
|
13
|
-
emit(result)
|
|
12
|
+
emit(result.to_h_for_wire)
|
|
14
13
|
end
|
|
15
14
|
end
|
|
16
15
|
end
|
|
@@ -15,7 +15,8 @@ module Textus
|
|
|
15
15
|
@raw_argv.each do |tok|
|
|
16
16
|
case tok
|
|
17
17
|
when /\A--as=(.+)\z/ then as_flag = ::Regexp.last_match(1)
|
|
18
|
-
when /\A--
|
|
18
|
+
when /\A--output=/ then next
|
|
19
|
+
when /\A--format=/ then raise FlagRenamed.new("--format", "--output")
|
|
19
20
|
when /\A--([\w-]+)=(.*)\z/ then args[::Regexp.last_match(1)] = ::Regexp.last_match(2)
|
|
20
21
|
else
|
|
21
22
|
raise UsageError.new("unknown arg to 'hook run #{name}': #{tok}")
|
|
@@ -23,7 +24,7 @@ module Textus
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
role = Role.resolve(flag: as_flag, env: ENV, root: store.root)
|
|
26
|
-
callable = store.registry.rpc_callable(:
|
|
27
|
+
callable = store.registry.rpc_callable(:resolve_intake, name)
|
|
27
28
|
view = Application::Context.new(store: store, role: role)
|
|
28
29
|
|
|
29
30
|
begin
|
data/lib/textus/cli/verb/list.rb
CHANGED
data/lib/textus/cli/verb/mv.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Textus
|
|
|
8
8
|
def call(store)
|
|
9
9
|
old_key = positional.shift or raise UsageError.new("mv requires <old-key> <new-key>")
|
|
10
10
|
new_key = positional.shift or raise UsageError.new("mv requires <old-key> <new-key>")
|
|
11
|
-
emit(store.mv(old_key, new_key,
|
|
11
|
+
emit(operations_for(store).writes.mv.call(old_key, new_key, dry_run: dry_run || false))
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
end
|
data/lib/textus/cli/verb/put.rb
CHANGED
|
@@ -15,7 +15,7 @@ module Textus
|
|
|
15
15
|
raw = @stdin.read
|
|
16
16
|
payload =
|
|
17
17
|
if fetch_name
|
|
18
|
-
callable = store.registry.rpc_callable(:
|
|
18
|
+
callable = store.registry.rpc_callable(:resolve_intake, fetch_name)
|
|
19
19
|
result =
|
|
20
20
|
begin
|
|
21
21
|
Timeout.timeout(Textus::Application::Refresh::Worker::FETCH_TIMEOUT_SECONDS) do
|
|
@@ -43,8 +43,8 @@ module Textus
|
|
|
43
43
|
meta = payload["_meta"] || {}
|
|
44
44
|
body = payload["body"] || ""
|
|
45
45
|
if_etag = payload["if_etag"]
|
|
46
|
-
|
|
47
|
-
emit(
|
|
46
|
+
result = Textus::Operations.for(store, role: role).writes.put.call(key, meta: meta, body: body, if_etag: if_etag)
|
|
47
|
+
emit(result.to_h_for_wire)
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
end
|
|
@@ -4,7 +4,7 @@ module Textus
|
|
|
4
4
|
class Rdeps < Verb
|
|
5
5
|
def call(store)
|
|
6
6
|
key = positional.shift or raise UsageError.new("rdeps requires a key")
|
|
7
|
-
emit({ "key" => key, "rdeps" => store.rdeps(key) })
|
|
7
|
+
emit({ "key" => key, "rdeps" => operations_for(store).reads.rdeps.call(key) })
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
end
|
|
@@ -6,8 +6,7 @@ module Textus
|
|
|
6
6
|
|
|
7
7
|
def call(store)
|
|
8
8
|
key = positional.shift or raise UsageError.new("refresh requires a key")
|
|
9
|
-
|
|
10
|
-
emit(Textus::Composition.refresh_worker(ctx).run(key))
|
|
9
|
+
emit(operations_for(store).refresh.worker.run(key).to_h_for_wire)
|
|
11
10
|
end
|
|
12
11
|
end
|
|
13
12
|
end
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
module Textus
|
|
2
2
|
class CLI
|
|
3
3
|
class Verb
|
|
4
|
-
class
|
|
4
|
+
class RuleExplain < Verb
|
|
5
5
|
def call(store)
|
|
6
6
|
key = positional.shift or raise UsageError.new("policy explain requires a KEY")
|
|
7
|
-
|
|
8
|
-
result = Textus::Composition.policy_explain(ctx).call(key: key)
|
|
7
|
+
result = operations_for(store).reads.policy_explain.call(key: key)
|
|
9
8
|
emit({ "verb" => "policy_explain" }.merge(result.transform_keys(&:to_s)))
|
|
10
9
|
end
|
|
11
10
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
module Textus
|
|
2
2
|
class CLI
|
|
3
3
|
class Verb
|
|
4
|
-
class
|
|
4
|
+
class RuleList < Verb
|
|
5
5
|
def call(store)
|
|
6
|
-
policies = store.manifest.
|
|
6
|
+
policies = store.manifest.rules.blocks.map do |b|
|
|
7
7
|
row = { "match" => b.match }
|
|
8
8
|
if b.refresh
|
|
9
9
|
row["refresh"] = {
|
|
@@ -13,7 +13,7 @@ module Textus
|
|
|
13
13
|
}
|
|
14
14
|
end
|
|
15
15
|
row["handler_allowlist"] = b.handler_allowlist.handlers if b.handler_allowlist
|
|
16
|
-
row["
|
|
16
|
+
row["promotion"] = { "requires" => b.promote.requires } if b.promote
|
|
17
17
|
row["retention"] = b.retention if b.retention
|
|
18
18
|
row
|
|
19
19
|
end
|
data/lib/textus/cli/verb/uid.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Textus
|
|
|
4
4
|
class Uid < Verb
|
|
5
5
|
def call(store)
|
|
6
6
|
key = positional.shift or raise UsageError.new("uid requires a key")
|
|
7
|
-
emit({ "key" => key, "uid" => store.uid(key) })
|
|
7
|
+
emit({ "key" => key, "uid" => operations_for(store).reads.uid.call(key) })
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
end
|
data/lib/textus/cli/verb.rb
CHANGED
|
@@ -41,9 +41,10 @@ module Textus
|
|
|
41
41
|
self.class.options.each do |name, optspec|
|
|
42
42
|
o.on(optspec) { |v| public_send(:"#{name}=", v) }
|
|
43
43
|
end
|
|
44
|
-
o.on("--
|
|
44
|
+
o.on("--output=FMT") { |v| fmt = v }
|
|
45
|
+
o.on("--format=FMT") { |_v| raise FlagRenamed.new("--format", "--output") }
|
|
45
46
|
end.permute!(argv)
|
|
46
|
-
raise UsageError.new("only --
|
|
47
|
+
raise UsageError.new("only --output=json is supported in v1") unless fmt == "json"
|
|
47
48
|
|
|
48
49
|
@positional = argv.dup
|
|
49
50
|
end
|
|
@@ -69,7 +70,12 @@ module Textus
|
|
|
69
70
|
# Convenience for verbs whose only pre-call boilerplate is
|
|
70
71
|
# resolving the role and wrapping it in a context.
|
|
71
72
|
def context_for(store)
|
|
72
|
-
Textus::
|
|
73
|
+
Textus::Operations.for(store, role: resolved_role(store)).ctx
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Returns an Operations instance bound to the resolved role.
|
|
77
|
+
def operations_for(store)
|
|
78
|
+
Textus::Operations.for(store, role: resolved_role(store))
|
|
73
79
|
end
|
|
74
80
|
end
|
|
75
81
|
end
|