Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make goreleaser archives reproducible #6299

Merged
merged 1 commit into from
Dec 23, 2024

Conversation

alexbozhenko
Copy link
Contributor

@alexbozhenko alexbozhenko commented Dec 23, 2024

Use commit time in mod_timestamp, as documented in:
https://goreleaser.com/customization/builds/#reproducible-builds
https://goreleaser.com/blog/reproducible-builds/
https://goreleaser.com/customization/templates/?h=templates#common-fields

Test plan:

Before.

Build two times:

goreleaser release --snapshot --clean -f .goreleaser.yml
mv dist/ ~/tmp/dist_before
goreleaser release --snapshot --clean -f .goreleaser.yml 
vimdiff dist/SHA256SUMS ~/tmp/dist_before/SHA256SUMS

Observe all the shasums are different:
image

After:

Do the build two times,

goreleaser release --snapshot --clean -f .goreleaser.yml
mv dist/ ~/tmp/dist_after
goreleaser release --snapshot --clean -f .goreleaser.yml 
vimdiff dist/SHA256SUMS ~/tmp/dist_after/SHA256SUMS

Observe that only rpm and deb packages are different
image

There was a feature added to goreleaser to make packages reproducible too, but I haven't figured out how to use it yet:
goreleaser/nfpm#748
I asked in Discord. We can tackle that separately

Signed-off-by: Alex Bozhenko [email protected]

@alexbozhenko alexbozhenko force-pushed the goreleaser_reproducible branch from a6e8d60 to 48d31ed Compare December 23, 2024 20:04
@alexbozhenko alexbozhenko force-pushed the goreleaser_reproducible branch from 48d31ed to 7751bc9 Compare December 23, 2024 20:06
@alexbozhenko alexbozhenko changed the title use commit time in mod_timestamp Make goreleaser archives reproducible Dec 23, 2024
@alexbozhenko alexbozhenko marked this pull request as ready for review December 23, 2024 20:15
@alexbozhenko alexbozhenko requested a review from a team as a code owner December 23, 2024 20:15
Copy link
Member

@wallyqs wallyqs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, the binary is already reproducible but this would make the tarballs from the release reproducible as well.

@derekcollison derekcollison merged commit c4b778c into nats-io:main Dec 23, 2024
2 checks passed
derekcollison added a commit that referenced this pull request Jan 23, 2025
Similar to #6299
Make packages set mtime to `commitdate`.

Upgrade goreleaser to the latest 2.6.1, which includes the feature used
in this PR: goreleaser/goreleaser#5392

```
go install github.com/goreleaser/goreleaser/[email protected]
goreleaser release --snapshot --clean -f .goreleaser.yml
mv dist/ ~/tmp/dist_before
goreleaser release --snapshot --clean -f .goreleaser.yml 

# now, all the artifacts of the two builds are exactly the same. Only sbom files are different(because those include timestamps)
vimdiff dist/SHA256SUMS ~/tmp/dist_before/SHA256SUMS
```

