From b1a58bdd79fdf8176fa163e4601e16ca2af71031 Mon Sep 17 00:00:00 2001 From: Jonas Meyer-Ohle <19151471+jonas-meyer@users.noreply.github.com> Date: Mon, 16 Feb 2026 18:35:52 +0000 Subject: [PATCH] encoding/yaml: add support for YAML tags via `@yaml(,tag)` attribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds support for encoding YAML tags by specifying them in @yaml(tag="...") attributes on CUE fields. The tag value is applied to the encoded YAML node. This enables generating YAML files that use custom tags, like those required by authentik blueprints (!KeyOf, !Env, !Find, etc.) and other YAML-based tools that rely on tags for special processing. Tags can be applied to scalars, sequences, and mappings. The implementation extracts tags from field attributes in the AST and sets them on the corresponding yaml.Node during encoding. Fixes #2316 Closes #4199 as merged as of commit 1206905c8. Signed-off-by: Jonas Meyer-Ohle <19151471+jonas-meyer@users.noreply.github.com> Change-Id: Ibac6b3c97539a87e215821ffbb71735b4f5330d4 Reviewed-on: https://cue.gerrithub.io/c/cue-lang/cue/+/1231452 Reviewed-by: Daniel Martí Reviewed-by: Chief Cueckoo TryBot-Result: CUEcueckoo Unity-Result: CUE porcuepine --- internal/encoding/yaml/encode.go | 31 ++++++++++++ internal/encoding/yaml/encode_test.go | 72 +++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/internal/encoding/yaml/encode.go b/internal/encoding/yaml/encode.go index 155182017..4e76ddb66 100644 --- a/internal/encoding/yaml/encode.go +++ b/internal/encoding/yaml/encode.go @@ -234,6 +234,28 @@ func encodeExprs(exprs []ast.Expr) (n *yaml.Node, err error) { return n, nil } +// extractYAMLTag looks for @yaml(,tag="...") attribute and returns the tag value. +// Returns an empty string if no @yaml attribute or no tag argument is found. +// Returns an error if the attribute is malformed. +func extractYAMLTag(attrs []*ast.Attribute) (string, error) { + for _, attr := range attrs { + key, body := attr.Split() + if key != "yaml" { + continue + } + parsed := internal.ParseAttrBody(attr.Pos(), body) + if parsed.Err != nil { + return "", parsed.Err + } + if val, found, err := parsed.Lookup(1, "tag"); err != nil { + return "", err + } else if found { + return val, nil + } + } + return "", nil +} + // encodeDecls converts a sequence of declarations to a value. If it encounters // an embedded value, it will return this expression. This is more relaxed for // structs than is currently allowed for CUE, but the expectation is that this @@ -289,6 +311,15 @@ func encodeDecls(decls []ast.Decl) (n *yaml.Node, err error) { if err != nil { return nil, err } + + yamlTag, err := extractYAMLTag(x.Attrs) + if err != nil { + return nil, err + } + if yamlTag != "" { + value.Tag = yamlTag + } + lastHead = label lastFoot = value addDocs(x, label, value) diff --git a/internal/encoding/yaml/encode_test.go b/internal/encoding/yaml/encode_test.go index 10ed40b6b..7c3bc2758 100644 --- a/internal/encoding/yaml/encode_test.go +++ b/internal/encoding/yaml/encode_test.go @@ -243,6 +243,78 @@ route: receiver: pager group_by: [alertname, cluster] `, + }, { + name: "yaml_tag_scalar", + in: ` + key: "value" @yaml(,tag="!Custom") + env: "VAR_NAME" @yaml(,tag="!Env") + `, + out: ` +key: !Custom value +env: !Env VAR_NAME + `, + }, { + name: "yaml_tag_sequence", + in: ` + lookup: ["table", ["key", "value"]] @yaml(,tag="!Find") + items: [1, 2, 3] @yaml(,tag="!Seq") + `, + out: ` +lookup: !Find [table, [key, value]] +items: !Seq [1, 2, 3] + `, + }, { + name: "yaml_tag_mapping", + in: ` + config: { + key: "value" + count: 42 + } @yaml(,tag="!Map") + `, + out: ` +config: !Map + key: value + count: 42 + `, + }, { + name: "yaml_tag_mixed", + in: ` + plain: "no-tag" + tagged: "has-tag" @yaml(,tag="!Custom") + nested: { + field: "value" @yaml(,tag="!Nested") + } + `, + out: ` +plain: no-tag +tagged: !Custom has-tag +nested: + field: !Nested value + `, + }, { + name: "yaml_tag_verbatim", + in: ` + custom: "value" @yaml(,tag="!") + `, + out: ` +custom: !%3Ctag:example.com,2000:app/foo%3E value + `, + }, { + name: "yaml_tag_url", + in: ` + item: "value" @yaml(,tag="!") + `, + out: ` +item: !%3Chttps://example.com/schema/v1%3E value + `, + }, { + name: "yaml_attribute_without_tag", + in: ` + field: "value" @yaml(,other="ignored") + `, + out: ` +field: value + `, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { -- 2.51.2