1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-16 03:52:12 +02:00

feat: add metadata to the release (#4714)

this will create a metadata artifact and allow to add them to the
release.

closes #4669
closes #4682

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
This commit is contained in:
Carlos Alexandro Becker 2024-03-26 23:41:41 -03:00 committed by GitHub
parent ec7106fdea
commit 2498ea7029
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 169 additions and 36 deletions

View File

@ -85,6 +85,8 @@ const (
CArchive
// CShared is a C shared library, generated via a CGo build with buildmode=c-shared.
CShared
// Metadata is an internal goreleaser metadata JSON file.
Metadata
)
func (t Type) String() string {
@ -137,6 +139,8 @@ func (t Type) String() string {
return "Winget Manifest"
case Nixpkg:
return "Nixpkg"
case Metadata:
return "Metadata"
default:
return "unknown"
}
@ -474,8 +478,9 @@ func ByIDs(ids ...string) Filter {
filters = append(filters, func(a *Artifact) bool {
// checksum and source archive are always for all artifacts, so return always true.
return a.Type == Checksum ||
a.Type == UploadableFile ||
a.Type == UploadableSourceArchive ||
a.Type == UploadableFile ||
a.Type == Metadata ||
a.ID() == id
})
}

View File

@ -934,7 +934,7 @@ func TestArtifactStringer(t *testing.T) {
}
func TestArtifactTypeStringer(t *testing.T) {
for i := 1; i <= 29; i++ {
for i := 1; i <= 30; i++ {
t.Run(fmt.Sprintf("type-%d-%s", i, Type(i).String()), func(t *testing.T) {
require.NotEqual(t, "unknown", Type(i).String())
})

View File

@ -134,6 +134,10 @@ func filterArtifacts(artifacts *artifact.Artifacts, publisher config.Publisher)
filters = append(filters, artifact.ByType(artifact.Checksum))
}
if publisher.Meta {
filters = append(filters, artifact.ByType(artifact.Metadata))
}
if publisher.Signature {
filters = append(filters, artifact.ByType(artifact.Signature), artifact.ByType(artifact.Certificate))
}

View File

@ -36,6 +36,7 @@ func TestExecute(t *testing.T) {
{"archive", "tar", artifact.UploadableArchive},
{"ubinary", "ubi", artifact.UploadableBinary},
{"checksum", "sum", artifact.Checksum},
{"metadata", "json", artifact.Metadata},
{"signature", "sig", artifact.Signature},
{"signature", "pem", artifact.Certificate},
} {
@ -186,6 +187,30 @@ func TestExecute(t *testing.T) {
nil,
nil,
},
{
"include metadata",
[]config.Publisher{
{
Name: "test",
Meta: true,
Cmd: MockCmd + " {{ .ArtifactName }}",
Env: []string{
MarshalMockEnv(&MockData{
AnyOf: []MockCall{
{ExpectedArgs: []string{"a.deb"}, ExitCode: 0, ExpectedEnv: osEnv()},
{ExpectedArgs: []string{"a.ubi"}, ExitCode: 0, ExpectedEnv: osEnv()},
{ExpectedArgs: []string{"a.tar"}, ExitCode: 0, ExpectedEnv: osEnv()},
{ExpectedArgs: []string{"a.json"}, ExitCode: 0, ExpectedEnv: osEnv()},
{ExpectedArgs: []string{"foo/bar"}, ExitCode: 0, ExpectedEnv: osEnv()},
{ExpectedArgs: []string{"foo/bar:amd64"}, ExitCode: 0, ExpectedEnv: osEnv()},
},
}),
},
},
},
nil,
nil,
},
{
"include signatures",
[]config.Publisher{

View File

@ -161,6 +161,9 @@ func Upload(ctx *context.Context, uploads []config.Upload, kind string, check Re
if upload.Checksum {
filters = append(filters, artifact.ByType(artifact.Checksum))
}
if upload.Meta {
filters = append(filters, artifact.ByType(artifact.Metadata))
}
if upload.Signature {
filters = append(filters, artifact.ByType(artifact.Signature), artifact.ByType(artifact.Certificate))
}

View File

@ -246,6 +246,7 @@ func TestUpload(t *testing.T) {
{"tar.gz", artifact.UploadableSourceArchive},
{"ubi", artifact.UploadableBinary},
{"sum", artifact.Checksum},
{"meta", artifact.Metadata},
{"sig", artifact.Signature},
{"pem", artifact.Certificate},
} {
@ -447,6 +448,25 @@ func TestUpload(t *testing.T) {
check{"/blah/2.1.0/a.pem", "u3", "x", content, map[string]string{}},
),
},
{
"metadata", true, true, false, false,
func(s *httptest.Server) (*context.Context, config.Upload) {
return ctx, config.Upload{
Mode: ModeArchive,
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}/",
Username: "u3",
Meta: true,
TrustedCerts: cert(s),
}
},
checks(
check{"/blah/2.1.0/a.deb", "u3", "x", content, map[string]string{}},
check{"/blah/2.1.0/a.tar", "u3", "x", content, map[string]string{}},
check{"/blah/2.1.0/a.tar.gz", "u3", "x", content, map[string]string{}},
check{"/blah/2.1.0/a.meta", "u3", "x", content, map[string]string{}},
),
},
{
"bad-template", true, true, true, true,
func(s *httptest.Server) (*context.Context, config.Upload) {

View File

@ -73,9 +73,11 @@ func TestMinioUpload(t *testing.T) {
tgzpath := filepath.Join(folder, "bin.tar.gz")
debpath := filepath.Join(folder, "bin.deb")
checkpath := filepath.Join(folder, "check.txt")
metapath := filepath.Join(folder, "metadata.json")
sigpath := filepath.Join(folder, "f.sig")
certpath := filepath.Join(folder, "f.pem")
require.NoError(t, os.WriteFile(checkpath, []byte("fake checksums"), 0o744))
require.NoError(t, os.WriteFile(metapath, []byte(`{"fake":true}`), 0o744))
require.NoError(t, os.WriteFile(srcpath, []byte("fake\nsrc"), 0o744))
require.NoError(t, os.WriteFile(tgzpath, []byte("fake\ntargz"), 0o744))
require.NoError(t, os.WriteFile(debpath, []byte("fake\ndeb"), 0o744))
@ -93,6 +95,7 @@ func TestMinioUpload(t *testing.T) {
IDs: []string{"foo", "bar"},
CacheControl: []string{"max-age=9999"},
ContentDisposition: "inline",
IncludeMeta: true,
ExtraFiles: []config.ExtraFile{
{
Glob: "./testdata/*.golden",
@ -101,6 +104,11 @@ func TestMinioUpload(t *testing.T) {
},
},
}, testctx.WithCurrentTag("v1.0.0"))
ctx.Artifacts.Add(&artifact.Artifact{
Type: artifact.Metadata,
Name: "metadata.json",
Path: metapath,
})
ctx.Artifacts.Add(&artifact.Artifact{
Type: artifact.Checksum,
Name: "checksum.txt",
@ -154,6 +162,7 @@ func TestMinioUpload(t *testing.T) {
require.Subset(t, getFiles(t, ctx, ctx.Config.Blobs[0]), []string{
"testupload/v1.0.0/bin.deb",
"testupload/v1.0.0/bin.tar.gz",
"testupload/v1.0.0/metadata.json",
"testupload/v1.0.0/checksum.txt",
"testupload/v1.0.0/checksum.txt.sig",
"testupload/v1.0.0/checksum.pem",

View File

@ -96,7 +96,7 @@ func doUpload(ctx *context.Context, conf config.Blob) error {
return err
}
filter := artifact.Or(
byTypes := []artifact.Filter{
artifact.ByType(artifact.UploadableArchive),
artifact.ByType(artifact.UploadableBinary),
artifact.ByType(artifact.UploadableSourceArchive),
@ -105,7 +105,12 @@ func doUpload(ctx *context.Context, conf config.Blob) error {
artifact.ByType(artifact.Certificate),
artifact.ByType(artifact.LinuxPackage),
artifact.ByType(artifact.SBOM),
)
}
if conf.IncludeMeta {
byTypes = append(byTypes, artifact.ByType(artifact.Metadata))
}
filter := artifact.Or(byTypes...)
if len(conf.IDs) > 0 {
filter = artifact.And(filter, artifact.ByIDs(conf.IDs...))
}

View File

@ -14,27 +14,29 @@ import (
"github.com/goreleaser/goreleaser/pkg/context"
)
// Pipe implementation.
type Pipe struct{}
type (
// Pipe implementation.
Pipe struct{}
// MetaPipe implementation.
MetaPipe struct{}
// ArtifactsPipe implementation.
ArtifactsPipe struct{}
)
func (Pipe) String() string { return "storing release metadata" }
func (Pipe) Skip(_ *context.Context) bool { return false }
// Run the pipe.
func (Pipe) String() string { return "setting up metadata" }
func (Pipe) Run(ctx *context.Context) error {
if err := tmpl.New(ctx).ApplyAll(
&ctx.Config.Metadata.ModTimestamp,
); err != nil {
return err
}
if err := writeArtifacts(ctx); err != nil {
return err
}
return writeMetadata(ctx)
return tmpl.New(ctx).ApplyAll(&ctx.Config.Metadata.ModTimestamp)
}
func (MetaPipe) String() string { return "storing release metadata" }
func (MetaPipe) Run(ctx *context.Context) error { return writeMetadata(ctx) }
func (ArtifactsPipe) String() string { return "storing artifacts metadata" }
func (ArtifactsPipe) Run(ctx *context.Context) error { return writeArtifacts(ctx) }
func writeMetadata(ctx *context.Context) error {
return writeJSON(ctx, metadata{
const name = "metadata.json"
path, err := writeJSON(ctx, metadata{
ProjectName: ctx.Config.ProjectName,
Tag: ctx.Git.CurrentTag,
PreviousTag: ctx.Git.PreviousTag,
@ -45,7 +47,13 @@ func writeMetadata(ctx *context.Context) error {
Goos: ctx.Runtime.Goos,
Goarch: ctx.Runtime.Goarch,
},
}, "metadata.json")
}, name)
ctx.Artifacts.Add(&artifact.Artifact{
Name: name,
Path: path,
Type: artifact.Metadata,
})
return err
}
func writeArtifacts(ctx *context.Context) error {
@ -54,21 +62,22 @@ func writeArtifacts(ctx *context.Context) error {
a.Path = filepath.ToSlash(filepath.Clean(a.Path))
return nil
})
return writeJSON(ctx, ctx.Artifacts.List(), "artifacts.json")
_, err := writeJSON(ctx, ctx.Artifacts.List(), "artifacts.json")
return err
}
func writeJSON(ctx *context.Context, j interface{}, name string) error {
func writeJSON(ctx *context.Context, j interface{}, name string) (string, error) {
bts, err := json.Marshal(j)
if err != nil {
return err
return "", err
}
path := filepath.Join(ctx.Config.Dist, name)
log.Log.WithField("file", path).Info("writing")
if err := os.WriteFile(path, bts, 0o644); err != nil {
return err
return "", err
}
return gio.Chtimes(path, ctx.Config.Metadata.ModTimestamp)
return path, gio.Chtimes(path, ctx.Config.Metadata.ModTimestamp)
}
type metadata struct {

View File

@ -22,7 +22,8 @@ func TestRunWithError(t *testing.T) {
Dist: "testadata/nope",
ProjectName: "foo",
})
require.ErrorIs(t, Pipe{}.Run(ctx), os.ErrNotExist)
require.ErrorIs(t, MetaPipe{}.Run(ctx), os.ErrNotExist)
require.ErrorIs(t, ArtifactsPipe{}.Run(ctx), os.ErrNotExist)
}
func TestRun(t *testing.T) {
@ -65,20 +66,29 @@ func TestRun(t *testing.T) {
tmp := t.TempDir()
ctx := getCtx(tmp)
require.NoError(t, Pipe{}.Run(ctx))
requireEqualJSONFile(t, tmp, "artifacts.json", modTime)
require.NoError(t, ArtifactsPipe{}.Run(ctx))
requireEqualJSONFile(t, filepath.Join(tmp, "artifacts.json"), modTime)
})
t.Run("metadata", func(t *testing.T) {
tmp := t.TempDir()
ctx := getCtx(tmp)
require.NoError(t, Pipe{}.Run(ctx))
requireEqualJSONFile(t, tmp, "metadata.json", modTime)
require.NoError(t, MetaPipe{}.Run(ctx))
metas := ctx.Artifacts.Filter(artifact.ByType(artifact.Metadata)).List()
require.Len(t, metas, 1)
require.Equal(t, "metadata.json", metas[0].Name)
requireEqualJSONFile(t, metas[0].Path, modTime)
})
t.Run("invalid mod metadata", func(t *testing.T) {
tmp := t.TempDir()
ctx := getCtx(tmp)
ctx.Config.Metadata.ModTimestamp = "not a number"
require.ErrorIs(t, Pipe{}.Run(ctx), strconv.ErrSyntax)
require.NoError(t, Pipe{}.Run(ctx))
require.ErrorIs(t, MetaPipe{}.Run(ctx), strconv.ErrSyntax)
require.ErrorIs(t, ArtifactsPipe{}.Run(ctx), strconv.ErrSyntax)
})
t.Run("invalid mod metadata tmpl", func(t *testing.T) {
@ -89,9 +99,8 @@ func TestRun(t *testing.T) {
})
}
func requireEqualJSONFile(tb testing.TB, tmp, s string, modTime time.Time) {
func requireEqualJSONFile(tb testing.TB, path string, modTime time.Time) {
tb.Helper()
path := filepath.Join(tmp, s)
golden.RequireEqualJSON(tb, golden.RequireReadFile(tb, path))
stat, err := os.Stat(path)
require.NoError(tb, err)

View File

@ -161,6 +161,9 @@ func doPublish(ctx *context.Context, client client.Client) error {
artifact.ByType(artifact.LinuxPackage),
artifact.ByType(artifact.SBOM),
}
if ctx.Config.Release.IncludeMeta {
typeFilters = append(typeFilters, artifact.ByType(artifact.Metadata))
}
filters := artifact.Or(typeFilters...)
if len(ctx.Config.Release.IDs) > 0 {

View File

@ -32,6 +32,7 @@ func TestRunPipeWithoutIDsThenDoesNotFilter(t *testing.T) {
tarfile := createTmpFile(t, folder, "bin.tar.gz")
srcfile := createTmpFile(t, folder, "source.tar.gz")
debfile := createTmpFile(t, folder, "bin.deb")
metafile := createTmpFile(t, folder, "metadata.json")
checksumfile := createTmpFile(t, folder, "checksum")
checksumsigfile := createTmpFile(t, folder, "checksum.sig")
checksumpemfile := createTmpFile(t, folder, "checksum.pem")
@ -45,6 +46,7 @@ func TestRunPipeWithoutIDsThenDoesNotFilter(t *testing.T) {
Owner: "test",
Name: "test",
},
IncludeMeta: true,
},
}
ctx := testctx.NewWithCfg(config, testctx.WithCurrentTag("v1.0.0"))
@ -94,7 +96,16 @@ func TestRunPipeWithoutIDsThenDoesNotFilter(t *testing.T) {
Name: "checksum",
Path: checksumfile,
Extra: map[string]interface{}{
artifact.ExtraID: "bar",
artifact.ExtraID: "doesnt-matter",
},
})
ctx.Artifacts.Add(&artifact.Artifact{
Type: artifact.Metadata,
Name: "metadata.json",
Path: metafile,
Extra: map[string]interface{}{
artifact.ExtraID: "doesnt-matter",
},
})
ctx.Artifacts.Add(&artifact.Artifact{
@ -123,6 +134,7 @@ func TestRunPipeWithoutIDsThenDoesNotFilter(t *testing.T) {
require.Contains(t, client.UploadedFileNames, "bin.tar.gz")
require.Contains(t, client.UploadedFileNames, "filtered.deb")
require.Contains(t, client.UploadedFileNames, "filtered.tar.gz")
require.Contains(t, client.UploadedFileNames, "metadata.json")
require.Contains(t, client.UploadedFileNames, "checksum")
require.Contains(t, client.UploadedFileNames, "checksum.pem")
require.Contains(t, client.UploadedFileNames, "checksum.sig")

View File

@ -68,6 +68,10 @@ var BuildPipeline = []Piper{
before.Pipe{},
// ensure ./dist is clean
dist.Pipe{},
// setup metadata options
metadata.Pipe{},
// creates a metadta.json files in the dist folder
metadata.MetaPipe{},
// setup gomod-related stuff
gomod.Pipe{},
// run prebuild stuff
@ -91,7 +95,7 @@ var BuildPipeline = []Piper{
var BuildCmdPipeline = append(
BuildPipeline,
reportsizes.Pipe{},
metadata.Pipe{},
metadata.ArtifactsPipe{},
)
// Pipeline contains all pipe implementations in order.
@ -134,8 +138,8 @@ var Pipeline = append(
docker.Pipe{},
// publishes artifacts
publish.New(),
// creates a metadata.json and an artifacts.json files in the dist folder
metadata.Pipe{},
// creates a artifacts.json files in the dist folder
metadata.ArtifactsPipe{},
// announce releases
announce.Pipe{},
)

View File

@ -753,6 +753,7 @@ type Release struct {
ReleaseNotesMode ReleaseNotesMode `yaml:"mode,omitempty" json:"mode,omitempty" jsonschema:"enum=keep-existing,enum=append,enum=prepend,enum=replace,default=keep-existing"`
ReplaceExistingArtifacts bool `yaml:"replace_existing_artifacts,omitempty" json:"replace_existing_artifacts,omitempty"`
IncludeMeta bool `yaml:"include_meta,omitempty" json:"include_meta,omitempty"`
}
// Milestone config used for VCS milestone.
@ -1120,6 +1121,7 @@ type Blob struct {
ACL string `yaml:"acl,omitempty" json:"acl,omitempty"`
CacheControl []string `yaml:"cache_control,omitempty" json:"cache_control,omitempty"`
ContentDisposition string `yaml:"content_disposition,omitempty" json:"content_disposition,omitempty"`
IncludeMeta bool `yaml:"include_meta,omitempty" json:"include_meta,omitempty"`
// Deprecated: use disable_ssl instead
OldDisableSSL bool `yaml:"disableSSL,omitempty" json:"disableSSL,omitempty" jsonschema:"deprecated=true,description=use disable_ssl instead"` // nolint:tagliatelle
@ -1142,6 +1144,7 @@ type Upload struct {
TrustedCerts string `yaml:"trusted_certificates,omitempty" json:"trusted_certificates,omitempty"`
Checksum bool `yaml:"checksum,omitempty" json:"checksum,omitempty"`
Signature bool `yaml:"signature,omitempty" json:"signature,omitempty"`
Meta bool `yaml:"meta,omitempty" json:"meta,omitempty"`
CustomArtifactName bool `yaml:"custom_artifact_name,omitempty" json:"custom_artifact_name,omitempty"`
CustomHeaders map[string]string `yaml:"custom_headers,omitempty" json:"custom_headers,omitempty"`
}
@ -1152,6 +1155,7 @@ type Publisher struct {
IDs []string `yaml:"ids,omitempty" json:"ids,omitempty"`
Checksum bool `yaml:"checksum,omitempty" json:"checksum,omitempty"`
Signature bool `yaml:"signature,omitempty" json:"signature,omitempty"`
Meta bool `yaml:"meta,omitempty" json:"meta,omitempty"`
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
Cmd string `yaml:"cmd,omitempty" json:"cmd,omitempty"`
Env []string `yaml:"env,omitempty" json:"env,omitempty"`

View File

@ -196,6 +196,11 @@ artifactories:
# Upload checksums.
checksum: true
# Upload metadata.json and artifacts.json.
#
# Since: v1.25
meta: true
# Upload signatures.
signature: true

View File

@ -108,6 +108,11 @@ publishers:
# Publish checksums.
checksum: true
# Upload metadata.json and artifacts.json.
#
# Since: v1.25
meta: true
# Publish signatures.
signature: true

View File

@ -203,6 +203,12 @@ release:
templated_extra_files:
- src: LICENSE.tpl
dst: LICENSE.txt
# Upload metadata.json and artifacts.json to the release as well.
#
# Since: v1.25
include_meta: true
```
!!! tip

View File

@ -223,6 +223,11 @@ uploads:
# Upload checksums.
checksum: true
# Upload metadata.json and artifacts.json.
#
# Since: v1.25
meta: true
# Upload signatures.
signature: true