Skip to content

Commit bb73932

Browse files
committed
Add support for templated campaign specs
1 parent aa7d107 commit bb73932

File tree

3 files changed

+195
-6
lines changed

3 files changed

+195
-6
lines changed

internal/campaigns/executor_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,23 @@ func TestExecutor_Integration(t *testing.T) {
9393
executorTimeout: 20 * time.Millisecond,
9494
wantErrInclude: "execution in github.com/sourcegraph/src-cli failed: Timeout reached. Execution took longer than 20ms.",
9595
},
96+
{
97+
name: "templated",
98+
repos: []*graphql.Repository{srcCLIRepo},
99+
archives: []mockRepoArchive{
100+
{repo: srcCLIRepo, files: map[string]string{
101+
"README.md": "# Welcome to the README\n",
102+
"main.go": "package main\n\nfunc main() {\n\tfmt.Println( \"Hello World\")\n}\n",
103+
}},
104+
},
105+
steps: []Step{
106+
{Run: `go fmt main.go`, Container: "doesntmatter:13"},
107+
{Run: `touch modified-${{ modified_files }}.md`, Container: "alpine:13"},
108+
},
109+
wantFilesChanged: map[string][]string{
110+
srcCLIRepo.ID: []string{"main.go", "modified-main.go.md"},
111+
},
112+
},
96113
}
97114

