Skip to content

Commit

Permalink
Build packages using local container images where available (#1173)
Browse files Browse the repository at this point in the history
This PR introduces functionality to be able use local images when
building a Zarf package.

This can be done in two ways:

1. you can modify the `zarf.yaml` images key to specify a path to an
image tarball (that has been generated using the `docker save {IMAGE} -o
my-image.tar` command.
i.e.
```
images:
  - "/home/user/jperry/mypackage/my-image.tar"
```

2. You can pull the image to your local daemon and Zarf will attempt to
use those images prior to pulling from a remote registry. (keeping your
`zarf.yaml` specifications the same as before!)

Co-authored-by: Megamind <[email protected]>
  • Loading branch information
YrrepNoj and jeff-mccoy authored Jan 12, 2023
1 parent 9d44edf commit 00736dd
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 13 deletions.
8 changes: 1 addition & 7 deletions .github/actions/packages/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,9 @@ description: "Create agent image, init package and example packages"
runs:
using: composite
steps:
- uses: docker/login-action@v2
with:
registry: ghcr.io
username: dummy
password: ${{ github.token }}

- run: |
make build-cli-linux-amd
cp build/zarf build/zarf-linux-amd64
docker buildx build --push --platform linux/amd64 --tag ghcr.io/defenseunicorns/zarf/dev-agent:$GITHUB_SHA .
docker buildx build --platform linux/amd64 --tag ghcr.io/defenseunicorns/zarf/dev-agent:$GITHUB_SHA .
make init-package build-examples ARCH=amd64 AGENT_IMAGE="dev-agent:$GITHUB_SHA"
shell: bash
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ zarf package create [DIRECTORY] [flags]
-h, --help help for create
--insecure Allow insecure registry connections when pulling OCI images
-m, --max-package-size int Specify the maximum size of the package in megabytes, packages larger than this will be split into multiple parts. Use 0 to disable splitting.
--no-local-images Do not use local container images when creating this package
-o, --output-directory string Specify the output directory for the created Zarf package
-s, --sbom View SBOM contents after creating the package
--sbom-out string Specify an output directory for the SBOMs from the created Zarf package
Expand Down
2 changes: 2 additions & 0 deletions src/cmd/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ func bindCreateFlags() {
v.SetDefault(V_PKG_CREATE_SKIP_SBOM, false)
v.SetDefault(V_PKG_CREATE_INSECURE, false)
v.SetDefault(V_PKG_CREATE_MAX_PACKAGE_SIZE, 0)
v.SetDefault(V_PKG_CREATE_NO_LOCAL_IMAGES, false)

createFlags.StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(V_PKG_CREATE_SET), "Specify package variables to set on the command line (KEY=value)")
createFlags.StringVarP(&pkgConfig.CreateOpts.OutputDirectory, "output-directory", "o", v.GetString(V_PKG_CREATE_OUTPUT_DIR), "Specify the output directory for the created Zarf package")
Expand All @@ -250,6 +251,7 @@ func bindCreateFlags() {
createFlags.BoolVar(&pkgConfig.CreateOpts.SkipSBOM, "skip-sbom", v.GetBool(V_PKG_CREATE_SKIP_SBOM), "Skip generating SBOM for this package")
createFlags.BoolVar(&pkgConfig.CreateOpts.Insecure, "insecure", v.GetBool(V_PKG_CREATE_INSECURE), "Allow insecure registry connections when pulling OCI images")
createFlags.IntVarP(&pkgConfig.CreateOpts.MaxPackageSizeMB, "max-package-size", "m", v.GetInt(V_PKG_CREATE_MAX_PACKAGE_SIZE), "Specify the maximum size of the package in megabytes, packages larger than this will be split into multiple parts. Use 0 to disable splitting.")
createFlags.BoolVar(&pkgConfig.CreateOpts.NoLocalImages, "no-local-images", v.GetBool(V_PKG_CREATE_NO_LOCAL_IMAGES), "Do not use local container images when creating this package")
}

func bindDeployFlags() {
Expand Down
1 change: 1 addition & 0 deletions src/cmd/viper.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const (
V_PKG_CREATE_SKIP_SBOM = "package.create.skip_sbom"
V_PKG_CREATE_INSECURE = "package.create.insecure"
V_PKG_CREATE_MAX_PACKAGE_SIZE = "package.create.max_package_size"
V_PKG_CREATE_NO_LOCAL_IMAGES = "package.create.no_local_images"

// Package deploy config keys
V_PKG_DEPLOY_SET = "package.deploy.set"
Expand Down
2 changes: 2 additions & 0 deletions src/internal/packager/images/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ type ImgConfig struct {
NoChecksum bool

Insecure bool

NoLocalImages bool
}
43 changes: 40 additions & 3 deletions src/internal/packager/images/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package images

import (
"context"
"crypto/sha256"
"encoding/json"
"errors"
Expand All @@ -23,6 +24,7 @@ import (
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/cache"
"github.com/google/go-containerregistry/pkg/v1/daemon"
"github.com/google/go-containerregistry/pkg/v1/tarball"
)

Expand Down Expand Up @@ -52,12 +54,11 @@ func (i *ImgConfig) PullAll() (map[name.Tag]v1.Image, error) {

for idx, src := range i.ImgList {
spinner.Updatef("Fetching image metadata (%d of %d): %s", idx+1, imgCount, src)
img, err := crane.Pull(src, config.GetCraneOptions(i.Insecure)...)

img, err := i.pullImage(src)
if err != nil {
return nil, fmt.Errorf("failed to pull image %s: %w", src, err)
}
imageCachePath := filepath.Join(config.GetAbsCachePath(), config.ZarfImageCacheDir)
img = cache.Image(img, cache.NewFilesystemCache(imageCachePath))
imageMap[src] = img
}

Expand Down Expand Up @@ -118,6 +119,42 @@ func (i *ImgConfig) PullAll() (map[name.Tag]v1.Image, error) {
return tagToImage, nil
}

// pullImage returns a v1.Image either by loading a local tarball, the pulling from the local daemon, or the wider internet
func (i *ImgConfig) pullImage(src string) (v1.Image, error) {
// Load image tarballs from the local filesystem
if strings.HasSuffix(src, ".tar") || strings.HasSuffix(src, ".tar.gz") || strings.HasSuffix(src, ".tgz") {
message.Debugf("loading image tarball: %s", src)
return crane.Load(src, config.GetCraneOptions(true)...)
}

// Unless disabled, attempt to pull the image from the local daemon
if !i.NoLocalImages {
reference, err := name.ParseReference(src)
if err != nil {
// log this error but don't return the error since we can still try pulling from the wider internet
message.Debugf("unable to parse the image reference, this might have impacts on pulling from the local daemon: %s", err.Error())
}

daemonOpts := daemon.WithContext(context.Background())
if img, err := daemon.Image(reference, daemonOpts); err == nil {
message.Debugf("loading image from docker daemon: %s", src)
return img, err
}
}

// We were unable to pull from the local daemon, so attempt to pull from the wider internet
img, err := crane.Pull(src, config.GetCraneOptions(i.Insecure)...)
if err != nil {
return nil, fmt.Errorf("failed to pull image %s: %w", src, err)
}

message.Debugf("loading image with cache: %s", src)
imageCachePath := filepath.Join(config.GetAbsCachePath(), config.ZarfImageCacheDir)
img = cache.Image(img, cache.NewFilesystemCache(imageCachePath))

return img, nil
}

// FormatCraneOCILayout ensures that all images are in the OCI format.
func FormatCraneOCILayout(ociPath string) error {
type IndexJSON struct {
Expand Down
7 changes: 4 additions & 3 deletions src/pkg/packager/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,10 @@ func (p *Packager) pullImages(imgList []string, path string) (map[name.Tag]v1.Im

return pulledImages, utils.Retry(func() error {
imgConfig := images.ImgConfig{
TarballPath: path,
ImgList: imgList,
Insecure: p.cfg.CreateOpts.Insecure,
TarballPath: path,
ImgList: imgList,
Insecure: p.cfg.CreateOpts.Insecure,
NoLocalImages: p.cfg.CreateOpts.NoLocalImages,
}

pulledImages, err = imgConfig.PullAll()
Expand Down
1 change: 1 addition & 0 deletions src/types/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type ZarfCreateOptions struct {
SBOMOutputDir string `json:"sbomOutput" jsonschema:"description=Location to output an SBOM into after package creation"`
SetVariables map[string]string `json:"setVariables" jsonschema:"description=Key-Value map of variable names and their corresponding values that will be used to template against the Zarf package being used"`
MaxPackageSizeMB int `json:"maxPackageSizeMB" jsonschema:"description=Size of chunks to use when splitting a zarf package into multiple files in megabytes"`
NoLocalImages bool `json:"noLocalImages" jsonschema:"description=Disable the use of local container images during package creation"`
}

// ZarfPartialPackageData contains info about a partial package.
Expand Down
5 changes: 5 additions & 0 deletions src/ui/lib/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,10 @@ export interface ZarfCreateOptions {
* Size of chunks to use when splitting a zarf package into multiple files in megabytes
*/
maxPackageSizeMB: number;
/**
* Disable the use of local container images during package creation
*/
noLocalImages: boolean;
/**
* Location where the finalized Zarf package will be placed
*/
Expand Down Expand Up @@ -1022,6 +1026,7 @@ const typeMap: any = {
"ZarfCreateOptions": o([
{ json: "insecure", js: "insecure", typ: true },
{ json: "maxPackageSizeMB", js: "maxPackageSizeMB", typ: 0 },
{ json: "noLocalImages", js: "noLocalImages", typ: true },
{ json: "outputDirectory", js: "outputDirectory", typ: "" },
{ json: "sbom", js: "sbom", typ: true },
{ json: "sbomOutput", js: "sbomOutput", typ: "" },
Expand Down

0 comments on commit 00736dd

Please sign in to comment.