Skip to content

Commit 87eb842

Browse files
authored
Refactor shared lib to use fs.FS (#706)
* fs.FS! * convert to fs.FS * fix tests * exclude symlink files * fix windows test * use path instead of filepath * remove unused method
1 parent 60e0b9d commit 87eb842

File tree

10 files changed

+73
-144
lines changed

10 files changed

+73
-144
lines changed

cmd/lk/agent.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {
535535
return err
536536
}
537537

538-
projectType, err := agentfs.DetectProjectType(workingDir)
538+
projectType, err := agentfs.DetectProjectType(os.DirFS(workingDir))
539539
fmt.Printf("Detected project type [%s]\n", util.Accented(string(projectType)))
540540
if err != nil {
541541
return fmt.Errorf("unable to determine project type: %w, please use a supported project type, or create your own Dockerfile in the current directory", err)
@@ -555,7 +555,7 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {
555555

556556
regions := cmd.StringSlice("regions")
557557
excludeFiles := []string{fmt.Sprintf("**/%s", config.LiveKitTOMLFile)}
558-
resp, err := agentsClient.CreateAgent(ctx, workingDir, secrets, regions, excludeFiles)
558+
resp, err := agentsClient.CreateAgent(ctx, os.DirFS(workingDir), secrets, regions, excludeFiles)
559559
if err != nil {
560560
if twerr, ok := err.(twirp.Error); ok {
561561
return fmt.Errorf("unable to create agent: %s", twerr.Msg())
@@ -681,7 +681,7 @@ func deployAgent(ctx context.Context, cmd *cli.Command) error {
681681
req.Secrets = secrets
682682
}
683683

684-
projectType, err := agentfs.DetectProjectType(workingDir)
684+
projectType, err := agentfs.DetectProjectType(os.DirFS(workingDir))
685685
if err != nil {
686686
return fmt.Errorf("unable to determine project type: %w, please use a supported project type, or create your own Dockerfile in the current directory", err)
687687
}
@@ -700,7 +700,7 @@ func deployAgent(ctx context.Context, cmd *cli.Command) error {
700700
}
701701

702702
excludeFiles := []string{fmt.Sprintf("**/%s", config.LiveKitTOMLFile)}
703-
if err := agentsClient.DeployAgent(ctx, agentId, workingDir, secrets, excludeFiles); err != nil {
703+
if err := agentsClient.DeployAgent(ctx, agentId, os.DirFS(workingDir), secrets, excludeFiles); err != nil {
704704
if twerr, ok := err.(twirp.Error); ok {
705705
return fmt.Errorf("unable to deploy agent: %s", twerr.Msg())
706706
}
@@ -1373,7 +1373,7 @@ func generateAgentDockerfile(ctx context.Context, cmd *cli.Command) error {
13731373
return err
13741374
}
13751375

1376-
projectType, err := agentfs.DetectProjectType(workingDir)
1376+
projectType, err := agentfs.DetectProjectType(os.DirFS(workingDir))
13771377
fmt.Printf("Detected project type [%s]\n", util.Accented(string(projectType)))
13781378
if err != nil {
13791379
return fmt.Errorf("unable to determine project type: %w, please use a supported project type, or create your own Dockerfile in the current directory", err)

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
module github.com/livekit/livekit-cli/v2
22

3-
go 1.24.4
4-
5-
toolchain go1.25.0
3+
go 1.25.0
64

75
require (
86
github.com/BurntSushi/toml v1.5.0

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,6 @@ github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 h1:9x+U2HGLrSw5AT
274274
github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ=
275275
github.com/livekit/mediatransportutil v0.0.0-20250825135402-7bc31f107ade h1:lpxPcglwzUWNB4J0S2qZuyMehzmR7vW9whzSwV4IGoI=
276276
github.com/livekit/mediatransportutil v0.0.0-20250825135402-7bc31f107ade/go.mod h1:mSNtYzSf6iY9xM3UX42VEI+STHvMgHmrYzEHPcdhB8A=
277-
github.com/livekit/protocol v1.42.1-0.20251008181454-49a136864c2d h1:ofC+CPiYDZ4LD+RgIJg4rUFLvKnWJqg66bn00FALKLI=
278-
github.com/livekit/protocol v1.42.1-0.20251008181454-49a136864c2d/go.mod h1:vhMS30QoEyH2p34vi6X1eWkC4EMV72ZGZwQb74ajY7A=
279277
github.com/livekit/protocol v1.42.1-0.20251014173106-16ec4db9f66a h1:bOEck0I7XZATRccDQkf4MVMbVm5fKigmQlPU5Q/9R2g=
280278
github.com/livekit/protocol v1.42.1-0.20251014173106-16ec4db9f66a/go.mod h1:vhMS30QoEyH2p34vi6X1eWkC4EMV72ZGZwQb74ajY7A=
281279
github.com/livekit/psrpc v0.7.0 h1:rtfqfjYN06WJYloE/S0nmkJ/Y04x4pxLQLe8kQ4FVHU=

pkg/agentfs/client.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"context"
1919
"fmt"
2020
"io"
21+
"io/fs"
2122
"net/http"
2223
"os"
2324
"regexp"
@@ -100,7 +101,7 @@ func WithHTTPClient(httpClient *http.Client) ClientOption {
100101
// CreateAgent creates a new agent by building from source.
101102
func (c *Client) CreateAgent(
102103
ctx context.Context,
103-
workingDir string,
104+
source fs.FS,
104105
secrets []*lkproto.AgentSecret,
105106
regions []string,
106107
excludeFiles []string,
@@ -116,7 +117,7 @@ func (c *Client) CreateAgent(
116117
resp.AgentId,
117118
resp.PresignedUrl,
118119
resp.PresignedPostRequest,
119-
workingDir,
120+
source,
120121
excludeFiles,
121122
); err != nil {
122123
return nil, err
@@ -128,7 +129,7 @@ func (c *Client) CreateAgent(
128129
func (c *Client) DeployAgent(
129130
ctx context.Context,
130131
agentID string,
131-
workingDir string,
132+
source fs.FS,
132133
secrets []*lkproto.AgentSecret,
133134
excludeFiles []string,
134135
) error {
@@ -142,7 +143,7 @@ func (c *Client) DeployAgent(
142143
if !resp.Success {
143144
return fmt.Errorf("failed to deploy agent: %s", resp.Message)
144145
}
145-
return c.uploadAndBuild(ctx, agentID, resp.PresignedUrl, resp.PresignedPostRequest, workingDir, excludeFiles)
146+
return c.uploadAndBuild(ctx, agentID, resp.PresignedUrl, resp.PresignedPostRequest, source, excludeFiles)
146147
}
147148

148149
// uploadAndBuild uploads the source and triggers remote build
@@ -151,15 +152,15 @@ func (c *Client) uploadAndBuild(
151152
agentID string,
152153
presignedUrl string,
153154
presignedPostRequest *lkproto.PresignedPostRequest,
154-
workingDir string,
155+
source fs.FS,
155156
excludeFiles []string,
156157
) error {
157-
projectType, err := DetectProjectType(workingDir)
158+
projectType, err := DetectProjectType(source)
158159
if err != nil {
159160
return err
160161
}
161162
if err := UploadTarball(
162-
workingDir,
163+
source,
163164
presignedUrl,
164165
presignedPostRequest,
165166
excludeFiles,

pkg/agentfs/docker.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import (
3333
)
3434

3535
//go:embed examples/*
36-
var fs embed.FS
36+
var embedfs embed.FS
3737

3838
func HasDockerfile(dir string) (bool, error) {
3939
entries, err := os.ReadDir(dir)
@@ -76,14 +76,14 @@ func GenerateDockerArtifacts(dir string, projectType ProjectType, settingsMap ma
7676

7777
// NOTE: embed.FS uses unix-style path separators on all platforms, so cannot use filepath.Join here.
7878
// path.Join always uses '/' as the separator.
79-
dockerfileContent, err := fs.ReadFile(path.Join("examples", string(projectType)+".Dockerfile"))
79+
dockerfileContent, err := embedfs.ReadFile(path.Join("examples", string(projectType)+".Dockerfile"))
8080
if err != nil {
8181
return nil, nil, err
8282
}
8383

8484
// NOTE: embed.FS uses unix-style path separators on all platforms, so cannot use filepath.Join here
8585
// path.Join always uses '/' as the separator.
86-
dockerIgnoreContent, err := fs.ReadFile(path.Join("examples", string(projectType)+".dockerignore"))
86+
dockerIgnoreContent, err := embedfs.ReadFile(path.Join("examples", string(projectType)+".dockerignore"))
8787
if err != nil {
8888
return nil, nil, err
8989
}

pkg/agentfs/docker_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func TestLoadDockerFiles(t *testing.T) {
1616
}
1717

1818
for _, file := range expectedFiles {
19-
bytes, err := fs.ReadFile(file)
19+
bytes, err := embedfs.ReadFile(file)
2020
if err != nil {
2121
t.Fatalf("failed to read Dockerfile: %v", err)
2222
}

pkg/agentfs/tar.go

Lines changed: 21 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,10 @@ import (
2020
"compress/gzip"
2121
"fmt"
2222
"io"
23+
"io/fs"
2324
"mime/multipart"
2425
"net/http"
25-
"os"
2626
"path"
27-
"path/filepath"
2827
"strings"
2928

3029
"github.com/schollz/progressbar/v3"
@@ -54,17 +53,17 @@ var (
5453
)
5554

5655
func UploadTarball(
57-
directory string,
56+
directory fs.FS,
5857
presignedUrl string,
5958
presignedPostRequest *livekit.PresignedPostRequest,
6059
excludeFiles []string,
6160
projectType ProjectType,
6261
) error {
6362
excludeFiles = append(excludeFiles, defaultExcludePatterns...)
6463

65-
loadExcludeFiles := func(filename string) (bool, string, error) {
66-
if _, err := os.Stat(filename); err == nil {
67-
content, err := os.ReadFile(filename)
64+
loadExcludeFiles := func(dir fs.FS, filename string) (bool, string, error) {
65+
if _, err := fs.Stat(dir, filename); err == nil {
66+
content, err := fs.ReadFile(dir, filename)
6867
if err != nil {
6968
return false, "", err
7069
}
@@ -75,7 +74,7 @@ func UploadTarball(
7574

7675
foundDockerIgnore := false
7776
for _, exclude := range ignoreFilePatterns {
78-
found, content, err := loadExcludeFiles(path.Join(directory, exclude))
77+
found, content, err := loadExcludeFiles(directory, exclude)
7978
if err != nil {
8079
logger.Debugw("failed to load exclude file", "filename", exclude, "error", err)
8180
continue
@@ -89,7 +88,7 @@ func UploadTarball(
8988
// need to ensure we use a dockerignore file
9089
// if we fail to load a dockerignore file, we have to exit
9190
if !foundDockerIgnore {
92-
dockerIgnoreContent, err := fs.ReadFile(path.Join("examples", string(projectType)+".dockerignore"))
91+
dockerIgnoreContent, err := embedfs.ReadFile(path.Join("examples", string(projectType)+".dockerignore"))
9392
if err != nil {
9493
return fmt.Errorf("failed to load exclude file %s: %w", string(projectType), err)
9594
}
@@ -105,14 +104,14 @@ func UploadTarball(
105104
excludeFiles[i] = strings.TrimSpace(exclude)
106105
}
107106

108-
checkFilesToInclude := func(path string, info os.FileInfo) bool {
109-
fileName := filepath.Base(path)
107+
checkFilesToInclude := func(p string) bool {
108+
fileName := path.Base(p)
110109
// we have to include the Dockerfile in the upload, as it is required for the build
111110
if strings.Contains(fileName, "Dockerfile") {
112111
return true
113112
}
114113

115-
if ignored, err := matcher.MatchesOrParentMatches(path); ignored {
114+
if ignored, err := matcher.MatchesOrParentMatches(p); ignored {
116115
return false
117116
} else if err != nil {
118117
return false
@@ -123,17 +122,17 @@ func UploadTarball(
123122
// we walk the directory first to calculate the total size of the tarball
124123
// this lets the progress bar show the correct progress
125124
var totalSize int64
126-
err = filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
125+
err = fs.WalkDir(directory, ".", func(path string, d fs.DirEntry, err error) error {
127126
if err != nil {
128127
return err
129128
}
130129

131-
relPath, err := filepath.Rel(directory, path)
130+
info, err := d.Info()
132131
if err != nil {
133-
return nil
132+
return err
134133
}
135134

136-
if !checkFilesToInclude(relPath, info) {
135+
if !checkFilesToInclude(path) {
137136
return nil
138137
}
139138

@@ -166,62 +165,28 @@ func UploadTarball(
166165
tarWriter := tar.NewWriter(gzipWriter)
167166
defer tarWriter.Close()
168167

169-
err = filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
168+
err = fs.WalkDir(directory, ".", func(path string, d fs.DirEntry, err error) error {
170169
if err != nil {
171170
return err
172171
}
173172

174-
relPath, err := filepath.Rel(directory, path)
173+
info, err := d.Info()
175174
if err != nil {
176-
return fmt.Errorf("failed to calculate relative path for %s: %w", path, err)
175+
return err
177176
}
178177

179-
if !checkFilesToInclude(relPath, info) {
178+
if !checkFilesToInclude(path) {
180179
logger.Debugw("excluding file from tarball", "path", path)
181180
return nil
182181
}
183182

184-
// Follow symlinks and include the actual file contents
185-
if info.Mode()&os.ModeSymlink != 0 {
186-
realPath, err := filepath.EvalSymlinks(path)
187-
if err != nil {
188-
return fmt.Errorf("failed to evaluate symlink %s: %w", path, err)
189-
}
190-
info, err = os.Stat(realPath)
191-
if err != nil {
192-
return fmt.Errorf("failed to stat %s: %w", realPath, err)
193-
}
194-
// Open the real file instead of the symlink
195-
file, err := os.Open(realPath)
196-
if err != nil {
197-
return fmt.Errorf("failed to open file %s: %w", realPath, err)
198-
}
199-
defer file.Close()
200-
201-
header, err := tar.FileInfoHeader(info, "")
202-
if err != nil {
203-
return fmt.Errorf("failed to create tar header for file %s: %w", path, err)
204-
}
205-
header.Name = relPath
206-
if err := tarWriter.WriteHeader(header); err != nil {
207-
return fmt.Errorf("failed to write tar header for file %s: %w", path, err)
208-
}
209-
210-
// Copy file contents directly without progress bar
211-
_, err = io.Copy(tarWriter, file)
212-
if err != nil {
213-
return fmt.Errorf("failed to copy file content for %s: %w", path, err)
214-
}
215-
return nil
216-
}
217-
218183
// Handle directories
219184
if info.IsDir() {
220185
header, err := tar.FileInfoHeader(info, "")
221186
if err != nil {
222187
return fmt.Errorf("failed to create tar header for directory %s: %w", path, err)
223188
}
224-
header.Name = relPath + "/"
189+
header.Name = util.ToUnixPath(path) + "/"
225190
if err := tarWriter.WriteHeader(header); err != nil {
226191
return fmt.Errorf("failed to write tar header for directory %s: %w", path, err)
227192
}
@@ -234,7 +199,7 @@ func UploadTarball(
234199
return nil
235200
}
236201

237-
file, err := os.Open(path)
202+
file, err := directory.Open(path)
238203
if err != nil {
239204
return fmt.Errorf("failed to open file %s: %w", path, err)
240205
}
@@ -244,7 +209,7 @@ func UploadTarball(
244209
if err != nil {
245210
return fmt.Errorf("failed to create tar header for file %s: %w", path, err)
246211
}
247-
header.Name = util.ToUnixPath(relPath)
212+
header.Name = util.ToUnixPath(path)
248213
if err := tarWriter.WriteHeader(header); err != nil {
249214
return fmt.Errorf("failed to write tar header for file %s: %w", path, err)
250215
}

0 commit comments

Comments
 (0)