98115
for _, tc := range tests {
@@ -149,6 +166,7 @@ func TestExecutor_Integration(t *testing.T) {
149166
t.Fatalf("wrong number of commits. want=%d, have=%d", want, have)
150167
}
151168

169+
t.Logf("diff=%s\n", spec.Commits[0].Diff)
152170
fileDiffs, err := diff.ParseMultiFileDiff([]byte(spec.Commits[0].Diff))
153171
if err != nil {
154172
t.Fatalf("failed to parse diff: %s", err)
@@ -165,8 +183,13 @@ func TestExecutor_Integration(t *testing.T) {
165183

166184
diffsByName := map[string]*diff.FileDiff{}
167185
for _, fd := range fileDiffs {
168-
diffsByName[fd.OrigName] = fd
186+
if fd.NewName == "/dev/null" {
187+
diffsByName[fd.OrigName] = fd
188+
} else {
189+
diffsByName[fd.NewName] = fd
190+
}
169191
}
192+
170193
for _, file := range wantFiles {
171194
if _, ok := diffsByName[file]; !ok {
172195
t.Errorf("%s was not changed", file)

internal/campaigns/run_steps.go

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"os"
1111
"os/exec"
1212
"strings"
13+
"text/template"
1314
"time"
1415

1516
"github.com/hashicorp/go-multierror"
@@ -57,6 +58,8 @@ func runSteps(ctx context.Context, wc *WorkspaceCreator, repo *graphql.Repositor
5758
return nil, errors.Wrap(err, "git commit failed")
5859
}
5960

61+
results := make([]StepResult, len(steps))
62+
6063
for i, step := range steps {
6164
logger.Logf("[Step %d] docker run %s %q", i+1, step.Container, step.Run)
6265

@@ -89,8 +92,18 @@ func runSteps(ctx context.Context, wc *WorkspaceCreator, repo *graphql.Repositor
8992
}
9093
hostTemp := fp.Name()
9194
defer os.Remove(hostTemp)
92-
if _, err := fp.WriteString(step.Run); err != nil {
93-
return nil, errors.Wrapf(err, "writing to temporary file %q", hostTemp)
95+
96+
stepContext := StepContext{Repository: repo}
97+
if i > 0 {
98+
stepContext.PreviousStep = results[i-1]
99+
}
100+
tmpl, err := parseStepRun(stepContext, step.Run)
101+
if err != nil {
102+
return nil, errors.Wrap(err, "parsing step run")
103+
}
104+
105+
if err := tmpl.Execute(fp, stepContext); err != nil {
106+
return nil, errors.Wrap(err, "executing template")
94107
}
95108
fp.Close()
96109

@@ -137,10 +150,22 @@ func runSteps(ctx context.Context, wc *WorkspaceCreator, repo *graphql.Repositor
137150
}
138151

139152
logger.Logf("[Step %d] complete in %s", i+1, elapsed)
140-
}
141153

142-
if _, err := runGitCmd("add", "--all"); err != nil {
143-
return nil, errors.Wrap(err, "git add failed")
154+
if _, err := runGitCmd("add", "--all"); err != nil {
155+
return nil, errors.Wrap(err, "git add failed")
156+
}
157+
158+
statusOut, err := runGitCmd("status", "--porcelain")
159+
if err != nil {
160+
return nil, errors.Wrap(err, "git status failed")
161+
}
162+
163+
changes, err := parseGitStatus(statusOut)
164+
if err != nil {
165+
return nil, errors.Wrap(err, "parsing git status output")
166+
}
167+
168+
results[i] = StepResult{Changes: changes}
144169
}
145170

146171
// As of Sourcegraph 3.14 we only support unified diff format.
@@ -157,6 +182,67 @@ func runSteps(ctx context.Context, wc *WorkspaceCreator, repo *graphql.Repositor
157182
return diffOut, err
158183
}
159184

185+
func parseStepRun(stepCtx StepContext, run string) (*template.Template, error) {
186+
t := template.New("step-run").Delims("${{", "}}")
187+
188+
funcMap := template.FuncMap{
189+
"repository_name": func() string { return stepCtx.Repository.Name },
190+
}
191+
192+
funcMap["modified_files"] = stepCtx.PreviousStep.ModifiedFiles
193+
194+
t.Funcs(funcMap)
195+
196+
return t.Parse(run)
197+
}
198+
199+
type StepContext struct {
200+
PreviousStep StepResult
201+
Repository *graphql.Repository
202+
}
203+
204+
type StepChanges struct {
205+
Modified []string
206+
Added []string
207+
Deleted []string
208+
}
209+
210+
type StepResult struct {
211+
Changes StepChanges
212+
}
213+
214+
func (r StepResult) ModifiedFiles() string { return strings.Join(r.Changes.Modified, " ") }
215+
func (r StepResult) AddedFiles() string { return strings.Join(r.Changes.Added, " ") }
216+
func (r StepResult) DeletedFiles() string { return strings.Join(r.Changes.Deleted, " ") }
217+
218+
func parseGitStatus(out []byte) (StepChanges, error) {
219+
result := StepChanges{}
220+
221+
stripped := strings.TrimSpace(string(out))
222+
if len(stripped) == 0 {
223+
return result, nil
224+
}
225+
226+
for _, line := range strings.Split(stripped, "\n") {
227+
if len(line) < 4 {
228+
return result, fmt.Errorf("git status line has unrecognized format: %q", line)
229+
}
230+
231+
file := line[3:len(line)]
232+
233+
switch line[0] {
234+
case 'M':
235+
result.Modified = append(result.Modified, file)
236+
case 'A':
237+
result.Added = append(result.Added, file)
238+
case 'D':
239+
result.Deleted = append(result.Deleted, file)
240+
}
241+
}
242+
243+
return result, nil
244+
}
245+
160246
func probeImageForShell(ctx context.Context, image string) (shell, tempfile string, err error) {
161247
// We need to know two things to be able to run a shell script:
162248
//
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package campaigns
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/sourcegraph/src-cli/internal/campaigns/graphql"
9+
)
10+
11+
func TestParseGitStatus(t *testing.T) {
12+
const input = `M README.md
13+
M another_file.go
14+
A new_file.txt
15+
A barfoo/new_file.txt
16+
D to_be_deleted.txt
17+
`
18+
parsed, err := parseGitStatus([]byte(input))
19+
if err != nil {
20+
t.Fatal(err)
21+
}
22+
23+
want := StepChanges{
24+
Modified: []string{"README.md", "another_file.go"},
25+
Added: []string{"new_file.txt", "barfoo/new_file.txt"},
26+
Deleted: []string{"to_be_deleted.txt"},
27+
}
28+
29+
if !cmp.Equal(want, parsed) {
30+
t.Fatalf("wrong output:\n%s", cmp.Diff(want, parsed))
31+
}
32+
}
33+
34+
func TestParseStepRun(t *testing.T) {
35+
tests := []struct {
36+
stepCtx StepContext
37+
run string
38+
want string
39+
}{
40+
{
41+
stepCtx: StepContext{
42+
Repository: &graphql.Repository{Name: "github.com/sourcegraph/src-cli"},
43+
PreviousStep: StepResult{
44+
Changes: StepChanges{
45+
Modified: []string{"go.mod"},
46+
Added: []string{"main.go.swp"},
47+
Deleted: []string{".DS_Store"},
48+
},
49+
},
50+
},
51+
52+
run: `${{ modified_files }} ${{ repository_name }}`,
53+
want: `go.mod github.com/sourcegraph/src-cli`,
54+
},
55+
{
56+
stepCtx: StepContext{
57+
Repository: &graphql.Repository{Name: "github.com/sourcegraph/src-cli"},
58+
},
59+
60+
run: `${{ modified_files }} ${{ repository_name }}`,
61+
want: ` github.com/sourcegraph/src-cli`,
62+
},
63+
}
64+
65+
for _, tc := range tests {
66+
parsed, err := parseStepRun(tc.stepCtx, tc.run)
67+
if err != nil {
68+
t.Fatal(err)
69+
}
70+
71+
var out bytes.Buffer
72+
if err := parsed.Execute(&out, tc.stepCtx); err != nil {
73+
t.Fatalf("executing template failed: %s", err)
74+
}
75+
76+
if out.String() != tc.want {
77+
t.Fatalf("wrong output:\n%s", cmp.Diff(tc.want, out.String()))
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)