Skip to content

Commit 1ff85fe

Browse files
authored
fix(librarian): update-image uses the newly detected image (#2830)
Fixes #2822 We update the docker request structs to contain an optional `Image` field which will override the docker image used to run the command. When running the generate command, we use this field to ensure we are using the correct language container.
1 parent 2070213 commit 1ff85fe

File tree

5 files changed

+180
-6
lines changed

5 files changed

+180
-6
lines changed

internal/docker/docker.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ type BuildRequest struct {
7979
// State is a pointer to the [config.LibrarianState] struct, representing
8080
// the overall state of the generation and release pipeline.
8181
State *config.LibrarianState
82+
83+
// Image is the name of the docker image to use when running. If not
84+
// specified, uses the default image configured for the client.
85+
Image string
8286
}
8387

8488
// ConfigureRequest contains all the information required for a language
@@ -106,6 +110,10 @@ type ConfigureRequest struct {
106110
// State is a pointer to the [config.LibrarianState] struct, representing
107111
// the overall state of the generation and release pipeline.
108112
State *config.LibrarianState
113+
114+
// Image is the name of the docker image to use when running. If not
115+
// specified, uses the default image configured for the client.
116+
Image string
109117
}
110118

111119
// GenerateRequest contains all the information required for a language
@@ -127,6 +135,10 @@ type GenerateRequest struct {
127135
// State is a pointer to the [config.LibrarianState] struct, representing
128136
// the overall state of the generation and release pipeline.
129137
State *config.LibrarianState
138+
139+
// Image is the name of the docker image to use when running. If not
140+
// specified, uses the default image configured for the client.
141+
Image string
130142
}
131143

132144
// ReleaseStageRequest contains all the information required for a language
@@ -164,6 +176,10 @@ type ReleaseStageRequest struct {
164176
// State is a pointer to the [config.LibrarianState] struct, representing
165177
// the overall state of the generation and release pipeline.
166178
State *config.LibrarianState
179+
180+
// Image is the name of the docker image to use when running. If not
181+
// specified, uses the default image configured for the client.
182+
Image string
167183
}
168184

169185
// DockerOptions contains optional configuration parameters for invoking
@@ -232,7 +248,9 @@ func (c *Docker) Generate(ctx context.Context, request *GenerateRequest) error {
232248
fmt.Sprintf("%s:/output", request.Output),
233249
fmt.Sprintf("%s:/source:ro", request.ApiRoot), // readonly volume
234250
}
235-
return c.runDocker(ctx, CommandGenerate, mounts, commandArgs)
251+
252+
image := c.resolveImage(request.Image)
253+
return c.runDocker(ctx, image, CommandGenerate, mounts, commandArgs)
236254
}
237255

238256
// Build builds the library with an ID of libraryID, as configured in
@@ -262,7 +280,8 @@ func (c *Docker) Build(ctx context.Context, request *BuildRequest) error {
262280
"--repo=/repo",
263281
}
264282

265-
return c.runDocker(ctx, CommandBuild, mounts, commandArgs)
283+
image := c.resolveImage(request.Image)
284+
return c.runDocker(ctx, image, CommandBuild, mounts, commandArgs)
266285
}
267286

268287
// Configure configures an API within a repository, either adding it to an
@@ -307,7 +326,8 @@ func (c *Docker) Configure(ctx context.Context, request *ConfigureRequest) (stri
307326
mounts = append(mounts, fmt.Sprintf("%s/%s:/repo/%s:ro", request.RepoDir, globalFile, globalFile))
308327
}
309328

310-
if err := c.runDocker(ctx, CommandConfigure, mounts, commandArgs); err != nil {
329+
image := c.resolveImage(request.Image)
330+
if err := c.runDocker(ctx, image, CommandConfigure, mounts, commandArgs); err != nil {
311331
return "", err
312332
}
313333

@@ -342,14 +362,15 @@ func (c *Docker) ReleaseStage(ctx context.Context, request *ReleaseStageRequest)
342362
fmt.Sprintf("%s:/output", request.Output),
343363
}
344364

345-
if err := c.runDocker(ctx, CommandReleaseStage, mounts, commandArgs); err != nil {
365+
image := c.resolveImage(request.Image)
366+
if err := c.runDocker(ctx, image, CommandReleaseStage, mounts, commandArgs); err != nil {
346367
return err
347368
}
348369

349370
return nil
350371
}
351372

