Skip to content

Commit

Permalink
Define oci.File. (#796)
Browse files Browse the repository at this point in the history
This type is currently just an alias for `v1.Image`, which is the unit of storage we use for files uploaded with `UploadFile[s]`.

This changes `static.NewFile` to create an `oci.File` (full image vs. just the layer previously), and `UploadFile` to use this (it's where the rest of the image came from).

Signed-off-by: Matt Moore <[email protected]>
  • Loading branch information
mattmoor authored Sep 25, 2021
1 parent 59a6200 commit 9d4070e
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 119 deletions.
2 changes: 1 addition & 1 deletion cmd/cosign/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ func AttestCmd(ctx context.Context, ko sign.KeyOpts, regOpts options.RegistryOpt
return nil
}

opts := []static.Option{static.WithMediaType(types.DssePayloadType)}
opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)}
if sv.Cert != nil {
opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain))
}
Expand Down
12 changes: 2 additions & 10 deletions pkg/cosign/remote/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,20 +138,12 @@ func UploadFiles(ref name.Reference, files []File, getMt MediaTypeGetter, remote
return ref.Context().Digest(lastHash.String()), nil
}

func UploadFile(b []byte, ref name.Reference, layerMT, configMt types.MediaType, remoteOpts ...remote.Option) (v1.Image, error) {
l, err := static.NewFile(b, static.WithMediaType(layerMT))
func UploadFile(b []byte, ref name.Reference, layerMT, configMT types.MediaType, remoteOpts ...remote.Option) (v1.Image, error) {
img, err := static.NewFile(b, static.WithLayerMediaType(layerMT), static.WithConfigMediaType(configMT))
if err != nil {
return nil, err
}

emptyOci := mutate.MediaType(empty.Image, types.OCIManifestSchema1)
img, err := mutate.Append(emptyOci, mutate.Addendum{
Layer: l,
})
if err != nil {
return nil, err
}
img = mutate.ConfigMediaType(img, configMt)
if err := remote.Write(ref, img, remoteOpts...); err != nil {
return nil, err
}
Expand Down
23 changes: 23 additions & 0 deletions pkg/oci/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package oci

// File is a degenerate form of SignedImage that stores a single file as a v1.Layer
type File interface {
SignedImage

// TODO(mattmoor): Consider adding useful helpers.
}
44 changes: 44 additions & 0 deletions pkg/oci/static/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package static

import (
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/sigstore/cosign/pkg/oci"
"github.com/sigstore/cosign/pkg/oci/signed"
)

// NewFile constructs a new v1.Image with the provided payload.
func NewFile(payload []byte, opts ...Option) (oci.File, error) {
o, err := makeOptions(opts...)
if err != nil {
return nil, err
}
base := mutate.MediaType(empty.Image, types.OCIManifestSchema1)
base = mutate.ConfigMediaType(base, o.ConfigMediaType)
img, err := mutate.Append(base, mutate.Addendum{
Layer: &staticLayer{
b: payload,
opts: o,
},
})
if err != nil {
return nil, err
}
return signed.Image(img), nil
}
115 changes: 115 additions & 0 deletions pkg/oci/static/file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package static

import (
"io"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/types"
)

func TestNewFile(t *testing.T) {
payload := "this is the content!"
img, err := NewFile([]byte(payload), WithLayerMediaType("foo"))
if err != nil {
t.Fatalf("NewFile() = %v", err)
}

layers, err := img.Layers()
if err != nil {
t.Fatalf("Layers() = %v", err)
} else if got, want := len(layers), 1; got != want {
t.Fatalf("len(Layers()) = %d, wanted %d", got, want)
}
l := layers[0]

t.Run("check size", func(t *testing.T) {
wantSize := int64(len(payload))
gotSize, err := l.Size()
if err != nil {
t.Fatalf("Size() = %v", err)
}
if gotSize != wantSize {
t.Errorf("Size() = %d, wanted %d", gotSize, wantSize)
}
})

t.Run("check media type", func(t *testing.T) {
wantMT := types.MediaType("foo")
gotMT, err := l.MediaType()
if err != nil {
t.Fatalf("MediaType() = %v", err)
}
if gotMT != wantMT {
t.Errorf("MediaType() = %s, wanted %s", gotMT, wantMT)
}
})

t.Run("check hashes", func(t *testing.T) {
wantHash, _, err := v1.SHA256(strings.NewReader(payload))
if err != nil {
t.Fatalf("SHA256() = %v", err)
}

gotDigest, err := l.Digest()
if err != nil {
t.Fatalf("Digest() = %v", err)
}
if !cmp.Equal(gotDigest, wantHash) {
t.Errorf("Digest = %s", cmp.Diff(gotDigest, wantHash))
}

gotDiffID, err := l.DiffID()
if err != nil {
t.Fatalf("DiffID() = %v", err)
}
if !cmp.Equal(gotDiffID, wantHash) {
t.Errorf("DiffID = %s", cmp.Diff(gotDiffID, wantHash))
}
})

t.Run("check content", func(t *testing.T) {
comp, err := l.Compressed()
if err != nil {
t.Fatalf("Compressed() = %v", err)
}
defer comp.Close()
compContent, err := io.ReadAll(comp)
if err != nil {
t.Fatalf("ReadAll() = %v", err)
}
if got, want := string(compContent), payload; got != want {
t.Errorf("Compressed() = %s, wanted %s", got, want)
}

uncomp, err := l.Uncompressed()
if err != nil {
t.Fatalf("Uncompressed() = %v", err)
}
defer uncomp.Close()
uncompContent, err := io.ReadAll(uncomp)
if err != nil {
t.Fatalf("ReadAll() = %v", err)
}
if got, want := string(uncompContent), payload; got != want {
t.Errorf("Uncompressed() = %s, wanted %s", got, want)
}
})
}
29 changes: 19 additions & 10 deletions pkg/oci/static/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,19 @@ import (
type Option func(*options)

type options struct {
MediaType types.MediaType
Bundle *oci.Bundle
Cert []byte
Chain []byte
Annotations map[string]string
LayerMediaType types.MediaType
ConfigMediaType types.MediaType
Bundle *oci.Bundle
Cert []byte
Chain []byte
Annotations map[string]string
}

func makeOptions(opts ...Option) (*options, error) {
o := &options{
MediaType: ctypes.SimpleSigningMediaType,
Annotations: make(map[string]string),
LayerMediaType: ctypes.SimpleSigningMediaType,
ConfigMediaType: types.OCIConfigJSON,
Annotations: make(map[string]string),
}

for _, opt := range opts {
Expand All @@ -59,10 +61,17 @@ func makeOptions(opts ...Option) (*options, error) {
return o, nil
}

// WithMediaType sets the media type of the signature.
func WithMediaType(mt types.MediaType) Option {
// WithLayerMediaType sets the media type of the signature.
func WithLayerMediaType(mt types.MediaType) Option {
return func(o *options) {
o.MediaType = mt
o.LayerMediaType = mt
}
}

// WithConfigMediaType sets the media type of the signature.
func WithConfigMediaType(mt types.MediaType) Option {
return func(o *options) {
o.ConfigMediaType = mt
}
}

Expand Down
32 changes: 23 additions & 9 deletions pkg/oci/static/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"reflect"
"testing"

"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/sigstore/cosign/pkg/oci"
ctypes "github.com/sigstore/cosign/pkg/types"
)
Expand All @@ -33,23 +34,34 @@ func TestOptions(t *testing.T) {
}{{
name: "no options",
want: &options{
MediaType: ctypes.SimpleSigningMediaType,
Annotations: make(map[string]string),
LayerMediaType: ctypes.SimpleSigningMediaType,
ConfigMediaType: types.OCIConfigJSON,
Annotations: make(map[string]string),
},
}, {
name: "with media type",
opts: []Option{WithMediaType("foo")},
name: "with layer media type",
opts: []Option{WithLayerMediaType("foo")},
want: &options{
MediaType: "foo",
Annotations: make(map[string]string),
LayerMediaType: "foo",
ConfigMediaType: types.OCIConfigJSON,
Annotations: make(map[string]string),
},
}, {
name: "with config media type",
opts: []Option{WithConfigMediaType("bar")},
want: &options{
LayerMediaType: ctypes.SimpleSigningMediaType,
ConfigMediaType: "bar",
Annotations: make(map[string]string),
},
}, {
name: "with annotations",
opts: []Option{WithAnnotations(map[string]string{
"foo": "bar",
})},
want: &options{
MediaType: ctypes.SimpleSigningMediaType,
LayerMediaType: ctypes.SimpleSigningMediaType,
ConfigMediaType: types.OCIConfigJSON,
Annotations: map[string]string{
"foo": "bar",
},
Expand All @@ -58,7 +70,8 @@ func TestOptions(t *testing.T) {
name: "with cert chain",
opts: []Option{WithCertChain([]byte("a"), []byte("b"))},
want: &options{
MediaType: ctypes.SimpleSigningMediaType,
LayerMediaType: ctypes.SimpleSigningMediaType,
ConfigMediaType: types.OCIConfigJSON,
Annotations: map[string]string{
CertificateAnnotationKey: "a",
ChainAnnotationKey: "b",
Expand All @@ -70,7 +83,8 @@ func TestOptions(t *testing.T) {
name: "with bundle",
opts: []Option{WithBundle(bundle)},
want: &options{
MediaType: ctypes.SimpleSigningMediaType,
LayerMediaType: ctypes.SimpleSigningMediaType,
ConfigMediaType: types.OCIConfigJSON,
Annotations: map[string]string{
BundleAnnotationKey: "{\"SignedEntryTimestamp\":\"\",\"Payload\":{\"body\":null,\"integratedTime\":0,\"logIndex\":0,\"logID\":\"\"}}",
},
Expand Down
7 changes: 1 addition & 6 deletions pkg/oci/static/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ func NewAttestation(payload []byte, opts ...Option) (oci.Signature, error) {
return NewSignature(payload, "", opts...)
}

// NewFile constructs a new v1.Layer with the provided payload.
func NewFile(payload []byte, opts ...Option) (v1.Layer, error) {
return NewSignature(payload, "", opts...)
}

type staticLayer struct {
b []byte
b64sig string
Expand Down Expand Up @@ -139,5 +134,5 @@ func (l *staticLayer) Size() (int64, error) {

// MediaType implements v1.Layer
func (l *staticLayer) MediaType() (types.MediaType, error) {
return l.opts.MediaType, nil
return l.opts.LayerMediaType, nil
}
Loading

0 comments on commit 9d4070e

Please sign in to comment.