refactor(misconf): decouple input fs and track extracted files with fs references (#9281)

Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
This commit is contained in:
Nikita Pivkin
2025-08-01 11:21:38 +06:00
committed by GitHub
parent b77d6e2c14
commit 649eb2f8e6
3 changed files with 49 additions and 57 deletions

View File

@@ -13,6 +13,7 @@ import (
"strings"
"github.com/google/uuid"
"github.com/liamg/memoryfs"
"gopkg.in/yaml.v3"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
@@ -32,8 +33,7 @@ type Parser struct {
helmClient *action.Install
rootPath string
ChartSource string
filepaths []string
workingFS fs.FS
filepaths map[string]fs.FS
valuesFiles []string
values []string
fileValues []string
@@ -58,6 +58,7 @@ func New(src string, opts ...Option) (*Parser, error) {
helmClient: client,
ChartSource: src,
logger: log.WithPrefix("helm parser"),
filepaths: make(map[string]fs.FS),
}
for _, option := range opts {
@@ -81,9 +82,12 @@ func New(src string, opts ...Option) (*Parser, error) {
}
func (p *Parser) ParseFS(ctx context.Context, fsys fs.FS, target string) error {
p.workingFS = fsys
return p.parseFS(ctx, fsys, target)
}
if err := fs.WalkDir(p.workingFS, filepath.ToSlash(target), func(filePath string, entry fs.DirEntry, err error) error {
func (p *Parser) parseFS(ctx context.Context, fsys fs.FS, target string) error {
target = filepath.ToSlash(target)
if err := fs.WalkDir(fsys, target, func(filePath string, entry fs.DirEntry, err error) error {
select {
case <-ctx.Done():
return ctx.Err()
@@ -96,31 +100,22 @@ func (p *Parser) ParseFS(ctx context.Context, fsys fs.FS, target string) error {
return nil
}
if _, err := fs.Stat(p.workingFS, filePath); err != nil {
return nil
}
if detection.IsArchive(filePath) && !isDependencyChartArchive(p.workingFS, filePath) {
tarFS, err := p.addTarToFS(filePath)
if errors.Is(err, errSkipFS) {
if detection.IsArchive(filePath) && !isDependencyChartArchive(fsys, filePath) {
memFS := memoryfs.New()
if err := p.unpackArchive(fsys, memFS, filePath); errors.Is(err, errSkipFS) {
// an unpacked Chart already exists
return nil
} else if err != nil {
return fmt.Errorf("failed to add tar %q to FS: %w", filePath, err)
return fmt.Errorf("unpack archive %q: %w", filePath, err)
}
targetPath := filepath.Dir(filePath)
if targetPath == "" {
targetPath = "."
}
if err := p.ParseFS(ctx, tarFS, targetPath); err != nil {
return fmt.Errorf("parse tar FS error: %w", err)
if err := p.parseFS(ctx, memFS, "."); err != nil {
return fmt.Errorf("parse archive FS error: %w", err)
}
return nil
} else {
return p.addPaths(filePath)
}
return p.addPaths(fsys, filePath)
}); err != nil {
return fmt.Errorf("walk dir error: %w", err)
}
@@ -138,26 +133,26 @@ func isDependencyChartArchive(fsys fs.FS, archivePath string) bool {
return err == nil
}
func (p *Parser) addPaths(paths ...string) error {
func (p *Parser) addPaths(fsys fs.FS, paths ...string) error {
for _, filePath := range paths {
if _, err := fs.Stat(p.workingFS, filePath); err != nil {
if _, err := fs.Stat(fsys, filePath); err != nil {
return err
}
if strings.HasSuffix(filePath, "Chart.yaml") && p.rootPath == "" {
if err := p.extractChartName(filePath); err != nil {
if err := p.extractChartName(fsys, filePath); err != nil {
return err
}
p.rootPath = filepath.Dir(filePath)
}
p.filepaths = append(p.filepaths, filePath)
p.filepaths[filePath] = fsys
}
return nil
}
func (p *Parser) extractChartName(chartPath string) error {
func (p *Parser) extractChartName(fsys fs.FS, chartPath string) error {
chrt, err := p.workingFS.Open(chartPath)
chrt, err := fsys.Open(chartPath)
if err != nil {
return err
}
@@ -228,11 +223,10 @@ func (p *Parser) getRelease(chrt *chart.Chart) (*release.Release, error) {
}
func (p *Parser) loadChart() (*chart.Chart, error) {
var files []*loader.BufferedFile
for _, filePath := range p.filepaths {
b, err := fs.ReadFile(p.workingFS, filePath)
for filePath, fsys := range p.filepaths {
b, err := fs.ReadFile(fsys, filePath)
if err != nil {
return nil, err
}

View File

@@ -19,12 +19,10 @@ import (
var errSkipFS = errors.New("skip parse FS")
func (p *Parser) addTarToFS(archivePath string) (fs.FS, error) {
tarFS := memoryfs.CloneFS(p.workingFS)
file, err := tarFS.Open(archivePath)
func (p *Parser) unpackArchive(srcFS fs.FS, targetFS *memoryfs.FS, archivePath string) error {
file, err := srcFS.Open(archivePath)
if err != nil {
return nil, fmt.Errorf("failed to open tar: %w", err)
return fmt.Errorf("failed to open tar: %w", err)
}
defer file.Close()
@@ -33,7 +31,7 @@ func (p *Parser) addTarToFS(archivePath string) (fs.FS, error) {
if detection.IsZip(archivePath) {
zipped, err := gzip.NewReader(file)
if err != nil {
return nil, fmt.Errorf("failed to create gzip reader: %w", err)
return fmt.Errorf("failed to create gzip reader: %w", err)
}
defer zipped.Close()
tr = tar.NewReader(zipped)
@@ -50,7 +48,7 @@ func (p *Parser) addTarToFS(archivePath string) (fs.FS, error) {
if errors.Is(err, io.EOF) {
break
}
return nil, fmt.Errorf("failed to get next entry: %w", err)
return fmt.Errorf("failed to get next entry: %w", err)
}
name := filepath.ToSlash(header.Name)
@@ -59,28 +57,27 @@ func (p *Parser) addTarToFS(archivePath string) (fs.FS, error) {
// Do not add archive files to FS if the chart already exists
// This can happen when the source chart is located next to an archived chart (with the `helm package` command)
// The first level folder in the archive is equal to the Chart name
if _, err := tarFS.Stat(path.Dir(archivePath) + "/" + path.Dir(name)); err == nil {
return nil, errSkipFS
if _, err := fs.Stat(srcFS, path.Clean(path.Dir(archivePath)+"/"+path.Dir(name))); err == nil {
return errSkipFS
}
checkExistedChart = false
}
// get the individual path and extract to the current directory
targetPath := path.Join(path.Dir(archivePath), path.Clean(name))
link := filepath.ToSlash(header.Linkname)
targetPath := archiveEntryPath(archivePath, name)
switch header.Typeflag {
case tar.TypeDir:
if err := tarFS.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil && !errors.Is(err, fs.ErrExist) {
return nil, err
if err := targetFS.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil && !errors.Is(err, fs.ErrExist) {
return err
}
case tar.TypeReg:
p.logger.Debug("Unpacking tar entry", log.FilePath(targetPath))
if err := copyFile(tarFS, tr, targetPath); err != nil {
return nil, err
if err := copyFile(targetFS, tr, targetPath); err != nil {
return err
}
case tar.TypeSymlink:
link := filepath.ToSlash(header.Linkname)
if path.IsAbs(link) {
p.logger.Debug("Symlink is absolute, skipping", log.String("link", link))
continue
@@ -88,21 +85,21 @@ func (p *Parser) addTarToFS(archivePath string) (fs.FS, error) {
symlinks[targetPath] = path.Join(path.Dir(targetPath), link) // nolint:gosec // virtual file system is used
default:
return nil, fmt.Errorf("header type %q is not supported", header.Typeflag)
return fmt.Errorf("header type %q is not supported", header.Typeflag)
}
}
for target, link := range symlinks {
if err := copySymlink(tarFS, link, target); err != nil {
return nil, fmt.Errorf("copy symlink error: %w", err)
if err := copySymlink(targetFS, link, target); err != nil {
return fmt.Errorf("copy symlink error: %w", err)
}
}
if err := tarFS.Remove(archivePath); err != nil {
return nil, fmt.Errorf("remove tar from FS error: %w", err)
}
return nil
}
return tarFS, nil
func archiveEntryPath(archivePath, name string) string {
return path.Join(path.Dir(archivePath), path.Clean(name))
}
func copySymlink(fsys *memoryfs.FS, src, dst string) error {

View File

@@ -5,6 +5,7 @@ import (
"path/filepath"
"testing"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -19,7 +20,7 @@ func TestParseFS(t *testing.T) {
"my-chart/Chart.yaml",
"my-chart/templates/pod.yaml",
}
assert.Equal(t, expectedFiles, p.filepaths)
assert.ElementsMatch(t, expectedFiles, lo.Keys(p.filepaths))
})
t.Run("archive with symlinks", func(t *testing.T) {
@@ -45,7 +46,7 @@ func TestParseFS(t *testing.T) {
"chart/sym-to-dir/Chart.yaml",
"chart/sym-to-file/Chart.yaml",
}
assert.Equal(t, expectedFiles, p.filepaths)
assert.ElementsMatch(t, expectedFiles, lo.Keys(p.filepaths))
})
t.Run("chart with multiple archived deps", func(t *testing.T) {
@@ -60,7 +61,7 @@ func TestParseFS(t *testing.T) {
"charts/common-2.26.0.tgz",
"charts/opentelemetry-collector-0.108.0.tgz",
}
assert.Equal(t, expectedFiles, p.filepaths)
assert.ElementsMatch(t, expectedFiles, lo.Keys(p.filepaths))
})
t.Run("archives are not dependencies", func(t *testing.T) {
@@ -75,6 +76,6 @@ func TestParseFS(t *testing.T) {
"backup_charts/wordpress-operator/Chart.yaml",
"backup_charts/mysql-operator/Chart.yaml",
}
assert.Subset(t, p.filepaths, expectedFiles)
assert.Subset(t, lo.Keys(p.filepaths), expectedFiles)
})
}