Skip to content

Commit 3b1df46

Browse files
authored
feat: supports adding files inside the VM to support bundles (runfinch#549)
Issue #, if available: *Description of changes:* Allows users to add files from inside their finch VM to a support bundle. The file can be specified in a config or in a command line flag with `vm:/path/to/file/in/vm` *Testing done:* ``` make test-unit ``` manual testing - [x] I've reviewed the guidance in CONTRIBUTING.md #### License Acceptance By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. Signed-off-by: Sam Berning <[email protected]>
1 parent e0160be commit 3b1df46

File tree

6 files changed

+220
-26
lines changed

6 files changed

+220
-26
lines changed

Diff for: cmd/finch/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ var newApp = func(logger flog.Logger, fp path.Finch, fs afero.Fs, fc *config.Fin
9696
support.NewBundleConfig(fp, system.NewStdLib().Env("HOME")),
9797
fp,
9898
ecc,
99+
lcc,
99100
wrapper.NewLimaWrapper(),
100101
)
101102

Diff for: cmd/finch/support_bundle.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ func newSupportBundleGenerateCommand(logger flog.Logger, builder support.BundleB
3535
}
3636

3737
supportBundleGenerateCommand.Flags().StringArray("include", []string{},
38-
"additional files to include in the support bundle, specified by absolute or relative path")
38+
//nolint:lll // usage string
39+
`additional files to include in the support bundle, specified by absolute or relative path. to include a file from the VM, prefix the file path with "vm:"`)
3940
supportBundleGenerateCommand.Flags().StringArray("exclude", []string{},
4041
//nolint:lll // usage string
4142
"files to exclude from the support bundle. if you specify a base name, all files matching that base name will be excluded. if you specify an absolute or relative path, only exact matches will be excluded")

Diff for: pkg/command/command.go

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ type Command interface {
2626
SetStderr(io.Writer)
2727

2828
Run() error
29+
Start() error
30+
Wait() error
2931
Output() ([]byte, error)
3032
CombinedOutput() ([]byte, error)
3133
}

Diff for: pkg/mocks/command_command.go

+28
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: pkg/support/support.go

+92-22
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package support
66

