diff --git a/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/config.json b/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/config.json new file mode 100644 index 0000000000..768d4a2354 --- /dev/null +++ b/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/config.json @@ -0,0 +1,6 @@ +{ + "showInput": true, + "showData": true, + "showTitles": false, + "titleSize": 4 +} diff --git a/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/data.json b/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/data.json new file mode 100644 index 0000000000..5abeb2ebc0 --- /dev/null +++ b/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/data.json @@ -0,0 +1,6 @@ +{ + "base_registries": [ + "registry.internal/prod", + "ghcr.io/example" + ] +} diff --git a/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/input.json b/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/input.json new file mode 100644 index 0000000000..38d5719e0b --- /dev/null +++ b/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/input.json @@ -0,0 +1,6 @@ +{ + "extra_registries": [ + "registry.internal/staging" + ], + "image": "docker.io/library/nginx:1.27" +} diff --git a/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/intro.md b/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/intro.md new file mode 100644 index 0000000000..c38f2a3c0b --- /dev/null +++ b/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/intro.md @@ -0,0 +1,5 @@ + + +`array.concat` appends one array to another. Policies often keep a fixed +base list (for example default registries) and extend it with values from +input or data for a particular tenant or environment. diff --git a/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/output.json b/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/output.json new file mode 100644 index 0000000000..f4a22352a6 --- /dev/null +++ b/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/output.json @@ -0,0 +1,11 @@ +{ + "allow": false, + "allowed_registries": [ + "registry.internal/prod", + "ghcr.io/example", + "registry.internal/staging" + ], + "deny": [ + "image \"docker.io/library/nginx:1.27\" is not from an allowed registry: [\"registry.internal/prod\", \"ghcr.io/example\", \"registry.internal/staging\"]" + ] +} diff --git a/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/policy.rego b/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/policy.rego new file mode 100644 index 0000000000..50df60d468 --- /dev/null +++ b/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/policy.rego @@ -0,0 +1,18 @@ +package play + +allowed_registries := array.concat(data.base_registries, input.extra_registries) + +default allow := false + +allow if { + some registry in allowed_registries + startswith(input.image, sprintf("%s/", [registry])) +} + +deny contains msg if { + not allow + msg := sprintf( + "image %q is not from an allowed registry: %v", + [input.image, allowed_registries], + ) +} diff --git a/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/title.txt b/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/title.txt new file mode 100644 index 0000000000..85ef598fec --- /dev/null +++ b/docs/docs/policy-reference/_examples/array/concat/merge-allowlists/title.txt @@ -0,0 +1 @@ +Merging a base allowlist with extra entries \ No newline at end of file diff --git a/docs/docs/policy-reference/_examples/object/get/optional-labels/config.json b/docs/docs/policy-reference/_examples/object/get/optional-labels/config.json new file mode 100644 index 0000000000..35c42cadd0 --- /dev/null +++ b/docs/docs/policy-reference/_examples/object/get/optional-labels/config.json @@ -0,0 +1,6 @@ +{ + "showInput": true, + "showData": false, + "showTitles": false, + "titleSize": 4 +} diff --git a/docs/docs/policy-reference/_examples/object/get/optional-labels/data.json b/docs/docs/policy-reference/_examples/object/get/optional-labels/data.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/docs/docs/policy-reference/_examples/object/get/optional-labels/data.json @@ -0,0 +1 @@ +{} diff --git a/docs/docs/policy-reference/_examples/object/get/optional-labels/input.json b/docs/docs/policy-reference/_examples/object/get/optional-labels/input.json new file mode 100644 index 0000000000..64592896e7 --- /dev/null +++ b/docs/docs/policy-reference/_examples/object/get/optional-labels/input.json @@ -0,0 +1,17 @@ +{ + "workloads": [ + { + "name": "frontend", + "labels": { + "app": "web", + "env": "prod" + } + }, + { + "name": "scratch", + "labels": { + "app": "jobs" + } + } + ] +} diff --git a/docs/docs/policy-reference/_examples/object/get/optional-labels/intro.md b/docs/docs/policy-reference/_examples/object/get/optional-labels/intro.md new file mode 100644 index 0000000000..e94fab9c9c --- /dev/null +++ b/docs/docs/policy-reference/_examples/object/get/optional-labels/intro.md @@ -0,0 +1,9 @@ + + +Kubernetes objects and API payloads often omit optional fields. `object.get` +reads a key (or a nested path) and returns a default when it is missing, so +the rest of the policy does not need extra existence checks. Using a path +array also covers the case where an intermediate field like `labels` is +undefined. + +Here, workloads without an `env` label are treated as `dev`. diff --git a/docs/docs/policy-reference/_examples/object/get/optional-labels/output.json b/docs/docs/policy-reference/_examples/object/get/optional-labels/output.json new file mode 100644 index 0000000000..2a20164bf4 --- /dev/null +++ b/docs/docs/policy-reference/_examples/object/get/optional-labels/output.json @@ -0,0 +1,9 @@ +{ + "deny": [ + "production workload \"frontend\" is missing labels.team" + ], + "envs": { + "frontend": "prod", + "scratch": "dev" + } +} diff --git a/docs/docs/policy-reference/_examples/object/get/optional-labels/policy.rego b/docs/docs/policy-reference/_examples/object/get/optional-labels/policy.rego new file mode 100644 index 0000000000..f7f681a7e6 --- /dev/null +++ b/docs/docs/policy-reference/_examples/object/get/optional-labels/policy.rego @@ -0,0 +1,15 @@ +package play + +# object.get(object, key, default) — key may also be a path array, which +# still returns the default when an intermediate field (like labels) is missing. +env_of(workload) := object.get(workload, ["labels", "env"], "dev") + +# Only production workloads need a team label. +deny contains msg if { + some w in input.workloads + env_of(w) == "prod" + not w.labels.team + msg := sprintf("production workload %q is missing labels.team", [w.name]) +} + +envs := {w.name: env_of(w) | some w in input.workloads} diff --git a/docs/docs/policy-reference/_examples/object/get/optional-labels/title.txt b/docs/docs/policy-reference/_examples/object/get/optional-labels/title.txt new file mode 100644 index 0000000000..73d7f80d46 --- /dev/null +++ b/docs/docs/policy-reference/_examples/object/get/optional-labels/title.txt @@ -0,0 +1 @@ +Reading optional labels with a default \ No newline at end of file diff --git a/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/config.json b/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/config.json new file mode 100644 index 0000000000..35c42cadd0 --- /dev/null +++ b/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/config.json @@ -0,0 +1,6 @@ +{ + "showInput": true, + "showData": false, + "showTitles": false, + "titleSize": 4 +} diff --git a/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/data.json b/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/data.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/data.json @@ -0,0 +1 @@ +{} diff --git a/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/input.json b/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/input.json new file mode 100644 index 0000000000..170ea290be --- /dev/null +++ b/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/input.json @@ -0,0 +1,4 @@ +{ + "granted": ["read:orders", "write:orders", "read:profile"], + "required": ["read:orders", "write:payments"] +} diff --git a/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/intro.md b/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/intro.md new file mode 100644 index 0000000000..3486d24055 --- /dev/null +++ b/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/intro.md @@ -0,0 +1,6 @@ + + +`intersection` returns the values common to every set you pass in. A typical +use is comparing a caller's granted scopes with the scopes an endpoint +requires: if the intersection is smaller than the requirement, something is +missing. diff --git a/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/output.json b/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/output.json new file mode 100644 index 0000000000..6ced71a28a --- /dev/null +++ b/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/output.json @@ -0,0 +1,21 @@ +{ + "allow": false, + "deny": [ + "missing required scopes: {\"write:payments\"}" + ], + "granted": [ + "read:orders", + "read:profile", + "write:orders" + ], + "missing": [ + "write:payments" + ], + "present": [ + "read:orders" + ], + "required": [ + "read:orders", + "write:payments" + ] +} diff --git a/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/policy.rego b/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/policy.rego new file mode 100644 index 0000000000..21c5826f4d --- /dev/null +++ b/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/policy.rego @@ -0,0 +1,17 @@ +package play + +granted := {s | some s in input.granted} +required := {s | some s in input.required} + +present := intersection({granted, required}) + +missing := required - present + +default allow := false + +allow if count(missing) == 0 + +deny contains msg if { + count(missing) > 0 + msg := sprintf("missing required scopes: %v", [missing]) +} diff --git a/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/title.txt b/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/title.txt new file mode 100644 index 0000000000..f153c2aa2a --- /dev/null +++ b/docs/docs/policy-reference/_examples/sets/intersection/required-scopes/title.txt @@ -0,0 +1 @@ +Checking that required scopes are present \ No newline at end of file diff --git a/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/config.json b/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/config.json new file mode 100644 index 0000000000..35c42cadd0 --- /dev/null +++ b/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/config.json @@ -0,0 +1,6 @@ +{ + "showInput": true, + "showData": false, + "showTitles": false, + "titleSize": 4 +} diff --git a/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/data.json b/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/data.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/data.json @@ -0,0 +1 @@ +{} diff --git a/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/input.json b/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/input.json new file mode 100644 index 0000000000..cf9d41949c --- /dev/null +++ b/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/input.json @@ -0,0 +1,6 @@ +{ + "user": "alice", + "role": "guest", + "action": "delete", + "resource": "orders/42" +} diff --git a/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/intro.md b/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/intro.md new file mode 100644 index 0000000000..93458d2d98 --- /dev/null +++ b/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/intro.md @@ -0,0 +1,5 @@ + + +`sprintf` formats a string with values from the policy. Admission and +authorization policies use it so users see _which_ field failed and _what_ +value was rejected, not only a bare `false`. diff --git a/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/output.json b/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/output.json new file mode 100644 index 0000000000..812701dbd2 --- /dev/null +++ b/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/output.json @@ -0,0 +1,5 @@ +{ + "deny": [ + "user alice with role guest cannot delete orders/42" + ] +} diff --git a/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/policy.rego b/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/policy.rego new file mode 100644 index 0000000000..16531e72b6 --- /dev/null +++ b/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/policy.rego @@ -0,0 +1,11 @@ +package play + +# Guests may read, but nothing else. +deny contains msg if { + input.role == "guest" + input.action != "read" + msg := sprintf( + "user %v with role %v cannot %v %v", + [input.user, input.role, input.action, input.resource], + ) +} diff --git a/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/title.txt b/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/title.txt new file mode 100644 index 0000000000..0415bf0532 --- /dev/null +++ b/docs/docs/policy-reference/_examples/strings/sprintf/deny-message/title.txt @@ -0,0 +1 @@ +Building a clear deny message \ No newline at end of file diff --git a/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/config.json b/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/config.json new file mode 100644 index 0000000000..35c42cadd0 --- /dev/null +++ b/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/config.json @@ -0,0 +1,6 @@ +{ + "showInput": true, + "showData": false, + "showTitles": false, + "titleSize": 4 +} diff --git a/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/data.json b/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/data.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/data.json @@ -0,0 +1 @@ +{} diff --git a/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/input.json b/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/input.json new file mode 100644 index 0000000000..ff7c11ae09 --- /dev/null +++ b/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/input.json @@ -0,0 +1,3 @@ +{ + "path": "/api/v2/users" +} diff --git a/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/intro.md b/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/intro.md new file mode 100644 index 0000000000..0c0bc0646e --- /dev/null +++ b/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/intro.md @@ -0,0 +1,7 @@ + + +When a policy only cares about the start of a string — for example an HTTP +path or a registry prefix — `startswith` is clearer (and usually safer) +than a loose `contains` check. + +This example allows only paths under `/api/v1/`. diff --git a/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/output.json b/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/output.json new file mode 100644 index 0000000000..1446c2cb78 --- /dev/null +++ b/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/output.json @@ -0,0 +1,6 @@ +{ + "allow": false, + "deny": [ + "path \"/api/v2/users\" is outside /api/v1/" + ] +} diff --git a/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/policy.rego b/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/policy.rego new file mode 100644 index 0000000000..e170b84dcd --- /dev/null +++ b/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/policy.rego @@ -0,0 +1,10 @@ +package play + +default allow := false + +allow if startswith(input.path, "/api/v1/") + +deny contains msg if { + not allow + msg := sprintf("path %q is outside /api/v1/", [input.path]) +} diff --git a/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/title.txt b/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/title.txt new file mode 100644 index 0000000000..7e34eaa02f --- /dev/null +++ b/docs/docs/policy-reference/_examples/strings/startswith/api-path-prefix/title.txt @@ -0,0 +1 @@ +Restricting requests to an API path prefix \ No newline at end of file diff --git a/docs/docs/policy-reference/_examples/units/parse/memory-limits/config.json b/docs/docs/policy-reference/_examples/units/parse/memory-limits/config.json new file mode 100644 index 0000000000..35c42cadd0 --- /dev/null +++ b/docs/docs/policy-reference/_examples/units/parse/memory-limits/config.json @@ -0,0 +1,6 @@ +{ + "showInput": true, + "showData": false, + "showTitles": false, + "titleSize": 4 +} diff --git a/docs/docs/policy-reference/_examples/units/parse/memory-limits/data.json b/docs/docs/policy-reference/_examples/units/parse/memory-limits/data.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/docs/docs/policy-reference/_examples/units/parse/memory-limits/data.json @@ -0,0 +1 @@ +{} diff --git a/docs/docs/policy-reference/_examples/units/parse/memory-limits/input.json b/docs/docs/policy-reference/_examples/units/parse/memory-limits/input.json new file mode 100644 index 0000000000..fbeaeb5dcd --- /dev/null +++ b/docs/docs/policy-reference/_examples/units/parse/memory-limits/input.json @@ -0,0 +1,13 @@ +{ + "namespace_memory_limit": "1Gi", + "containers": [ + { + "name": "app", + "memory_limit": "512Mi" + }, + { + "name": "batch", + "memory_limit": "2Gi" + } + ] +} diff --git a/docs/docs/policy-reference/_examples/units/parse/memory-limits/intro.md b/docs/docs/policy-reference/_examples/units/parse/memory-limits/intro.md new file mode 100644 index 0000000000..0b717a8194 --- /dev/null +++ b/docs/docs/policy-reference/_examples/units/parse/memory-limits/intro.md @@ -0,0 +1,6 @@ + + +Container specs often express memory with different suffixes (`Mi`, `Gi`, +bare numbers). `units.parse` turns those strings into numbers so a policy +can compare them. Here, a container is rejected when its memory limit is +higher than the namespace cap. diff --git a/docs/docs/policy-reference/_examples/units/parse/memory-limits/output.json b/docs/docs/policy-reference/_examples/units/parse/memory-limits/output.json new file mode 100644 index 0000000000..59015a7f77 --- /dev/null +++ b/docs/docs/policy-reference/_examples/units/parse/memory-limits/output.json @@ -0,0 +1,6 @@ +{ + "deny": [ + "container \"batch\" memory limit 2Gi exceeds namespace cap 1Gi" + ], + "max_memory": 1073741824 +} diff --git a/docs/docs/policy-reference/_examples/units/parse/memory-limits/policy.rego b/docs/docs/policy-reference/_examples/units/parse/memory-limits/policy.rego new file mode 100644 index 0000000000..ffc8e9194d --- /dev/null +++ b/docs/docs/policy-reference/_examples/units/parse/memory-limits/policy.rego @@ -0,0 +1,14 @@ +package play + +# Cap for the whole namespace, normalized to a number. +max_memory := units.parse(input.namespace_memory_limit) + +# Containers whose limit is above the namespace cap. +deny contains msg if { + some c in input.containers + units.parse(c.memory_limit) > max_memory + msg := sprintf( + "container %q memory limit %s exceeds namespace cap %s", + [c.name, c.memory_limit, input.namespace_memory_limit], + ) +} diff --git a/docs/docs/policy-reference/_examples/units/parse/memory-limits/title.txt b/docs/docs/policy-reference/_examples/units/parse/memory-limits/title.txt new file mode 100644 index 0000000000..458b5f0420 --- /dev/null +++ b/docs/docs/policy-reference/_examples/units/parse/memory-limits/title.txt @@ -0,0 +1 @@ +Comparing memory limits with different units \ No newline at end of file diff --git a/docs/docs/policy-reference/builtins/array.mdx b/docs/docs/policy-reference/builtins/array.mdx index 10fcedcd72..d38a84a764 100644 --- a/docs/docs/policy-reference/builtins/array.mdx +++ b/docs/docs/policy-reference/builtins/array.mdx @@ -3,3 +3,13 @@ title: Array Built-ins sidebar_label: Arrays --- + +## Examples + +### `array.concat` + +`array.concat` returns a new array with the elements of the second array +appended to the first. Policies use it to extend a base list — for example +default registries or hosts — with extra values from input or data. + + diff --git a/docs/docs/policy-reference/builtins/object.mdx b/docs/docs/policy-reference/builtins/object.mdx index 95e87cdcd7..1c586dd507 100644 --- a/docs/docs/policy-reference/builtins/object.mdx +++ b/docs/docs/policy-reference/builtins/object.mdx @@ -15,3 +15,14 @@ sidebar_label: Objects in `{ "foo/bar~": "baz" }`. - The `json` string `paths` may be an array of string path segments rather than a `/` separated string. For example the path `a/b/c` can be passed in as `["a", "b", "c"]`. + +## Examples + +### `object.get` + +`object.get` reads a key from an object and returns a default when the key +is missing. The second argument can also be a path array to walk nested +objects. That is useful for optional labels, annotations, or config keys +that callers are allowed to omit. + + diff --git a/docs/docs/policy-reference/builtins/sets.mdx b/docs/docs/policy-reference/builtins/sets.mdx index c7c575dc04..7705f61a5b 100644 --- a/docs/docs/policy-reference/builtins/sets.mdx +++ b/docs/docs/policy-reference/builtins/sets.mdx @@ -3,3 +3,14 @@ title: Set Built-ins sidebar_label: Sets --- + +## Examples + +### `intersection` + +`intersection` returns the values that appear in every set of a set-of-sets. +Comparing a caller's granted scopes with the scopes an endpoint requires is +a typical case: anything left after subtracting the intersection from the +requirement is missing. + + diff --git a/docs/docs/policy-reference/builtins/strings.mdx b/docs/docs/policy-reference/builtins/strings.mdx index 1a4ef35db5..89d78492be 100644 --- a/docs/docs/policy-reference/builtins/strings.mdx +++ b/docs/docs/policy-reference/builtins/strings.mdx @@ -46,3 +46,21 @@ about it in the [keywords section](/docs/policy-reference/keywords/contains). + +### `startswith` + +`startswith` reports whether a string begins with a given prefix. Prefer it +over `contains` when the match must be at the front of the value (HTTP paths, +registry prefixes, file paths). + + + +### `sprintf` + +`sprintf` builds a string from a format and a list of values. Deny rules use +it to put the failing field and value into the message returned to the caller. + +See also the note at the top of this page about how `sprintf` pre-processes +values (for example with `%T`). + + diff --git a/docs/docs/policy-reference/builtins/units.mdx b/docs/docs/policy-reference/builtins/units.mdx index 298414652b..7760dbb843 100644 --- a/docs/docs/policy-reference/builtins/units.mdx +++ b/docs/docs/policy-reference/builtins/units.mdx @@ -3,3 +3,17 @@ title: Unit Built-ins sidebar_label: Units --- + +## Examples + +### `units.parse` + +`units.parse` turns a string with an optional unit suffix into a number. +It accepts decimal SI suffixes (`K`, `M`, `G`, …), binary suffixes +(`Ki`, `Mi`, `Gi`, …), and a lower-case `m` for milli. `m` and `M` are +case-sensitive so milli and mega stay distinct. + +A common use is comparing resource limits that arrive with different +suffixes in the same policy check. + +