refactor!: move oci output from mirror to oci pkg
BREAKING_CHANGE: output methods to save and push images are now part of the oci pkg
This commit is contained in:
@@ -6,7 +6,6 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/attest/pkg/mirror"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -25,7 +24,7 @@ func TestRegistryAuth(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Image, func(t *testing.T) {
|
||||
err := mirror.PushIndexToRegistry(attIdx.Index, tc.Image)
|
||||
err := oci.PushIndexToRegistry(attIdx.Index, tc.Image)
|
||||
require.NoError(t, err)
|
||||
_, err = oci.IndexFromRemote(tc.Image)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
"github.com/docker/attest/pkg/mirror"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
@@ -80,7 +81,7 @@ func ExampleNewTUFMirror() {
|
||||
func mirrorToRegistry(o *TufMirrorOutput) error {
|
||||
// push metadata to registry
|
||||
metadataRepo := "registry-1.docker.io/docker/tuf-metadata:latest"
|
||||
err := mirror.PushImageToRegistry(o.metadata, metadataRepo)
|
||||
err := oci.PushImageToRegistry(o.metadata, metadataRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -91,7 +92,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
|
||||
return fmt.Errorf("failed to get repo without tag: %s", metadataRepo)
|
||||
}
|
||||
imageName := fmt.Sprintf("%s:%s", repo, metadata.Tag)
|
||||
err = mirror.PushImageToRegistry(metadata.Image, imageName)
|
||||
err = oci.PushImageToRegistry(metadata.Image, imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -101,7 +102,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
|
||||
targetsRepo := "registry-1.docker.io/docker/tuf-targets"
|
||||
for _, target := range o.targets {
|
||||
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
|
||||
err = mirror.PushImageToRegistry(target.Image, imageName)
|
||||
err = oci.PushImageToRegistry(target.Image, imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -109,7 +110,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
|
||||
// push delegated targets to registry
|
||||
for _, target := range o.delegatedTargets {
|
||||
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
|
||||
err = mirror.PushIndexToRegistry(target.Index, imageName)
|
||||
err = oci.PushIndexToRegistry(target.Index, imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -119,14 +120,14 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
|
||||
|
||||
func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
|
||||
// output metadata to local directory
|
||||
err := mirror.SaveImageAsOCILayout(o.metadata, outputPath)
|
||||
err := oci.SaveImageAsOCILayout(o.metadata, outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// output delegated metadata to local directory
|
||||
for _, metadata := range o.delegatedMetadata {
|
||||
path := filepath.Join(outputPath, metadata.Tag)
|
||||
err = mirror.SaveImageAsOCILayout(metadata.Image, path)
|
||||
err = oci.SaveImageAsOCILayout(metadata.Image, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -135,7 +136,7 @@ func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
|
||||
// output top-level targets to local directory
|
||||
for _, target := range o.targets {
|
||||
path := filepath.Join(outputPath, target.Tag)
|
||||
err = mirror.SaveImageAsOCILayout(target.Image, path)
|
||||
err = oci.SaveImageAsOCILayout(target.Image, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -143,7 +144,7 @@ func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
|
||||
// output delegated targets to local directory
|
||||
for _, target := range o.delegatedTargets {
|
||||
path := filepath.Join(outputPath, target.Tag)
|
||||
err = mirror.SaveIndexAsOCILayout(target.Index, path)
|
||||
err = oci.SaveIndexAsOCILayout(target.Index, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,18 +2,9 @@ package mirror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
"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/layout"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
)
|
||||
|
||||
func NewTUFMirror(root []byte, tufPath, metadataURL, targetsURL string, versionChecker tuf.VersionChecker) (*TUFMirror, error) {
|
||||
@@ -26,136 +17,3 @@ func NewTUFMirror(root []byte, tufPath, metadataURL, targetsURL string, versionC
|
||||
}
|
||||
return &TUFMirror{TUFClient: tufClient, tufPath: tufPath, metadataURL: metadataURL, targetsURL: targetsURL}, nil
|
||||
}
|
||||
|
||||
func PushImageToRegistry(image v1.Image, imageName string) error {
|
||||
ref, err := name.ParseReference(imageName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse image name '%s': %w", imageName, err)
|
||||
}
|
||||
|
||||
// Push the image to the registry
|
||||
return remote.Write(ref, image, oci.MultiKeychainOption())
|
||||
}
|
||||
|
||||
func PushIndexToRegistry(index v1.ImageIndex, imageName string) error {
|
||||
// Parse the index name
|
||||
ref, err := name.ParseReference(imageName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse image name: %w", err)
|
||||
}
|
||||
|
||||
// Push the index to the registry
|
||||
return remote.WriteIndex(ref, index, oci.MultiKeychainOption())
|
||||
}
|
||||
|
||||
func SaveImageAsOCILayout(image v1.Image, path string) error {
|
||||
// Save the image to the local filesystem
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
index := empty.Index
|
||||
l, err := layout.Write(path, index)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create index: %w", err)
|
||||
}
|
||||
return l.AppendImage(image)
|
||||
}
|
||||
|
||||
func SaveIndexAsOCILayout(image v1.ImageIndex, path string) error {
|
||||
// Save the index to the local filesystem
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
|
||||
_, err = layout.Write(path, image)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create index: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveIndex(outputs []*oci.ImageSpec, index v1.ImageIndex, indexName string) error {
|
||||
// split output by comma and write or push each one
|
||||
for _, output := range outputs {
|
||||
if output.Type == oci.OCI {
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: index,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OCIReferenceTarget: indexName,
|
||||
},
|
||||
},
|
||||
})
|
||||
err := SaveIndexAsOCILayout(idx, output.Identifier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write signed image: %w", err)
|
||||
}
|
||||
} else {
|
||||
err := PushIndexToRegistry(index, output.Identifier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to push signed image: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveImage(output *oci.ImageSpec, image v1.Image, imageName string) error {
|
||||
if output.Type == oci.OCI {
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: image,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OCIReferenceTarget: imageName,
|
||||
},
|
||||
},
|
||||
})
|
||||
err := SaveIndexAsOCILayout(idx, output.Identifier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write signed image: %w", err)
|
||||
}
|
||||
} else {
|
||||
err := PushImageToRegistry(image, output.Identifier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to push signed image: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveReferrers(manifest *attestation.Manifest, outputs []*oci.ImageSpec) error {
|
||||
for _, output := range outputs {
|
||||
// OCI layout output for referrers not supported
|
||||
if output.Type == oci.OCI {
|
||||
continue
|
||||
}
|
||||
// so that we use the same tag each time to reduce number of tags (tags aren't needed for referrers but we must push one)
|
||||
// attOut, err := oci.ReplaceTagInSpec(output, manifest.SubjectDescriptor.Digest)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
images, err := manifest.BuildReferringArtifacts()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build image: %w", err)
|
||||
}
|
||||
for _, image := range images {
|
||||
digest, err := image.Digest()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get attestation image digest: %w", err)
|
||||
}
|
||||
attOut, err := oci.ReplaceDigestInSpec(output, digest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create attestation image spec: %w", err)
|
||||
}
|
||||
err = PushImageToRegistry(image, attOut.Identifier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to push image: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
package mirror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/attest/internal/test"
|
||||
"github.com/docker/attest/pkg/attest"
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/google/go-containerregistry/pkg/registry"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSavingIndex(t *testing.T) {
|
||||
UnsignedTestImage := filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
|
||||
outputLayout := test.CreateTempDir(t, "", "mirror-test")
|
||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||
require.NoError(t, err)
|
||||
|
||||
server := httptest.NewServer(registry.New())
|
||||
defer server.Close()
|
||||
|
||||
u, err := url.Parse(server.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
||||
output, err := oci.ParseImageSpecs(indexName)
|
||||
require.NoError(t, err)
|
||||
err = SaveIndex(output, attIdx.Index, indexName)
|
||||
require.NoError(t, err)
|
||||
|
||||
ociOutput, err := oci.ParseImageSpecs(oci.LocalPrefix + outputLayout)
|
||||
require.NoError(t, err)
|
||||
err = SaveIndex(ociOutput, attIdx.Index, indexName)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSavingImage(t *testing.T) {
|
||||
outputLayout := test.CreateTempDir(t, "", "mirror-test")
|
||||
|
||||
img := empty.Image
|
||||
|
||||
server := httptest.NewServer(registry.New())
|
||||
defer server.Close()
|
||||
|
||||
u, err := url.Parse(server.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
||||
output, err := oci.ParseImageSpec(indexName)
|
||||
require.NoError(t, err)
|
||||
err = SaveImage(output, img, indexName)
|
||||
require.NoError(t, err)
|
||||
|
||||
ociOutput, err := oci.ParseImageSpec(oci.LocalPrefix + outputLayout)
|
||||
require.NoError(t, err)
|
||||
err = SaveImage(ociOutput, img, indexName)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSavingReferrers(t *testing.T) {
|
||||
ctx, signer := test.Setup(t)
|
||||
opts := &attestation.SigningOptions{}
|
||||
statement := &intoto.Statement{
|
||||
StatementHeader: intoto.StatementHeader{
|
||||
PredicateType: attestation.VSAPredicateType,
|
||||
},
|
||||
}
|
||||
|
||||
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620")
|
||||
require.NoError(t, err)
|
||||
subject := &v1.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
Digest: digest,
|
||||
}
|
||||
manifest, err := attest.NewAttestationManifest(subject)
|
||||
require.NoError(t, err)
|
||||
err = manifest.AddAttestation(ctx, signer, statement, opts)
|
||||
require.NoError(t, err)
|
||||
server := httptest.NewServer(registry.New(registry.WithReferrersSupport(true)))
|
||||
defer server.Close()
|
||||
|
||||
u, err := url.Parse(server.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
||||
output, err := oci.ParseImageSpecs(indexName)
|
||||
require.NoError(t, err)
|
||||
err = SaveReferrers(manifest, output)
|
||||
require.NoError(t, err)
|
||||
|
||||
reg := &oci.MockRegistryResolver{
|
||||
Subject: subject,
|
||||
MockResolver: &oci.MockResolver{},
|
||||
ImageNameStr: indexName,
|
||||
}
|
||||
require.NoError(t, err)
|
||||
refResolver, err := oci.NewReferrersAttestationResolver(reg)
|
||||
require.NoError(t, err)
|
||||
attestations, err := refResolver.Attestations(ctx, attestation.VSAPredicateType)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, attestations, 1)
|
||||
}
|
||||
Reference in New Issue
Block a user