Skip to content

Commit

Permalink
refactor: pull
Browse files Browse the repository at this point in the history
Signed-off-by: Philip Laine <philip.laine@gmail.com>
  • Loading branch information
phillebaba committed Sep 11, 2024
1 parent cf4e989 commit 41c0807
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 7 deletions.
32 changes: 25 additions & 7 deletions src/cmd/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@ import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/zarf-dev/zarf/src/cmd/common"
"github.com/zarf-dev/zarf/src/config/lang"
"github.com/zarf-dev/zarf/src/internal/packager2"
"github.com/zarf-dev/zarf/src/pkg/lint"
"github.com/zarf-dev/zarf/src/pkg/message"
"github.com/zarf-dev/zarf/src/pkg/packager/filters"
"github.com/zarf-dev/zarf/src/pkg/packager/sources"
"github.com/zarf-dev/zarf/src/types"
"helm.sh/helm/v3/pkg/time"

"oras.land/oras-go/v2/registry"

Expand Down Expand Up @@ -272,21 +276,34 @@ var packagePublishCmd = &cobra.Command{
},
}

var pullOptions = struct {
OutputDirectory string
Name string
}{}

var packagePullCmd = &cobra.Command{
Use: "pull PACKAGE_SOURCE",
Short: lang.CmdPackagePullShort,
Example: lang.CmdPackagePullExample,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
pkgConfig.PkgOpts.PackageSource = args[0]
pkgClient, err := packager.New(&pkgConfig)
outputDir := pullOptions.OutputDirectory
if outputDir == "" {
wd, err := os.Getwd()
if err != nil {
return err
}
outputDir = wd
}
name := pullOptions.Name
if name == "" {
name = fmt.Sprintf("zarf-package-%d", time.Now().Unix())
}
tarPath := filepath.Join(outputDir, name)
err := packager2.Fetch(cmd.Context(), args[0], tarPath, pkgConfig.PkgOpts.Shasum, filters.Empty())
if err != nil {
return err
}
defer pkgClient.ClearTempPaths()
if err := pkgClient.Pull(cmd.Context()); err != nil {
return fmt.Errorf("failed to pull package: %w", err)
}
return nil
},
}
Expand Down Expand Up @@ -483,5 +500,6 @@ func bindPublishFlags(v *viper.Viper) {

func bindPullFlags(v *viper.Viper) {
pullFlags := packagePullCmd.Flags()
pullFlags.StringVarP(&pkgConfig.PullOpts.OutputDirectory, "output-directory", "o", v.GetString(common.VPkgPullOutputDir), lang.CmdPackagePullFlagOutputDirectory)
pullFlags.StringVarP(&pullOptions.OutputDirectory, "output-directory", "o", v.GetString(common.VPkgPullOutputDir), "Specify the output directory for the pulled Zarf package")
pullFlags.StringVarP(&pullOptions.Name, "name", "n", "", "The name of the output tar file.")
}
164 changes: 164 additions & 0 deletions src/internal/packager2/packager2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors

// Package packager2 is the new implementation for packager.
package packager2

import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"

"github.com/defenseunicorns/pkg/helpers/v2"
"github.com/defenseunicorns/pkg/oci"
goyaml "github.com/goccy/go-yaml"
"github.com/mholt/archiver/v3"

"github.com/zarf-dev/zarf/src/api/v1alpha1"
"github.com/zarf-dev/zarf/src/config"
"github.com/zarf-dev/zarf/src/pkg/layout"
"github.com/zarf-dev/zarf/src/pkg/packager/filters"
"github.com/zarf-dev/zarf/src/pkg/utils"
"github.com/zarf-dev/zarf/src/pkg/zoci"
)

// Fetch fetches the Zarf package from the given sources.
func Fetch(ctx context.Context, src, tarPath, shasum string, filter filters.ComponentFilterStrategy) error {
u, err := url.Parse(src)
if err != nil {
return err
}
if u.Scheme == "" {
return errors.New("scheme cannot be empty")
}
if u.Host == "" {
return errors.New("scheme cannot be empty")
}
switch u.Scheme {
case "oci":
err := fetchOCI(ctx, src, tarPath, shasum, filter)
if err != nil {
return err
}
case "http", "https":
err := fetchHTTP(ctx, src, tarPath, shasum)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown scheme %s", u.Scheme)
}

// Minimal effort to verify that this is a Zarf package.
// TODO (phillebaba): Expand in the future to include more package verification.
err = archiver.Walk(tarPath, func(f archiver.File) error {
if f.Name() == layout.ZarfYAML {
b, err := io.ReadAll(f)
if err != nil {
return err
}
var pkg v1alpha1.ZarfPackage
err = goyaml.Unmarshal(b, &pkg)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
return nil
}

func fetchOCI(ctx context.Context, src, tarPath, shasum string, filter filters.ComponentFilterStrategy) error {
tmpDir, err := utils.MakeTempDir(config.CommonOptions.TempDirectory)
if err != nil {
return err
}
defer os.Remove(tmpDir)
if shasum != "" {
src = fmt.Sprintf("%s@sha256:%s", src, shasum)
}
arch := config.GetArch()
remote, err := zoci.NewRemote(src, oci.PlatformForArch(arch))
if err != nil {
return err
}
pkg, err := remote.FetchZarfYAML(ctx)
if err != nil {
return err
}
pkg.Components, err = filter.Apply(pkg)
if err != nil {
return err
}
layersToPull, err := remote.LayersFromRequestedComponents(ctx, pkg.Components)
if err != nil {
return err
}
_, err = remote.PullPackage(ctx, tmpDir, config.CommonOptions.OCIConcurrency, layersToPull...)
if err != nil {
return err
}
allTheLayers, err := filepath.Glob(filepath.Join(tmpDir, "*"))
if err != nil {
return err
}
err = os.Remove(tarPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
err = archiver.Archive(allTheLayers, tarPath)
if err != nil {
return err
}
return nil
}

func fetchHTTP(ctx context.Context, src, tarPath, shasum string) error {
if !config.CommonOptions.Insecure && shasum == "" {
return errors.New("remote package provided without shasum while insecure is not enabled")
}
f, err := os.Create(tarPath)
if err != nil {
return err
}
defer f.Close()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, src, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
_, err := io.Copy(io.Discard, resp.Body)
if err != nil {
return err
}
return fmt.Errorf("unexpected http response status code %s for source %s", resp.Status, src)
}
_, err = io.Copy(f, resp.Body)
if err != nil {
return err
}
// Check checksum if src included one.
if shasum != "" {
received, err := helpers.GetSHA256OfFile(tarPath)
if err != nil {
return err
}
if received != shasum {
return fmt.Errorf("shasum mismatch for file %s, expected %s bu got %s ", tarPath, shasum, received)
}
}
return nil
}

0 comments on commit 41c0807

Please sign in to comment.