Skip to content

Commit

Permalink
migrate zarf package create to logger
Browse files Browse the repository at this point in the history
Signed-off-by: Kit Patella <[email protected]>
  • Loading branch information
mkcp committed Oct 24, 2024
1 parent c81d69a commit 863548a
Show file tree
Hide file tree
Showing 11 changed files with 377 additions and 103 deletions.
2 changes: 1 addition & 1 deletion src/cmd/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ var packageCreateCmd = &cobra.Command{
// TODO(mkcp): Finish migrating packager.Create
err = pkgClient.Create()

// TODO(mkcp): Migrate linterrs to logger
// NOTE(mkcp): LintErrors are rendered with a table
var lintErr *lint.LintError
if errors.As(err, &lintErr) {
common.PrintFindings(lintErr)
Expand Down
207 changes: 199 additions & 8 deletions src/internal/packager/helm/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"context"
"errors"
"fmt"
"github.com/zarf-dev/zarf/src/pkg/logger"
"log/slog"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -38,7 +40,7 @@ func (h *Helm) PackageChart(ctx context.Context, cosignKeyPath string) error {
// check if the chart is a git url with a ref (if an error is returned url will be empty)
isGitURL := strings.HasSuffix(url, ".git")
if err != nil {
message.Debugf("unable to parse the url, continuing with %s", h.chart.URL)
logger.From(ctx).Debug("unable to parse the url, continuing", "url", h.chart.URL)
}

if isGitURL {
Expand Down Expand Up @@ -68,6 +70,63 @@ func (h *Helm) PackageChart(ctx context.Context, cosignKeyPath string) error {

// PackageChartFromLocalFiles creates a chart archive from a path to a chart on the host os.
func (h *Helm) PackageChartFromLocalFiles(ctx context.Context, cosignKeyPath string) error {
l := logger.From(ctx)
l.Info("processing local helm chart",
"name", h.chart.Name,
"version", h.chart.Version,
"path", h.chart.LocalPath,
)

// Load and validate the chart
cl, _, err := h.loadAndValidateChart(h.chart.LocalPath)
if err != nil {
return err
}

// Handle the chart directory or tarball
var saved string
temp := filepath.Join(h.chartPath, "temp")
if _, ok := cl.(loader.DirLoader); ok {
err = h.buildChartDependencies(ctx)
if err != nil {
return fmt.Errorf("unable to build dependencies for the chart: %w", err)
}

client := action.NewPackage()

client.Destination = temp
saved, err = client.Run(h.chart.LocalPath, nil)
} else {
saved = filepath.Join(temp, filepath.Base(h.chart.LocalPath))
err = helpers.CreatePathAndCopy(h.chart.LocalPath, saved)
}
defer func(l *slog.Logger) {
err := os.RemoveAll(temp)
if err != nil {
l.Error(err.Error())
}
}(l)

if err != nil {
return fmt.Errorf("unable to save the archive and create the package %s: %w", saved, err)
}

// Finalize the chart
err = h.finalizeChartPackage(ctx, saved, cosignKeyPath)
if err != nil {
return err
}

l.Debug("done processing local helm chart",
"name", h.chart.Name,
"version", h.chart.Version,
"path", h.chart.LocalPath,
)
return nil
}

// PackageChartFromLocalFilesSpinner creates a chart archive from a path to a chart on the host os. It displays a spinner.
func (h *Helm) PackageChartFromLocalFilesSpinner(ctx context.Context, cosignKeyPath string) error {
spinner := message.NewProgressSpinner("Processing helm chart %s:%s from %s", h.chart.Name, h.chart.Version, h.chart.LocalPath)
defer spinner.Stop()

Expand All @@ -81,7 +140,7 @@ func (h *Helm) PackageChartFromLocalFiles(ctx context.Context, cosignKeyPath str
var saved string
temp := filepath.Join(h.chartPath, "temp")
if _, ok := cl.(loader.DirLoader); ok {
err = h.buildChartDependencies()
err = h.buildChartDependencies(ctx)
if err != nil {
return fmt.Errorf("unable to build dependencies for the chart: %w", err)
}
Expand Down Expand Up @@ -113,6 +172,28 @@ func (h *Helm) PackageChartFromLocalFiles(ctx context.Context, cosignKeyPath str

// PackageChartFromGit is a special implementation of chart archiving that supports the https://p1.dso.mil/#/products/big-bang/ model.
func (h *Helm) PackageChartFromGit(ctx context.Context, cosignKeyPath string) error {
l := logger.From(ctx)
l.Info("processing helm chart", "name", h.chart.Name)

// Retrieve the repo containing the chart
gitPath, err := DownloadChartFromGitToTemp(ctx, h.chart.URL)
if err != nil {
return err
}
defer func(l *slog.Logger) {
if err := os.RemoveAll(gitPath); err != nil {
l.Error(err.Error())
}
}(l)

// Set the directory for the chart and package it
h.chart.LocalPath = filepath.Join(gitPath, h.chart.GitPath)
return h.PackageChartFromLocalFiles(ctx, cosignKeyPath)
}

// PackageChartFromGitSpinner is a special implementation of chart archiving that supports the https://p1.dso.mil/#/products/big-bang/ model.
// It includes a CLI spinner.
func (h *Helm) PackageChartFromGitSpinner(ctx context.Context, cosignKeyPath string) error {
spinner := message.NewProgressSpinner("Processing helm chart %s", h.chart.Name)
defer spinner.Stop()

Expand All @@ -130,6 +211,116 @@ func (h *Helm) PackageChartFromGit(ctx context.Context, cosignKeyPath string) er

// DownloadPublishedChart loads a specific chart version from a remote repo.
func (h *Helm) DownloadPublishedChart(ctx context.Context, cosignKeyPath string) error {
l := logger.From(ctx)
l.Info("processing helm chart",
"name", h.chart.Name,
"version", h.chart.Version,
"repo", h.chart.URL,
)

// Set up the helm pull config
pull := action.NewPull()
pull.Settings = cli.New()

var (
regClient *registry.Client
chartURL string
err error
)
repoFile, err := repo.LoadFile(pull.Settings.RepositoryConfig)

// Not returning the error here since the repo file is only needed if we are pulling from a repo that requires authentication
if err != nil {
l.Debug("unable to load the repo file",
"path", pull.Settings.RepositoryConfig,
"error", err.Error(),
)
}

var username string
var password string

// Handle OCI registries
if registry.IsOCI(h.chart.URL) {
regClient, err = registry.NewClient(registry.ClientOptEnableCache(true))
if err != nil {
return fmt.Errorf("unable to create the new registry client: %w", err)
}
chartURL = h.chart.URL
// Explicitly set the pull version for OCI
pull.Version = h.chart.Version
} else {
chartName := h.chart.Name
if h.chart.RepoName != "" {
chartName = h.chart.RepoName
}

if repoFile != nil {
// TODO: @AustinAbro321 Currently this selects the last repo with the same url
// We should introduce a new field in zarf to allow users to specify the local repo they want
for _, r := range repoFile.Repositories {
if r.URL == h.chart.URL {
username = r.Username
password = r.Password
}
}
}

chartURL, err = repo.FindChartInAuthRepoURL(h.chart.URL, username, password, chartName, h.chart.Version, pull.CertFile, pull.KeyFile, pull.CaFile, getter.All(pull.Settings))
if err != nil {
return fmt.Errorf("unable to pull the helm chart: %w", err)
}
}

// Set up the chart chartDownloader
chartDownloader := downloader.ChartDownloader{
Out: logger.DestinationDefault,
RegistryClient: regClient,
// TODO: Further research this with regular/OCI charts
Verify: downloader.VerifyNever,
Getters: getter.All(pull.Settings),
Options: []getter.Option{
getter.WithInsecureSkipVerifyTLS(config.CommonOptions.InsecureSkipTLSVerify),
getter.WithBasicAuth(username, password),
},
}

// Download the file into a temp directory since we don't control what name helm creates here
temp := filepath.Join(h.chartPath, "temp")
if err = helpers.CreateDirectory(temp, helpers.ReadWriteExecuteUser); err != nil {
return fmt.Errorf("unable to create helm chart temp directory: %w", err)
}
defer func(l *slog.Logger) {
err := os.RemoveAll(temp)
if err != nil {
l.Error(err.Error())
}
}(l)

saved, _, err := chartDownloader.DownloadTo(chartURL, pull.Version, temp)
if err != nil {
return fmt.Errorf("unable to download the helm chart: %w", err)
}

// Validate the chart
_, _, err = h.loadAndValidateChart(saved)
if err != nil {
return err
}

// Finalize the chart
err = h.finalizeChartPackage(ctx, saved, cosignKeyPath)
if err != nil {
return err
}

l.Debug("done downloading helm chart")

return nil
}

// DownloadPublishedChartSpinner loads a specific chart version from a remote repo and renders it with a spinner.
func (h *Helm) DownloadPublishedChartSpinner(ctx context.Context, cosignKeyPath string) error {
spinner := message.NewProgressSpinner("Processing helm chart %s:%s from repo %s", h.chart.Name, h.chart.Version, h.chart.URL)
defer spinner.Stop()

Expand Down Expand Up @@ -273,7 +464,8 @@ func (h *Helm) packageValues(ctx context.Context, cosignKeyPath string) error {
}

// buildChartDependencies builds the helm chart dependencies
func (h *Helm) buildChartDependencies() error {
func (h *Helm) buildChartDependencies(ctx context.Context) error {
l := logger.From(ctx)
// Download and build the specified dependencies
regClient, err := registry.NewClient(registry.ClientOptEnableCache(true))
if err != nil {
Expand All @@ -287,7 +479,7 @@ func (h *Helm) buildChartDependencies() error {
}

man := &downloader.Manager{
Out: &message.DebugWriter{},
Out: logger.DestinationDefault,
ChartPath: h.chart.LocalPath,
Getters: getter.All(h.settings),
RegistryClient: regClient,
Expand All @@ -304,15 +496,14 @@ func (h *Helm) buildChartDependencies() error {
var notFoundErr *downloader.ErrRepoNotFound
if errors.As(err, &notFoundErr) {
// If we encounter a repo not found error point the user to `zarf tools helm repo add`
message.Warnf("%s. Please add the missing repo(s) via the following:", notFoundErr.Error())
l.Warn(fmt.Sprintf("%s. please add the missing repo(s) via the following: %s", notFoundErr.Error()))

Check failure on line 499 in src/internal/packager/helm/repo.go

View workflow job for this annotation

GitHub Actions / validate-unit

fmt.Sprintf format %s reads arg #2, but call has 1 arg

Check failure on line 499 in src/internal/packager/helm/repo.go

View workflow job for this annotation

GitHub Actions / test-unit

fmt.Sprintf format %s reads arg #2, but call has 1 arg
for _, repository := range notFoundErr.Repos {
message.ZarfCommand(fmt.Sprintf("tools helm repo add <your-repo-name> %s", repository))
l.Warn(fmt.Sprintf("'$zarf tools helm repo add <your-repo-name> %s'", repository))
}
return err
}
if err != nil {
message.ZarfCommand("tools helm dependency build --verify")
message.Warnf("Unable to perform a rebuild of Helm dependencies: %s", err.Error())
l.Warn("unable to perform a rebuild of Helm dependencies, try running '$zarf tools helm dependency build --verify'", "error", err.Error())
return err
}
return nil
Expand Down
42 changes: 19 additions & 23 deletions src/internal/packager/images/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/zarf-dev/zarf/src/pkg/logger"
"io/fs"
"maps"
"os"
Expand Down Expand Up @@ -60,16 +61,9 @@ func checkForIndex(refInfo transform.Image, desc *remote.Descriptor) error {
return nil
}

// Pull pulls all of the images from the given config.
// Pull pulls all images from the given config.
func Pull(ctx context.Context, cfg PullConfig) (map[transform.Image]v1.Image, error) {
var longer string
imageCount := len(cfg.ImageList)
// Give some additional user feedback on larger image sets
if imageCount > 15 {
longer = "This step may take a couple of minutes to complete."
} else if imageCount > 5 {
longer = "This step may take several seconds to complete."
}
l := logger.From(ctx)

if err := helpers.CreateDirectory(cfg.DestinationDirectory, helpers.ReadExecuteAllWriteUser); err != nil {
return nil, fmt.Errorf("failed to create image path %s: %w", cfg.DestinationDirectory, err)
Expand All @@ -80,8 +74,17 @@ func Pull(ctx context.Context, cfg PullConfig) (map[transform.Image]v1.Image, er
return nil, err
}

spinner := message.NewProgressSpinner("Fetching info for %d images. %s", imageCount, longer)
defer spinner.Stop()
// Give some additional user feedback on larger image sets
switch c := len(cfg.ImageList); {
case c > 15:
l.Info("fetching info for images. This step may take a couple of minutes to complete", "count", c)
break
case c > 5:
l.Info("fetching info for images. This step may take several seconds to complete", "count", c)
break
default:
l.Info("fetching info for images", "count", c)
}

logs.Warn.SetOutput(&message.DebugWriter{})
logs.Progress.SetOutput(&message.DebugWriter{})
Expand All @@ -95,13 +98,12 @@ func Pull(ctx context.Context, cfg PullConfig) (map[transform.Image]v1.Image, er

fetched := map[transform.Image]v1.Image{}

var counter, totalBytes atomic.Int64
var totalBytes atomic.Int64

for _, refInfo := range cfg.ImageList {
refInfo := refInfo
eg.Go(func() error {
idx := counter.Add(1)
spinner.Updatef("Fetching image info (%d of %d)", idx, imageCount)
l.Info("fetching image info", "name", refInfo.Name)

ref := refInfo.Reference
for k, v := range cfg.RegistryOverrides {
Expand Down Expand Up @@ -221,11 +223,9 @@ func Pull(ctx context.Context, cfg PullConfig) (map[transform.Image]v1.Image, er
return nil, err
}

spinner.Successf("Fetched info for %d images", imageCount)
l.Debug("done fetching info for images", "count", len(cfg.ImageList))

doneSaving := make(chan error)
updateText := fmt.Sprintf("Pulling %d images", imageCount)
go utils.RenderProgressBarForLocalDirWrite(cfg.DestinationDirectory, totalBytes.Load(), doneSaving, updateText, updateText)
l.Info("pulling images", "count", len(cfg.ImageList))

toPull := maps.Clone(fetched)

Expand All @@ -237,7 +237,7 @@ func Pull(ctx context.Context, cfg PullConfig) (map[transform.Image]v1.Image, er
return err
}, retry.Context(ctx), retry.Attempts(2))
if err != nil {
message.Warnf("Failed to save images in parallel, falling back to sequential save: %s", err.Error())
l.Warn("failed to save images in parallel, falling back to sequential save", "error", err.Error())
err = retry.Do(func() error {
saved, err := SaveSequential(ctx, cranePath, toPull)
for k := range saved {
Expand All @@ -250,10 +250,6 @@ func Pull(ctx context.Context, cfg PullConfig) (map[transform.Image]v1.Image, er
}
}

// Send a signal to the progress bar that we're done and wait for the thread to finish
doneSaving <- nil
<-doneSaving

// Needed because when pulling from the local docker daemon, while using the docker containerd runtime
// Crane incorrectly names the blob of the docker image config to a sha that does not match the contents
// https://github.com/zarf-dev/zarf/issues/2584
Expand Down
Loading

0 comments on commit 863548a

Please sign in to comment.