Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a zarf package publish command #1336

Merged
merged 120 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
6259628
initial copy code
Noxsios Feb 6, 2023
68e0b08
go mod tidy
Noxsios Feb 6, 2023
8d7db1b
go mod tidy
Noxsios Feb 6, 2023
13aac2f
start adding types
Noxsios Feb 6, 2023
817f053
start adding cli flags
Noxsios Feb 6, 2023
0354bba
thanks wayne
Noxsios Feb 7, 2023
6445e22
working locally w/o auth
Noxsios Feb 7, 2023
c48c211
replace fmt.print w/ message
Noxsios Feb 7, 2023
ff06ed3
borrow a page from helm's book
Noxsios Feb 7, 2023
4bc4482
spinner go brr
Noxsios Feb 7, 2023
b0e4b8a
message
Noxsios Feb 7, 2023
7bcddfd
notes
Noxsios Feb 7, 2023
642dcf4
add basic auth
Noxsios Feb 7, 2023
a1bff67
update docs
Noxsios Feb 7, 2023
fd8baa9
docker auth working
Noxsios Feb 8, 2023
8b96c61
some cleaner errors
Noxsios Feb 8, 2023
4de2f65
gitlab working
Noxsios Feb 8, 2023
4e40458
cleanup an errent error
Noxsios Feb 8, 2023
ff95b3b
rename insecure --> http-only , cleanup mediatypes used during push
Noxsios Feb 8, 2023
17fdf49
docs and schema
Noxsios Feb 9, 2023
35a54b9
cleanup some cli usage stuffs
Noxsios Feb 9, 2023
6c8db45
some better cli logging
Noxsios Feb 9, 2023
68616dd
docs
Noxsios Feb 9, 2023
e7fc6d3
consolidate the code a bit
Noxsios Feb 9, 2023
3246b89
cleaner error
Noxsios Feb 9, 2023
ee3943d
log
Noxsios Feb 9, 2023
e1150a7
some verification on the repo ref
Noxsios Feb 9, 2023
18467cf
comments
Noxsios Feb 9, 2023
19929e9
make lintint happy
Noxsios Feb 9, 2023
bc74f18
cleanup
Noxsios Feb 9, 2023
8ab7e52
paramaterize publish and also create skeleton ref
Noxsios Feb 9, 2023
896b2cd
cleanup + comments
Noxsios Feb 9, 2023
badc441
only create skeleton unless otherwise specified, also add the viper i…
Noxsios Feb 9, 2023
f440e17
Update src/cmd/package.go
Noxsios Feb 9, 2023
5c4b79b
some breakage of publish into helper fn
Noxsios Feb 9, 2023
7ffd756
Merge branch 'features/oci-package' into features/oci-publish
Noxsios Feb 9, 2023
a81f538
spdx
Noxsios Feb 10, 2023
3b59ca8
move some functions to utils
Noxsios Feb 10, 2023
20336e4
more utils
Noxsios Feb 10, 2023
bd2a67a
comments
Noxsios Feb 10, 2023
ee2df33
comments
Noxsios Feb 10, 2023
92e61b8
move zarf media types into utils
Noxsios Feb 10, 2023
0a3c155
no need for that extra fmt.errorf
Noxsios Feb 10, 2023
95660e8
some cleaner cli output + error handling
Noxsios Feb 10, 2023
2da5b93
logging
Noxsios Feb 10, 2023
32c67f6
remove the layer limit
Noxsios Feb 10, 2023
a0647c8
better cli output on push
Noxsios Feb 10, 2023
0e900fc
shorten name shown in cli because of char limit
Noxsios Feb 10, 2023
0f36fb8
less ambiguity on some errors
Noxsios Feb 10, 2023
4b2ba49
oci type
Noxsios Feb 11, 2023
0016aec
ecr is finally working
Noxsios Feb 11, 2023
f277d61
restore oras orignal code
Noxsios Feb 11, 2023
b5df4fe
merge oras opts w/ publish opts
Noxsios Feb 13, 2023
cb5c85f
fn to pull zarf oci pkg MANUALLY
Noxsios Feb 13, 2023
e378377
link packopts to publish opts
Noxsios Feb 13, 2023
7e7b7cb
replace prints w/ message and spinner
Noxsios Feb 13, 2023
b8e4db1
Merge branch 'features/oci-package' into features/oci-publish
Noxsios Feb 13, 2023
17419cf
dont include sboms if images are not included
Noxsios Feb 13, 2023
7261b52
use the global config flag for insecure
Noxsios Feb 13, 2023
3118752
stop merges w/ needs-walkthrough label
Noxsios Feb 13, 2023
1580d23
handle components now being folders instead of tar.zst, solution is t…
Noxsios Feb 14, 2023
1d17571
init compose components via oci noun
Noxsios Feb 14, 2023
cabafd9
docs and schema
Noxsios Feb 14, 2023
3043f2b
better handling if skeleton is not specified
Noxsios Feb 14, 2023
de1d5f1
cleanu from using global insecure flag
Noxsios Feb 14, 2023
0e99083
final commit before transition
Noxsios Feb 21, 2023
6caa839
Merge branch 'features/oci-package' into features/oci-publish
Noxsios Feb 21, 2023
74ba90e
remove compose code
Noxsios Feb 21, 2023
20c346d
remove compose code
Noxsios Feb 21, 2023
82dc2db
remove compose code
Noxsios Feb 21, 2023
ab10094
fmt
Noxsios Feb 21, 2023
ab3b876
deploy via oci is hopefully working
Noxsios Feb 21, 2023
a82c8a4
cleanup
Noxsios Feb 21, 2023
ccab240
Merge branch 'features/oci-package' into features/oci-publish
Noxsios Feb 22, 2023
8fc87c9
Merge branch 'features/oci-package' into features/oci-publish
Noxsios Feb 22, 2023
75699c1
use oras reference system, move oras into packager, cleanup some Publ…
Noxsios Feb 22, 2023
bcb443e
fix deploy ref parsing
Noxsios Feb 22, 2023
9a00741
docs and schema
Noxsios Feb 22, 2023
e1dd4e1
docs and schema
Noxsios Feb 22, 2023
bf71741
docs and schema
Noxsios Feb 22, 2023
3b12168
remove checksums.txt for now
Noxsios Feb 22, 2023
d858111
make docker-config a root level flag for easier management, create a …
Noxsios Feb 22, 2023
4a48285
actually get latest tag
Noxsios Feb 23, 2023
1adc1c1
lous working reasonably
Noxsios Feb 23, 2023
28e515a
some cleanup on pulloci fn
Noxsios Feb 23, 2023
9b52fca
error if version is not set
Noxsios Feb 23, 2023
c680d0c
start working on walkthrough docs + walkthrough gif
Noxsios Feb 23, 2023
ef27d14
continue working on docs
Noxsios Feb 23, 2023
42d4d7a
insecure != http, http is only for localhost
Noxsios Feb 23, 2023
03d8248
insecure is now on tls config
Noxsios Feb 23, 2023
43ef9ed
no need for config dir once I discovered `zarf tools registry login`
Noxsios Feb 23, 2023
7659571
update walkthrough with newfound knowledge
Noxsios Feb 23, 2023
1a69af5
docs
Noxsios Feb 23, 2023
26e3d62
consolidate code
Noxsios Feb 23, 2023
11ab770
add a walkthrough gif
Noxsios Feb 23, 2023
1565baa
docs
Noxsios Feb 23, 2023
c993a09
docs
Noxsios Feb 23, 2023
95fe015
insecure == http
Noxsios Feb 23, 2023
490f675
cleaner publish options
Noxsios Feb 24, 2023
3c8698d
new multiline spinner + fix SuccessF
Noxsios Feb 24, 2023
c604772
Merge branch 'features/oci-publish' of https://github.com/defenseunic…
Noxsios Feb 24, 2023
fda8dbb
fix multispinner w/ NoProgress
Noxsios Feb 26, 2023
b748334
comments
Noxsios Feb 26, 2023
fc150bd
lets not commit bad code
Noxsios Feb 27, 2023
3d2a210
pull now uses oras copy w/ hooks and multispinner
Noxsios Feb 27, 2023
6034e0b
port publish to use new multispinner
Noxsios Feb 27, 2023
3b516a4
better start signature on multispinner
Noxsios Feb 27, 2023
486e2ac
move multispinner into its own file
Noxsios Feb 27, 2023
3733c90
multispinner AddRow fn
Noxsios Feb 27, 2023
fbf7dc8
stop making bad commits
Noxsios Feb 27, 2023
ede8681
try to get incremental multispinner working
Noxsios Feb 27, 2023
484ca51
Refactor with Mutexes
Racer159 Feb 28, 2023
1611aa1
no more multispinner :( maybe another time
Noxsios Feb 28, 2023
ee066e5
use plain old spinner for now
Noxsios Feb 28, 2023
0d854ef
initial `package pull` command in place, refactor where concurrency i…
Noxsios Feb 28, 2023
6523ae1
docs and schema
Noxsios Feb 28, 2023
7d19ec3
archive temp after pull
Noxsios Feb 28, 2023
d4494db
move functions around to make code more dry and consistent
Noxsios Feb 28, 2023
18b7007
move oras back into utils now that it is cleaned up
Noxsios Feb 28, 2023
5e4c438
comments
Noxsios Feb 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/goccy/go-yaml v1.9.8
github.com/google/go-containerregistry v0.13.0
github.com/mholt/archiver/v3 v3.5.1
github.com/opencontainers/image-spec v1.1.0-rc2
github.com/otiai10/copy v1.9.0
github.com/pkg/errors v0.9.1
github.com/pterm/pterm v0.12.54
Expand All @@ -32,6 +33,7 @@ require (
k8s.io/apimachinery v0.26.1
k8s.io/client-go v0.26.1
k8s.io/klog/v2 v2.90.0
oras.land/oras-go/v2 v2.0.0
sigs.k8s.io/kustomize/api v0.12.1
sigs.k8s.io/kustomize/kyaml v0.13.9
sigs.k8s.io/yaml v1.3.0
Expand Down Expand Up @@ -262,7 +264,6 @@ require (
github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2671,6 +2671,8 @@ modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM=
modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
oras.land/oras-go v1.2.2 h1:0E9tOHUfrNH7TCDk5KU0jVBEzCqbfdyuVfGmJ7ZeRPE=
oras.land/oras-go v1.2.2/go.mod h1:Apa81sKoZPpP7CDciE006tSZ0x3Q3+dOoBcMZ/aNxvw=
oras.land/oras-go/v2 v2.0.0 h1:+LRAz92WF7AvYQsQjPEAIw3Xb2zPPhuydjpi4pIHmc0=
oras.land/oras-go/v2 v2.0.0/go.mod h1:iVExH1NxrccIxjsiq17L91WCZ4KIw6jVQyCLsZsu1gc=
pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
Expand Down
31 changes: 31 additions & 0 deletions src/cmd/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,28 @@ var packageRemoveCmd = &cobra.Command{
},
}

var packagePublishCmd = &cobra.Command{
Use: "publish [PACKAGE]",
Short: "Publish a Zarf package to a remote registry",
Long: "Publish a Zarf package to a remote registry\n" +
"Publishes a compiled package file to a remote registry. " +
"By default, the package will be published to the registry " +
"specified in the package's zarf.yaml file.",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
pkgConfig.PublishOpts.PackagePath = choosePackage(args)

// Configure the packager
pkgClient := packager.NewOrDie(&pkgConfig)
defer pkgClient.ClearTempPaths()

// Publish the package
if err := pkgClient.Publish(); err != nil {
message.Fatalf(err, "Failed to publish package: %s", err.Error())
}
},
}

func choosePackage(args []string) string {
if len(args) > 0 {
return args[0]
Expand Down Expand Up @@ -229,11 +251,13 @@ func init() {
packageCmd.AddCommand(packageInspectCmd)
packageCmd.AddCommand(packageRemoveCmd)
packageCmd.AddCommand(packageListCmd)
packageCmd.AddCommand(packagePublishCmd)

bindCreateFlags()
bindDeployFlags()
bindInspectFlags()
bindRemoveFlags()
bindPublishFlags()
}

func bindCreateFlags() {
Expand Down Expand Up @@ -288,3 +312,10 @@ func bindRemoveFlags() {
removeFlags.StringVar(&pkgConfig.DeployOpts.Components, "components", v.GetString(V_PKG_DEPLOY_COMPONENTS), "Comma-separated list of components to uninstall")
_ = packageRemoveCmd.MarkFlagRequired("confirm")
}

func bindPublishFlags() {
publishFlags := packagePublishCmd.Flags()
publishFlags.StringVar(&pkgConfig.PublishOpts.RegistryURL, "registry", "", "URL of the registry to publish the package to")
publishFlags.BoolVar(&pkgConfig.PublishOpts.Insecure, "insecure", false, "Allow insecure connections to the registry")
publishFlags.IntVar(&pkgConfig.PublishOpts.Concurrency, "concurrency", 3, "Number of concurrent uploads to the registry")
}
226 changes: 226 additions & 0 deletions src/pkg/packager/publish.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package packager

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"path/filepath"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"

"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/content/file"
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/errcode"
)

// NOTES:
// - This is a WIP, not yet functional
// - This is a copy of the oras example code, with some modifications
//
// replace comments w/ CLI args where appropriate
// replace fmt.Printf w/ message.Debug

var zarfMediaType = "application/vnd.zarf.layer.v1+tar.zst"

func (p *Packager) Publish() error {
registry := "localhost:666"
Noxsios marked this conversation as resolved.
Show resolved Hide resolved

name := p.cfg.Pkg.Metadata.Name
ver := p.cfg.Pkg.Metadata.Version
arch := p.cfg.Pkg.Metadata.Architecture
ref := fmt.Sprintf("%s/%s:%s-%s", registry, name, ver, arch)

pathRoot := p.tmp.Base

ctx := context.Background()

glob := func(path string) []string {
paths, _ := filepath.Glob(filepath.Join(pathRoot, path))
return paths
}
paths := []string{
filepath.Join(pathRoot, "checksums.txt"),
filepath.Join(pathRoot, "zarf.yaml"),
filepath.Join(pathRoot, "sboms.tar.zst"),
}
paths = append(paths, glob("components/*.tar.zst")...)
paths = append(paths, glob("images/*")...)

store, err := file.New("")
if err != nil {
panic(err)
}
defer store.Close()
descs, err := loadFiles(ctx, store, nil, paths)
if err != nil {
panic(err)
}
packOpts := oras.PackOptions{}
pack := func() (ocispec.Descriptor, error) {
// note the empty string for the artifactType
root, err := oras.Pack(ctx, store, "", descs, packOpts)
if err != nil {
return ocispec.Descriptor{}, err
}
if err = store.Tag(ctx, root, root.Digest.String()); err != nil {
return ocispec.Descriptor{}, err
}
return root, nil
}

// prepare push
dst, err := remote.NewRepository(ref)
if err != nil {
panic(err)
}

if p.cfg.PublishOpts.Insecure {
dst.PlainHTTP = true
}
copyOpts := oras.DefaultCopyOptions
if p.cfg.PublishOpts.Concurrency > copyOpts.Concurrency {
copyOpts.Concurrency = p.cfg.PublishOpts.Concurrency
}
copy := func(root ocispec.Descriptor) error {
fmt.Printf("%v\n", root)
if tag := dst.Reference.Reference; tag == "" {
err = oras.CopyGraph(ctx, store, dst, root, copyOpts.CopyGraphOptions)
} else {
_, err = oras.Copy(ctx, store, root.Digest.String(), dst, tag, copyOpts)
}
return err
}

// push
root, err := pushArtifact(dst, pack, &packOpts, copy, &copyOpts.CopyGraphOptions)
if err != nil {
panic(err)
}
fmt.Println("Pushed", root.Digest, "to", dst.Reference.String())
return nil
}

type packFunc func() (ocispec.Descriptor, error)
type copyFunc func(desc ocispec.Descriptor) error

// taken from https://github.com/oras-project/oras/blob/main/cmd/oras/push.go
func pushArtifact(dst oras.Target, pack packFunc, packOpts *oras.PackOptions, copy copyFunc, copyOpts *oras.CopyGraphOptions) (ocispec.Descriptor, error) {
root, err := pack()
if err != nil {
return ocispec.Descriptor{}, err
}

copyRootAttempted := false
preCopy := copyOpts.PreCopy
copyOpts.PreCopy = func(ctx context.Context, desc ocispec.Descriptor) error {
if content.Equal(root, desc) {
// copyRootAttempted helps track whether the returned error is
// generated from copying root.
copyRootAttempted = true
}
if preCopy != nil {
return preCopy(ctx, desc)
}
return nil
}

// push
if err = copy(root); err == nil {
return root, nil
}

if !copyRootAttempted || root.MediaType != ocispec.MediaTypeArtifactManifest ||
!isManifestUnsupported(err) {
return ocispec.Descriptor{}, err
}

if repo, ok := dst.(*remote.Repository); ok {
// assumes referrers API is not supported since OCI artifact
// media type is not supported
repo.SetReferrersCapability(false)
}
packOpts.PackImageManifest = true
root, err = pack()
if err != nil {
return ocispec.Descriptor{}, err
}

copyOpts.FindSuccessors = func(ctx context.Context, fetcher content.Fetcher, node ocispec.Descriptor) ([]ocispec.Descriptor, error) {
if content.Equal(node, root) {
// skip non-config
content, err := content.FetchAll(ctx, fetcher, root)
if err != nil {
return nil, err
}
var manifest ocispec.Manifest
if err := json.Unmarshal(content, &manifest); err != nil {
return nil, err
}
return []ocispec.Descriptor{manifest.Config}, nil
}

// config has no successors
return nil, nil
}
if err = copy(root); err != nil {
return ocispec.Descriptor{}, err
}
return root, nil
}
Noxsios marked this conversation as resolved.
Show resolved Hide resolved

// taken from https://github.com/oras-project/oras/blob/main/cmd/oras/file.go
func loadFiles(ctx context.Context, store *file.Store, annotations map[string]map[string]string, fileRefs []string) ([]ocispec.Descriptor, error) {
var files []ocispec.Descriptor
for _, filename := range fileRefs {
// get shortest absolute path as unique name
name := filepath.Clean(filename)
if !filepath.IsAbs(name) {
name = filepath.ToSlash(name)
}

// fmt.Println("Preparing", name)
file, err := store.Add(ctx, name, zarfMediaType, filename)
if err != nil {
return nil, err
}
if value, ok := annotations[filename]; ok {
if file.Annotations == nil {
file.Annotations = value
} else {
for k, v := range value {
file.Annotations[k] = v
}
}
}
files = append(files, file)
}
if len(files) == 0 {
fmt.Println("Uploading empty artifact")
}
return files, nil
}

func isManifestUnsupported(err error) bool {
var errResp *errcode.ErrorResponse
if !errors.As(err, &errResp) || errResp.StatusCode != http.StatusBadRequest {
return false
}

var errCode errcode.Error
if !errors.As(errResp, &errCode) {
return false
}

// As of November 2022, ECR is known to return UNSUPPORTED error when
// putting an OCI artifact manifest.
switch errCode.Code {
case errcode.ErrorCodeManifestInvalid, errcode.ErrorCodeUnsupported:
return true
}
return false
}

3 changes: 3 additions & 0 deletions src/types/packager.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ type PackagerConfig struct {
// InitOpts tracks user-defined values for the active Zarf initialization.
InitOpts ZarfInitOptions

// PublishOpts tracks user-defined options used to publish the package
PublishOpts ZarfPublishOptions

// Track if CLI prompts should be generated
IsInteractive bool

Expand Down
8 changes: 8 additions & 0 deletions src/types/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ type ZarfDeployOptions struct {
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"`
}

// ZarfPublishOptions tracks the user-defined preferences during a package publish.
type ZarfPublishOptions struct {
PackagePath string `json:"packagePath" jsonschema:"description=Location where a Zarf package to publish can be found"`
RegistryURL string `json:"registryURL" jsonschema:"description=URL of the registry to publish the package to"`
Insecure bool `json:"insecure" jsonschema:"description=Allow insecure connections for remote registries"`
Concurrency int `json:"concurrency" jsonschema:"description=Number of concurrent uploads to perform"`
}

// ZarfInitOptions tracks the user-defined options during cluster initialization.
type ZarfInitOptions struct {
// Zarf init is installing the k3s component
Expand Down