From c0ea98d319f55558916049a8586a17aa379bc038 Mon Sep 17 00:00:00 2001 From: Collin Pesicka Date: Thu, 9 May 2024 12:53:37 -0400 Subject: [PATCH 01/14] feat(inspect): Added --list-images option and YAML bundle reading capability --- src/cmd/uds.go | 5 +- src/pkg/bundle/inspect.go | 133 ++++++++++++++++++++++++++++++++++ src/pkg/bundle/local.go | 26 +++++++ src/test/e2e/commands_test.go | 13 ++++ src/types/bundle.go | 1 + src/types/options.go | 1 + 6 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 src/pkg/bundle/local.go diff --git a/src/cmd/uds.go b/src/cmd/uds.go index 9474247f..644f65d6 100644 --- a/src/cmd/uds.go +++ b/src/cmd/uds.go @@ -69,7 +69,7 @@ var deployCmd = &cobra.Command{ } var inspectCmd = &cobra.Command{ - Use: "inspect [BUNDLE_TARBALL|OCI_REF]", + Use: "inspect [BUNDLE_TARBALL|OCI_REF|BUNDLE_YAML_FILE]", Aliases: []string{"i"}, Short: lang.CmdBundleInspectShort, Args: cobra.MaximumNArgs(1), @@ -203,6 +203,7 @@ func init() { inspectCmd.Flags().BoolVarP(&bundleCfg.InspectOpts.IncludeSBOM, "sbom", "s", false, lang.CmdPackageInspectFlagSBOM) inspectCmd.Flags().BoolVarP(&bundleCfg.InspectOpts.ExtractSBOM, "extract", "e", false, lang.CmdPackageInspectFlagExtractSBOM) inspectCmd.Flags().StringVarP(&bundleCfg.InspectOpts.PublicKeyPath, "key", "k", v.GetString(V_BNDL_INSPECT_KEY), lang.CmdBundleInspectFlagKey) + inspectCmd.Flags().BoolVarP(&bundleCfg.InspectOpts.ExtractImages, "list-images", "i", false, "Recursively go through all of the sbom's and get the images") // remove cmd flags rootCmd.AddCommand(removeCmd) @@ -235,9 +236,11 @@ func chooseBundle(args []string) string { files, _ := filepath.Glob(config.BundlePrefix + toComplete + "*.tar") gzFiles, _ := filepath.Glob(config.BundlePrefix + toComplete + "*.tar.zst") partialFiles, _ := filepath.Glob(config.BundlePrefix + toComplete + "*.part000") + yamlFiles, _ := filepath.Glob(config.BundlePrefix + toComplete + "*.yaml") // Add this line files = append(files, gzFiles...) files = append(files, partialFiles...) + files = append(files, yamlFiles...) return files }, } diff --git a/src/pkg/bundle/inspect.go b/src/pkg/bundle/inspect.go index d60592ce..749410f9 100644 --- a/src/pkg/bundle/inspect.go +++ b/src/pkg/bundle/inspect.go @@ -5,13 +5,89 @@ package bundle import ( + "bytes" + "os" + "os/exec" + "path/filepath" + "strings" + "github.com/defenseunicorns/uds-cli/src/config" "github.com/defenseunicorns/uds-cli/src/pkg/utils" zarfUtils "github.com/defenseunicorns/zarf/src/pkg/utils" + "github.com/defenseunicorns/zarf/src/pkg/message" + "github.com/defenseunicorns/zarf/src/pkg/utils" + "gopkg.in/yaml.v2" ) +func (b *Bundle) extractImagesFromPackages() { + for i, pkg := range b.bundle.Packages { + if pkg.Repository != "" && pkg.Ref != "" { + message.Debugf("Package:", pkg.Name, "Repository:", pkg.Repository, "Ref:", pkg.Ref) + type Component struct { + Images []string `yaml:"images"` + } + + type Output struct { + Components []Component `yaml:"components"` + } + + cmd := exec.Command("uds", "zarf", "package", "inspect", "oci://"+pkg.Repository+":"+pkg.Ref, "--no-color", "--no-log-file") + + var output bytes.Buffer + cmd.Stdout = &output + cmd.Stderr = &output + + err := cmd.Run() // Use Run instead of CombinedOutput + if err != nil { + message.Fatalf("Error executing command: %s, output: %s", err.Error(), output.String()) + continue + } + outputStr := output.String() + + // Find the index of "kind:" + idx := strings.Index(outputStr, "kind:") + if idx == -1 { + message.Fatalf("Invalid output: %s", outputStr) + continue + } + + // Trim the output + trimmedOutput := outputStr[idx:] + + message.Debugf("Trimmed Output: %s", trimmedOutput) + + var result interface{} + err = yaml.Unmarshal([]byte(trimmedOutput), &result) + if err != nil { + message.Fatalf("Error unmarshaling YAML: %s", err.Error()) + continue + } + + var allImages []string + findImages(result, &allImages) + + // Add the images to the package + if len(allImages) > 0 { + message.Debugf("Images: %v", allImages) + b.bundle.Packages[i].Images = allImages + } else { + message.Debugf("No images found in package %v", pkg.Name) + } + } + } +} + // Inspect pulls/unpacks a bundle's metadata and shows it func (b *Bundle) Inspect() error { + // Check if the source is a YAML file + if filepath.Ext(b.cfg.InspectOpts.Source) == ".yaml" { + source, err := CheckYAMLSourcePath(b.cfg.InspectOpts.Source) + if err != nil { + return err + } + b.cfg.InspectOpts.Source = source + return b.InspectYAML(b.cfg.InspectOpts.Source) + } // Check that provided oci source path is valid, and update it if it's missing the full path source, err := CheckOCISourcePath(b.cfg.InspectOpts.Source) @@ -48,6 +124,13 @@ func (b *Bundle) Inspect() error { if err := utils.ReadYAMLStrict(loaded[config.BundleYAML], &b.bundle); err != nil { return err } + // If ExtractImages flag is set, extract images from each package + if b.cfg.InspectOpts.ExtractImages { + message.Debugf("Extracting images from packages") + b.extractImagesFromPackages() + } else { + message.Debugf("Skipping image extraction") + } // show the bundle's metadata zarfUtils.ColorPrintYAML(b.bundle, nil, false) @@ -56,3 +139,53 @@ func (b *Bundle) Inspect() error { // TODO: could be cool to have an interactive mode that lets you select a package and show its metadata return nil } + +// InspectYAML inspects a bundle from a YAML file +func (b *Bundle) InspectYAML(yamlPath string) error { + message.Debugf("Reading the yaml file: %s", yamlPath) + // Read the YAML file + data, err := os.ReadFile(yamlPath) + if err != nil { + return err + } + + // Unmarshal the YAML data into the Bundle struct + err = yaml.Unmarshal(data, &b.bundle) + if err != nil { + return err + } + + // If ExtractImages flag is set, extract images from each package + if b.cfg.InspectOpts.ExtractImages { + b.extractImagesFromPackages() + } + + // show the bundle's metadata + utils.ColorPrintYAML(b.bundle, nil, false) + return nil +} + +func findImages(node interface{}, images *[]string) { + switch node := node.(type) { + case map[interface{}]interface{}: + for k, v := range node { + if k == "images" { + // Check if v is a slice of interfaces + if imgSlice, ok := v.([]interface{}); ok { + // Convert each element to a string and append it to images + for _, img := range imgSlice { + if imgStr, ok := img.(string); ok { + *images = append(*images, imgStr) + } + } + } + } else { + findImages(v, images) + } + } + case []interface{}: + for _, v := range node { + findImages(v, images) + } + } +} diff --git a/src/pkg/bundle/local.go b/src/pkg/bundle/local.go new file mode 100644 index 00000000..d1aa977c --- /dev/null +++ b/src/pkg/bundle/local.go @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023-Present The UDS Authors + +// Package bundle contains functions for interacting with, managing and deploying UDS packages +package bundle + +import ( + "fmt" + "os" + "path/filepath" +) + +// CheckYAMLSourcePath checks if the provided YAML source path is valid +func CheckYAMLSourcePath(source string) (string, error) { + // Check if the file exists + if _, err := os.Stat(source); os.IsNotExist(err) { + return "", fmt.Errorf("file does not exist: %s", source) + } + + // Check if the file has a .yaml extension + if filepath.Ext(source) != ".yaml" { + return "", fmt.Errorf("file is not a YAML file: %s", source) + } + + return source, nil +} diff --git a/src/test/e2e/commands_test.go b/src/test/e2e/commands_test.go index cda25a57..250b0388 100644 --- a/src/test/e2e/commands_test.go +++ b/src/test/e2e/commands_test.go @@ -16,6 +16,9 @@ import ( "testing" "github.com/defenseunicorns/pkg/helpers/v2" + "gopkg.in/yaml.v2" + + "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/uds-cli/src/config" "github.com/defenseunicorns/zarf/src/pkg/message" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -111,6 +114,16 @@ func inspectLocalAndSBOMExtract(t *testing.T, tarballPath string) { require.NoError(t, err) } +func inspectBundleYAML(t *testing.T, yamlPath string) { + cmd := strings.Split(fmt.Sprintf("inspect %s", yamlPath), " ") + _, _, err := e2e.UDS(cmd...) + data, err := os.ReadFile(yamlPath) + require.NoError(t, err) + var bundleData map[string]interface{} + err = yaml.Unmarshal(data, &bundleData) + require.NoError(t, err) +} + func deploy(t *testing.T, tarballPath string) (stdout string, stderr string) { cmd := strings.Split(fmt.Sprintf("deploy %s --retries 1 --confirm", tarballPath), " ") stdout, stderr, err := e2e.UDS(cmd...) diff --git a/src/types/bundle.go b/src/types/bundle.go index a64496b4..358ba78e 100644 --- a/src/types/bundle.go +++ b/src/types/bundle.go @@ -32,6 +32,7 @@ type UDSBundle struct { type Package struct { Name string `json:"name" jsonschema:"name=Name of the Zarf package"` Description string `json:"description,omitempty" jsonschema:"description=Description of the Zarf package"` + Images []string `json:"images,omitempty" jsonschema:"description=List of images included in the Zarf package"` Repository string `json:"repository,omitempty" jsonschema:"description=The repository to import the package from"` Path string `json:"path,omitempty" jsonschema:"description=The local path to import the package from"` Ref string `json:"ref" jsonschema:"description=Ref (tag) of the Zarf package"` diff --git a/src/types/options.go b/src/types/options.go index a88dd22e..62ff5c13 100644 --- a/src/types/options.go +++ b/src/types/options.go @@ -44,6 +44,7 @@ type BundleInspectOptions struct { Source string IncludeSBOM bool ExtractSBOM bool + ExtractImages bool } // BundlePublishOptions is the options for the bundle.Publish() function From f51cd4bbaa5ddc9e08aae74dc24b1bf757fc7cae Mon Sep 17 00:00:00 2001 From: Collin Pesicka Date: Fri, 10 May 2024 07:46:46 -0400 Subject: [PATCH 02/14] chore(inspect.go): fix debugf style error --- src/pkg/bundle/inspect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pkg/bundle/inspect.go b/src/pkg/bundle/inspect.go index 749410f9..bce340f2 100644 --- a/src/pkg/bundle/inspect.go +++ b/src/pkg/bundle/inspect.go @@ -22,7 +22,7 @@ import ( func (b *Bundle) extractImagesFromPackages() { for i, pkg := range b.bundle.Packages { if pkg.Repository != "" && pkg.Ref != "" { - message.Debugf("Package:", pkg.Name, "Repository:", pkg.Repository, "Ref:", pkg.Ref) + message.Debugf("Package: %s, Repository: %s, Ref: %s", pkg.Name, pkg.Repository, pkg.Ref) type Component struct { Images []string `yaml:"images"` } From 0cdab7d490decf72cf01010c7924c216842c80ca Mon Sep 17 00:00:00 2001 From: Collin Pesicka Date: Fri, 10 May 2024 07:51:21 -0400 Subject: [PATCH 03/14] chore(schema): update --- uds.schema.json | 7 + zarf.schema.json | 540 +++++++++++++++++++++++++---------------------- 2 files changed, 290 insertions(+), 257 deletions(-) diff --git a/uds.schema.json b/uds.schema.json index 96c03734..8ca65ba7 100644 --- a/uds.schema.json +++ b/uds.schema.json @@ -132,6 +132,13 @@ "type": "string", "description": "Description of the Zarf package" }, + "images": { + "items": { + "type": "string" + }, + "type": "array", + "description": "List of images included in the Zarf package" + }, "repository": { "type": "string", "description": "The repository to import the package from" diff --git a/zarf.schema.json b/zarf.schema.json index 27a8a89a..69f4aae9 100644 --- a/zarf.schema.json +++ b/zarf.schema.json @@ -1,8 +1,11 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/defenseunicorns/zarf/src/types/zarf-package", - "$defs": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfPackage", + "definitions": { "BigBang": { + "required": [ + "version" + ], "properties": { "version": { "type": "string", @@ -33,43 +36,6 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "version" - ], - "patternProperties": { - "^x-": {} - } - }, - "Constant": { - "properties": { - "name": { - "type": "string", - "pattern": "^[A-Z0-9_]+$", - "description": "The name to be used for the constant" - }, - "value": { - "type": "string", - "description": "The value to set for the constant during deploy" - }, - "description": { - "type": "string", - "description": "A description of the constant to explain its purpose on package create or deploy confirmation prompts" - }, - "autoIndent": { - "type": "boolean", - "description": "Whether to automatically indent the variable's value (if multiline) when templating. Based on the number of chars before the start of ###ZARF_CONST_." - }, - "pattern": { - "type": "string", - "description": "An optional regex pattern that a constant value must match before a package can be created." - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "name", - "value" - ], "patternProperties": { "^x-": {} } @@ -116,55 +82,6 @@ "^x-": {} } }, - "InteractiveVariable": { - "properties": { - "name": { - "type": "string", - "pattern": "^[A-Z0-9_]+$", - "description": "The name to be used for the variable" - }, - "sensitive": { - "type": "boolean", - "description": "Whether to mark this variable as sensitive to not print it in the log" - }, - "autoIndent": { - "type": "boolean", - "description": "Whether to automatically indent the variable's value (if multiline) when templating. Based on the number of chars before the start of ###ZARF_VAR_." - }, - "pattern": { - "type": "string", - "description": "An optional regex pattern that a variable value must match before a package deployment can continue." - }, - "type": { - "type": "string", - "enum": [ - "raw", - "file" - ], - "description": "Changes the handling of a variable to load contents differently (i.e. from a file rather than as a raw variable - templated files should be kept below 1 MiB)" - }, - "description": { - "type": "string", - "description": "A description of the variable to be used when prompting the user a value" - }, - "default": { - "type": "string", - "description": "The default value to use for the variable" - }, - "prompt": { - "type": "boolean", - "description": "Whether to prompt the user for input for this variable" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "name" - ], - "patternProperties": { - "^x-": {} - } - }, "Shell": { "properties": { "windows": { @@ -208,44 +125,14 @@ "^x-": {} } }, - "Variable": { - "properties": { - "name": { - "type": "string", - "pattern": "^[A-Z0-9_]+$", - "description": "The name to be used for the variable" - }, - "sensitive": { - "type": "boolean", - "description": "Whether to mark this variable as sensitive to not print it in the log" - }, - "autoIndent": { - "type": "boolean", - "description": "Whether to automatically indent the variable's value (if multiline) when templating. Based on the number of chars before the start of ###ZARF_VAR_." - }, - "pattern": { - "type": "string", - "description": "An optional regex pattern that a variable value must match before a package deployment can continue." - }, - "type": { - "type": "string", - "enum": [ - "raw", - "file" - ], - "description": "Changes the handling of a variable to load contents differently (i.e. from a file rather than as a raw variable - templated files should be kept below 1 MiB)" - } - }, - "additionalProperties": false, - "type": "object", + "ZarfBuildData": { "required": [ - "name" + "terminal", + "user", + "architecture", + "timestamp", + "version" ], - "patternProperties": { - "^x-": {} - } - }, - "ZarfBuildData": { "properties": { "terminal": { "type": "string", @@ -275,8 +162,10 @@ "description": "Any migrations that have been run on this package" }, "registryOverrides": { - "additionalProperties": { - "type": "string" + "patternProperties": { + ".*": { + "type": "string" + } }, "type": "object", "description": "Any registry domains that were overridden on package create when pulling images" @@ -307,18 +196,15 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "terminal", - "user", - "architecture", - "timestamp", - "version" - ], "patternProperties": { "^x-": {} } }, "ZarfChart": { + "required": [ + "name", + "namespace" + ], "properties": { "name": { "type": "string", @@ -373,7 +259,8 @@ }, "variables": { "items": { - "$ref": "#/$defs/ZarfChartVariable" + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfChartVariable" }, "type": "array", "description": "[alpha] List of variables to set in the Helm chart" @@ -381,19 +268,20 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "name", - "namespace" - ], "patternProperties": { "^x-": {} } }, "ZarfChartVariable": { + "required": [ + "name", + "description", + "path" + ], "properties": { "name": { - "type": "string", "pattern": "^[A-Z0-9_]+$", + "type": "string", "description": "The name of the variable" }, "description": { @@ -407,20 +295,18 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "name", - "description", - "path" - ], "patternProperties": { "^x-": {} } }, "ZarfComponent": { + "required": [ + "name" + ], "properties": { "name": { - "type": "string", "pattern": "^[a-z0-9\\-]*[a-z0-9]$", + "type": "string", "description": "The name of the component" }, "description": { @@ -436,7 +322,8 @@ "description": "Do not prompt user to install this component" }, "only": { - "$ref": "#/$defs/ZarfComponentOnlyTarget", + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfComponentOnlyTarget", "description": "Filter when this component is included in package creation or deployment" }, "group": { @@ -448,33 +335,38 @@ "description": "[Deprecated] Specify a path to a public key to validate signed online resources. This will be removed in Zarf v1.0.0." }, "import": { - "$ref": "#/$defs/ZarfComponentImport", + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfComponentImport", "description": "Import a component from another Zarf package" }, "manifests": { "items": { - "$ref": "#/$defs/ZarfManifest" + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfManifest" }, "type": "array", "description": "Kubernetes manifests to be included in a generated Helm chart on package deploy" }, "charts": { "items": { - "$ref": "#/$defs/ZarfChart" + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfChart" }, "type": "array", "description": "Helm charts to install during package deploy" }, "dataInjections": { "items": { - "$ref": "#/$defs/ZarfDataInjection" + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfDataInjection" }, "type": "array", "description": "Datasets to inject into a container in the target cluster" }, "files": { "items": { - "$ref": "#/$defs/ZarfFile" + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfFile" }, "type": "array", "description": "Files or folders to place on disk during package deployment" @@ -494,23 +386,23 @@ "description": "List of git repos to include in the package" }, "extensions": { - "$ref": "#/$defs/ZarfComponentExtensions", + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfComponentExtensions", "description": "Extend component functionality with additional features" }, "scripts": { - "$ref": "#/$defs/DeprecatedZarfComponentScripts", + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/DeprecatedZarfComponentScripts", "description": "[Deprecated] (replaced by actions) Custom commands to run before or after package deployment. This will be removed in Zarf v1.0.0." }, "actions": { - "$ref": "#/$defs/ZarfComponentActions", + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfComponentActions", "description": "Custom commands to run at various stages of a package lifecycle" } }, "additionalProperties": false, "type": "object", - "required": [ - "name" - ], "patternProperties": { "^x-": {} } @@ -545,17 +437,18 @@ "description": "The command to run. Must specify either cmd or wait for the action to do anything." }, "shell": { - "$ref": "#/$defs/Shell", + "$ref": "#/definitions/Shell", "description": "(cmd only) Indicates a preference for a shell for the provided cmd to be executed in on supported operating systems" }, "setVariable": { - "type": "string", "pattern": "^[A-Z0-9_]+$", + "type": "string", "description": "[Deprecated] (replaced by setVariables) (onDeploy/cmd only) The name of a variable to update with the output of the command. This variable will be available to all remaining actions and components in the package. This will be removed in Zarf v1.0.0" }, "setVariables": { "items": { - "$ref": "#/$defs/Variable" + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfComponentActionSetVariable" }, "type": "array", "description": "(onDeploy/cmd only) An array of variables to update with the output of the command. These variables will be available to all remaining actions and components in the package." @@ -565,7 +458,8 @@ "description": "Description of the action to be displayed during package execution instead of the command" }, "wait": { - "$ref": "#/$defs/ZarfComponentActionWait", + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfComponentActionWait", "description": "Wait for a condition to be met before continuing. Must specify either cmd or wait for the action. See the 'zarf tools wait-for' command for more info." } }, @@ -601,7 +495,8 @@ "description": "Additional environment variables for commands" }, "shell": { - "$ref": "#/$defs/Shell", + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Shell", "description": "(cmd only) Indicates a preference for a shell for the provided cmd to be executed in on supported operating systems" } }, @@ -614,33 +509,35 @@ "ZarfComponentActionSet": { "properties": { "defaults": { - "$ref": "#/$defs/ZarfComponentActionDefaults", + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfComponentActionDefaults", "description": "Default configuration for all actions in this set" }, "before": { "items": { - "$ref": "#/$defs/ZarfComponentAction" + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfComponentAction" }, "type": "array", "description": "Actions to run at the start of an operation" }, "after": { "items": { - "$ref": "#/$defs/ZarfComponentAction" + "$ref": "#/definitions/ZarfComponentAction" }, "type": "array", "description": "Actions to run at the end of an operation" }, "onSuccess": { "items": { - "$ref": "#/$defs/ZarfComponentAction" + "$ref": "#/definitions/ZarfComponentAction" }, "type": "array", "description": "Actions to run if all operations succeed" }, "onFailure": { "items": { - "$ref": "#/$defs/ZarfComponentAction" + "$ref": "#/definitions/ZarfComponentAction" }, "type": "array", "description": "Actions to run if all operations fail" @@ -652,14 +549,53 @@ "^x-": {} } }, + "ZarfComponentActionSetVariable": { + "required": [ + "name" + ], + "properties": { + "name": { + "pattern": "^[A-Z0-9_]+$", + "type": "string", + "description": "The name to be used for the variable" + }, + "type": { + "enum": [ + "raw", + "file" + ], + "type": "string", + "description": "Changes the handling of a variable to load contents differently (i.e. from a file rather than as a raw variable - templated files should be kept below 1 MiB)" + }, + "pattern": { + "type": "string", + "description": "An optional regex pattern that a variable value must match before a package deployment can continue." + }, + "sensitive": { + "type": "boolean", + "description": "Whether to mark this variable as sensitive to not print it in the Zarf log" + }, + "autoIndent": { + "type": "boolean", + "description": "Whether to automatically indent the variable's value (if multiline) when templating. Based on the number of chars before the start of ###ZARF_VAR_." + } + }, + "additionalProperties": false, + "type": "object", + "patternProperties": { + "^x-": {} + } + }, "ZarfComponentActionWait": { "properties": { "cluster": { - "$ref": "#/$defs/ZarfComponentActionWaitCluster", + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfComponentActionWaitCluster", "description": "Wait for a condition to be met in the cluster before continuing. Only one of cluster or network can be specified." }, "network": { - "$ref": "#/$defs/ZarfComponentActionWaitNetwork", + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfComponentActionWaitNetwork", "description": "Wait for a condition to be met on the network before continuing. Only one of cluster or network can be specified." } }, @@ -670,6 +606,10 @@ } }, "ZarfComponentActionWaitCluster": { + "required": [ + "kind", + "name" + ], "properties": { "kind": { "type": "string", @@ -702,23 +642,23 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "kind", - "name" - ], "patternProperties": { "^x-": {} } }, "ZarfComponentActionWaitNetwork": { + "required": [ + "protocol", + "address" + ], "properties": { "protocol": { - "type": "string", "enum": [ "tcp", "http", "https" ], + "type": "string", "description": "The protocol to wait for" }, "address": { @@ -740,10 +680,6 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "protocol", - "address" - ], "patternProperties": { "^x-": {} } @@ -751,15 +687,16 @@ "ZarfComponentActions": { "properties": { "onCreate": { - "$ref": "#/$defs/ZarfComponentActionSet", + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfComponentActionSet", "description": "Actions to run during package creation" }, "onDeploy": { - "$ref": "#/$defs/ZarfComponentActionSet", + "$ref": "#/definitions/ZarfComponentActionSet", "description": "Actions to run during package deployment" }, "onRemove": { - "$ref": "#/$defs/ZarfComponentActionSet", + "$ref": "#/definitions/ZarfComponentActionSet", "description": "Actions to run during package removal" } }, @@ -772,7 +709,8 @@ "ZarfComponentExtensions": { "properties": { "bigbang": { - "$ref": "#/$defs/BigBang", + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/BigBang", "description": "Configurations for installing Big Bang and Flux in the cluster" } }, @@ -793,8 +731,8 @@ "description": "The relative path to a directory containing a zarf.yaml to import from" }, "url": { - "type": "string", "pattern": "^oci://.*$", + "type": "string", "description": "[beta] The URL to a Zarf package to import via OCI" } }, @@ -807,20 +745,16 @@ "ZarfComponentOnlyCluster": { "properties": { "architecture": { - "type": "string", "enum": [ "amd64", "arm64" ], + "type": "string", "description": "Only create and deploy to clusters of the given architecture" }, "distros": { "items": { - "type": "string", - "examples": [ - "k3s", - "eks" - ] + "type": "string" }, "type": "array", "description": "A list of kubernetes distros this package works with (Reserved for future use)" @@ -835,16 +769,17 @@ "ZarfComponentOnlyTarget": { "properties": { "localOS": { - "type": "string", "enum": [ "linux", "darwin", "windows" ], + "type": "string", "description": "Only deploy component to specified OS" }, "cluster": { - "$ref": "#/$defs/ZarfComponentOnlyCluster", + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfComponentOnlyCluster", "description": "Only deploy component to specified clusters" }, "flavor": { @@ -859,6 +794,12 @@ } }, "ZarfContainerTarget": { + "required": [ + "namespace", + "selector", + "container", + "path" + ], "properties": { "namespace": { "type": "string", @@ -882,24 +823,23 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "namespace", - "selector", - "container", - "path" - ], "patternProperties": { "^x-": {} } }, "ZarfDataInjection": { + "required": [ + "source", + "target" + ], "properties": { "source": { "type": "string", "description": "Either a path to a local folder/file or a remote URL of a file to inject into the given target pod + container" }, "target": { - "$ref": "#/$defs/ZarfContainerTarget", + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfContainerTarget", "description": "The target pod + container to inject the data into" }, "compress": { @@ -909,15 +849,15 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "source", - "target" - ], "patternProperties": { "^x-": {} } }, "ZarfFile": { + "required": [ + "source", + "target" + ], "properties": { "source": { "type": "string", @@ -949,15 +889,14 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "source", - "target" - ], "patternProperties": { "^x-": {} } }, "ZarfManifest": { + "required": [ + "name" + ], "properties": { "name": { "type": "string", @@ -992,18 +931,18 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "name" - ], "patternProperties": { "^x-": {} } }, "ZarfMetadata": { + "required": [ + "name" + ], "properties": { "name": { - "type": "string", "pattern": "^[a-z0-9\\-]*[a-z0-9]$", + "type": "string", "description": "Name to identify this Zarf package" }, "description": { @@ -1064,61 +1003,148 @@ }, "additionalProperties": false, "type": "object", - "required": [ - "name" - ], "patternProperties": { "^x-": {} } - } - }, - "properties": { - "kind": { - "type": "string", - "enum": [ - "ZarfInitConfig", - "ZarfPackageConfig" - ], - "description": "The kind of Zarf package", - "default": "ZarfPackageConfig" - }, - "metadata": { - "$ref": "#/$defs/ZarfMetadata", - "description": "Package metadata" - }, - "build": { - "$ref": "#/$defs/ZarfBuildData", - "description": "Zarf-generated package build data" }, - "components": { - "items": { - "$ref": "#/$defs/ZarfComponent" + "ZarfPackage": { + "required": [ + "kind", + "components" + ], + "properties": { + "kind": { + "enum": [ + "ZarfInitConfig", + "ZarfPackageConfig" + ], + "type": "string", + "description": "The kind of Zarf package", + "default": "ZarfPackageConfig" + }, + "metadata": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfMetadata", + "description": "Package metadata" + }, + "build": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfBuildData", + "description": "Zarf-generated package build data" + }, + "components": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfComponent" + }, + "type": "array", + "description": "List of components to deploy in this package" + }, + "constants": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfPackageConstant" + }, + "type": "array", + "description": "Constant template values applied on deploy for K8s resources" + }, + "variables": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ZarfPackageVariable" + }, + "type": "array", + "description": "Variable template values applied on deploy for K8s resources" + } }, - "type": "array", - "description": "List of components to deploy in this package" + "additionalProperties": false, + "type": "object", + "patternProperties": { + "^x-": {} + } }, - "constants": { - "items": { - "$ref": "#/$defs/Constant" + "ZarfPackageConstant": { + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "pattern": "^[A-Z0-9_]+$", + "type": "string", + "description": "The name to be used for the constant" + }, + "value": { + "type": "string", + "description": "The value to set for the constant during deploy" + }, + "description": { + "type": "string", + "description": "A description of the constant to explain its purpose on package create or deploy confirmation prompts" + }, + "autoIndent": { + "type": "boolean", + "description": "Whether to automatically indent the variable's value (if multiline) when templating. Based on the number of chars before the start of ###ZARF_CONST_." + }, + "pattern": { + "type": "string", + "description": "An optional regex pattern that a constant value must match before a package can be created." + } }, - "type": "array", - "description": "Constant template values applied on deploy for K8s resources" + "additionalProperties": false, + "type": "object", + "patternProperties": { + "^x-": {} + } }, - "variables": { - "items": { - "$ref": "#/$defs/InteractiveVariable" + "ZarfPackageVariable": { + "required": [ + "name" + ], + "properties": { + "name": { + "pattern": "^[A-Z0-9_]+$", + "type": "string", + "description": "The name to be used for the variable" + }, + "description": { + "type": "string", + "description": "A description of the variable to be used when prompting the user a value" + }, + "default": { + "type": "string", + "description": "The default value to use for the variable" + }, + "prompt": { + "type": "boolean", + "description": "Whether to prompt the user for input for this variable" + }, + "sensitive": { + "type": "boolean", + "description": "Whether to mark this variable as sensitive to not print it in the Zarf log" + }, + "autoIndent": { + "type": "boolean", + "description": "Whether to automatically indent the variable's value (if multiline) when templating. Based on the number of chars before the start of ###ZARF_VAR_." + }, + "pattern": { + "type": "string", + "description": "An optional regex pattern that a variable value must match before a package can be deployed." + }, + "type": { + "enum": [ + "raw", + "file" + ], + "type": "string", + "description": "Changes the handling of a variable to load contents differently (i.e. from a file rather than as a raw variable - templated files should be kept below 1 MiB)" + } }, - "type": "array", - "description": "Variable template values applied on deploy for K8s resources" + "additionalProperties": false, + "type": "object", + "patternProperties": { + "^x-": {} + } } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "kind", - "components" - ], - "patternProperties": { - "^x-": {} } } From c04cc5538b876e4a82812bf5f8f4c34dffd57c77 Mon Sep 17 00:00:00 2001 From: unclegedd Date: Tue, 4 Jun 2024 15:27:43 -0500 Subject: [PATCH 04/14] WIP: refactoring approach, still need local pkgs --- .pre-commit-config.yaml | 1 - src/cmd/uds.go | 2 +- src/config/lang/lang.go | 1 + src/pkg/bundle/inspect.go | 184 +++++++----------- .../14-optional-components/uds-bundle.yaml | 24 +-- src/test/e2e/commands_test.go | 13 -- src/types/options.go | 2 +- 7 files changed, 86 insertions(+), 141 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 669f4639..60617141 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,6 @@ repos: hooks: - id: golangci-lint-full args: [--timeout=5m] - linters: - repo: local hooks: - id: check-docs-and-schema diff --git a/src/cmd/uds.go b/src/cmd/uds.go index 644f65d6..1e0f86e4 100644 --- a/src/cmd/uds.go +++ b/src/cmd/uds.go @@ -203,7 +203,7 @@ func init() { inspectCmd.Flags().BoolVarP(&bundleCfg.InspectOpts.IncludeSBOM, "sbom", "s", false, lang.CmdPackageInspectFlagSBOM) inspectCmd.Flags().BoolVarP(&bundleCfg.InspectOpts.ExtractSBOM, "extract", "e", false, lang.CmdPackageInspectFlagExtractSBOM) inspectCmd.Flags().StringVarP(&bundleCfg.InspectOpts.PublicKeyPath, "key", "k", v.GetString(V_BNDL_INSPECT_KEY), lang.CmdBundleInspectFlagKey) - inspectCmd.Flags().BoolVarP(&bundleCfg.InspectOpts.ExtractImages, "list-images", "i", false, "Recursively go through all of the sbom's and get the images") + inspectCmd.Flags().BoolVarP(&bundleCfg.InspectOpts.ListImages, "list-images", "i", false, lang.CmdBundleInspectFlagFindImages) // remove cmd flags rootCmd.AddCommand(removeCmd) diff --git a/src/config/lang/lang.go b/src/config/lang/lang.go index ccd3bacc..ff5ae8ef 100644 --- a/src/config/lang/lang.go +++ b/src/config/lang/lang.go @@ -43,6 +43,7 @@ const ( CmdBundleInspectFlagKey = "Path to a public key file that will be used to validate a signed bundle" CmdPackageInspectFlagSBOM = "Create a tarball of SBOMs contained in the bundle" CmdPackageInspectFlagExtractSBOM = "Create a folder of SBOMs contained in the bundle" + CmdBundleInspectFlagFindImages = "Find all images in the bundle" // bundle remove CmdBundleRemoveShort = "Remove a bundle that has been deployed already" diff --git a/src/pkg/bundle/inspect.go b/src/pkg/bundle/inspect.go index bce340f2..e7a1c51e 100644 --- a/src/pkg/bundle/inspect.go +++ b/src/pkg/bundle/inspect.go @@ -5,88 +5,43 @@ package bundle import ( - "bytes" - "os" - "os/exec" + "fmt" "path/filepath" "strings" + "github.com/defenseunicorns/pkg/oci" "github.com/defenseunicorns/uds-cli/src/config" "github.com/defenseunicorns/uds-cli/src/pkg/utils" + "github.com/defenseunicorns/zarf/src/pkg/layout" + "github.com/defenseunicorns/zarf/src/pkg/packager/filters" + zarfSources "github.com/defenseunicorns/zarf/src/pkg/packager/sources" zarfUtils "github.com/defenseunicorns/zarf/src/pkg/utils" - "github.com/defenseunicorns/zarf/src/pkg/message" - "github.com/defenseunicorns/zarf/src/pkg/utils" - "gopkg.in/yaml.v2" + "github.com/defenseunicorns/zarf/src/pkg/zoci" + zarfTypes "github.com/defenseunicorns/zarf/src/types" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) -func (b *Bundle) extractImagesFromPackages() { - for i, pkg := range b.bundle.Packages { - if pkg.Repository != "" && pkg.Ref != "" { - message.Debugf("Package: %s, Repository: %s, Ref: %s", pkg.Name, pkg.Repository, pkg.Ref) - type Component struct { - Images []string `yaml:"images"` - } - - type Output struct { - Components []Component `yaml:"components"` - } - - cmd := exec.Command("uds", "zarf", "package", "inspect", "oci://"+pkg.Repository+":"+pkg.Ref, "--no-color", "--no-log-file") - - var output bytes.Buffer - cmd.Stdout = &output - cmd.Stderr = &output - - err := cmd.Run() // Use Run instead of CombinedOutput - if err != nil { - message.Fatalf("Error executing command: %s, output: %s", err.Error(), output.String()) - continue - } - outputStr := output.String() - - // Find the index of "kind:" - idx := strings.Index(outputStr, "kind:") - if idx == -1 { - message.Fatalf("Invalid output: %s", outputStr) - continue - } - - // Trim the output - trimmedOutput := outputStr[idx:] - - message.Debugf("Trimmed Output: %s", trimmedOutput) - - var result interface{} - err = yaml.Unmarshal([]byte(trimmedOutput), &result) - if err != nil { - message.Fatalf("Error unmarshaling YAML: %s", err.Error()) - continue - } - - var allImages []string - findImages(result, &allImages) - - // Add the images to the package - if len(allImages) > 0 { - message.Debugf("Images: %v", allImages) - b.bundle.Packages[i].Images = allImages - } else { - message.Debugf("No images found in package %v", pkg.Name) - } - } - } -} - // Inspect pulls/unpacks a bundle's metadata and shows it func (b *Bundle) Inspect() error { // Check if the source is a YAML file - if filepath.Ext(b.cfg.InspectOpts.Source) == ".yaml" { + if b.cfg.InspectOpts.ListImages && filepath.Ext(b.cfg.InspectOpts.Source) == ".yaml" { // todo: or .yml source, err := CheckYAMLSourcePath(b.cfg.InspectOpts.Source) if err != nil { return err } b.cfg.InspectOpts.Source = source - return b.InspectYAML(b.cfg.InspectOpts.Source) + + // read the bundle's metadata into memory + if err := utils.ReadYAMLStrict(b.cfg.InspectOpts.Source, &b.bundle); err != nil { + return err + } + + imgs, err := b.extractImagesFromPackages() + if err != nil { + return err + } + fmt.Println(strings.Join(imgs, "\n")) + return nil } // Check that provided oci source path is valid, and update it if it's missing the full path @@ -124,13 +79,6 @@ func (b *Bundle) Inspect() error { if err := utils.ReadYAMLStrict(loaded[config.BundleYAML], &b.bundle); err != nil { return err } - // If ExtractImages flag is set, extract images from each package - if b.cfg.InspectOpts.ExtractImages { - message.Debugf("Extracting images from packages") - b.extractImagesFromPackages() - } else { - message.Debugf("Skipping image extraction") - } // show the bundle's metadata zarfUtils.ColorPrintYAML(b.bundle, nil, false) @@ -140,52 +88,62 @@ func (b *Bundle) Inspect() error { return nil } -// InspectYAML inspects a bundle from a YAML file -func (b *Bundle) InspectYAML(yamlPath string) error { - message.Debugf("Reading the yaml file: %s", yamlPath) - // Read the YAML file - data, err := os.ReadFile(yamlPath) - if err != nil { - return err - } +func (b *Bundle) extractImagesFromPackages() ([]string, error) { + imgMap := make(map[string]string) + for _, pkg := range b.bundle.Packages { + if pkg.Repository != "" && pkg.Ref != "" { - // Unmarshal the YAML data into the Bundle struct - err = yaml.Unmarshal(data, &b.bundle) - if err != nil { - return err - } + url := fmt.Sprintf("oci://%s:%s", pkg.Repository, pkg.Ref) + platform := ocispec.Platform{ + Architecture: config.GetArch(), + OS: oci.MultiOS, + } + remote, err := zoci.NewRemote(url, platform) + if err != nil { + return nil, err + } - // If ExtractImages flag is set, extract images from each package - if b.cfg.InspectOpts.ExtractImages { - b.extractImagesFromPackages() - } + source := zarfSources.OCISource{ + ZarfPackageOptions: &zarfTypes.ZarfPackageOptions{ + PublicKeyPath: "", + }, + Remote: remote, + } - // show the bundle's metadata - utils.ColorPrintYAML(b.bundle, nil, false) - return nil -} + tmpDir, err := zarfUtils.MakeTempDir(config.CommonOptions.TempDirectory) + if err != nil { + return nil, err + } + pkgPaths := layout.New(tmpDir) + zarfPkg, _, err := source.LoadPackageMetadata(pkgPaths, false, true) + if err != nil { + return nil, err + } + + // create filter for optional components + inspectFilter := filters.Combine( + filters.ForDeploy(strings.Join(pkg.OptionalComponents, ","), false), + ) -func findImages(node interface{}, images *[]string) { - switch node := node.(type) { - case map[interface{}]interface{}: - for k, v := range node { - if k == "images" { - // Check if v is a slice of interfaces - if imgSlice, ok := v.([]interface{}); ok { - // Convert each element to a string and append it to images - for _, img := range imgSlice { - if imgStr, ok := img.(string); ok { - *images = append(*images, imgStr) - } - } + filteredComponents, err := inspectFilter.Apply(zarfPkg) + if err != nil { + return nil, err + } + + // grab images from each filtered component + for _, component := range filteredComponents { + for _, img := range component.Images { + imgMap[img] = img } - } else { - findImages(v, images) } } - case []interface{}: - for _, v := range node { - findImages(v, images) - } } + + // convert img map to list of strings + var images []string + for _, img := range imgMap { + images = append(images, img) + } + + return images, nil } diff --git a/src/test/bundles/14-optional-components/uds-bundle.yaml b/src/test/bundles/14-optional-components/uds-bundle.yaml index 39073362..6142a3cc 100644 --- a/src/test/bundles/14-optional-components/uds-bundle.yaml +++ b/src/test/bundles/14-optional-components/uds-bundle.yaml @@ -11,15 +11,15 @@ packages: ref: v0.34.0 # deploys prometheus as a required component and upload-image as an optional component (with noOptionalComponents key) - - name: prometheus - repository: localhost:888/prometheus - ref: 0.0.1 - optionalComponents: - - upload-image - - # deploys podinfo as an optional component and apache as a required component - - name: podinfo-nginx - path: ../../packages/podinfo-nginx - ref: 0.0.1 - optionalComponents: - - podinfo +# - name: prometheus +# repository: localhost:888/prometheus +# ref: 0.0.1 +# optionalComponents: +# - upload-image +# +# # deploys podinfo as an optional component and apache as a required component +# - name: podinfo-nginx +# path: ../../packages/podinfo-nginx +# ref: 0.0.1 +# optionalComponents: +# - podinfo diff --git a/src/test/e2e/commands_test.go b/src/test/e2e/commands_test.go index 250b0388..cda25a57 100644 --- a/src/test/e2e/commands_test.go +++ b/src/test/e2e/commands_test.go @@ -16,9 +16,6 @@ import ( "testing" "github.com/defenseunicorns/pkg/helpers/v2" - "gopkg.in/yaml.v2" - - "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/uds-cli/src/config" "github.com/defenseunicorns/zarf/src/pkg/message" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -114,16 +111,6 @@ func inspectLocalAndSBOMExtract(t *testing.T, tarballPath string) { require.NoError(t, err) } -func inspectBundleYAML(t *testing.T, yamlPath string) { - cmd := strings.Split(fmt.Sprintf("inspect %s", yamlPath), " ") - _, _, err := e2e.UDS(cmd...) - data, err := os.ReadFile(yamlPath) - require.NoError(t, err) - var bundleData map[string]interface{} - err = yaml.Unmarshal(data, &bundleData) - require.NoError(t, err) -} - func deploy(t *testing.T, tarballPath string) (stdout string, stderr string) { cmd := strings.Split(fmt.Sprintf("deploy %s --retries 1 --confirm", tarballPath), " ") stdout, stderr, err := e2e.UDS(cmd...) diff --git a/src/types/options.go b/src/types/options.go index 62ff5c13..e060bff3 100644 --- a/src/types/options.go +++ b/src/types/options.go @@ -44,7 +44,7 @@ type BundleInspectOptions struct { Source string IncludeSBOM bool ExtractSBOM bool - ExtractImages bool + ListImages bool } // BundlePublishOptions is the options for the bundle.Publish() function From 4af54fe158cb164309614b496b436fdde05e93f8 Mon Sep 17 00:00:00 2001 From: unclegedd Date: Wed, 5 Jun 2024 12:16:11 -0500 Subject: [PATCH 05/14] WIP: empty zarf opts --- src/pkg/bundle/inspect.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/pkg/bundle/inspect.go b/src/pkg/bundle/inspect.go index e7a1c51e..3b5845e3 100644 --- a/src/pkg/bundle/inspect.go +++ b/src/pkg/bundle/inspect.go @@ -83,8 +83,6 @@ func (b *Bundle) Inspect() error { // show the bundle's metadata zarfUtils.ColorPrintYAML(b.bundle, nil, false) - // TODO: showing package metadata? - // TODO: could be cool to have an interactive mode that lets you select a package and show its metadata return nil } @@ -104,10 +102,8 @@ func (b *Bundle) extractImagesFromPackages() ([]string, error) { } source := zarfSources.OCISource{ - ZarfPackageOptions: &zarfTypes.ZarfPackageOptions{ - PublicKeyPath: "", - }, - Remote: remote, + ZarfPackageOptions: &zarfTypes.ZarfPackageOptions{}, + Remote: remote, } tmpDir, err := zarfUtils.MakeTempDir(config.CommonOptions.TempDirectory) From 6da643d6adb4c44128ed916ce88298a5a6e19c18 Mon Sep 17 00:00:00 2001 From: unclegedd Date: Thu, 6 Jun 2024 10:24:15 -0500 Subject: [PATCH 06/14] feat: adds list images flag --- src/pkg/bundle/common.go | 15 ++++ src/pkg/bundle/inspect.go | 87 ++++++++++++------- src/pkg/bundle/local.go | 26 ------ src/pkg/utils/utils.go | 6 +- .../14-optional-components/uds-bundle.yaml | 24 ++--- src/test/e2e/bundle_test.go | 28 ++++++ 6 files changed, 115 insertions(+), 71 deletions(-) delete mode 100644 src/pkg/bundle/local.go diff --git a/src/pkg/bundle/common.go b/src/pkg/bundle/common.go index c1f31791..9f5306a3 100644 --- a/src/pkg/bundle/common.go +++ b/src/pkg/bundle/common.go @@ -344,3 +344,18 @@ func validateBundleVars(packages []types.Package) error { } return nil } + +// checkYAMLSourcePath checks if the provided YAML source path is valid +func checkYAMLSourcePath(source string) (string, error) { + // check if the source is a YAML file + isYaml := strings.HasSuffix(source, ".yaml") || strings.HasSuffix(source, ".yml") + if !isYaml { + return "", fmt.Errorf("source must be a YAML file") + } + // Check if the file exists + if _, err := os.Stat(source); os.IsNotExist(err) { + return "", fmt.Errorf("file does not exist: %s", source) + } + + return source, nil +} diff --git a/src/pkg/bundle/inspect.go b/src/pkg/bundle/inspect.go index 3b5845e3..1492f549 100644 --- a/src/pkg/bundle/inspect.go +++ b/src/pkg/bundle/inspect.go @@ -6,6 +6,7 @@ package bundle import ( "fmt" + "os" "path/filepath" "strings" @@ -18,29 +19,32 @@ import ( zarfUtils "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/pkg/zoci" zarfTypes "github.com/defenseunicorns/zarf/src/types" + "github.com/fatih/color" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pterm/pterm" ) // Inspect pulls/unpacks a bundle's metadata and shows it func (b *Bundle) Inspect() error { - // Check if the source is a YAML file - if b.cfg.InspectOpts.ListImages && filepath.Ext(b.cfg.InspectOpts.Source) == ".yaml" { // todo: or .yml - source, err := CheckYAMLSourcePath(b.cfg.InspectOpts.Source) + // handle --list-images flag + if b.cfg.InspectOpts.ListImages { + source, err := checkYAMLSourcePath(b.cfg.InspectOpts.Source) if err != nil { return err } - b.cfg.InspectOpts.Source = source - // read the bundle's metadata into memory - if err := utils.ReadYAMLStrict(b.cfg.InspectOpts.Source, &b.bundle); err != nil { + if err := utils.ReadYAMLStrict(source, &b.bundle); err != nil { return err } - imgs, err := b.extractImagesFromPackages() + // find images in the packages taking into account optional components + imgs, err := b.getPackageImages() if err != nil { return err } - fmt.Println(strings.Join(imgs, "\n")) + + formattedImgs := pterm.Color(color.FgHiMagenta).Sprintf(strings.Join(imgs, "\n")) + pterm.Printfln("\n%s\n", formattedImgs) return nil } @@ -86,11 +90,15 @@ func (b *Bundle) Inspect() error { return nil } -func (b *Bundle) extractImagesFromPackages() ([]string, error) { +func (b *Bundle) getPackageImages() ([]string, error) { + // use a map to track the images for easy de-duping imgMap := make(map[string]string) - for _, pkg := range b.bundle.Packages { - if pkg.Repository != "" && pkg.Ref != "" { + for _, pkg := range b.bundle.Packages { + // get package source + var source zarfSources.PackageSource + if pkg.Repository != "" { + // handle remote packages url := fmt.Sprintf("oci://%s:%s", pkg.Repository, pkg.Ref) platform := ocispec.Platform{ Architecture: config.GetArch(), @@ -101,38 +109,55 @@ func (b *Bundle) extractImagesFromPackages() ([]string, error) { return nil, err } - source := zarfSources.OCISource{ + source = &zarfSources.OCISource{ ZarfPackageOptions: &zarfTypes.ZarfPackageOptions{}, Remote: remote, } - - tmpDir, err := zarfUtils.MakeTempDir(config.CommonOptions.TempDirectory) + } else if pkg.Path != "" { + // handle local packages + err := os.Chdir(filepath.Dir(b.cfg.InspectOpts.Source)) // change to the bundle's directory if err != nil { return nil, err } - pkgPaths := layout.New(tmpDir) - zarfPkg, _, err := source.LoadPackageMetadata(pkgPaths, false, true) - if err != nil { - return nil, err + + bundleArch := config.GetArch(b.bundle.Metadata.Architecture) + tarballName := fmt.Sprintf("zarf-package-%s-%s-%s.tar.zst", pkg.Name, bundleArch, pkg.Ref) + source = &zarfSources.TarballSource{ + ZarfPackageOptions: &zarfTypes.ZarfPackageOptions{ + PackageSource: filepath.Join(pkg.Path, tarballName), + }, } + } else { + return nil, fmt.Errorf("package %s is missing a repository or path", pkg.Name) + } - // create filter for optional components - inspectFilter := filters.Combine( - filters.ForDeploy(strings.Join(pkg.OptionalComponents, ","), false), - ) + tmpDir, err := zarfUtils.MakeTempDir(config.CommonOptions.TempDirectory) + if err != nil { + return nil, err + } + pkgPaths := layout.New(tmpDir) + zarfPkg, _, err := source.LoadPackageMetadata(pkgPaths, false, true) + if err != nil { + return nil, err + } - filteredComponents, err := inspectFilter.Apply(zarfPkg) - if err != nil { - return nil, err - } + // create filter for optional components + inspectFilter := filters.Combine( + filters.ForDeploy(strings.Join(pkg.OptionalComponents, ","), false), + ) - // grab images from each filtered component - for _, component := range filteredComponents { - for _, img := range component.Images { - imgMap[img] = img - } + filteredComponents, err := inspectFilter.Apply(zarfPkg) + if err != nil { + return nil, err + } + + // grab images from each filtered component + for _, component := range filteredComponents { + for _, img := range component.Images { + imgMap[img] = img } } + } // convert img map to list of strings diff --git a/src/pkg/bundle/local.go b/src/pkg/bundle/local.go deleted file mode 100644 index d1aa977c..00000000 --- a/src/pkg/bundle/local.go +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023-Present The UDS Authors - -// Package bundle contains functions for interacting with, managing and deploying UDS packages -package bundle - -import ( - "fmt" - "os" - "path/filepath" -) - -// CheckYAMLSourcePath checks if the provided YAML source path is valid -func CheckYAMLSourcePath(source string) (string, error) { - // Check if the file exists - if _, err := os.Stat(source); os.IsNotExist(err) { - return "", fmt.Errorf("file does not exist: %s", source) - } - - // Check if the file has a .yaml extension - if filepath.Ext(source) != ".yaml" { - return "", fmt.Errorf("file is not a YAML file: %s", source) - } - - return source, nil -} diff --git a/src/pkg/utils/utils.go b/src/pkg/utils/utils.go index 019a2ef6..5d102bad 100644 --- a/src/pkg/utils/utils.go +++ b/src/pkg/utils/utils.go @@ -91,8 +91,10 @@ func ConfigureLogs(cmd *cobra.Command) error { return err } - // use Zarf pterm output - message.Notef("Saving log file to %s", tmpLogLocation) + // don't print the note for inspect cmds because they are used in automation + if !strings.Contains(cmd.Use, "inspect") { + message.Notef("Saving log file to %s", tmpLogLocation) + } return nil } diff --git a/src/test/bundles/14-optional-components/uds-bundle.yaml b/src/test/bundles/14-optional-components/uds-bundle.yaml index 6142a3cc..39073362 100644 --- a/src/test/bundles/14-optional-components/uds-bundle.yaml +++ b/src/test/bundles/14-optional-components/uds-bundle.yaml @@ -11,15 +11,15 @@ packages: ref: v0.34.0 # deploys prometheus as a required component and upload-image as an optional component (with noOptionalComponents key) -# - name: prometheus -# repository: localhost:888/prometheus -# ref: 0.0.1 -# optionalComponents: -# - upload-image -# -# # deploys podinfo as an optional component and apache as a required component -# - name: podinfo-nginx -# path: ../../packages/podinfo-nginx -# ref: 0.0.1 -# optionalComponents: -# - podinfo + - name: prometheus + repository: localhost:888/prometheus + ref: 0.0.1 + optionalComponents: + - upload-image + + # deploys podinfo as an optional component and apache as a required component + - name: podinfo-nginx + path: ../../packages/podinfo-nginx + ref: 0.0.1 + optionalComponents: + - podinfo diff --git a/src/test/e2e/bundle_test.go b/src/test/e2e/bundle_test.go index 8db3e99c..508c2bc5 100644 --- a/src/test/e2e/bundle_test.go +++ b/src/test/e2e/bundle_test.go @@ -643,3 +643,31 @@ func TestArchCheck(t *testing.T) { _, stderr, _ := e2e.UDS(cmd...) require.Contains(t, stderr, fmt.Sprintf("arch %s does not match cluster arch, [%s]", testArch, e2e.Arch)) } + +func TestListImages(t *testing.T) { + e2e.SetupDockerRegistry(t, 888) + defer e2e.TeardownRegistry(t, 888) + + zarfPkgPath := "src/test/packages/prometheus" + pkg := filepath.Join(zarfPkgPath, fmt.Sprintf("zarf-package-prometheus-%s-0.0.1.tar.zst", e2e.Arch)) + e2e.CreateZarfPkg(t, zarfPkgPath, false) + zarfPublish(t, pkg, "localhost:888") + + bundleDir := "src/test/bundles/14-optional-components" + + t.Run("list images on bundle YAML only", func(t *testing.T) { + cmd := strings.Split(fmt.Sprintf("inspect %s --list-images --insecure", filepath.Join(bundleDir, config.BundleYAML)), " ") + _, stderr, err := e2e.UDS(cmd...) + require.NoError(t, err) + require.Contains(t, stderr, "library/registry") + require.Contains(t, stderr, "ghcr.io/defenseunicorns/zarf/agent") + require.Contains(t, stderr, "ghcr.io/stefanprodan/podinfo") + require.Contains(t, stderr, "quay.io/prometheus/node-exporter") + + // ensure non-req'd components got filtered + require.NotContains(t, stderr, "grafana") + require.NotContains(t, stderr, "gitea") + require.NotContains(t, stderr, "kiwix") + require.NotContains(t, stderr, "nginx") + }) +} From 9899da59090749a9fd2c31793c82ea61280c5c77 Mon Sep 17 00:00:00 2001 From: unclegedd Date: Thu, 6 Jun 2024 10:37:49 -0500 Subject: [PATCH 07/14] small refactor --- src/pkg/bundle/common.go | 15 --------------- src/pkg/bundle/inspect.go | 4 ++-- src/pkg/utils/utils.go | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/pkg/bundle/common.go b/src/pkg/bundle/common.go index 9f5306a3..c1f31791 100644 --- a/src/pkg/bundle/common.go +++ b/src/pkg/bundle/common.go @@ -344,18 +344,3 @@ func validateBundleVars(packages []types.Package) error { } return nil } - -// checkYAMLSourcePath checks if the provided YAML source path is valid -func checkYAMLSourcePath(source string) (string, error) { - // check if the source is a YAML file - isYaml := strings.HasSuffix(source, ".yaml") || strings.HasSuffix(source, ".yml") - if !isYaml { - return "", fmt.Errorf("source must be a YAML file") - } - // Check if the file exists - if _, err := os.Stat(source); os.IsNotExist(err) { - return "", fmt.Errorf("file does not exist: %s", source) - } - - return source, nil -} diff --git a/src/pkg/bundle/inspect.go b/src/pkg/bundle/inspect.go index 1492f549..bae9035f 100644 --- a/src/pkg/bundle/inspect.go +++ b/src/pkg/bundle/inspect.go @@ -28,12 +28,12 @@ import ( func (b *Bundle) Inspect() error { // handle --list-images flag if b.cfg.InspectOpts.ListImages { - source, err := checkYAMLSourcePath(b.cfg.InspectOpts.Source) + err := utils.CheckYAMLSourcePath(b.cfg.InspectOpts.Source) if err != nil { return err } - if err := utils.ReadYAMLStrict(source, &b.bundle); err != nil { + if err := utils.ReadYAMLStrict(b.cfg.InspectOpts.Source, &b.bundle); err != nil { return err } diff --git a/src/pkg/utils/utils.go b/src/pkg/utils/utils.go index 5d102bad..5cf17699 100644 --- a/src/pkg/utils/utils.go +++ b/src/pkg/utils/utils.go @@ -196,3 +196,18 @@ func ReadYAMLStrict(path string, destConfig any) error { } return nil } + +// CheckYAMLSourcePath checks if the provided YAML source path is valid +func CheckYAMLSourcePath(source string) error { + // check if the source is a YAML file + isYaml := strings.HasSuffix(source, ".yaml") || strings.HasSuffix(source, ".yml") + if !isYaml { + return fmt.Errorf("source must have .yaml or yml file extension") + } + // Check if the file exists + if _, err := os.Stat(source); os.IsNotExist(err) { + return fmt.Errorf("file %s does not exist", source) + } + + return nil +} From 5b3e457c98f98fce7a0ea53a87fc5157f31cdc43 Mon Sep 17 00:00:00 2001 From: unclegedd Date: Thu, 6 Jun 2024 10:39:39 -0500 Subject: [PATCH 08/14] update schema --- zarf.schema.json | 540 ++++++++++++++++++++++------------------------- 1 file changed, 257 insertions(+), 283 deletions(-) diff --git a/zarf.schema.json b/zarf.schema.json index 69f4aae9..27a8a89a 100644 --- a/zarf.schema.json +++ b/zarf.schema.json @@ -1,11 +1,8 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfPackage", - "definitions": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/defenseunicorns/zarf/src/types/zarf-package", + "$defs": { "BigBang": { - "required": [ - "version" - ], "properties": { "version": { "type": "string", @@ -36,6 +33,43 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "version" + ], + "patternProperties": { + "^x-": {} + } + }, + "Constant": { + "properties": { + "name": { + "type": "string", + "pattern": "^[A-Z0-9_]+$", + "description": "The name to be used for the constant" + }, + "value": { + "type": "string", + "description": "The value to set for the constant during deploy" + }, + "description": { + "type": "string", + "description": "A description of the constant to explain its purpose on package create or deploy confirmation prompts" + }, + "autoIndent": { + "type": "boolean", + "description": "Whether to automatically indent the variable's value (if multiline) when templating. Based on the number of chars before the start of ###ZARF_CONST_." + }, + "pattern": { + "type": "string", + "description": "An optional regex pattern that a constant value must match before a package can be created." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name", + "value" + ], "patternProperties": { "^x-": {} } @@ -82,6 +116,55 @@ "^x-": {} } }, + "InteractiveVariable": { + "properties": { + "name": { + "type": "string", + "pattern": "^[A-Z0-9_]+$", + "description": "The name to be used for the variable" + }, + "sensitive": { + "type": "boolean", + "description": "Whether to mark this variable as sensitive to not print it in the log" + }, + "autoIndent": { + "type": "boolean", + "description": "Whether to automatically indent the variable's value (if multiline) when templating. Based on the number of chars before the start of ###ZARF_VAR_." + }, + "pattern": { + "type": "string", + "description": "An optional regex pattern that a variable value must match before a package deployment can continue." + }, + "type": { + "type": "string", + "enum": [ + "raw", + "file" + ], + "description": "Changes the handling of a variable to load contents differently (i.e. from a file rather than as a raw variable - templated files should be kept below 1 MiB)" + }, + "description": { + "type": "string", + "description": "A description of the variable to be used when prompting the user a value" + }, + "default": { + "type": "string", + "description": "The default value to use for the variable" + }, + "prompt": { + "type": "boolean", + "description": "Whether to prompt the user for input for this variable" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name" + ], + "patternProperties": { + "^x-": {} + } + }, "Shell": { "properties": { "windows": { @@ -125,14 +208,44 @@ "^x-": {} } }, - "ZarfBuildData": { + "Variable": { + "properties": { + "name": { + "type": "string", + "pattern": "^[A-Z0-9_]+$", + "description": "The name to be used for the variable" + }, + "sensitive": { + "type": "boolean", + "description": "Whether to mark this variable as sensitive to not print it in the log" + }, + "autoIndent": { + "type": "boolean", + "description": "Whether to automatically indent the variable's value (if multiline) when templating. Based on the number of chars before the start of ###ZARF_VAR_." + }, + "pattern": { + "type": "string", + "description": "An optional regex pattern that a variable value must match before a package deployment can continue." + }, + "type": { + "type": "string", + "enum": [ + "raw", + "file" + ], + "description": "Changes the handling of a variable to load contents differently (i.e. from a file rather than as a raw variable - templated files should be kept below 1 MiB)" + } + }, + "additionalProperties": false, + "type": "object", "required": [ - "terminal", - "user", - "architecture", - "timestamp", - "version" + "name" ], + "patternProperties": { + "^x-": {} + } + }, + "ZarfBuildData": { "properties": { "terminal": { "type": "string", @@ -162,10 +275,8 @@ "description": "Any migrations that have been run on this package" }, "registryOverrides": { - "patternProperties": { - ".*": { - "type": "string" - } + "additionalProperties": { + "type": "string" }, "type": "object", "description": "Any registry domains that were overridden on package create when pulling images" @@ -196,15 +307,18 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "terminal", + "user", + "architecture", + "timestamp", + "version" + ], "patternProperties": { "^x-": {} } }, "ZarfChart": { - "required": [ - "name", - "namespace" - ], "properties": { "name": { "type": "string", @@ -259,8 +373,7 @@ }, "variables": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfChartVariable" + "$ref": "#/$defs/ZarfChartVariable" }, "type": "array", "description": "[alpha] List of variables to set in the Helm chart" @@ -268,20 +381,19 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "name", + "namespace" + ], "patternProperties": { "^x-": {} } }, "ZarfChartVariable": { - "required": [ - "name", - "description", - "path" - ], "properties": { "name": { - "pattern": "^[A-Z0-9_]+$", "type": "string", + "pattern": "^[A-Z0-9_]+$", "description": "The name of the variable" }, "description": { @@ -295,18 +407,20 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "name", + "description", + "path" + ], "patternProperties": { "^x-": {} } }, "ZarfComponent": { - "required": [ - "name" - ], "properties": { "name": { - "pattern": "^[a-z0-9\\-]*[a-z0-9]$", "type": "string", + "pattern": "^[a-z0-9\\-]*[a-z0-9]$", "description": "The name of the component" }, "description": { @@ -322,8 +436,7 @@ "description": "Do not prompt user to install this component" }, "only": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentOnlyTarget", + "$ref": "#/$defs/ZarfComponentOnlyTarget", "description": "Filter when this component is included in package creation or deployment" }, "group": { @@ -335,38 +448,33 @@ "description": "[Deprecated] Specify a path to a public key to validate signed online resources. This will be removed in Zarf v1.0.0." }, "import": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentImport", + "$ref": "#/$defs/ZarfComponentImport", "description": "Import a component from another Zarf package" }, "manifests": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfManifest" + "$ref": "#/$defs/ZarfManifest" }, "type": "array", "description": "Kubernetes manifests to be included in a generated Helm chart on package deploy" }, "charts": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfChart" + "$ref": "#/$defs/ZarfChart" }, "type": "array", "description": "Helm charts to install during package deploy" }, "dataInjections": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfDataInjection" + "$ref": "#/$defs/ZarfDataInjection" }, "type": "array", "description": "Datasets to inject into a container in the target cluster" }, "files": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfFile" + "$ref": "#/$defs/ZarfFile" }, "type": "array", "description": "Files or folders to place on disk during package deployment" @@ -386,23 +494,23 @@ "description": "List of git repos to include in the package" }, "extensions": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentExtensions", + "$ref": "#/$defs/ZarfComponentExtensions", "description": "Extend component functionality with additional features" }, "scripts": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/DeprecatedZarfComponentScripts", + "$ref": "#/$defs/DeprecatedZarfComponentScripts", "description": "[Deprecated] (replaced by actions) Custom commands to run before or after package deployment. This will be removed in Zarf v1.0.0." }, "actions": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentActions", + "$ref": "#/$defs/ZarfComponentActions", "description": "Custom commands to run at various stages of a package lifecycle" } }, "additionalProperties": false, "type": "object", + "required": [ + "name" + ], "patternProperties": { "^x-": {} } @@ -437,18 +545,17 @@ "description": "The command to run. Must specify either cmd or wait for the action to do anything." }, "shell": { - "$ref": "#/definitions/Shell", + "$ref": "#/$defs/Shell", "description": "(cmd only) Indicates a preference for a shell for the provided cmd to be executed in on supported operating systems" }, "setVariable": { - "pattern": "^[A-Z0-9_]+$", "type": "string", + "pattern": "^[A-Z0-9_]+$", "description": "[Deprecated] (replaced by setVariables) (onDeploy/cmd only) The name of a variable to update with the output of the command. This variable will be available to all remaining actions and components in the package. This will be removed in Zarf v1.0.0" }, "setVariables": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentActionSetVariable" + "$ref": "#/$defs/Variable" }, "type": "array", "description": "(onDeploy/cmd only) An array of variables to update with the output of the command. These variables will be available to all remaining actions and components in the package." @@ -458,8 +565,7 @@ "description": "Description of the action to be displayed during package execution instead of the command" }, "wait": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentActionWait", + "$ref": "#/$defs/ZarfComponentActionWait", "description": "Wait for a condition to be met before continuing. Must specify either cmd or wait for the action. See the 'zarf tools wait-for' command for more info." } }, @@ -495,8 +601,7 @@ "description": "Additional environment variables for commands" }, "shell": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/Shell", + "$ref": "#/$defs/Shell", "description": "(cmd only) Indicates a preference for a shell for the provided cmd to be executed in on supported operating systems" } }, @@ -509,35 +614,33 @@ "ZarfComponentActionSet": { "properties": { "defaults": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentActionDefaults", + "$ref": "#/$defs/ZarfComponentActionDefaults", "description": "Default configuration for all actions in this set" }, "before": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentAction" + "$ref": "#/$defs/ZarfComponentAction" }, "type": "array", "description": "Actions to run at the start of an operation" }, "after": { "items": { - "$ref": "#/definitions/ZarfComponentAction" + "$ref": "#/$defs/ZarfComponentAction" }, "type": "array", "description": "Actions to run at the end of an operation" }, "onSuccess": { "items": { - "$ref": "#/definitions/ZarfComponentAction" + "$ref": "#/$defs/ZarfComponentAction" }, "type": "array", "description": "Actions to run if all operations succeed" }, "onFailure": { "items": { - "$ref": "#/definitions/ZarfComponentAction" + "$ref": "#/$defs/ZarfComponentAction" }, "type": "array", "description": "Actions to run if all operations fail" @@ -549,53 +652,14 @@ "^x-": {} } }, - "ZarfComponentActionSetVariable": { - "required": [ - "name" - ], - "properties": { - "name": { - "pattern": "^[A-Z0-9_]+$", - "type": "string", - "description": "The name to be used for the variable" - }, - "type": { - "enum": [ - "raw", - "file" - ], - "type": "string", - "description": "Changes the handling of a variable to load contents differently (i.e. from a file rather than as a raw variable - templated files should be kept below 1 MiB)" - }, - "pattern": { - "type": "string", - "description": "An optional regex pattern that a variable value must match before a package deployment can continue." - }, - "sensitive": { - "type": "boolean", - "description": "Whether to mark this variable as sensitive to not print it in the Zarf log" - }, - "autoIndent": { - "type": "boolean", - "description": "Whether to automatically indent the variable's value (if multiline) when templating. Based on the number of chars before the start of ###ZARF_VAR_." - } - }, - "additionalProperties": false, - "type": "object", - "patternProperties": { - "^x-": {} - } - }, "ZarfComponentActionWait": { "properties": { "cluster": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentActionWaitCluster", + "$ref": "#/$defs/ZarfComponentActionWaitCluster", "description": "Wait for a condition to be met in the cluster before continuing. Only one of cluster or network can be specified." }, "network": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentActionWaitNetwork", + "$ref": "#/$defs/ZarfComponentActionWaitNetwork", "description": "Wait for a condition to be met on the network before continuing. Only one of cluster or network can be specified." } }, @@ -606,10 +670,6 @@ } }, "ZarfComponentActionWaitCluster": { - "required": [ - "kind", - "name" - ], "properties": { "kind": { "type": "string", @@ -642,23 +702,23 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "kind", + "name" + ], "patternProperties": { "^x-": {} } }, "ZarfComponentActionWaitNetwork": { - "required": [ - "protocol", - "address" - ], "properties": { "protocol": { + "type": "string", "enum": [ "tcp", "http", "https" ], - "type": "string", "description": "The protocol to wait for" }, "address": { @@ -680,6 +740,10 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "protocol", + "address" + ], "patternProperties": { "^x-": {} } @@ -687,16 +751,15 @@ "ZarfComponentActions": { "properties": { "onCreate": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentActionSet", + "$ref": "#/$defs/ZarfComponentActionSet", "description": "Actions to run during package creation" }, "onDeploy": { - "$ref": "#/definitions/ZarfComponentActionSet", + "$ref": "#/$defs/ZarfComponentActionSet", "description": "Actions to run during package deployment" }, "onRemove": { - "$ref": "#/definitions/ZarfComponentActionSet", + "$ref": "#/$defs/ZarfComponentActionSet", "description": "Actions to run during package removal" } }, @@ -709,8 +772,7 @@ "ZarfComponentExtensions": { "properties": { "bigbang": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/BigBang", + "$ref": "#/$defs/BigBang", "description": "Configurations for installing Big Bang and Flux in the cluster" } }, @@ -731,8 +793,8 @@ "description": "The relative path to a directory containing a zarf.yaml to import from" }, "url": { - "pattern": "^oci://.*$", "type": "string", + "pattern": "^oci://.*$", "description": "[beta] The URL to a Zarf package to import via OCI" } }, @@ -745,16 +807,20 @@ "ZarfComponentOnlyCluster": { "properties": { "architecture": { + "type": "string", "enum": [ "amd64", "arm64" ], - "type": "string", "description": "Only create and deploy to clusters of the given architecture" }, "distros": { "items": { - "type": "string" + "type": "string", + "examples": [ + "k3s", + "eks" + ] }, "type": "array", "description": "A list of kubernetes distros this package works with (Reserved for future use)" @@ -769,17 +835,16 @@ "ZarfComponentOnlyTarget": { "properties": { "localOS": { + "type": "string", "enum": [ "linux", "darwin", "windows" ], - "type": "string", "description": "Only deploy component to specified OS" }, "cluster": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentOnlyCluster", + "$ref": "#/$defs/ZarfComponentOnlyCluster", "description": "Only deploy component to specified clusters" }, "flavor": { @@ -794,12 +859,6 @@ } }, "ZarfContainerTarget": { - "required": [ - "namespace", - "selector", - "container", - "path" - ], "properties": { "namespace": { "type": "string", @@ -823,23 +882,24 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "namespace", + "selector", + "container", + "path" + ], "patternProperties": { "^x-": {} } }, "ZarfDataInjection": { - "required": [ - "source", - "target" - ], "properties": { "source": { "type": "string", "description": "Either a path to a local folder/file or a remote URL of a file to inject into the given target pod + container" }, "target": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfContainerTarget", + "$ref": "#/$defs/ZarfContainerTarget", "description": "The target pod + container to inject the data into" }, "compress": { @@ -849,15 +909,15 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "source", + "target" + ], "patternProperties": { "^x-": {} } }, "ZarfFile": { - "required": [ - "source", - "target" - ], "properties": { "source": { "type": "string", @@ -889,14 +949,15 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "source", + "target" + ], "patternProperties": { "^x-": {} } }, "ZarfManifest": { - "required": [ - "name" - ], "properties": { "name": { "type": "string", @@ -931,18 +992,18 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "name" + ], "patternProperties": { "^x-": {} } }, "ZarfMetadata": { - "required": [ - "name" - ], "properties": { "name": { - "pattern": "^[a-z0-9\\-]*[a-z0-9]$", "type": "string", + "pattern": "^[a-z0-9\\-]*[a-z0-9]$", "description": "Name to identify this Zarf package" }, "description": { @@ -1003,148 +1064,61 @@ }, "additionalProperties": false, "type": "object", - "patternProperties": { - "^x-": {} - } - }, - "ZarfPackage": { "required": [ - "kind", - "components" + "name" ], - "properties": { - "kind": { - "enum": [ - "ZarfInitConfig", - "ZarfPackageConfig" - ], - "type": "string", - "description": "The kind of Zarf package", - "default": "ZarfPackageConfig" - }, - "metadata": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfMetadata", - "description": "Package metadata" - }, - "build": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfBuildData", - "description": "Zarf-generated package build data" - }, - "components": { - "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponent" - }, - "type": "array", - "description": "List of components to deploy in this package" - }, - "constants": { - "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfPackageConstant" - }, - "type": "array", - "description": "Constant template values applied on deploy for K8s resources" - }, - "variables": { - "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfPackageVariable" - }, - "type": "array", - "description": "Variable template values applied on deploy for K8s resources" - } - }, - "additionalProperties": false, - "type": "object", "patternProperties": { "^x-": {} } - }, - "ZarfPackageConstant": { - "required": [ - "name", - "value" + } + }, + "properties": { + "kind": { + "type": "string", + "enum": [ + "ZarfInitConfig", + "ZarfPackageConfig" ], - "properties": { - "name": { - "pattern": "^[A-Z0-9_]+$", - "type": "string", - "description": "The name to be used for the constant" - }, - "value": { - "type": "string", - "description": "The value to set for the constant during deploy" - }, - "description": { - "type": "string", - "description": "A description of the constant to explain its purpose on package create or deploy confirmation prompts" - }, - "autoIndent": { - "type": "boolean", - "description": "Whether to automatically indent the variable's value (if multiline) when templating. Based on the number of chars before the start of ###ZARF_CONST_." - }, - "pattern": { - "type": "string", - "description": "An optional regex pattern that a constant value must match before a package can be created." - } + "description": "The kind of Zarf package", + "default": "ZarfPackageConfig" + }, + "metadata": { + "$ref": "#/$defs/ZarfMetadata", + "description": "Package metadata" + }, + "build": { + "$ref": "#/$defs/ZarfBuildData", + "description": "Zarf-generated package build data" + }, + "components": { + "items": { + "$ref": "#/$defs/ZarfComponent" }, - "additionalProperties": false, - "type": "object", - "patternProperties": { - "^x-": {} - } + "type": "array", + "description": "List of components to deploy in this package" }, - "ZarfPackageVariable": { - "required": [ - "name" - ], - "properties": { - "name": { - "pattern": "^[A-Z0-9_]+$", - "type": "string", - "description": "The name to be used for the variable" - }, - "description": { - "type": "string", - "description": "A description of the variable to be used when prompting the user a value" - }, - "default": { - "type": "string", - "description": "The default value to use for the variable" - }, - "prompt": { - "type": "boolean", - "description": "Whether to prompt the user for input for this variable" - }, - "sensitive": { - "type": "boolean", - "description": "Whether to mark this variable as sensitive to not print it in the Zarf log" - }, - "autoIndent": { - "type": "boolean", - "description": "Whether to automatically indent the variable's value (if multiline) when templating. Based on the number of chars before the start of ###ZARF_VAR_." - }, - "pattern": { - "type": "string", - "description": "An optional regex pattern that a variable value must match before a package can be deployed." - }, - "type": { - "enum": [ - "raw", - "file" - ], - "type": "string", - "description": "Changes the handling of a variable to load contents differently (i.e. from a file rather than as a raw variable - templated files should be kept below 1 MiB)" - } + "constants": { + "items": { + "$ref": "#/$defs/Constant" }, - "additionalProperties": false, - "type": "object", - "patternProperties": { - "^x-": {} - } + "type": "array", + "description": "Constant template values applied on deploy for K8s resources" + }, + "variables": { + "items": { + "$ref": "#/$defs/InteractiveVariable" + }, + "type": "array", + "description": "Variable template values applied on deploy for K8s resources" } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "kind", + "components" + ], + "patternProperties": { + "^x-": {} } } From 3473982883c2298be59691685edfdb7eeb468202 Mon Sep 17 00:00:00 2001 From: unclegedd Date: Thu, 6 Jun 2024 11:05:13 -0500 Subject: [PATCH 09/14] fix tests --- src/test/bundles/14-optional-components/uds-bundle.yaml | 2 +- src/test/e2e/bundle_test.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/bundles/14-optional-components/uds-bundle.yaml b/src/test/bundles/14-optional-components/uds-bundle.yaml index 39073362..0ee9ead3 100644 --- a/src/test/bundles/14-optional-components/uds-bundle.yaml +++ b/src/test/bundles/14-optional-components/uds-bundle.yaml @@ -17,7 +17,7 @@ packages: optionalComponents: - upload-image - # deploys podinfo as an optional component and apache as a required component + # deploys podinfo as an optional component - name: podinfo-nginx path: ../../packages/podinfo-nginx ref: 0.0.1 diff --git a/src/test/e2e/bundle_test.go b/src/test/e2e/bundle_test.go index 508c2bc5..8245237f 100644 --- a/src/test/e2e/bundle_test.go +++ b/src/test/e2e/bundle_test.go @@ -653,6 +653,9 @@ func TestListImages(t *testing.T) { e2e.CreateZarfPkg(t, zarfPkgPath, false) zarfPublish(t, pkg, "localhost:888") + zarfPkgPath = "src/test/packages/podinfo-nginx" + e2e.CreateZarfPkg(t, zarfPkgPath, false) + bundleDir := "src/test/bundles/14-optional-components" t.Run("list images on bundle YAML only", func(t *testing.T) { From 6d888153bdc997a4e03fd06329ec0e1a39528ff8 Mon Sep 17 00:00:00 2001 From: unclegedd Date: Thu, 6 Jun 2024 11:11:19 -0500 Subject: [PATCH 10/14] fix workflow logs --- .github/actions/save-logs/action.yaml | 13 +++++++------ .github/workflows/test-e2e-pr.yaml | 10 +++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/actions/save-logs/action.yaml b/.github/actions/save-logs/action.yaml index f6bfbc14..0d516cfe 100644 --- a/.github/actions/save-logs/action.yaml +++ b/.github/actions/save-logs/action.yaml @@ -2,19 +2,20 @@ name: save-logs description: "Save debug logs" inputs: - name: - description: "unique name to put in log file" - required: true + suffix: + description: 'Suffix to append to the debug log' + required: false + default: ' runs: using: composite steps: - name: Fix log permissions run: | - sudo chown $USER /tmp/uds-${{ inputs.name }}.log || echo "" + sudo chown $USER /tmp/uds-*.log || echo "" shell: bash - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: - name: debug-log - path: /tmp/uds-${{ inputs.name }}.log + name: debug-log${{ inputs.suffix }} + path: /tmp/uds-*.log diff --git a/.github/workflows/test-e2e-pr.yaml b/.github/workflows/test-e2e-pr.yaml index c3312893..052c7a38 100644 --- a/.github/workflows/test-e2e-pr.yaml +++ b/.github/workflows/test-e2e-pr.yaml @@ -67,7 +67,7 @@ jobs: if: always() uses: ./.github/actions/save-logs with: - name: build + suffix: build test-dev: runs-on: ubuntu-latest @@ -89,7 +89,7 @@ jobs: if: always() uses: ./.github/actions/save-logs with: - name: test-dev + suffix: test-dev test-variables: runs-on: ubuntu-latest @@ -111,7 +111,7 @@ jobs: if: always() uses: ./.github/actions/save-logs with: - name: test-variables + suffix: test-variables test-optional-bundle: runs-on: ubuntu-latest @@ -133,7 +133,7 @@ jobs: if: always() uses: ./.github/actions/save-logs with: - name: test-optional-bundle + suffix: test-optional-bundle test-vendor: runs-on: ubuntu-latest @@ -155,4 +155,4 @@ jobs: if: always() uses: ./.github/actions/save-logs with: - name: test-vendor + suffix: test-vendor From e374b0f142b28c5bfd4fbd21c6bb18caa7985996 Mon Sep 17 00:00:00 2001 From: unclegedd Date: Thu, 6 Jun 2024 12:21:28 -0500 Subject: [PATCH 11/14] revert logs changes, will make follow up ticket --- .github/actions/save-logs/action.yaml | 13 ++++++------- .github/workflows/test-e2e-pr.yaml | 10 +++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/actions/save-logs/action.yaml b/.github/actions/save-logs/action.yaml index 0d516cfe..f6bfbc14 100644 --- a/.github/actions/save-logs/action.yaml +++ b/.github/actions/save-logs/action.yaml @@ -2,20 +2,19 @@ name: save-logs description: "Save debug logs" inputs: - suffix: - description: 'Suffix to append to the debug log' - required: false - default: ' + name: + description: "unique name to put in log file" + required: true runs: using: composite steps: - name: Fix log permissions run: | - sudo chown $USER /tmp/uds-*.log || echo "" + sudo chown $USER /tmp/uds-${{ inputs.name }}.log || echo "" shell: bash - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: - name: debug-log${{ inputs.suffix }} - path: /tmp/uds-*.log + name: debug-log + path: /tmp/uds-${{ inputs.name }}.log diff --git a/.github/workflows/test-e2e-pr.yaml b/.github/workflows/test-e2e-pr.yaml index 052c7a38..c3312893 100644 --- a/.github/workflows/test-e2e-pr.yaml +++ b/.github/workflows/test-e2e-pr.yaml @@ -67,7 +67,7 @@ jobs: if: always() uses: ./.github/actions/save-logs with: - suffix: build + name: build test-dev: runs-on: ubuntu-latest @@ -89,7 +89,7 @@ jobs: if: always() uses: ./.github/actions/save-logs with: - suffix: test-dev + name: test-dev test-variables: runs-on: ubuntu-latest @@ -111,7 +111,7 @@ jobs: if: always() uses: ./.github/actions/save-logs with: - suffix: test-variables + name: test-variables test-optional-bundle: runs-on: ubuntu-latest @@ -133,7 +133,7 @@ jobs: if: always() uses: ./.github/actions/save-logs with: - suffix: test-optional-bundle + name: test-optional-bundle test-vendor: runs-on: ubuntu-latest @@ -155,4 +155,4 @@ jobs: if: always() uses: ./.github/actions/save-logs with: - suffix: test-vendor + name: test-vendor From 9533f3b0792076a8117c818af3a98109e81bf873 Mon Sep 17 00:00:00 2001 From: unclegedd Date: Thu, 6 Jun 2024 12:25:29 -0500 Subject: [PATCH 12/14] remove yaml from bundle picker --- src/cmd/uds.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cmd/uds.go b/src/cmd/uds.go index 1e0f86e4..09f7b1b5 100644 --- a/src/cmd/uds.go +++ b/src/cmd/uds.go @@ -236,11 +236,9 @@ func chooseBundle(args []string) string { files, _ := filepath.Glob(config.BundlePrefix + toComplete + "*.tar") gzFiles, _ := filepath.Glob(config.BundlePrefix + toComplete + "*.tar.zst") partialFiles, _ := filepath.Glob(config.BundlePrefix + toComplete + "*.part000") - yamlFiles, _ := filepath.Glob(config.BundlePrefix + toComplete + "*.yaml") // Add this line files = append(files, gzFiles...) files = append(files, partialFiles...) - files = append(files, yamlFiles...) return files }, } From 09439ee3c22e56e920dd90eff55015aa41e51b18 Mon Sep 17 00:00:00 2001 From: unclegedd Date: Thu, 6 Jun 2024 12:28:20 -0500 Subject: [PATCH 13/14] words --- src/config/lang/lang.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/lang/lang.go b/src/config/lang/lang.go index ff5ae8ef..904bbfad 100644 --- a/src/config/lang/lang.go +++ b/src/config/lang/lang.go @@ -43,7 +43,7 @@ const ( CmdBundleInspectFlagKey = "Path to a public key file that will be used to validate a signed bundle" CmdPackageInspectFlagSBOM = "Create a tarball of SBOMs contained in the bundle" CmdPackageInspectFlagExtractSBOM = "Create a folder of SBOMs contained in the bundle" - CmdBundleInspectFlagFindImages = "Find all images in the bundle" + CmdBundleInspectFlagFindImages = "Derive images from a uds-bundle.yaml file and list them" // bundle remove CmdBundleRemoveShort = "Remove a bundle that has been deployed already" From bc005b279797995e4f56499803e2edac57f80f48 Mon Sep 17 00:00:00 2001 From: unclegedd Date: Thu, 6 Jun 2024 12:33:08 -0500 Subject: [PATCH 14/14] docs --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index a96e502b..1b906478 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,13 @@ Inspect the `uds-bundle.yaml` of a bundle 1. From an OCI registry: `uds inspect oci://ghcr.io/defenseunicorns/dev/:` 1. From your local filesystem: `uds inspect uds-bundle-.tar.zst` +#### Viewing Images in a Bundle +It is possible derive images from a `uds-bundle.yaml`. This can be useful for situations where you need to know what images will be bundled before you actually create the bundle. This is accomplished with the `--list-images`. For example: + +`uds inspect ./uds-bundle.yaml --list-images` + +This command will return a list of images derived from the bundle's packages and taking into account optional and required package components. + #### Viewing SBOMs There are 2 additional flags for the `uds inspect` command you can use to extract and view SBOMs: - Output the SBOMs as a tar file: `uds inspect ... --sbom`