Skip to content

Commit d519905

Browse files
authored
Merge branch 'main' into wb/login-reauth
2 parents e33fe91 + 4bcd4ce commit d519905

5 files changed

Lines changed: 92 additions & 5 deletions

File tree

internal/batches/executor/run_steps.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,16 @@ func RunSteps(ctx context.Context, opts *RunStepsOpts) (stepResults []execution.
165165
continue
166166
}
167167

168+
resolvedContainer, err := renderStepContainer(step.Container, &stepContext)
169+
if err != nil {
170+
return nil, errors.Wrapf(err, "failed to resolve image for step %d", i+1)
171+
}
172+
step.Container = resolvedContainer
173+
168174
// We need to grab the digest for the exact image we're using.
169175
img, err := opts.EnsureImage(ctx, step.Container)
170176
if err != nil {
171-
return nil, err
177+
return nil, errors.Wrapf(err, "failed to pull image for step %d: %s", i+1, step.Container)
172178
}
173179
digest, err := img.Digest(ctx)
174180
if err != nil {
@@ -241,6 +247,27 @@ func RunSteps(ctx context.Context, opts *RunStepsOpts) (stepResults []execution.
241247
return stepResults, err
242248
}
243249

250+
func renderStepContainer(container string, stepContext *template.StepContext) (string, error) {
251+
if container == "" {
252+
return "", nil
253+
}
254+
255+
var out bytes.Buffer
256+
if err := template.RenderStepTemplate("step-container", container, &out, stepContext); err != nil {
257+
return "", err
258+
}
259+
260+
resolved := out.String()
261+
if strings.TrimSpace(resolved) == "" {
262+
return "", errors.New("empty image")
263+
}
264+
if strings.Contains(resolved, "${{") {
265+
return "", errors.Errorf("unresolved template in image %q", resolved)
266+
}
267+
268+
return resolved, nil
269+
}
270+
244271
const workDir = "/work"
245272

246273
func executeSingleStep(
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package executor
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
8+
"github.com/sourcegraph/sourcegraph/lib/batches/template"
9+
)
10+
11+
func TestRenderStepContainer(t *testing.T) {
12+
t.Run("static image", func(t *testing.T) {
13+
got, err := renderStepContainer("alpine:3", &template.StepContext{})
14+
require.NoError(t, err)
15+
require.Equal(t, "alpine:3", got)
16+
})
17+
18+
t.Run("output image", func(t *testing.T) {
19+
got, err := renderStepContainer("${{ outputs.imageName }}", &template.StepContext{
20+
Outputs: map[string]any{"imageName": "alpine:3"},
21+
})
22+
require.NoError(t, err)
23+
require.Equal(t, "alpine:3", got)
24+
})
25+
26+
t.Run("missing output", func(t *testing.T) {
27+
_, err := renderStepContainer("${{ outputs.imageName }}", &template.StepContext{})
28+
require.Error(t, err)
29+
})
30+
}

internal/batches/service/service.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,10 +295,19 @@ func (svc *Service) EnsureDockerImages(
295295
parallelism int,
296296
progress func(done, total int),
297297
) (map[string]docker.Image, error) {
298-
// Figure out the image names used in the batch spec.
298+
// Figure out the concrete image names used in the batch spec. Images that
299+
// still depend on runtime values, such as outputs from earlier steps, are
300+
// resolved and pulled just-in-time by the executor.
299301
names := map[string]struct{}{}
300302
for i := range steps {
301-
names[steps[i].Container] = struct{}{}
303+
isStatic, name, err := templatelib.IsStaticString(steps[i].Container, &templatelib.StepContext{})
304+
if err != nil {
305+
return nil, err
306+
}
307+
if !isStatic {
308+
continue
309+
}
310+
names[name] = struct{}{}
302311
}
303312

304313
total := len(names)

internal/batches/service/service_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,9 @@ func TestEnsureDockerImages(t *testing.T) {
113113
}
114114

115115
for name, steps := range map[string][]batcheslib.Step{
116-
"single step": {{Container: "image"}},
117-
"multiple steps": {{Container: "image"}, {Container: "image"}},
116+
"single step": {{Container: "image"}},
117+
"multiple steps": {{Container: "image"}, {Container: "image"}},
118+
"dynamic deferred": {{Container: "${{ outputs.imageName }}"}, {Container: "image"}},
118119
} {
119120
t.Run(name, func(t *testing.T) {
120121
for _, parallelism := range parallelCases {

lib/batches/template/partial_eval.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,26 @@ func IsStaticBool(input string, ctx *StepContext) (isStatic bool, boolVal bool,
4242
return true, isTrueOutput(t.Tree.Root), nil
4343
}
4444

45+
// IsStaticString parses the input as a text/template and attempts to evaluate it
46+
// with only the information currently available in StepContext. If any template
47+
// actions remain after partial evaluation, the first return value is false.
48+
func IsStaticString(input string, ctx *StepContext) (isStatic bool, value string, err error) {
49+
t, err := parseAndPartialEval(input, ctx)
50+
if err != nil {
51+
return false, "", err
52+
}
53+
54+
var out bytes.Buffer
55+
for _, n := range t.Tree.Root.Nodes {
56+
if n.Type() != parse.NodeText {
57+
return false, "", nil
58+
}
59+
out.WriteString(n.String())
60+
}
61+
62+
return true, out.String(), nil
63+
}
64+
4565
// parseAndPartialEval parses input as a text/template and then attempts to
4666
// partially evaluate the parts of the template it can evaluate ahead of time
4767
// (meaning: before we've executed any batch spec steps and have a full

0 commit comments

Comments
 (0)