From 9bd73697aa1c0f60107c3dccc09cc0ff47b02053 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Sun, 19 Feb 2023 04:14:55 -0600 Subject: [PATCH 01/20] add kubectl / "wait-for" command / vendored cmd cleanup --- .../100-cli-commands/zarf_tools.md | 2 + .../100-cli-commands/zarf_tools_kubectl.md | 31 ++++++ .../100-cli-commands/zarf_tools_wait-for.md | 43 +++++++++ examples/flux-test/zarf.yaml | 6 ++ go.mod | 10 +- go.sum | 17 ++++ src/cmd/root.go | 37 +++++++ src/cmd/tools.go | 96 ++++++++++++++++++- src/cmd/viper.go | 6 ++ src/config/lang/english.go | 10 ++ src/test/e2e/20_zarf_init_test.go | 14 +-- src/test/e2e/22_git_and_flux_test.go | 33 +------ 12 files changed, 264 insertions(+), 41 deletions(-) create mode 100644 docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_kubectl.md create mode 100644 docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md diff --git a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools.md b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools.md index 850c3c50d0..fcc0768cce 100644 --- a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools.md +++ b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools.md @@ -28,7 +28,9 @@ Collection of additional tools to make airgap easier * [zarf tools clear-cache](zarf_tools_clear-cache.md) - Clears the configured git and image cache directory. * [zarf tools gen-pki](zarf_tools_gen-pki.md) - Generates a Certificate Authority and PKI chain of trust for the given host * [zarf tools get-creds](zarf_tools_get-creds.md) - Display a Table of credentials for deployed components. Pass a component name to get a single credential. +* [zarf tools kubectl](zarf_tools_kubectl.md) - Kubectl command. See https://kubernetes.io/docs/reference/kubectl/overview/ for more information. * [zarf tools monitor](zarf_tools_monitor.md) - Launch a terminal UI to monitor the connected cluster using K9s. * [zarf tools registry](zarf_tools_registry.md) - Tools for working with container registries using go-containertools. * [zarf tools sbom](zarf_tools_sbom.md) - Generates a Software Bill of Materials (SBOM) for the given package +* [zarf tools wait-for](zarf_tools_wait-for.md) - Waits for a given Kubernetes resource to be ready diff --git a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_kubectl.md b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_kubectl.md new file mode 100644 index 0000000000..f9919cf7ad --- /dev/null +++ b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_kubectl.md @@ -0,0 +1,31 @@ +# zarf tools kubectl + + +Kubectl command. See https://kubernetes.io/docs/reference/kubectl/overview/ for more information. + +``` +zarf tools kubectl [flags] +``` + +## Options + +``` + -h, --help help for kubectl +``` + +## Options inherited from parent commands + +``` + -a, --architecture string Architecture for OCI images + --insecure Allow access to insecure registries and disable other recommended security enforcements. This flag should only be used if you have a specific reason and accept the reduced security posture. + -l, --log-level string Log level when running Zarf. Valid options are: warn, info, debug, trace (default "info") + --no-log-file Disable log file creation + --no-progress Disable fancy UI progress bars, spinners, logos, etc + --tmpdir string Specify the temporary directory to use for intermediate files + --zarf-cache string Specify the location of the Zarf cache directory (default "~/.zarf-cache") +``` + +## SEE ALSO + +* [zarf tools](zarf_tools.md) - Collection of additional tools to make airgap easier + diff --git a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md new file mode 100644 index 0000000000..316121c699 --- /dev/null +++ b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md @@ -0,0 +1,43 @@ +# zarf tools wait-for + + +Waits for a given Kubernetes resource to be ready + +## Synopsis + +By default Zarf will wait for all Kubernetes resources to be ready before completion of component during deployment. +This command can be used to wait for a specific resource to exist and be ready that may be created by a Gitops tool or Operator. + +``` +zarf tools wait-for {NAMESPACE} {RESOURCE} {NAME} {CONDITION} [flags] +``` + +## Examples + +``` +zarf tools wait-for default pod my-pod-name ready +``` + +## Options + +``` + -h, --help help for wait-for + --timeout string Specify the timeout duration for the wait command. (default "5m") +``` + +## Options inherited from parent commands + +``` + -a, --architecture string Architecture for OCI images + --insecure Allow access to insecure registries and disable other recommended security enforcements. This flag should only be used if you have a specific reason and accept the reduced security posture. + -l, --log-level string Log level when running Zarf. Valid options are: warn, info, debug, trace (default "info") + --no-log-file Disable log file creation + --no-progress Disable fancy UI progress bars, spinners, logos, etc + --tmpdir string Specify the temporary directory to use for intermediate files + --zarf-cache string Specify the location of the Zarf cache directory (default "~/.zarf-cache") +``` + +## SEE ALSO + +* [zarf tools](zarf_tools.md) - Collection of additional tools to make airgap easier + diff --git a/examples/flux-test/zarf.yaml b/examples/flux-test/zarf.yaml index 1c929679b2..97213cff7c 100644 --- a/examples/flux-test/zarf.yaml +++ b/examples/flux-test/zarf.yaml @@ -29,3 +29,9 @@ components: - https://github.com/stefanprodan/podinfo.git images: - ghcr.io/stefanprodan/podinfo:6.3.3 + actions: + onDeploy: + after: + - cmd: "./zarf tools wait-for podinfo deployment podinfo available" + description: "Podinfo deployment to be ready" + mute: true diff --git a/go.mod b/go.mod index bca9a3d444..9b81693218 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,9 @@ require ( k8s.io/api v0.26.1 k8s.io/apimachinery v0.26.1 k8s.io/client-go v0.26.1 + k8s.io/component-base v0.26.1 k8s.io/klog/v2 v2.90.0 + k8s.io/kubectl v0.26.1 sigs.k8s.io/kustomize/api v0.12.1 sigs.k8s.io/kustomize/kyaml v0.13.9 sigs.k8s.io/yaml v1.3.0 @@ -112,6 +114,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/blang/semver v3.5.1+incompatible // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/bmatcuk/doublestar/v4 v4.6.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect @@ -133,6 +136,7 @@ require ( github.com/cyberphone/json-canonicalization v0.0.0-20210823021906-dc406ceaf94b // indirect github.com/cyphar/filepath-securejoin v0.2.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/daviddengcn/go-colortext v1.0.0 // indirect github.com/derailed/popeye v0.10.1 // indirect github.com/derailed/tcell/v2 v2.3.1-rc.3 // indirect github.com/derailed/tview v0.8.1 // indirect @@ -237,6 +241,7 @@ require ( github.com/letsencrypt/boulder v0.0.0-20220929215747-76583552c2be // indirect github.com/lib/pq v1.10.7 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/lithammer/dedent v1.1.0 // indirect github.com/lithammer/fuzzysearch v1.1.5 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -263,6 +268,7 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/mozillazg/docker-credential-acr-helper v0.3.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/nwaples/rardecode v1.1.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect @@ -386,9 +392,8 @@ require ( k8s.io/apiextensions-apiserver v0.26.1 // indirect k8s.io/apiserver v0.26.1 // indirect k8s.io/cli-runtime v0.26.1 // indirect - k8s.io/component-base v0.26.1 // indirect + k8s.io/component-helpers v0.26.1 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect - k8s.io/kubectl v0.26.1 // indirect k8s.io/metrics v0.26.1 // indirect k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect lukechampine.com/uint128 v1.1.1 // indirect @@ -403,6 +408,7 @@ require ( modernc.org/token v1.0.0 // indirect oras.land/oras-go v1.2.2 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/kustomize/kustomize/v4 v4.5.7 // indirect sigs.k8s.io/release-utils v0.7.3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect ) diff --git a/go.sum b/go.sum index b17dc56254..c2854c0d07 100644 --- a/go.sum +++ b/go.sum @@ -375,6 +375,8 @@ github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqO github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvzIZhEXc= github.com/bmatcuk/doublestar/v4 v4.6.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= @@ -486,6 +488,8 @@ github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/daviddengcn/go-colortext v1.0.0 h1:ANqDyC0ys6qCSvuEK7l3g5RaehL/Xck9EX8ATG8oKsE= +github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c= github.com/defenseunicorns/oras-go v1.2.3 h1:PAxO1Ows6gFVRaf7qYReONWbnjYJxfIh7avifw6QruE= github.com/defenseunicorns/oras-go v1.2.3/go.mod h1:Tsn7yEnnTlAgyJztm02IalpzdKWXaFbpiLVYF0F5gKU= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= @@ -808,6 +812,11 @@ github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= +github.com/golangplus/bytes v1.0.0/go.mod h1:AdRaCFwmc/00ZzELMWb01soso6W1R/++O1XL80yAn+A= +github.com/golangplus/fmt v1.0.0/go.mod h1:zpM0OfbMCjPtd2qkTD/jX2MgiFCqklhSUFyDW44gVQE= +github.com/golangplus/testing v1.0.0 h1:+ZeeiKZENNOMkTTELoSySazi+XaEhVO0mb+eanrSEUQ= +github.com/golangplus/testing v1.0.0/go.mod h1:ZDreixUV3YzhoVraIDyOzHrr76p6NUh6k/pPg/Q3gYA= github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -1157,6 +1166,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linkedin/goavro v2.1.0+incompatible/go.mod h1:bBCwI2eGYpUI/4820s67MElg9tdeLbINjLjiM2xZFYM= +github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c= github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs= @@ -1298,6 +1309,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= @@ -2630,6 +2643,8 @@ k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= +k8s.io/component-helpers v0.26.1 h1:Y5h1OYUJTGyHZlSAsc7mcfNsWF08S/MlrQyF/vn93mU= +k8s.io/component-helpers v0.26.1/go.mod h1:jxNTnHb1axLe93MyVuvKj9T/+f4nxBVrj/xf01/UNFk= k8s.io/klog/v2 v2.90.0 h1:VkTxIV/FjRXn1fgNNcKGM8cfmL1Z33ZjXRTVxKCoF5M= k8s.io/klog/v2 v2.90.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= @@ -2683,6 +2698,8 @@ sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= +sigs.k8s.io/kustomize/kustomize/v4 v4.5.7 h1:cDW6AVMl6t/SLuQaezMET8hgnadZGIAr8tUrxFVOrpg= +sigs.k8s.io/kustomize/kustomize/v4 v4.5.7/go.mod h1:VSNKEH9D9d9bLiWEGbS6Xbg/Ih0tgQalmPvntzRxZ/Q= sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= sigs.k8s.io/release-utils v0.7.3 h1:6pS8x6c5RmdUgR9qcg1LO6hjUzuE4Yo9TGZ3DemrZdM= diff --git a/src/cmd/root.go b/src/cmd/root.go index 50d8532c88..51ec570ea7 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -12,6 +12,7 @@ import ( "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/message" + "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/types" "github.com/pterm/pterm" "github.com/spf13/cobra" @@ -33,6 +34,11 @@ var ( var rootCmd = &cobra.Command{ Use: "zarf [COMMAND]", PersistentPreRun: func(cmd *cobra.Command, args []string) { + // Skip for vendor-only commands + if checkVendorOnly() { + return + } + // Don't add the logo to the help command if cmd.Parent() == nil { skipLogFile = true @@ -65,6 +71,11 @@ func Execute() { } func init() { + // Skip for vendor-only commands + if checkVendorOnly() { + return + } + initViper() v.SetDefault(V_LOG_LEVEL, "info") @@ -84,6 +95,32 @@ func init() { rootCmd.PersistentFlags().BoolVar(&config.CommonOptions.Insecure, "insecure", v.GetBool(V_INSECURE), lang.RootCmdFlagInseure) } +// Check if the command is being run as a vendor-only command +func checkVendorOnly() bool { + vendorCmd := []string{ + "kubectl", + "k", + "syft", + "sbom", + "s", + "k9s", + "monitor", + "wait-for", + "wait", + "w", + } + + // Check for "zarf tools " where is in the vendorCmd list + if len(os.Args) > 2 && os.Args[1] == "tools" || os.Args[1] == "t" { + if utils.SliceContains(vendorCmd, os.Args[2]) { + return true + } + } + + // Not a vendor-only command + return false +} + func cliSetup() { config.CliArch = arch diff --git a/src/cmd/tools.go b/src/cmd/tools.go index 1e97df1943..dc3ab51c1b 100644 --- a/src/cmd/tools.go +++ b/src/cmd/tools.go @@ -5,7 +5,9 @@ package cmd import ( + "fmt" "os" + "time" "github.com/anchore/syft/cmd/syft/cli" "github.com/defenseunicorns/zarf/src/config" @@ -14,14 +16,23 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/pki" "github.com/defenseunicorns/zarf/src/pkg/utils" + "github.com/defenseunicorns/zarf/src/pkg/utils/exec" k9s "github.com/derailed/k9s/cmd" craneCmd "github.com/google/go-containerregistry/cmd/crane/cmd" "github.com/google/go-containerregistry/pkg/crane" "github.com/mholt/archiver/v3" "github.com/spf13/cobra" + kubeCLI "k8s.io/component-base/cli" + kubeCmd "k8s.io/kubectl/pkg/cmd" + + // Import to initialize client auth plugins. + _ "k8s.io/client-go/plugin/pkg/client/auth" ) -var subAltNames []string +var ( + subAltNames []string + waitTimeout string +) var toolsCmd = &cobra.Command{ Use: "tools", @@ -157,6 +168,65 @@ var generatePKICmd = &cobra.Command{ }, } +var waitForCmd = &cobra.Command{ + Use: "wait-for {NAMESPACE} {RESOURCE} {NAME} {CONDITION}", + Aliases: []string{"w", "wait"}, + Short: lang.CmdToolsWaitForShort, + Long: lang.CmdToolsWaitForLong, + Example: `zarf tools wait-for default pod my-pod-name ready`, + Args: cobra.ExactArgs(4), + Run: func(cmd *cobra.Command, args []string) { + // Parse the timeout string + timeout, err := time.ParseDuration(waitTimeout) + if err != nil { + message.Fatalf(err, lang.CmdToolsWaitForErrTimeoutString, waitTimeout) + } + + namespace, resource, name, condition := args[0], args[1], args[2], args[3] + zarf, err := utils.GetFinalExecutablePath() + if err != nil { + message.Fatal(err, lang.CmdToolsWaitForErrZarfPath) + } + + expired := time.After(timeout) + + conditionMsg := fmt.Sprintf("Waiting for %s/%s in namespace %s to be %s.", resource, name, namespace, condition) + existMsg := fmt.Sprintf("Waiting for %s/%s in namespace %s to exist.", resource, name, namespace) + spinner := message.NewProgressSpinner(existMsg) + defer spinner.Stop() + + for { + // Delay the check for 1 second + time.Sleep(time.Second) + + select { + case <-expired: + message.Fatal(nil, lang.CmdToolsWaitForErrTimeout) + + default: + spinner.Updatef(existMsg) + // Check if the resource exists. + args := []string{"tools", "kubectl", "get", "-n", namespace, resource, name} + if stdout, stderr, err := exec.Cmd(zarf, args...); err != nil { + message.Debug(stdout, stderr, err) + continue + } + + spinner.Updatef(conditionMsg) + // Wait for the resource to meet the given condition. + args = []string{"tools", "kubectl", "wait", "-n", namespace, resource, name, "--for", "condition=" + condition, "--timeout=" + waitTimeout} + if stdout, stderr, err := exec.Cmd(zarf, args...); err != nil { + message.Debug(stdout, stderr, err) + continue + } + + spinner.Successf(conditionMsg) + os.Exit(0) + } + } + }, +} + func init() { rootCmd.AddCommand(toolsCmd) toolsCmd.AddCommand(archiverCmd) @@ -199,6 +269,30 @@ func init() { } toolsCmd.AddCommand(syftCmd) + + // Add the kubectl command to the tools command + kubectlCmd := kubeCmd.NewDefaultKubectlCommand() + + if err := kubeCLI.RunNoErrOutput(kubectlCmd); err != nil { + // @todo(jeff-mccoy) - Kubectl gets mad about being a subcommand + message.Debug(err) + } + + // If the generate-cli-docs flag is set, just add a stub command + if len(os.Args) > 2 && os.Args[2] == "generate-cli-docs" { + kubectlCmd = &cobra.Command{ + Short: lang.CmdToolsKubectlDocs, + Run: func(cmd *cobra.Command, args []string) {}, + } + } + + kubectlCmd.Use = "kubectl" + kubectlCmd.Aliases = []string{"k"} + + toolsCmd.AddCommand(kubectlCmd) + + toolsCmd.AddCommand(waitForCmd) + waitForCmd.Flags().StringVar(&waitTimeout, "timeout", "5m", lang.CmdToolsWaitForFlagTimeout) } // Wrap the original crane catalog with a zarf specific version diff --git a/src/cmd/viper.go b/src/cmd/viper.go index 65add1400d..d7e3809c8d 100644 --- a/src/cmd/viper.go +++ b/src/cmd/viper.go @@ -64,6 +64,12 @@ func initViper() { } v = viper.New() + + // Skip for vendor-only commands + if checkVendorOnly() { + return + } + // Specify an alternate config file cfgFile := os.Getenv("ZARF_CONFIG") diff --git a/src/config/lang/english.go b/src/config/lang/english.go index 458eb06bba..8c54997b11 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -262,6 +262,16 @@ const ( CmdToolsSbomShort = "Generates a Software Bill of Materials (SBOM) for the given package" CmdToolsSbomErr = "Unable to create sbom (syft) CLI" + CmdToolsWaitForShort = "Waits for a given Kubernetes resource to be ready" + CmdToolsWaitForLong = "By default Zarf will wait for all Kubernetes resources to be ready before completion of component during deployment. \n" + + "This command can be used to wait for a specific resource to exist and be ready that may be created by a Gitops tool or Operator." + CmdToolsWaitForFlagTimeout = "Specify the timeout duration for the wait command." + CmdToolsWaitForErrTimeoutString = "Invalid timeout duration. Please use a valid duration string (e.g. 1s, 2m, 3h)." + CmdToolsWaitForErrTimeout = "Wait timed out." + CmdToolsWaitForErrZarfPath = "Could not locate the current Zarf binary path." + + CmdToolsKubectlDocs = "Kubectl command. See https://kubernetes.io/docs/reference/kubectl/overview/ for more information." + CmdToolsGetCredsShort = "Display a Table of credentials for deployed components. Pass a component name to get a single credential." CmdToolsGetCredsLong = "Display a Table of credentials for deployed components. Pass a component name to get a single credential. i.e. 'zarf tools get-creds registry' " diff --git a/src/test/e2e/20_zarf_init_test.go b/src/test/e2e/20_zarf_init_test.go index 1d042b6ac9..e6afb0141e 100644 --- a/src/test/e2e/20_zarf_init_test.go +++ b/src/test/e2e/20_zarf_init_test.go @@ -33,27 +33,27 @@ func TestZarfInit(t *testing.T) { require.NoError(t, err) // Check that gitea is actually running and healthy - stdOut, _, err := exec.CmdWithContext(ctx, exec.PrintCfg(), "kubectl", "get", "pods", "-l", "app in (gitea)", "-n", "zarf", "-o", "jsonpath={.items[*].status.phase}") + stdOut, _, err := e2e.execZarfCommand("tools", "kubectl", "get", "pods", "-l", "app in (gitea)", "-n", "zarf", "-o", "jsonpath={.items[*].status.phase}") require.NoError(t, err) require.Contains(t, stdOut, "Running") // Check that the logging stack is actually running and healthy - stdOut, _, err = exec.CmdWithContext(ctx, exec.PrintCfg(), "kubectl", "get", "pods", "-l", "app in (loki)", "-n", "zarf", "-o", "jsonpath={.items[*].status.phase}") + stdOut, _, err = e2e.execZarfCommand("tools", "kubectl", "get", "pods", "-l", "app in (loki)", "-n", "zarf", "-o", "jsonpath={.items[*].status.phase}") require.NoError(t, err) require.Contains(t, stdOut, "Running") - stdOut, _, err = exec.CmdWithContext(ctx, exec.PrintCfg(), "kubectl", "get", "pods", "-l", "app.kubernetes.io/name in (grafana)", "-n", "zarf", "-o", "jsonpath={.items[*].status.phase}") + stdOut, _, err = e2e.execZarfCommand("tools", "kubectl", "get", "pods", "-l", "app.kubernetes.io/name in (grafana)", "-n", "zarf", "-o", "jsonpath={.items[*].status.phase}") require.NoError(t, err) require.Contains(t, stdOut, "Running") - stdOut, _, err = exec.CmdWithContext(ctx, exec.PrintCfg(), "kubectl", "get", "pods", "-l", "app.kubernetes.io/name in (promtail)", "-n", "zarf", "-o", "jsonpath={.items[*].status.phase}") + stdOut, _, err = e2e.execZarfCommand("tools", "kubectl", "get", "pods", "-l", "app.kubernetes.io/name in (promtail)", "-n", "zarf", "-o", "jsonpath={.items[*].status.phase}") require.NoError(t, err) require.Contains(t, stdOut, "Running") // Check that the registry is running on the correct NodePort - stdOut, _, err = exec.CmdWithContext(ctx, exec.PrintCfg(), "kubectl", "get", "service", "-n", "zarf", "zarf-docker-registry", "-o=jsonpath='{.spec.ports[*].nodePort}'") + stdOut, _, err = e2e.execZarfCommand("tools", "kubectl", "get", "service", "-n", "zarf", "zarf-docker-registry", "-o=jsonpath='{.spec.ports[*].nodePort}'") require.NoError(t, err) require.Contains(t, stdOut, "31337") // Special sizing-hacking for reducing resources where Kind + CI eats a lot of free cycles (ignore errors) - _, _, _ = exec.CmdWithContext(ctx, exec.PrintCfg(), "kubectl", "scale", "deploy", "-n", "kube-system", "coredns", "--replicas=1") - _, _, _ = exec.CmdWithContext(ctx, exec.PrintCfg(), "kubectl", "scale", "deploy", "-n", "zarf", "agent-hook", "--replicas=1") + _, _, _ = e2e.execZarfCommand("tools", "kubectl", "scale", "deploy", "-n", "kube-system", "coredns", "--replicas=1") + _, _, _ = e2e.execZarfCommand("tools", "kubectl", "scale", "deploy", "-n", "zarf", "agent-hook", "--replicas=1") } diff --git a/src/test/e2e/22_git_and_flux_test.go b/src/test/e2e/22_git_and_flux_test.go index 16552f2691..0487ac4a03 100644 --- a/src/test/e2e/22_git_and_flux_test.go +++ b/src/test/e2e/22_git_and_flux_test.go @@ -11,7 +11,6 @@ import ( "os" "os/exec" "testing" - "time" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/internal/cluster" @@ -116,36 +115,8 @@ func waitFluxPodInfoDeployment(t *testing.T) { stdOut, stdErr, err := e2e.execZarfCommand("package", "deploy", path, "--confirm") require.NoError(t, err, stdOut, stdErr) - var kubectlOut []byte - timeout := time.After(3 * time.Minute) - -timer: - for { - // delay check 3 seconds - time.Sleep(2 * time.Second) - select { - - // on timeout abort - case <-timeout: - t.Error("Timeout waiting for flux podinfo deployment") - - break timer - - // after delay, try running - default: - // Check that flux deployed the podinfo example - kubectlOut, err = exec.Command("kubectl", "wait", "deployment", "-n=podinfo", "podinfo", "--for", "condition=Available=True", "--timeout=3s").Output() - // Log error - if err != nil { - t.Log(string(kubectlOut), err) - } else { - // Otherwise, break the loop and continue - break timer - } - } - } - - assert.Contains(t, string(kubectlOut), "condition met") + kubectlOut, _, _ := e2e.execZarfCommand("tools", "kubectl", "-n=podinfo", "rollout", "status", "deployment/podinfo") + assert.Contains(t, string(kubectlOut), "successfully rolled out") } func testRemovingTagsOnCreate(t *testing.T) { From b42972619af54a03d5a8ccf2664fb8e736abb300 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Sun, 19 Feb 2023 04:44:13 -0600 Subject: [PATCH 02/20] add better vendor side-effect escaping --- src/cmd/root.go | 27 ----------------------- src/cmd/tools.go | 56 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/src/cmd/root.go b/src/cmd/root.go index 51ec570ea7..a5aa0dc488 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -12,7 +12,6 @@ import ( "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/message" - "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/types" "github.com/pterm/pterm" "github.com/spf13/cobra" @@ -95,32 +94,6 @@ func init() { rootCmd.PersistentFlags().BoolVar(&config.CommonOptions.Insecure, "insecure", v.GetBool(V_INSECURE), lang.RootCmdFlagInseure) } -// Check if the command is being run as a vendor-only command -func checkVendorOnly() bool { - vendorCmd := []string{ - "kubectl", - "k", - "syft", - "sbom", - "s", - "k9s", - "monitor", - "wait-for", - "wait", - "w", - } - - // Check for "zarf tools " where is in the vendorCmd list - if len(os.Args) > 2 && os.Args[1] == "tools" || os.Args[1] == "t" { - if utils.SliceContains(vendorCmd, os.Args[2]) { - return true - } - } - - // Not a vendor-only command - return false -} - func cliSetup() { config.CliArch = arch diff --git a/src/cmd/tools.go b/src/cmd/tools.go index dc3ab51c1b..464ce18b1b 100644 --- a/src/cmd/tools.go +++ b/src/cmd/tools.go @@ -270,19 +270,20 @@ func init() { toolsCmd.AddCommand(syftCmd) - // Add the kubectl command to the tools command - kubectlCmd := kubeCmd.NewDefaultKubectlCommand() - - if err := kubeCLI.RunNoErrOutput(kubectlCmd); err != nil { - // @todo(jeff-mccoy) - Kubectl gets mad about being a subcommand - message.Debug(err) + // Kubectl stub command. + kubectlCmd := &cobra.Command{ + Short: lang.CmdToolsKubectlDocs, + Run: func(cmd *cobra.Command, args []string) {}, } - // If the generate-cli-docs flag is set, just add a stub command - if len(os.Args) > 2 && os.Args[2] == "generate-cli-docs" { - kubectlCmd = &cobra.Command{ - Short: lang.CmdToolsKubectlDocs, - Run: func(cmd *cobra.Command, args []string) {}, + // Only load this command if it is being called directly. + if isVendorCmd([]string{"kubectl", "k"}) { + // Add the kubectl command to the tools command. + kubectlCmd = kubeCmd.NewDefaultKubectlCommand() + + if err := kubeCLI.RunNoErrOutput(kubectlCmd); err != nil { + // @todo(jeff-mccoy) - Kubectl gets mad about being a subcommand. + message.Debug(err) } } @@ -338,3 +339,36 @@ func zarfCraneCatalog(cranePlatformOptions *[]crane.Option) *cobra.Command { return craneCatalog } + +// isVendorCmd checks if the command is a vendor command. +func isVendorCmd(cmd []string) bool { + a := os.Args + if len(a) > 2 { + if a[1] == "tools" || a[1] == "t" { + if utils.SliceContains(cmd, a[2]) { + return true + } + } + } + + return false +} + +// Check if the command is being run as a vendor-only command +func checkVendorOnly() bool { + vendorCmd := []string{ + "kubectl", + "k", + "syft", + "sbom", + "s", + "k9s", + "monitor", + "wait-for", + "wait", + "w", + } + + // Check for "zarf tools|t " where is in the vendorCmd list + return isVendorCmd(vendorCmd) +} From 04d48c727f527e204dedbc4409efb995ef64a6a7 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Sun, 19 Feb 2023 19:05:23 -0600 Subject: [PATCH 03/20] move tools to own pkg & add generic network waits --- src/cmd/root.go | 19 +- src/cmd/tools.go | 374 ------------------------------------- src/cmd/tools/archiver.go | 53 ++++++ src/cmd/tools/common.go | 60 ++++++ src/cmd/tools/crane.go | 79 ++++++++ src/cmd/tools/k9s.go | 28 +++ src/cmd/tools/kubectl.go | 40 ++++ src/cmd/tools/syft.go | 28 +++ src/cmd/tools/wait.go | 193 +++++++++++++++++++ src/cmd/tools/zarf.go | 103 ++++++++++ src/cmd/version.go | 2 +- src/cmd/viper.go | 3 +- src/config/config.go | 3 + src/config/lang/english.go | 15 +- 14 files changed, 610 insertions(+), 390 deletions(-) delete mode 100644 src/cmd/tools.go create mode 100644 src/cmd/tools/archiver.go create mode 100644 src/cmd/tools/common.go create mode 100644 src/cmd/tools/crane.go create mode 100644 src/cmd/tools/k9s.go create mode 100644 src/cmd/tools/kubectl.go create mode 100644 src/cmd/tools/syft.go create mode 100644 src/cmd/tools/wait.go create mode 100644 src/cmd/tools/zarf.go diff --git a/src/cmd/root.go b/src/cmd/root.go index a5aa0dc488..28b5e80654 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -9,6 +9,7 @@ import ( "os" "strings" + "github.com/defenseunicorns/zarf/src/cmd/tools" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/message" @@ -19,9 +20,8 @@ import ( ) var ( - skipLogFile bool - logLevel string - arch string + logLevel string + arch string // Default global config for the CLI pkgConfig = types.PackagerConfig{} @@ -34,13 +34,13 @@ var rootCmd = &cobra.Command{ Use: "zarf [COMMAND]", PersistentPreRun: func(cmd *cobra.Command, args []string) { // Skip for vendor-only commands - if checkVendorOnly() { + if tools.CheckVendorOnly() { return } // Don't add the logo to the help command if cmd.Parent() == nil { - skipLogFile = true + config.SkipLogFile = true } cliSetup() }, @@ -70,8 +70,11 @@ func Execute() { } func init() { + // Add the tools commands + tools.Include(rootCmd) + // Skip for vendor-only commands - if checkVendorOnly() { + if tools.CheckVendorOnly() { return } @@ -87,7 +90,7 @@ func init() { rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", v.GetString(V_LOG_LEVEL), lang.RootCmdFlagLogLevel) rootCmd.PersistentFlags().StringVarP(&arch, "architecture", "a", v.GetString(V_ARCHITECTURE), lang.RootCmdFlagArch) - rootCmd.PersistentFlags().BoolVar(&skipLogFile, "no-log-file", v.GetBool(V_NO_LOG_FILE), lang.RootCmdFlagSkipLogFile) + rootCmd.PersistentFlags().BoolVar(&config.SkipLogFile, "no-log-file", v.GetBool(V_NO_LOG_FILE), lang.RootCmdFlagSkipLogFile) rootCmd.PersistentFlags().BoolVar(&message.NoProgress, "no-progress", v.GetBool(V_NO_PROGRESS), lang.RootCmdFlagNoProgress) rootCmd.PersistentFlags().StringVar(&config.CommonOptions.CachePath, "zarf-cache", v.GetString(V_ZARF_CACHE), lang.RootCmdFlagCachePath) rootCmd.PersistentFlags().StringVar(&config.CommonOptions.TempDirectory, "tmpdir", v.GetString(V_TMP_DIR), lang.RootCmdFlagTempDir) @@ -120,7 +123,7 @@ func cliSetup() { message.NoProgress = true } - if !skipLogFile { + if !config.SkipLogFile { message.UseLogFile() } } diff --git a/src/cmd/tools.go b/src/cmd/tools.go deleted file mode 100644 index 464ce18b1b..0000000000 --- a/src/cmd/tools.go +++ /dev/null @@ -1,374 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package cmd contains the CLI commands for Zarf. -package cmd - -import ( - "fmt" - "os" - "time" - - "github.com/anchore/syft/cmd/syft/cli" - "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/config/lang" - "github.com/defenseunicorns/zarf/src/internal/cluster" - "github.com/defenseunicorns/zarf/src/pkg/message" - "github.com/defenseunicorns/zarf/src/pkg/pki" - "github.com/defenseunicorns/zarf/src/pkg/utils" - "github.com/defenseunicorns/zarf/src/pkg/utils/exec" - k9s "github.com/derailed/k9s/cmd" - craneCmd "github.com/google/go-containerregistry/cmd/crane/cmd" - "github.com/google/go-containerregistry/pkg/crane" - "github.com/mholt/archiver/v3" - "github.com/spf13/cobra" - kubeCLI "k8s.io/component-base/cli" - kubeCmd "k8s.io/kubectl/pkg/cmd" - - // Import to initialize client auth plugins. - _ "k8s.io/client-go/plugin/pkg/client/auth" -) - -var ( - subAltNames []string - waitTimeout string -) - -var toolsCmd = &cobra.Command{ - Use: "tools", - Aliases: []string{"t"}, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - skipLogFile = true - cliSetup() - }, - Short: lang.CmdToolsShort, -} - -var archiverCmd = &cobra.Command{ - Use: "archiver", - Aliases: []string{"a"}, - Short: lang.CmdToolsArchiverShort, -} - -var archiverCompressCmd = &cobra.Command{ - Use: "compress {SOURCES} {ARCHIVE}", - Aliases: []string{"c"}, - Short: lang.CmdToolsArchiverCompressShort, - Args: cobra.MinimumNArgs(2), - Run: func(cmd *cobra.Command, args []string) { - sourceFiles, destinationArchive := args[:len(args)-1], args[len(args)-1] - err := archiver.Archive(sourceFiles, destinationArchive) - if err != nil { - message.Fatal(err, lang.CmdToolsArchiverCompressErr) - } - }, -} - -var archiverDecompressCmd = &cobra.Command{ - Use: "decompress {ARCHIVE} {DESTINATION}", - Aliases: []string{"d"}, - Short: lang.CmdToolsArchiverDecompressShort, - Args: cobra.ExactArgs(2), - Run: func(cmd *cobra.Command, args []string) { - sourceArchive, destinationPath := args[0], args[1] - err := archiver.Unarchive(sourceArchive, destinationPath) - if err != nil { - message.Fatal(err, lang.CmdToolsArchiverDecompressErr) - } - }, -} - -var registryCmd = &cobra.Command{ - Use: "registry", - Aliases: []string{"r", "crane"}, - Short: lang.CmdToolsRegistryShort, -} - -var readCredsCmd = &cobra.Command{ - Use: "get-git-password", - Hidden: true, - Short: lang.CmdToolsGetGitPasswdShort, - Long: lang.CmdToolsGetGitPasswdLong, - Run: func(cmd *cobra.Command, args []string) { - state, err := cluster.NewClusterOrDie().LoadZarfState() - if err != nil || state.Distro == "" { - // If no distro the zarf secret did not load properly - message.Fatalf(nil, lang.ErrLoadState) - } - - message.Note(lang.CmdToolsGetGitPasswdInfo) - message.Warn(lang.CmdToolGetGitDeprecation) - utils.PrintComponentCredential(state, "git") - }, -} - -var readAllCredsCmd = &cobra.Command{ - Use: "get-creds", - Short: lang.CmdToolsGetCredsShort, - Long: lang.CmdToolsGetCredsLong, - Aliases: []string{"gc"}, - Args: cobra.MaximumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { - state, err := cluster.NewClusterOrDie().LoadZarfState() - if err != nil || state.Distro == "" { - // If no distro the zarf secret did not load properly - message.Fatalf(nil, lang.ErrLoadState) - } - - if len(args) > 0 { - // If a component name is provided, only show that component's credentials - utils.PrintComponentCredential(state, args[0]) - } else { - utils.PrintCredentialTable(state, nil) - } - }, -} - -var k9sCmd = &cobra.Command{ - Use: "monitor", - Aliases: []string{"m", "k9s"}, - Short: lang.CmdToolsMonitorShort, - Run: func(cmd *cobra.Command, args []string) { - // Hack to make k9s think it's all alone - os.Args = []string{os.Args[0]} - k9s.Execute() - }, -} - -var clearCacheCmd = &cobra.Command{ - Use: "clear-cache", - Aliases: []string{"c"}, - Short: lang.CmdToolsClearCacheShort, - Run: func(cmd *cobra.Command, args []string) { - message.Debugf("Cache directory set to: %s", config.GetAbsCachePath()) - if err := os.RemoveAll(config.GetAbsCachePath()); err != nil { - message.Fatalf(err, lang.CmdToolsClearCacheErr, config.GetAbsCachePath()) - } - message.SuccessF(lang.CmdToolsClearCacheSuccess, config.GetAbsCachePath()) - }, -} - -var generatePKICmd = &cobra.Command{ - Use: "gen-pki {HOST}", - Aliases: []string{"pki"}, - Short: lang.CmdToolsGenPkiShort, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - pki := pki.GeneratePKI(args[0], subAltNames...) - if err := os.WriteFile("tls.ca", pki.CA, 0644); err != nil { - message.Fatalf(err, lang.ErrWritingFile, "tls.ca", err.Error()) - } - if err := os.WriteFile("tls.crt", pki.Cert, 0644); err != nil { - message.Fatalf(err, lang.ErrWritingFile, "tls.crt", err.Error()) - } - if err := os.WriteFile("tls.key", pki.Key, 0600); err != nil { - message.Fatalf(err, lang.ErrWritingFile, "tls.key", err.Error()) - } - message.SuccessF(lang.CmdToolsGenPkiSuccess, args[0]) - }, -} - -var waitForCmd = &cobra.Command{ - Use: "wait-for {NAMESPACE} {RESOURCE} {NAME} {CONDITION}", - Aliases: []string{"w", "wait"}, - Short: lang.CmdToolsWaitForShort, - Long: lang.CmdToolsWaitForLong, - Example: `zarf tools wait-for default pod my-pod-name ready`, - Args: cobra.ExactArgs(4), - Run: func(cmd *cobra.Command, args []string) { - // Parse the timeout string - timeout, err := time.ParseDuration(waitTimeout) - if err != nil { - message.Fatalf(err, lang.CmdToolsWaitForErrTimeoutString, waitTimeout) - } - - namespace, resource, name, condition := args[0], args[1], args[2], args[3] - zarf, err := utils.GetFinalExecutablePath() - if err != nil { - message.Fatal(err, lang.CmdToolsWaitForErrZarfPath) - } - - expired := time.After(timeout) - - conditionMsg := fmt.Sprintf("Waiting for %s/%s in namespace %s to be %s.", resource, name, namespace, condition) - existMsg := fmt.Sprintf("Waiting for %s/%s in namespace %s to exist.", resource, name, namespace) - spinner := message.NewProgressSpinner(existMsg) - defer spinner.Stop() - - for { - // Delay the check for 1 second - time.Sleep(time.Second) - - select { - case <-expired: - message.Fatal(nil, lang.CmdToolsWaitForErrTimeout) - - default: - spinner.Updatef(existMsg) - // Check if the resource exists. - args := []string{"tools", "kubectl", "get", "-n", namespace, resource, name} - if stdout, stderr, err := exec.Cmd(zarf, args...); err != nil { - message.Debug(stdout, stderr, err) - continue - } - - spinner.Updatef(conditionMsg) - // Wait for the resource to meet the given condition. - args = []string{"tools", "kubectl", "wait", "-n", namespace, resource, name, "--for", "condition=" + condition, "--timeout=" + waitTimeout} - if stdout, stderr, err := exec.Cmd(zarf, args...); err != nil { - message.Debug(stdout, stderr, err) - continue - } - - spinner.Successf(conditionMsg) - os.Exit(0) - } - } - }, -} - -func init() { - rootCmd.AddCommand(toolsCmd) - toolsCmd.AddCommand(archiverCmd) - toolsCmd.AddCommand(readCredsCmd) - toolsCmd.AddCommand(k9sCmd) - toolsCmd.AddCommand(registryCmd) - toolsCmd.AddCommand(readAllCredsCmd) - - toolsCmd.AddCommand(clearCacheCmd) - clearCacheCmd.Flags().StringVar(&config.CommonOptions.CachePath, "zarf-cache", config.ZarfDefaultCachePath, lang.CmdToolsClearCacheFlagCachePath) - - toolsCmd.AddCommand(generatePKICmd) - generatePKICmd.Flags().StringArrayVar(&subAltNames, "sub-alt-name", []string{}, lang.CmdToolsGenPkiFlagAltName) - - archiverCmd.AddCommand(archiverCompressCmd) - archiverCmd.AddCommand(archiverDecompressCmd) - - cranePlatformOptions := config.GetCraneOptions(false) - - craneLogin := craneCmd.NewCmdAuthLogin() - craneLogin.Example = "" - - registryCmd.AddCommand(craneLogin) - registryCmd.AddCommand(craneCmd.NewCmdPull(&cranePlatformOptions)) - registryCmd.AddCommand(craneCmd.NewCmdPush(&cranePlatformOptions)) - registryCmd.AddCommand(craneCmd.NewCmdCopy(&cranePlatformOptions)) - registryCmd.AddCommand(zarfCraneCatalog(&cranePlatformOptions)) - - syftCmd, err := cli.New() - if err != nil { - message.Fatal(err, lang.CmdToolsSbomErr) - } - syftCmd.Use = "sbom" - syftCmd.Short = lang.CmdToolsSbomShort - syftCmd.Aliases = []string{"s", "syft"} - syftCmd.Example = "" - - for _, subCmd := range syftCmd.Commands() { - subCmd.Example = "" - } - - toolsCmd.AddCommand(syftCmd) - - // Kubectl stub command. - kubectlCmd := &cobra.Command{ - Short: lang.CmdToolsKubectlDocs, - Run: func(cmd *cobra.Command, args []string) {}, - } - - // Only load this command if it is being called directly. - if isVendorCmd([]string{"kubectl", "k"}) { - // Add the kubectl command to the tools command. - kubectlCmd = kubeCmd.NewDefaultKubectlCommand() - - if err := kubeCLI.RunNoErrOutput(kubectlCmd); err != nil { - // @todo(jeff-mccoy) - Kubectl gets mad about being a subcommand. - message.Debug(err) - } - } - - kubectlCmd.Use = "kubectl" - kubectlCmd.Aliases = []string{"k"} - - toolsCmd.AddCommand(kubectlCmd) - - toolsCmd.AddCommand(waitForCmd) - waitForCmd.Flags().StringVar(&waitTimeout, "timeout", "5m", lang.CmdToolsWaitForFlagTimeout) -} - -// Wrap the original crane catalog with a zarf specific version -func zarfCraneCatalog(cranePlatformOptions *[]crane.Option) *cobra.Command { - craneCatalog := craneCmd.NewCmdCatalog(cranePlatformOptions) - - eg := ` # list the repos internal to Zarf - $ zarf tools registry catalog - - # list the repos for reg.example.com - $ zarf tools registry catalog reg.example.com` - - craneCatalog.Example = eg - craneCatalog.Args = nil - - originalCatalogFn := craneCatalog.RunE - - craneCatalog.RunE = func(cmd *cobra.Command, args []string) error { - if len(args) > 0 { - return originalCatalogFn(cmd, args) - } - - // Load Zarf state - zarfState, err := cluster.NewClusterOrDie().LoadZarfState() - if err != nil { - return err - } - - // Open a tunnel to the Zarf registry - tunnelReg, err := cluster.NewZarfTunnel() - if err != nil { - return err - } - tunnelReg.Connect(cluster.ZarfRegistry, false) - - // Add the correct authentication to the crane command options - authOption := config.GetCraneAuthOption(zarfState.RegistryInfo.PullUsername, zarfState.RegistryInfo.PullPassword) - *cranePlatformOptions = append(*cranePlatformOptions, authOption) - registryEndpoint := tunnelReg.Endpoint() - - return originalCatalogFn(cmd, []string{registryEndpoint}) - } - - return craneCatalog -} - -// isVendorCmd checks if the command is a vendor command. -func isVendorCmd(cmd []string) bool { - a := os.Args - if len(a) > 2 { - if a[1] == "tools" || a[1] == "t" { - if utils.SliceContains(cmd, a[2]) { - return true - } - } - } - - return false -} - -// Check if the command is being run as a vendor-only command -func checkVendorOnly() bool { - vendorCmd := []string{ - "kubectl", - "k", - "syft", - "sbom", - "s", - "k9s", - "monitor", - "wait-for", - "wait", - "w", - } - - // Check for "zarf tools|t " where is in the vendorCmd list - return isVendorCmd(vendorCmd) -} diff --git a/src/cmd/tools/archiver.go b/src/cmd/tools/archiver.go new file mode 100644 index 0000000000..5072246301 --- /dev/null +++ b/src/cmd/tools/archiver.go @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package tools contains the CLI commands for Zarf. +package tools + +import ( + "github.com/defenseunicorns/zarf/src/config/lang" + "github.com/defenseunicorns/zarf/src/pkg/message" + "github.com/mholt/archiver/v3" + "github.com/spf13/cobra" +) + +var archiverCmd = &cobra.Command{ + Use: "archiver", + Aliases: []string{"a"}, + Short: lang.CmdToolsArchiverShort, +} + +var archiverCompressCmd = &cobra.Command{ + Use: "compress {SOURCES} {ARCHIVE}", + Aliases: []string{"c"}, + Short: lang.CmdToolsArchiverCompressShort, + Args: cobra.MinimumNArgs(2), + Run: func(cmd *cobra.Command, args []string) { + sourceFiles, destinationArchive := args[:len(args)-1], args[len(args)-1] + err := archiver.Archive(sourceFiles, destinationArchive) + if err != nil { + message.Fatal(err, lang.CmdToolsArchiverCompressErr) + } + }, +} + +var archiverDecompressCmd = &cobra.Command{ + Use: "decompress {ARCHIVE} {DESTINATION}", + Aliases: []string{"d"}, + Short: lang.CmdToolsArchiverDecompressShort, + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + sourceArchive, destinationPath := args[0], args[1] + err := archiver.Unarchive(sourceArchive, destinationPath) + if err != nil { + message.Fatal(err, lang.CmdToolsArchiverDecompressErr) + } + }, +} + +func init() { + toolsCmd.AddCommand(archiverCmd) + + archiverCmd.AddCommand(archiverCompressCmd) + archiverCmd.AddCommand(archiverDecompressCmd) +} diff --git a/src/cmd/tools/common.go b/src/cmd/tools/common.go new file mode 100644 index 0000000000..83f20fac9a --- /dev/null +++ b/src/cmd/tools/common.go @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package tools contains the CLI commands for Zarf. +package tools + +import ( + "os" + + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/config/lang" + "github.com/defenseunicorns/zarf/src/pkg/utils" + "github.com/spf13/cobra" +) + +var toolsCmd = &cobra.Command{ + Use: "tools", + Aliases: []string{"t"}, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + config.SkipLogFile = true + }, + Short: lang.CmdToolsShort, +} + +func Include(rootCmd *cobra.Command) { + rootCmd.AddCommand(toolsCmd) +} + +// Check if the command is being run as a vendor-only command +func CheckVendorOnly() bool { + vendorCmd := []string{ + "kubectl", + "k", + "syft", + "sbom", + "s", + "k9s", + "monitor", + "wait-for", + "wait", + "w", + } + + // Check for "zarf tools|t " where is in the vendorCmd list + return isVendorCmd(vendorCmd) +} + +// isVendorCmd checks if the command is a vendor command. +func isVendorCmd(cmd []string) bool { + a := os.Args + if len(a) > 2 { + if a[1] == "tools" || a[1] == "t" { + if utils.SliceContains(cmd, a[2]) { + return true + } + } + } + + return false +} diff --git a/src/cmd/tools/crane.go b/src/cmd/tools/crane.go new file mode 100644 index 0000000000..10e666b7f1 --- /dev/null +++ b/src/cmd/tools/crane.go @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package tools contains the CLI commands for Zarf. +package tools + +import ( + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/config/lang" + "github.com/defenseunicorns/zarf/src/internal/cluster" + craneCmd "github.com/google/go-containerregistry/cmd/crane/cmd" + "github.com/google/go-containerregistry/pkg/crane" + "github.com/spf13/cobra" +) + +func init() { + cranePlatformOptions := config.GetCraneOptions(false) + + craneLogin := craneCmd.NewCmdAuthLogin() + craneLogin.Example = "" + + registryCmd := &cobra.Command{ + Use: "registry", + Aliases: []string{"r", "crane"}, + Short: lang.CmdToolsRegistryShort, + } + + toolsCmd.AddCommand(registryCmd) + + registryCmd.AddCommand(craneLogin) + registryCmd.AddCommand(craneCmd.NewCmdPull(&cranePlatformOptions)) + registryCmd.AddCommand(craneCmd.NewCmdPush(&cranePlatformOptions)) + registryCmd.AddCommand(craneCmd.NewCmdCopy(&cranePlatformOptions)) + registryCmd.AddCommand(zarfCraneCatalog(&cranePlatformOptions)) +} + +// Wrap the original crane catalog with a zarf specific version +func zarfCraneCatalog(cranePlatformOptions *[]crane.Option) *cobra.Command { + craneCatalog := craneCmd.NewCmdCatalog(cranePlatformOptions) + + eg := ` # list the repos internal to Zarf + $ zarf tools registry catalog + + # list the repos for reg.example.com + $ zarf tools registry catalog reg.example.com` + + craneCatalog.Example = eg + craneCatalog.Args = nil + + originalCatalogFn := craneCatalog.RunE + + craneCatalog.RunE = func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + return originalCatalogFn(cmd, args) + } + + // Load Zarf state + zarfState, err := cluster.NewClusterOrDie().LoadZarfState() + if err != nil { + return err + } + + // Open a tunnel to the Zarf registry + tunnelReg, err := cluster.NewZarfTunnel() + if err != nil { + return err + } + tunnelReg.Connect(cluster.ZarfRegistry, false) + + // Add the correct authentication to the crane command options + authOption := config.GetCraneAuthOption(zarfState.RegistryInfo.PullUsername, zarfState.RegistryInfo.PullPassword) + *cranePlatformOptions = append(*cranePlatformOptions, authOption) + registryEndpoint := tunnelReg.Endpoint() + + return originalCatalogFn(cmd, []string{registryEndpoint}) + } + + return craneCatalog +} diff --git a/src/cmd/tools/k9s.go b/src/cmd/tools/k9s.go new file mode 100644 index 0000000000..8d25d01617 --- /dev/null +++ b/src/cmd/tools/k9s.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package tools contains the CLI commands for Zarf. +package tools + +import ( + "os" + + "github.com/defenseunicorns/zarf/src/config/lang" + k9s "github.com/derailed/k9s/cmd" + "github.com/spf13/cobra" +) + +func init() { + k9sCmd := &cobra.Command{ + Use: "monitor", + Aliases: []string{"m", "k9s"}, + Short: lang.CmdToolsMonitorShort, + Run: func(cmd *cobra.Command, args []string) { + // Hack to make k9s think it's all alone + os.Args = []string{os.Args[0]} + k9s.Execute() + }, + } + + toolsCmd.AddCommand(k9sCmd) +} diff --git a/src/cmd/tools/kubectl.go b/src/cmd/tools/kubectl.go new file mode 100644 index 0000000000..eb2d5e5cc6 --- /dev/null +++ b/src/cmd/tools/kubectl.go @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package tools contains the CLI commands for Zarf. +package tools + +import ( + "github.com/defenseunicorns/zarf/src/config/lang" + "github.com/defenseunicorns/zarf/src/pkg/message" + "github.com/spf13/cobra" + kubeCLI "k8s.io/component-base/cli" + kubeCmd "k8s.io/kubectl/pkg/cmd" + + // Import to initialize client auth plugins. + _ "k8s.io/client-go/plugin/pkg/client/auth" +) + +func init() { + // Kubectl stub command. + kubectlCmd := &cobra.Command{ + Short: lang.CmdToolsKubectlDocs, + Run: func(cmd *cobra.Command, args []string) {}, + } + + // Only load this command if it is being called directly. + if isVendorCmd([]string{"kubectl", "k"}) { + // Add the kubectl command to the tools command. + kubectlCmd = kubeCmd.NewDefaultKubectlCommand() + + if err := kubeCLI.RunNoErrOutput(kubectlCmd); err != nil { + // @todo(jeff-mccoy) - Kubectl gets mad about being a subcommand. + message.Debug(err) + } + } + + kubectlCmd.Use = "kubectl" + kubectlCmd.Aliases = []string{"k"} + + toolsCmd.AddCommand(kubectlCmd) +} diff --git a/src/cmd/tools/syft.go b/src/cmd/tools/syft.go new file mode 100644 index 0000000000..f40dde4a15 --- /dev/null +++ b/src/cmd/tools/syft.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package tools contains the CLI commands for Zarf. +package tools + +import ( + syftCLI "github.com/anchore/syft/cmd/syft/cli" + "github.com/defenseunicorns/zarf/src/config/lang" + "github.com/defenseunicorns/zarf/src/pkg/message" +) + +func init() { + syftCmd, err := syftCLI.New() + if err != nil { + message.Fatal(err, lang.CmdToolsSbomErr) + } + syftCmd.Use = "sbom" + syftCmd.Short = lang.CmdToolsSbomShort + syftCmd.Aliases = []string{"s", "syft"} + syftCmd.Example = "" + + for _, subCmd := range syftCmd.Commands() { + subCmd.Example = "" + } + + toolsCmd.AddCommand(syftCmd) +} diff --git a/src/cmd/tools/wait.go b/src/cmd/tools/wait.go new file mode 100644 index 0000000000..d152056d1f --- /dev/null +++ b/src/cmd/tools/wait.go @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package tools contains the CLI commands for Zarf. +package tools + +import ( + "fmt" + "net" + "net/http" + "strconv" + "time" + + "github.com/defenseunicorns/zarf/src/config/lang" + "github.com/defenseunicorns/zarf/src/pkg/message" + "github.com/defenseunicorns/zarf/src/pkg/utils" + "github.com/defenseunicorns/zarf/src/pkg/utils/exec" + "github.com/spf13/cobra" + + // Import to initialize client auth plugins. + _ "k8s.io/client-go/plugin/pkg/client/auth" +) + +var ( + waitTimeout string + waitNamespace string +) + +var waitForCmd = &cobra.Command{ + Use: "wait-for {RESOURCE|PROTOCOL} {NAME|URI} {CONDITION}", + Aliases: []string{"w", "wait"}, + Short: lang.CmdToolsWaitForShort, + Long: lang.CmdToolsWaitForLong, + Example: ` Wait for Kubernetes resources: + zarf tools wait-for pod my-pod-name ready -n default wait for pod my-pod-name in namespace default to be ready + zarf tools wait-for p cool-pod-name ready -n cool wait for pod (using p alias) cool-pod-name in namespace cool to be ready + zarf tools wait-for deployment podinfo available -n podinfo wait for deployment podinfo in namespace podinfo to be available + zarf tools wait-for svc zarf-docker-registry exists -n zarf wait for service zarf-docker-registry in namespace zarf to exist + zarf tools wait-for svc zarf-docker-registry -n zarf same as above, except exists is the default condition + zarf tools wati-for crd addons.k3s.cattle.io wait for crd addons.k3s.cattle.io to exist + + Wait for network endpoints: + zarf tools wait-for http localhost:8080 200 wait for a 200 response from http://localhost:8080 + zarf tools wait-for tcp localhost:8080 wait for a connection to be established on localhost:8080 + zarf tools wait-for https 1.1.1.1 200 wait for a 200 response from https://1.1.1.1 + `, + Args: cobra.MinimumNArgs(2), + Run: func(cmd *cobra.Command, args []string) { + // Parse the timeout string + timeout, err := time.ParseDuration(waitTimeout) + if err != nil { + message.Fatalf(err, lang.CmdToolsWaitForErrTimeoutString, waitTimeout) + } + + // Parse the resource type and name. + resource, name := args[0], args[1] + + // Condition is optional, default to "exists". + condition := "" + if len(args) > 2 { + condition = args[2] + } + + // Handle network endpoints. + switch resource { + case "http", "https", "tcp": + waitForNetworkEndpoint(resource, name, condition, timeout) + return + } + + // Get the Zarf executable path. + zarf, err := utils.GetFinalExecutablePath() + if err != nil { + message.Fatal(err, lang.CmdToolsWaitForErrZarfPath) + } + + // Set the timeout for the wait-for command. + expired := time.After(timeout) + + // Set the custom message for optional namespace. + namespaceMsg := "" + if waitNamespace != "" { + namespaceMsg = fmt.Sprintf(" in namespace %s", waitNamespace) + } + + // Setup the spinner messages. + conditionMsg := fmt.Sprintf("Waiting for %s/%s%s to be %s.", resource, name, namespaceMsg, condition) + existMsg := fmt.Sprintf("Waiting for %s/%s%s to exist.", resource, name, namespaceMsg) + spinner := message.NewProgressSpinner(existMsg) + defer spinner.Stop() + + for { + // Delay the check for 1 second + time.Sleep(time.Second) + + select { + case <-expired: + message.Fatal(nil, lang.CmdToolsWaitForErrTimeout) + + default: + spinner.Updatef(existMsg) + // Check if the resource exists. + args := []string{"tools", "kubectl", "get", "-n", waitNamespace, resource, name} + if stdout, stderr, err := exec.Cmd(zarf, args...); err != nil { + message.Debug(stdout, stderr, err) + continue + } + + // If only checking for existence, exit here + switch condition { + case "", "exist", "exists": + spinner.Success() + return + } + + spinner.Updatef(conditionMsg) + // Wait for the resource to meet the given condition. + args = []string{"tools", "kubectl", "wait", "-n", waitNamespace, + resource, name, "--for", "condition=" + condition, + "--timeout=" + waitTimeout} + if stdout, stderr, err := exec.Cmd(zarf, args...); err != nil { + message.Debug(stdout, stderr, err) + continue + } + + spinner.Successf(conditionMsg) + return + } + } + }, +} + +func waitForNetworkEndpoint(resource, name, condition string, timeout time.Duration) { + // Set the timeout for the wait-for command. + expired := time.After(timeout) + + // Setup the spinner messages. + if condition == "" { + condition = "available" + } + spinner := message.NewProgressSpinner("Waiting for network endpoint %s://%s to be %s.", resource, name, condition) + defer spinner.Stop() + + for { + // Delay the check for 1 second + time.Sleep(time.Second) + + select { + case <-expired: + message.Fatal(nil, lang.CmdToolsWaitForErrTimeout) + + default: + switch resource { + + case "http", "https": + // Handle HTTP and HTTPS endpoints. + url := fmt.Sprintf("%s://%s", resource, name) + + // Convert the condition to an int and check if it's a valid HTTP status code. + code, err := strconv.Atoi(condition) + if err != nil || http.StatusText(code) == "" { + message.Fatalf(err, lang.CmdToolsWaitForErrConditionString, condition) + } + + // Try to get the URL and check the status code. + resp, err := http.Get(url) + if err != nil || resp.StatusCode != code { + message.Debug(err) + continue + } + + default: + // Fallback to any generic protocol using net.Dial + conn, err := net.Dial(resource, name) + if err != nil { + message.Debug(err) + continue + } + defer conn.Close() + } + + spinner.Success() + return + } + + } +} + +func init() { + toolsCmd.AddCommand(waitForCmd) + waitForCmd.Flags().StringVar(&waitTimeout, "timeout", "5m", lang.CmdToolsWaitForFlagTimeout) + waitForCmd.Flags().StringVarP(&waitNamespace, "namespace", "n", "", lang.CmdToolsWaitForFlagNamespace) +} diff --git a/src/cmd/tools/zarf.go b/src/cmd/tools/zarf.go new file mode 100644 index 0000000000..c1a8c6e9bd --- /dev/null +++ b/src/cmd/tools/zarf.go @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package tools contains the CLI commands for Zarf. +package tools + +import ( + "os" + + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/config/lang" + "github.com/defenseunicorns/zarf/src/internal/cluster" + "github.com/defenseunicorns/zarf/src/pkg/message" + "github.com/defenseunicorns/zarf/src/pkg/pki" + "github.com/defenseunicorns/zarf/src/pkg/utils" + "github.com/spf13/cobra" +) + +func init() { + var subAltNames []string + + readCredsCmd := &cobra.Command{ + Use: "get-git-password", + Hidden: true, + Short: lang.CmdToolsGetGitPasswdShort, + Long: lang.CmdToolsGetGitPasswdLong, + Run: func(cmd *cobra.Command, args []string) { + state, err := cluster.NewClusterOrDie().LoadZarfState() + if err != nil || state.Distro == "" { + // If no distro the zarf secret did not load properly + message.Fatalf(nil, lang.ErrLoadState) + } + + message.Note(lang.CmdToolsGetGitPasswdInfo) + message.Warn(lang.CmdToolGetGitDeprecation) + utils.PrintComponentCredential(state, "git") + }, + } + + readAllCredsCmd := &cobra.Command{ + Use: "get-creds", + Short: lang.CmdToolsGetCredsShort, + Long: lang.CmdToolsGetCredsLong, + Aliases: []string{"gc"}, + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + state, err := cluster.NewClusterOrDie().LoadZarfState() + if err != nil || state.Distro == "" { + // If no distro the zarf secret did not load properly + message.Fatalf(nil, lang.ErrLoadState) + } + + if len(args) > 0 { + // If a component name is provided, only show that component's credentials + utils.PrintComponentCredential(state, args[0]) + } else { + utils.PrintCredentialTable(state, nil) + } + }, + } + + clearCacheCmd := &cobra.Command{ + Use: "clear-cache", + Aliases: []string{"c"}, + Short: lang.CmdToolsClearCacheShort, + Run: func(cmd *cobra.Command, args []string) { + message.Debugf("Cache directory set to: %s", config.GetAbsCachePath()) + if err := os.RemoveAll(config.GetAbsCachePath()); err != nil { + message.Fatalf(err, lang.CmdToolsClearCacheErr, config.GetAbsCachePath()) + } + message.SuccessF(lang.CmdToolsClearCacheSuccess, config.GetAbsCachePath()) + }, + } + + generatePKICmd := &cobra.Command{ + Use: "gen-pki {HOST}", + Aliases: []string{"pki"}, + Short: lang.CmdToolsGenPkiShort, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + pki := pki.GeneratePKI(args[0], subAltNames...) + if err := os.WriteFile("tls.ca", pki.CA, 0644); err != nil { + message.Fatalf(err, lang.ErrWritingFile, "tls.ca", err.Error()) + } + if err := os.WriteFile("tls.crt", pki.Cert, 0644); err != nil { + message.Fatalf(err, lang.ErrWritingFile, "tls.crt", err.Error()) + } + if err := os.WriteFile("tls.key", pki.Key, 0600); err != nil { + message.Fatalf(err, lang.ErrWritingFile, "tls.key", err.Error()) + } + message.SuccessF(lang.CmdToolsGenPkiSuccess, args[0]) + }, + } + + toolsCmd.AddCommand(readCredsCmd) + toolsCmd.AddCommand(readAllCredsCmd) + + toolsCmd.AddCommand(clearCacheCmd) + clearCacheCmd.Flags().StringVar(&config.CommonOptions.CachePath, "zarf-cache", config.ZarfDefaultCachePath, lang.CmdToolsClearCacheFlagCachePath) + + toolsCmd.AddCommand(generatePKICmd) + generatePKICmd.Flags().StringArrayVar(&subAltNames, "sub-alt-name", []string{}, lang.CmdToolsGenPkiFlagAltName) +} diff --git a/src/cmd/version.go b/src/cmd/version.go index ccaef96e28..8b3aa30e30 100644 --- a/src/cmd/version.go +++ b/src/cmd/version.go @@ -16,7 +16,7 @@ var versionCmd = &cobra.Command{ Use: "version", Aliases: []string{"v"}, PersistentPreRun: func(cmd *cobra.Command, args []string) { - skipLogFile = true + config.SkipLogFile = true cliSetup() }, Short: lang.CmdVersionShort, diff --git a/src/cmd/viper.go b/src/cmd/viper.go index d7e3809c8d..c8b0221c74 100644 --- a/src/cmd/viper.go +++ b/src/cmd/viper.go @@ -7,6 +7,7 @@ package cmd import ( "os" + "github.com/defenseunicorns/zarf/src/cmd/tools" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/spf13/viper" @@ -66,7 +67,7 @@ func initViper() { v = viper.New() // Skip for vendor-only commands - if checkVendorOnly() { + if tools.CheckVendorOnly() { return } diff --git a/src/config/config.go b/src/config/config.go index b7d2c35a9d..26a6875912 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -79,6 +79,9 @@ var ( // Dirty Solution to getting the real time deployedComponents components. deployedComponents []types.DeployedComponent + // SkipLogFile is a flag to skip logging to a file + SkipLogFile bool + SGetPublicKey string UIAssets embed.FS diff --git a/src/config/lang/english.go b/src/config/lang/english.go index 8c54997b11..857d3e4ecf 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -263,12 +263,15 @@ const ( CmdToolsSbomErr = "Unable to create sbom (syft) CLI" CmdToolsWaitForShort = "Waits for a given Kubernetes resource to be ready" - CmdToolsWaitForLong = "By default Zarf will wait for all Kubernetes resources to be ready before completion of component during deployment. \n" + - "This command can be used to wait for a specific resource to exist and be ready that may be created by a Gitops tool or Operator." - CmdToolsWaitForFlagTimeout = "Specify the timeout duration for the wait command." - CmdToolsWaitForErrTimeoutString = "Invalid timeout duration. Please use a valid duration string (e.g. 1s, 2m, 3h)." - CmdToolsWaitForErrTimeout = "Wait timed out." - CmdToolsWaitForErrZarfPath = "Could not locate the current Zarf binary path." + CmdToolsWaitForLong = "By default Zarf will wait for all Kubernetes resources to be ready before completion of a component during a deployment. \n" + + "This command can be used to wait for a Kubernetes resources to exist and be ready that may be created by a Gitops tool or a Kubernetes operator. \n" + + "You can also wait for aribtrary network endpoints using REST or TCP checks. \n\n" + CmdToolsWaitForFlagTimeout = "Specify the timeout duration for the wait command." + CmdToolsWaitForErrTimeoutString = "Invalid timeout duration. Please use a valid duration string (e.g. 1s, 2m, 3h)." + CmdToolsWaitForErrTimeout = "Wait timed out." + CmdToolsWaitForErrConditionString = "Invalid HTTP status code. Please use a valid HTTP status code (e.g. 200, 404, 500)." + CmdToolsWaitForErrZarfPath = "Could not locate the current Zarf binary path." + CmdToolsWaitForFlagNamespace = "Specify the namespace of the resources to wait for." CmdToolsKubectlDocs = "Kubectl command. See https://kubernetes.io/docs/reference/kubectl/overview/ for more information." From a1a3e298f031e2defcda164ced52404e7108f059 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Sun, 19 Feb 2023 19:06:33 -0600 Subject: [PATCH 04/20] update containerd for CVE failure --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 9b81693218..b5f620d0a1 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,8 @@ replace oras.land/oras-go v1.2.2 => github.com/defenseunicorns/oras-go v1.2.3 require ( github.com/AlecAivazis/survey/v2 v2.3.6 github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b - github.com/anchore/stereoscope v0.0.0-20230208154630-5a306f07f2e7 - github.com/anchore/syft v0.71.0 + github.com/anchore/stereoscope v0.0.0-20230216143338-4b5ebf8c7f4b + github.com/anchore/syft v0.72.0 github.com/derailed/k9s v0.27.2 github.com/distribution/distribution v2.8.1+incompatible github.com/fatih/color v1.14.1 @@ -127,7 +127,7 @@ require ( github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect github.com/containerd/console v1.0.3 // indirect - github.com/containerd/containerd v1.6.17 // indirect + github.com/containerd/containerd v1.6.18 // indirect github.com/containerd/stargz-snapshotter/estargz v0.12.1 // indirect github.com/coreos/go-oidc/v3 v3.4.0 // indirect github.com/coreos/go-semver v0.3.0 // indirect @@ -368,7 +368,7 @@ require ( go.uber.org/zap v1.23.0 // indirect golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b // indirect golang.org/x/mod v0.8.0 // indirect - golang.org/x/net v0.6.0 // indirect + golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.1.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.5.0 // indirect diff --git a/go.sum b/go.sum index c2854c0d07..6eae3a7044 100644 --- a/go.sum +++ b/go.sum @@ -261,10 +261,10 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8= github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4= -github.com/anchore/stereoscope v0.0.0-20230208154630-5a306f07f2e7 h1:PrdFBPMyika+AM1/AwDmYqrVeUATDU90wbrd81ugicU= -github.com/anchore/stereoscope v0.0.0-20230208154630-5a306f07f2e7/go.mod h1:TUCfo52tEz7ahTUFtKN//wcB7kJzQs0Oifmnd4NkIXw= -github.com/anchore/syft v0.71.0 h1:dagtH0oeq2K6dG0gVOj35+KDl438uCTMVU6swQu8Jk0= -github.com/anchore/syft v0.71.0/go.mod h1:LupCSiYF24mblW65/alRYa3jBWWwUM1l5jc37ErtkkU= +github.com/anchore/stereoscope v0.0.0-20230216143338-4b5ebf8c7f4b h1:vMEAfz91QLjJq2W8JPxpIC4dG4OeynTY4MisHnZ19F0= +github.com/anchore/stereoscope v0.0.0-20230216143338-4b5ebf8c7f4b/go.mod h1:6oSG43mzahqiktzXZDctqi1o66fwU2wDk3xki0KlnbA= +github.com/anchore/syft v0.72.0 h1:EpZMDitSElK/Qm1zgrL/3HM2Cw0hHo7hy9uwrhIXDGA= +github.com/anchore/syft v0.72.0/go.mod h1:T3ZSrApwb+jwI+vyTfE5R54Xej4NBoQ8c2t1LyJWGao= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= @@ -447,8 +447,8 @@ github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/containerd/containerd v1.6.17 h1:XDnJIeJW0cLf6v7/+N+6L9kGrChHeXekZp2VHu6OpiY= -github.com/containerd/containerd v1.6.17/go.mod h1:1RdCUu95+gc2v9t3IL+zIlpClSmew7/0YS8O5eQZrOw= +github.com/containerd/containerd v1.6.18 h1:qZbsLvmyu+Vlty0/Ex5xc0z2YtKpIsb5n45mAMI+2Ns= +github.com/containerd/containerd v1.6.18/go.mod h1:1RdCUu95+gc2v9t3IL+zIlpClSmew7/0YS8O5eQZrOw= github.com/containerd/stargz-snapshotter/estargz v0.12.1 h1:+7nYmHJb0tEkcRaAW+MHqoKaJYZmkikupxCqVtmPuY0= github.com/containerd/stargz-snapshotter/estargz v0.12.1/go.mod h1:12VUuCq3qPq4y8yUW+l5w3+oXV3cx2Po3KSe/SmPGqw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -2034,8 +2034,8 @@ golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From b6fd6e1c90817fe9be66550ae0415aed74446292 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Sun, 19 Feb 2023 19:07:49 -0600 Subject: [PATCH 05/20] update wait-for docs --- .../100-cli-commands/zarf_tools_wait-for.md | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md index 316121c699..bcac487dd1 100644 --- a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md +++ b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md @@ -5,24 +5,40 @@ Waits for a given Kubernetes resource to be ready ## Synopsis -By default Zarf will wait for all Kubernetes resources to be ready before completion of component during deployment. -This command can be used to wait for a specific resource to exist and be ready that may be created by a Gitops tool or Operator. +By default Zarf will wait for all Kubernetes resources to be ready before completion of a component during a deployment. +This command can be used to wait for a Kubernetes resources to exist and be ready that may be created by a Gitops tool or a Kubernetes operator. +You can also wait for aribtrary network endpoints using REST or TCP checks. + + ``` -zarf tools wait-for {NAMESPACE} {RESOURCE} {NAME} {CONDITION} [flags] +zarf tools wait-for {RESOURCE|PROTOCOL} {NAME|URI} {CONDITION} [flags] ``` ## Examples ``` -zarf tools wait-for default pod my-pod-name ready + Wait for Kubernetes resources: + zarf tools wait-for pod my-pod-name ready -n default wait for pod my-pod-name in namespace default to be ready + zarf tools wait-for p cool-pod-name ready -n cool wait for pod (using p alias) cool-pod-name in namespace cool to be ready + zarf tools wait-for deployment podinfo available -n podinfo wait for deployment podinfo in namespace podinfo to be available + zarf tools wait-for svc zarf-docker-registry exists -n zarf wait for service zarf-docker-registry in namespace zarf to exist + zarf tools wait-for svc zarf-docker-registry -n zarf same as above, except exists is the default condition + zarf tools wati-for crd addons.k3s.cattle.io wait for crd addons.k3s.cattle.io to exist + + Wait for network endpoints: + zarf tools wait-for http localhost:8080 200 wait for a 200 response from http://localhost:8080 + zarf tools wait-for tcp localhost:8080 wait for a connection to be established on localhost:8080 + zarf tools wait-for https 1.1.1.1 200 wait for a 200 response from https://1.1.1.1 + ``` ## Options ``` - -h, --help help for wait-for - --timeout string Specify the timeout duration for the wait command. (default "5m") + -h, --help help for wait-for + -n, --namespace string Specify the namespace of the resources to wait for. + --timeout string Specify the timeout duration for the wait command. (default "5m") ``` ## Options inherited from parent commands From 7f172560f193a1763b8876ca9ef5c278ded639ad Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Sun, 19 Feb 2023 19:09:12 -0600 Subject: [PATCH 06/20] use updated wait-for syntax for flux-test --- examples/flux-test/zarf.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/flux-test/zarf.yaml b/examples/flux-test/zarf.yaml index 97213cff7c..1572c6f9f4 100644 --- a/examples/flux-test/zarf.yaml +++ b/examples/flux-test/zarf.yaml @@ -32,6 +32,6 @@ components: actions: onDeploy: after: - - cmd: "./zarf tools wait-for podinfo deployment podinfo available" + - cmd: "./zarf tools wait-for deployment podinfo available -n podinfo" description: "Podinfo deployment to be ready" mute: true From b4a0afe6a3e93ed786c36e232fca671d99af2dce Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Sun, 19 Feb 2023 19:17:28 -0600 Subject: [PATCH 07/20] update docs & data injection for embedded kubectl --- .../4-creating-a-k8s-cluster-with-zarf.md | 1 - docs/13-walkthroughs/index.md | 4 +--- docs/5-operator-manual/0-set-up-and-install.md | 2 +- examples/git-data/README.md | 2 +- examples/minio/zarf-connect.yaml | 2 +- examples/postgres-operator/README.md | 2 +- examples/tiny-kafka/README.md | 2 +- packages/big-bang-core/README.md | 4 ++-- src/internal/cluster/data.go | 11 ++++++++++- 9 files changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/13-walkthroughs/4-creating-a-k8s-cluster-with-zarf.md b/docs/13-walkthroughs/4-creating-a-k8s-cluster-with-zarf.md index 5c74b648db..a36ec60355 100644 --- a/docs/13-walkthroughs/4-creating-a-k8s-cluster-with-zarf.md +++ b/docs/13-walkthroughs/4-creating-a-k8s-cluster-with-zarf.md @@ -11,7 +11,6 @@ In this walkthrough, we are going to show how you can use Zarf on a fresh linux 1. The [Zarf](https://github.com/defenseunicorns/zarf) repository cloned: ([`git clone` Instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository)) 1. Zarf binary installed on your $PATH: ([Install Instructions](../3-getting-started.md#installing-zarf)) 1. An init-package built/downloaded: ([init-package Build Instructions](./0-using-zarf-package-create.md)) or ([Download Location](https://github.com/defenseunicorns/zarf/releases)) -1. kubectl: ([kubectl Install Instructions](https://kubernetes.io/docs/tasks/tools/#kubectl)) 1. `root` access on a Linux machine ## Install the k3s component diff --git a/docs/13-walkthroughs/index.md b/docs/13-walkthroughs/index.md index 2075c0072d..feb5821d3a 100644 --- a/docs/13-walkthroughs/index.md +++ b/docs/13-walkthroughs/index.md @@ -3,7 +3,6 @@ This section of the documentation has a collection of walkthroughs that will help you get more familiar with Zarf and its features. The walkthroughs assume that you have a very basic understanding of what Zarf is and aims to help expand your working knowledge of how to use Zarf and what Zarf is capable of doing. ## Walk Through Prerequisites - If a walkthrough has any prerequisites, they will be listed at the beginning of the walkthrough with instructions on how to fulfill them. Almost all walkthroughs will have the following prerequisites/assumptions: @@ -11,7 +10,6 @@ Almost all walkthroughs will have the following prerequisites/assumptions: 1. You have a Zarf binary installed on your $PATH: ([Zarf Install Instructions](../3-getting-started.md#installing-zarf)) 1. You have an init-package built/downloaded: ([init-package Build Instructions](./0-using-zarf-package-create.md)) or ([Download Location](https://github.com/defenseunicorns/zarf/releases)) 1. Have a kubernetes cluster running/available (ex. [k3s](https://k3s.io/)/[k3d](https://k3d.io/v5.4.1/)/[Kind](https://kind.sigs.k8s.io/docs/user/quick-start#installation)) -1. You have kubectl installed: ([kubectl Install Instructions](https://kubernetes.io/docs/tasks/tools/#kubectl)) ## Setting Up a Local Kubernetes Cluster @@ -29,7 +27,7 @@ k3d cluster create # Creates a k3d cluster # This will take a couple of minutes to complete -kubectl get pods -A # Check to see if the cluster is ready +zarf tools kubectl get pods -A # Check to see if the cluster is ready ``` ### Tear Down k3d CLuster diff --git a/docs/5-operator-manual/0-set-up-and-install.md b/docs/5-operator-manual/0-set-up-and-install.md index e1d8417edd..41930d0010 100644 --- a/docs/5-operator-manual/0-set-up-and-install.md +++ b/docs/5-operator-manual/0-set-up-and-install.md @@ -49,7 +49,7 @@ During the initialization process, Zarf will create a `zarf` namespace and deplo You can find the relevant [init package release](https://github.com/defenseunicorns/zarf/releases) on the GitHub releases page. Once downloaded, you can install the init package by navigating to the directory containing the init package and running the command [zarf init](../4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_init.md). Zarf will prompt you, asking if you want to deploy the optional component, you can type `y` or `n` depending on your use case and needs. -Once the init command is finished, you can run `kubectl get pods -n zarf` to verify that the pods have come up healthy. You should expect to see two `agent-hook` pods, a `zarf-docker-registry` pod, and optionally a `zarf-gitea` pod. +Once the init command is finished, you can run `zarf tools kubectl get pods -n zarf` to verify that the pods have come up healthy. You should expect to see two `agent-hook` pods, a `zarf-docker-registry` pod, and optionally a `zarf-gitea` pod. ## Setup Complete diff --git a/examples/git-data/README.md b/examples/git-data/README.md index c1cd11b4d8..0dbce21a27 100644 --- a/examples/git-data/README.md +++ b/examples/git-data/README.md @@ -82,7 +82,7 @@ The following assumes you are using the internal Gitea server. If you are using zarf connect git& # Apply the kustomization -kubectl apply -k http://zarf-git-user:$(zarf tools get-admin-password)@localhost:/zarf-git-user/mirror__github.com__stefanprodan__podinfo//kustomize +zarf tools kubectl apply -k http://zarf-git-user:$(zarf tools get-admin-password)@localhost:/zarf-git-user/mirror__github.com__stefanprodan__podinfo//kustomize # Inspect zarf tools k9s diff --git a/examples/minio/zarf-connect.yaml b/examples/minio/zarf-connect.yaml index 46d0a0d09c..51d69fc163 100644 --- a/examples/minio/zarf-connect.yaml +++ b/examples/minio/zarf-connect.yaml @@ -4,7 +4,7 @@ metadata: name: minio-console-zarf-connect namespace: minio-operator annotations: - zarf.dev/connect-description: "Launch the minio console, to get a JWT run:\n\n kubectl -n minio-operator get secrets console-sa-secret -o jsonpath=\"{.data.token}\" | base64 --decode\n" + zarf.dev/connect-description: "Launch the minio console, to get a JWT run:\n\n zarf tools kubectl -n minio-operator get secrets console-sa-secret -o jsonpath=\"{.data.token}\" | base64 --decode\n" labels: zarf.dev/connect-name: minio spec: diff --git a/examples/postgres-operator/README.md b/examples/postgres-operator/README.md index a292b5b6d2..e3650efadf 100644 --- a/examples/postgres-operator/README.md +++ b/examples/postgres-operator/README.md @@ -79,7 +79,7 @@ If you want to run other commands after/during the browsing of the postgres tool | ------------------------- | ------------------------------------------------------------------------------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Postgres Operator UI | `zarf connect postgres-operator-ui` | N/A | N/A | | PGAdmin | `zarf connect pgadmin` | `zarf@example.local` | Run: `zarf tools get-admin-password` | -| Example Postgres Database | `acid-zarf-test.postgres-operator.svc.cluster.local` | `zarf` | Run: `echo $(kubectl get secret zarf.acid-zarf-test.credentials.postgresql.acid.zalan.do -n postgres-operator --template={{.data.password}} | base64 -d)` | +| Example Postgres Database | `acid-zarf-test.postgres-operator.svc.cluster.local` | `zarf` | Run: `echo $(zarf tools kubectl get secret zarf.acid-zarf-test.credentials.postgresql.acid.zalan.do -n postgres-operator --template={{.data.password}} | base64 -d)` | | Minio Console | `zarf connect minio` | `minio` | `minio123` | ## References diff --git a/examples/tiny-kafka/README.md b/examples/tiny-kafka/README.md index 5bd576ed10..b93128471a 100644 --- a/examples/tiny-kafka/README.md +++ b/examples/tiny-kafka/README.md @@ -39,6 +39,6 @@ Wait a few seconds for the cluster to deploy the package. Testing requires JDK and the Kafka tools: `sudo apt install openjdk-14-jdk-headless` (on Ubuntu). More details can be found at . Steps to test: 1. Install JDK and extract the Kafka tools from the package `kafka.tgz` -2. Get the Nodeport: `NODEPORT=$(kubectl get service demo-kafka-external-bootstrap -n kafka-demo -o=jsonpath='{.spec.ports[0].nodePort}{"\n"}')` +2. Get the Nodeport: `NODEPORT=$(zarf tools kubectl get service demo-kafka-external-bootstrap -n kafka-demo -o=jsonpath='{.spec.ports[0].nodePort}{"\n"}')` 3. For pub: `./bin/kafka-console-producer.sh --broker-list localhost:$NODEPORT --topic cool-topic` 4. For sub: `./bin/kafka-console-consumer.sh --bootstrap-server localhost:$NODEPORT --topic cool-topic` diff --git a/packages/big-bang-core/README.md b/packages/big-bang-core/README.md index 900fcd8b52..fb08917bf7 100644 --- a/packages/big-bang-core/README.md +++ b/packages/big-bang-core/README.md @@ -106,8 +106,8 @@ k3d cluster delete | ----------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | | [AlertManager](https://alertmanager.bigbang.dev:8443) | n/a | n/a | Unauthenticated | | [Grafana](https://grafana.bigbang.dev:8443) | `admin` | `prom-operator` | | -| [Kiali](https://kiali.bigbang.dev:8443) | n/a | `kubectl get secret -n kiali -o=json \| jq -r '.items[] \| select(.metadata.annotations."kubernetes.io/service-account.name"=="kiali-service-account") \| .data.token' \| base64 -d; echo` | | -| [Kibana](https://kibana.bigbang.dev:8443) | `elastic` | `kubectl get secret -n logging logging-ek-es-elastic-user -o=jsonpath='{.data.elastic}' \| base64 -d; echo` | | +| [Kiali](https://kiali.bigbang.dev:8443) | n/a | `zarf tools kubectl get secret -n kiali -o=json \| jq -r '.items[] \| select(.metadata.annotations."kubernetes.io/service-account.name"=="kiali-service-account") \| .data.token' \| base64 -d; echo` | | +| [Kibana](https://kibana.bigbang.dev:8443) | `elastic` | `zarf tools kubectl get secret -n logging logging-ek-es-elastic-user -o=jsonpath='{.data.elastic}' \| base64 -d; echo` | | | [Prometheus](https://prometheus.bigbang.dev:8443) | n/a | n/a | Unauthenticated | | [Jaeger](https://tracing.bigbang.dev:8443) | n/a | n/a | Unauthenticated | | [Twistlock](https://twistlock.bigbang.dev:8443) | n/a | n/a | Twistlock has you create an admin account the first time you log in | diff --git a/src/internal/cluster/data.go b/src/internal/cluster/data.go index b0d2499d0b..050ad807c6 100644 --- a/src/internal/cluster/data.go +++ b/src/internal/cluster/data.go @@ -63,7 +63,16 @@ iterator: // Inject into all the pods for _, pod := range pods { - kubectlExec := fmt.Sprintf("kubectl exec -i -n %s %s -c %s ", data.Target.Namespace, pod, data.Target.Container) + // Try to use the embedded kubectl if we can + binPath, err := utils.GetFinalExecutablePath() + if err != nil { + message.Warnf("Unable to get the final executable path, falling back to host kubectl: %s", err) + binPath = "" + } else { + binPath = fmt.Sprintf("%s tools ", binPath) + } + + kubectlExec := fmt.Sprintf("%skubectl exec -i -n %s %s -c %s ", binPath, data.Target.Namespace, pod, data.Target.Container) tarExec := fmt.Sprintf("tar c%s", tarCompressFlag) untarExec := fmt.Sprintf("tar x%svf - -C %s", tarCompressFlag, data.Target.Path) From 3593d780cc785ec277a0f0b16f3e65fcbd6a3424 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Mon, 20 Feb 2023 10:21:58 -0600 Subject: [PATCH 08/20] add wait action (wait-for alias) + label selector option for wait-for --- .../100-cli-commands/zarf_tools_wait-for.md | 3 +- docs/4-user-guide/3-zarf-schema.md | 181 +++++++++++++++++- examples/flux-test/zarf.yaml | 15 +- src/cmd/tools/wait.go | 28 ++- src/config/lang/english.go | 2 + src/internal/packager/validate/validate.go | 37 +++- src/pkg/packager/actions.go | 54 ++++-- src/types/component.go | 38 +++- src/ui/lib/api-types.ts | 93 ++++++++- zarf.schema.json | 75 +++++++- 10 files changed, 480 insertions(+), 46 deletions(-) diff --git a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md index bcac487dd1..ffa41bff06 100644 --- a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md +++ b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md @@ -12,7 +12,7 @@ You can also wait for aribtrary network endpoints using REST or TCP checks. ``` -zarf tools wait-for {RESOURCE|PROTOCOL} {NAME|URI} {CONDITION} [flags] +zarf tools wait-for {KIND|PROTOCOL} {NAME|SELECTOR|URI} {CONDITION|HTTP_CODE} [flags] ``` ## Examples @@ -22,6 +22,7 @@ zarf tools wait-for {RESOURCE|PROTOCOL} {NAME|URI} {CONDITION} [flags] zarf tools wait-for pod my-pod-name ready -n default wait for pod my-pod-name in namespace default to be ready zarf tools wait-for p cool-pod-name ready -n cool wait for pod (using p alias) cool-pod-name in namespace cool to be ready zarf tools wait-for deployment podinfo available -n podinfo wait for deployment podinfo in namespace podinfo to be available + zarf tools wait-for pod app=podinfo ready -n podinfo wait for pod with label app=podinfo in namespace podinfo to be ready zarf tools wait-for svc zarf-docker-registry exists -n zarf wait for service zarf-docker-registry in namespace zarf to exist zarf tools wait-for svc zarf-docker-registry -n zarf same as above, except exists is the default condition zarf tools wati-for crd addons.k3s.cattle.io wait for crd addons.k3s.cattle.io to exist diff --git a/docs/4-user-guide/3-zarf-schema.md b/docs/4-user-guide/3-zarf-schema.md index 99680906b0..1303971967 100644 --- a/docs/4-user-guide/3-zarf-schema.md +++ b/docs/4-user-guide/3-zarf-schema.md @@ -1035,7 +1035,7 @@ Must be one of:  
-**Description:** The command to run +**Description:** The command to run. Must specify either cmd or wait for the action to do anything. | | | | -------- | -------- | @@ -1080,6 +1080,185 @@ Must be one of:
+
+ wait + + +  +
+ +**Description:** Wait for a condition to be met before continuing. Must specify either cmd or wait for the action to do anything. If both are specified + +| | | +| ------------------------- | -------------------------------------------------------------------------------------------------------- | +| **Type** | `object` | +| **Additional properties** | [![Not allowed](https://img.shields.io/badge/Not%20allowed-red)](# "Additional Properties not allowed.") | +| **Defined in** | #/definitions/ZarfComponentActionWait | + +
+ cluster + + +  +
+ +**Description:** Wait for a condition to be met in the cluster before continuing. Only one of cluster or network can be specified. + +| | | +| ------------------------- | -------------------------------------------------------------------------------------------------------- | +| **Type** | `object` | +| **Additional properties** | [![Not allowed](https://img.shields.io/badge/Not%20allowed-red)](# "Additional Properties not allowed.") | +| **Defined in** | #/definitions/ZarfComponentActionWaitCluster | + +
+ kind * + + +  +
+ +![Required](https://img.shields.io/badge/Required-red) + +**Description:** The kind of resource to wait for (e.g. Pod; Deployment; etc.) + +| | | +| -------- | -------- | +| **Type** | `string` | + +
+
+ +
+ name * + + +  +
+ +![Required](https://img.shields.io/badge/Required-red) + +| | | +| -------- | -------- | +| **Type** | `string` | + +
+
+ +
+ namespace + + +  +
+ +**Description:** The namespace of the resource to wait for + +| | | +| -------- | -------- | +| **Type** | `string` | + +
+
+ +
+ condition + + +  +
+ +**Description:** The condition to wait for (e.g. Ready; Available; etc.). Defautls to exist + +| | | +| -------- | -------- | +| **Type** | `string` | + +
+
+ +
+
+ +
+ network + + +  +
+ +**Description:** Wait for a condition to be met on the network before continuing. Only one of cluster or network can be specified. + +| | | +| ------------------------- | -------------------------------------------------------------------------------------------------------- | +| **Type** | `object` | +| **Additional properties** | [![Not allowed](https://img.shields.io/badge/Not%20allowed-red)](# "Additional Properties not allowed.") | +| **Defined in** | #/definitions/ZarfComponentActionWaitNetwork | + +
+ protocol * + + +  +
+ +![Required](https://img.shields.io/badge/Required-red) + +**Description:** The protocol to wait for (e.g. tcp; http; etc.). + +| | | +| -------- | ------------------ | +| **Type** | `enum (of string)` | + +:::note +Must be one of: +* "tcp" +* "http" +* "https" +::: + +
+
+ +
+ address * + + +  +
+ +![Required](https://img.shields.io/badge/Required-red) + +**Description:** The address to wait for (e.g. localhost:8080; 1.1.1.1; etc.) + +| | | +| -------- | -------- | +| **Type** | `string` | + +
+
+ +
+ code + + +  +
+ +**Description:** The HTTP status code to wait for if using http or https (e.g. 200; 404; etc.) + +| | | +| -------- | --------- | +| **Type** | `integer` | + +
+
+ +
+
+ +
+
+ diff --git a/examples/flux-test/zarf.yaml b/examples/flux-test/zarf.yaml index 1572c6f9f4..7566f3370e 100644 --- a/examples/flux-test/zarf.yaml +++ b/examples/flux-test/zarf.yaml @@ -32,6 +32,17 @@ components: actions: onDeploy: after: - - cmd: "./zarf tools wait-for deployment podinfo available -n podinfo" - description: "Podinfo deployment to be ready" + # Waiting using the wait-for command + - description: "Podinfo deployment to be ready via cmd action" + cmd: "./zarf tools wait-for deployment podinfo available -n podinfo" mute: true + + # Waiting using the wait action + - description: "Podinfo pods to be ready via wait action" + mute: true + wait: + cluster: + kind: pod + name: app=podinfo + namespace: podinfo + condition: ready diff --git a/src/cmd/tools/wait.go b/src/cmd/tools/wait.go index d152056d1f..b0beed1136 100644 --- a/src/cmd/tools/wait.go +++ b/src/cmd/tools/wait.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "strconv" + "strings" "time" "github.com/defenseunicorns/zarf/src/config/lang" @@ -27,7 +28,7 @@ var ( ) var waitForCmd = &cobra.Command{ - Use: "wait-for {RESOURCE|PROTOCOL} {NAME|URI} {CONDITION}", + Use: "wait-for {KIND|PROTOCOL} {NAME|SELECTOR|URI} {CONDITION|HTTP_CODE}", Aliases: []string{"w", "wait"}, Short: lang.CmdToolsWaitForShort, Long: lang.CmdToolsWaitForLong, @@ -35,6 +36,7 @@ var waitForCmd = &cobra.Command{ zarf tools wait-for pod my-pod-name ready -n default wait for pod my-pod-name in namespace default to be ready zarf tools wait-for p cool-pod-name ready -n cool wait for pod (using p alias) cool-pod-name in namespace cool to be ready zarf tools wait-for deployment podinfo available -n podinfo wait for deployment podinfo in namespace podinfo to be available + zarf tools wait-for pod app=podinfo ready -n podinfo wait for pod with label app=podinfo in namespace podinfo to be ready zarf tools wait-for svc zarf-docker-registry exists -n zarf wait for service zarf-docker-registry in namespace zarf to exist zarf tools wait-for svc zarf-docker-registry -n zarf same as above, except exists is the default condition zarf tools wati-for crd addons.k3s.cattle.io wait for crd addons.k3s.cattle.io to exist @@ -52,8 +54,8 @@ var waitForCmd = &cobra.Command{ message.Fatalf(err, lang.CmdToolsWaitForErrTimeoutString, waitTimeout) } - // Parse the resource type and name. - resource, name := args[0], args[1] + // Parse the kind type and identifier. + kind, identifier := args[0], args[1] // Condition is optional, default to "exists". condition := "" @@ -62,9 +64,9 @@ var waitForCmd = &cobra.Command{ } // Handle network endpoints. - switch resource { + switch kind { case "http", "https", "tcp": - waitForNetworkEndpoint(resource, name, condition, timeout) + waitForNetworkEndpoint(kind, identifier, condition, timeout) return } @@ -74,6 +76,13 @@ var waitForCmd = &cobra.Command{ message.Fatal(err, lang.CmdToolsWaitForErrZarfPath) } + // If the identifier contains an equals sign, convert to a label selector. + identifierMsg := fmt.Sprintf("/%s", identifier) + if strings.ContainsRune(identifier, '=') { + identifierMsg = fmt.Sprintf(" with label `%s`", identifier) + identifier = fmt.Sprintf("-l %s", identifier) + } + // Set the timeout for the wait-for command. expired := time.After(timeout) @@ -84,8 +93,8 @@ var waitForCmd = &cobra.Command{ } // Setup the spinner messages. - conditionMsg := fmt.Sprintf("Waiting for %s/%s%s to be %s.", resource, name, namespaceMsg, condition) - existMsg := fmt.Sprintf("Waiting for %s/%s%s to exist.", resource, name, namespaceMsg) + conditionMsg := fmt.Sprintf("Waiting for %s%s%s to be %s.", kind, identifierMsg, namespaceMsg, condition) + existMsg := fmt.Sprintf("Waiting for %s%s%s to exist.", kind, identifierMsg, namespaceMsg) spinner := message.NewProgressSpinner(existMsg) defer spinner.Stop() @@ -100,7 +109,7 @@ var waitForCmd = &cobra.Command{ default: spinner.Updatef(existMsg) // Check if the resource exists. - args := []string{"tools", "kubectl", "get", "-n", waitNamespace, resource, name} + args := []string{"tools", "kubectl", "get", "-n", waitNamespace, kind, identifier} if stdout, stderr, err := exec.Cmd(zarf, args...); err != nil { message.Debug(stdout, stderr, err) continue @@ -116,7 +125,7 @@ var waitForCmd = &cobra.Command{ spinner.Updatef(conditionMsg) // Wait for the resource to meet the given condition. args = []string{"tools", "kubectl", "wait", "-n", waitNamespace, - resource, name, "--for", "condition=" + condition, + kind, identifier, "--for", "condition=" + condition, "--timeout=" + waitTimeout} if stdout, stderr, err := exec.Cmd(zarf, args...); err != nil { message.Debug(stdout, stderr, err) @@ -182,7 +191,6 @@ func waitForNetworkEndpoint(resource, name, condition string, timeout time.Durat spinner.Success() return } - } } diff --git a/src/config/lang/english.go b/src/config/lang/english.go index 857d3e4ecf..2380be0435 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -314,6 +314,8 @@ const ( const ( PkgValidateMustBeUppercase = "variable name '%s' must be all uppercase and contain no special characters except _" PkgValidateErrAction = "invalid action: %w" + PkgValidateErrActionCmdWait = "action %s cannot be both a command and wait action" + PkgValidateErrActionClusterNetwork = "a single wait action must contain only one of cluster or network" PkgValidateErrChart = "invalid chart definition: %w" PkgValidateErrChartName = "chart %s exceed the maximum length of %d characters" PkgValidateErrChartNameMissing = "chart %s must include a name" diff --git a/src/internal/packager/validate/validate.go b/src/internal/packager/validate/validate.go index b3af9addc9..0f50cf7df8 100644 --- a/src/internal/packager/validate/validate.go +++ b/src/internal/packager/validate/validate.go @@ -125,40 +125,39 @@ func validateComponent(pkg types.ZarfPackage, component types.ZarfComponent) err } } - if err := validateActionsetVariable(component.Actions.OnCreate); err != nil { + if err := validateActionset(component.Actions.OnCreate); err != nil { return fmt.Errorf(lang.PkgValidateErrAction, err) } - if err := validateActionsetVariable(component.Actions.OnDeploy); err != nil { + if err := validateActionset(component.Actions.OnDeploy); err != nil { return fmt.Errorf(lang.PkgValidateErrAction, err) } - if err := validateActionsetVariable(component.Actions.OnRemove); err != nil { + if err := validateActionset(component.Actions.OnRemove); err != nil { return fmt.Errorf(lang.PkgValidateErrAction, err) } return nil } -func validateActionsetVariable(actions types.ZarfComponentActionSet) error { - // Validate actions.OnDeploy.*.SetVariable +func validateActionset(actions types.ZarfComponentActionSet) error { for _, action := range actions.Before { - if err := validateActionVariable(action); err != nil { + if err := validateAction(action); err != nil { return err } } for _, action := range actions.After { - if err := validateActionVariable(action); err != nil { + if err := validateAction(action); err != nil { return err } } for _, action := range actions.OnSuccess { - if err := validateActionVariable(action); err != nil { + if err := validateAction(action); err != nil { return err } } for _, action := range actions.OnFailure { - if err := validateActionVariable(action); err != nil { + if err := validateAction(action); err != nil { return err } } @@ -166,11 +165,29 @@ func validateActionsetVariable(actions types.ZarfComponentActionSet) error { return nil } -func validateActionVariable(action types.ZarfComponentAction) error { +func validateAction(action types.ZarfComponentAction) error { + // Validate SetVariable if action.SetVariable != "" && !isUppercaseNumberUnderscore(action.SetVariable) { return fmt.Errorf(lang.PkgValidateMustBeUppercase, action.SetVariable) } + if action.Wait != nil { + // Validate only cmd or wait, not both + if action.Cmd != "" { + return fmt.Errorf(lang.PkgValidateErrActionCmdWait, action.Cmd) + } + + // Validate only cluster or network, not both + if action.Wait.Cluster != nil && action.Wait.Network != nil { + return fmt.Errorf(lang.PkgValidateErrActionClusterNetwork) + } + + // Validate at least one of cluster or network + if action.Wait.Cluster == nil && action.Wait.Network == nil { + return fmt.Errorf(lang.PkgValidateErrActionClusterNetwork) + } + } + return nil } diff --git a/src/pkg/packager/actions.go b/src/pkg/packager/actions.go index d3532ebe74..20a4c3e1be 100644 --- a/src/pkg/packager/actions.go +++ b/src/pkg/packager/actions.go @@ -30,27 +30,34 @@ func (p *Packager) runActions(defaultCfg types.ZarfComponentActionDefaults, acti // Run commands that a component has provided. func (p *Packager) runAction(defaultCfg types.ZarfComponentActionDefaults, action types.ZarfComponentAction, valueTemplate *template.Values) error { - var cmdEscaped string + + var ( + ctx context.Context + cancel context.CancelFunc + cmdEscaped string + cmd string + out string + err error + vars map[string]string + ) + + // If the action is a wait, convert it to a command. + if action.Wait != nil { + if cmd, err = convertWaitToCmd(*action.Wait); err != nil { + return err + } + } if action.Description != "" { cmdEscaped = action.Description } else { - cmdEscaped = escapeCmdForPrint(action.Cmd) + cmdEscaped = escapeCmdForPrint(cmd) } spinner := message.NewProgressSpinner("Running command \"%s\"", cmdEscaped) // Persist the spinner output so it doesn't get overwritten by the command output. spinner.EnablePreserveWrites() - var ( - ctx context.Context - cancel context.CancelFunc - cmd string - out string - err error - vars map[string]string - ) - // If the value template is not nil, get the variables for the action. // No special variables or deprecations will be used in the action. // Reload the variables each time in case they have been changed by a previous action. @@ -60,7 +67,7 @@ func (p *Packager) runAction(defaultCfg types.ZarfComponentActionDefaults, actio cfg := actionGetCfg(defaultCfg, action, vars) - if cmd, err = actionCmdMutation(action.Cmd); err != nil { + if cmd, err = actionCmdMutation(cmd); err != nil { spinner.Errorf(err, "Error mutating command: %s", cmdEscaped) } @@ -122,6 +129,29 @@ func (p *Packager) runAction(defaultCfg types.ZarfComponentActionDefaults, actio return fmt.Errorf("command \"%s\" failed after %d retries", cmdEscaped, cfg.MaxRetries) } +// convertWaitToCmd will return the wait command if it exists, otherwise it will return the original command. +func convertWaitToCmd(wait types.ZarfComponentActionWait) (string, error) { + // If the action has a wait, build a cmd from that instead. + cluster := wait.Cluster + if cluster != nil { + ns := cluster.Namespace + if ns != "" { + ns = fmt.Sprintf("-n %s", ns) + } + + // Build a call to the zarf tools wait-for command. + return fmt.Sprintf("./zarf tools wait-for %s %s %s %s", cluster.Kind, cluster.Identifier, cluster.Condition, ns), nil + } + + network := wait.Network + if network != nil { + // Build a call to the zarf tools wait-for command. + return fmt.Sprintf("./zarf tools wait-for %s %s %d", network.Protocol, network.Address, network.Code), nil + } + + return "", fmt.Errorf("wait action is missing a cluster or network") +} + // Perform some basic string mutations to make commands more useful. func actionCmdMutation(cmd string) (string, error) { binaryPath, err := os.Executable() diff --git a/src/types/component.go b/src/types/component.go index 62a7329943..0d53993ee2 100644 --- a/src/types/component.go +++ b/src/types/component.go @@ -137,14 +137,36 @@ type ZarfComponentActionDefaults struct { // ZarfComponentAction represents a single action to run during a zarf package operation type ZarfComponentAction struct { - Mute *bool `json:"mute,omitempty" jsonschema:"description=Hide the output of the command during package deployment (default false)"` - MaxTotalSeconds *int `json:"maxTotalSeconds,omitempty" jsonschema:"description=Timeout in seconds for the command (default to 0, no timeout)"` - MaxRetries *int `json:"maxRetries,omitempty" jsonschema:"description=Retry the command if it fails up to given number of times (default 0)"` - Dir *string `json:"dir,omitempty" jsonschema:"description=The working directory to run the command in (default is CWD)"` - Env []string `json:"env,omitempty" jsonschema:"description=Additional environment variables to set for the command"` - Cmd string `json:"cmd,omitempty" jsonschema:"description=The command to run"` - SetVariable string `json:"setVariable,omitempty" jsonschema:"description=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.,pattern=^[A-Z0-9_]+$"` - Description string `json:"description,omitempty" jsonschema:"description=Description of the action to be displayed during package execution instead of the command"` + Mute *bool `json:"mute,omitempty" jsonschema:"description=Hide the output of the command during package deployment (default false)"` + MaxTotalSeconds *int `json:"maxTotalSeconds,omitempty" jsonschema:"description=Timeout in seconds for the command (default to 0, no timeout)"` + MaxRetries *int `json:"maxRetries,omitempty" jsonschema:"description=Retry the command if it fails up to given number of times (default 0)"` + Dir *string `json:"dir,omitempty" jsonschema:"description=The working directory to run the command in (default is CWD)"` + Env []string `json:"env,omitempty" jsonschema:"description=Additional environment variables to set for the command"` + Cmd string `json:"cmd,omitempty" jsonschema:"description=The command to run. Must specify either cmd or wait for the action to do anything."` + SetVariable string `json:"setVariable,omitempty" jsonschema:"description=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.,pattern=^[A-Z0-9_]+$"` + Description string `json:"description,omitempty" jsonschema:"description=Description of the action to be displayed during package execution instead of the command"` + Wait *ZarfComponentActionWait `json:"wait,omitempty" jsonschema:"description=Wait for a condition to be met before continuing. Must specify either cmd or wait for the action to do anything. If both are specified, the command will be run first, then the wait condition will be checked. See the 'zarf tools wait-for' command for more info."` +} + +// ZarfComponentActionWait specifies a condition to wait for before continuing +type ZarfComponentActionWait struct { + Cluster *ZarfComponentActionWaitCluster `json:"cluster,omitempty" jsonschema:"description=Wait for a condition to be met in the cluster before continuing. Only one of cluster or network can be specified."` + Network *ZarfComponentActionWaitNetwork `json:"network,omitempty" jsonschema:"description=Wait for a condition to be met on the network before continuing. Only one of cluster or network can be specified."` +} + +// ZarfComponentActionWaitCluster specifies a condition to wait for before continuing +type ZarfComponentActionWaitCluster struct { + Kind string `json:"kind" jsonschema:"description=The kind of resource to wait for (e.g. Pod; Deployment; etc.)"` + Identifier string `json:"name" jsonschema:"description=The name of the resource or selector to wait for (e.g. podinfo; app=podinfo; etc.)"` + Namespace string `json:"namespace,omitempty" jsonschema:"description=The namespace of the resource to wait for"` + Condition string `json:"condition,omitempty" jsonschema:"description=The condition to wait for (e.g. Ready; Available; etc.). Defautls to exist, a special condition that will wait for the resource to exist."` +} + +// ZarfComponentActionWaitNetwork specifies a condition to wait for before continuing +type ZarfComponentActionWaitNetwork struct { + Protocol string `json:"protocol" jsonschema:"description=The protocol to wait for (e.g. tcp; http; etc.).,enum=tcp,enum=http,enum=https"` + Address string `json:"address" jsonschema:"description=The address to wait for (e.g. localhost:8080; 1.1.1.1; etc.)"` + Code int `json:"code,omitempty" jsonschema:"description=The HTTP status code to wait for if using http or https (e.g. 200; 404; etc.)"` } // ZarfContainerTarget defines the destination info for a ZarfData target diff --git a/src/ui/lib/api-types.ts b/src/ui/lib/api-types.ts index beceae8218..1a4d8981d4 100644 --- a/src/ui/lib/api-types.ts +++ b/src/ui/lib/api-types.ts @@ -305,7 +305,7 @@ export interface ZarfComponentActionSet { export interface ZarfComponentAction { /** - * The command to run + * The command to run. Must specify either cmd or wait for the action to do anything. */ cmd?: string; /** @@ -337,6 +337,76 @@ export interface ZarfComponentAction { * available to all remaining actions and components in the package. */ setVariable?: string; + /** + * Wait for a condition to be met before continuing. Must specify either cmd or wait for the + * action to do anything. If both are specified + */ + wait?: ZarfComponentActionWait; +} + +/** + * Wait for a condition to be met before continuing. Must specify either cmd or wait for the + * action to do anything. If both are specified + */ +export interface ZarfComponentActionWait { + /** + * Wait for a condition to be met in the cluster before continuing. Only one of cluster or + * network can be specified. + */ + cluster?: ZarfComponentActionWaitCluster; + /** + * Wait for a condition to be met on the network before continuing. Only one of cluster or + * network can be specified. + */ + network?: ZarfComponentActionWaitNetwork; +} + +/** + * Wait for a condition to be met in the cluster before continuing. Only one of cluster or + * network can be specified. + */ +export interface ZarfComponentActionWaitCluster { + /** + * The condition to wait for (e.g. Ready; Available; etc.). Defautls to exist + */ + condition?: string; + /** + * The kind of resource to wait for (e.g. Pod; Deployment; etc.) + */ + kind: string; + name: string; + /** + * The namespace of the resource to wait for + */ + namespace?: string; +} + +/** + * Wait for a condition to be met on the network before continuing. Only one of cluster or + * network can be specified. + */ +export interface ZarfComponentActionWaitNetwork { + /** + * The address to wait for (e.g. localhost:8080; 1.1.1.1; etc.) + */ + address: string; + /** + * The HTTP status code to wait for if using http or https (e.g. 200; 404; etc.) + */ + code?: number; + /** + * The protocol to wait for (e.g. tcp; http; etc.). + */ + protocol: Protocol; +} + +/** + * The protocol to wait for (e.g. tcp; http; etc.). + */ +export enum Protocol { + HTTP = "http", + HTTPS = "https", + TCP = "tcp", } /** @@ -1053,6 +1123,22 @@ const typeMap: any = { { json: "maxTotalSeconds", js: "maxTotalSeconds", typ: u(undefined, 0) }, { json: "mute", js: "mute", typ: u(undefined, true) }, { json: "setVariable", js: "setVariable", typ: u(undefined, "") }, + { json: "wait", js: "wait", typ: u(undefined, r("ZarfComponentActionWait")) }, + ], false), + "ZarfComponentActionWait": o([ + { json: "cluster", js: "cluster", typ: u(undefined, r("ZarfComponentActionWaitCluster")) }, + { json: "network", js: "network", typ: u(undefined, r("ZarfComponentActionWaitNetwork")) }, + ], false), + "ZarfComponentActionWaitCluster": o([ + { json: "condition", js: "condition", typ: u(undefined, "") }, + { json: "kind", js: "kind", typ: "" }, + { json: "name", js: "name", typ: "" }, + { json: "namespace", js: "namespace", typ: u(undefined, "") }, + ], false), + "ZarfComponentActionWaitNetwork": o([ + { json: "address", js: "address", typ: "" }, + { json: "code", js: "code", typ: u(undefined, 0) }, + { json: "protocol", js: "protocol", typ: r("Protocol") }, ], false), "ZarfComponentActionDefaults": o([ { json: "dir", js: "dir", typ: u(undefined, "") }, @@ -1192,6 +1278,11 @@ const typeMap: any = { { json: "setVariables", js: "setVariables", typ: m("") }, { json: "skipSBOM", js: "skipSBOM", typ: true }, ], false), + "Protocol": [ + "http", + "https", + "tcp", + ], "Architecture": [ "amd64", "arm64", diff --git a/zarf.schema.json b/zarf.schema.json index a9bd30c3a4..9ba812db7a 100644 --- a/zarf.schema.json +++ b/zarf.schema.json @@ -266,7 +266,7 @@ }, "cmd": { "type": "string", - "description": "The command to run" + "description": "The command to run. Must specify either cmd or wait for the action to do anything." }, "setVariable": { "pattern": "^[A-Z0-9_]+$", @@ -276,6 +276,11 @@ "description": { "type": "string", "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", + "description": "Wait for a condition to be met before continuing. Must specify either cmd or wait for the action to do anything. If both are specified" } }, "additionalProperties": false, @@ -350,6 +355,74 @@ "additionalProperties": false, "type": "object" }, + "ZarfComponentActionWait": { + "properties": { + "cluster": { + "$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": { + "$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." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ZarfComponentActionWaitCluster": { + "required": [ + "kind", + "name" + ], + "properties": { + "kind": { + "type": "string", + "description": "The kind of resource to wait for (e.g. Pod; Deployment; etc.)" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string", + "description": "The namespace of the resource to wait for" + }, + "condition": { + "type": "string", + "description": "The condition to wait for (e.g. Ready; Available; etc.). Defautls to exist" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ZarfComponentActionWaitNetwork": { + "required": [ + "protocol", + "address" + ], + "properties": { + "protocol": { + "enum": [ + "tcp", + "http", + "https" + ], + "type": "string", + "description": "The protocol to wait for (e.g. tcp; http; etc.)." + }, + "address": { + "type": "string", + "description": "The address to wait for (e.g. localhost:8080; 1.1.1.1; etc.)" + }, + "code": { + "type": "integer", + "description": "The HTTP status code to wait for if using http or https (e.g. 200; 404; etc.)" + } + }, + "additionalProperties": false, + "type": "object" + }, "ZarfComponentActions": { "properties": { "onCreate": { From 62d4da7eeba8d7870edc083fa842ebb5ab3b1be1 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Mon, 20 Feb 2023 10:29:33 -0600 Subject: [PATCH 09/20] words are hard --- examples/flux-test/zarf.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/flux-test/zarf.yaml b/examples/flux-test/zarf.yaml index 7566f3370e..c9eec07e26 100644 --- a/examples/flux-test/zarf.yaml +++ b/examples/flux-test/zarf.yaml @@ -33,7 +33,7 @@ components: onDeploy: after: # Waiting using the wait-for command - - description: "Podinfo deployment to be ready via cmd action" + - description: "Podinfo deployment to be available via cmd action" cmd: "./zarf tools wait-for deployment podinfo available -n podinfo" mute: true From 1a436b66bf9c3c9045dcb29afa7fb8c7f50be8a5 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Mon, 20 Feb 2023 10:31:56 -0600 Subject: [PATCH 10/20] stop changing tabs -> spaces vscode..... --- src/cmd/tools/wait.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/tools/wait.go b/src/cmd/tools/wait.go index b0beed1136..e9ae618ea4 100644 --- a/src/cmd/tools/wait.go +++ b/src/cmd/tools/wait.go @@ -36,7 +36,7 @@ var waitForCmd = &cobra.Command{ zarf tools wait-for pod my-pod-name ready -n default wait for pod my-pod-name in namespace default to be ready zarf tools wait-for p cool-pod-name ready -n cool wait for pod (using p alias) cool-pod-name in namespace cool to be ready zarf tools wait-for deployment podinfo available -n podinfo wait for deployment podinfo in namespace podinfo to be available - zarf tools wait-for pod app=podinfo ready -n podinfo wait for pod with label app=podinfo in namespace podinfo to be ready + zarf tools wait-for pod app=podinfo ready -n podinfo wait for pod with label app=podinfo in namespace podinfo to be ready zarf tools wait-for svc zarf-docker-registry exists -n zarf wait for service zarf-docker-registry in namespace zarf to exist zarf tools wait-for svc zarf-docker-registry -n zarf same as above, except exists is the default condition zarf tools wati-for crd addons.k3s.cattle.io wait for crd addons.k3s.cattle.io to exist From d05c0577c261345e757bbcc5ad09e1efee624a21 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Mon, 20 Feb 2023 10:34:51 -0600 Subject: [PATCH 11/20] saddest of sad days --- .../1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md index ffa41bff06..e2089c5e4e 100644 --- a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md +++ b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md @@ -22,7 +22,7 @@ zarf tools wait-for {KIND|PROTOCOL} {NAME|SELECTOR|URI} {CONDITION|HTTP_CODE} [f zarf tools wait-for pod my-pod-name ready -n default wait for pod my-pod-name in namespace default to be ready zarf tools wait-for p cool-pod-name ready -n cool wait for pod (using p alias) cool-pod-name in namespace cool to be ready zarf tools wait-for deployment podinfo available -n podinfo wait for deployment podinfo in namespace podinfo to be available - zarf tools wait-for pod app=podinfo ready -n podinfo wait for pod with label app=podinfo in namespace podinfo to be ready + zarf tools wait-for pod app=podinfo ready -n podinfo wait for pod with label app=podinfo in namespace podinfo to be ready zarf tools wait-for svc zarf-docker-registry exists -n zarf wait for service zarf-docker-registry in namespace zarf to exist zarf tools wait-for svc zarf-docker-registry -n zarf same as above, except exists is the default condition zarf tools wati-for crd addons.k3s.cattle.io wait for crd addons.k3s.cattle.io to exist From 3f3f3a78e3ca3fd401c8a2b3fd67a97179d75853 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Mon, 20 Feb 2023 14:14:56 -0600 Subject: [PATCH 12/20] initializing the variable might help --- src/pkg/packager/actions.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pkg/packager/actions.go b/src/pkg/packager/actions.go index 20a4c3e1be..3ee3d8c24a 100644 --- a/src/pkg/packager/actions.go +++ b/src/pkg/packager/actions.go @@ -35,10 +35,11 @@ func (p *Packager) runAction(defaultCfg types.ZarfComponentActionDefaults, actio ctx context.Context cancel context.CancelFunc cmdEscaped string - cmd string out string err error vars map[string]string + + cmd = action.Cmd ) // If the action is a wait, convert it to a command. From 09953a231be67686c664ff3f5e8669d085b1827e Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Tue, 21 Feb 2023 01:35:03 -0600 Subject: [PATCH 13/20] add wait action examples --- examples/component-actions/zarf.yaml | 30 ++++++++++++++++++++++++++++ src/pkg/packager/actions.go | 16 +++++++++++---- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/examples/component-actions/zarf.yaml b/examples/component-actions/zarf.yaml index 06462fbf82..8e2a91dbc9 100644 --- a/examples/component-actions/zarf.yaml +++ b/examples/component-actions/zarf.yaml @@ -151,3 +151,33 @@ components: - cmd: touch $ZARF_VAR_TEST_FILENAME env: - "ZARF_VAR_TEST_FILENAME=filename-from-env.txt" + + - name: on-create-with-network-wait-action + description: This component will wait for 15 seconds for a network resource to be available + actions: + onCreate: + after: + - description: Cloudflare 1.1.1.1 site to be available + maxTotalSeconds: 15 + wait: + network: + protocol: https + address: 1.1.1.1 + code: 200 + + - name: on-deploy-with-wait-action + description: This component will wait for 5 seconds for the test-configmap to be exist + manifests: + - name: test-configmap + files: + - "test-configmap.yaml" + actions: + onDeploy: + after: + - description: The simple-configmap to exist + maxTotalSeconds: 5 + wait: + cluster: + kind: configmap + name: simple-configmap + namespace: zarf diff --git a/src/pkg/packager/actions.go b/src/pkg/packager/actions.go index 3ee3d8c24a..815f9f3a6b 100644 --- a/src/pkg/packager/actions.go +++ b/src/pkg/packager/actions.go @@ -30,7 +30,6 @@ func (p *Packager) runActions(defaultCfg types.ZarfComponentActionDefaults, acti // Run commands that a component has provided. func (p *Packager) runAction(defaultCfg types.ZarfComponentActionDefaults, action types.ZarfComponentAction, valueTemplate *template.Values) error { - var ( ctx context.Context cancel context.CancelFunc @@ -47,6 +46,10 @@ func (p *Packager) runAction(defaultCfg types.ZarfComponentActionDefaults, actio if cmd, err = convertWaitToCmd(*action.Wait); err != nil { return err } + + // If the action is a wait, mute the output becuase it will be noisy. + t := true + action.Mute = &t } if action.Description != "" { @@ -55,7 +58,7 @@ func (p *Packager) runAction(defaultCfg types.ZarfComponentActionDefaults, actio cmdEscaped = escapeCmdForPrint(cmd) } - spinner := message.NewProgressSpinner("Running command \"%s\"", cmdEscaped) + spinner := message.NewProgressSpinner("Running \"%s\"", cmdEscaped) // Persist the spinner output so it doesn't get overwritten by the command output. spinner.EnablePreserveWrites() @@ -92,9 +95,14 @@ func (p *Packager) runAction(defaultCfg types.ZarfComponentActionDefaults, actio p.setVariable(action.SetVariable, out) } - // If the command ran successfully, continue to the next action. - spinner.Successf("Completed command \"%s\"", cmdEscaped) + // If the action has a wait, change the spinner message to reflect that on success. + if action.Wait != nil { + spinner.Successf("Wait for \"%s\" succeeded", cmdEscaped) + } else { + spinner.Successf("Completed \"%s\"", cmdEscaped) + } + // If the command ran successfully, continue to the next action. return nil } From 148970c6ee9a17871cb642c92f38ad19d5aace6c Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Tue, 21 Feb 2023 02:02:55 -0600 Subject: [PATCH 14/20] update test to match new action spinnner messsage --- src/test/e2e/02_component_actions_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/e2e/02_component_actions_test.go b/src/test/e2e/02_component_actions_test.go index 01728a1e83..80ee621009 100644 --- a/src/test/e2e/02_component_actions_test.go +++ b/src/test/e2e/02_component_actions_test.go @@ -38,11 +38,11 @@ func TestComponentActions(t *testing.T) { // Try creating the package to test the onCreate actions. stdOut, stdErr, err := e2e.execZarfCommand("package", "create", "examples/component-actions", "--confirm") require.NoError(t, err, stdOut, stdErr) - require.Contains(t, stdErr, "Completed command \"touch test-create-before.txt\"") + require.Contains(t, stdErr, "Completed \"touch test-create-before.txt\"") require.Contains(t, stdErr, "multiline!") require.Contains(t, stdErr, "updates!") require.Contains(t, stdErr, "realtime!") - require.Contains(t, stdErr, "Completed command \"multiline & description demo\"") + require.Contains(t, stdErr, "Completed \"multiline & description demo\"") // Test for package create prepare artifacts. for _, artifact := range createArtifacts { From 5d766a3756c7bce682bcabe0a93a27c4217e7474 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Tue, 21 Feb 2023 11:29:24 -0600 Subject: [PATCH 15/20] more worky --- .../100-cli-commands/zarf_tools_wait-for.md | 2 + docs/4-user-guide/3-zarf-schema.md | 2 +- docs/4-user-guide/5-component-actions.md | 33 ++++++++++++--- src/cmd/tools/wait.go | 35 +++++++++++++--- src/pkg/packager/actions.go | 40 ++++++++++++++++--- src/types/component.go | 6 +-- src/ui/lib/api-types.ts | 4 +- zarf.schema.json | 2 +- 8 files changed, 101 insertions(+), 23 deletions(-) diff --git a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md index e2089c5e4e..0549eb1787 100644 --- a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md +++ b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_wait-for.md @@ -31,6 +31,8 @@ zarf tools wait-for {KIND|PROTOCOL} {NAME|SELECTOR|URI} {CONDITION|HTTP_CODE} [f zarf tools wait-for http localhost:8080 200 wait for a 200 response from http://localhost:8080 zarf tools wait-for tcp localhost:8080 wait for a connection to be established on localhost:8080 zarf tools wait-for https 1.1.1.1 200 wait for a 200 response from https://1.1.1.1 + zarf tools wait-for http google.com wait for any 2xx response from http://google.com + zarf tools wait-for http google.com success wait for any 2xx response from http://google.com ``` diff --git a/docs/4-user-guide/3-zarf-schema.md b/docs/4-user-guide/3-zarf-schema.md index 1303971967..26c226cc54 100644 --- a/docs/4-user-guide/3-zarf-schema.md +++ b/docs/4-user-guide/3-zarf-schema.md @@ -1051,7 +1051,7 @@ Must be one of:  
-**Description:** 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. +**Description:** (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. | | | | -------- | -------- | diff --git a/docs/4-user-guide/5-component-actions.md b/docs/4-user-guide/5-component-actions.md index 3316e7dcfb..5928ff3c17 100644 --- a/docs/4-user-guide/5-component-actions.md +++ b/docs/4-user-guide/5-component-actions.md @@ -15,7 +15,7 @@ Component Actions provide a number of exec entrypoints for a component to perfor These `action sets` contain (optional) `action` lists. The `onSuccess` and `onFailure` action lists are conditional and depend on the success or failure of previous actions in the same component as well as steps in the component lifecycle. - `before` - sequential list of actions that will run before this component is processed for `create`, `deploy`, or `remove` -- `after` - sequential list of actions that will run after this component is successfully processed for `create`, `deploy`, or `remove` +- `after` - sequential list of actions that will run after this component is successfully processed for `create`, `deploy`, or `remove` - `onSuccess` - sequential list of actions that will run after **ALL** `after` actions have successfully completed - `onFailure` - sequential list of actions that will run after **ANY** error during the above actions or component operations @@ -103,23 +103,44 @@ actions: - cmd: echo "before" ``` -## Action Configuration +## Common Action Configuration Keys -Within each of the `action` lists (`before`, `after`, `onSuccess`, and `onFailure`), the following action configurations are available: +The following keys are common to all action configurations (wait or command): -- `cmd` - (required) the command to run +- `description` - a description of the action that will replace the text the users sees when the action is running, e.g. `description: "File to be created"` would show `Waiting for "File to be created"` instead of `Waiting for "touch test-create-before.txt"` +- `maxTotalSeconds` - the maximum total time to allow the command to run (default: `0` - no limit for command actions, `300` - 5 minutes for wait actions) + +## Command Action Configuration + +A command action executes arbitrary commands or scripts in a shell wrapper. Use the `cmd` key to define the command(s) to run. This can also be a multi-line script. _You cannot use `cmd` and `wait` in the same action_. Within each of the `action` lists (`before`, `after`, `onSuccess`, and `onFailure`), the following action configurations are available: + +- `cmd` - (required if not a wait action) the command to run - `dir` - the directory to run the command in, defaults to the current working directory - `mute` - whether to mute the realtime output of the command, output is always shown at the end on failure (default: `false`) -- `maxTotalSeconds` - the maximum total time to allow the command to run (default: `0` - no limit) - `maxRetries` - the maximum number of times to retry the command if it fails (default: `0` - no retries) - `env` - an array of environment variables to set for the command in the form of `name=value` - `setVariable` - set the standard output of the command to a variable that can be used in other actions or components +## Wait Action Configuration + +A wait action holds the component stage it is triggered in until the condition is met, or triggers a failure if the timeout is exceeded (defaults to 5 minutes). Use the `wait` key to define the wait paramaters, _you cannot use `cmd` and `wait` in the same action_. A wait action is really just _yaml sugar_ for a call to `./zarf tools wait-for`, but in a more exciting yaml-sort-of-way. Within each of the `action` lists (`before`, `after`, `onSuccess`, and `onFailure`), the following action configurations are available: + +- `wait` - (required if not a cmd action) the wait parameters + - `cluster` - perform a wait operation on a Kubernetes resource (kubectl wait) + - `kind` - the kind of resource to wait for (required) + - `identifier` - the identifier of the resource to wait for (required), can be a name or label selector + - `namespace` - the namespace of the resource to wait for + - `condition` - the condition to wait for (default: `exists`) + - `network` - perform a wait operation on a network resource (curl) + - `protocol` - the protocol to use (i.e. `http`, `https`, `tcp`) + - `address` - the address/port to wait for (required) + - `code` - the HTTP status code to wait for if using `http` or `https`, or `success` to check for any 2xx response code (default: `success`) + --- ## Creating dynamic variables from actions -You can use the `setVariable` action configuration to set a variable that can be used in other actions or components. The variable will be set in the environment variable `ZARF_VAR_{NAME}` and `TF_VAR_{name}` in the remaining actions as well as available for templating in files or manifests in the remaining components as `###ZARF_VAR_{NAME}###`. This feature allows package authors to define dynamic runtime variables for consumption by other components or actions. *Unlike normal variables, these do not need to be defined at the top of the `zarf.yaml` and can be used during `package create`, `package deploy` or `package remove`.* +You can use the `setVariable` action configuration to set a variable that can be used in other actions or components. The variable will be set in the environment variable `ZARF_VAR_{NAME}` and `TF_VAR_{name}` in the remaining actions as well as available for templating in files or manifests in the remaining components as `###ZARF_VAR_{NAME}###`. This feature allows package authors to define dynamic runtime variables for consumption by other components or actions. _Unlike normal variables, these do not need to be defined at the top of the `zarf.yaml` and can be used during `package create`, `package deploy` or `package remove`._ ## More examples diff --git a/src/cmd/tools/wait.go b/src/cmd/tools/wait.go index e9ae618ea4..106aa77b32 100644 --- a/src/cmd/tools/wait.go +++ b/src/cmd/tools/wait.go @@ -45,6 +45,8 @@ var waitForCmd = &cobra.Command{ zarf tools wait-for http localhost:8080 200 wait for a 200 response from http://localhost:8080 zarf tools wait-for tcp localhost:8080 wait for a connection to be established on localhost:8080 zarf tools wait-for https 1.1.1.1 200 wait for a 200 response from https://1.1.1.1 + zarf tools wait-for http google.com wait for any 2xx response from http://google.com + zarf tools wait-for http google.com success wait for any 2xx response from http://google.com `, Args: cobra.MinimumNArgs(2), Run: func(cmd *cobra.Command, args []string) { @@ -115,7 +117,7 @@ var waitForCmd = &cobra.Command{ continue } - // If only checking for existence, exit here + // If only checking for existence, exit here. switch condition { case "", "exist", "exists": spinner.Success() @@ -127,11 +129,14 @@ var waitForCmd = &cobra.Command{ args = []string{"tools", "kubectl", "wait", "-n", waitNamespace, kind, identifier, "--for", "condition=" + condition, "--timeout=" + waitTimeout} + + // If there is an error, log it and try again. if stdout, stderr, err := exec.Cmd(zarf, args...); err != nil { message.Debug(stdout, stderr, err) continue } + // And just like that, success! spinner.Successf(conditionMsg) return } @@ -144,15 +149,19 @@ func waitForNetworkEndpoint(resource, name, condition string, timeout time.Durat expired := time.After(timeout) // Setup the spinner messages. + condition = strings.ToLower(condition) if condition == "" { - condition = "available" + condition = "success" } - spinner := message.NewProgressSpinner("Waiting for network endpoint %s://%s to be %s.", resource, name, condition) + spinner := message.NewProgressSpinner("Waiting for network endpoint %s://%s to respond %s.", resource, name, condition) defer spinner.Stop() + delay := 100 * time.Millisecond + for { - // Delay the check for 1 second - time.Sleep(time.Second) + // Delay the check for 100ms the first time and then 1 second after that. + time.Sleep(delay) + delay = time.Second select { case <-expired: @@ -165,6 +174,21 @@ func waitForNetworkEndpoint(resource, name, condition string, timeout time.Durat // Handle HTTP and HTTPS endpoints. url := fmt.Sprintf("%s://%s", resource, name) + // Default to checking for a 2xx response. + if condition == "success" { + // Try to get the URL and check the status code. + resp, err := http.Get(url) + + // If the status code is not in the 2xx range, try again. + if err != nil || resp.StatusCode < 200 || resp.StatusCode > 299 { + message.Debug(err) + continue + } + + // Success, break out of the swtich statement. + break + } + // Convert the condition to an int and check if it's a valid HTTP status code. code, err := strconv.Atoi(condition) if err != nil || http.StatusText(code) == "" { @@ -188,6 +212,7 @@ func waitForNetworkEndpoint(resource, name, condition string, timeout time.Durat defer conn.Close() } + // Yay, we made it! spinner.Success() return } diff --git a/src/pkg/packager/actions.go b/src/pkg/packager/actions.go index 815f9f3a6b..82249ebed6 100644 --- a/src/pkg/packager/actions.go +++ b/src/pkg/packager/actions.go @@ -43,13 +43,30 @@ func (p *Packager) runAction(defaultCfg types.ZarfComponentActionDefaults, actio // If the action is a wait, convert it to a command. if action.Wait != nil { - if cmd, err = convertWaitToCmd(*action.Wait); err != nil { + // If the wait has no timeout, set a default of 5 minutes. + if action.MaxTotalSeconds == nil { + fiveMin := 300 + action.MaxTotalSeconds = &fiveMin + } + + // Convert the wait to a command. + if cmd, err = convertWaitToCmd(*action.Wait, action.MaxTotalSeconds); err != nil { return err } - // If the action is a wait, mute the output becuase it will be noisy. + // Mute the output becuase it will be noisy. t := true action.Mute = &t + + // Set the max retries to 0. + z := 0 + action.MaxRetries = &z + + // Not used for wait actions. + d := "" + action.Dir = &d + action.Env = []string{} + action.SetVariable = "" } if action.Description != "" { @@ -139,7 +156,10 @@ func (p *Packager) runAction(defaultCfg types.ZarfComponentActionDefaults, actio } // convertWaitToCmd will return the wait command if it exists, otherwise it will return the original command. -func convertWaitToCmd(wait types.ZarfComponentActionWait) (string, error) { +func convertWaitToCmd(wait types.ZarfComponentActionWait, timeout *int) (string, error) { + // Build the timeout string. + timeoutString := fmt.Sprintf("--timeout %ds", *timeout) + // If the action has a wait, build a cmd from that instead. cluster := wait.Cluster if cluster != nil { @@ -149,13 +169,23 @@ func convertWaitToCmd(wait types.ZarfComponentActionWait) (string, error) { } // Build a call to the zarf tools wait-for command. - return fmt.Sprintf("./zarf tools wait-for %s %s %s %s", cluster.Kind, cluster.Identifier, cluster.Condition, ns), nil + return fmt.Sprintf("./zarf tools wait-for %s %s %s %s %s", + cluster.Kind, cluster.Identifier, cluster.Condition, ns, timeoutString), nil } network := wait.Network if network != nil { + // Make sure the protocol is lower case. + network.Protocol = strings.ToLower(network.Protocol) + + // If the protocol is http and no code is set, default to 200. + if strings.HasPrefix(network.Protocol, "http") && network.Code == 0 { + network.Code = 200 + } + // Build a call to the zarf tools wait-for command. - return fmt.Sprintf("./zarf tools wait-for %s %s %d", network.Protocol, network.Address, network.Code), nil + return fmt.Sprintf("./zarf tools wait-for %s %s %d %s", + network.Protocol, network.Address, network.Code, timeoutString), nil } return "", fmt.Errorf("wait action is missing a cluster or network") diff --git a/src/types/component.go b/src/types/component.go index 0d53993ee2..f30577f717 100644 --- a/src/types/component.go +++ b/src/types/component.go @@ -138,12 +138,12 @@ type ZarfComponentActionDefaults struct { // ZarfComponentAction represents a single action to run during a zarf package operation type ZarfComponentAction struct { Mute *bool `json:"mute,omitempty" jsonschema:"description=Hide the output of the command during package deployment (default false)"` - MaxTotalSeconds *int `json:"maxTotalSeconds,omitempty" jsonschema:"description=Timeout in seconds for the command (default to 0, no timeout)"` + MaxTotalSeconds *int `json:"maxTotalSeconds,omitempty" jsonschema:"description=Timeout in seconds for the command (default to 0, no timeout for cmd actions and 300, 5 minutes for wait actions)"` MaxRetries *int `json:"maxRetries,omitempty" jsonschema:"description=Retry the command if it fails up to given number of times (default 0)"` Dir *string `json:"dir,omitempty" jsonschema:"description=The working directory to run the command in (default is CWD)"` Env []string `json:"env,omitempty" jsonschema:"description=Additional environment variables to set for the command"` Cmd string `json:"cmd,omitempty" jsonschema:"description=The command to run. Must specify either cmd or wait for the action to do anything."` - SetVariable string `json:"setVariable,omitempty" jsonschema:"description=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.,pattern=^[A-Z0-9_]+$"` + SetVariable string `json:"setVariable,omitempty" jsonschema:"description=(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.,pattern=^[A-Z0-9_]+$"` Description string `json:"description,omitempty" jsonschema:"description=Description of the action to be displayed during package execution instead of the command"` Wait *ZarfComponentActionWait `json:"wait,omitempty" jsonschema:"description=Wait for a condition to be met before continuing. Must specify either cmd or wait for the action to do anything. If both are specified, the command will be run first, then the wait condition will be checked. See the 'zarf tools wait-for' command for more info."` } @@ -154,7 +154,7 @@ type ZarfComponentActionWait struct { Network *ZarfComponentActionWaitNetwork `json:"network,omitempty" jsonschema:"description=Wait for a condition to be met on the network before continuing. Only one of cluster or network can be specified."` } -// ZarfComponentActionWaitCluster specifies a condition to wait for before continuing +// ZarfComponenatActionWaitCluster specifies a condition to wait for before continuing type ZarfComponentActionWaitCluster struct { Kind string `json:"kind" jsonschema:"description=The kind of resource to wait for (e.g. Pod; Deployment; etc.)"` Identifier string `json:"name" jsonschema:"description=The name of the resource or selector to wait for (e.g. podinfo; app=podinfo; etc.)"` diff --git a/src/ui/lib/api-types.ts b/src/ui/lib/api-types.ts index 1a4d8981d4..4ba65a39a1 100644 --- a/src/ui/lib/api-types.ts +++ b/src/ui/lib/api-types.ts @@ -333,8 +333,8 @@ export interface ZarfComponentAction { */ mute?: boolean; /** - * 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. + * (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. */ setVariable?: string; /** diff --git a/zarf.schema.json b/zarf.schema.json index 9ba812db7a..0b2d79b692 100644 --- a/zarf.schema.json +++ b/zarf.schema.json @@ -271,7 +271,7 @@ "setVariable": { "pattern": "^[A-Z0-9_]+$", "type": "string", - "description": "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." + "description": "(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." }, "description": { "type": "string", From 4fc096b0cf93eff7bbca51377c75a9caffb97dbc Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Tue, 21 Feb 2023 11:45:34 -0600 Subject: [PATCH 16/20] lint things --- src/cmd/internal.go | 6 +++--- src/cmd/tools/common.go | 3 ++- src/config/lang/english.go | 6 +++--- src/types/component.go | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/cmd/internal.go b/src/cmd/internal.go index f017e6a4b6..5dfc49e65f 100644 --- a/src/cmd/internal.go +++ b/src/cmd/internal.go @@ -75,12 +75,12 @@ var configSchemaCmd = &cobra.Command{ var apiSchemaCmd = &cobra.Command{ Use: "api-schema", - Short: lang.CmdInternalApiSchemaShort, + Short: lang.CmdInternalAPISchemaShort, Run: func(cmd *cobra.Command, args []string) { schema := jsonschema.Reflect(&types.RestAPI{}) output, err := json.MarshalIndent(schema, "", " ") if err != nil { - message.Fatal(err, lang.CmdInternalApiSchemaGenerateErr) + message.Fatal(err, lang.CmdInternalAPISchemaGenerateErr) } fmt.Print(string(output) + "\n") }, @@ -106,7 +106,7 @@ var createReadOnlyGiteaUser = &cobra.Command{ var uiCmd = &cobra.Command{ Use: "ui", - Short: lang.CmdInternalUiShort, + Short: lang.CmdInternalUIShort, Run: func(cmd *cobra.Command, args []string) { api.LaunchAPIServer() }, diff --git a/src/cmd/tools/common.go b/src/cmd/tools/common.go index 83f20fac9a..efbbd6049c 100644 --- a/src/cmd/tools/common.go +++ b/src/cmd/tools/common.go @@ -22,11 +22,12 @@ var toolsCmd = &cobra.Command{ Short: lang.CmdToolsShort, } +// Include adds the tools command to the root command. func Include(rootCmd *cobra.Command) { rootCmd.AddCommand(toolsCmd) } -// Check if the command is being run as a vendor-only command +// CheckVendorOnly checks if the command is being run as a vendor-only command func CheckVendorOnly() bool { vendorCmd := []string{ "kubectl", diff --git a/src/config/lang/english.go b/src/config/lang/english.go index 2380be0435..c1c0942c17 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -151,8 +151,8 @@ const ( CmdInternalConfigSchemaShort = "Generates a JSON schema for the zarf.yaml configuration" CmdInternalConfigSchemaErr = "Unable to generate the zarf config schema" - CmdInternalApiSchemaShort = "Generates a JSON schema from the API types" - CmdInternalApiSchemaGenerateErr = "Unable to generate the zarf api schema" + CmdInternalAPISchemaShort = "Generates a JSON schema from the API types" + CmdInternalAPISchemaGenerateErr = "Unable to generate the zarf api schema" CmdInternalCreateReadOnlyGiteaUserShort = "Creates a read-only user in Gitea" CmdInternalCreateReadOnlyGiteaUserLong = "Creates a read-only user in Gitea by using the Gitea API. " + @@ -160,7 +160,7 @@ const ( CmdInternalCreateReadOnlyGiteaUserLoadErr = "Unable to load the Zarf state" CmdInternalCreateReadOnlyGiteaUserErr = "Unable to create a read-only user in the Gitea service." - CmdInternalUiShort = "Launch the experimental Zarf UI" + CmdInternalUIShort = "Launch the experimental Zarf UI" CmdInternalIsValidHostnameShort = "Checks if the current machine's hostname is RFC1123 compliant" diff --git a/src/types/component.go b/src/types/component.go index f30577f717..06c4cc5936 100644 --- a/src/types/component.go +++ b/src/types/component.go @@ -154,7 +154,7 @@ type ZarfComponentActionWait struct { Network *ZarfComponentActionWaitNetwork `json:"network,omitempty" jsonschema:"description=Wait for a condition to be met on the network before continuing. Only one of cluster or network can be specified."` } -// ZarfComponenatActionWaitCluster specifies a condition to wait for before continuing +// ZarfComponentActionWaitCluster specifies a condition to wait for before continuing type ZarfComponentActionWaitCluster struct { Kind string `json:"kind" jsonschema:"description=The kind of resource to wait for (e.g. Pod; Deployment; etc.)"` Identifier string `json:"name" jsonschema:"description=The name of the resource or selector to wait for (e.g. podinfo; app=podinfo; etc.)"` From ea0aaa76869f043105e257f4f81517d8734b5209 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Tue, 21 Feb 2023 11:47:23 -0600 Subject: [PATCH 17/20] pr comments --- examples/flux-test/zarf.yaml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/examples/flux-test/zarf.yaml b/examples/flux-test/zarf.yaml index c9eec07e26..d7128db254 100644 --- a/examples/flux-test/zarf.yaml +++ b/examples/flux-test/zarf.yaml @@ -32,14 +32,8 @@ components: actions: onDeploy: after: - # Waiting using the wait-for command - - description: "Podinfo deployment to be available via cmd action" - cmd: "./zarf tools wait-for deployment podinfo available -n podinfo" - mute: true - - # Waiting using the wait action + # This will use a wait action to wait for the podinfo pods to be ready - description: "Podinfo pods to be ready via wait action" - mute: true wait: cluster: kind: pod From 3e983683a7d8d0ee9f8a4d4d19a2ee151362e443 Mon Sep 17 00:00:00 2001 From: Megamind <882485+jeff-mccoy@users.noreply.github.com> Date: Tue, 21 Feb 2023 13:25:22 -0600 Subject: [PATCH 18/20] Update docs/4-user-guide/5-component-actions.md Co-authored-by: Wayne Starr --- docs/4-user-guide/5-component-actions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/4-user-guide/5-component-actions.md b/docs/4-user-guide/5-component-actions.md index 5928ff3c17..03c180c15b 100644 --- a/docs/4-user-guide/5-component-actions.md +++ b/docs/4-user-guide/5-component-actions.md @@ -123,7 +123,7 @@ A command action executes arbitrary commands or scripts in a shell wrapper. Use ## Wait Action Configuration -A wait action holds the component stage it is triggered in until the condition is met, or triggers a failure if the timeout is exceeded (defaults to 5 minutes). Use the `wait` key to define the wait paramaters, _you cannot use `cmd` and `wait` in the same action_. A wait action is really just _yaml sugar_ for a call to `./zarf tools wait-for`, but in a more exciting yaml-sort-of-way. Within each of the `action` lists (`before`, `after`, `onSuccess`, and `onFailure`), the following action configurations are available: +A wait action pauses the component stage it is triggered in until the condition is met, or triggers a failure if maxTotalSeconds is exceeded (defaults to 5 minutes). Use the `wait` key to define the wait paramaters, _you cannot use `cmd` and `wait` in the same action_. A wait action is really just _yaml sugar_ for a call to `./zarf tools wait-for`, but in a more exciting yaml-sort-of-way. Within each of the `action` lists (`before`, `after`, `onSuccess`, and `onFailure`), the following action configurations are available: - `wait` - (required if not a cmd action) the wait parameters - `cluster` - perform a wait operation on a Kubernetes resource (kubectl wait) From c50811b29dbba9d5ab605abab7d7f82a20bb611a Mon Sep 17 00:00:00 2001 From: Megamind <882485+jeff-mccoy@users.noreply.github.com> Date: Tue, 21 Feb 2023 13:25:33 -0600 Subject: [PATCH 19/20] Update docs/4-user-guide/5-component-actions.md Co-authored-by: Wayne Starr --- docs/4-user-guide/5-component-actions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/4-user-guide/5-component-actions.md b/docs/4-user-guide/5-component-actions.md index 03c180c15b..d1728eaf70 100644 --- a/docs/4-user-guide/5-component-actions.md +++ b/docs/4-user-guide/5-component-actions.md @@ -107,7 +107,7 @@ actions: The following keys are common to all action configurations (wait or command): -- `description` - a description of the action that will replace the text the users sees when the action is running, e.g. `description: "File to be created"` would show `Waiting for "File to be created"` instead of `Waiting for "touch test-create-before.txt"` +- `description` - a description of the action that will replace the text the user sees when the action is running, e.g. `description: "File to be created"` would show `Waiting for "File to be created"` instead of `Waiting for "touch test-create-before.txt"` - `maxTotalSeconds` - the maximum total time to allow the command to run (default: `0` - no limit for command actions, `300` - 5 minutes for wait actions) ## Command Action Configuration From a8618a7a3dd16246b9cc19bc8978438331a02d3e Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Tue, 21 Feb 2023 13:28:06 -0600 Subject: [PATCH 20/20] fix schema description --- docs/4-user-guide/3-zarf-schema.md | 2 +- src/types/component.go | 2 +- src/ui/lib/api-types.ts | 4 ++-- zarf.schema.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/4-user-guide/3-zarf-schema.md b/docs/4-user-guide/3-zarf-schema.md index 26c226cc54..4789ea3cb3 100644 --- a/docs/4-user-guide/3-zarf-schema.md +++ b/docs/4-user-guide/3-zarf-schema.md @@ -1087,7 +1087,7 @@ Must be one of:  
-**Description:** Wait for a condition to be met before continuing. Must specify either cmd or wait for the action to do anything. If both are specified +**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. | | | | ------------------------- | -------------------------------------------------------------------------------------------------------- | diff --git a/src/types/component.go b/src/types/component.go index 06c4cc5936..482fe6248b 100644 --- a/src/types/component.go +++ b/src/types/component.go @@ -145,7 +145,7 @@ type ZarfComponentAction struct { Cmd string `json:"cmd,omitempty" jsonschema:"description=The command to run. Must specify either cmd or wait for the action to do anything."` SetVariable string `json:"setVariable,omitempty" jsonschema:"description=(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.,pattern=^[A-Z0-9_]+$"` Description string `json:"description,omitempty" jsonschema:"description=Description of the action to be displayed during package execution instead of the command"` - Wait *ZarfComponentActionWait `json:"wait,omitempty" jsonschema:"description=Wait for a condition to be met before continuing. Must specify either cmd or wait for the action to do anything. If both are specified, the command will be run first, then the wait condition will be checked. See the 'zarf tools wait-for' command for more info."` + Wait *ZarfComponentActionWait `json:"wait,omitempty" jsonschema:"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."` } // ZarfComponentActionWait specifies a condition to wait for before continuing diff --git a/src/ui/lib/api-types.ts b/src/ui/lib/api-types.ts index 4ba65a39a1..3c726252e7 100644 --- a/src/ui/lib/api-types.ts +++ b/src/ui/lib/api-types.ts @@ -339,14 +339,14 @@ export interface ZarfComponentAction { setVariable?: string; /** * Wait for a condition to be met before continuing. Must specify either cmd or wait for the - * action to do anything. If both are specified + * action. See the 'zarf tools wait-for' command for more info. */ wait?: ZarfComponentActionWait; } /** * Wait for a condition to be met before continuing. Must specify either cmd or wait for the - * action to do anything. If both are specified + * action. See the 'zarf tools wait-for' command for more info. */ export interface ZarfComponentActionWait { /** diff --git a/zarf.schema.json b/zarf.schema.json index 0b2d79b692..86dd04c629 100644 --- a/zarf.schema.json +++ b/zarf.schema.json @@ -280,7 +280,7 @@ "wait": { "$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 to do anything. If both are specified" + "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." } }, "additionalProperties": false,