/
add-retry-instructions.go
168 lines (143 loc) · 4.34 KB
/
add-retry-instructions.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package main
import (
_ "embed"
"errors"
"flag"
"fmt"
"html/template"
"io"
"log"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/microsoft/go-infra/azdo"
"github.com/microsoft/go-infra/subcmd"
)
func init() {
subcommands = append(subcommands, subcmd.Option{
Name: "add-retry-instructions",
Summary: "Create a Markdown doc with retry instructions and add it to the AzDO build.",
Description: `
This command creates a temp markdown file and uses the 'task.uploadsummary' logging command to
upload and attach it to the currently running build. Scans the environment for polling variables
passed to the agent by AzDO that should be used to retry the currently running release build.
The temp markdown file is not cleaned up, to allow AzDO time to process the logging command.
`,
Handle: handleAddRetryInstructions,
})
}
type retryTemplateArgs struct {
Checkboxes bool
Preapproval bool
LastNonNilEnv *envArg
}
//go:embed templates/retry.template.md
var retryTemplate string
func handleAddRetryInstructions(p subcmd.ParseFunc) error {
var args retryTemplateArgs
flag.BoolVar(&args.Checkboxes, "checkboxes", false, "Alert the dev to release checkboxes that may also need tweaking.")
flag.BoolVar(&args.Preapproval, "preapproval", false, "Alert the dev to a 'pre-approval' checkbox they should check.")
if err := p(); err != nil {
return err
}
for _, env := range os.Environ() {
a, err := newEnvArg(env)
if err != nil {
return err
}
if a == nil || a.Value == "nil" {
continue
}
if args.LastNonNilEnv == nil || args.LastNonNilEnv.Index < a.Index {
args.LastNonNilEnv = a
}
}
content, err := generateContent(args)
if err != nil {
log.Printf("Failed to evaluate template to produce instructions: %v\n", err)
content = fmt.Sprintf(
"Instruction text generation failed!\n\n"+
"Args: %#v\n\n"+
"Error: %v\n\n"+
"[The instructions template.](https://github.com/microsoft/go-infra/tree/main/cmd/releasego/templates)\n",
args,
err)
}
fmt.Printf("Saving generated markdown text to file:\n===\n%v===\n", content)
temp, err := os.CreateTemp(os.TempDir(), "retry-instructions-*.md")
if err != nil {
return err
}
defer temp.Close()
if _, err := io.WriteString(temp, content); err != nil {
return err
}
tempPath, err := filepath.Abs(temp.Name())
if err != nil {
return err
}
azdo.LogCmdUploadSummary(tempPath)
return nil
}
// Match env vars like "poll1buildId=123" with the number and value in groups. If there are
// multiple "=", treat the leftmost one as the separator. Ignore case.
var reg = regexp.MustCompile(`(?i)poll(\d+)(.+?)=(.*)`)
type envArg struct {
Value string
Name string
Index int
}
func newEnvArg(envVar string) (*envArg, error) {
matches := reg.FindStringSubmatch(envVar)
if len(matches) > 0 {
fmt.Printf("Found polling env var %q. Matches: %#v\n", envVar, matches)
num, err := strconv.Atoi(matches[1])
if err != nil {
return nil, fmt.Errorf("match 1 is not an int: %w", err)
}
value := matches[3]
return &envArg{value, matches[2], num}, nil
}
return nil, nil
}
func generateContent(args retryTemplateArgs) (string, error) {
// The AzDO Markdown renderer being used in this particular context (the Extensions page)
// displays numbered lists as a bulleted list, and doesn't indent the code block after the first
// step. Create our own numbering text instead.
n := 0
funcs := template.FuncMap{
"nextListEntry": func() string {
n++
return strconv.Itoa(n) + " - "
},
// AzDO capitalizes env vars, which makes them less readable, but the template has to
// compare against them to determine which instructions to show. Let the template use the
// readable mixed-case string by providing a case-insensitive "ieq" comparison func that
// behaves like "eq".
"ieq": func(args ...string) (bool, error) {
if len(args) < 2 {
return false, errors.New("missing argument for comparison")
}
left := args[0]
for _, right := range args[1:] {
if strings.EqualFold(left, right) {
return true, nil
}
}
return false, nil
},
}
t, err := template.New("retry.template.md").Funcs(funcs).Parse(retryTemplate)
if err != nil {
return "", err
}
var b strings.Builder
if err := t.Execute(&b, args); err != nil {
return "", err
}
return b.String(), nil
}