feature!: support for setting HTTP User-Agent header (#157)

* feature!: support for setting HTTP User-Agent header

* fix lint

* fix e2e

* refactor: move http.go to internal/util/useragent package and rename functions to Get and Set

* Move packages and use attest version
This commit is contained in:
James Carnegie
2024-09-09 14:22:17 +01:00
committed by GitHub
parent ed0ae8ecf6
commit b4e6767cc6
33 changed files with 307 additions and 340 deletions

View File

@@ -3,6 +3,7 @@
package oci_test
import (
"context"
"testing"
"github.com/docker/attest/internal/test"
@@ -20,11 +21,12 @@ func TestRegistryAuth(t *testing.T) {
{Image: "175142243308.dkr.ecr.us-east-1.amazonaws.com/e2e-test-image:latest"},
{Image: "docker/image-signer-verifier-test:latest"},
}
ctx := context.Background()
for _, tc := range testCases {
t.Run(tc.Image, func(t *testing.T) {
err := oci.PushIndexToRegistry(attIdx.Index, tc.Image)
err := oci.PushIndexToRegistry(ctx, attIdx.Index, tc.Image)
require.NoError(t, err)
_, err = oci.IndexFromRemote(tc.Image)
_, err = oci.IndexFromRemote(ctx, tc.Image)
require.NoError(t, err)
})
}

View File

@@ -1,27 +0,0 @@
package oci
import (
"net/http"
"github.com/hashicorp/go-cleanhttp"
)
type userAgentTransporter struct {
userAgent string
roundTripper http.RoundTripper
}
type Option = func(*http.Client)
func (u *userAgentTransporter) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", u.userAgent)
return u.roundTripper.RoundTrip(req)
}
func HTTPTransport() http.RoundTripper {
return &userAgentTransporter{
userAgent: "Docker-Client",
roundTripper: cleanhttp.DefaultTransport(),
}
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/containerd/platforms"
"github.com/distribution/reference"
"github.com/docker/attest/internal/useragent"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
@@ -33,7 +34,11 @@ func ParsePlatform(platformStr string) (*v1.Platform, error) {
func WithOptions(ctx context.Context, platform *v1.Platform) []remote.Option {
// prepare options
options := []remote.Option{MultiKeychainOption(), remote.WithTransport(HTTPTransport()), remote.WithContext(ctx)}
options := []remote.Option{
MultiKeychainOption(),
remote.WithContext(ctx),
remote.WithUserAgent(useragent.Get(ctx)),
}
// add in platform into remote Get operation; this might conflict with an explicit digest, but we are trying anyway
if platform != nil {

View File

@@ -1,6 +1,7 @@
package oci
import (
"context"
"fmt"
"os"
@@ -13,18 +14,18 @@ import (
)
// PushImageToRegistry pushes an image to the registry with the specified name.
func PushImageToRegistry(image v1.Image, imageName string) error {
func PushImageToRegistry(ctx context.Context, 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, MultiKeychainOption())
return remote.Write(ref, image, WithOptions(ctx, nil)...)
}
// PushIndexToRegistry pushes an index to the registry with the specified name.
func PushIndexToRegistry(index v1.ImageIndex, imageName string) error {
func PushIndexToRegistry(ctx context.Context, index v1.ImageIndex, imageName string) error {
// Parse the index name
ref, err := name.ParseReference(imageName)
if err != nil {
@@ -32,7 +33,7 @@ func PushIndexToRegistry(index v1.ImageIndex, imageName string) error {
}
// Push the index to the registry
return remote.WriteIndex(ref, index, MultiKeychainOption())
return remote.WriteIndex(ref, index, WithOptions(ctx, nil)...)
}
// SaveIndexAsOCILayout saves an image as an OCI layout to the specified path.
@@ -66,7 +67,7 @@ func SaveIndexAsOCILayout(image v1.ImageIndex, path string) error {
}
// SaveIndex saves an index to the specified outputs.
func SaveIndex(outputs []*ImageSpec, index v1.ImageIndex, indexName string) error {
func SaveIndex(ctx context.Context, outputs []*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 {
@@ -84,7 +85,7 @@ func SaveIndex(outputs []*ImageSpec, index v1.ImageIndex, indexName string) erro
return fmt.Errorf("failed to write signed image: %w", err)
}
} else {
err := PushIndexToRegistry(index, output.Identifier)
err := PushIndexToRegistry(ctx, index, output.Identifier)
if err != nil {
return fmt.Errorf("failed to push signed image: %w", err)
}
@@ -94,7 +95,7 @@ func SaveIndex(outputs []*ImageSpec, index v1.ImageIndex, indexName string) erro
}
// SaveImage saves an image to the specified output.
func SaveImage(output *ImageSpec, image v1.Image, imageName string) error {
func SaveImage(ctx context.Context, output *ImageSpec, image v1.Image, imageName string) error {
if output.Type == OCI {
idx := v1.ImageIndex(empty.Index)
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
@@ -110,7 +111,7 @@ func SaveImage(output *ImageSpec, image v1.Image, imageName string) error {
return fmt.Errorf("failed to write signed image: %w", err)
}
} else {
err := PushImageToRegistry(image, output.Identifier)
err := PushImageToRegistry(ctx, image, output.Identifier)
if err != nil {
return fmt.Errorf("failed to push signed image: %w", err)
}
@@ -119,7 +120,7 @@ func SaveImage(output *ImageSpec, image v1.Image, imageName string) error {
}
// SaveImagesNoTag saves a list of images by digest to the specified outputs.
func SaveImagesNoTag(images []v1.Image, outputs []*ImageSpec) error {
func SaveImagesNoTag(ctx context.Context, images []v1.Image, outputs []*ImageSpec) error {
for _, output := range outputs {
// OCI layout output not supported
if output.Type == OCI {
@@ -134,7 +135,7 @@ func SaveImagesNoTag(images []v1.Image, outputs []*ImageSpec) error {
if err != nil {
return fmt.Errorf("failed to create image spec: %w", err)
}
err = PushImageToRegistry(image, spec.Identifier)
err = PushImageToRegistry(ctx, image, spec.Identifier)
if err != nil {
return fmt.Errorf("failed to push image: %w", err)
}

View File

@@ -1,8 +1,8 @@
package oci_test
import (
"context"
"fmt"
"net/http/httptest"
"net/url"
"testing"
@@ -21,21 +21,22 @@ func TestSavingIndex(t *testing.T) {
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage(".."))
require.NoError(t, err)
server := httptest.NewServer(registry.New())
defer server.Close()
ctx := context.Background()
regServer := test.NewLocalRegistry(ctx)
defer regServer.Close()
u, err := url.Parse(server.URL)
u, err := url.Parse(regServer.URL)
require.NoError(t, err)
indexName := fmt.Sprintf("%s/repo:root", u.Host)
output, err := oci.ParseImageSpecs(indexName)
require.NoError(t, err)
err = oci.SaveIndex(output, attIdx.Index, indexName)
err = oci.SaveIndex(ctx, output, attIdx.Index, indexName)
require.NoError(t, err)
ociOutput, err := oci.ParseImageSpecs(oci.LocalPrefix + outputLayout)
require.NoError(t, err)
err = oci.SaveIndex(ociOutput, attIdx.Index, indexName)
err = oci.SaveIndex(ctx, ociOutput, attIdx.Index, indexName)
require.NoError(t, err)
}
@@ -44,21 +45,22 @@ func TestSavingImage(t *testing.T) {
img := empty.Image
server := httptest.NewServer(registry.New())
defer server.Close()
ctx := context.Background()
regServer := test.NewLocalRegistry(ctx)
defer regServer.Close()
u, err := url.Parse(server.URL)
u, err := url.Parse(regServer.URL)
require.NoError(t, err)
indexName := fmt.Sprintf("%s/repo:root", u.Host)
output, err := oci.ParseImageSpec(indexName)
require.NoError(t, err)
err = oci.SaveImage(output, img, indexName)
err = oci.SaveImage(ctx, output, img, indexName)
require.NoError(t, err)
ociOutput, err := oci.ParseImageSpec(oci.LocalPrefix + outputLayout)
require.NoError(t, err)
err = oci.SaveImage(ociOutput, img, indexName)
err = oci.SaveImage(ctx, ociOutput, img, indexName)
require.NoError(t, err)
}
@@ -81,10 +83,10 @@ func TestSavingReferrers(t *testing.T) {
require.NoError(t, err)
err = manifest.Add(ctx, signer, statement, opts)
require.NoError(t, err)
server := httptest.NewServer(registry.New(registry.WithReferrersSupport(true)))
defer server.Close()
regServer := test.NewLocalRegistry(ctx, registry.WithReferrersSupport(true))
defer regServer.Close()
u, err := url.Parse(server.URL)
u, err := url.Parse(regServer.URL)
require.NoError(t, err)
indexName := fmt.Sprintf("%s/repo:root", u.Host)
@@ -92,7 +94,7 @@ func TestSavingReferrers(t *testing.T) {
require.NoError(t, err)
artifacts, err := manifest.BuildReferringArtifacts()
require.NoError(t, err)
err = oci.SaveImagesNoTag(artifacts, output)
err = oci.SaveImagesNoTag(ctx, artifacts, output)
require.NoError(t, err)
reg := &attestation.MockRegistryResolver{

View File

@@ -2,6 +2,7 @@ package oci
import (
"bytes"
"context"
"encoding/json"
"fmt"
"strings"
@@ -61,14 +62,14 @@ func IndexFromPath(path string) (*NamedIndex, error) {
}, nil
}
func IndexFromRemote(image string) (*NamedIndex, error) {
func IndexFromRemote(ctx context.Context, image string) (*NamedIndex, error) {
ref, err := name.ParseReference(image)
if err != nil {
return nil, fmt.Errorf("failed to parse image reference %s: %w", image, err)
}
// Pull the image from the registry
idx, err := remote.Index(ref, MultiKeychainOption())
idx, err := remote.Index(ref, WithOptions(ctx, nil)...)
if err != nil {
return nil, fmt.Errorf("failed to pull image %s: %w", image, err)
}
@@ -78,11 +79,11 @@ func IndexFromRemote(image string) (*NamedIndex, error) {
}, nil
}
func LoadIndex(input *ImageSpec) (*NamedIndex, error) {
func LoadIndex(ctx context.Context, input *ImageSpec) (*NamedIndex, error) {
if input.Type == OCI {
return IndexFromPath(input.Identifier)
}
return IndexFromRemote(input.Identifier)
return IndexFromRemote(ctx, input.Identifier)
}
func (i *ImageSpec) ForPlatforms(platform string) ([]*ImageSpec, error) {