Skip to content

Commit e96c5c8

Browse files
authored
Refactor composed package logic to handle nested composes (zarf-dev#588)
1 parent b051061 commit e96c5c8

File tree

4 files changed

+120
-92
lines changed

4 files changed

+120
-92
lines changed

src/internal/packager/compose.go

+118-88
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package packager
22

33
import (
4-
"strings"
4+
"path/filepath"
55

66
"github.com/defenseunicorns/zarf/src/config"
77
"github.com/defenseunicorns/zarf/src/internal/message"
@@ -10,111 +10,166 @@ import (
1010
"github.com/defenseunicorns/zarf/src/types"
1111
)
1212

13-
func GetComposedComponents() (components []types.ZarfComponent) {
13+
func GetComponents() (components []types.ZarfComponent) {
1414
for _, component := range config.GetComponents() {
15-
// Check for standard component.
1615
if component.Import.Path == "" {
17-
// Append standard component to list.
1816
components = append(components, component)
1917
} else {
20-
validateOrBail(&component)
21-
22-
// Expand and add components from imported package.
23-
importedComponent := getImportedComponent(component)
24-
// Merge in parent component changes.
25-
mergeComponentOverrides(&importedComponent, component)
26-
// Add to the list of components for the package.
27-
components = append(components, importedComponent)
18+
components = append(components, GetComposedComponent(component))
2819
}
2920
}
3021

3122
// Update the parent package config with the expanded sub components.
3223
// This is important when the deploy package is created.
3324
config.SetComponents(components)
25+
3426
return components
3527
}
3628

37-
// Validates the sub component, exits program if validation fails.
38-
func validateOrBail(component *types.ZarfComponent) {
39-
if err := validate.ValidateImportPackage(component); err != nil {
40-
message.Fatalf(err, "Invalid import definition in the %s component: %s", component.Name, err)
41-
}
42-
}
29+
func GetComposedComponent(childComponent types.ZarfComponent) types.ZarfComponent {
30+
// Make sure the component we're trying to import cant be accessed
31+
validateOrBail(&childComponent)
4332

44-
// Sets Name, Default, Required and Description to the original components values
45-
func mergeComponentOverrides(target *types.ZarfComponent, src types.ZarfComponent) {
46-
target.Name = src.Name
47-
target.Default = src.Default
48-
target.Required = src.Required
33+
// Keep track of the composed components import path to build nestedily composed components
34+
everGrowingComposePath := ""
4935

50-
if src.Description != "" {
51-
target.Description = src.Description
52-
}
36+
// Get the component that we are trying to import
37+
// NOTE: This function is recursive and will continue getting the parents until there are no more 'imported' components left
38+
parentComponent := getParentComponent(childComponent, everGrowingComposePath)
39+
40+
// Merge the overrides from the parent that we just received with the child we were provided
41+
mergeComponentOverrides(&parentComponent, childComponent)
42+
43+
return parentComponent
5344
}
5445

55-
// Get expanded components from imported component.
56-
func getImportedComponent(importComponent types.ZarfComponent) (component types.ZarfComponent) {
57-
// Read the imported package.
58-
importedPackage := getSubPackage(&importComponent)
46+
func getParentComponent(childComponent types.ZarfComponent, everGrowingComposePath string) (parentComponent types.ZarfComponent) {
47+
importedPackage, err := getSubPackage(filepath.Join(everGrowingComposePath, childComponent.Import.Path))
48+
if err != nil {
49+
message.Fatal(err, "Unable to get the package that we're importing a component from")
50+
}
5951

60-
componentName := importComponent.Import.ComponentName
61-
// Default to the component name if a custom one was not provided
62-
if componentName == "" {
63-
componentName = importComponent.Name
52+
// Figure out which component we are actually importing
53+
// NOTE: Default to the component name if a custom one was not provided
54+
parentComponentName := childComponent.Import.ComponentName
55+
if parentComponentName == "" {
56+
parentComponentName = childComponent.Name
6457
}
6558

66-
// Loop over package components looking for a match the componentName
67-
for _, componentToCompose := range importedPackage.Components {
68-
if componentToCompose.Name == componentName {
69-
return *prepComponentToCompose(&componentToCompose, importComponent)
59+
// Find the parent component from the imported package that matches our arch
60+
for _, importedComponent := range importedPackage.Components {
61+
if importedComponent.Name == parentComponentName {
62+
parentComponent = importedComponent
63+
break
7064
}
7165
}
7266

73-
return component
74-
}
67+
// Check if we need to get more of the parents!!!
68+
if parentComponent.Import.Path != "" {
69+
// Set a temporary composePath so we can get future parents/grandparents from our current location
70+
tempEverGrowingComposePath := filepath.Join(everGrowingComposePath, childComponent.Import.Path)
7571

76-
// Reads the locally imported zarf.yaml
77-
func getSubPackage(component *types.ZarfComponent) (importedPackage types.ZarfPackage) {
78-
utils.ReadYaml(component.Import.Path+"zarf.yaml", &importedPackage)
79-
return importedPackage
80-
}
72+
// Recursively call this function to get the next layer of parents
73+
grandparentComponent := getParentComponent(parentComponent, tempEverGrowingComposePath)
8174

82-
// Updates the name and sets all local asset paths relative to the importing component.
83-
func prepComponentToCompose(child *types.ZarfComponent, parent types.ZarfComponent) *types.ZarfComponent {
75+
// Merge the grandparents values into the parent
76+
mergeComponentOverrides(&grandparentComponent, parentComponent)
8477

85-
if child.Import.Path != "" {
86-
// The component we are trying to compose is a composed component itself!
87-
nestedComponent := getImportedComponent(*child)
88-
child = prepComponentToCompose(&nestedComponent, *child)
78+
// Set the grandparent as the parent component now that we're done with recursively importing
79+
parentComponent = grandparentComponent
8980
}
9081

82+
// Fix the filePaths of imported components to be accessible from our current location
83+
parentComponent = fixComposedFilepaths(parentComponent, childComponent)
84+
85+
return
86+
}
87+
88+
func fixComposedFilepaths(parentComponent, childComponent types.ZarfComponent) types.ZarfComponent {
9189
// Prefix composed component file paths.
92-
for fileIdx, file := range child.Files {
93-
child.Files[fileIdx].Source = getComposedFilePath(file.Source, parent.Import.Path)
90+
for fileIdx, file := range parentComponent.Files {
91+
parentComponent.Files[fileIdx].Source = getComposedFilePath(file.Source, childComponent.Import.Path)
9492
}
9593

9694
// Prefix non-url composed component chart values files.
97-
for chartIdx, chart := range child.Charts {
95+
for chartIdx, chart := range parentComponent.Charts {
9896
for valuesIdx, valuesFile := range chart.ValuesFiles {
99-
child.Charts[chartIdx].ValuesFiles[valuesIdx] = getComposedFilePath(valuesFile, parent.Import.Path)
97+
parentComponent.Charts[chartIdx].ValuesFiles[valuesIdx] = getComposedFilePath(valuesFile, childComponent.Import.Path)
10098
}
10199
}
102100

103101
// Prefix non-url composed manifest files and kustomizations.
104-
for manifestIdx, manifest := range child.Manifests {
102+
for manifestIdx, manifest := range parentComponent.Manifests {
105103
for fileIdx, file := range manifest.Files {
106-
child.Manifests[manifestIdx].Files[fileIdx] = getComposedFilePath(file, parent.Import.Path)
104+
parentComponent.Manifests[manifestIdx].Files[fileIdx] = getComposedFilePath(file, childComponent.Import.Path)
107105
}
108106
for kustomIdx, kustomization := range manifest.Kustomizations {
109-
child.Manifests[manifestIdx].Kustomizations[kustomIdx] = getComposedFilePath(kustomization, parent.Import.Path)
107+
parentComponent.Manifests[manifestIdx].Kustomizations[kustomIdx] = getComposedFilePath(kustomization, childComponent.Import.Path)
110108
}
111109
}
112110

113-
if child.CosignKeyPath != "" {
114-
child.CosignKeyPath = getComposedFilePath(child.CosignKeyPath, parent.Import.Path)
111+
if parentComponent.CosignKeyPath != "" {
112+
parentComponent.CosignKeyPath = getComposedFilePath(parentComponent.CosignKeyPath, childComponent.Import.Path)
115113
}
116114

117-
return child
115+
return parentComponent
116+
}
117+
118+
// Validates the sub component, exits program if validation fails.
119+
func validateOrBail(component *types.ZarfComponent) {
120+
if err := validate.ValidateImportPackage(component); err != nil {
121+
message.Fatalf(err, "Invalid import definition in the %s component: %s", component.Name, err)
122+
}
123+
}
124+
125+
// Sets Name, Default, Required and Description to the original components values
126+
func mergeComponentOverrides(target *types.ZarfComponent, override types.ZarfComponent) {
127+
target.Name = override.Name
128+
target.Default = override.Default
129+
target.Required = override.Required
130+
target.Group = override.Group
131+
132+
// Override description if it was provided.
133+
if override.Description != "" {
134+
target.Description = override.Description
135+
}
136+
137+
// Override cosign key path if it was provided.
138+
if override.CosignKeyPath != "" {
139+
target.CosignKeyPath = override.CosignKeyPath
140+
}
141+
142+
// Append slices where they exist.
143+
target.Charts = append(target.Charts, override.Charts...)
144+
target.DataInjections = append(target.DataInjections, override.DataInjections...)
145+
target.Files = append(target.Files, override.Files...)
146+
target.Images = append(target.Images, override.Images...)
147+
target.Manifests = append(target.Manifests, override.Manifests...)
148+
target.Repos = append(target.Repos, override.Repos...)
149+
150+
// Merge variables.
151+
for key, variable := range override.Variables {
152+
target.Variables[key] = variable
153+
}
154+
155+
// Merge scripts.
156+
target.Scripts.Before = append(target.Scripts.Before, override.Scripts.Before...)
157+
target.Scripts.After = append(target.Scripts.After, override.Scripts.After...)
158+
target.Scripts.ShowOutput = override.Scripts.ShowOutput
159+
if override.Scripts.Retry {
160+
target.Scripts.Retry = override.Scripts.Retry
161+
}
162+
163+
if override.Scripts.TimeoutSeconds > 0 {
164+
target.Scripts.TimeoutSeconds = override.Scripts.TimeoutSeconds
165+
}
166+
}
167+
168+
// Reads the locally imported zarf.yaml
169+
func getSubPackage(packagePath string) (importedPackage types.ZarfPackage, err error) {
170+
path := filepath.Join(packagePath, config.ZarfYAML)
171+
err = utils.ReadYaml(path, &importedPackage)
172+
return importedPackage, err
118173
}
119174

120175
// Prefix file path with importPath if original file path is not a url.
@@ -123,32 +178,7 @@ func getComposedFilePath(originalPath string, pathPrefix string) string {
123178
if utils.IsUrl(originalPath) {
124179
return originalPath
125180
}
126-
// Add prefix for local files.
127-
return fixRelativePathBacktracking(pathPrefix + originalPath)
128-
}
129181

130-
func fixRelativePathBacktracking(path string) string {
131-
var newPathBuilder []string
132-
var hitRealPath = false // We might need to go back several directories at the begining
133-
134-
// Turn paths like `../../this/is/a/very/../silly/../path` into `../../this/is/a/path`
135-
splitString := strings.Split(path, "/")
136-
for _, dir := range splitString {
137-
if dir == ".." {
138-
if hitRealPath {
139-
// Instead of going back a directory, just don't get here in the first place
140-
newPathBuilder = newPathBuilder[:len(newPathBuilder)-1]
141-
} else {
142-
// We are still going back directories for the first time, keep going back
143-
newPathBuilder = append(newPathBuilder, dir)
144-
}
145-
} else {
146-
// This is a regular directory we want to travel through
147-
hitRealPath = true
148-
newPathBuilder = append(newPathBuilder, dir)
149-
}
150-
}
151-
152-
// NOTE: This assumes a relative path
153-
return strings.Join(newPathBuilder, "/")
182+
// Add prefix for local files.
183+
return filepath.Join(pathPrefix, originalPath)
154184
}

src/internal/packager/create.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func Create(baseDir string) {
4040
tempPath := createPaths()
4141
defer tempPath.clean()
4242

43-
components := GetComposedComponents()
43+
components := GetComponents()
4444
seedImage := config.GetSeedImage()
4545

4646
configFile := tempPath.base + "/zarf.yaml"

src/internal/packager/prepare.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func FindImages(baseDir, repoHelmChartPath string) {
3939
message.Fatal(err, "Unable to read the zarf.yaml file")
4040
}
4141

42-
components := GetComposedComponents()
42+
components := GetComponents()
4343
tempPath := createPaths()
4444
defer tempPath.clean()
4545

src/internal/packager/validate/validate.go

-2
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,5 @@ func ValidateImportPackage(composedComponent *types.ZarfComponent) error {
145145
return fmt.Errorf("invalid file path \"%s\" provided directory must contain a valid zarf.yaml file", composedComponent.Import.Path)
146146
}
147147

148-
// replace component path with doctored path
149-
composedComponent.Import.Path = path
150148
return nil
151149
}

0 commit comments

Comments
 (0)