Bound a policy's authored text before compiling its scope
What does this MR do and why?
Splits Gitlab::PolicyStore::Ports::PolicyRepository#validate_text_limits! into
validate_authored_text_limits! and validate_compiled_text_limits!, and has
InMemoryPolicyRepository#create call one on each side of with_compiled_scope.
A single call could not serve both sides. scope_rego does not exist until the transpiler runs, so
bounding it has to happen after compilation, while name and description arrive from the caller
and can be bounded before. The transpiler also embeds the policy name in its output, so checking the
authored limits first means an over-limit name is rejected without first compiling a program that is
about to be discarded.
COMPILED_TEXT_ATTRIBUTES names the one attribute on the far side of that step. It is
private_constant because it is a detail of how the two halves divide TEXT_LIMITS rather than part
of the port's contract.
Only one of the three new examples guards the change: rejects an over-limit name before the scope reaches the transpiler fails against master, because nothing stopped the transpiler running first.
The other two pin behaviour the split must not alter, and pass either way by design.
Design decisions
No error message changes, deliberately. TEXT_LIMITS already iterated name ahead of
scope_rego, so a policy with both an over-limit name and an over-limit compiled scope reported the
name first before this change and still does. That makes the split invisible through the public API,
which is why the spec asserts the transpiler is never instantiated rather than asserting on a
message.
How to set up and validate locally
Every step runs on the rails console. The split changes when each limit is checked rather than which policies are valid, so the observable difference is whether the transpiler runs at all. Step 1 makes that visible.
- Announce every call to the transpiler, so the later steps can show whether it ran
Gitlab::PolicyStore::ScopeTranspiler.singleton_class.prepend(Module.new do
def new(...)
puts "ScopeTranspiler ran"
super
end
end)- Create a policy whose name is over the limit and whose scope would compile. Verify it prints the
message alone, with no
ScopeTranspiler ranabove it, because the authored limits are checked before compilation. Onmasterthe same call printsScopeTranspiler ranfirst, having compiled a program it then discards
begin
Gitlab::PolicyStore.create(
organization_id: 1,
name: "a" * 256,
trigger_type: "deployment_requested",
policy_scope: { "compliance_frameworks" => [{ "id" => 5 }] }
)
rescue Gitlab::PolicyStore::ValidationError => error
puts error.message
end
# => name exceeds maximum length of 255 characters- Confirm the compiled half still runs, by authoring a scope that compiles past the
scope_regolimit. Verify it printsScopeTranspiler ranand then the message, becausescope_regois bounded after the transpiler rather than skipped
limit = Gitlab::PolicyStore::Ports::PolicyRepository::TEXT_LIMITS[:scope_rego]
begin
Gitlab::PolicyStore.create(
organization_id: 1, name: "Wide scope", trigger_type: "deployment_requested",
policy_scope: { "projects" => { "including" => (1..limit).map { |id| { "id" => id } } } }
)
rescue Gitlab::PolicyStore::ValidationError => error
puts error.message
end
# => scope_rego exceeds maximum length of 4096 characters- Confirm the opposite path still saves. Create a policy within both limits and verify it returns a
compiled
scope_rego, because nothing about which policies are valid has changed
Gitlab::PolicyStore.create(
organization_id: 1, name: "Framework 5 only", trigger_type: "deployment_requested",
policy_scope: { "compliance_frameworks" => [{ "id" => 5 }] }
).scope_rego.lines.first
# => "package gitlab.scope\n"References
- Related to https://gitlab.com/gitlab-org/gitlab/-/work_items/606971
- Extracted from !249979 (merged) (open), which keeps the scope id coercion it shipped alongside. The two are independent: this one touches the port and the in-memory adapter, that one touches the scope transpiler
- Follows !249899 (merged) (merged), which established the repository boundary as the place a policy's attributes are normalized
- Epic: https://gitlab.com/groups/gitlab-org/-/epics/22937