Let applications contribute template functions - #1117
Merged
Conversation
A certificate template can only call the functions this library provides, and some of what a CA wants to put in a template is knowledge this library does not have and should not acquire. There is currently no way to supply one: WithTemplate builds its function map internally, and it is reached through provisioners the CA constructs for itself, so no call path exists along which a function could be passed down. RegisterTemplateFunc is that path. x509util and sshutil each keep their own registry, so registering for one kind of certificate does not silently affect the other, and an application wanting a function in both asks for both. Registration refuses to shadow a built-in. A template calling toJson or fail has to get this library's implementation; a registry that allowed the substitution would make a template's meaning depend on which packages a binary happened to link. Duplicate names, non-functions and names that are not Go identifiers are refused for the same reason -- better an error at start-up than a template that parses and does something else. A registered function receives only its own arguments, as any template function does. One that needs the data being rendered takes it as a parameter and the template supplies it with $, which is the value the template was executed with wherever it appears -- unlike the dot, which a range block rebinds. No new dependencies. Change-Type: feature Release-Note: yes Audience: developer Impact: none Breaking: false Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6LAjGgci2KLKYDbdWVcw
|
|
Register refuses to shadow a built-in, which is right for the accidental case and wrong for the intended one. An application may decide its own implementation of a function is the one its templates should get -- and if that is the intent, it should be stated rather than left to depend on the order in which two packages happen to touch the function map. Replace states it. It validates the name and the value the same way, and skips only the reserved-name and duplicate checks. Change-Type: feature Release-Note: yes Audience: developer Impact: none Breaking: false Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6LAjGgci2KLKYDbdWVcw
joshdrake
marked this pull request as ready for review
August 26, 2026 23:07
They were carrying rationale that belongs in the pull request, not the package. Cut to what a caller needs: what the function does, the start-up ordering requirement, and the "$" convention -- in the length and voice the rest of the package uses. Change-Type: docs Release-Note: no Audience: developer Impact: none Breaking: false Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6LAjGgci2KLKYDbdWVcw
maraino
approved these changes
Aug 27, 2026
maraino
left a comment
Contributor
There was a problem hiding this comment.
LGTM, replace feels a little overkill
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A certificate template can only call the functions this library provides, and some of what a CA wants to put in a template is knowledge this library doesn't have and shouldn't acquire. There's currently no way to supply one:
WithTemplatebuilds its function map internally, and it's reached through provisioners the CA constructs for itself, so no call path exists along which a function could be passed down.RegisterTemplateFuncis that path.x509utilandsshutileach keep their own registry, so registering for one kind of certificate doesn't silently affect the other, and an application wanting a function in both asks for both.What it refuses
Shadowing a built-in. A template calling
toJsonorfailhas to get this library's implementation; a registry that allowed the substitution would make a template's meaning depend on which packages a binary happened to link. Duplicate names, non-functions, and names that aren't Go identifiers are refused for the same reason — better an error at start-up than a template that parses and does something else.The
$conventionA registered function receives only its own arguments, as any template function does. One that needs the data being rendered takes it as a parameter and the template supplies it with
$— which is the value the template was executed with wherever it appears, unlike the dot, which a range block rebinds. There's a test covering exactly that case.Notes
text/templateresolves function names at parse time, so a template rendered before the function is registered fails to parse. The doc comment says so, and a test demonstrates it.go test ./...passes, including-race.Why this rather than a CEL environment in the library
This replaces #1116, which took the other approach — a public CEL package here, with an extension registry. That put cel-go in this library's public API to serve one embedder's needs. This is the smaller seam: crypto gains a way to accept a function and stays out of the expression business entirely, and the CEL machinery lives with the application that knows what a
deviceis.🤖 Generated with Claude Code
https://claude.ai/code/session_016N6LAjGgci2KLKYDbdWVcw