Skip to content

Commit 86979d8

Browse files
authored
chore(internal/librarian): check error field in docker response (#1565)
Fixes #1518
1 parent 91bb107 commit 86979d8

14 files changed

Lines changed: 279 additions & 91 deletions

File tree

generate_e2e_test.go

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
package librarian
1919

2020
import (
21-
"encoding/json"
2221
"fmt"
2322
"math/rand"
2423
"os"
@@ -51,8 +50,8 @@ func TestRunGenerate(t *testing.T) {
5150
api: "google/cloud/pubsub/v1",
5251
},
5352
{
54-
name: "non existent in api source",
55-
api: "google/non-existent/path",
53+
name: "failed due to simulated error in generate command",
54+
api: "google/cloud/future/v2",
5655
wantErr: true,
5756
},
5857
} {
@@ -84,30 +83,19 @@ func TestRunGenerate(t *testing.T) {
8483
if err == nil {
8584
t.Fatalf("%s should fail", test.name)
8685
}
86+
87+
// the exact message is not populated here, but we can check it's
88+
// indeed an error returned from docker container.
89+
if g, w := err.Error(), "exit status 1"; !strings.Contains(g, w) {
90+
t.Fatalf("got %q, wanted it to contain %q", g, w)
91+
}
92+
8793
return
8894
}
95+
8996
if err != nil {
9097
t.Fatalf("librarian generate command error = %v", err)
9198
}
92-
93-
responseFile := filepath.Join(workRoot, "output", "generate-response.json")
94-
if _, err := os.Stat(responseFile); err != nil {
95-
t.Fatalf("can not find generate response, error = %v", err)
96-
}
97-
98-
if test.wantErr {
99-
data, err := os.ReadFile(responseFile)
100-
if err != nil {
101-
t.Fatalf("ReadFile() error = %v", err)
102-
}
103-
content := &genResponse{}
104-
if err := json.Unmarshal(data, content); err != nil {
105-
t.Fatalf("Unmarshal() error = %v", err)
106-
}
107-
if content.ErrorMessage == "" {
108-
t.Fatalf("can not find error message in generate response")
109-
}
110-
}
11199
})
112100
}
113101
}
@@ -137,7 +125,7 @@ func TestRunConfigure(t *testing.T) {
137125
{
138126
name: "failed due to simulated error in configure command",
139127
api: "google/cloud/another-library/v3",
140-
library: "simulate-configure-error-id",
128+
library: "simulate-command-error-id",
141129
apiSource: "testdata/e2e/configure/api_root",
142130
updatedState: "testdata/e2e/configure/updated-state.yaml",
143131
wantErr: true,

internal/config/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ import (
2626
const (
2727
// BuildRequest is a JSON file that describes which library to build/test.
2828
BuildRequest = "build-request.json"
29+
// BuildResponse is a JSON file that describes which library to change after
30+
// built/test.
31+
BuildResponse = "build-response.json"
2932
// ConfigureRequest is a JSON file that describes which library to configure.
3033
ConfigureRequest = "configure-request.json"
3134
// ConfigureResponse is a JSON file that describes which library to change
@@ -36,6 +39,9 @@ const (
3639
GeneratorInputDir = ".librarian/generator-input"
3740
// GenerateRequest is a JSON file that describes which library to generate.
3841
GenerateRequest = "generate-request.json"
42+
// GenerateResponse is a JSON file that describes which library to change
43+
// after re-generation.
44+
GenerateResponse = "generate-response.json"
3945
// LibrarianDir is the default directory to store librarian state/config files,
4046
// along with any additional configuration.
4147
LibrarianDir = ".librarian"

internal/config/state.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ type LibraryState struct {
9999
// If not set, this defaults to the `source_roots`.
100100
// A more specific `preserve_regex` takes precedence.
101101
RemoveRegex []string `yaml:"remove_regex" json:"remove_regex"`
102+
// An error message from the docker response.
103+
// This field is ignored when writing to state.yaml.
104+
ErrorMessage string `yaml:"-" json:"error,omitempty"`
102105
}
103106

104107
var (

internal/docker/docker.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func (c *Docker) Generate(ctx context.Context, request *GenerateRequest) error {
152152
generatorInput := filepath.Join(request.RepoDir, config.GeneratorInputDir)
153153
librarianDir := filepath.Join(request.RepoDir, config.LibrarianDir)
154154
mounts := []string{
155-
fmt.Sprintf("%s:/librarian:ro", librarianDir), // readonly volume
155+
fmt.Sprintf("%s:/librarian", librarianDir),
156156
fmt.Sprintf("%s:/input", generatorInput),
157157
fmt.Sprintf("%s:/output", request.Output),
158158
fmt.Sprintf("%s:/source:ro", request.ApiRoot), // readonly volume

internal/docker/docker_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func TestDockerRun(t *testing.T) {
9494
},
9595
want: []string{
9696
"run", "--rm",
97-
"-v", fmt.Sprintf("%s/.librarian:/librarian:ro", repoDir),
97+
"-v", fmt.Sprintf("%s/.librarian:/librarian", repoDir),
9898
"-v", fmt.Sprintf("%s/.librarian/generator-input:/input", repoDir),
9999
"-v", fmt.Sprintf("%s:/output", testOutput),
100100
"-v", fmt.Sprintf("%s:/source:ro", testAPIRoot),
@@ -165,7 +165,7 @@ func TestDockerRun(t *testing.T) {
165165
},
166166
want: []string{
167167
"run", "--rm",
168-
"-v", fmt.Sprintf("%s/.librarian:/librarian:ro", repoDir),
168+
"-v", fmt.Sprintf("%s/.librarian:/librarian", repoDir),
169169
"-v", fmt.Sprintf("%s/.librarian/generator-input:/input", repoDir),
170170
"-v", "localDir:/output",
171171
"-v", fmt.Sprintf("%s:/source:ro", testAPIRoot),

internal/librarian/generate.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,15 @@ func (r *generateRunner) runGenerateCommand(ctx context.Context, libraryID, outp
273273
return "", err
274274
}
275275

276+
// Read the library state from the response.
277+
if _, err := readLibraryState(
278+
func(data []byte, libraryState *config.LibraryState) error {
279+
return json.Unmarshal(data, libraryState)
280+
},
281+
filepath.Join(generateRequest.RepoDir, config.LibrarianDir, config.GenerateResponse)); err != nil {
282+
return "", err
283+
}
284+
276285
if err := r.cleanAndCopyLibrary(libraryID, outputDir); err != nil {
277286
return "", err
278287
}
@@ -320,7 +329,18 @@ func (r *generateRunner) runBuildCommand(ctx context.Context, libraryID string)
320329
RepoDir: r.repo.GetDir(),
321330
}
322331
slog.Info("Build requested for library", "id", libraryID)
323-
return r.containerClient.Build(ctx, buildRequest)
332+
if err := r.containerClient.Build(ctx, buildRequest); err != nil {
333+
return err
334+
}
335+
336+
// Read the library state from the response.
337+
_, err := readLibraryState(
338+
func(data []byte, libraryState *config.LibraryState) error {
339+
return json.Unmarshal(data, libraryState)
340+
},
341+
filepath.Join(buildRequest.RepoDir, config.LibrarianDir, config.BuildResponse))
342+
343+
return err
324344
}
325345

326346
// clean removes files and directories from a root directory based on remove and preserve patterns.
@@ -530,7 +550,7 @@ func (r *generateRunner) runConfigureCommand(ctx context.Context) (string, error
530550
}
531551

532552
// Read the new library state from the response.
533-
libraryState, err := readConfigureResponse(
553+
libraryState, err := readLibraryState(
534554
func(data []byte, libraryState *config.LibraryState) error {
535555
return json.Unmarshal(data, libraryState)
536556
},

internal/librarian/generate_test.go

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,40 @@ func TestRunBuildCommand(t *testing.T) {
130130
build: true,
131131
container: &mockContainerClient{},
132132
},
133+
{
134+
name: "build with no response",
135+
build: true,
136+
libraryID: "some-library",
137+
repo: newTestGitRepo(t),
138+
state: &config.LibrarianState{
139+
Libraries: []*config.LibraryState{
140+
{
141+
ID: "some-library",
142+
},
143+
},
144+
},
145+
container: &mockContainerClient{
146+
noBuildResponse: true,
147+
},
148+
wantErr: true,
149+
},
150+
{
151+
name: "build with error response in response",
152+
build: true,
153+
libraryID: "some-library",
154+
repo: newTestGitRepo(t),
155+
state: &config.LibrarianState{
156+
Libraries: []*config.LibraryState{
157+
{
158+
ID: "some-library",
159+
},
160+
},
161+
},
162+
container: &mockContainerClient{
163+
wantErrorMsg: true,
164+
},
165+
wantErr: true,
166+
},
133167
} {
134168
t.Run(test.name, func(t *testing.T) {
135169
t.Parallel()
@@ -141,6 +175,7 @@ func TestRunBuildCommand(t *testing.T) {
141175
state: test.state,
142176
containerClient: test.container,
143177
}
178+
144179
err := r.runBuildCommand(context.Background(), test.libraryID)
145180
if test.wantErr {
146181
if err == nil {
@@ -170,7 +205,7 @@ func TestRunConfigureCommand(t *testing.T) {
170205
wantErr bool
171206
}{
172207
{
173-
name: "configures library",
208+
name: "configures library successfully",
174209
api: "some/api",
175210
repo: newTestGitRepo(t),
176211
state: &config.LibrarianState{
@@ -200,6 +235,24 @@ func TestRunConfigureCommand(t *testing.T) {
200235
wantConfigureCalls: 1,
201236
wantErr: true,
202237
},
238+
{
239+
name: "configures library with error message in response",
240+
api: "some/api",
241+
repo: newTestGitRepo(t),
242+
state: &config.LibrarianState{
243+
Libraries: []*config.LibraryState{
244+
{
245+
ID: "some-library",
246+
APIs: []*config.API{{Path: "some/api"}},
247+
},
248+
},
249+
},
250+
container: &mockContainerClient{
251+
wantErrorMsg: true,
252+
},
253+
wantConfigureCalls: 1,
254+
wantErr: true,
255+
},
203256
{
204257
name: "configures library with no response",
205258
api: "some/api",
@@ -212,7 +265,9 @@ func TestRunConfigureCommand(t *testing.T) {
212265
},
213266
},
214267
},
215-
container: &mockContainerClient{},
268+
container: &mockContainerClient{
269+
noConfigureResponse: true,
270+
},
216271
wantConfigureCalls: 1,
217272
wantErr: true,
218273
},
@@ -249,7 +304,8 @@ func TestRunConfigureCommand(t *testing.T) {
249304
containerClient: test.container,
250305
}
251306

252-
if test.name == "configures library" {
307+
if test.name == "configures library successfully" ||
308+
test.name == "configures library with error message in response" {
253309
if err := os.MkdirAll(filepath.Join(cfg.APISource, test.api), 0755); err != nil {
254310
t.Fatal(err)
255311
}
@@ -258,19 +314,6 @@ func TestRunConfigureCommand(t *testing.T) {
258314
if err := os.WriteFile(filepath.Join(cfg.APISource, test.api, "example_service_v2.yaml"), data, 0755); err != nil {
259315
t.Fatal(err)
260316
}
261-
262-
// Write a configure-response.json because it is required by configure
263-
// command.
264-
if err := os.MkdirAll(filepath.Join(r.repo.GetDir(), config.LibrarianDir), 0755); err != nil {
265-
t.Fatal(err)
266-
}
267-
268-
libraryStr := fmt.Sprintf(`{
269-
"ID": "%s"
270-
}`, test.state.Libraries[0].ID)
271-
if err := os.WriteFile(filepath.Join(r.repo.GetDir(), config.LibrarianDir, config.ConfigureResponse), []byte(libraryStr), 0755); err != nil {
272-
t.Fatal(err)
273-
}
274317
}
275318

276319
if test.name == "configures library with no response" ||
@@ -819,6 +862,28 @@ func TestGenerateScenarios(t *testing.T) {
819862
build: true,
820863
wantErr: true,
821864
},
865+
{
866+
name: "generate single existing library with error message in response",
867+
api: "some/api",
868+
library: "some-library",
869+
repo: newTestGitRepo(t),
870+
state: &config.LibrarianState{
871+
Image: "gcr.io/test/image:v1.2.3",
872+
Libraries: []*config.LibraryState{
873+
{
874+
ID: "some-library",
875+
APIs: []*config.API{{Path: "some/api"}},
876+
},
877+
},
878+
},
879+
container: &mockContainerClient{
880+
wantErrorMsg: true,
881+
},
882+
ghClient: &mockGitHubClient{},
883+
wantGenerateCalls: 1,
884+
wantConfigureCalls: 0,
885+
wantErr: true,
886+
},
822887
{
823888
name: "generate all libraries configured in state",
824889
repo: newTestGitRepo(t),

0 commit comments

Comments
 (0)