mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-01-18 03:56:52 +02:00
feat(ko): support labels and creation times (#3852)
* 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
This commit is contained in:
parent
b96dba0333
commit
c47315fead
@ -8,13 +8,16 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/awslabs/amazon-ecr-credential-helper/ecr-login"
|
||||
"github.com/chrismellard/docker-credential-acr-env/pkg/credhelper"
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/authn/github"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/google"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/google/ko/pkg/build"
|
||||
@ -138,7 +141,10 @@ type buildOptions struct {
|
||||
workingDir string
|
||||
platforms []string
|
||||
baseImage string
|
||||
labels map[string]string
|
||||
tags []string
|
||||
creationTime *v1.Time
|
||||
koDataCreationTime *v1.Time
|
||||
sbom string
|
||||
ldflags []string
|
||||
bare bool
|
||||
@ -187,6 +193,15 @@ func (o *buildOptions) makeBuilder(ctx *context.Context) (*build.Caching, error)
|
||||
return nil, nil, fmt.Errorf("unexpected base image media type: %s", desc.MediaType)
|
||||
}),
|
||||
}
|
||||
if o.creationTime != nil {
|
||||
buildOptions = append(buildOptions, build.WithCreationTime(*o.creationTime))
|
||||
}
|
||||
if o.koDataCreationTime != nil {
|
||||
buildOptions = append(buildOptions, build.WithKoDataCreationTime(*o.koDataCreationTime))
|
||||
}
|
||||
for k, v := range o.labels {
|
||||
buildOptions = append(buildOptions, build.WithLabel(k, v))
|
||||
}
|
||||
switch o.sbom {
|
||||
case "spdx":
|
||||
buildOptions = append(buildOptions, build.WithSPDX("devel"))
|
||||
@ -299,6 +314,33 @@ func buildBuildOptions(ctx *context.Context, cfg config.Ko) (*buildOptions, erro
|
||||
}
|
||||
opts.tags = tags
|
||||
|
||||
if cfg.CreationTime != "" {
|
||||
creationTime, err := getTimeFromTemplate(ctx, cfg.CreationTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.creationTime = creationTime
|
||||
}
|
||||
|
||||
if cfg.KoDataCreationTime != "" {
|
||||
koDataCreationTime, err := getTimeFromTemplate(ctx, cfg.KoDataCreationTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.koDataCreationTime = koDataCreationTime
|
||||
}
|
||||
|
||||
if len(cfg.Labels) > 0 {
|
||||
opts.labels = make(map[string]string, len(cfg.Labels))
|
||||
for k, v := range cfg.Labels {
|
||||
tv, err := tmpl.New(ctx).Apply(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.labels[k] = tv
|
||||
}
|
||||
}
|
||||
|
||||
if len(cfg.Env) > 0 {
|
||||
env, err := applyTemplate(ctx, cfg.Env)
|
||||
if err != nil {
|
||||
@ -336,3 +378,16 @@ func applyTemplate(ctx *context.Context, templateable []string) ([]string, error
|
||||
}
|
||||
return templated, nil
|
||||
}
|
||||
|
||||
func getTimeFromTemplate(ctx *context.Context, t string) (*v1.Time, error) {
|
||||
epoch, err := tmpl.New(ctx).Apply(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
seconds, err := strconv.ParseInt(epoch, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v1.Time{Time: time.Unix(seconds, 0)}, nil
|
||||
}
|
||||
|
@ -2,8 +2,10 @@ 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"
|
||||
@ -123,10 +125,14 @@ func TestPublishPipeSuccess(t *testing.T) {
|
||||
testlib.StartRegistry(t, "ko_registry", registryPort)
|
||||
|
||||
table := []struct {
|
||||
Name string
|
||||
SBOM string
|
||||
BaseImage string
|
||||
Platforms []string
|
||||
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
|
||||
@ -153,6 +159,19 @@ func TestPublishPipeSuccess(t *testing.T) {
|
||||
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)
|
||||
@ -173,15 +192,18 @@ func TestPublishPipeSuccess(t *testing.T) {
|
||||
},
|
||||
Kos: []config.Ko{
|
||||
{
|
||||
ID: "default",
|
||||
Build: "foo",
|
||||
WorkingDir: "./testdata/app/",
|
||||
BaseImage: table.BaseImage,
|
||||
Repository: repository,
|
||||
Platforms: table.Platforms,
|
||||
Tags: []string{table.Name},
|
||||
SBOM: table.SBOM,
|
||||
Bare: true,
|
||||
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,
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -250,6 +272,30 @@ func TestPublishPipeSuccess(t *testing.T) {
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -282,6 +328,13 @@ func TestPublishPipeError(t *testing.T) {
|
||||
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"
|
||||
@ -303,6 +356,38 @@ func TestPublishPipeError(t *testing.T) {
|
||||
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}}"}
|
||||
|
@ -202,21 +202,24 @@ type Krew struct {
|
||||
|
||||
// Ko contains the ko section
|
||||
type Ko struct {
|
||||
ID string `yaml:"id,omitempty" json:"id,omitempty"`
|
||||
Build string `yaml:"build,omitempty" json:"build,omitempty"`
|
||||
Main string `yaml:"main,omitempty" json:"main,omitempty"`
|
||||
WorkingDir string `yaml:"working_dir,omitempty" json:"working_dir,omitempty"`
|
||||
BaseImage string `yaml:"base_image,omitempty" json:"base_image,omitempty"`
|
||||
Repository string `yaml:"repository,omitempty" json:"repository,omitempty"`
|
||||
Platforms []string `yaml:"platforms,omitempty" json:"platforms,omitempty"`
|
||||
Tags []string `yaml:"tags,omitempty" json:"tags,omitempty"`
|
||||
SBOM string `yaml:"sbom,omitempty" json:"sbom,omitempty"`
|
||||
Ldflags []string `yaml:"ldflags,omitempty" json:"ldflags,omitempty"`
|
||||
Flags []string `yaml:"flags,omitempty" json:"flags,omitempty"`
|
||||
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
|
||||
Bare bool `yaml:"bare,omitempty" json:"bare,omitempty"`
|
||||
PreserveImportPaths bool `yaml:"preserve_import_paths,omitempty" json:"preserve_import_paths,omitempty"`
|
||||
BaseImportPaths bool `yaml:"base_import_paths,omitempty" json:"base_import_paths,omitempty"`
|
||||
ID string `yaml:"id,omitempty" json:"id,omitempty"`
|
||||
Build string `yaml:"build,omitempty" json:"build,omitempty"`
|
||||
Main string `yaml:"main,omitempty" json:"main,omitempty"`
|
||||
WorkingDir string `yaml:"working_dir,omitempty" json:"working_dir,omitempty"`
|
||||
BaseImage string `yaml:"base_image,omitempty" json:"base_image,omitempty"`
|
||||
Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"`
|
||||
Repository string `yaml:"repository,omitempty" json:"repository,omitempty"`
|
||||
Platforms []string `yaml:"platforms,omitempty" json:"platforms,omitempty"`
|
||||
Tags []string `yaml:"tags,omitempty" json:"tags,omitempty"`
|
||||
CreationTime string `yaml:"creation_time,omitempty" json:"creation_time,omitempty"`
|
||||
KoDataCreationTime string `yaml:"ko_data_creation_time,omitempty" json:"ko_data_creation_time,omitempty"`
|
||||
SBOM string `yaml:"sbom,omitempty" json:"sbom,omitempty"`
|
||||
Ldflags []string `yaml:"ldflags,omitempty" json:"ldflags,omitempty"`
|
||||
Flags []string `yaml:"flags,omitempty" json:"flags,omitempty"`
|
||||
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
|
||||
Bare bool `yaml:"bare,omitempty" json:"bare,omitempty"`
|
||||
PreserveImportPaths bool `yaml:"preserve_import_paths,omitempty" json:"preserve_import_paths,omitempty"`
|
||||
BaseImportPaths bool `yaml:"base_import_paths,omitempty" json:"base_import_paths,omitempty"`
|
||||
}
|
||||
|
||||
// Scoop contains the scoop.sh section.
|
||||
|
@ -39,6 +39,13 @@ kos:
|
||||
# Defaults to cgr.dev/chainguard/static.
|
||||
base_image: alpine
|
||||
|
||||
# Labels for the image.
|
||||
#
|
||||
# Defaults to null.
|
||||
# Since v1.17.
|
||||
labels:
|
||||
foo: bar
|
||||
|
||||
# Repository to push to.
|
||||
#
|
||||
# Defaults to the value of $KO_DOCKER_REPO.
|
||||
@ -58,6 +65,20 @@ kos:
|
||||
- latest
|
||||
- '{{.Tag}}'
|
||||
|
||||
# Creation time given to the image
|
||||
# in seconds since the Unix epoch as a string.
|
||||
#
|
||||
# Defaults to empty string.
|
||||
# Since v1.17.
|
||||
creation_time: '{{.CommitTimestamp}}'
|
||||
|
||||
# Creation time given to the files in the kodata directory
|
||||
# in seconds since the Unix epoch as a string.
|
||||
#
|
||||
# Defaults to empty string.
|
||||
# Since v1.17.
|
||||
ko_data_creation_time: '{{.CommitTimestamp}}'
|
||||
|
||||
# SBOM format to use.
|
||||
#
|
||||
# Defaults to spdx.
|
||||
|
Loading…
x
Reference in New Issue
Block a user