golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/task/tagtelemetry.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package task 6 7 import ( 8 "bytes" 9 "context" 10 "fmt" 11 "strings" 12 "time" 13 14 "golang.org/x/build/gerrit" 15 wf "golang.org/x/build/internal/workflow" 16 "golang.org/x/mod/semver" 17 ) 18 19 // TagTelemetryTasks implements a new workflow definition to tag 20 // x/telemetry/config whenever the generated config.json changes. 21 type TagTelemetryTasks struct { 22 Gerrit GerritClient 23 CloudBuild CloudBuildClient 24 } 25 26 func (t *TagTelemetryTasks) NewDefinition() *wf.Definition { 27 wd := wf.New() 28 29 reviewers := wf.Param(wd, reviewersParam) 30 changeID := wf.Task1(wd, "generate config CL", t.GenerateConfig, reviewers) 31 submitted := wf.Action1(wd, "await config CL submission", t.AwaitSubmission, changeID) 32 tag := wf.Task0(wd, "tag if appropriate", t.MaybeTag, wf.After(submitted)) 33 wf.Output(wd, "tag", tag) 34 35 return wd 36 } 37 38 // GenerateConfig runs the upload config generator in a buildlet, extracts the 39 // resulting config.json, and creates a CL with the result if anything changed. 40 // 41 // It returns the change ID, or "" if the CL was not created. 42 func (t *TagTelemetryTasks) GenerateConfig(ctx *wf.TaskContext, reviewers []string) (string, error) { 43 const clTitle = "config: regenerate upload config" 44 45 // Query for an existing pending config CL, to avoid duplication. 46 // 47 // Only wait a week, because configs are volatile: we really want to update 48 // them within a week. 49 query := fmt.Sprintf(`message:%q status:open owner:gobot@golang.org repo:telemetry -age:7d`, clTitle) 50 changes, err := t.Gerrit.QueryChanges(ctx, query) 51 if err != nil { 52 return "", err 53 } 54 if len(changes) > 0 { 55 ctx.Printf("not creating CL: found existing CL %d", changes[0].ChangeNumber) 56 return "", nil 57 } 58 59 const script = ` 60 cp config/config.json config/config.json.before 61 go run ./internal/configgen -w 62 ` 63 64 build, err := t.CloudBuild.RunScript(ctx, script, "telemetry", []string{"config/config.json.before", "config/config.json"}) 65 if err != nil { 66 return "", err 67 } 68 69 outputs, err := buildToOutputs(ctx, t.CloudBuild, build) 70 if err != nil { 71 return "", err 72 } 73 74 before, after := outputs["config/config.json.before"], outputs["config/config.json"] 75 if before == after { 76 ctx.Printf("not creating CL: config has not changed") 77 return "", nil 78 } 79 80 changeInput := gerrit.ChangeInput{ 81 Project: "telemetry", 82 Subject: fmt.Sprintf("%s\n\nThis is an automated CL which updates the generated upload config.", clTitle), 83 Branch: "master", 84 } 85 files := map[string]string{ 86 "config/config.json": string(after), 87 } 88 return t.Gerrit.CreateAutoSubmitChange(ctx, changeInput, reviewers, files) 89 } 90 91 // AwaitSubmission waits for the CL with the given change ID to be submitted. 92 // 93 // The return value is the submitted commit hash, or "" if changeID is "". 94 func (t *TagTelemetryTasks) AwaitSubmission(ctx *wf.TaskContext, changeID string) error { 95 if changeID == "" { 96 ctx.Printf("not awaiting: no CL was created") 97 return nil 98 } 99 100 ctx.Printf("awaiting review/submit of %v", ChangeLink(changeID)) 101 _, err := AwaitCondition(ctx, 10*time.Second, func() (string, bool, error) { 102 return t.Gerrit.Submitted(ctx, changeID, "") 103 }) 104 return err 105 } 106 107 // MaybeTag tags x/telemetry/config with the next version if config/config.json 108 // has changed. 109 // 110 // It returns the tag that was created, or "" if no tagging occurred. 111 func (t *TagTelemetryTasks) MaybeTag(ctx *wf.TaskContext) (string, error) { 112 latestTag, latestVersion, err := t.latestConfigVersion(ctx) 113 if err != nil { 114 return "", err 115 } 116 if latestTag == "" { 117 ctx.Printf("not tagging: no existing release tag found, not tagging the initial version") 118 return "", nil 119 } 120 tagInfo, err := t.Gerrit.GetTag(ctx, "telemetry", latestTag) 121 if err != nil { 122 return "", fmt.Errorf("reading tag %s: %v", latestTag, err) 123 } 124 125 latestConfig, err := t.Gerrit.ReadFile(ctx, "telemetry", tagInfo.Revision, "config/config.json") 126 if err != nil { 127 return "", fmt.Errorf("reading config/config.json@latest: %v", err) 128 } 129 master, err := t.Gerrit.ReadBranchHead(ctx, "telemetry", "master") 130 if err != nil { 131 return "", fmt.Errorf("reading master commit: %v", err) 132 } 133 masterConfig, err := t.Gerrit.ReadFile(ctx, "telemetry", master, "config/config.json") 134 if err != nil { 135 return "", fmt.Errorf("reading config/config.json@master: %v", err) 136 } 137 138 if bytes.Equal(latestConfig, masterConfig) { 139 ctx.Printf("not tagging: no change to config.json since latest tag") 140 return "", nil 141 } 142 143 nextVer, err := nextMinor(latestVersion) 144 if err != nil { 145 return "", fmt.Errorf("couldn't pick next version: %v", err) 146 } 147 tag := "config/" + nextVer 148 149 ctx.Printf("tagging x/telemetry/config at %v as %v", master, tag) 150 if err := t.Gerrit.Tag(ctx, "telemetry", tag, master); err != nil { 151 return "", fmt.Errorf("failed to tag: %v", err) 152 } 153 154 return tag, nil 155 } 156 157 func (t *TagTelemetryTasks) latestConfigVersion(ctx context.Context) (tag, version string, _ error) { 158 tags, err := t.Gerrit.ListTags(ctx, "telemetry") 159 if err != nil { 160 return "", "", err 161 } 162 latestTag := "" 163 latestRelease := "" 164 for _, tag := range tags { 165 ver, ok := strings.CutPrefix(tag, "config/") 166 if !ok { 167 continue 168 } 169 if semver.IsValid(ver) && semver.Prerelease(ver) == "" && 170 (latestRelease == "" || semver.Compare(latestRelease, ver) < 0) { 171 latestTag = tag 172 latestRelease = ver 173 } 174 } 175 return latestTag, latestRelease, nil 176 }