352-
func (c *Docker) runDocker(_ context.Context, command Command, mounts []string, commandArgs []string) (err error) {
373+
func (c *Docker) runDocker(_ context.Context, image string, command Command, mounts []string, commandArgs []string) (err error) {
353374
mounts = maybeRelocateMounts(c.HostMount, mounts)
354375
args := []string{
355376
"run",
@@ -365,7 +386,7 @@ func (c *Docker) runDocker(_ context.Context, command Command, mounts []string,
365386
args = append(args, "--user", fmt.Sprintf("%s:%s", c.uid, c.gid))
366387
}
367388

368-
args = append(args, c.Image)
389+
args = append(args, image)
369390
args = append(args, string(command))
370391
args = append(args, commandArgs...)
371392
return c.run(args...)
@@ -389,6 +410,13 @@ func maybeRelocateMounts(hostMount string, mounts []string) []string {
389410
return relocatedMounts
390411
}
391412

413+
func (c *Docker) resolveImage(requestedImage string) string {
414+
if requestedImage != "" {
415+
return requestedImage
416+
}
417+
return c.Image
418+
}
419+
392420
func (c *Docker) runCommand(cmdName string, args ...string) error {
393421
cmd := exec.Command(cmdName, args...)
394422
cmd.Stderr = os.Stderr

internal/docker/docker_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,38 @@ func TestDockerRun(t *testing.T) {
144144
wantErr: true,
145145
wantErrMsg: simulateDockerErrMsg,
146146
},
147+
{
148+
name: "Generate runs in docker with image override",
149+
docker: &Docker{
150+
Image: testImage,
151+
HostMount: "hostDir:localDir",
152+
},
153+
runCommand: func(ctx context.Context, d *Docker) error {
154+
generateRequest := &GenerateRequest{
155+
State: state,
156+
RepoDir: repoDir,
157+
ApiRoot: testAPIRoot,
158+
Output: "hostDir",
159+
LibraryID: testLibraryID,
160+
Image: "custom-image:abcd123",
161+
}
162+
163+
return d.Generate(ctx, generateRequest)
164+
},
165+
want: []string{
166+
"run", "--rm",
167+
"-v", fmt.Sprintf("%s/.librarian:/librarian", repoDir),
168+
"-v", fmt.Sprintf("%s/.librarian/generator-input:/input", repoDir),
169+
"-v", "localDir:/output",
170+
"-v", fmt.Sprintf("%s:/source:ro", testAPIRoot),
171+
"custom-image:abcd123",
172+
string(CommandGenerate),
173+
"--librarian=/librarian",
174+
"--input=/input",
175+
"--output=/output",
176+
"--source=/source",
177+
},
178+
},
147179
{
148180
name: "Generate runs in docker",
149181
docker: &Docker{
@@ -199,6 +231,32 @@ func TestDockerRun(t *testing.T) {
199231
"--repo=/repo",
200232
},
201233
},
234+
{
235+
name: "Build runs in docker with image override",
236+
docker: &Docker{
237+
Image: testImage,
238+
HostMount: "hostDir:localDir",
239+
},
240+
runCommand: func(ctx context.Context, d *Docker) error {
241+
buildRequest := &BuildRequest{
242+
State: state,
243+
LibraryID: testLibraryID,
244+
RepoDir: repoDir,
245+
Image: "custom-image:abcd123",
246+
}
247+
248+
return d.Build(ctx, buildRequest)
249+
},
250+
want: []string{
251+
"run", "--rm",
252+
"-v", fmt.Sprintf("%s/.librarian:/librarian", repoDir),
253+
"-v", fmt.Sprintf("%s:/repo", repoDir),
254+
"custom-image:abcd123",
255+
string(CommandBuild),
256+
"--librarian=/librarian",
257+
"--repo=/repo",
258+
},
259+
},
202260
{
203261
name: "Build with invalid repo dir",
204262
docker: &Docker{
@@ -273,6 +331,46 @@ func TestDockerRun(t *testing.T) {
273331
"--source=/source",
274332
},
275333
},
334+
{
335+
name: "Configure runs in docker with image override",
336+
docker: &Docker{
337+
Image: testImage,
338+
},
339+
runCommand: func(ctx context.Context, d *Docker) error {
340+
configureRequest := &ConfigureRequest{
341+
State: state,
342+
LibraryID: testLibraryID,
343+
RepoDir: repoDir,
344+
ApiRoot: testAPIRoot,
345+
Output: testOutput,
346+
GlobalFiles: []string{
347+
"a/b/go.mod",
348+
"go.mod",
349+
},
350+
Image: "custom-image:abcd123",
351+
}
352+
353+
_, err := d.Configure(ctx, configureRequest)
354+
355+
return err
356+
},
357+
want: []string{
358+
"run", "--rm",
359+
"-v", fmt.Sprintf("%s/.librarian:/librarian", repoDir),
360+
"-v", fmt.Sprintf("%s/.librarian/generator-input:/input", repoDir),
361+
"-v", fmt.Sprintf("%s:/output", testOutput),
362+
"-v", fmt.Sprintf("%s:/source:ro", testAPIRoot),
363+
"-v", fmt.Sprintf("%s/a/b/go.mod:/repo/a/b/go.mod:ro", repoDir),
364+
"-v", fmt.Sprintf("%s/go.mod:/repo/go.mod:ro", repoDir),
365+
"custom-image:abcd123",
366+
string(CommandConfigure),
367+
"--librarian=/librarian",
368+
"--input=/input",
369+
"--output=/output",
370+
"--repo=/repo",
371+
"--source=/source",
372+
},
373+
},
276374
{
277375
name: "configure_with_nil_global_files",
278376
docker: &Docker{
@@ -521,6 +619,41 @@ func TestDockerRun(t *testing.T) {
521619
"--output=/output",
522620
},
523621
},
622+
{
623+
name: "Release stage runs in docker with image override",
624+
docker: &Docker{
625+
Image: testImage,
626+
},
627+
runCommand: func(ctx context.Context, d *Docker) error {
628+
partialRepoDir := filepath.Join(repoDir, "release-stage-all-libraries")
629+
if err := os.MkdirAll(filepath.Join(repoDir, config.LibrarianDir), 0755); err != nil {
630+
t.Fatal(err)
631+
}
632+
633+
releaseInitRequest := &ReleaseStageRequest{
634+
State: state,
635+
Output: testOutput,
636+
LibrarianConfig: &config.LibrarianConfig{},
637+
RepoDir: partialRepoDir,
638+
Image: "custom-image:abcd123",
639+
}
640+
641+
defer os.RemoveAll(partialRepoDir)
642+
643+
return d.ReleaseStage(ctx, releaseInitRequest)
644+
},
645+
want: []string{
646+
"run", "--rm",
647+
"-v", fmt.Sprintf("%s/.librarian:/librarian", filepath.Join(repoDir, "release-stage-all-libraries")),
648+
"-v", fmt.Sprintf("%s:/repo:ro", filepath.Join(repoDir, "release-stage-all-libraries")),
649+
"-v", fmt.Sprintf("%s:/output", testOutput),
650+
"custom-image:abcd123",
651+
string(CommandReleaseStage),
652+
"--librarian=/librarian",
653+
"--repo=/repo",
654+
"--output=/output",
655+
},
656+
},
524657
{
525658
name: "Release stage returns error",
526659
docker: &Docker{

internal/librarian/generate.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func generateSingleLibrary(ctx context.Context, containerClient ContainerClient,
4848
Output: libraryOutputDir,
4949
RepoDir: repo.GetDir(),
5050
State: state,
51+
Image: state.Image,
5152
}
5253
slog.Info("performing generation for library", "id", libraryState.ID, "outputDir", libraryOutputDir)
5354
if err := containerClient.Generate(ctx, generateRequest); err != nil {

internal/librarian/mocks_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ type mockContainerClient struct {
157157
// Set this value if you want the configure-response
158158
// has library source roots and remove regex.
159159
configureLibraryPaths []string
160+
// The last generation request
161+
generateRequest *docker.GenerateRequest
160162
}
161163

162164
func (m *mockContainerClient) Build(ctx context.Context, request *docker.BuildRequest) error {
@@ -241,6 +243,7 @@ func (m *mockContainerClient) Configure(ctx context.Context, request *docker.Con
241243

242244
func (m *mockContainerClient) Generate(ctx context.Context, request *docker.GenerateRequest) error {
243245
m.generateCalls++
246+
m.generateRequest = request
244247

245248
if m.noGenerateResponse {
246249
return m.generateErr

internal/librarian/update_image_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ func TestUpdateImageRunnerRun(t *testing.T) {
210210
wantCreateIssueCalls int
211211
wantCommitMsg string
212212
checkoutError error
213+
wantImage string
213214
}{
214215
{
215216
name: "specific image",
@@ -234,6 +235,7 @@ func TestUpdateImageRunnerRun(t *testing.T) {
234235
wantGenerateCalls: 1,
235236
wantBuildCalls: 0, // no -build flag
236237
wantCheckoutCalls: 2,
238+
wantImage: "some-image",
237239
},
238240
{
239241
name: "no change image",
@@ -258,6 +260,7 @@ func TestUpdateImageRunnerRun(t *testing.T) {
258260
wantGenerateCalls: 1,
259261
wantBuildCalls: 0,
260262
wantCheckoutCalls: 2,
263+
wantImage: "gcr.io/test/image:v1.2.3",
261264
},
262265
{
263266
name: "finds latest image",
@@ -283,6 +286,7 @@ func TestUpdateImageRunnerRun(t *testing.T) {
283286
wantGenerateCalls: 1,
284287
wantBuildCalls: 0, // no -build flag
285288
wantCheckoutCalls: 2,
289+
wantImage: "gcr.io/test/image@sha256:abc123",
286290
},
287291
{
288292
name: "finds image error",
@@ -761,6 +765,11 @@ func TestUpdateImageRunnerRun(t *testing.T) {
761765
if diff := cmp.Diff(test.wantGenerateCalls, test.containerClient.generateCalls); diff != "" {
762766
t.Errorf("%s: run() generateCalls mismatch (-want +got):%s", test.name, diff)
763767
}
768+
if test.wantImage != "" {
769+
if diff := cmp.Diff(test.wantImage, test.containerClient.generateRequest.Image); diff != "" {
770+
t.Errorf("%s: run() image mismatch (-want +got):%s", test.name, diff)
771+
}
772+
}
764773
if diff := cmp.Diff(test.wantBuildCalls, test.containerClient.buildCalls); diff != "" {
765774
t.Errorf("%s: run() buildCalls mismatch (-want +got):%s", test.name, diff)
766775
}

0 commit comments

Comments
 (0)