internal/relui: use a per-workflow directory for signing
We learned the hard way that using a well-known directory creates
confusion in the release process. Instead, create a "signing" directory
inside the per-workflow scratch dir and use that to communicate with the
internal signing tool.
In addition, create a sentinel file "ready" that can be used to signal
the internal tool to start working.
For golang/go#51797.
Change-Id: Id8236b932e22c445cc03ecec3e975d85583c914b
Reviewed-on: https://go-review.googlesource.com/c/build/+/411902
Run-TryBot: Heschi Kreinick <heschi@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
diff --git a/cmd/relui/deployment-prod.yaml b/cmd/relui/deployment-prod.yaml
index fd1b3fd..2b4c40b 100644
--- a/cmd/relui/deployment-prod.yaml
+++ b/cmd/relui/deployment-prod.yaml
@@ -37,7 +37,6 @@
- "--builder-master-key=secret:symbolic-datum-552/builder-master-key"
- "--github-token=secret:symbolic-datum-552/maintner-github-token"
- "--scratch-files-base=gs://golang-release-staging/relui-scratch"
- - "--staging-files-base=gs://golang-release-staging"
- "--serving-files-base=gs://golang"
- "--edge-cache-url=https://dl.google.com/go"
- "--website-upload-url=https://go.dev/dl/upload"
diff --git a/cmd/relui/main.go b/cmd/relui/main.go
index 2245ecd..4d031c3 100644
--- a/cmd/relui/main.go
+++ b/cmd/relui/main.go
@@ -46,7 +46,6 @@
pgConnect = flag.String("pg-connect", "", "Postgres connection string or URI. If empty, libpq connection defaults are used.")
scratchFilesBase = flag.String("scratch-files-base", "", "Storage for scratch files. gs://bucket/path or file:///path/to/scratch.")
- stagingFilesBase = flag.String("staging-files-base", "", "Storage for staging files. gs://bucket/path or file:///path/to/staging.")
servingFilesBase = flag.String("serving-files-base", "", "Storage for serving files. gs://bucket/path or file:///path/to/serving.")
edgeCacheURL = flag.String("edge-cache-url", "", "URL release files appear at when published to the CDN, e.g. https://dl.google.com/go.")
websiteUploadURL = flag.String("website-upload-url", "", "URL to POST website file data to, e.g. https://go.dev/dl/upload.")
@@ -130,7 +129,6 @@
CreateBuildlet: coordinator.CreateBuildlet,
GCSClient: gcsClient,
ScratchURL: *scratchFilesBase,
- StagingURL: *stagingFilesBase,
ServingURL: *servingFilesBase,
DownloadURL: *edgeCacheURL,
PublishFile: func(f *relui.WebsiteFile) error {
diff --git a/internal/relui/buildrelease_test.go b/internal/relui/buildrelease_test.go
index 3d2b93b..c86ef6f 100644
--- a/internal/relui/buildrelease_test.go
+++ b/internal/relui/buildrelease_test.go
@@ -20,6 +20,7 @@
"os"
"os/exec"
"path/filepath"
+ "regexp"
"runtime"
"strings"
"sync"
@@ -65,9 +66,23 @@
}
// Set up the fake signing process.
- stagingDir := t.TempDir()
- go fakeSign(ctx, t, filepath.Join(stagingDir, wantVersion))
+ scratchDir := t.TempDir()
signingPollDuration = 100 * time.Millisecond
+ argRe := regexp.MustCompile(`--relui_staging="(.*?)"`)
+ outputListener := func(taskName string, output interface{}) {
+ if taskName != "Start signing command" {
+ return
+ }
+ matches := argRe.FindStringSubmatch(output.(string))
+ if matches == nil {
+ return
+ }
+ u, err := url.Parse(matches[1])
+ if err != nil {
+ t.Fatal(err)
+ }
+ go fakeSign(ctx, t, u.Path)
+ }
// Set up the fake CDN publishing process.
servingDir := t.TempDir()
@@ -100,8 +115,7 @@
buildTasks := &BuildReleaseTasks{
GerritURL: tarballServer.URL,
GCSClient: nil,
- ScratchURL: "file://" + filepath.ToSlash(t.TempDir()),
- StagingURL: "file://" + filepath.ToSlash(stagingDir),
+ ScratchURL: "file://" + filepath.ToSlash(scratchDir),
ServingURL: "file://" + filepath.ToSlash(servingDir),
CreateBuildlet: fakeBuildlets.createBuildlet,
DownloadURL: dlServer.URL,
@@ -122,7 +136,7 @@
if err != nil {
t.Fatal(err)
}
- _, err = w.Run(ctx, &verboseListener{t})
+ _, err = w.Run(ctx, &verboseListener{t, outputListener})
if err != nil {
t.Fatal(err)
}
@@ -596,7 +610,10 @@
return b.dir, nil
}
-type verboseListener struct{ t *testing.T }
+type verboseListener struct {
+ t *testing.T
+ outputListener func(string, interface{})
+}
func (l *verboseListener) TaskStateChanged(_ uuid.UUID, _ string, st *workflow.TaskState) error {
switch {
@@ -606,6 +623,9 @@
l.t.Logf("task %-10v: error: %v", st.Name, st.Error)
default:
l.t.Logf("task %-10v: done: %v", st.Name, st.Result)
+ if l.outputListener != nil {
+ l.outputListener(st.Name, st.Result)
+ }
}
return nil
}
@@ -632,13 +652,17 @@
}
func fakeSignOnce(t *testing.T, dir string, seen map[string]bool) {
- contents, err := os.ReadDir(dir)
+ _, err := os.Stat(filepath.Join(dir, "ready"))
if os.IsNotExist(err) {
return
}
if err != nil {
t.Fatal(err)
}
+ contents, err := os.ReadDir(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
for _, fi := range contents {
fn := fi.Name()
if fn == "signed" || seen[fn] {
@@ -759,6 +783,9 @@
t.Fatal(err)
}
}
+ if err := ioutil.WriteFile(filepath.Join(dir, "ready"), nil, 0777); err != nil {
+ t.Fatal(err)
+ }
fakeSignOnce(t, dir, map[string]bool{})
want := map[string]bool{}
for _, f := range strings.Split(strings.TrimSpace(outputs), "\n") {
diff --git a/internal/relui/workflows.go b/internal/relui/workflows.go
index aa57775..9aca4fd 100644
--- a/internal/relui/workflows.go
+++ b/internal/relui/workflows.go
@@ -307,6 +307,9 @@
dlclCommit := wd.Task("Wait for DL CL", version.AwaitCL, dlcl, wd.Constant(""))
wd.Output("Download CL submitted", dlclCommit)
+ startSigner := wd.Task("Start signing command", build.startSigningCommand, nextVersion)
+ wd.Output("Signing command", startSigner)
+
// Build, test, and sign release.
signedAndTestedArtifacts, err := build.addBuildTasks(wd, "go1.19", nextVersion, releaseBase, skipTests, checked)
if err != nil {
@@ -382,13 +385,13 @@
// BuildReleaseTasks serves as an adapter to the various build tasks in the task package.
type BuildReleaseTasks struct {
- GerritURL string
- GCSClient *storage.Client
- ScratchURL, StagingURL, ServingURL string
- DownloadURL string
- PublishFile func(*WebsiteFile) error
- CreateBuildlet func(string) (buildlet.Client, error)
- ApproveActionFunc func(taskName string) func(*workflow.TaskContext, interface{}) error
+ GerritURL string
+ GCSClient *storage.Client
+ ScratchURL, ServingURL string
+ DownloadURL string
+ PublishFile func(*WebsiteFile) error
+ CreateBuildlet func(string) (buildlet.Client, error)
+ ApproveActionFunc func(taskName string) func(*workflow.TaskContext, interface{}) error
}
func (b *BuildReleaseTasks) buildSource(ctx *workflow.TaskContext, revision, version string) (artifact, error) {
@@ -530,15 +533,22 @@
}, nil
}
+// An artifact represents a file as it moves through the release process. Most
+// files will appear on go.dev/dl eventually.
type artifact struct {
// The target platform of this artifact, or nil for source.
Target *releasetargets.Target
- // The scratch path of this artifact.
+ // The scratch path of this artifact within the scratch directory.
+ // <workflow-id>/<filename>-<random-number>
ScratchPath string
- // The path the artifact was staged to for the signing process.
+ // The path within the scratch directory the artifact was staged to for the
+ // signing process.
+ // <workflow-id>/signing/<go version>/<filename>
StagingPath string
- // The path artifact can be found at after the signing process. It may be
- // the same as the staging path for artifacts that are externally signed.
+ // The path within the scratch directory the artifact can be found at
+ // after the signing process. For files not modified by the signing
+ // process, the staging path, or for those that are
+ // <workflow-id>/signing/<go version>/signed/<filename>
SignedPath string
// The contents of the GPG signature for this artifact (.asc file).
GPGSignature string
@@ -560,15 +570,17 @@
return len(p), nil
}
+func (tasks *BuildReleaseTasks) startSigningCommand(ctx *workflow.TaskContext, version string) (string, error) {
+ args := fmt.Sprintf("--relui_staging=%q", path.Join(tasks.ScratchURL, signingStagingDir(ctx, version)))
+ ctx.Printf("run signer with " + args)
+ return args, nil
+}
+
func (tasks *BuildReleaseTasks) copyToStaging(ctx *workflow.TaskContext, version string, artifacts []artifact) ([]artifact, error) {
scratchFS, err := gcsfs.FromURL(ctx, tasks.GCSClient, tasks.ScratchURL)
if err != nil {
return nil, err
}
- stagingFS, err := gcsfs.FromURL(ctx, tasks.GCSClient, tasks.StagingURL)
- if err != nil {
- return nil, err
- }
var stagedArtifacts []artifact
for _, a := range artifacts {
staged := a
@@ -577,14 +589,14 @@
} else {
staged.Filename = version + "." + a.Suffix
}
- staged.StagingPath = path.Join(version, staged.Filename)
+ staged.StagingPath = path.Join(signingStagingDir(ctx, version), staged.Filename)
stagedArtifacts = append(stagedArtifacts, staged)
in, err := scratchFS.Open(a.ScratchPath)
if err != nil {
return nil, err
}
- out, err := gcsfs.Create(stagingFS, staged.StagingPath)
+ out, err := gcsfs.Create(scratchFS, staged.StagingPath)
if err != nil {
return nil, err
}
@@ -598,9 +610,20 @@
return nil, err
}
}
+ out, err := gcsfs.Create(scratchFS, path.Join(signingStagingDir(ctx, version), "ready"))
+ if err != nil {
+ return nil, err
+ }
+ if err := out.Close(); err != nil {
+ return nil, err
+ }
return stagedArtifacts, nil
}
+func signingStagingDir(ctx *workflow.TaskContext, version string) string {
+ return path.Join(ctx.WorkflowID.String(), "signing", version)
+}
+
var signingPollDuration = 30 * time.Second
// awaitSigned waits for all of artifacts to be signed, plus the pkgs for
@@ -617,7 +640,7 @@
})
}
- stagingFS, err := gcsfs.FromURL(ctx, tasks.GCSClient, tasks.StagingURL)
+ scratchFS, err := gcsfs.FromURL(ctx, tasks.GCSClient, tasks.ScratchURL)
if err != nil {
return nil, err
}
@@ -629,7 +652,7 @@
var signedArtifacts []artifact
for {
for a := range todo {
- signed, ok, err := readSignedArtifact(stagingFS, version, a)
+ signed, ok, err := readSignedArtifact(ctx, scratchFS, version, a)
if err != nil {
return nil, err
}
@@ -653,7 +676,7 @@
}
}
-func readSignedArtifact(stagingFS fs.FS, version string, a artifact) (_ artifact, ok bool, _ error) {
+func readSignedArtifact(ctx *workflow.TaskContext, scratchFS fs.FS, version string, a artifact) (_ artifact, ok bool, _ error) {
// Our signing process has somewhat uneven behavior. In general, for things
// that contain their own signature, such as MSIs and .pkgs, we don't
// produce a GPG signature, just the new file. On macOS, tars can be signed
@@ -684,18 +707,19 @@
Filename: a.Filename,
Suffix: a.Suffix,
}
+ stagingDir := signingStagingDir(ctx, version)
if modifiedBySigning {
- signed.SignedPath = version + "/signed/" + a.Filename
+ signed.SignedPath = stagingDir + "/signed/" + a.Filename
} else {
- signed.SignedPath = version + "/" + a.Filename
+ signed.SignedPath = stagingDir + "/" + a.Filename
}
- fi, err := fs.Stat(stagingFS, signed.SignedPath)
+ fi, err := fs.Stat(scratchFS, signed.SignedPath)
if err != nil {
return artifact{}, false, nil
}
if modifiedBySigning {
- hash, err := fs.ReadFile(stagingFS, version+"/signed/"+a.Filename+".sha256")
+ hash, err := fs.ReadFile(scratchFS, stagingDir+"/signed/"+a.Filename+".sha256")
if err != nil {
return artifact{}, false, nil
}
@@ -706,7 +730,7 @@
signed.Size = a.Size
}
if hasGPG {
- sig, err := fs.ReadFile(stagingFS, version+"/signed/"+a.Filename+".asc")
+ sig, err := fs.ReadFile(scratchFS, stagingDir+"/signed/"+a.Filename+".asc")
if err != nil {
return artifact{}, false, nil
}
@@ -718,7 +742,7 @@
var uploadPollDuration = 30 * time.Second
func (tasks *BuildReleaseTasks) uploadArtifacts(ctx *workflow.TaskContext, artifacts []artifact) error {
- stagingFS, err := gcsfs.FromURL(ctx, tasks.GCSClient, tasks.StagingURL)
+ scratchFS, err := gcsfs.FromURL(ctx, tasks.GCSClient, tasks.ScratchURL)
if err != nil {
return err
}
@@ -729,7 +753,7 @@
todo := map[artifact]bool{}
for _, a := range artifacts {
- if err := uploadArtifact(stagingFS, servingFS, a); err != nil {
+ if err := uploadArtifact(scratchFS, servingFS, a); err != nil {
return err
}
todo[a] = true
@@ -762,8 +786,8 @@
}
}
-func uploadArtifact(stagingFS, servingFS fs.FS, a artifact) error {
- in, err := stagingFS.Open(a.SignedPath)
+func uploadArtifact(scratchFS, servingFS fs.FS, a artifact) error {
+ in, err := scratchFS.Open(a.SignedPath)
if err != nil {
return err
}
diff --git a/internal/relui/workflows_test.go b/internal/relui/workflows_test.go
index cc326d8..4cecad1 100644
--- a/internal/relui/workflows_test.go
+++ b/internal/relui/workflows_test.go
@@ -150,7 +150,7 @@
defer cancel()
t.Helper()
if listener == nil {
- listener = &verboseListener{t}
+ listener = &verboseListener{t, nil}
}
return w.Run(ctx, listener)
}