Skip to content

Commit ad446e1

Browse files
authored
fix(internal/librarian): make response files optional for generate/build (#1788)
readLibraryState() now returns a nil pointer (but no error) if the container response file doesn't exist. When the response is required (e.g. for configure) this should be checked explicitly. Fixes #1741
1 parent bded5dc commit ad446e1

File tree

5 files changed

+59
-11
lines changed

5 files changed

+59
-11
lines changed

internal/librarian/generate.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,9 @@ func (r *generateRunner) runConfigureCommand(ctx context.Context) (string, error
506506
if err != nil {
507507
return "", err
508508
}
509+
if libraryState == nil {
510+
return "", errors.New("no response file for configure container command")
511+
}
509512

510513
if libraryState.Version == "" {
511514
slog.Info("library doesn't receive a version, apply the default version", "id", r.cfg.Library)

internal/librarian/generate_test.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,25 @@ func TestRunGenerateCommand(t *testing.T) {
5959
wantLibraryID: "some-library",
6060
wantGenerateCalls: 1,
6161
},
62+
{
63+
name: "works with no response",
64+
api: "some/api",
65+
repo: newTestGitRepo(t),
66+
ghClient: &mockGitHubClient{},
67+
state: &config.LibrarianState{
68+
Libraries: []*config.LibraryState{
69+
{
70+
ID: "some-library",
71+
APIs: []*config.API{{Path: "some/api"}},
72+
},
73+
},
74+
},
75+
container: &mockContainerClient{
76+
noGenerateResponse: true,
77+
},
78+
wantLibraryID: "some-library",
79+
wantGenerateCalls: 1,
80+
},
6281
} {
6382
t.Run(test.name, func(t *testing.T) {
6483
t.Parallel()
@@ -143,7 +162,7 @@ func TestRunBuildCommand(t *testing.T) {
143162
container: &mockContainerClient{
144163
noBuildResponse: true,
145164
},
146-
wantErr: true,
165+
wantBuildCalls: 1,
147166
},
148167
{
149168
name: "build with error response in response",
@@ -271,7 +290,7 @@ func TestRunConfigureCommand(t *testing.T) {
271290
},
272291
wantConfigureCalls: 1,
273292
wantErr: true,
274-
wantErrMsg: "failed to read response file",
293+
wantErrMsg: "no response file for configure container command",
275294
},
276295
{
277296
name: "configures library without initial version",

internal/librarian/mocks_test.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ type mockContainerClient struct {
103103
requestLibraryID string
104104
noBuildResponse bool
105105
noConfigureResponse bool
106+
noGenerateResponse bool
106107
noInitVersion bool
107108
wantErrorMsg bool
108109
}
@@ -112,8 +113,7 @@ func (m *mockContainerClient) Build(ctx context.Context, request *docker.BuildRe
112113
if m.noBuildResponse {
113114
return m.buildErr
114115
}
115-
// Write a build-response.json because it is required by generate
116-
// command.
116+
// Write a build-response.json unless we're configured not to.
117117
if err := os.MkdirAll(filepath.Join(request.RepoDir, ".librarian"), 0755); err != nil {
118118
return err
119119
}
@@ -135,8 +135,7 @@ func (m *mockContainerClient) Configure(ctx context.Context, request *docker.Con
135135
return "", m.configureErr
136136
}
137137

138-
// Write a configure-response.json because it is required by configure
139-
// command.
138+
// Write a configure-response.json unless we're configured not to.
140139
if err := os.MkdirAll(filepath.Join(request.RepoDir, config.LibrarianDir), 0755); err != nil {
141140
return "", err
142141
}
@@ -158,8 +157,12 @@ func (m *mockContainerClient) Configure(ctx context.Context, request *docker.Con
158157

159158
func (m *mockContainerClient) Generate(ctx context.Context, request *docker.GenerateRequest) error {
160159
m.generateCalls++
161-
// Write a generate-response.json because it is required by generate
162-
// command.
160+
161+
if m.noGenerateResponse {
162+
return m.generateErr
163+
}
164+
165+
// // Write a generate-response.json unless we're configured not to.
163166
if err := os.MkdirAll(filepath.Join(request.RepoDir, config.LibrarianDir), 0755); err != nil {
164167
return err
165168
}

internal/librarian/state.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,17 +158,24 @@ func saveLibrarianState(repoDir string, state *config.LibrarianState) error {
158158
return os.WriteFile(path, bytes, 0644)
159159
}
160160

161-
// readLibraryState reads the library state from a container response.
161+
// readLibraryState reads the library state from a container response, if it exists.
162+
// If the response file does not exist, readLibraryState succeeds but returns a nil pointer.
162163
//
163164
// The response file is removed afterwards.
164165
func readLibraryState(jsonFilePath string) (*config.LibraryState, error) {
165166
data, err := os.ReadFile(jsonFilePath)
166167
defer func() {
167-
if err := os.Remove(jsonFilePath); err != nil {
168+
if err := os.Remove(jsonFilePath); err != nil && !errors.Is(err, os.ErrNotExist) {
168169
slog.Warn("fail to remove file", slog.String("name", jsonFilePath), slog.Any("err", err))
169170
}
170171
}()
171172
if err != nil {
173+
// If we only failed to read the file because it didn't exist, just succeed
174+
// with a nil pointer.
175+
176+
if errors.Is(err, os.ErrNotExist) {
177+
return nil, nil
178+
}
172179
return nil, fmt.Errorf("failed to read response file, path: %s, error: %w", jsonFilePath, err)
173180
}
174181

internal/librarian/state_test.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ func TestSaveLibrarianState(t *testing.T) {
346346
}
347347
}
348348

349-
func TestReadConfigureResponseJSON(t *testing.T) {
349+
func TestReadLibraryState(t *testing.T) {
350350
t.Parallel()
351351
for _, test := range []struct {
352352
name string
@@ -385,6 +385,10 @@ func TestReadConfigureResponseJSON(t *testing.T) {
385385
name: "invalid file name",
386386
wantState: nil,
387387
},
388+
{
389+
name: "missing file",
390+
wantState: nil,
391+
},
388392
} {
389393
t.Run(test.name, func(t *testing.T) {
390394
tempDir := t.TempDir()
@@ -402,6 +406,18 @@ func TestReadConfigureResponseJSON(t *testing.T) {
402406
return
403407
}
404408

409+
if test.name == "missing file" {
410+
filePath := filepath.Join(tempDir, "missing.json")
411+
gotState, err := readLibraryState(filePath)
412+
if err != nil {
413+
t.Fatalf("readLibraryState() unexpected error: %v", err)
414+
}
415+
if diff := cmp.Diff(test.wantState, gotState); diff != "" {
416+
t.Errorf("Response library state mismatch (-want +got):\n%s", diff)
417+
}
418+
return
419+
}
420+
405421
if test.name == "invalid content loader" {
406422
dst := fmt.Sprintf("%s/copy.json", os.TempDir())
407423
if err := copyFile(dst, test.jsonFilePath); err != nil {

0 commit comments

Comments
 (0)