Skip to content

Commit 1503a12

Browse files
committed
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 <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent 66dff59 commit 1503a12

File tree

5 files changed

+88
-40
lines changed

5 files changed

+88
-40
lines changed

cmd/relui/deployment-prod.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ spec:
3737
- "--builder-master-key=secret:symbolic-datum-552/builder-master-key"
3838
- "--github-token=secret:symbolic-datum-552/maintner-github-token"
3939
- "--scratch-files-base=gs://golang-release-staging/relui-scratch"
40-
- "--staging-files-base=gs://golang-release-staging"
4140
- "--serving-files-base=gs://golang"
4241
- "--edge-cache-url=https://dl.google.com/go"
4342
- "--website-upload-url=https://go.dev/dl/upload"

cmd/relui/main.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ var (
4646
pgConnect = flag.String("pg-connect", "", "Postgres connection string or URI. If empty, libpq connection defaults are used.")
4747

4848
scratchFilesBase = flag.String("scratch-files-base", "", "Storage for scratch files. gs://bucket/path or file:///path/to/scratch.")
49-
stagingFilesBase = flag.String("staging-files-base", "", "Storage for staging files. gs://bucket/path or file:///path/to/staging.")
5049
servingFilesBase = flag.String("serving-files-base", "", "Storage for serving files. gs://bucket/path or file:///path/to/serving.")
5150
edgeCacheURL = flag.String("edge-cache-url", "", "URL release files appear at when published to the CDN, e.g. https://dl.google.com/go.")
5251
websiteUploadURL = flag.String("website-upload-url", "", "URL to POST website file data to, e.g. https://go.dev/dl/upload.")
@@ -130,7 +129,6 @@ func main() {
130129
CreateBuildlet: coordinator.CreateBuildlet,
131130
GCSClient: gcsClient,
132131
ScratchURL: *scratchFilesBase,
133-
StagingURL: *stagingFilesBase,
134132
ServingURL: *servingFilesBase,
135133
DownloadURL: *edgeCacheURL,
136134
PublishFile: func(f *relui.WebsiteFile) error {

internal/relui/buildrelease_test.go

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"os"
2121
"os/exec"
2222
"path/filepath"
23+
"regexp"
2324
"runtime"
2425
"strings"
2526
"sync"
@@ -65,9 +66,23 @@ func testRelease(t *testing.T, wantVersion string, kind task.ReleaseKind) {
6566
}
6667

6768
// Set up the fake signing process.
68-
stagingDir := t.TempDir()
69-
go fakeSign(ctx, t, filepath.Join(stagingDir, wantVersion))
69+
scratchDir := t.TempDir()
7070
signingPollDuration = 100 * time.Millisecond
71+
argRe := regexp.MustCompile(`--relui_staging="(.*?)"`)
72+
outputListener := func(taskName string, output interface{}) {
73+
if taskName != "Start signing command" {
74+
return
75+
}
76+
matches := argRe.FindStringSubmatch(output.(string))
77+
if matches == nil {
78+
return
79+
}
80+
u, err := url.Parse(matches[1])
81+
if err != nil {
82+
t.Fatal(err)
83+
}
84+
go fakeSign(ctx, t, u.Path)
85+
}
7186

7287
// Set up the fake CDN publishing process.
7388
servingDir := t.TempDir()
@@ -100,8 +115,7 @@ func testRelease(t *testing.T, wantVersion string, kind task.ReleaseKind) {
100115
buildTasks := &BuildReleaseTasks{
101116
GerritURL: tarballServer.URL,
102117
GCSClient: nil,
103-
ScratchURL: "file://" + filepath.ToSlash(t.TempDir()),
104-
StagingURL: "file://" + filepath.ToSlash(stagingDir),
118+
ScratchURL: "file://" + filepath.ToSlash(scratchDir),
105119
ServingURL: "file://" + filepath.ToSlash(servingDir),
106120
CreateBuildlet: fakeBuildlets.createBuildlet,
107121
DownloadURL: dlServer.URL,
@@ -122,7 +136,7 @@ func testRelease(t *testing.T, wantVersion string, kind task.ReleaseKind) {
122136
if err != nil {
123137
t.Fatal(err)
124138
}
125-
_, err = w.Run(ctx, &verboseListener{t})
139+
_, err = w.Run(ctx, &verboseListener{t, outputListener})
126140
if err != nil {
127141
t.Fatal(err)
128142
}
@@ -596,7 +610,10 @@ func (b *fakeBuildlet) WorkDir(ctx context.Context) (string, error) {
596610
return b.dir, nil
597611
}
598612

599-
type verboseListener struct{ t *testing.T }
613+
type verboseListener struct {
614+
t *testing.T
615+
outputListener func(string, interface{})
616+
}
600617

601618
func (l *verboseListener) TaskStateChanged(_ uuid.UUID, _ string, st *workflow.TaskState) error {
602619
switch {
@@ -606,6 +623,9 @@ func (l *verboseListener) TaskStateChanged(_ uuid.UUID, _ string, st *workflow.T
606623
l.t.Logf("task %-10v: error: %v", st.Name, st.Error)
607624
default:
608625
l.t.Logf("task %-10v: done: %v", st.Name, st.Result)
626+
if l.outputListener != nil {
627+
l.outputListener(st.Name, st.Result)
628+
}
609629
}
610630
return nil
611631
}
@@ -632,13 +652,17 @@ func fakeSign(ctx context.Context, t *testing.T, dir string) {
632652
}
633653

634654
func fakeSignOnce(t *testing.T, dir string, seen map[string]bool) {
635-
contents, err := os.ReadDir(dir)
655+
_, err := os.Stat(filepath.Join(dir, "ready"))
636656
if os.IsNotExist(err) {
637657
return
638658
}
639659
if err != nil {
640660
t.Fatal(err)
641661
}
662+
contents, err := os.ReadDir(dir)
663+
if err != nil {
664+
t.Fatal(err)
665+
}
642666
for _, fi := range contents {
643667
fn := fi.Name()
644668
if fn == "signed" || seen[fn] {
@@ -759,6 +783,9 @@ func TestFakeSign(t *testing.T) {
759783
t.Fatal(err)
760784
}
761785
}
786+
if err := ioutil.WriteFile(filepath.Join(dir, "ready"), nil, 0777); err != nil {
787+
t.Fatal(err)
788+
}
762789
fakeSignOnce(t, dir, map[string]bool{})
763790
want := map[string]bool{}
764791
for _, f := range strings.Split(strings.TrimSpace(outputs), "\n") {

internal/relui/workflows.go

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,9 @@ func addSingleReleaseWorkflow(build *BuildReleaseTasks, milestone *task.Mileston
307307
dlclCommit := wd.Task("Wait for DL CL", version.AwaitCL, dlcl, wd.Constant(""))
308308
wd.Output("Download CL submitted", dlclCommit)
309309

310+
startSigner := wd.Task("Start signing command", build.startSigningCommand, nextVersion)
311+
wd.Output("Signing command", startSigner)
312+
310313
// Build, test, and sign release.
311314
signedAndTestedArtifacts, err := build.addBuildTasks(wd, "go1.19", nextVersion, releaseBase, skipTests, checked)
312315
if err != nil {
@@ -382,13 +385,13 @@ func (tasks *BuildReleaseTasks) addBuildTasks(wd *workflow.Definition, majorVers
382385

383386
// BuildReleaseTasks serves as an adapter to the various build tasks in the task package.
384387
type BuildReleaseTasks struct {
385-
GerritURL string
386-
GCSClient *storage.Client
387-
ScratchURL, StagingURL, ServingURL string
388-
DownloadURL string
389-
PublishFile func(*WebsiteFile) error
390-
CreateBuildlet func(string) (buildlet.Client, error)
391-
ApproveActionFunc func(taskName string) func(*workflow.TaskContext, interface{}) error
388+
GerritURL string
389+
GCSClient *storage.Client
390+
ScratchURL, ServingURL string
391+
DownloadURL string
392+
PublishFile func(*WebsiteFile) error
393+
CreateBuildlet func(string) (buildlet.Client, error)
394+
ApproveActionFunc func(taskName string) func(*workflow.TaskContext, interface{}) error
392395
}
393396

394397
func (b *BuildReleaseTasks) buildSource(ctx *workflow.TaskContext, revision, version string) (artifact, error) {
@@ -530,15 +533,22 @@ func (b *BuildReleaseTasks) runBuildStep(
530533
}, nil
531534
}
532535

536+
// An artifact represents a file as it moves through the release process. Most
537+
// files will appear on go.dev/dl eventually.
533538
type artifact struct {
534539
// The target platform of this artifact, or nil for source.
535540
Target *releasetargets.Target
536-
// The scratch path of this artifact.
541+
// The scratch path of this artifact within the scratch directory.
542+
// <workflow-id>/<filename>-<random-number>
537543
ScratchPath string
538-
// The path the artifact was staged to for the signing process.
544+
// The path within the scratch directory the artifact was staged to for the
545+
// signing process.
546+
// <workflow-id>/signing/<go version>/<filename>
539547
StagingPath string
540-
// The path artifact can be found at after the signing process. It may be
541-
// the same as the staging path for artifacts that are externally signed.
548+
// The path within the scratch directory the artifact can be found at
549+
// after the signing process. For files not modified by the signing
550+
// process, the staging path, or for those that are
551+
// <workflow-id>/signing/<go version>/signed/<filename>
542552
SignedPath string
543553
// The contents of the GPG signature for this artifact (.asc file).
544554
GPGSignature string
@@ -560,15 +570,17 @@ func (w *sizeWriter) Write(p []byte) (n int, err error) {
560570
return len(p), nil
561571
}
562572

573+
func (tasks *BuildReleaseTasks) startSigningCommand(ctx *workflow.TaskContext, version string) (string, error) {
574+
args := fmt.Sprintf("--relui_staging=%q", path.Join(tasks.ScratchURL, signingStagingDir(ctx, version)))
575+
ctx.Printf("run signer with " + args)
576+
return args, nil
577+
}
578+
563579
func (tasks *BuildReleaseTasks) copyToStaging(ctx *workflow.TaskContext, version string, artifacts []artifact) ([]artifact, error) {
564580
scratchFS, err := gcsfs.FromURL(ctx, tasks.GCSClient, tasks.ScratchURL)
565581
if err != nil {
566582
return nil, err
567583
}
568-
stagingFS, err := gcsfs.FromURL(ctx, tasks.GCSClient, tasks.StagingURL)
569-
if err != nil {
570-
return nil, err
571-
}
572584
var stagedArtifacts []artifact
573585
for _, a := range artifacts {
574586
staged := a
@@ -577,14 +589,14 @@ func (tasks *BuildReleaseTasks) copyToStaging(ctx *workflow.TaskContext, version
577589
} else {
578590
staged.Filename = version + "." + a.Suffix
579591
}
580-
staged.StagingPath = path.Join(version, staged.Filename)
592+
staged.StagingPath = path.Join(signingStagingDir(ctx, version), staged.Filename)
581593
stagedArtifacts = append(stagedArtifacts, staged)
582594

583595
in, err := scratchFS.Open(a.ScratchPath)
584596
if err != nil {
585597
return nil, err
586598
}
587-
out, err := gcsfs.Create(stagingFS, staged.StagingPath)
599+
out, err := gcsfs.Create(scratchFS, staged.StagingPath)
588600
if err != nil {
589601
return nil, err
590602
}
@@ -598,9 +610,20 @@ func (tasks *BuildReleaseTasks) copyToStaging(ctx *workflow.TaskContext, version
598610
return nil, err
599611
}
600612
}
613+
out, err := gcsfs.Create(scratchFS, path.Join(signingStagingDir(ctx, version), "ready"))
614+
if err != nil {
615+
return nil, err
616+
}
617+
if err := out.Close(); err != nil {
618+
return nil, err
619+
}
601620
return stagedArtifacts, nil
602621
}
603622

623+
func signingStagingDir(ctx *workflow.TaskContext, version string) string {
624+
return path.Join(ctx.WorkflowID.String(), "signing", version)
625+
}
626+
604627
var signingPollDuration = 30 * time.Second
605628

606629
// awaitSigned waits for all of artifacts to be signed, plus the pkgs for
@@ -617,7 +640,7 @@ func (tasks *BuildReleaseTasks) awaitSigned(ctx *workflow.TaskContext, version s
617640
})
618641
}
619642

620-
stagingFS, err := gcsfs.FromURL(ctx, tasks.GCSClient, tasks.StagingURL)
643+
scratchFS, err := gcsfs.FromURL(ctx, tasks.GCSClient, tasks.ScratchURL)
621644
if err != nil {
622645
return nil, err
623646
}
@@ -629,7 +652,7 @@ func (tasks *BuildReleaseTasks) awaitSigned(ctx *workflow.TaskContext, version s
629652
var signedArtifacts []artifact
630653
for {
631654
for a := range todo {
632-
signed, ok, err := readSignedArtifact(stagingFS, version, a)
655+
signed, ok, err := readSignedArtifact(ctx, scratchFS, version, a)
633656
if err != nil {
634657
return nil, err
635658
}
@@ -653,7 +676,7 @@ func (tasks *BuildReleaseTasks) awaitSigned(ctx *workflow.TaskContext, version s
653676
}
654677
}
655678

656-
func readSignedArtifact(stagingFS fs.FS, version string, a artifact) (_ artifact, ok bool, _ error) {
679+
func readSignedArtifact(ctx *workflow.TaskContext, scratchFS fs.FS, version string, a artifact) (_ artifact, ok bool, _ error) {
657680
// Our signing process has somewhat uneven behavior. In general, for things
658681
// that contain their own signature, such as MSIs and .pkgs, we don't
659682
// produce a GPG signature, just the new file. On macOS, tars can be signed
@@ -684,18 +707,19 @@ func readSignedArtifact(stagingFS fs.FS, version string, a artifact) (_ artifact
684707
Filename: a.Filename,
685708
Suffix: a.Suffix,
686709
}
710+
stagingDir := signingStagingDir(ctx, version)
687711
if modifiedBySigning {
688-
signed.SignedPath = version + "/signed/" + a.Filename
712+
signed.SignedPath = stagingDir + "/signed/" + a.Filename
689713
} else {
690-
signed.SignedPath = version + "/" + a.Filename
714+
signed.SignedPath = stagingDir + "/" + a.Filename
691715
}
692716

693-
fi, err := fs.Stat(stagingFS, signed.SignedPath)
717+
fi, err := fs.Stat(scratchFS, signed.SignedPath)
694718
if err != nil {
695719
return artifact{}, false, nil
696720
}
697721
if modifiedBySigning {
698-
hash, err := fs.ReadFile(stagingFS, version+"/signed/"+a.Filename+".sha256")
722+
hash, err := fs.ReadFile(scratchFS, stagingDir+"/signed/"+a.Filename+".sha256")
699723
if err != nil {
700724
return artifact{}, false, nil
701725
}
@@ -706,7 +730,7 @@ func readSignedArtifact(stagingFS fs.FS, version string, a artifact) (_ artifact
706730
signed.Size = a.Size
707731
}
708732
if hasGPG {
709-
sig, err := fs.ReadFile(stagingFS, version+"/signed/"+a.Filename+".asc")
733+
sig, err := fs.ReadFile(scratchFS, stagingDir+"/signed/"+a.Filename+".asc")
710734
if err != nil {
711735
return artifact{}, false, nil
712736
}
@@ -718,7 +742,7 @@ func readSignedArtifact(stagingFS fs.FS, version string, a artifact) (_ artifact
718742
var uploadPollDuration = 30 * time.Second
719743

720744
func (tasks *BuildReleaseTasks) uploadArtifacts(ctx *workflow.TaskContext, artifacts []artifact) error {
721-
stagingFS, err := gcsfs.FromURL(ctx, tasks.GCSClient, tasks.StagingURL)
745+
scratchFS, err := gcsfs.FromURL(ctx, tasks.GCSClient, tasks.ScratchURL)
722746
if err != nil {
723747
return err
724748
}
@@ -729,7 +753,7 @@ func (tasks *BuildReleaseTasks) uploadArtifacts(ctx *workflow.TaskContext, artif
729753

730754
todo := map[artifact]bool{}
731755
for _, a := range artifacts {
732-
if err := uploadArtifact(stagingFS, servingFS, a); err != nil {
756+
if err := uploadArtifact(scratchFS, servingFS, a); err != nil {
733757
return err
734758
}
735759
todo[a] = true
@@ -762,8 +786,8 @@ func (tasks *BuildReleaseTasks) uploadArtifacts(ctx *workflow.TaskContext, artif
762786
}
763787
}
764788

765-
func uploadArtifact(stagingFS, servingFS fs.FS, a artifact) error {
766-
in, err := stagingFS.Open(a.SignedPath)
789+
func uploadArtifact(scratchFS, servingFS fs.FS, a artifact) error {
790+
in, err := scratchFS.Open(a.SignedPath)
767791
if err != nil {
768792
return err
769793
}

internal/relui/workflows_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func runWorkflow(t *testing.T, ctx context.Context, w *workflow.Workflow, listen
150150
defer cancel()
151151
t.Helper()
152152
if listener == nil {
153-
listener = &verboseListener{t}
153+
listener = &verboseListener{t, nil}
154154
}
155155
return w.Run(ctx, listener)
156156
}

0 commit comments

Comments
 (0)