twpayne / chezmoi

Manage your dotfiles across multiple diverse machines, securely.
https://www.chezmoi.io/
MIT License
12.9k stars 478 forks source link

`gitHubLatestRelease` and string functions #3339

Closed zacwest closed 10 months ago

zacwest commented 10 months ago

What exactly are you trying to do?

I'm guessing this is a Go Template thing that I'm holding wrong, but perhaps there's a better way to accomplish this or something can be done to improve it.

I'm trying to construct resource download URLs for GitHub released assets. For example, something like:

{{- $version := trimPrefix "v" (gitHubLatestRelease "twpayne/chezmoi").TagName -}}
{{- printf "https://github.com/twpayne/chezmoi/releases/download/v%s/chezmoi-%s-aarch64.rpm" $version $version }}

Works great, it outputs a URL like:

https://github.com/twpayne/chezmoi/releases/download/v2.41.0/chezmoi-2.41.0-aarch64.rpm

However, when I take out the trimPrefix for a repo that does not prefix release tags with 'v' I end up with something less expected:

{{- $version := (gitHubLatestRelease "twpayne/chezmoi").TagName -}}
{{- printf "https://github.com/twpayne/chezmoi/releases/download/v%s/chezmoi-%s-aarch64.rpm" $version $version }}

produces:

https://github.com/twpayne/chezmoi/releases/download/v%!s(*string=0x14000154540)/chezmoi-%!s(*string=0x14000154540)-aarch64.rpm

What have you tried so far?

My temporary hack is to use trim to do nothing but 'materialize' the pointer to an actual string value.

Some cursory searching produced toString as an option, but the version included in Chezmoi turns it into:

https://github.com/twpayne/chezmoi/releases/download/v0x140005ac1e0/chezmoi-0x140005ac1e0-aarch64.rpm

So slightly nicer pointer addresses, but pointer addresses nonetheless.

Where else have you checked for solutions?

Output of any commands you've tried with --verbose flag

No difference to the template rendering.

Output of chezmoi doctor

RESULT    CHECK                MESSAGE
ok        version              v2.40.4, commit 97de3c9738828f6f5c2c282b9e8114142f07edb5, built at 2023-10-29T16:51:24Z, built by Homebrew
warning   latest-version       v2.41.0
ok        os-arch              darwin/arm64
ok        uname                Darwin zac-mbp.local 23.1.0 Darwin Kernel Version 23.1.0: Mon Oct  9 21:27:24 PDT 2023; root:xnu-10002.41.9~6/RELEASE_ARM64_T6000 arm64
ok        go-version           go1.21.3 (gc)
ok        executable           /opt/homebrew/bin/chezmoi
ok        upgrade-method       brew-upgrade
ok        config-file          ~/.config/chezmoi/chezmoi.yaml, last modified 2023-10-07T08:29:01-07:00
ok        source-dir           ~/.local/share/chezmoi is a git working tree (clean)
ok        suspicious-entries   no suspicious entries
ok        working-tree         ~/.local/share/chezmoi is a git working tree (clean)
ok        dest-dir             ~ is a directory
ok        umask                022
ok        cd-command           found /opt/homebrew/bin/fish
ok        cd-args              /opt/homebrew/bin/fish
info      diff-command         not set
ok        edit-command         found ~/.local/bin/nova
ok        edit-args            nova -w
ok        git-command          found /opt/homebrew/bin/git, version 2.42.1
ok        merge-command        found /usr/bin/vimdiff
ok        shell-command        found /opt/homebrew/bin/fish
ok        shell-args           /opt/homebrew/bin/fish
ok        age-command          found /opt/homebrew/bin/age, version 1.1.1
ok        gpg-command          found /opt/homebrew/bin/gpg, version 2.4.3
info      pinentry-command     not set
ok        1password-command    found /opt/homebrew/bin/op, version 2.22.0
warning   bitwarden-command    found /opt/homebrew/bin/bw, cannot parse version from (node:1388) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
2023.10.0
info   bitwarden-secrets-command   bws not found in $PATH
info   dashlane-command            dcli not found in $PATH
info   doppler-command             doppler not found in $PATH
info   gopass-command              gopass not found in $PATH
info   keepassxc-command           keepassxc-cli not found in $PATH
info   keepassxc-db                not set
info   keeper-command              keeper not found in $PATH
info   lastpass-command            lpass not found in $PATH
info   pass-command                pass not found in $PATH
info   passhole-command            ph not found in $PATH
info   rbw-command                 rbw not found in $PATH
info   vault-command               vault not found in $PATH
info   vlt-command                 vlt not found in $PATH
info   secret-command              not set

Additional context

Add any other context about the problem here.

zydou commented 10 months ago

Is there a specific reason for using printf to obtain the URL? Does the following method meet your requirements?

{{- $version := (gitHubLatestRelease "junegunn/fzf").TagName -}}
https://github.com/junegunn/fzf/releases/download/{{ $version }}/fzf-{{ $version }}-{{ .chezmoi.os }}_{{ .chezmoi.arch }}.tar.gz

Output:

https://github.com/junegunn/fzf/releases/download/0.44.0/fzf-0.44.0-darwin_amd64.tar.gz
zacwest commented 10 months ago

