Skip to content

Commit 5b54240

Browse files
committed
refactor: normal creator
Signed-off-by: Philip Laine <[email protected]>
1 parent 2428b2d commit 5b54240

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3628
-235
lines changed

examples/manifests/zarf.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ components:
3939
kustomizations:
4040
# kustomizations can be specified relative to the `zarf.yaml` or as remoteBuild resources with the
4141
# following syntax: https://github.com/kubernetes-sigs/kustomize/blob/master/examples/remoteBuild.md:
42-
- github.com/stefanprodan/podinfo//kustomize?ref=6.4.0
42+
- https://github.com/stefanprodan/podinfo//kustomize?ref=6.4.0
4343
# while ?ref= is not a requirement, it is recommended to use a specific commit hash / git tag to
4444
# ensure that the kustomization is not changed in a way that breaks your deployment.
4545
# image discovery is supported in all manifests and charts using:

src/cmd/package.go

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"github.com/zarf-dev/zarf/src/internal/dns"
2727
"github.com/zarf-dev/zarf/src/internal/packager2"
2828
"github.com/zarf-dev/zarf/src/pkg/cluster"
29-
"github.com/zarf-dev/zarf/src/pkg/lint"
3029
"github.com/zarf-dev/zarf/src/pkg/message"
3130
"github.com/zarf-dev/zarf/src/pkg/packager"
3231
"github.com/zarf-dev/zarf/src/pkg/packager/filters"
@@ -59,19 +58,18 @@ var packageCreateCmd = &cobra.Command{
5958
pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap(
6059
v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper)
6160

62-
pkgClient, err := packager.New(&pkgConfig)
63-
if err != nil {
64-
return err
65-
}
66-
defer pkgClient.ClearTempPaths()
67-
68-
err = pkgClient.Create(cmd.Context())
69-
var lintErr *lint.LintError
70-
if errors.As(err, &lintErr) {
71-
common.PrintFindings(lintErr)
61+
opt := packager2.CreateOptions{
62+
Flavor: pkgConfig.CreateOpts.Flavor,
63+
RegistryOverrides: pkgConfig.CreateOpts.RegistryOverrides,
64+
SigningKeyPath: pkgConfig.CreateOpts.SigningKeyPath,
65+
SigningKeyPassword: pkgConfig.CreateOpts.SigningKeyPassword,
66+
SetVariables: pkgConfig.CreateOpts.SetVariables,
67+
MaxPackageSizeMB: pkgConfig.CreateOpts.MaxPackageSizeMB,
68+
Output: pkgConfig.CreateOpts.Output,
7269
}
70+
err := packager2.Create(cmd.Context(), pkgConfig.CreateOpts.BaseDir, opt)
7371
if err != nil {
74-
return fmt.Errorf("failed to create package: %w", err)
72+
return err
7573
}
7674
return nil
7775
},
@@ -489,8 +487,6 @@ func bindCreateFlags(v *viper.Viper) {
489487

490488
createFlags.StringVar(&pkgConfig.CreateOpts.DifferentialPackagePath, "differential", v.GetString(common.VPkgCreateDifferential), lang.CmdPackageCreateFlagDifferential)
491489
createFlags.StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdPackageCreateFlagSet)
492-
createFlags.BoolVarP(&pkgConfig.CreateOpts.ViewSBOM, "sbom", "s", v.GetBool(common.VPkgCreateSbom), lang.CmdPackageCreateFlagSbom)
493-
createFlags.StringVar(&pkgConfig.CreateOpts.SBOMOutputDir, "sbom-out", v.GetString(common.VPkgCreateSbomOutput), lang.CmdPackageCreateFlagSbomOut)
494490
createFlags.BoolVar(&pkgConfig.CreateOpts.SkipSBOM, "skip-sbom", v.GetBool(common.VPkgCreateSkipSbom), lang.CmdPackageCreateFlagSkipSbom)
495491
createFlags.IntVarP(&pkgConfig.CreateOpts.MaxPackageSizeMB, "max-package-size", "m", v.GetInt(common.VPkgCreateMaxPackageSize), lang.CmdPackageCreateFlagMaxPackageSize)
496492
createFlags.StringToStringVar(&pkgConfig.CreateOpts.RegistryOverrides, "registry-override", v.GetStringMapString(common.VPkgCreateRegistryOverride), lang.CmdPackageCreateFlagRegistryOverride)
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
3+
4+
// Package actions contains functions for running component actions within Zarf packages.
5+
package actions
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"path/filepath"
11+
"regexp"
12+
"runtime"
13+
"strings"
14+
"time"
15+
16+
"github.com/defenseunicorns/pkg/helpers/v2"
17+
"github.com/zarf-dev/zarf/src/api/v1alpha1"
18+
"github.com/zarf-dev/zarf/src/internal/packager/template"
19+
"github.com/zarf-dev/zarf/src/pkg/message"
20+
"github.com/zarf-dev/zarf/src/pkg/utils"
21+
"github.com/zarf-dev/zarf/src/pkg/utils/exec"
22+
"github.com/zarf-dev/zarf/src/pkg/variables"
23+
)
24+
25+
// Run runs all provided actions.
26+
func Run(ctx context.Context, basePath string, defaultCfg v1alpha1.ZarfComponentActionDefaults, actions []v1alpha1.ZarfComponentAction, variableConfig *variables.VariableConfig) error {
27+
if variableConfig == nil {
28+
variableConfig = template.GetZarfVariableConfig()
29+
}
30+
31+
for _, a := range actions {
32+
if err := runAction(ctx, basePath, defaultCfg, a, variableConfig); err != nil {
33+
return err
34+
}
35+
}
36+
return nil
37+
}
38+
39+
// Run commands that a component has provided.
40+
func runAction(ctx context.Context, basePath string, defaultCfg v1alpha1.ZarfComponentActionDefaults, action v1alpha1.ZarfComponentAction, variableConfig *variables.VariableConfig) error {
41+
var (
42+
cmdEscaped string
43+
out string
44+
err error
45+
46+
cmd = action.Cmd
47+
)
48+
49+
// If the action is a wait, convert it to a command.
50+
if action.Wait != nil {
51+
// If the wait has no timeout, set a default of 5 minutes.
52+
if action.MaxTotalSeconds == nil {
53+
fiveMin := 300
54+
action.MaxTotalSeconds = &fiveMin
55+
}
56+
57+
// Convert the wait to a command.
58+
if cmd, err = convertWaitToCmd(ctx, *action.Wait, action.MaxTotalSeconds); err != nil {
59+
return err
60+
}
61+
62+
// Mute the output because it will be noisy.
63+
t := true
64+
action.Mute = &t
65+
66+
// Set the max retries to 0.
67+
z := 0
68+
action.MaxRetries = &z
69+
70+
// Not used for wait actions.
71+
d := ""
72+
action.Dir = &d
73+
action.Env = []string{}
74+
action.SetVariables = []v1alpha1.Variable{}
75+
}
76+
77+
if action.Description != "" {
78+
cmdEscaped = action.Description
79+
} else {
80+
cmdEscaped = helpers.Truncate(cmd, 60, false)
81+
}
82+
83+
spinner := message.NewProgressSpinner("Running \"%s\"", cmdEscaped)
84+
// Persist the spinner output so it doesn't get overwritten by the command output.
85+
spinner.EnablePreserveWrites()
86+
87+
actionDefaults := actionGetCfg(ctx, defaultCfg, action, variableConfig.GetAllTemplates())
88+
actionDefaults.Dir = filepath.Join(basePath, actionDefaults.Dir)
89+
90+
fmt.Println("base path", basePath)
91+
fmt.Println("action path", actionDefaults.Dir)
92+
93+
if cmd, err = actionCmdMutation(ctx, cmd, actionDefaults.Shell); err != nil {
94+
spinner.Errorf(err, "Error mutating command: %s", cmdEscaped)
95+
}
96+
97+
duration := time.Duration(actionDefaults.MaxTotalSeconds) * time.Second
98+
timeout := time.After(duration)
99+
100+
// Keep trying until the max retries is reached.
101+
// TODO: Refactor using go-retry
102+
retryCmd:
103+
for remaining := actionDefaults.MaxRetries + 1; remaining > 0; remaining-- {
104+
// Perform the action run.
105+
tryCmd := func(ctx context.Context) error {
106+
// Try running the command and continue the retry loop if it fails.
107+
if out, err = actionRun(ctx, actionDefaults, cmd, actionDefaults.Shell, spinner); err != nil {
108+
return err
109+
}
110+
111+
out = strings.TrimSpace(out)
112+
113+
// If an output variable is defined, set it.
114+
for _, v := range action.SetVariables {
115+
variableConfig.SetVariable(v.Name, out, v.Sensitive, v.AutoIndent, v.Type)
116+
if err := variableConfig.CheckVariablePattern(v.Name, v.Pattern); err != nil {
117+
return err
118+
}
119+
}
120+
121+
// If the action has a wait, change the spinner message to reflect that on success.
122+
if action.Wait != nil {
123+
spinner.Successf("Wait for \"%s\" succeeded", cmdEscaped)
124+
} else {
125+
spinner.Successf("Completed \"%s\"", cmdEscaped)
126+
}
127+
128+
// If the command ran successfully, continue to the next action.
129+
return nil
130+
}
131+
132+
// If no timeout is set, run the command and return or continue retrying.
133+
if actionDefaults.MaxTotalSeconds < 1 {
134+
spinner.Updatef("Waiting for \"%s\" (no timeout)", cmdEscaped)
135+
//TODO (schristoff): Make it so tryCmd can take a normal ctx
136+
if err := tryCmd(context.Background()); err != nil {
137+
continue retryCmd
138+
}
139+
140+
return nil
141+
}
142+
143+
// Run the command on repeat until success or timeout.
144+
spinner.Updatef("Waiting for \"%s\" (timeout: %ds)", cmdEscaped, actionDefaults.MaxTotalSeconds)
145+
select {
146+
// On timeout break the loop to abort.
147+
case <-timeout:
148+
break retryCmd
149+
150+
// Otherwise, try running the command.
151+
default:
152+
ctx, cancel := context.WithTimeout(ctx, duration)
153+
defer cancel()
154+
if err := tryCmd(ctx); err != nil {
155+
continue retryCmd
156+
}
157+
158+
return nil
159+
}
160+
}
161+
162+
select {
163+
case <-timeout:
164+
// If we reached this point, the timeout was reached or command failed with no retries.
165+
if actionDefaults.MaxTotalSeconds < 1 {
166+
return fmt.Errorf("command %q failed after %d retries", cmdEscaped, actionDefaults.MaxRetries)
167+
} else {
168+
return fmt.Errorf("command %q timed out after %d seconds", cmdEscaped, actionDefaults.MaxTotalSeconds)
169+
}
170+
default:
171+
// If we reached this point, the retry limit was reached.
172+
return fmt.Errorf("command %q failed after %d retries", cmdEscaped, actionDefaults.MaxRetries)
173+
}
174+
}
175+
176+
// convertWaitToCmd will return the wait command if it exists, otherwise it will return the original command.
177+
func convertWaitToCmd(_ context.Context, wait v1alpha1.ZarfComponentActionWait, timeout *int) (string, error) {
178+
// Build the timeout string.
179+
timeoutString := fmt.Sprintf("--timeout %ds", *timeout)
180+
181+
// If the action has a wait, build a cmd from that instead.
182+
cluster := wait.Cluster
183+
if cluster != nil {
184+
ns := cluster.Namespace
185+
if ns != "" {
186+
ns = fmt.Sprintf("-n %s", ns)
187+
}
188+
189+
// Build a call to the zarf tools wait-for command.
190+
return fmt.Sprintf("./zarf tools wait-for %s %s %s %s %s",
191+
cluster.Kind, cluster.Name, cluster.Condition, ns, timeoutString), nil
192+
}
193+
194+
network := wait.Network
195+
if network != nil {
196+
// Make sure the protocol is lower case.
197+
network.Protocol = strings.ToLower(network.Protocol)
198+
199+
// If the protocol is http and no code is set, default to 200.
200+
if strings.HasPrefix(network.Protocol, "http") && network.Code == 0 {
201+
network.Code = 200
202+
}
203+
204+
// Build a call to the zarf tools wait-for command.
205+
return fmt.Sprintf("./zarf tools wait-for %s %s %d %s",
206+
network.Protocol, network.Address, network.Code, timeoutString), nil
207+
}
208+
209+
return "", fmt.Errorf("wait action is missing a cluster or network")
210+
}
211+
212+
// Perform some basic string mutations to make commands more useful.
213+
func actionCmdMutation(_ context.Context, cmd string, shellPref v1alpha1.Shell) (string, error) {
214+
zarfCommand, err := utils.GetFinalExecutableCommand()
215+
if err != nil {
216+
return cmd, err
217+
}
218+
219+
// Try to patch the zarf binary path in case the name isn't exactly "./zarf".
220+
cmd = strings.ReplaceAll(cmd, "./zarf ", zarfCommand+" ")
221+
222+
// Make commands 'more' compatible with Windows OS PowerShell
223+
if runtime.GOOS == "windows" && (exec.IsPowershell(shellPref.Windows) || shellPref.Windows == "") {
224+
// Replace "touch" with "New-Item" on Windows as it's a common command, but not POSIX so not aliased by M$.
225+
// See https://mathieubuisson.github.io/powershell-linux-bash/ &
226+
// http://web.cs.ucla.edu/~miryung/teaching/EE461L-Spring2012/labs/posix.html for more details.
227+
cmd = regexp.MustCompile(`^touch `).ReplaceAllString(cmd, `New-Item `)
228+
229+
// Convert any ${ZARF_VAR_*} or $ZARF_VAR_* to ${env:ZARF_VAR_*} or $env:ZARF_VAR_* respectively (also TF_VAR_*).
230+
// https://regex101.com/r/xk1rkw/1
231+
envVarRegex := regexp.MustCompile(`(?P<envIndicator>\${?(?P<varName>(ZARF|TF)_VAR_([a-zA-Z0-9_-])+)}?)`)
232+
get, err := helpers.MatchRegex(envVarRegex, cmd)
233+
if err == nil {
234+
newCmd := strings.ReplaceAll(cmd, get("envIndicator"), fmt.Sprintf("$Env:%s", get("varName")))
235+
message.Debugf("Converted command \"%s\" to \"%s\" t", cmd, newCmd)
236+
cmd = newCmd
237+
}
238+
}
239+
240+
return cmd, nil
241+
}
242+
243+
// Merge the ActionSet defaults with the action config.
244+
func actionGetCfg(_ context.Context, cfg v1alpha1.ZarfComponentActionDefaults, a v1alpha1.ZarfComponentAction, vars map[string]*variables.TextTemplate) v1alpha1.ZarfComponentActionDefaults {
245+
if a.Mute != nil {
246+
cfg.Mute = *a.Mute
247+
}
248+
249+
// Default is no timeout, but add a timeout if one is provided.
250+
if a.MaxTotalSeconds != nil {
251+
cfg.MaxTotalSeconds = *a.MaxTotalSeconds
252+
}
253+
254+
if a.MaxRetries != nil {
255+
cfg.MaxRetries = *a.MaxRetries
256+
}
257+
258+
if a.Dir != nil {
259+
cfg.Dir = *a.Dir
260+
}
261+
262+
if len(a.Env) > 0 {
263+
cfg.Env = append(cfg.Env, a.Env...)
264+
}
265+
266+
if a.Shell != nil {
267+
cfg.Shell = *a.Shell
268+
}
269+
270+
// Add variables to the environment.
271+
for k, v := range vars {
272+
// Remove # from env variable name.
273+
k = strings.ReplaceAll(k, "#", "")
274+
// Make terraform variables available to the action as TF_VAR_lowercase_name.
275+
k1 := strings.ReplaceAll(strings.ToLower(k), "zarf_var", "TF_VAR")
276+
cfg.Env = append(cfg.Env, fmt.Sprintf("%s=%s", k, v.Value))
277+
cfg.Env = append(cfg.Env, fmt.Sprintf("%s=%s", k1, v.Value))
278+
}
279+
280+
return cfg
281+
}
282+
283+
func actionRun(ctx context.Context, cfg v1alpha1.ZarfComponentActionDefaults, cmd string, shellPref v1alpha1.Shell, spinner *message.Spinner) (string, error) {
284+
shell, shellArgs := exec.GetOSShell(shellPref)
285+
286+
message.Debugf("Running command in %s: %s", shell, cmd)
287+
288+
execCfg := exec.Config{
289+
Env: cfg.Env,
290+
Dir: cfg.Dir,
291+
}
292+
293+
fmt.Println("exec cfg", execCfg.Dir)
294+
295+
if !cfg.Mute {
296+
execCfg.Stdout = spinner
297+
execCfg.Stderr = spinner
298+
}
299+
300+
out, errOut, err := exec.CmdWithContext(ctx, execCfg, shell, append(shellArgs, cmd)...)
301+
// Dump final complete output (respect mute to prevent sensitive values from hitting the logs).
302+
if !cfg.Mute {
303+
message.Debug(cmd, out, errOut)
304+
}
305+
306+
return out, err
307+
}

0 commit comments

Comments
 (0)