Skip to content

Commit

Permalink
Merge pull request #3 from compliance-framework/feature-upload-single…
Browse files Browse the repository at this point in the history
…-artifacts

Feature upload single artifacts
  • Loading branch information
chris-cmsoft authored Dec 2, 2024
2 parents d3f7619 + 05ec423 commit 08c411c
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 13 deletions.
132 changes: 132 additions & 0 deletions cmd/upload-single.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package cmd

import (
"errors"
"fmt"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/tarball"
v2 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
"log"
"os"
"path"
"time"
)

func UploadSingleCmd() *cobra.Command {
command := &cobra.Command{
Use: "upload-single [source artifact] [destination registry]",
Short: "Upload single archive to an OCI registry",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
downloadCmd := &uploadSingleArtifact{}
err := downloadCmd.run(cmd, args)
if err != nil {
log.Fatal(err)
}
},
}

return command
}

type uploadSingleArtifact struct {
}

func (d *uploadSingleArtifact) validateArgs(args []string) (*uploadConfig, error) {
// Validate the first arg is a directory.
archive := args[0]
if !path.IsAbs(archive) {
workDir, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("failed to get current working directory: %v", err)
}
archive = path.Join(workDir, archive)
}

fi, err := os.Stat(archive)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("source archive does not exist: %v", err)
}

return nil, fmt.Errorf("failed to stat archive: %v", err)
}

if fi.IsDir() {

return nil, fmt.Errorf("source archive is a directory")
}

// Validate the second arg is a valid OCI registry
repositoryName := args[1]
tag, err := name.NewTag(repositoryName, name.StrictValidation)
if err != nil {
return nil, fmt.Errorf("failed to parse repository: %v", err)
}

return &uploadConfig{
source: archive,
tag: tag,
}, nil
}

func (d *uploadSingleArtifact) run(cmd *cobra.Command, args []string) error {
config, err := d.validateArgs(args)
if err != nil {
return err
}

// We'll use the filename as the image title for the moment
title := path.Base(config.source)

// We could add more annotations later based on flags or potentially a config file.
// Right now we push what we know.
index := mutate.Annotations(empty.Index, map[string]string{
"org.opencontainers.image.created": time.Now().UTC().Format("2006-01-02T15:04:05Z"),
"org.opencontainers.image.title": title,
"org.opencontainers.image.ref.name": config.tag.TagStr(),
"org.opencontainers.image.version": config.tag.TagStr(),
}).(v1.ImageIndex)

layer, err := tarball.LayerFromFile(config.source)
if err != nil {
log.Fatalf("failed to create tarball layer: %v", err)
}

// Create an OCI image with the layer
img, err := mutate.Append(empty.Image, mutate.Addendum{
MediaType: v2.MediaTypeImageLayerGzip,
Annotations: map[string]string{
"org.opencontainers.image.ref.name": title,
},
Layer: layer,
})
if err != nil {
return err
}

if err := remote.Write(
config.tag,
img,
remote.WithAuthFromKeychain(authn.DefaultKeychain),
); err != nil {
return err
}

index = mutate.AppendManifests(index, mutate.IndexAddendum{
Add: img,
})

err = remote.WriteIndex(config.tag, index, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
return err
}

return nil
}
10 changes: 5 additions & 5 deletions cmd/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ func UploadReleaseCmd() *cobra.Command {
}

type uploadConfig struct {
directory string
tag name.Tag
source string
tag name.Tag
}

type uploadRelease struct {
Expand Down Expand Up @@ -76,8 +76,8 @@ func (d *uploadRelease) validateArgs(args []string) (*uploadConfig, error) {
}

return &uploadConfig{
directory: archiveDir,
tag: tag,
source: archiveDir,
tag: tag,
}, nil
}

Expand All @@ -87,7 +87,7 @@ func (d *uploadRelease) run(cmd *cobra.Command, args []string) error {
return err
}

data, err := metadata.New(config.directory)
data, err := metadata.New(config.source)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/upload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func Test_UploadCmd_ValidateArgs_Response(t *testing.T) {
if err != nil {
t.Errorf("uploadCmd.validateArgs() error = %v", err)
}
if config.directory != path.Join(workDir, validDirectory) {
if config.source != path.Join(workDir, validDirectory) {
t.Errorf("uploadCmd.validateArgs() config = %v", config)
}
})
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func main() {
}

rootCmd.AddCommand(cmd.UploadReleaseCmd())
rootCmd.AddCommand(cmd.UploadSingleCmd())
rootCmd.AddCommand(cmd.DownloadReleaseCmd())
rootCmd.AddCommand(cmd.LoginCmd())
rootCmd.AddCommand(cmd.LogoutCmd())
Expand Down
9 changes: 2 additions & 7 deletions pkg/oci/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import (
"archive/tar"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"io"
"os"
"path"
"path/filepath"
"runtime"
)

type Downloader struct {
Expand All @@ -30,14 +28,11 @@ func NewDownloader(source name.Tag, destination string) (Downloader, error) {
// Download executes the download of the OCI artifact into memory, untars it and write it to a directory.
// This will need to be updated at some point when we are working with OCI artifacts rather than images,
// to take slightly different actions based on the artifact type we receive from the registry (image / binary / fs)
func (dl *Downloader) Download() error {
func (dl *Downloader) Download(option ...remote.Option) error {
opts := []remote.Option{
remote.WithAuthFromKeychain(authn.DefaultKeychain),
remote.WithPlatform(v1.Platform{
Architecture: runtime.GOARCH,
OS: runtime.GOOS,
}),
}
opts = append(opts, option...)
img, err := remote.Image(dl.reference, opts...)
if err != nil {
return err
Expand Down

0 comments on commit 08c411c

Please sign in to comment.