Largely because I use the variable in a few other places (e.g. another $var set to print rather than printing inline). Just tried to slim down the examples here to the minimum case.

(I also find it much more readable, personally, when there's several substitutions.)

twpayne commented 10 months ago
https://github.com/twpayne/chezmoi/releases/download/v%!s(*string=0x14000154540)/chezmoi-%!s(*string=0x14000154540)-aarch64.rpm

What's happening here is that the printf verbs don't match the type being passed. Go's text/template does some automatic translation of types which resolves the problem when you use trimPrefix. Specifically:

  1. (gitHubLatestRelease "twpayne/chezmoi").TagName is actually a pointer to a string (*string).
  2. When you call trimPrefix, text/template sees that trimPrefix wants a string so text/template dereferences the pointer for you. The return value from trimPrefix is a string.

printf "%s" wants a string, but text/template doesn't know this as the type is in the value passed to printf, not in the function signature of printf itself. printf then prints %!s(*string=0x14000154540) instead, which means "I didn't get something I could convert to a string, I got something else instead, and that thing is a *string with this value".

sprig has a toString template function, but, sadly, like most things in sprig, the function is broken and does not help you here.

The ugly quick short-term work-around is to leave the call to trimPrefix in. I'm not sure what the best long term solution is.

bradenhilton commented 10 months ago

@zacwest The sprig semver function uses https://pkg.go.dev/github.com/Masterminds/semver/v3 which optionally supports v-prefixed versions:

❯ '{{ $version := semver "v1.2.3" }}{{ printf "%s" $version }}' | chezmoi execute-template
1.2.3

❯ '{{ $version := semver "1.2.3" }}{{ printf "%s" $version }}' | chezmoi execute-template
1.2.3

❯ '{{ $version := semver (gitHubLatestRelease "twpayne/chezmoi").TagName }}{{ printf "%s" $version }}' | chezmoi execute-template
2.41.0

❯ '{{ $version := semver (gitHubLatestRelease "gorhill/uBlock").TagName }}{{ printf "%s" $version }}' | chezmoi execute-template
1.53.4

uBlock tags are not prefixed.

halostatue commented 10 months ago

I'm not quite sure why this is showing incorrectly‡, but you can just call trim (removes trailing and leading spaces) which will properly convert this to a usable string value.

{{- $version := (gitHubLatestRelease "twpayne/chezmoi").TagName | trim -}}
{{- printf "https://github.com/twpayne/chezmoi/releases/download/v%s/chezmoi-%s-aarch64.rpm" $version $version }}

There is another alternative that does the right thing, but would be a breaking change to adopt in chezmoi:

$ chezmoi execute-template '{{- $version := (gitHubLatestRelease "twpayne/chezmoi" | toJson | fromJson).tag_name -}}
  {{- printf "https://github.com/twpayne/chezmoi/releases/download/v%s/chezmoi-%s-aarch64.rpm" $version $version }}'
https://github.com/twpayne/chezmoi/releases/download/vv2.41.0/chezmoi-v2.41.0-aarch64.rpm

‡ It appears that the value is returned as a string pointer, so changing %s to %v results in seeing the pointer address. I don't think that there's a way to dereference a pointer in go text templates, but sprig's trim* functions appear to work correctly with either string or string pointers.

If there is a fix to be found in chezmoi, I think it would be in simulating a JSON roundtrip for the GitHub values, rebuilding the result map with scalars rather than pointers. This would also be somewhat incompatible (it would break the exact flow of the fromJson | toJson because the key shape would no longer switch from TagName to tag_name), but I suspect that is less disruptive than forcing everyone to use .tag_name.

zydou commented 10 months ago

Here is another solution:

{{- $version := output "bash" "-c" "curl https://api.github.com/repos/junegunn/fzf/tags | jq -r '.[0].name'" | trim -}}

{{- printf "https://github.com/junegunn/fzf/releases/download/%s/fzf-%s-%s_%s.tar.gz" $version $version .chezmoi.os .chezmoi.arch -}}

Output:

https://github.com/junegunn/fzf/releases/download/0.44.0/fzf-0.44.0-linux_amd64.tar.gz

Because chezmoi only supports some basic GitHub template functions, this method uses GitHub's REST API and jq to get any info you want.

halostatue commented 10 months ago

That could be done with gitHubLatestTag or gitHubTags. If there are other GitHub APIs required, we would consider pull requests or feature suggestions — but these seem to be the ones most required. We also have jq built into chezmoi (via itchyny/gojq).

$ chezmoi execute-template '{{-
    $version := index ((gitHubTags "junegunn/fzf" | toJson | fromJson) | jq ".[0].name") 0
-}}{{-
    printf "https://github.com/junegunn/fzf/releases/download/%s/fzf-%s-%s_%s.tar.gz" $version $version .chezmoi.os .chezmoi.arch
-}}'
https://github.com/junegunn/fzf/releases/download/0.44.0/fzf-0.44.0-darwin_arm64.tar.gz

The use of index $list 0 is required because the output of jq is an array; this is unavoidable.

twpayne commented 10 months ago

Hopefully this is now resolved. Please re-open if needed.