mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-01-08 03:31:59 +02:00
feat: add artifact sign pipeline
This patch adds a generic artifact signing pipeline. Fixes #166
This commit is contained in:
parent
3ea8bc097f
commit
7b95e1e342
@ -144,6 +144,14 @@ type FPM struct {
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// Sign config
|
||||
type Sign struct {
|
||||
Cmd string `yaml:"cmd,omitempty"`
|
||||
Args []string `yaml:"args,omitempty"`
|
||||
Signature string `yaml:"signature,omitempty"`
|
||||
Artifacts string `yaml:"artifacts,omitempty"`
|
||||
}
|
||||
|
||||
// SnapcraftAppMetadata for the binaries that will be in the snap package
|
||||
type SnapcraftAppMetadata struct {
|
||||
Plugs []string
|
||||
@ -238,6 +246,7 @@ type Project struct {
|
||||
Artifactories []Artifactory `yaml:",omitempty"`
|
||||
Changelog Changelog `yaml:",omitempty"`
|
||||
Dist string `yaml:",omitempty"`
|
||||
Sign Sign `yaml:",omitempty"`
|
||||
|
||||
// this is a hack ¯\_(ツ)_/¯
|
||||
SingleBuild Build `yaml:"build,omitempty"`
|
||||
|
@ -37,6 +37,7 @@ type Context struct {
|
||||
Git GitInfo
|
||||
Binaries map[string]map[string][]Binary
|
||||
Artifacts []string
|
||||
Checksums []string
|
||||
Dockers []string
|
||||
ReleaseNotes string
|
||||
Version string
|
||||
@ -50,6 +51,7 @@ type Context struct {
|
||||
|
||||
var (
|
||||
artifactsLock sync.Mutex
|
||||
checksumsLock sync.Mutex
|
||||
dockersLock sync.Mutex
|
||||
binariesLock sync.Mutex
|
||||
)
|
||||
@ -63,6 +65,15 @@ func (ctx *Context) AddArtifact(file string) {
|
||||
log.WithField("artifact", file).Info("new release artifact")
|
||||
}
|
||||
|
||||
// AddChecksum adds a checksum file.
|
||||
func (ctx *Context) AddChecksum(file string) {
|
||||
checksumsLock.Lock()
|
||||
defer checksumsLock.Unlock()
|
||||
file = strings.TrimPrefix(file, ctx.Config.Dist+string(filepath.Separator))
|
||||
ctx.Checksums = append(ctx.Checksums, file)
|
||||
log.WithField("checksum", file).Info("new checksum file")
|
||||
}
|
||||
|
||||
// AddDocker adds a docker image to the docker images list
|
||||
func (ctx *Context) AddDocker(image string) {
|
||||
dockersLock.Lock()
|
||||
|
51
docs/075-sign.md
Normal file
51
docs/075-sign.md
Normal file
@ -0,0 +1,51 @@
|
||||
---
|
||||
title: Signing
|
||||
---
|
||||
|
||||
GoReleaser can sign some or all of the generated artifacts. Signing ensures
|
||||
that the artifacts have been generated by yourself and your users can verify
|
||||
that by comparing the generated signature with your public signing key.
|
||||
|
||||
Signing works in combination with checksum files and it is generally sufficient
|
||||
to sign the checksum files only.
|
||||
|
||||
The default is configured to create a detached signature for the checksum files
|
||||
with [GunPG](https://www.gnupg.org/) and your default key. To enable signing
|
||||
just add
|
||||
|
||||
```yaml
|
||||
# goreleaser.yml
|
||||
sign:
|
||||
artifacts: checksum
|
||||
```
|
||||
|
||||
To customize the signing pipeline you can use the following options:
|
||||
|
||||
```yml
|
||||
# .goreleaser.yml
|
||||
sign:
|
||||
# name of the signature file.
|
||||
# '${in}' is the path to the artifact that should be signed.
|
||||
#
|
||||
# signature: "${artifact}.sig"
|
||||
|
||||
# path to the signature command
|
||||
#
|
||||
# cmd: gpg
|
||||
|
||||
# command line arguments for the command
|
||||
#
|
||||
# to sign with a specific key use
|
||||
# args: ["-u", "<key id, fingerprint, email, ...>", "--output", "${signature}", "--detach-sign", "${artifact}"]
|
||||
#
|
||||
# args: ["--output", "${signature}", "--detach-sign", "${artifact}"]
|
||||
|
||||
|
||||
# which artifacts to sign
|
||||
#
|
||||
# checksum: only checksum file(s)
|
||||
# all: all artifacts
|
||||
# none: no signing
|
||||
#
|
||||
# artifacts: none
|
||||
```
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/goreleaser/goreleaser/pipeline/fpm"
|
||||
"github.com/goreleaser/goreleaser/pipeline/git"
|
||||
"github.com/goreleaser/goreleaser/pipeline/release"
|
||||
"github.com/goreleaser/goreleaser/pipeline/sign"
|
||||
"github.com/goreleaser/goreleaser/pipeline/snapcraft"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
@ -50,6 +51,7 @@ var pipes = []pipeline.Piper{
|
||||
fpm.Pipe{}, // archive via fpm (deb, rpm, etc)
|
||||
snapcraft.Pipe{}, // archive via snapcraft (snap)
|
||||
checksums.Pipe{}, // checksums of the files
|
||||
sign.Pipe{}, // sign artifacts
|
||||
docker.Pipe{}, // create and push docker images
|
||||
artifactory.Pipe{}, // push to artifactory
|
||||
release.Pipe{}, // release to github
|
||||
|
@ -39,6 +39,7 @@ func (Pipe) Run(ctx *context.Context) (err error) {
|
||||
log.WithError(err).Errorf("failed to close %s", file.Name())
|
||||
}
|
||||
ctx.AddArtifact(file.Name())
|
||||
ctx.AddChecksum(file.Name())
|
||||
}()
|
||||
var g errgroup.Group
|
||||
for _, artifact := range ctx.Artifacts {
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/goreleaser/goreleaser/pipeline/docker"
|
||||
"github.com/goreleaser/goreleaser/pipeline/fpm"
|
||||
"github.com/goreleaser/goreleaser/pipeline/release"
|
||||
"github.com/goreleaser/goreleaser/pipeline/sign"
|
||||
"github.com/goreleaser/goreleaser/pipeline/snapshot"
|
||||
)
|
||||
|
||||
@ -31,6 +32,7 @@ var defaulters = []pipeline.Defaulter{
|
||||
build.Pipe{},
|
||||
fpm.Pipe{},
|
||||
checksums.Pipe{},
|
||||
sign.Pipe{},
|
||||
docker.Pipe{},
|
||||
artifactory.Pipe{},
|
||||
brew.Pipe{},
|
||||
|
103
pipeline/sign/sign.go
Normal file
103
pipeline/sign/sign.go
Normal file
@ -0,0 +1,103 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/goreleaser/goreleaser/pipeline"
|
||||
)
|
||||
|
||||
type Pipe struct{}
|
||||
|
||||
func (Pipe) String() string {
|
||||
return "signing artifacts"
|
||||
}
|
||||
|
||||
func (Pipe) Default(ctx *context.Context) error {
|
||||
cfg := &ctx.Config.Sign
|
||||
if cfg.Cmd == "" {
|
||||
cfg.Cmd = "gpg"
|
||||
}
|
||||
if cfg.Signature == "" {
|
||||
cfg.Signature = "${artifact}.sig"
|
||||
}
|
||||
if len(cfg.Args) == 0 {
|
||||
cfg.Args = []string{"--output", "$signature", "--detach-sig", "$artifact"}
|
||||
}
|
||||
if cfg.Artifacts == "" {
|
||||
cfg.Artifacts = "none"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Pipe) Run(ctx *context.Context) error {
|
||||
switch ctx.Config.Sign.Artifacts {
|
||||
case "checksum":
|
||||
return sign(ctx, ctx.Checksums)
|
||||
case "all":
|
||||
return sign(ctx, ctx.Artifacts)
|
||||
case "none":
|
||||
return pipeline.Skip("artifact signing disabled")
|
||||
default:
|
||||
return fmt.Errorf("invalid list of artifacts to sign: %s", ctx.Config.Sign.Artifacts)
|
||||
}
|
||||
}
|
||||
|
||||
func sign(ctx *context.Context, artifacts []string) error {
|
||||
var sigs []string
|
||||
for _, a := range artifacts {
|
||||
sig, err := signone(ctx, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sigs = append(sigs, sig)
|
||||
}
|
||||
for _, sig := range sigs {
|
||||
ctx.AddArtifact(sig)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func signone(ctx *context.Context, artifact string) (string, error) {
|
||||
cfg := ctx.Config.Sign
|
||||
|
||||
artifact = filepath.Join(ctx.Config.Dist, artifact)
|
||||
env := map[string]string{
|
||||
"artifact": artifact,
|
||||
}
|
||||
|
||||
sig := expand(cfg.Signature, env)
|
||||
if sig == "" {
|
||||
return "", fmt.Errorf("sign: signature file cannot be empty")
|
||||
}
|
||||
if sig == artifact {
|
||||
return "", fmt.Errorf("sign: artifact and signature cannot be the same")
|
||||
}
|
||||
env["signature"] = sig
|
||||
|
||||
// todo(fs): check if $out already exists
|
||||
|
||||
var args []string
|
||||
for _, a := range cfg.Args {
|
||||
args = append(args, expand(a, env))
|
||||
}
|
||||
|
||||
cmd := exec.Command(cfg.Cmd, args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if len(output) > 200 {
|
||||
output = output[:200]
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("sign: %s failed with %q", cfg.Cmd, string(output))
|
||||
}
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
func expand(s string, env map[string]string) string {
|
||||
return os.Expand(s, func(key string) string {
|
||||
return env[key]
|
||||
})
|
||||
}
|
18
pipeline/sign/sign_test.go
Normal file
18
pipeline/sign/sign_test.go
Normal file
@ -0,0 +1,18 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
gpgPlainKeyID = "0279C27FC1602A0E"
|
||||
gpgEncryptedKeyID = "2AB4ABE1A4A47546"
|
||||
gpgPassword = "secret"
|
||||
gpgHome = "./testdata/gnupg"
|
||||
)
|
||||
|
||||
func TestDescription(t *testing.T) {
|
||||
assert.NotEmpty(t, Pipe{}.String())
|
||||
}
|
Loading…
Reference in New Issue
Block a user