diff --git a/internal/pipe/sign/sign.go b/internal/pipe/sign/sign.go index fa9afb58d7a..d7706abd778 100644 --- a/internal/pipe/sign/sign.go +++ b/internal/pipe/sign/sign.go @@ -8,9 +8,9 @@ import ( "reflect" "github.com/apex/log" - "github.com/goreleaser/goreleaser/internal/artifact" "github.com/goreleaser/goreleaser/internal/deprecate" + "github.com/goreleaser/goreleaser/internal/ids" "github.com/goreleaser/goreleaser/internal/logext" "github.com/goreleaser/goreleaser/internal/pipe" "github.com/goreleaser/goreleaser/internal/semerrgroup" @@ -33,6 +33,7 @@ func (Pipe) Default(ctx *context.Context) error { deprecate.Notice("sign") } } + var ids = ids.New("signs") for i := range ctx.Config.Signs { cfg := &ctx.Config.Signs[i] if cfg.Cmd == "" { @@ -47,8 +48,12 @@ func (Pipe) Default(ctx *context.Context) error { if cfg.Artifacts == "" { cfg.Artifacts = "none" } + if cfg.ID == "" { + cfg.ID = "default" + } + ids.Inc(cfg.ID) } - return nil + return ids.Validate() } // Run executes the Pipe. @@ -101,7 +106,7 @@ func sign(ctx *context.Context, cfg config.Sign, artifacts []*artifact.Artifact) } func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*artifact.Artifact, error) { - env := ctx.Env + env := ctx.Env.Copy() env["artifact"] = a.Path env["signature"] = expand(cfg.Signature, env) @@ -133,6 +138,9 @@ func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*arti Type: artifact.Signature, Name: name, Path: filepath.Join(artifactPathBase, sigFilename), + Extra: map[string]interface{}{ + "ID": cfg.ID, + }, }, nil } diff --git a/internal/pipe/sign/sign_test.go b/internal/pipe/sign/sign_test.go index 63f688613c4..7dd11a735aa 100644 --- a/internal/pipe/sign/sign_test.go +++ b/internal/pipe/sign/sign_test.go @@ -9,6 +9,7 @@ import ( "os/exec" "path/filepath" "sort" + "strings" "testing" "time" @@ -16,6 +17,7 @@ import ( "github.com/goreleaser/goreleaser/pkg/config" "github.com/goreleaser/goreleaser/pkg/context" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var originKeyring = "testdata/gnupg" @@ -78,7 +80,21 @@ func TestSignArtifacts(t *testing.T) { ctx *context.Context signaturePaths []string signatureNames []string + expectedErrMsg string }{ + { + desc: "sign errors", + expectedErrMsg: "sign: exit failed", + ctx: context.New( + config.Project{ + Sign: config.Sign{ + Artifacts: "all", + Cmd: "exit", + Args: []string{"1"}, + }, + }, + ), + }, { desc: "sign single", ctx: context.New( @@ -105,6 +121,36 @@ func TestSignArtifacts(t *testing.T) { signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig"}, signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig"}, }, + { + desc: "multiple sign configs", + ctx: context.New( + config.Project{ + Signs: []config.Sign{ + { + ID: "s1", + Artifacts: "checksum", + }, + { + ID: "s2", + Artifacts: "checksum", + Signature: "${artifact}.sog", + }, + }, + }, + ), + signaturePaths: []string{ + "checksum.sig", + "checksum2.sig", + "checksum.sog", + "checksum2.sog", + }, + signatureNames: []string{ + "checksum.sig", + "checksum2.sig", + "checksum.sog", + "checksum2.sog", + }, + }, { desc: "sign filtered artifacts", ctx: context.New( @@ -178,14 +224,14 @@ func TestSignArtifacts(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(tt *testing.T) { - testSign(tt, test.ctx, test.signaturePaths, test.signatureNames) + testSign(tt, test.ctx, test.signaturePaths, test.signatureNames, test.expectedErrMsg) }) } } const user = "nopass" -func testSign(t *testing.T, ctx *context.Context, signaturePaths []string, signatureNames []string) { +func testSign(t *testing.T, ctx *context.Context, signaturePaths []string, signatureNames []string, expectedErrMsg string) { // create temp dir for file and signature tmpdir, err := ioutil.TempDir("", "goreleaser") assert.NoError(t, err) @@ -249,11 +295,26 @@ func testSign(t *testing.T, ctx *context.Context, signaturePaths []string, signa // configure the pipeline // make sure we are using the test keyring assert.NoError(t, Pipe{}.Default(ctx)) - ctx.Config.Signs[0].Args = append([]string{"--homedir", keyring}, ctx.Config.Signs[0].Args...) + for i := range ctx.Config.Signs { + ctx.Config.Signs[i].Args = append( + []string{"--homedir", keyring}, + ctx.Config.Signs[i].Args..., + ) + } // run the pipeline + if expectedErrMsg != "" { + assert.EqualError(t, Pipe{}.Run(ctx), expectedErrMsg) + return + } + assert.NoError(t, Pipe{}.Run(ctx)) + // ensure all artifacts have an ID + for _, arti := range ctx.Artifacts.Filter(artifact.ByType(artifact.Signature)).List() { + assert.NotEmptyf(t, arti.ExtraOr("ID", ""), ".Extra.ID on %s", arti.Path) + } + // verify that only the artifacts and the signatures are in the dist dir gotFiles := []string{} @@ -276,7 +337,7 @@ func testSign(t *testing.T, ctx *context.Context, signaturePaths []string, signa wantFiles := append(artifacts, signaturePaths...) sort.Strings(wantFiles) - assert.Equal(t, wantFiles, gotFiles) + assert.ElementsMatch(t, wantFiles, gotFiles) // verify the signatures for _, sig := range signaturePaths { @@ -288,11 +349,11 @@ func testSign(t *testing.T, ctx *context.Context, signaturePaths []string, signa signArtifacts = append(signArtifacts, sig.Name) } // check signature is an artifact - assert.Equal(t, signArtifacts, signatureNames) + assert.ElementsMatch(t, signArtifacts, signatureNames) } func verifySignature(t *testing.T, ctx *context.Context, sig string) { - artifact := sig[:len(sig)-len(".sig")] + artifact := strings.Replace(sig, filepath.Ext(sig), "", 1) // verify signature was made with key for usesr 'nopass' cmd := exec.Command("gpg", "--homedir", keyring, "--verify", filepath.Join(ctx.Config.Dist, sig), filepath.Join(ctx.Config.Dist, artifact)) @@ -304,6 +365,22 @@ func verifySignature(t *testing.T, ctx *context.Context, sig string) { // keyring before we do the verification. For now we punt and look in the // output. if !bytes.Contains(out, []byte(user)) { - t.Fatalf("signature is not from %s", user) + t.Fatalf("%s: signature is not from %s: %s", sig, user, string(out)) + } +} + +func TestSeveralSignsWithTheSameID(t *testing.T) { + var ctx = &context.Context{ + Config: config.Project{ + Signs: []config.Sign{ + { + ID: "a", + }, + { + ID: "a", + }, + }, + }, } + require.EqualError(t, Pipe{}.Default(ctx), "found 2 signs with the ID 'a', please fix your config") } diff --git a/pkg/config/config.go b/pkg/config/config.go index 58a11f9bf4b..658d8f2e4dc 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -230,6 +230,7 @@ type NFPMOverridables struct { // Sign config type Sign struct { + ID string `yaml:"id,omitempty"` Cmd string `yaml:"cmd,omitempty"` Args []string `yaml:"args,omitempty"` Signature string `yaml:"signature,omitempty"` diff --git a/pkg/context/context.go b/pkg/context/context.go index e09bf33d55b..870b5d75ded 100644 --- a/pkg/context/context.go +++ b/pkg/context/context.go @@ -28,6 +28,15 @@ type GitInfo struct { // Env is the environment variables type Env map[string]string +// Copy returns a copy of the environment. +func (e Env) Copy() Env { + var out = Env{} + for k, v := range e { + out[k] = v + } + return out +} + // Strings returns the current environment as a list of strings, suitable for // os executions. func (e Env) Strings() []string { diff --git a/www/content/sign.md b/www/content/sign.md index 6f75aa1026e..0ff138a9d25 100644 --- a/www/content/sign.md +++ b/www/content/sign.md @@ -28,6 +28,10 @@ To customize the signing pipeline you can use the following options: # .goreleaser.yml signs: - + # ID of the sign config, must be unique. + # Defaults to "default". + id: foo + # name of the signature file. # '${artifact}' is the path to the artifact that should be signed. #