Skip to content

Commit

Permalink
Add annotations for upload blob. (#2188)
Browse files Browse the repository at this point in the history
* Add annotations for upload blob.
Add tests for UploadFile()

Signed-off-by: Magnus Bengtsson <[email protected]>

* Fix failing checks

Signed-off-by: Magnus Bengtsson <[email protected]>

* Last linter errors

Signed-off-by: Magnus Bengtsson <[email protected]>

* docgen

Signed-off-by: Magnus Bengtsson <[email protected]>

* use options instead of casting to a v1.image

Signed-off-by: Magnus Bengtsson <[email protected]>

Signed-off-by: Magnus Bengtsson <[email protected]>
  • Loading branch information
cldmnky authored Oct 5, 2022
1 parent 91b03f2 commit dc40467
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 10 deletions.
3 changes: 3 additions & 0 deletions cmd/cosign/cli/options/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type UploadBlobOptions struct {
ContentType string
Files FilesOptions
Registry RegistryOptions
Annotations map[string]string
}

var _ Interface = (*UploadBlobOptions)(nil)
Expand All @@ -35,6 +36,8 @@ func (o *UploadBlobOptions) AddFlags(cmd *cobra.Command) {

cmd.Flags().StringVar(&o.ContentType, "ct", "",
"content type to set")
cmd.Flags().StringToStringVarP(&o.Annotations, "annotation", "a", nil,
"annotations to set")
}

// UploadWASMOptions is the top level wrapper for the `upload wasm` command.
Expand Down
4 changes: 2 additions & 2 deletions cmd/cosign/cli/policy_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ func initPolicy() *cobra.Command {
cremote.FileFromFlag(outfile),
}

return upload.BlobCmd(cmd.Context(), o.Registry, files, "", rootPath(o.ImageRef))
return upload.BlobCmd(cmd.Context(), o.Registry, files, nil, "", rootPath(o.ImageRef))
},
}

Expand Down Expand Up @@ -297,7 +297,7 @@ func signPolicy() *cobra.Command {
cremote.FileFromFlag(outfile),
}

return upload.BlobCmd(ctx, o.Registry, files, "", rootPath(o.ImageRef))
return upload.BlobCmd(ctx, o.Registry, files, nil, "", rootPath(o.ImageRef))
},
}

Expand Down
10 changes: 8 additions & 2 deletions cmd/cosign/cli/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,13 @@ func uploadBlob() *cobra.Command {
cosign upload blob -f foo:MYOS/MYPLATFORM <IMAGE>
# upload two blobs named foo-darwin and foo-linux to the location specified by <IMAGE>, setting the os fields
cosign upload blob -f foo-darwin:darwin -f foo-linux:linux <IMAGE>`,
cosign upload blob -f foo-darwin:darwin -f foo-linux:linux <IMAGE>
# upload a blob named foo to the location specified by <IMAGE>, setting annotations mykey=myvalue.
cosign upload blob -a mykey=myvalue -f foo <IMAGE>
# upload two blobs named foo-darwin and foo-linux to the location specified by <IMAGE>, setting annotations
cosign upload blob -a mykey=myvalue -a myotherkey="my other value" -f foo-darwin:darwin -f foo-linux:linux <IMAGE>`,
Args: cobra.ExactArgs(1),
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(o.Files.Files) < 1 {
Expand All @@ -70,7 +76,7 @@ func uploadBlob() *cobra.Command {
return err
}

return upload.BlobCmd(cmd.Context(), o.Registry, files, o.ContentType, args[0])
return upload.BlobCmd(cmd.Context(), o.Registry, files, o.Annotations, o.ContentType, args[0])
},
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/cosign/cli/upload/blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
cremote "github.com/sigstore/cosign/pkg/cosign/remote"
)