77
import (
88
"archive/zip"
9+
"bufio"
910
"bytes"
1011
"errors"
1112
"fmt"
@@ -51,6 +52,7 @@ type bundleBuilder struct {
5152
config BundleConfig
5253
finch fpath.Finch
5354
ecc command.Creator
55+
lcc command.LimaCmdCreator
5456
lima wrapper.LimaWrapper
5557
}
5658

@@ -61,6 +63,7 @@ func NewBundleBuilder(
6163
config BundleConfig,
6264
finch fpath.Finch,
6365
ecc command.Creator,
66+
lcc command.LimaCmdCreator,
6467
lima wrapper.LimaWrapper,
6568
) BundleBuilder {
6669
return &bundleBuilder{
@@ -69,6 +72,7 @@ func NewBundleBuilder(
6972
config: config,
7073
finch: finch,
7174
ecc: ecc,
75+
lcc: lcc,
7276
lima: lima,
7377
}
7478
}
@@ -108,7 +112,8 @@ func (bb *bundleBuilder) GenerateSupportBundle(additionalFiles []string, exclude
108112
bb.logger.Infof("Excluding %s...", file)
109113
continue
110114
}
111-
err := bb.copyInFile(writer, file, path.Join(zipPrefix, logPrefix))
115+
bb.logger.Debugf("Copying %s...", file)
116+
err = bb.copyFileFromVMOrLocal(writer, file, path.Join(zipPrefix, logPrefix))
112117
if err != nil {
113118
bb.logger.Warnf("Could not copy in %q. Error: %s", file, err)
114119
}
@@ -120,7 +125,8 @@ func (bb *bundleBuilder) GenerateSupportBundle(additionalFiles []string, exclude
120125
bb.logger.Infof("Excluding %s...", file)
121126
continue
122127
}
123-
err := bb.copyInFile(writer, file, path.Join(zipPrefix, configPrefix))
128+
bb.logger.Debugf("Copying %s...", file)
129+
err = bb.copyFileFromVMOrLocal(writer, file, path.Join(zipPrefix, configPrefix))
124130
if err != nil {
125131
bb.logger.Warnf("Could not copy in %q. Error: %s", file, err)
126132
}
@@ -132,7 +138,8 @@ func (bb *bundleBuilder) GenerateSupportBundle(additionalFiles []string, exclude
132138
bb.logger.Infof("Excluding %s...", file)
133139
continue
134140
}
135-
err := bb.copyInFile(writer, file, path.Join(zipPrefix, additionalPrefix))
141+
bb.logger.Debugf("Copying %s...", file)
142+
err = bb.copyFileFromVMOrLocal(writer, file, path.Join(zipPrefix, additionalPrefix))
136143
if err != nil {
137144
bb.logger.Warnf("Could not add additional file %s. Error: %s", file, err)
138145
}
@@ -146,30 +153,28 @@ func (bb *bundleBuilder) GenerateSupportBundle(additionalFiles []string, exclude
146153
return zipFileName, nil
147154
}
148155

149-
func (bb *bundleBuilder) copyInFile(writer *zip.Writer, fileName string, prefix string) error {
150-
f, err := bb.fs.Open(fileName)
151-
if err != nil {
152-
return err
153-
}
154-
155-
bb.logger.Debugf("Copying %s...", fileName)
156+
type bufReader interface {
157+
ReadBytes(delim byte) ([]byte, error)
158+
}
156159

157-
var buf bytes.Buffer
158-
_, err = buf.ReadFrom(f)
159-
if err != nil {
160-
return err
160+
func (bb *bundleBuilder) copyFileFromVMOrLocal(writer *zip.Writer, filename, zipPath string) error {
161+
if isFileFromVM(filename) {
162+
return bb.streamFileFromVM(writer, filename, zipPath)
161163
}
164+
return bb.copyInFile(writer, filename, zipPath)
165+
}
162166

163-
var redacted []byte
167+
func (bb *bundleBuilder) copyAndRedactFile(writer io.Writer, reader bufReader) error {
164168
var bufErr error
165169
for bufErr == nil {
166170
var line []byte
167-
line, bufErr = buf.ReadBytes('\n')
171+
line, bufErr = reader.ReadBytes('\n')
168172
if bufErr != nil && !errors.Is(bufErr, io.EOF) {
173+
bb.logger.Error(bufErr.Error())
169174
continue
170175
}
171176

172-
line, err = redactFinchInstall(line, bb.finch)
177+
line, err := redactFinchInstall(line, bb.finch)
173178
if err != nil {
174179
return err
175180
}
@@ -187,7 +192,20 @@ func (bb *bundleBuilder) copyInFile(writer *zip.Writer, fileName string, prefix
187192
line = redactPorts(line)
188193
line = redactSSHKeys(line)
189194

190-
redacted = append(redacted, line...)
195+
_, err = writer.Write(line)
196+
if err != nil {
197+
return err
198+
}
199+
}
200+
201+
return nil
202+
}
203+
204+
func (bb *bundleBuilder) copyInFile(writer *zip.Writer, fileName string, prefix string) error {
205+
// check filename validity?
206+
f, err := bb.fs.Open(fileName)
207+
if err != nil {
208+
return err
191209
}
192210

193211
baseName := path.Base(fileName)
@@ -196,12 +214,56 @@ func (bb *bundleBuilder) copyInFile(writer *zip.Writer, fileName string, prefix
196214
return err
197215
}
198216

199-
_, err = zipCopy.Write(redacted)
217+
buf := new(bytes.Buffer)
218+
_, err = buf.ReadFrom(f)
200219
if err != nil {
201220
return err
202221
}
203222

204-
return nil
223+
return bb.copyAndRedactFile(zipCopy, buf)
224+
}
225+
226+
func (bb *bundleBuilder) streamFileFromVM(writer *zip.Writer, filename, prefix string) error {
227+
pipeReader, pipeWriter := io.Pipe()
228+
errBuf := new(bytes.Buffer)
229+
230+
_, filePathInVM, _ := strings.Cut(filename, ":")
231+
cmd := bb.lcc.CreateWithoutStdio("shell", "finch", "sudo", "cat", filePathInVM)
232+
cmd.SetStdout(pipeWriter)
233+
cmd.SetStderr(errBuf)
234+
235+
err := cmd.Start()
236+
if err != nil {
237+
return err
238+
}
239+
240+
waitStatus := make(chan error)
241+
go func() {
242+
err := cmd.Wait()
243+
if err != nil {
244+
errorMsg, readErr := io.ReadAll(errBuf)
245+
if readErr == nil && len(errorMsg) > 0 {
246+
err = errors.New(string(errorMsg))
247+
}
248+
}
249+
_ = pipeWriter.Close()
250+
waitStatus <- err
251+
}()
252+
253+
baseName := path.Base(filename)
254+
zipCopy, err := writer.Create(path.Join(prefix, baseName))
255+
if err != nil {
256+
return err
257+
}
258+
259+
bufReader := bufio.NewReader(pipeReader)
260+
261+
err = bb.copyAndRedactFile(zipCopy, bufReader)
262+
if err != nil {
263+
return err
264+
}
265+
266+
return <-waitStatus
205267
}
206268

207269
func (bb *bundleBuilder) getPlatformData() (*PlatformData, error) {
@@ -280,7 +342,11 @@ func bundleFileName() string {
280342
}
281343

282344
func fileShouldBeExcluded(filename string, exclude []string) bool {
283-
fileAbs, err := filepath.Abs(filename)
345+
realFilename := filename
346+
if isFileFromVM(filename) {
347+
_, realFilename, _ = strings.Cut(filename, ":")
348+
}
349+
fileAbs, err := filepath.Abs(realFilename)
284350
if err != nil {
285351
return true
286352
}
@@ -292,9 +358,13 @@ func fileShouldBeExcluded(filename string, exclude []string) bool {
292358
if fileAbs == excludeAbs {
293359
return true
294360
}
295-
if path.Base(filename) == excludeFile {
361+
if path.Base(realFilename) == excludeFile {
296362
return true
297363
}
298364
}
299365
return false
300366
}
367+
368+
func isFileFromVM(filename string) bool {
369+
return strings.HasPrefix(filename, "vm:")
370+
}

0 commit comments

Comments
 (0)