Skip to content

Commit 946e550

Browse files
authored
feat(librarian/docker): partially copy a repo (#1686)
Fixes #1685
1 parent 004401f commit 946e550

File tree

3 files changed

+586
-40
lines changed

3 files changed

+586
-40
lines changed

internal/config/global_config.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ import (
1818
"fmt"
1919
)
2020

21+
const (
22+
PermissionReadOnly = "read-only"
23+
PermissionWriteOnly = "write-only"
24+
PermissionReadWrite = "read-write"
25+
)
26+
2127
// GlobalConfig defines the contract for the config.yaml file.
2228
type GlobalConfig struct {
2329
GlobalFilesAllowlist []*GlobalFile `yaml:"global_files_allowlist"`
@@ -30,9 +36,9 @@ type GlobalFile struct {
3036
}
3137

3238
var validPermissions = map[string]bool{
33-
"read-only": true,
34-
"write-only": true,
35-
"read-write": true,
39+
PermissionReadOnly: true,
40+
PermissionWriteOnly: true,
41+
PermissionReadWrite: true,
3642
}
3743

3844
// Validate checks that the GlobalConfig is valid.

internal/docker/docker.go

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"context"
2323
"encoding/json"
2424
"fmt"
25+
"io"
2526
"log/slog"
2627
"os"
2728
"os/exec"
@@ -118,21 +119,26 @@ type GenerateRequest struct {
118119
// ReleaseRequest contains all the information required for a language
119120
// container to run the release command.
120121
type ReleaseRequest struct {
121-
// cfg is a pointer to the [config.Config] struct, holding general configuration
122+
// Cfg is a pointer to the [config.Config] struct, holding general configuration
122123
// values parsed from flags or environment variables.
123124
Cfg *config.Config
124-
// state is a pointer to the [config.LibrarianState] struct, representing
125+
// GlobalConfig is a pointer to the [config.GlobalConfig] struct, holding
126+
// global files configuration in a language repository.
127+
GlobalConfig *config.GlobalConfig
128+
// State is a pointer to the [config.LibrarianState] struct, representing
125129
// the overall state of the generation and release pipeline.
126130
State *config.LibrarianState
127-
// libraryID specifies the ID of the library to release.
131+
// LibraryID specifies the ID of the library to release.
128132
LibraryID string
129-
// libraryID specifies the version of the library to release.
133+
// LibraryVersion specifies the version of the library to release.
130134
LibraryVersion string
131-
// output specifies the empty output directory into which the command should
132-
// generate code
135+
// Output specifies the empty output directory into which the command should
136+
// generate code.
133137
Output string
134-
// RepoDir is the local root directory of the language repository.
135-
RepoDir string
138+
// partialRepoDir is the local root directory of language repository contains
139+
// files that make up libraries and global files.
140+
// This is the directory that container can access.
141+
partialRepoDir string
136142
}
137143

138144
// New constructs a Docker instance which will invoke the specified
@@ -247,7 +253,7 @@ func (c *Docker) Configure(ctx context.Context, request *ConfigureRequest) (stri
247253

248254
// ReleaseInit initiates a release for a given language repository.
249255
func (c *Docker) ReleaseInit(ctx context.Context, request *ReleaseRequest) error {
250-
requestFilePath := filepath.Join(request.RepoDir, config.LibrarianDir, config.ReleaseInitRequest)
256+
requestFilePath := filepath.Join(request.Cfg.Repo, config.LibrarianDir, config.ReleaseInitRequest)
251257
if err := writeLibrarianState(request.State, requestFilePath); err != nil {
252258
return err
253259
}
@@ -269,10 +275,14 @@ func (c *Docker) ReleaseInit(ctx context.Context, request *ReleaseRequest) error
269275
commandArgs = append(commandArgs, fmt.Sprintf("--library-version=%s", request.LibraryVersion))
270276
}
271277

272-
librarianDir := filepath.Join(request.RepoDir, config.LibrarianDir)
278+
if err := setupPartialRepo(request); err != nil {
279+
return err
280+
}
281+
282+
librarianDir := filepath.Join(request.partialRepoDir, config.LibrarianDir)
273283
mounts := []string{
274284
fmt.Sprintf("%s:/librarian", librarianDir),
275-
fmt.Sprintf("%s:/repo:ro", request.RepoDir), // readonly volume
285+
fmt.Sprintf("%s:/repo:ro", request.partialRepoDir), // readonly volume
276286
fmt.Sprintf("%s:/output", request.Output),
277287
}
278288

@@ -337,6 +347,96 @@ func (c *Docker) runCommand(cmdName string, args ...string) error {
337347
return err
338348
}
339349

350+
// setupPartialRepo copies the following files from the [config.Config.Repo] to
351+
// partialRepoDir in the given ReleaseRequest:
352+
//
353+
// 1. all directories that make up all libraries, or one library, if the library
354+
// ID is specified.
355+
//
356+
// 2. the .librarian directory.
357+
//
358+
// 3. global files declared in config.yaml.
359+
func setupPartialRepo(request *ReleaseRequest) error {
360+
if request.partialRepoDir == "" {
361+
request.partialRepoDir = filepath.Join(request.Cfg.WorkRoot, "release-init")
362+
}
363+
dst := request.partialRepoDir
364+
src := request.Cfg.Repo
365+
if err := os.MkdirAll(dst, 0755); err != nil {
366+
return fmt.Errorf("failed to make directory: %w", err)
367+
}
368+
369+
for _, library := range request.State.Libraries {
370+
// Only copy files that make up one library.
371+
if request.LibraryID != "" {
372+
if library.ID == request.LibraryID {
373+
if err := copyOneLibrary(dst, src, library); err != nil {
374+
return err
375+
}
376+
break
377+
}
378+
continue
379+
}
380+
381+
// Copy all files make up all libraries.
382+
if err := copyOneLibrary(dst, src, library); err != nil {
383+
return err
384+
}
385+
}
386+
387+
// Copy the .librarian directory.
388+
if err := os.CopyFS(
389+
filepath.Join(dst, config.LibrarianDir),
390+
os.DirFS(filepath.Join(src, config.LibrarianDir))); err != nil {
391+
return fmt.Errorf("failed to copy librarian dir to %s: %w", dst, err)
392+
}
393+
394+
// Copy global files declared in global config.
395+
for _, globalFile := range request.GlobalConfig.GlobalFilesAllowlist {
396+
dstPath := filepath.Join(dst, globalFile.Path)
397+
srcPath := filepath.Join(src, globalFile.Path)
398+
if err := copyFile(dstPath, srcPath); err != nil {
399+
return err
400+
}
401+
}
402+
403+
return nil
404+
}
405+
406+
func copyOneLibrary(dst, src string, library *config.LibraryState) error {
407+
for _, srcRoot := range library.SourceRoots {
408+
dstPath := filepath.Join(dst, srcRoot)
409+
srcPath := filepath.Join(src, srcRoot)
410+
if err := os.CopyFS(dstPath, os.DirFS(srcPath)); err != nil {
411+
return fmt.Errorf("failed to copy %s to %s: %w", library.ID, dstPath, err)
412+
}
413+
}
414+
415+
return nil
416+
}
417+
418+
func copyFile(dst, src string) (err error) {
419+
sourceFile, err := os.Open(src)
420+
if err != nil {
421+
return fmt.Errorf("failed to open file: %q: %w", src, err)
422+
}
423+
defer sourceFile.Close()
424+
425+
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
426+
return fmt.Errorf("failed to make directory: %s", src)
427+
}
428+
429+
destinationFile, err := os.Create(dst)
430+
if err != nil {
431+
return fmt.Errorf("failed to create file: %s", dst)
432+
}
433+
defer destinationFile.Close()
434+
435+
_, err = io.Copy(destinationFile, sourceFile)
436+
437+
return err
438+
}
439+
340440
func writeLibraryState(state *config.LibrarianState, libraryID, jsonFilePath string) error {
341441
if err := os.MkdirAll(filepath.Dir(jsonFilePath), 0755); err != nil {
342442
return fmt.Errorf("failed to make directory: %w", err)

0 commit comments

Comments
 (0)