func BlobCmd(ctx context.Context, regOpts options.RegistryOptions, files []cremote.File, contentType, imageRef string) error {
func BlobCmd(ctx context.Context, regOpts options.RegistryOptions, files []cremote.File, annotations map[string]string, contentType, imageRef string) error {
ref, err := name.ParseReference(imageRef)
if err != nil {
return err
Expand All @@ -43,7 +43,7 @@ func BlobCmd(ctx context.Context, regOpts options.RegistryOptions, files []cremo
}
}

dgstAddr, err := cremote.UploadFiles(ref, files, mt, regOpts.GetRegistryClientOpts(ctx)...)
dgstAddr, err := cremote.UploadFiles(ref, files, annotations, mt, regOpts.GetRegistryClientOpts(ctx)...)
if err != nil {
return err
}
Expand Down
7 changes: 7 additions & 0 deletions doc/cosign_upload_blob.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions pkg/cosign/remote/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func DefaultMediaTypeGetter(b []byte) types.MediaType {
return types.MediaType(strings.Split(http.DetectContentType(b), ";")[0])
}

func UploadFiles(ref name.Reference, files []File, getMt MediaTypeGetter, remoteOpts ...remote.Option) (name.Digest, error) {
func UploadFiles(ref name.Reference, files []File, annotations map[string]string, getMt MediaTypeGetter, remoteOpts ...remote.Option) (name.Digest, error) {
var lastHash v1.Hash
var idx v1.ImageIndex = empty.Index

Expand All @@ -109,14 +109,16 @@ func UploadFiles(ref name.Reference, files []File, getMt MediaTypeGetter, remote
mt := getMt(b)
fmt.Fprintf(os.Stderr, "Uploading file from [%s] to [%s] with media type [%s]\n", f.Path(), ref.Name(), mt)

img, err := static.NewFile(b, static.WithLayerMediaType(mt))
img, err := static.NewFile(b, static.WithLayerMediaType(mt), static.WithAnnotations(annotations))
if err != nil {
return name.Digest{}, err
}

lastHash, err = img.Digest()
if err != nil {
return name.Digest{}, err
}

if err := remote.Write(ref, img, remoteOpts...); err != nil {
return name.Digest{}, err
}
Expand All @@ -128,8 +130,10 @@ func UploadFiles(ref name.Reference, files []File, getMt MediaTypeGetter, remote
if err != nil {
return name.Digest{}, err
}

blobURL := ref.Context().Registry.RegistryStr() + "/v2/" + ref.Context().RepositoryStr() + "/blobs/" + layerHash.String()
fmt.Fprintf(os.Stderr, "File [%s] is available directly at [%s]\n", f.Path(), blobURL)

if f.Platform() != nil {
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
Add: img,
Expand All @@ -141,6 +145,9 @@ func UploadFiles(ref name.Reference, files []File, getMt MediaTypeGetter, remote
}

if len(files) > 1 {
if annotations != nil {
idx = mutate.Annotations(idx, annotations).(v1.ImageIndex)
}
err := remote.WriteIndex(ref, idx, remoteOpts...)
if err != nil {
return name.Digest{}, err
Expand Down
217 changes: 217 additions & 0 deletions pkg/cosign/remote/index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,18 @@
package remote

import (
"fmt"
"io/ioutil"
"log"
"net/http/httptest"
"net/url"
"reflect"
"testing"

"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/registry"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
)

func TestFilesFromFlagList(t *testing.T) {
Expand Down Expand Up @@ -93,3 +101,212 @@ func TestFileFromFlag(t *testing.T) {
})
}
}

func TestUploadFiles(t *testing.T) {
var (
expectedRepo = "foo"
mt = DefaultMediaTypeGetter
err error
)
// Set up a fake registry (with NOP logger to avoid spamming test logs).
nopLog := log.New(ioutil.Discard, "", 0)
s := httptest.NewServer(registry.New(registry.Logger(nopLog)))
defer s.Close()
u, err := url.Parse(s.URL)
if err != nil {
t.Fatal(err)
}

tests := []struct {
name string
image string
fs []File
annotations map[string]string
wantErr bool
}{{
name: "one file",
image: "one",
fs: []File{
&file{path: "testdata/foo"},
},
wantErr: false,
}, {
name: "missing file",
image: "one-missing",
fs: []File{
&file{path: "testdata/missing"},
},
wantErr: true,
},
{
name: "two files with platform",
image: "two-platform",
fs: []File{
&file{path: "testdata/foo", platform: &v1.Platform{
OS: "darwin",
Architecture: "amd64",
}},
&file{path: "testdata/bar", platform: &v1.Platform{
OS: "linux",
Architecture: "amd64",
}},
},
wantErr: false,
}, {
name: "one file with annotations",
image: "one-annotations",
fs: []File{
&file{path: "testdata/foo"},
},
annotations: map[string]string{
"foo": "bar",
},
wantErr: false,
}, {
name: "two files with annotations",
image: "two-annotations",
fs: []File{
&file{path: "testdata/foo", platform: &v1.Platform{
OS: "darwin",
Architecture: "amd64",
}},
&file{path: "testdata/bar", platform: &v1.Platform{
OS: "linux",
Architecture: "amd64",
}},
},
annotations: map[string]string{
"foo": "bar",
"bar": "baz",
},
wantErr: false,
}}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ref, err := name.ParseReference(fmt.Sprintf("%s/%s/%s:latest", u.Host, expectedRepo, tt.image))
if err != nil {
t.Fatalf("ParseRef() = %v", err)
}
_, err = UploadFiles(ref, tt.fs, tt.annotations, mt)

if (err != nil) != tt.wantErr {
t.Errorf("UploadFiles() error = %v, wantErr %v", err, tt.wantErr)
}

if len(tt.fs) > 1 {
if !checkIndex(t, ref) {
t.Errorf("UploadFiles() index = %v", checkIndex(t, ref))
}
}

for _, f := range tt.fs {
if f.Platform() != nil {
if !checkPlatform(t, ref, f.Platform()) {
t.Errorf("UploadFiles() platform = %v was not found", checkPlatform(t, ref, f.Platform()))
}
}
}

if tt.annotations != nil {
if !checkAnnotations(t, ref, tt.annotations) {
t.Errorf("UploadFiles() annotations = %v was not found", checkAnnotations(t, ref, tt.annotations))
}
}
})
}
}

func checkPlatform(t *testing.T, ref name.Reference, p *v1.Platform) bool {
t.Helper()
d, err := remote.Get(ref)
if err != nil {
t.Fatalf("Get() = %v", err)
}

if d.MediaType.IsIndex() {
// if it is an index recurse into the index
idx, err := d.ImageIndex()
if err != nil {
t.Fatalf("ImageIndex() = %v", err)
}
manifest, err := idx.IndexManifest()
if err != nil {
t.Fatalf("IndexManifest() = %v", err)
}
for _, m := range manifest.Manifests {
if !m.MediaType.IsImage() {
return false
}
if m.Platform != nil {
if m.Platform.OS == p.OS && m.Platform.Architecture == p.Architecture {
return true
}
}
}
return false
} else if d.MediaType.IsImage() {
// if it is an image check the platform
if d.Platform != nil {
if d.Platform.OS == p.OS && d.Platform.Architecture == p.Architecture {
return true
}
}
}
return false
}

func checkIndex(t *testing.T, ref name.Reference) bool {
t.Helper()
d, err := remote.Get(ref)
if err != nil {
t.Fatalf("Get() = %v", err)
}
if d.MediaType.IsIndex() {
return true
}
return false
}

func checkAnnotations(t *testing.T, ref name.Reference, annotations map[string]string) bool {
t.Helper()
var found bool
d, err := remote.Get(ref)
if err != nil {
t.Fatalf("Get() = %v", err)
}

if d.MediaType.IsIndex() {
idx, err := remote.Index(ref)
if err != nil {
t.Fatalf("Index() = %v", err)
}
m, err := idx.IndexManifest()
if err != nil {
t.Fatalf("IndexManifest() = %v", err)
}
for annotationsKey := range annotations {
_, ok := m.Annotations[annotationsKey]
if ok {
found = true
}
}
return found
}

if d.MediaType.IsImage() {
i, err := remote.Image(ref)
if err != nil {
t.Fatalf("Image() = %v", err)
}
m, _ := i.Manifest()
for annotationsKey := range annotations {
_, ok := m.Annotations[annotationsKey]
if ok {
found = true
}
}
return found
}
return false
}
1 change: 1 addition & 0 deletions pkg/cosign/remote/testdata/bar
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bar
1 change: 1 addition & 0 deletions pkg/cosign/remote/testdata/foo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo
3 changes: 3 additions & 0 deletions pkg/oci/static/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ func NewFile(payload []byte, opts ...Option) (oci.File, error) {
return nil, err
}

// Add annotations from options
img = mutate.Annotations(img, o.Annotations).(v1.Image)

// Set the Created date to time of execution
img, err = mutate.CreatedAt(img, v1.Time{Time: time.Now()})
if err != nil {
Expand Down
Loading

0 comments on commit dc40467

Please sign in to comment.