Skip to content

Commit

Permalink
fix: avoid false positives in import cycle detection (#3449)
Browse files Browse the repository at this point in the history
Signed-off-by: Austin Abro <[email protected]>
  • Loading branch information
AustinAbro321 authored Jan 30, 2025
1 parent bea609f commit 345abaa
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 14 deletions.
2 changes: 1 addition & 1 deletion src/internal/packager2/layout/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ func LoadPackage(ctx context.Context, packagePath, flavor string, setVariables m
return v1alpha1.ZarfPackage{}, err
}
pkg.Metadata.Architecture = config.GetArch(pkg.Metadata.Architecture)
pkg, err = resolveImports(ctx, pkg, packagePath, pkg.Metadata.Architecture, flavor, map[string]interface{}{})
pkg, err = resolveImports(ctx, pkg, packagePath, pkg.Metadata.Architecture, flavor, []string{})
if err != nil {
return v1alpha1.ZarfPackage{}, err
}
Expand Down
20 changes: 10 additions & 10 deletions src/internal/packager2/layout/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"context"
"errors"
"fmt"
"maps"
"os"
"path/filepath"
"slices"
Expand All @@ -24,7 +23,9 @@ import (
"github.com/zarf-dev/zarf/src/pkg/zoci"
)

func resolveImports(ctx context.Context, pkg v1alpha1.ZarfPackage, packagePath, arch, flavor string, seenImports map[string]interface{}) (v1alpha1.ZarfPackage, error) {
func resolveImports(ctx context.Context, pkg v1alpha1.ZarfPackage, packagePath, arch, flavor string, importStack []string) (v1alpha1.ZarfPackage, error) {
importStack = append(importStack, packagePath)

variables := pkg.Variables
constants := pkg.Constants
components := []v1alpha1.ZarfComponent{}
Expand All @@ -47,12 +48,11 @@ func resolveImports(ctx context.Context, pkg v1alpha1.ZarfPackage, packagePath,
var importedPkg v1alpha1.ZarfPackage
if component.Import.Path != "" {
importPath := filepath.Join(packagePath, component.Import.Path)
importKey := fmt.Sprintf("%s-%s", component.Name, importPath)
if _, ok := seenImports[importKey]; ok {
return v1alpha1.ZarfPackage{}, fmt.Errorf("package %s imported in cycle by %s in component %s", filepath.ToSlash(importPath), filepath.ToSlash(packagePath), component.Name)
for _, sp := range importStack {
if sp == importPath {
return v1alpha1.ZarfPackage{}, fmt.Errorf("package %s imported in cycle by %s in component %s", filepath.ToSlash(importPath), filepath.ToSlash(packagePath), component.Name)
}
}
seenImports = maps.Clone(seenImports)
seenImports[importKey] = nil
b, err := os.ReadFile(filepath.Join(importPath, layout.ZarfYAML))
if err != nil {
return v1alpha1.ZarfPackage{}, err
Expand All @@ -61,7 +61,7 @@ func resolveImports(ctx context.Context, pkg v1alpha1.ZarfPackage, packagePath,
if err != nil {
return v1alpha1.ZarfPackage{}, err
}
importedPkg, err = resolveImports(ctx, importedPkg, importPath, arch, flavor, seenImports)
importedPkg, err = resolveImports(ctx, importedPkg, importPath, arch, flavor, importStack)
if err != nil {
return v1alpha1.ZarfPackage{}, err
}
Expand Down Expand Up @@ -91,7 +91,7 @@ func resolveImports(ctx context.Context, pkg v1alpha1.ZarfPackage, packagePath,
}
}
if len(found) == 0 {
return v1alpha1.ZarfPackage{}, fmt.Errorf("component %s not found", name)
return v1alpha1.ZarfPackage{}, fmt.Errorf("no compatible component named %s found", name)
} else if len(found) > 1 {
return v1alpha1.ZarfPackage{}, fmt.Errorf("multiple components named %s found", name)
}
Expand Down Expand Up @@ -122,7 +122,7 @@ func resolveImports(ctx context.Context, pkg v1alpha1.ZarfPackage, packagePath,
pkg.Constants = slices.CompactFunc(constants, func(l, r v1alpha1.Constant) bool {
return l.Name == r.Name
})

importStack = importStack[0:len(importStack)-1]
return pkg, nil
}

Expand Down
28 changes: 25 additions & 3 deletions src/internal/packager2/layout/import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,35 @@ func TestResolveImportsCircular(t *testing.T) {

lint.ZarfSchema = testutil.LoadSchema(t, "../../../../zarf.schema.json")

b, err := os.ReadFile(filepath.Join("./testdata/import/first", ZarfYAML))
b, err := os.ReadFile(filepath.Join("./testdata/import/circular/first", ZarfYAML))
require.NoError(t, err)
pkg, err := ParseZarfPackage(b)
require.NoError(t, err)

_, err = resolveImports(ctx, pkg, "./testdata/import/first", "", "", map[string]interface{}{})
require.EqualError(t, err, "package testdata/import/second imported in cycle by testdata/import/third in component component")
_, err = resolveImports(ctx, pkg, "./testdata/import/circular/first", "", "", []string{})
require.EqualError(t, err, "package testdata/import/circular/second imported in cycle by testdata/import/circular/third in component component")
}

func TestResolveImportsBranches(t *testing.T) {
t.Parallel()
ctx := testutil.TestContext(t)
lint.ZarfSchema = testutil.LoadSchema(t, "../../../../zarf.schema.json")

// Get the parent package
b, err := os.ReadFile(filepath.Join("./testdata/import/branch", ZarfYAML))
require.NoError(t, err)
pkg, err := ParseZarfPackage(b)
require.NoError(t, err)

resolvedPkg, err := resolveImports(ctx, pkg, "./testdata/import/branch", "", "", []string{})
require.NoError(t, err)

// ensure imports were resolved correctly
b, err = os.ReadFile(filepath.Join("./testdata/import/branch", "expected.yaml"))
require.NoError(t, err)
expectedPkg, err := ParseZarfPackage(b)
require.NoError(t, err)
require.Equal(t, expectedPkg, resolvedPkg)
}

func TestValidateComponentCompose(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
kind: ZarfPackageConfig
metadata:
name: example-child

components:
- name: common
import:
path: ../common

- name: parent-child-common
import:
path: ../common
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
kind: ZarfPackageConfig
metadata:
name: example-shared

components:
- name: common
description: This gets imported by the parent and child directly

- name: parent-child-common
description: This gets imported in a chain parent->child->common
10 changes: 10 additions & 0 deletions src/internal/packager2/layout/testdata/import/branch/expected.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
kind: ZarfPackageConfig
metadata:
name: example-package
components:
- name: common
description: This gets imported by the parent and child directly
required: true
- name: parent-child-common
description: This gets imported in a chain parent->child->common
required: true
14 changes: 14 additions & 0 deletions src/internal/packager2/layout/testdata/import/branch/zarf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
kind: ZarfPackageConfig
metadata:
name: example-package

components:
- name: common
required: true
import:
path: common

- name: parent-child-common
required: true
import:
path: child

0 comments on commit 345abaa

Please sign in to comment.