mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-01-06 03:13:48 +02:00
c47315fead
* Add a `labels` key-value map to the `kos` config. My interest is to be able to label the built images: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#labelling-container-images * Add creation times to allow using the commit timestamp as meaningful creation time
434 lines
11 KiB
Go
434 lines
11 KiB
Go
package ko
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
_ "github.com/distribution/distribution/v3/registry/auth/htpasswd"
|
|
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
|
"github.com/google/go-containerregistry/pkg/name"
|
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
|
"github.com/goreleaser/goreleaser/internal/testctx"
|
|
"github.com/goreleaser/goreleaser/internal/testlib"
|
|
"github.com/goreleaser/goreleaser/pkg/config"
|
|
"github.com/goreleaser/goreleaser/pkg/context"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const (
|
|
registryPort = "5052"
|
|
registry = "localhost:5052/"
|
|
)
|
|
|
|
func TestDefault(t *testing.T) {
|
|
ctx := testctx.NewWithCfg(config.Project{
|
|
Env: []string{
|
|
"KO_DOCKER_REPO=" + registry,
|
|
"COSIGN_REPOSITORY=" + registry,
|
|
"LDFLAGS=foobar",
|
|
"FLAGS=barfoo",
|
|
"LE_ENV=test",
|
|
},
|
|
ProjectName: "test",
|
|
Builds: []config.Build{
|
|
{
|
|
ID: "test",
|
|
Dir: ".",
|
|
BuildDetails: config.BuildDetails{
|
|
Ldflags: []string{"{{.Env.LDFLAGS}}"},
|
|
Flags: []string{"{{.Env.FLAGS}}"},
|
|
Env: []string{"SOME_ENV={{.Env.LE_ENV}}"},
|
|
},
|
|
},
|
|
},
|
|
Kos: []config.Ko{
|
|
{},
|
|
},
|
|
})
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
require.Equal(t, config.Ko{
|
|
ID: "test",
|
|
Build: "test",
|
|
BaseImage: chainguardStatic,
|
|
Repository: registry,
|
|
Platforms: []string{"linux/amd64"},
|
|
SBOM: "spdx",
|
|
Tags: []string{"latest"},
|
|
WorkingDir: ".",
|
|
Ldflags: []string{"{{.Env.LDFLAGS}}"},
|
|
Flags: []string{"{{.Env.FLAGS}}"},
|
|
Env: []string{"SOME_ENV={{.Env.LE_ENV}}"},
|
|
}, ctx.Config.Kos[0])
|
|
}
|
|
|
|
func TestDefaultNoImage(t *testing.T) {
|
|
ctx := testctx.NewWithCfg(config.Project{
|
|
ProjectName: "test",
|
|
Builds: []config.Build{
|
|
{
|
|
ID: "test",
|
|
},
|
|
},
|
|
Kos: []config.Ko{
|
|
{},
|
|
},
|
|
})
|
|
require.ErrorIs(t, Pipe{}.Default(ctx), errNoRepository)
|
|
}
|
|
|
|
func TestDescription(t *testing.T) {
|
|
require.NotEmpty(t, Pipe{}.String())
|
|
}
|
|
|
|
func TestSkip(t *testing.T) {
|
|
t.Run("skip ko set", func(t *testing.T) {
|
|
ctx := testctx.NewWithCfg(config.Project{
|
|
Kos: []config.Ko{{}},
|
|
})
|
|
ctx.SkipKo = true
|
|
require.True(t, Pipe{}.Skip(ctx))
|
|
})
|
|
t.Run("skip no kos", func(t *testing.T) {
|
|
ctx := testctx.New()
|
|
require.True(t, Pipe{}.Skip(ctx))
|
|
})
|
|
t.Run("dont skip", func(t *testing.T) {
|
|
ctx := testctx.NewWithCfg(config.Project{
|
|
Kos: []config.Ko{{}},
|
|
})
|
|
require.False(t, Pipe{}.Skip(ctx))
|
|
})
|
|
}
|
|
|
|
func TestPublishPipeNoMatchingBuild(t *testing.T) {
|
|
ctx := testctx.NewWithCfg(config.Project{
|
|
Builds: []config.Build{
|
|
{
|
|
ID: "doesnt matter",
|
|
},
|
|
},
|
|
Kos: []config.Ko{
|
|
{
|
|
ID: "default",
|
|
Build: "wont match nothing",
|
|
},
|
|
},
|
|
})
|
|
|
|
require.EqualError(t, Pipe{}.Default(ctx), `no builds with id "wont match nothing"`)
|
|
}
|
|
|
|
func TestPublishPipeSuccess(t *testing.T) {
|
|
testlib.StartRegistry(t, "ko_registry", registryPort)
|
|
|
|
table := []struct {
|
|
Name string
|
|
SBOM string
|
|
BaseImage string
|
|
Labels map[string]string
|
|
ExpectedLabels map[string]string
|
|
Platforms []string
|
|
CreationTime string
|
|
KoDataCreationTime string
|
|
}{
|
|
{
|
|
// Must be first as others add an SBOM for the same image
|
|
Name: "sbom-none",
|
|
SBOM: "none",
|
|
},
|
|
{
|
|
Name: "sbom-spdx",
|
|
SBOM: "spdx",
|
|
},
|
|
{
|
|
Name: "sbom-cyclonedx",
|
|
SBOM: "cyclonedx",
|
|
},
|
|
{
|
|
Name: "sbom-go.version-m",
|
|
SBOM: "go.version-m",
|
|
},
|
|
{
|
|
Name: "base-image-is-not-index",
|
|
BaseImage: "alpine:latest@sha256:c0d488a800e4127c334ad20d61d7bc21b4097540327217dfab52262adc02380c",
|
|
},
|
|
{
|
|
Name: "multiple-platforms",
|
|
Platforms: []string{"linux/amd64", "linux/arm64"},
|
|
},
|
|
{
|
|
Name: "labels",
|
|
Labels: map[string]string{"foo": "bar", "project": "{{.ProjectName}}"},
|
|
ExpectedLabels: map[string]string{"foo": "bar", "project": "test"},
|
|
},
|
|
{
|
|
Name: "creation-time",
|
|
CreationTime: "1672531200",
|
|
},
|
|
{
|
|
Name: "kodata-creation-time",
|
|
KoDataCreationTime: "1672531200",
|
|
},
|
|
}
|
|
|
|
repository := fmt.Sprintf("%sgoreleasertest/testapp", registry)
|
|
|
|
for _, table := range table {
|
|
t.Run(table.Name, func(t *testing.T) {
|
|
ctx := testctx.NewWithCfg(config.Project{
|
|
ProjectName: "test",
|
|
Builds: []config.Build{
|
|
{
|
|
ID: "foo",
|
|
BuildDetails: config.BuildDetails{
|
|
Ldflags: []string{"-s", "-w"},
|
|
Flags: []string{"-tags", "netgo"},
|
|
Env: []string{"GOCACHE=" + t.TempDir()},
|
|
},
|
|
},
|
|
},
|
|
Kos: []config.Ko{
|
|
{
|
|
ID: "default",
|
|
Build: "foo",
|
|
WorkingDir: "./testdata/app/",
|
|
BaseImage: table.BaseImage,
|
|
Repository: repository,
|
|
Labels: table.Labels,
|
|
Platforms: table.Platforms,
|
|
Tags: []string{table.Name},
|
|
CreationTime: table.CreationTime,
|
|
KoDataCreationTime: table.KoDataCreationTime,
|
|
SBOM: table.SBOM,
|
|
Bare: true,
|
|
},
|
|
},
|
|
})
|
|
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
require.NoError(t, Pipe{}.Publish(ctx))
|
|
|
|
ref, err := name.ParseReference(
|
|
fmt.Sprintf("%s:%s", repository, table.Name),
|
|
name.Insecure,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
index, err := remote.Index(ref)
|
|
if len(table.Platforms) > 1 {
|
|
require.NoError(t, err)
|
|
imf, err := index.IndexManifest()
|
|
require.NoError(t, err)
|
|
|
|
platforms := make([]string, 0, len(imf.Manifests))
|
|
for _, mf := range imf.Manifests {
|
|
platforms = append(platforms, mf.Platform.String())
|
|
}
|
|
require.ElementsMatch(t, table.Platforms, platforms)
|
|
} else {
|
|
require.Error(t, err)
|
|
}
|
|
|
|
image, err := remote.Image(ref)
|
|
require.NoError(t, err)
|
|
|
|
digest, err := image.Digest()
|
|
require.NoError(t, err)
|
|
|
|
sbomRef, err := name.ParseReference(
|
|
fmt.Sprintf(
|
|
"%s:%s.sbom",
|
|
repository,
|
|
strings.Replace(digest.String(), ":", "-", 1),
|
|
),
|
|
name.Insecure,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
sbom, err := remote.Image(sbomRef)
|
|
if table.SBOM == "none" {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
|
|
layers, err := sbom.Layers()
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, layers)
|
|
|
|
mediaType, err := layers[0].MediaType()
|
|
require.NoError(t, err)
|
|
|
|
switch table.SBOM {
|
|
case "spdx", "":
|
|
require.Equal(t, "spdx+json", string(mediaType))
|
|
case "cyclonedx":
|
|
require.Equal(t, "application/vnd.cyclonedx+json", string(mediaType))
|
|
case "go.version-m":
|
|
require.Equal(t, "application/vnd.go.version-m", string(mediaType))
|
|
default:
|
|
require.Fail(t, "unknown SBOM type", table.SBOM)
|
|
}
|
|
}
|
|
|
|
configFile, err := image.ConfigFile()
|
|
require.NoError(t, err)
|
|
require.GreaterOrEqual(t, len(configFile.History), 3)
|
|
|
|
require.Equal(t, table.ExpectedLabels, configFile.Config.Labels)
|
|
|
|
var creationTime time.Time
|
|
if table.CreationTime != "" {
|
|
ct, err := strconv.ParseInt(table.CreationTime, 10, 64)
|
|
require.NoError(t, err)
|
|
creationTime = time.Unix(ct, 0).UTC()
|
|
|
|
require.Equal(t, creationTime, configFile.Created.Time)
|
|
}
|
|
require.Equal(t, creationTime, configFile.History[len(configFile.History)-1].Created.Time)
|
|
|
|
var koDataCreationTime time.Time
|
|
if table.KoDataCreationTime != "" {
|
|
kdct, err := strconv.ParseInt(table.KoDataCreationTime, 10, 64)
|
|
require.NoError(t, err)
|
|
koDataCreationTime = time.Unix(kdct, 0).UTC()
|
|
}
|
|
require.Equal(t, koDataCreationTime, configFile.History[len(configFile.History)-2].Created.Time)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPublishPipeError(t *testing.T) {
|
|
makeCtx := func() *context.Context {
|
|
return testctx.NewWithCfg(config.Project{
|
|
Builds: []config.Build{
|
|
{
|
|
ID: "foo",
|
|
Main: "./...",
|
|
},
|
|
},
|
|
Kos: []config.Ko{
|
|
{
|
|
ID: "default",
|
|
Build: "foo",
|
|
WorkingDir: "./testdata/app/",
|
|
Repository: "fakerepo:8080/",
|
|
Tags: []string{"latest", "{{.Tag}}"},
|
|
},
|
|
},
|
|
}, testctx.WithCurrentTag("v1.0.0"))
|
|
}
|
|
|
|
t.Run("invalid base image", func(t *testing.T) {
|
|
ctx := makeCtx()
|
|
ctx.Config.Kos[0].BaseImage = "not a valid image hopefully"
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
require.EqualError(t, Pipe{}.Publish(ctx), `build: could not parse reference: not a valid image hopefully`)
|
|
})
|
|
|
|
t.Run("invalid label tmpl", func(t *testing.T) {
|
|
ctx := makeCtx()
|
|
ctx.Config.Kos[0].Labels = map[string]string{"nope": "{{.Nope}}"}
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
testlib.RequireTemplateError(t, Pipe{}.Publish(ctx))
|
|
})
|
|
|
|
t.Run("invalid sbom", func(t *testing.T) {
|
|
ctx := makeCtx()
|
|
ctx.Config.Kos[0].SBOM = "nope"
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
require.EqualError(t, Pipe{}.Publish(ctx), `makeBuilder: unknown sbom type: "nope"`)
|
|
})
|
|
|
|
t.Run("invalid build", func(t *testing.T) {
|
|
ctx := makeCtx()
|
|
ctx.Config.Kos[0].WorkingDir = t.TempDir()
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
require.EqualError(t, Pipe{}.Publish(ctx), `build: exit status 1`)
|
|
})
|
|
|
|
t.Run("invalid tags tmpl", func(t *testing.T) {
|
|
ctx := makeCtx()
|
|
ctx.Config.Kos[0].Tags = []string{"{{.Nope}}"}
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
testlib.RequireTemplateError(t, Pipe{}.Publish(ctx))
|
|
})
|
|
|
|
t.Run("invalid creation time", func(t *testing.T) {
|
|
ctx := makeCtx()
|
|
ctx.Config.Kos[0].CreationTime = "nope"
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
err := Pipe{}.Publish(ctx)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), `strconv.ParseInt: parsing "nope": invalid syntax`)
|
|
})
|
|
|
|
t.Run("invalid creation time tmpl", func(t *testing.T) {
|
|
ctx := makeCtx()
|
|
ctx.Config.Kos[0].CreationTime = "{{.Nope}}"
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
testlib.RequireTemplateError(t, Pipe{}.Publish(ctx))
|
|
})
|
|
|
|
t.Run("invalid kodata creation time", func(t *testing.T) {
|
|
ctx := makeCtx()
|
|
ctx.Config.Kos[0].KoDataCreationTime = "nope"
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
err := Pipe{}.Publish(ctx)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), `strconv.ParseInt: parsing "nope": invalid syntax`)
|
|
})
|
|
|
|
t.Run("invalid kodata creation time tmpl", func(t *testing.T) {
|
|
ctx := makeCtx()
|
|
ctx.Config.Kos[0].KoDataCreationTime = "{{.Nope}}"
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
testlib.RequireTemplateError(t, Pipe{}.Publish(ctx))
|
|
})
|
|
|
|
t.Run("invalid env tmpl", func(t *testing.T) {
|
|
ctx := makeCtx()
|
|
ctx.Config.Builds[0].Env = []string{"{{.Nope}}"}
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
testlib.RequireTemplateError(t, Pipe{}.Publish(ctx))
|
|
})
|
|
|
|
t.Run("invalid ldflags tmpl", func(t *testing.T) {
|
|
ctx := makeCtx()
|
|
ctx.Config.Builds[0].Ldflags = []string{"{{.Nope}}"}
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
testlib.RequireTemplateError(t, Pipe{}.Publish(ctx))
|
|
})
|
|
|
|
t.Run("invalid flags tmpl", func(t *testing.T) {
|
|
ctx := makeCtx()
|
|
ctx.Config.Builds[0].Flags = []string{"{{.Nope}}"}
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
testlib.RequireTemplateError(t, Pipe{}.Publish(ctx))
|
|
})
|
|
|
|
t.Run("publish fail", func(t *testing.T) {
|
|
ctx := makeCtx()
|
|
require.NoError(t, Pipe{}.Default(ctx))
|
|
err := Pipe{}.Publish(ctx)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), `publish: writing sbom: Get "https://fakerepo:8080/v2/": dial tcp:`)
|
|
})
|
|
}
|
|
|
|
func TestApplyTemplate(t *testing.T) {
|
|
t.Run("success", func(t *testing.T) {
|
|
foo, err := applyTemplate(testctx.NewWithCfg(config.Project{
|
|
Env: []string{"FOO=bar"},
|
|
}), []string{"{{ .Env.FOO }}"})
|
|
require.NoError(t, err)
|
|
require.Equal(t, []string{"bar"}, foo)
|
|
})
|
|
t.Run("error", func(t *testing.T) {
|
|
_, err := applyTemplate(testctx.New(), []string{"{{ .Nope}}"})
|
|
require.Error(t, err)
|
|
})
|
|
}
|