Skip to content

Commit 4b7168e

Browse files
authored
Feature: Add only filters for local OS and cluster architecture component filtering (zarf-dev#591)
* add Only filters for components * start describing the schema better * Add only filter to docs * add compose debug messages and handle bad component name * Add debug message to component compatibility test
1 parent 647857b commit 4b7168e

22 files changed

+360
-144
lines changed

.tool-versions

-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
golang 1.18
2-
terraform 1.1.4
32
adr-tools 3.0.0

docs/4-user-guide/2-zarf-packages/1-zarf-packages.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ sidebar_position: 1
44

55
# Understanding Zarf Packages
66

7-
A Zarf package is a single binary Tarball that contains everything you need to deploy a system or capability while fully disconnected. Zarf packages are defined by a `zarf.yaml` file.
7+
A Zarf package is a single tarball archive that contains everything you need to deploy a system or capability while fully disconnected. Zarf packages are defined by a `zarf.yaml` file.
88

9-
Zarf packages are built while 'online' and connected to whatever is hosting the dependencies your package definition defined. When being built, all these defined dependencies are pulled from the internet and stored within the tarball package. Because all the dependencies are now within the tarball, the package can be deployed on to disconnected systems that don't have connection to the outside world.
9+
Zarf packages are built while 'online' and connected to whatever is hosting the dependencies your package definition defined. When being built, all these defined dependencies are downloaded and stored within the archive. Because all the dependencies are now within the tarball, the package can be deployed on to disconnected systems that don't have connection to the outside world.
1010

1111
The `zarf.yaml` file, that the package builds from, defines declarative instructions on how capabilities of the package should be deployed. The declarative nature of the package means everything is represented by code and automatically runs as it is configured, instead of having to give manual steps that might not be reproducible on all systems.
1212

docs/4-user-guide/2-zarf-packages/2-zarf-components.md

+6
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ components:
6969
7070
required: <BOOLEAN> # If required, this component will always be deployed with the package
7171
72+
only: <OJB> # Filter components on package create / deploy to control when it is available
73+
localOS: <STRING> # Only present on package deploy if the OS matches
74+
cluster: <OBJ>
75+
architecture: <STRING> # Filter on both package deploy & create for matching cluster architecture
76+
distros: <STRING LIST> # Future use
77+
7278
secretName: <STRING> # The secret Zarf will use for the registry; default is 'zarf-registry'>
7379
# The secret lives in the 'zarf' namespace.
7480

go.mod

+1-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ replace (
1414

1515
require (
1616
github.com/AlecAivazis/survey/v2 v2.3.5
17+
github.com/Masterminds/semver/v3 v3.1.1
1718
github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b
1819
github.com/derailed/k9s v0.25.21
1920
github.com/distribution/distribution/v3 v3.0.0-20220612151901-b5e2f3f33dbc
@@ -44,8 +45,6 @@ require (
4445
sigs.k8s.io/yaml v1.3.0
4546
)
4647

47-
require github.com/Masterminds/semver/v3 v3.1.1
48-
4948
require (
5049
atomicgo.dev/cursor v0.1.1 // indirect
5150
atomicgo.dev/keyboard v0.2.8 // indirect

go.sum

-2
Original file line numberDiff line numberDiff line change
@@ -3359,8 +3359,6 @@ k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
33593359
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
33603360
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
33613361
k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
3362-
k8s.io/klog/v2 v2.70.0 h1:GMmmjoFOrNepPN0ZeGCzvD2Gh5IKRwdFx8W5PBxVTQU=
3363-
k8s.io/klog/v2 v2.70.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
33643362
k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ=
33653363
k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
33663364
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=

packages/big-bang-core/zarf.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ components:
99
- name: flux
1010
required: true
1111
import:
12-
path: ../../packages/flux-iron-bank
12+
path: ../flux-iron-bank
1313

1414
- name: big-bang-core-assets
1515
description: "Git repositories and OCI images used by Big Bang Core"
File renamed without changes.

packages/distros/k3s/common/zarf.yaml

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
kind: ZarfInitConfig
2+
metadata:
3+
name: "distro-k3s"
4+
5+
components:
6+
- name: k3s
7+
only:
8+
localOS: linux
9+
description: >
10+
*** REQUIRES ROOT ***
11+
Install K3s, certified Kubernetes distribution built for IoT & Edge computing.
12+
K3s provides the cluster need for Zarf running in Appliance Mode as well as can
13+
host a low-resource Gitops Service if not using an existing Kubernetes platform.
14+
scripts:
15+
retry: true
16+
before:
17+
# If running RHEL variant, disable firewalld
18+
# https://rancher.com/docs/k3s/latest/en/advanced/#additional-preparation-for-red-hat-centos-enterprise-linux
19+
# NOTE: The empty echo prevents infinite retry loops on non-RHEL systems where the exit code would be an error
20+
- "[ -e /etc/redhat-release ] && systemctl disable firewalld --now || echo ''"
21+
after:
22+
# Configure K3s systemd service
23+
- "systemctl daemon-reload"
24+
- "systemctl enable --now k3s"
25+
files:
26+
# K3s removal script
27+
- source: zarf-clean-k3s.sh
28+
target: /opt/zarf/zarf-clean-k3s.sh
29+
executable: true
30+
# The K3s systemd service definition
31+
- source: k3s.service
32+
target: /etc/systemd/system/k3s.service
33+
symlinks:
34+
- /etc/systemd/system/multi-user.target.wants/k3s.service
35+
# Mock file for creating the kube config symlink
36+
- source: empty-file
37+
target: /etc/rancher/k3s/k3s.yaml
38+
symlinks:
39+
- /root/.kube/config

packages/distros/k3s/zarf.yaml

+29-29
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,16 @@ kind: ZarfInitConfig
22
metadata:
33
name: "distro-k3s"
44
description: "Used to establish a new Zarf cluster"
5-
architecture: amd64
65

76
components:
7+
# AMD-64 version of the K3s stack
88
- name: k3s
9-
description: >
10-
*** REQUIRES ROOT ***
11-
Install K3s, certified Kubernetes distribution built for IoT & Edge computing.
12-
K3s provides the cluster need for Zarf running in Appliance Mode as well as can
13-
host a low-resource Gitops Service if not using an existing Kubernetes platform.
14-
scripts:
15-
retry: true
16-
before:
17-
# If running RHEL variant, disable firewalld
18-
# https://rancher.com/docs/k3s/latest/en/advanced/#additional-preparation-for-red-hat-centos-enterprise-linux
19-
# NOTE: The empty echo prevents infinite retry loops on non-RHEL systems where the exit code would be an error
20-
- "[ -e /etc/redhat-release ] && systemctl disable firewalld --now || echo ''"
21-
after:
22-
# Configure K3s systemd service
23-
- "systemctl daemon-reload"
24-
- "systemctl enable --now k3s"
9+
import:
10+
path: common
11+
name: k3s
12+
only:
13+
cluster:
14+
architecture: amd64
2515
files:
2616
# Include the actual K3s binary
2717
- source: https://github.com/k3s-io/k3s/releases/download/v1.24.1+k3s1/k3s
@@ -37,17 +27,27 @@ components:
3727
- source: https://github.com/k3s-io/k3s/releases/download/v1.24.1+k3s1/k3s-airgap-images-amd64.tar.zst
3828
shasum: 6736f9fa4d5754d60b0508bafb2f888170cb99a2d93a3a1617a919ca4ee74034
3929
target: /var/lib/rancher/k3s/agent/images/k3s.tar.zst
40-
# K3s removal script
41-
- source: assets/zarf-clean-k3s.sh
42-
target: /opt/zarf/zarf-clean-k3s.sh
30+
31+
# ARM-64 version of the K3s stack
32+
- name: k3s
33+
import:
34+
path: common
35+
name: k3s
36+
only:
37+
cluster:
38+
architecture: arm64
39+
files:
40+
# Include the actual K3s binary
41+
- source: https://github.com/k3s-io/k3s/releases/download/v1.24.1+k3s1/k3s-arm64
42+
shasum: bd8b87d215f7a073d0139a0ab70e3fbeaa76e1b9ce6c00cd8d471cb44ba80687
43+
target: /usr/sbin/k3s
4344
executable: true
44-
# The K3s systemd service definition
45-
- source: assets/k3s.service
46-
target: /etc/systemd/system/k3s.service
47-
symlinks:
48-
- /etc/systemd/system/multi-user.target.wants/k3s.service
49-
# Mock file for creating the kube config symlink
50-
- source: assets/empty-file
51-
target: /etc/rancher/k3s/k3s.yaml
45+
# K3s magic provides these tools when symlinking
5246
symlinks:
53-
- /root/.kube/config
47+
- /usr/sbin/kubectl
48+
- /usr/sbin/ctr
49+
- /usr/sbin/crictl
50+
# Transfer the K3s images for containerd to pick them up
51+
- source: https://github.com/k3s-io/k3s/releases/download/v1.24.1+k3s1/k3s-airgap-images-arm64.tar.zst
52+
shasum: 12029e4bbfecfa16942345aeac798f4790e568a7202c2b85ee068d7b4756ba04
53+
target: /var/lib/rancher/k3s/agent/images/k3s.tar.zst

src/config/config.go

+47-2
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,28 @@ func GetRegistry() string {
182182
return fmt.Sprintf("%s:%s", IPV4Localhost, state.NodePort)
183183
}
184184

185-
func LoadConfig(path string) error {
186-
return utils.ReadYaml(path, &active)
185+
// LoadConfig loads the config from the given path and removes
186+
// components not matching the current OS if filterByOS is set.
187+
func LoadConfig(path string, filterByOS bool) error {
188+
if err := utils.ReadYaml(path, &active); err != nil {
189+
return err
190+
}
191+
192+
// Filter each component to only compatible platforms
193+
filteredComponents := []types.ZarfComponent{}
194+
for _, component := range active.Components {
195+
if isCompatibleComponent(component, filterByOS) {
196+
filteredComponents = append(filteredComponents, component)
197+
}
198+
}
199+
// Update the active package with the filtered components
200+
active.Components = filteredComponents
201+
202+
return nil
203+
}
204+
205+
func GetActiveConfig() types.ZarfPackage {
206+
return active
187207
}
188208

189209
func BuildConfig(path string) error {
@@ -231,3 +251,28 @@ func GetImageCachePath() string {
231251

232252
return strings.Replace(CreateOptions.ImageCachePath, "~", homePath, 1)
233253
}
254+
255+
func isCompatibleComponent(component types.ZarfComponent, filterByOS bool) bool {
256+
message.Debugf("config.isCompatibleComponent(%s, %v)", component.Name, filterByOS)
257+
258+
// Ignore only filters that are empty
259+
var validArch, validOS bool
260+
261+
targetArch := GetArch()
262+
263+
// Test for valid architecture
264+
if component.Only.Cluster.Architecture == "" || component.Only.Cluster.Architecture == targetArch {
265+
validArch = true
266+
} else {
267+
message.Debugf("Skipping component %s, %s is not compatible with %s", component.Name, component.Only.Cluster.Architecture, targetArch)
268+
}
269+
270+
// Test for a valid OS
271+
if !filterByOS || component.Only.LocalOS == "" || component.Only.LocalOS == runtime.GOOS {
272+
validOS = true
273+
} else {
274+
message.Debugf("Skipping component %s, %s is not compatible with %s", component.Name, component.Only.LocalOS, runtime.GOOS)
275+
}
276+
277+
return validArch && validOS
278+
}

src/internal/packager/common.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"encoding/hex"
77
"fmt"
88
"io"
9-
"io/ioutil"
109
"net/http"
1110
"net/url"
1211
"os"
@@ -15,6 +14,7 @@ import (
1514

1615
"github.com/defenseunicorns/zarf/src/types"
1716
"github.com/pterm/pterm"
17+
"gopkg.in/yaml.v2"
1818

1919
"github.com/AlecAivazis/survey/v2"
2020
"github.com/defenseunicorns/zarf/src/config"
@@ -75,8 +75,10 @@ func createComponentPaths(basePath string, component types.ZarfComponent) compon
7575
}
7676
}
7777

78-
func confirmAction(configPath, userMessage string, sbomViewFiles []string) bool {
79-
content, err := ioutil.ReadFile(configPath)
78+
func confirmAction(userMessage string, sbomViewFiles []string) bool {
79+
active := config.GetActiveConfig()
80+
81+
content, err := yaml.Marshal(active)
8082
if err != nil {
8183
message.Fatal(err, "Unable to open the package config file")
8284
}

src/internal/packager/compose.go

+47-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
)
1212

1313
func GetComponents() (components []types.ZarfComponent) {
14+
message.Debugf("packager.GetComponents()")
15+
1416
for _, component := range config.GetComponents() {
1517
if component.Import.Path == "" {
1618
components = append(components, component)
@@ -27,6 +29,8 @@ func GetComponents() (components []types.ZarfComponent) {
2729
}
2830

2931
func GetComposedComponent(childComponent types.ZarfComponent) types.ZarfComponent {
32+
message.Debugf("packager.GetComposedComponent(%+v)", childComponent)
33+
3034
// Make sure the component we're trying to import cant be accessed
3135
validateOrBail(&childComponent)
3236

@@ -44,6 +48,8 @@ func GetComposedComponent(childComponent types.ZarfComponent) types.ZarfComponen
4448
}
4549

4650
func getParentComponent(childComponent types.ZarfComponent, everGrowingComposePath string) (parentComponent types.ZarfComponent) {
51+
message.Debugf("packager.getParentComponent(%+v, %s)", childComponent, everGrowingComposePath)
52+
4753
importedPackage, err := getSubPackage(filepath.Join(everGrowingComposePath, childComponent.Import.Path))
4854
if err != nil {
4955
message.Fatal(err, "Unable to get the package that we're importing a component from")
@@ -56,14 +62,30 @@ func getParentComponent(childComponent types.ZarfComponent, everGrowingComposePa
5662
parentComponentName = childComponent.Name
5763
}
5864

65+
targetArch := config.GetArch()
5966
// Find the parent component from the imported package that matches our arch
6067
for _, importedComponent := range importedPackage.Components {
6168
if importedComponent.Name == parentComponentName {
62-
parentComponent = importedComponent
63-
break
69+
filterArch := importedComponent.Only.Cluster.Architecture
70+
71+
// Override the filter if it is set by the child component
72+
if childComponent.Only.Cluster.Architecture != "" {
73+
filterArch = childComponent.Only.Cluster.Architecture
74+
}
75+
76+
// Only add this component if it is valid for the target architecture.
77+
if filterArch == "" || filterArch == targetArch {
78+
parentComponent = importedComponent
79+
break
80+
}
6481
}
6582
}
6683

84+
// If we didn't find a parent component, bail
85+
if parentComponent.Name == "" {
86+
message.Fatalf(nil, "Unable to find the component %s in the imported package", parentComponentName)
87+
}
88+
6789
// Check if we need to get more of the parents!!!
6890
if parentComponent.Import.Path != "" {
6991
// Set a temporary composePath so we can get future parents/grandparents from our current location
@@ -86,6 +108,8 @@ func getParentComponent(childComponent types.ZarfComponent, everGrowingComposePa
86108
}
87109

88110
func fixComposedFilepaths(parentComponent, childComponent types.ZarfComponent) types.ZarfComponent {
111+
message.Debugf("packager.fixComposedFilepaths(%+v, %+v)", parentComponent, childComponent)
112+
89113
// Prefix composed component file paths.
90114
for fileIdx, file := range parentComponent.Files {
91115
parentComponent.Files[fileIdx].Source = getComposedFilePath(file.Source, childComponent.Import.Path)
@@ -117,13 +141,17 @@ func fixComposedFilepaths(parentComponent, childComponent types.ZarfComponent) t
117141

118142
// Validates the sub component, exits program if validation fails.
119143
func validateOrBail(component *types.ZarfComponent) {
144+
message.Debugf("packager.validateOrBail(%+v)", component)
145+
120146
if err := validate.ValidateImportPackage(component); err != nil {
121147
message.Fatalf(err, "Invalid import definition in the %s component: %s", component.Name, err)
122148
}
123149
}
124150

125151
// Sets Name, Default, Required and Description to the original components values
126152
func mergeComponentOverrides(target *types.ZarfComponent, override types.ZarfComponent) {
153+
message.Debugf("packager.mergeComponentOverrides(%+v, %+v)", target, override)
154+
127155
target.Name = override.Name
128156
target.Default = override.Default
129157
target.Required = override.Required
@@ -157,23 +185,38 @@ func mergeComponentOverrides(target *types.ZarfComponent, override types.ZarfCom
157185
target.Scripts.After = append(target.Scripts.After, override.Scripts.After...)
158186
target.Scripts.ShowOutput = override.Scripts.ShowOutput
159187
if override.Scripts.Retry {
160-
target.Scripts.Retry = override.Scripts.Retry
188+
target.Scripts.Retry = true
189+
}
190+
if override.Scripts.ShowOutput {
191+
target.Scripts.ShowOutput = true
161192
}
162-
163193
if override.Scripts.TimeoutSeconds > 0 {
164194
target.Scripts.TimeoutSeconds = override.Scripts.TimeoutSeconds
165195
}
196+
197+
// Merge Only filters
198+
target.Only.Cluster.Distros = append(target.Only.Cluster.Distros, override.Only.Cluster.Distros...)
199+
if override.Only.Cluster.Architecture != "" {
200+
target.Only.Cluster.Architecture = override.Only.Cluster.Architecture
201+
}
202+
if override.Only.LocalOS != "" {
203+
target.Only.LocalOS = override.Only.LocalOS
204+
}
166205
}
167206

168207
// Reads the locally imported zarf.yaml
169208
func getSubPackage(packagePath string) (importedPackage types.ZarfPackage, err error) {
209+
message.Debugf("packager.getSubPackage(%s)", packagePath)
210+
170211
path := filepath.Join(packagePath, config.ZarfYAML)
171212
err = utils.ReadYaml(path, &importedPackage)
172213
return importedPackage, err
173214
}
174215

175216
// Prefix file path with importPath if original file path is not a url.
176217
func getComposedFilePath(originalPath string, pathPrefix string) string {
218+
message.Debugf("packager.getComposedFilePath(%s, %s)", originalPath, pathPrefix)
219+
177220
// Return original if it is a remote file.
178221
if utils.IsUrl(originalPath) {
179222
return originalPath

0 commit comments

Comments
 (0)