1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-03-17 20:47:50 +02:00

feat: checksums.split (#4707)

closes #4618
closes #4641

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
This commit is contained in:
Carlos Alexandro Becker 2024-03-17 15:16:50 -03:00 committed by GitHub
parent 280e68431a
commit 54ee014b50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 339 additions and 75 deletions

View File

@ -143,16 +143,18 @@ func (t Type) String() string {
}
const (
ExtraID = "ID"
ExtraBinary = "Binary"
ExtraExt = "Ext"
ExtraFormat = "Format"
ExtraWrappedIn = "WrappedIn"
ExtraBinaries = "Binaries"
ExtraRefresh = "Refresh"
ExtraReplaces = "Replaces"
ExtraDigest = "Digest"
ExtraSize = "Size"
ExtraID = "ID"
ExtraBinary = "Binary"
ExtraExt = "Ext"
ExtraFormat = "Format"
ExtraWrappedIn = "WrappedIn"
ExtraBinaries = "Binaries"
ExtraRefresh = "Refresh"
ExtraReplaces = "Replaces"
ExtraDigest = "Digest"
ExtraSize = "Size"
ExtraChecksum = "Checksum"
ExtraChecksumOf = "ChecksumOf"
)
// Extras represents the extra fields in an artifact.
@ -258,7 +260,12 @@ func (a Artifact) Checksum(algorithm string) (string, error) {
if _, err := io.Copy(h, file); err != nil {
return "", fmt.Errorf("failed to checksum: %w", err)
}
return hex.EncodeToString(h.Sum(nil)), nil
check := hex.EncodeToString(h.Sum(nil))
if a.Extra == nil {
a.Extra = make(Extras)
}
a.Extra[ExtraChecksum] = fmt.Sprintf("%s:%s", algorithm, check)
return check, nil
}
var noRefresh = func() error { return nil }

View File

@ -36,23 +36,72 @@ func (Pipe) Skip(ctx *context.Context) bool { return ctx.Config.Checksum.Disable
// Default sets the pipe defaults.
func (Pipe) Default(ctx *context.Context) error {
if ctx.Config.Checksum.NameTemplate == "" {
ctx.Config.Checksum.NameTemplate = "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
cs := &ctx.Config.Checksum
if cs.Algorithm == "" {
cs.Algorithm = "sha256"
}
if ctx.Config.Checksum.Algorithm == "" {
ctx.Config.Checksum.Algorithm = "sha256"
if cs.NameTemplate == "" {
if cs.Split {
cs.NameTemplate = "{{ .ArtifactName }}.{{ .Algorithm }}"
} else {
cs.NameTemplate = "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
}
}
return nil
}
// Run the pipe.
func (Pipe) Run(ctx *context.Context) error {
if ctx.Config.Checksum.Split {
return splitChecksum(ctx)
}
return singleChecksum(ctx)
}
func splitChecksum(ctx *context.Context) error {
artifactList, err := buildArtifactList(ctx)
if err != nil {
return err
}
for _, art := range artifactList {
filename, err := tmpl.New(ctx).
WithArtifact(art).
WithExtraFields(tmpl.Fields{
"Algorithm": ctx.Config.Checksum.Algorithm,
}).
Apply(ctx.Config.Checksum.NameTemplate)
if err != nil {
return fmt.Errorf("checksum: name template: %w", err)
}
filepath := filepath.Join(ctx.Config.Dist, filename)
if err := refreshOne(ctx, *art, filepath); err != nil {
return fmt.Errorf("checksum: %s: %w", art.Path, err)
}
ctx.Artifacts.Add(&artifact.Artifact{
Type: artifact.Checksum,
Path: filepath,
Name: filename,
Extra: map[string]interface{}{
artifact.ExtraChecksumOf: art.Path,
artifact.ExtraRefresh: func() error {
log.WithField("file", filename).Info("refreshing checksums")
return refreshOne(ctx, *art, filepath)
},
},
})
}
return nil
}
func singleChecksum(ctx *context.Context) error {
filename, err := tmpl.New(ctx).Apply(ctx.Config.Checksum.NameTemplate)
if err != nil {
return err
}
filepath := filepath.Join(ctx.Config.Dist, filename)
if err := refresh(ctx, filepath); err != nil {
if err := refreshAll(ctx, filepath); err != nil {
if errors.Is(err, errNoArtifacts) {
return nil
}
@ -65,44 +114,28 @@ func (Pipe) Run(ctx *context.Context) error {
Extra: map[string]interface{}{
artifact.ExtraRefresh: func() error {
log.WithField("file", filename).Info("refreshing checksums")
return refresh(ctx, filepath)
return refreshAll(ctx, filepath)
},
},
})
return nil
}
func refresh(ctx *context.Context, filepath string) error {
lock.Lock()
defer lock.Unlock()
filter := artifact.Or(
artifact.ByType(artifact.UploadableArchive),
artifact.ByType(artifact.UploadableBinary),
artifact.ByType(artifact.UploadableSourceArchive),
artifact.ByType(artifact.LinuxPackage),
artifact.ByType(artifact.SBOM),
)
if len(ctx.Config.Checksum.IDs) > 0 {
filter = artifact.And(filter, artifact.ByIDs(ctx.Config.Checksum.IDs...))
}
artifactList := ctx.Artifacts.Filter(filter).List()
extraFiles, err := extrafiles.Find(ctx, ctx.Config.Checksum.ExtraFiles)
func refreshOne(ctx *context.Context, art artifact.Artifact, path string) error {
check, err := art.Checksum(ctx.Config.Checksum.Algorithm)
if err != nil {
return err
}
return os.WriteFile(path, []byte(check), 0o644)
}
for name, path := range extraFiles {
artifactList = append(artifactList, &artifact.Artifact{
Name: name,
Path: path,
Type: artifact.UploadableFile,
})
}
func refreshAll(ctx *context.Context, filepath string) error {
lock.Lock()
defer lock.Unlock()
if len(artifactList) == 0 {
return errNoArtifacts
artifactList, err := buildArtifactList(ctx)
if err != nil {
return err
}
g := semerrgroup.New(ctx.Parallelism)
@ -141,6 +174,39 @@ func refresh(ctx *context.Context, filepath string) error {
return err
}
func buildArtifactList(ctx *context.Context) ([]*artifact.Artifact, error) {
filter := artifact.Or(
artifact.ByType(artifact.UploadableArchive),
artifact.ByType(artifact.UploadableBinary),
artifact.ByType(artifact.UploadableSourceArchive),
artifact.ByType(artifact.LinuxPackage),
artifact.ByType(artifact.SBOM),
)
if len(ctx.Config.Checksum.IDs) > 0 {
filter = artifact.And(filter, artifact.ByIDs(ctx.Config.Checksum.IDs...))
}
artifactList := ctx.Artifacts.Filter(filter).List()
extraFiles, err := extrafiles.Find(ctx, ctx.Config.Checksum.ExtraFiles)
if err != nil {
return nil, err
}
for name, path := range extraFiles {
artifactList = append(artifactList, &artifact.Artifact{
Name: name,
Path: path,
Type: artifact.UploadableFile,
})
}
if len(artifactList) == 0 {
return nil, errNoArtifacts
}
return artifactList, nil
}
func checksums(algorithm string, a *artifact.Artifact) (string, error) {
log.WithField("file", a.Name).Debug("checksumming")
sha, err := a.Checksum(algorithm)

View File

@ -1,6 +1,7 @@
package checksums
import (
"fmt"
"os"
"path/filepath"
"strings"
@ -103,6 +104,66 @@ func TestPipe(t *testing.T) {
}
}
func TestPipeSplit(t *testing.T) {
const binary = "binary"
const archive = binary + ".tar.gz"
const linuxPackage = binary + ".rpm"
const sum = "61d034473102d7dac305902770471fd50f4c5b26f6831a56dd90b5184b3c30fc"
folder := t.TempDir()
file := filepath.Join(folder, binary)
require.NoError(t, os.WriteFile(file, []byte("some string"), 0o644))
ctx := testctx.NewWithCfg(
config.Project{
Dist: folder,
ProjectName: "foo",
Checksum: config.Checksum{
Split: true,
},
},
)
ctx.Artifacts.Add(&artifact.Artifact{
Name: binary,
Path: file,
Type: artifact.UploadableBinary,
Extra: map[string]interface{}{
artifact.ExtraID: "id-1",
},
})
ctx.Artifacts.Add(&artifact.Artifact{
Name: archive,
Path: file,
Type: artifact.UploadableArchive,
Extra: map[string]interface{}{
artifact.ExtraID: "id-2",
},
})
ctx.Artifacts.Add(&artifact.Artifact{
Name: linuxPackage,
Path: file,
Type: artifact.LinuxPackage,
Extra: map[string]interface{}{
artifact.ExtraID: "id-3",
},
})
require.NoError(t, Pipe{}.Default(ctx))
require.NoError(t, Pipe{}.Run(ctx))
require.NoError(t, ctx.Artifacts.Visit(func(a *artifact.Artifact) error {
return a.Refresh()
}))
checks := ctx.Artifacts.Filter(artifact.ByType(artifact.Checksum)).List()
require.Len(t, checks, 3)
for _, check := range checks {
require.NotEmpty(t, check.Extra[artifact.ExtraChecksumOf])
bts, err := os.ReadFile(check.Path)
require.NoError(t, err)
require.Equal(t, sum, string(bts))
}
}
func TestRefreshModifying(t *testing.T) {
const binary = "binary"
folder := t.TempDir()
@ -134,6 +195,37 @@ func TestRefreshModifying(t *testing.T) {
require.NotEqual(t, string(previous), string(current))
}
func TestRefreshModifyingSplit(t *testing.T) {
const binary = "binary"
folder := t.TempDir()
file := filepath.Join(folder, binary)
require.NoError(t, os.WriteFile(file, []byte("some string"), 0o644))
ctx := testctx.NewWithCfg(config.Project{
Dist: folder,
ProjectName: binary,
Checksum: config.Checksum{
Split: true,
},
Env: []string{"FOO=bar"},
}, testctx.WithCurrentTag("1.2.3"))
ctx.Artifacts.Add(&artifact.Artifact{
Name: binary,
Path: file,
Type: artifact.UploadableBinary,
})
require.NoError(t, Pipe{}.Default(ctx))
require.NoError(t, Pipe{}.Run(ctx))
checks := ctx.Artifacts.Filter(artifact.ByType(artifact.Checksum)).List()
require.Len(t, checks, 1)
previous, err := os.ReadFile(checks[0].Path)
require.NoError(t, err)
require.NoError(t, os.WriteFile(file, []byte("some other string"), 0o644))
require.NoError(t, checks[0].Refresh())
current, err := os.ReadFile(checks[0].Path)
require.NoError(t, err)
require.NotEqual(t, string(previous), string(current))
}
func TestPipeFileNotExist(t *testing.T) {
folder := t.TempDir()
ctx := testctx.NewWithCfg(
@ -164,26 +256,29 @@ func TestPipeInvalidNameTemplate(t *testing.T) {
"{{ .Pro }_checksums.txt",
"{{.Env.NOPE}}",
} {
t.Run(template, func(t *testing.T) {
folder := t.TempDir()
ctx := testctx.NewWithCfg(
config.Project{
Dist: folder,
ProjectName: "name",
Checksum: config.Checksum{
NameTemplate: template,
Algorithm: "sha256",
for _, split := range []bool{true, false} {
t.Run(fmt.Sprintf("split_%v_%s", split, template), func(t *testing.T) {
folder := t.TempDir()
ctx := testctx.NewWithCfg(
config.Project{
Dist: folder,
ProjectName: "name",
Checksum: config.Checksum{
NameTemplate: template,
Algorithm: "sha256",
Split: split,
},
},
},
testctx.WithCurrentTag("1.2.3"),
)
ctx.Artifacts.Add(&artifact.Artifact{
Name: "whatever",
Type: artifact.UploadableBinary,
Path: binFile.Name(),
testctx.WithCurrentTag("1.2.3"),
)
ctx.Artifacts.Add(&artifact.Artifact{
Name: "whatever",
Type: artifact.UploadableBinary,
Path: binFile.Name(),
})
testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
})
testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
})
}
}
}
@ -236,6 +331,21 @@ func TestDefault(t *testing.T) {
require.Equal(t, "sha256", ctx.Config.Checksum.Algorithm)
}
func TestDefaultSPlit(t *testing.T) {
ctx := testctx.NewWithCfg(config.Project{
Checksum: config.Checksum{
Split: true,
},
})
require.NoError(t, Pipe{}.Default(ctx))
require.Equal(
t,
"{{ .ArtifactName }}.{{ .Algorithm }}",
ctx.Config.Checksum.NameTemplate,
)
require.Equal(t, "sha256", ctx.Config.Checksum.Algorithm)
}
func TestDefaultSet(t *testing.T) {
ctx := testctx.NewWithCfg(config.Project{
Checksum: config.Checksum{

View File

@ -17,21 +17,39 @@ const bodyTemplateText = `{{ with .Header }}{{ . }}{{ "\n" }}{{ end }}
func describeBody(ctx *context.Context) (bytes.Buffer, error) {
var out bytes.Buffer
var checksum string
if l := ctx.Artifacts.Filter(artifact.ByType(artifact.Checksum)).List(); len(l) > 0 {
if err := l[0].Refresh(); err != nil {
return out, err
}
bts, err := os.ReadFile(l[0].Path)
fields := tmpl.Fields{}
checksums := ctx.Artifacts.Filter(artifact.ByType(artifact.Checksum))
if err := checksums.Visit(func(a *artifact.Artifact) error {
return a.Refresh()
}); err != nil {
return out, err
}
checksumsList := checksums.List()
switch len(checksumsList) {
case 0:
// do nothing
case 1:
bts, err := os.ReadFile(checksumsList[0].Path)
if err != nil {
return out, err
}
checksum = string(bts)
fields["Checksums"] = string(bts)
default:
checkMap := map[string]string{}
for _, check := range checksumsList {
bts, err := os.ReadFile(check.Path)
if err != nil {
return out, err
}
checkMap[artifact.ExtraOr(*check, artifact.ExtraChecksumOf, "")] = string(bts)
}
fields["Checksums"] = checkMap
}
t := tmpl.New(ctx).WithExtraFields(tmpl.Fields{
"Checksums": checksum,
})
t := tmpl.New(ctx).WithExtraFields(fields)
header, err := t.Apply(ctx.Config.Release.Header)
if err != nil {

View File

@ -34,6 +34,49 @@ func TestDontEscapeHTML(t *testing.T) {
require.Contains(t, out.String(), changelog)
}
func TestDescribeBodyMultipleChecksums(t *testing.T) {
ctx := testctx.NewWithCfg(
config.Project{
Release: config.Release{
Header: "## Yada yada yada\nsomething\n",
Footer: `
---
## Checksums
` + "```\n{{ range $key, $value := .Checksums }}{{ $value }} {{ $key }}\n{{ end }}```\n",
},
},
testctx.WithCurrentTag("v1.0"),
func(ctx *context.Context) { ctx.ReleaseNotes = "nothing" },
)
checksums := map[string]string{
"bar.zip": "f674623cf1edd0f753e620688cedee4e7c0e837ac1e53c0cbbce132ffe35fd52",
"foo.zip": "271a74b75a12f6c3affc88df101f9ef29af79717b1b2f4bdd5964aacf65bcf40",
}
for name, check := range checksums {
name := name
check := check
checksumPath := filepath.Join(t.TempDir(), name+".sha256")
ctx.Artifacts.Add(&artifact.Artifact{
Name: name + ".sha256",
Path: checksumPath,
Type: artifact.Checksum,
Extra: map[string]interface{}{
artifact.ExtraChecksumOf: name,
artifact.ExtraRefresh: func() error {
return os.WriteFile(checksumPath, []byte(check), 0o644)
},
},
})
}
out, err := describeBody(ctx)
require.NoError(t, err)
golden.RequireEqual(t, out.Bytes())
}
func TestDescribeBodyWithHeaderAndFooter(t *testing.T) {
changelog := "feature1: description\nfeature2: other description"
ctx := testctx.NewWithCfg(

View File

@ -0,0 +1,14 @@
## Yada yada yada
something
nothing
---
## Checksums
```
f674623cf1edd0f753e620688cedee4e7c0e837ac1e53c0cbbce132ffe35fd52 bar.zip
271a74b75a12f6c3affc88df101f9ef29af79717b1b2f4bdd5964aacf65bcf40 foo.zip
```

View File

@ -1030,6 +1030,7 @@ type Snapshot struct {
type Checksum struct {
NameTemplate string `yaml:"name_template,omitempty" json:"name_template,omitempty"`
Algorithm string `yaml:"algorithm,omitempty" json:"algorithm,omitempty"`
Split bool `yaml:"split,omitempty" json:"split,omitempty"`
IDs []string `yaml:"ids,omitempty" json:"ids,omitempty"`
Disable bool `yaml:"disable,omitempty" json:"disable,omitempty"`
ExtraFiles []ExtraFile `yaml:"extra_files,omitempty" json:"extra_files,omitempty"`

View File

@ -10,7 +10,8 @@ The `checksum` section allows customizations of the filename:
checksum:
# You can change the name of the checksums file.
#
# Default: {{ .ProjectName }}_{{ .Version }}_checksums.txt
# Default: '{{ .ProjectName }}_{{ .Version }}_checksums.txt'
# or, when split is set: '{{ .ArtifactName }}.{{ .Algorithm }}'
# Templates: allowed
name_template: "{{ .ProjectName }}_checksums.txt"
@ -20,6 +21,10 @@ checksum:
# Default: sha256.
algorithm: sha256
# If true, will create one checksum file for each artifact.
# Since: v1.25
split: true
# IDs of artifacts to include in the checksums file.
#
# If left empty, all published binaries, archives, linux packages and source archives

View File

@ -133,9 +133,9 @@ In the nFPM name template field, you can use those extra fields:
In the `release.body` field, you can use these extra fields:
| Key | Description |
| ------------ | ----------------------------------------------------------------------------------- |
| `.Checksums` | the current checksum file contents. Only available in the release body. Since v1.19 |
| Key | Description |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `.Checksums` | the current checksum file contents, or a map of filename/checksum contents if `checksum.split` is set. Only available in the release body. Since v1.19 |
## Functions