![image](https://github.com/user-attachments/assets/6ca9bc37-301a-4cda-b4ab-4d724762a31c)



Signed-off-by: Alex Bozhenko <[email protected]>
derekcollison added a commit that referenced this pull request Feb 20, 2025
## Details
We have reproducible binaries,
[rpms/debs](#6359) and
[archives](#6299).
And `goreleaser.yml` holds [all the
knobs](https://goreleaser.com/customization/builds/go/) that can be
tuned to build the binary.

The only thing that affects the build and is not defined in the
goreleaser config is the go toolchain version.
Currently, we set the version of the go toolchain that is used for
releases in Travis:

<https://github.com/nats-io/nats-server/blob/5e6017135b4b1d333b435ff55b720275a39bb7af/.travis.yml#L11-L12>

<https://github.com/nats-io/nats-server/blob/5e6017135b4b1d333b435ff55b720275a39bb7af/.travis.yml#L67>

I spent some time [trying to understand the behavior of go and toolchain
directives](https://alexbozhenko.github.io/posts/2024-12-19-understand-go-toolchain-directive-or-your-money-back/).
I think setting the toolchain used for releases in .goreleser.yml would
make it more explicit and future-proof.


With this change, any human or script who has `go>1.21.0` and goreleaser
installed can checkout the repo at any commit, run one command, and get
binaries that will be _exactly_ as if we were cutting a release on that
commit.

```
goreleaser build --snapshot --clean --single-target
```

Several places would benefit from not having to worry about keeping
toolchain and all the go build flags in sync:

* get-nats.io(already uses gorelaser, but toolchain used to build
depends on the build host):

<https://github.com/ConnectEverything/client-tools/blob/eba999ac9a1e107205fbf89ba230df3f80458028/build-nightlies.sh#L184-L188>
* <redacted> number of private repos

If we land this, we can update the above places to use `goreleaser
build`, thus making sure we use _exactly the same_ binary everywhere,
and forever forget about managing/updating go versions in other places
that need to build the binary

## Reproducible test plan

### Before

Behavior is not hermetic. We depend on toolchain that happens to be
installed on the build host.

1.
    <details>
<summary>Local go(`1.21.0`) < one that is specified in go.mod. Go will
download and use toolchain from go.mod:</summary>

        ```
        # go version
        go version go1.21.0 linux/amd64

# TARGET='linux_amd64' goreleaser build --snapshot --clean
--single-target
# go version -m dist/nats-server_linux_amd64_v1/nats-server | grep go1
        dist/nats-server_linux_amd64_v1/nats-server: go1.22.8
        ```
    </details>

2.
    <details>
<summary>Local go(`1.23.3`) > one that is specified in go.mod. Local
toolchain will be used:</summary>

    ```
    # wget https://go.dev/dl/go1.23.3.linux-amd64.tar.gz
# sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf
go1.23.3.linux-amd64.tar.gz
    # go version
    go version go1.23.3 linux/amd64
    
# TARGET='linux_amd64' goreleaser build --snapshot --clean
--single-target
# go version -m dist/nats-server_linux_amd64_v1/nats-server | grep go1
    dist/nats-server_linux_amd64_v1/nats-server: go1.23.3
    ```

    </details>

### After

1.
    <details>
<summary>Local go version (1.23.4) did not affect what was used for the
binary</summary>

It will download (just like it [downloads all the
modules](https://youtu.be/KqTySYYhPUE?feature=shared&t=229))
the version of the toolchain specified in goreleaer config, and use it
for building the binary.

    ```

    # go version
    go version go1.23.4 linux/amd64

# TARGET='linux_amd64' goreleaser build --snapshot --clean
--single-target
# go version -m dist/nats-server_linux_amd64_v1/nats-server | grep go1
    dist/nats-server_linux_amd64_v1/nats-server: go1.23.5

    # sha256sum  dist/nats-server_linux_amd64_v1/nats-server
95d52ed8656f74abd0c0576d8d9be50fcc00562c8e18215d1f1f04b8c0b6fc3d
dist/nats-server_linux_amd64_v1/nats-server
    ```

    </details>

2.
    <details>
<summary>Any go >= go1.21.0 (released 2023-08-08) will build exactly the
same binary:</summary>

    ```
    # wget <https://go.dev/dl/go1.21.0.linux-amd64.tar.gz>
# sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf
go1.21.0.linux-amd64.tar.gz
    # go version
    go version go1.21.0 linux/amd64

# TARGET='linux_amd64' goreleaser build --snapshot --clean
--single-target
# go version -m dist/nats-server_linux_amd64_v1/nats-server | grep go1
    dist/nats-server_linux_amd64_v1/nats-server: go1.23.5

    # sha256sum  dist/nats-server_linux_amd64_v1/nats-server
95d52ed8656f74abd0c0576d8d9be50fcc00562c8e18215d1f1f04b8c0b6fc3d
dist/nats-server_linux_amd64_v1/nats-server
    ```

    </details>

3. To build using specific toolchain one would set the
`GORELEASER_TOOLCHAIN` env variable:

    ```
# GORELEASER_TOOLCHAIN="go1.22.8" TARGET='linux_amd64' goreleaser build
--snapshot --clean --single-target
# go version -m dist/nats-server_linux_amd64_v1/nats-server | grep go1
    dist/nats-server_linux_amd64_v1/nats-server: go1.22.8
    ```


Note that starting from go1.24.0 we can use the [tool
directive](https://tip.golang.org/doc/modules/managing-dependencies#tools
) in go.mod, and even version goreleaser itself. It will look something
like this:

```
go tool goreleaser build --snapshot --clean --single-target
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants