Conversation
A template in archives.files[].dst landed in the archive verbatim, so
dst: docs_{{ .Os }} produced a directory literally named docs_{{ .Os }}.
The src on the same entry is already templated, and nfpm and srpm already
apply the template to both source and destination.
The apply sits below the no-match check so an entry whose glob matched
nothing is still skipped rather than aborting on a bad template.
|
Bump, with something specific rather than a generic ping. The That is why it shows as blocked, on a missing required check rather than a failing one. It needs someone to approve the workflow run before CI can say anything either way. There are no conflicts with If the change is not one you want, that is a perfectly good answer too and I will close it. |
caarlos0
left a comment
There was a problem hiding this comment.
Reviewed the full diff against origin/main (merge base), plus every caller of archivefiles.Eval.
Verdict: the fix is correct and the approach is right — two small changes before merge.
What I verified, so it isn't re-litigated:
- No double-apply.
archivefiles.go:43is the onlyApplyonDestinationin the non-test tree. Post-Evalconsumers only concatenate:sourcearchive/source.go:108,makeself/makeself.go:262,archive/archive.go:389. - No shared-state mutation.
archivefiles.go:22ranges by value, sof.Destination = dstwrites a copy. That matters, becausearchive.gorunscreate()in parallel goroutines over one sharedarch.Filesbacking array.go test -race ./internal/pipe/archive/... ./internal/archivefiles/...is clean. - No machine-generated
dst. The defaults atarchive/archive.go:84setSourceonly;makeself.go:344copies user config verbatim;archivefiles.go:64is output, never fed back in. - The
src/dstparity claim in the description holds.tmpl.Newnever populates.Os; onlyWithArtifactdoes, andarchive.go:224skips it formeta: true, as doessourcearchive/source.go:106. Sodstgains no failure modesrcdid not already have. - The new subtests are real regression tests. Reverse-applying only the production hunk fails both:
templated_dstgetsvar/{{ .Env.FOLDER }}/d.txtinstead ofvar/d/d.txt, andtemplated_dst_errorgetsAn error is expected but got nil. They call the realEvalwith a realtmpl.Template, no mocks. All four affected packages pass.
One correction to the description: there is an escape hatch for a literal {{. dst: '{{ "{{" }}literal{{ "}}" }}' renders {{literal}} — I ran it. So the compatibility cost is smaller than stated.
Verification gaps worth stating:
- Pro.
templated_files[].dstis closed-source and not in this repo. Worth confirming Pro does not templateFiles[].dstitself before callingEval; the OSS path is clean. - Integration coverage. There is no test anywhere in
internal/pipe/{archive,sourcearchive,makeself}with a template in aDestination. All coverage of this change is unit-level ininternal/archivefiles. That is proportionate for a six-line change, not a blocker.
The two requested changes are inline.
Reviewed by a bot.
| dst, err := template.Apply(f.Destination) | ||
| if err != nil { | ||
| return result, fmt.Errorf("failed to apply template %s: %w", f.Destination, err) | ||
| } | ||
| f.Destination = dst |
There was a problem hiding this comment.
Whether a bad dst template fails the release depends on what is on disk.
This block sits below the len(files) == 0 / continue at lines 35-41, so the template is only applied when the glob matched something. src is templated at line 23, before fileglob.Glob, so it always fails.
Trigger:
archives:
- files:
- src: "docs/*"
dst: "docs_{{ .Env.NOPE }}"With docs/ empty, the release succeeds and logs no files matched. Add one file to docs/ and the same config now aborts the release. Same config, two outcomes, decided by the working tree — so a typo can sit dormant in a repo for months and then break a release, or fail on one platform and pass on another when the glob is platform-dependent.
That is the exact src/dst asymmetry this PR sets out to remove, so it seems worth closing here rather than leaving a second one behind. The description argues the placement protects against "aborting the release on a bad template", but the len(files) == 0 skip exists to tolerate globs that match nothing (see #4013, which made it warn only for non-default globs) — not to tolerate malformed config. A malformed template is a config error and should be reported deterministically.
Smallest fix — move the apply up next to the src one, which also collapses it into the existing call:
glob, err := template.Apply(f.Source)
if err != nil {
return result, fmt.Errorf("failed to apply template %s: %w", f.Source, err)
}
if err := template.ApplyAll(&f.Destination); err != nil {
return result, err
}That also matches internal/pipe/nfpm/nfpm.go, which does ApplyAll(&content.Source, &content.Destination, ...) in one go — the precedent cited in the description. Note the current error text also duplicates the template string, since tmpl errors already include it; ApplyAll avoids that too.
Needed test: a subtest with a glob that matches nothing and a bad dst template, asserting the error — e.g. src: "./testdata/nope/**/*", dst: "var/{{ .Env.NOPE }}/", testlib.RequireTemplateError. Nothing currently pins this behaviour in either direction; rlcp no results at archivefiles_test.go:198 uses a literal dst.
| # | ||
| # Templates: allowed. |
There was a problem hiding this comment.
This annotation is redundant, and it is the only one of three pages that gets it.
Line 117 already carries # Templates: allowed. directly above files:, which covers every entry in the list including this one. The two sibling pages documenting the same dst through the same archivefiles.Eval — package/source.md:35 and package/makeself.md:129 — use exactly that key-level annotation and have no per-entry one.
So after this hunk, archives.md annotates the same fact twice while the other two pages that gained the same capability say nothing extra, which reads as though dst templating is archives-only. It isn't: source.files[].dst and makeself.files[].dst both route through Eval (sourcearchive/source.go:106, makeself/makeself.go:257).
Smallest fix: drop the hunk. The existing line 117 is already true and now more completely so.
If the intent is to call out dst specifically, this file's own convention is field-level — see owner: at lines 138-139 — so it should sit directly above dst: and be added to all three pages, not above the list item in one of them.
Move dst template application to immediately after src apply, before the file existence check, ensuring deterministic failure of malformed templates regardless of glob matches. Use template.ApplyAll to eliminate redundant error wrapping. Add test coverage for bad dst template and remove redundant docs annotation.
|
Both changes are in You were right that the outcome depended on the working tree. The apply now sits next to the The new subtest is Dropping the wrap also cleans the message up, since I left the The docs hunk is dropped, so On the escape hatch you are right and my description was wrong: On Pro, I cannot check
|
archives.files[].dstnever goes through the template engine, so a template in it lands in the archive verbatim.srcon the same entry is templated, andinternal/pipe/nfpm/nfpm.goalready doesApplyAll(&content.Source, &content.Destination, ...)for the equivalentcontentsentry, as doesinternal/pipe/srpm/srpm.go. This makesdstbehave like its ownsrcand like its nfpm counterpart.Two things worth flagging before review rather than after.
A
dstcontaining a literal{{now errors instead of passing through. Nothing else does: Windows paths,$, backticks and single braces all still work, since Go templates only react to{{. The escape hatch is the usual one,dst: '{{ "{{" }}literal{{ "}}" }}'gives a directory named{{literal}}, which I ran.The template context is not the same at every call site.
internal/pipe/sourcearchivepasses a baretmpl.New(ctx), andinternal/pipe/archiveonly attaches an artifact when the archive has binaries, so.Osis unavailable insource.filesand inmeta: truearchives. That is not new:srcalready fails there the same way today, which I checked before writing this.So
dstbecomes consistent withsrcrather than gaining a new failure mode of its own. If you would rather.Oswere bound in those two call sites, that is a separate change and I am happy to do it.The apply sits next to the
srcone, above the glob, so a malformeddstaborts the release deterministically instead of depending on whether the glob happened to match anything on disk.Verification: three subtests added,
templated dst,templated dst errorandtemplated dst error when the glob matches nothing, mirroring the existingtemplated srcpair. Each fails without the production change and no other test changes state.go testis green on every package that depends oninternal/archivefiles, and-raceis clean on the archive pipes. The archive listing above is from a realgoreleaser release --snapshotrun with binaries built from this branch and frommain.No docs change:
archives.mdalready carries# Templates: allowed.abovefiles:, which covers every entry in the list.AI disclosure: written with Claude Code. I ran the release before and after, checked the template context at each call site, and ran the mutations myself.