From 0b23be4ab832064102ea14f213e2401492d1edd1 Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Mon, 27 Oct 2025 14:34:49 -0500 Subject: [PATCH] libartifact: strong type where possible introduction of ArtifactReference and ArtifactStorageReference types for libartifact. ArtifactReference is a theoretical reference to an artifact like for things like pull. ArtifactStorageReference is for looking things up in the existing store for things like rm or inspect. ArtifactStoreReference allows for things like full or partial "id" lookups. the pr also accomdates a user ask for appending "latest" as a tag when no tag is provided. and finally, here we introduce a bunch of artifact tests to get the ball rolling. it should not be considered fully complete but a good start. Signed-off-by: Brent Baude --- common/go.mod | 1 + common/go.sum | 4 + .../pkg/libartifact/{ => store}/artifact.go | 36 +- common/pkg/libartifact/store/reference.go | 100 +++ .../pkg/libartifact/store/reference_test.go | 186 +++++ common/pkg/libartifact/store/store.go | 161 ++-- common/pkg/libartifact/store/store_test.go | 719 ++++++++++++++++++ go.work.sum | 192 +++++ vendor/github.com/pkg/errors/.gitignore | 24 + vendor/github.com/pkg/errors/.travis.yml | 10 + vendor/github.com/pkg/errors/LICENSE | 23 + vendor/github.com/pkg/errors/Makefile | 44 ++ vendor/github.com/pkg/errors/README.md | 59 ++ vendor/github.com/pkg/errors/appveyor.yml | 32 + vendor/github.com/pkg/errors/errors.go | 288 +++++++ vendor/github.com/pkg/errors/go113.go | 38 + vendor/github.com/pkg/errors/stack.go | 177 +++++ vendor/go.step.sm/crypto/LICENSE | 201 +++++ vendor/go.step.sm/crypto/randutil/random.go | 113 +++ vendor/modules.txt | 4 + 20 files changed, 2305 insertions(+), 107 deletions(-) rename common/pkg/libartifact/{ => store}/artifact.go (64%) create mode 100644 common/pkg/libartifact/store/reference.go create mode 100644 common/pkg/libartifact/store/reference_test.go create mode 100644 common/pkg/libartifact/store/store_test.go create mode 100644 vendor/github.com/pkg/errors/.gitignore create mode 100644 vendor/github.com/pkg/errors/.travis.yml create mode 100644 vendor/github.com/pkg/errors/LICENSE create mode 100644 vendor/github.com/pkg/errors/Makefile create mode 100644 vendor/github.com/pkg/errors/README.md create mode 100644 vendor/github.com/pkg/errors/appveyor.yml create mode 100644 vendor/github.com/pkg/errors/errors.go create mode 100644 vendor/github.com/pkg/errors/go113.go create mode 100644 vendor/github.com/pkg/errors/stack.go create mode 100644 vendor/go.step.sm/crypto/LICENSE create mode 100644 vendor/go.step.sm/crypto/randutil/random.go diff --git a/common/go.mod b/common/go.mod index d87f2b5b7d..2c74138d87 100644 --- a/common/go.mod +++ b/common/go.mod @@ -45,6 +45,7 @@ require ( go.etcd.io/bbolt v1.4.3 go.podman.io/image/v5 v5.38.0 go.podman.io/storage v1.61.0 + go.step.sm/crypto v0.57.0 golang.org/x/crypto v0.44.0 golang.org/x/sync v0.18.0 golang.org/x/sys v0.38.0 diff --git a/common/go.sum b/common/go.sum index aafd372cf1..93d0d200d3 100644 --- a/common/go.sum +++ b/common/go.sum @@ -254,6 +254,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/smallstep/pkcs7 v0.1.1 h1:x+rPdt2W088V9Vkjho4KtoggyktZJlMduZAtRHm68LU= github.com/smallstep/pkcs7 v0.1.1/go.mod h1:dL6j5AIz9GHjVEBTXtW+QliALcgM19RtXaTeyxI+AfA= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= @@ -325,6 +327,8 @@ go.podman.io/image/v5 v5.38.0 h1:aUKrCANkPvze1bnhLJsaubcfz0d9v/bSDLnwsXJm6G4= go.podman.io/image/v5 v5.38.0/go.mod h1:hSIoIUzgBnmc4DjoIdzk63aloqVbD7QXDMkSE/cvG90= go.podman.io/storage v1.61.0 h1:5hD/oyRYt1f1gxgvect+8syZBQhGhV28dCw2+CZpx0Q= go.podman.io/storage v1.61.0/go.mod h1:A3UBK0XypjNZ6pghRhuxg62+2NIm5lcUGv/7XyMhMUI= +go.step.sm/crypto v0.57.0 h1:YjoRQDaJYAxHLVwjst0Bl0xcnoKzVwuHCJtEo2VSHYU= +go.step.sm/crypto v0.57.0/go.mod h1:+Lwp5gOVPaTa3H/Ul/TzGbxQPXZZcKIUGMS0lG6n9Go= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= diff --git a/common/pkg/libartifact/artifact.go b/common/pkg/libartifact/store/artifact.go similarity index 64% rename from common/pkg/libartifact/artifact.go rename to common/pkg/libartifact/store/artifact.go index 2d5934c09d..83775a6bb1 100644 --- a/common/pkg/libartifact/artifact.go +++ b/common/pkg/libartifact/store/artifact.go @@ -1,4 +1,4 @@ -package libartifact +package store import ( "encoding/json" @@ -7,6 +7,7 @@ import ( "github.com/opencontainers/go-digest" "go.podman.io/common/pkg/libartifact/types" + "go.podman.io/image/v5/docker/reference" "go.podman.io/image/v5/manifest" ) @@ -36,7 +37,7 @@ func (a *Artifact) GetName() (string, error) { return "", types.ErrArtifactUnamed } -// SetName is a accessor for setting the artifact name +// SetName is an accessor for setting the artifact name // Note: long term this may not be needed, and we would // be comfortable with simply using the exported field // called Name. @@ -55,16 +56,16 @@ func (a *Artifact) GetDigest() (*digest.Digest, error) { type ArtifactList []*Artifact -// GetByNameOrDigest returns an artifact, if present, by a given name +// getByNameOrDigest returns an artifact, if present, by a given name // Returns an error if not found. -func (al ArtifactList) GetByNameOrDigest(nameOrDigest string) (*Artifact, bool, error) { +func (al ArtifactList) getByNameOrDigest(nameOrDigest string) (*Artifact, bool, error) { // This is the hot route through for _, artifact := range al { if artifact.Name == nameOrDigest { return artifact, false, nil } } - // Before giving up, check by digest + // Before giving up, check by full or partial ID for _, artifact := range al { artifactDigest, err := artifact.GetDigest() if err != nil { @@ -75,5 +76,30 @@ func (al ArtifactList) GetByNameOrDigest(nameOrDigest string) (*Artifact, bool, return artifact, true, nil } } + named, err := reference.ParseNamed(nameOrDigest) + if err != nil { + return nil, false, fmt.Errorf("invalid artifact: %q", nameOrDigest) + } + + // And finally, check for things with a name and digest + // i.e. quay.io/podman/machine-os:sha256:7e952f1deece2717022d7cc066dd21d1468236560d23a79c80448d49b2048e99 + if d, isDigested := named.(reference.Digested); isDigested { + for _, a := range al { + storedArtifactNamed, err := reference.ParseNamed(a.Name) + if err != nil { + return nil, false, err + } + if storedArtifactNamed.Name() == named.Name() { + artifactDigest, err := a.GetDigest() + if err != nil { + return nil, false, err + } + if d.Digest() == *artifactDigest { + return a, true, nil + } + } + } + } + // Nothing was found in the store that matches return nil, false, fmt.Errorf("%s: %w", nameOrDigest, types.ErrArtifactNotExist) } diff --git a/common/pkg/libartifact/store/reference.go b/common/pkg/libartifact/store/reference.go new file mode 100644 index 0000000000..a9c533de69 --- /dev/null +++ b/common/pkg/libartifact/store/reference.go @@ -0,0 +1,100 @@ +//go:build !remote + +package store + +import ( + "context" + + "go.podman.io/image/v5/docker/reference" +) + +type ArtifactReference struct { + reference.Named +} + +// NewArtifactReference is a theoretical reference to an artifact. It needs to be +// a fully qualified oci reference except for tag, where we add +// "latest" as the tag if tag is empty. Valid references: +// +// quay.io/podman/machine-os:latest +// quay.io/podman/machine-os +// quay.io/podman/machine-os@sha256:916ede4b2b9012f91f63100f8ba82d07ed81bf8a55d23c1503285a22a9759a1e +// +// Note: Partial sha references and digests (IDs) are not allowed. +func NewArtifactReference(input string) (ArtifactReference, error) { + ar := ArtifactReference{} + named, err := stringToNamed(input) + if err != nil { + return ArtifactReference{}, err + } + ar.Named = named + return ar, nil +} + +func (ar ArtifactReference) IsDigested() bool { + _, isDigested := ar.Named.(reference.Digested) + return isDigested +} + +type ArtifactStoreReference struct { + ArtifactFromStore *Artifact + IsDigested bool + Ref reference.Named +} + +// NewArtifactStorageReference refers to an object already in the artifact store. It +// can be a name or a full or partial digest. Conveniently, it also embeds the artifact +// as part of its return. +func NewArtifactStorageReference(nameOrDigest string, as *ArtifactStore) (ArtifactStoreReference, error) { + lookupInput := nameOrDigest + asf := ArtifactStoreReference{} + al, err := as.getArtifacts(context.Background(), nil) + if err != nil { + return ArtifactStoreReference{}, err + } + + // Try to parse as a valid OCI reference + named, parseErr := stringToNamed(nameOrDigest) + if parseErr == nil { + lookupInput = named.String() + } + + // Lookup in the store + a, isDigest, err := al.getByNameOrDigest(lookupInput) + if err != nil { + return ArtifactStoreReference{}, err + } + + // If parsing failed, parse the artifact's name instead + if parseErr != nil { + fqName, err := a.GetName() + if err != nil { + return ArtifactStoreReference{}, err + } + named, err = stringToNamed(fqName) + if err != nil { + return ArtifactStoreReference{}, err + } + } + + asf.Ref = named + asf.IsDigested = isDigest + asf.ArtifactFromStore = a + return asf, nil +} + +// stringToNamed converts a string to a reference.Named. +func stringToNamed(s string) (reference.Named, error) { + named, err := reference.ParseNamed(s) + if err != nil { + return ArtifactReference{}, err + } + // If the supplied input is neither tagged nor has + // a digest, then add "latest" + _, isTagged := named.(reference.Tagged) + _, isDigested := named.(reference.Digested) + if !isTagged && !isDigested { + named = reference.TagNameOnly(named) + } + return named, nil +} diff --git a/common/pkg/libartifact/store/reference_test.go b/common/pkg/libartifact/store/reference_test.go new file mode 100644 index 0000000000..4eb24e4582 --- /dev/null +++ b/common/pkg/libartifact/store/reference_test.go @@ -0,0 +1,186 @@ +package store + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.podman.io/image/v5/types" +) + +func TestNewArtifactReference(t *testing.T) { + // Test valid reference + ar, err := NewArtifactReference("quay.io/podman/machine-os:5.1") + assert.NoError(t, err) + assert.NotNil(t, ar.Named) + assert.Equal(t, "quay.io/podman/machine-os:5.1", ar.Named.String()) + + // Test another valid reference + ar, err = NewArtifactReference("docker.io/library/nginx:latest") + assert.NoError(t, err) + assert.NotNil(t, ar.Named) + + // Test invalid reference - empty string + _, err = NewArtifactReference("") + assert.Error(t, err) + + // Test invalid reference - malformed + _, err = NewArtifactReference("invalid::reference") + assert.Error(t, err) + + // Test latest is added when no tag is provided + ar, err = NewArtifactReference("quay.io/machine-os/podman") + assert.NoError(t, err) + assert.Equal(t, "quay.io/machine-os/podman:latest", ar.Named.String()) + + // Input with a digest is good + ar, err = NewArtifactReference("quay.io/machine-os/podman@sha256:8b96f36deaf1d2713858eebd9ef2fee9610df8452fbd083bbfa7dca66d6fcd0b") + assert.NoError(t, err) + assert.True(t, ar.IsDigested()) + + // Partial digests are a no-go + _, err = NewArtifactReference("quay.io/machine-os/podman@sha256:8b96f36deaf1d2") + assert.Error(t, err) + + // "IDs" are also a no-go + _, err = NewArtifactReference("84ddb405470e733d0202d6946e48fc75a7ee231337bdeb31a8579407a7052d9e") + assert.Error(t, err) +} + +func TestArtifactReference_IsDigested(t *testing.T) { + // Test reference with tag (not digested) + ar, err := NewArtifactReference("quay.io/podman/machine-os:5.1") + require.NoError(t, err) + assert.False(t, ar.IsDigested()) + + // Test reference with digest (digested) + ar, err = NewArtifactReference("quay.io/podman/machine-os@sha256:8b96f36deaf1d2713858eebd9ef2fee9610df8452fbd083bbfa7dca66d6fcd0b") + require.NoError(t, err) + assert.True(t, ar.IsDigested()) + + // Test reference with latest tag (not digested) + ar, err = NewArtifactReference("quay.io/podman/machine-os:latest") + require.NoError(t, err) + assert.False(t, ar.IsDigested()) +} + +func TestNewArtifactStorageReference_ValidReference(t *testing.T) { + repo := "quay.io/podman/machine-os" + tag := "5.1" + ref := fmt.Sprintf("%s:%s", repo, tag) + as, artifactDigest := setupNewStore(t, ref, nil, nil) + + // Test with a valid named reference - should find the artifact in the store + asr, err := NewArtifactStorageReference(ref, as) + assert.NoError(t, err) + assert.NotNil(t, asr.Ref) + assert.Equal(t, "quay.io/podman/machine-os:5.1", asr.Ref.String()) + assert.False(t, asr.IsDigested) + assert.NotNil(t, asr.ArtifactFromStore) + assert.Equal(t, "quay.io/podman/machine-os:5.1", asr.ArtifactFromStore.Name) + + // Lookup by Digest + asr, err = NewArtifactStorageReference(fmt.Sprintf("%s@%s", repo, artifactDigest.String()), as) + assert.NoError(t, err) + assert.NotNil(t, asr.ArtifactFromStore) + assert.True(t, asr.IsDigested) + assert.NotNil(t, asr.ArtifactFromStore) +} + +func TestNewArtifactStorageReference_AutoTagLatest(t *testing.T) { + repoNameOnly := "quay.io/podman/machine-os" + as, _ := setupNewStore(t, repoNameOnly, nil, nil) + + // Test with a reference without a tag (should auto-add :latest) + asr, err := NewArtifactStorageReference(repoNameOnly, as) + assert.NoError(t, err) + assert.NotNil(t, asr.Ref) + assert.Equal(t, fmt.Sprintf("%s:latest", repoNameOnly), asr.Ref.String()) + assert.False(t, asr.IsDigested) +} + +func TestNewArtifactStorageReference_InvalidReference(t *testing.T) { + storePath := filepath.Join(t.TempDir(), "store") + sc := &types.SystemContext{} + + // Create an artifact store + as, err := NewArtifactStore(storePath, sc) + require.NoError(t, err) + require.NotNil(t, as) + + // Test with an invalid reference that also doesn't exist in the store + // This should fail both as a reference parse and as a store lookup + _, err = NewArtifactStorageReference("nonexistent-digest-12345", as) + assert.Error(t, err) +} + +func TestNewArtifactStorageReference_EmptyString(t *testing.T) { + storePath := filepath.Join(t.TempDir(), "store") + sc := &types.SystemContext{} + + // Create an artifact store + as, err := NewArtifactStore(storePath, sc) + require.NoError(t, err) + require.NotNil(t, as) + + // Test with empty string + _, err = NewArtifactStorageReference("", as) + assert.Error(t, err) +} + +func TestStringToNamed(t *testing.T) { + // Test valid named reference + named, err := stringToNamed("quay.io/podman/machine-os:5.1") + assert.NoError(t, err) + assert.NotNil(t, named) + assert.Equal(t, "quay.io/podman/machine-os:5.1", named.String()) + + // Test reference without tag (should add :latest) + named, err = stringToNamed("quay.io/podman/machine-os") + assert.NoError(t, err) + assert.NotNil(t, named) + assert.Equal(t, "quay.io/podman/machine-os:latest", named.String()) + + // Test reference with digest + named, err = stringToNamed("quay.io/podman/machine-os@sha256:8b96f36deaf1d2713858eebd9ef2fee9610df8452fbd083bbfa7dca66d6fcd0b") + assert.NoError(t, err) + assert.NotNil(t, named) + assert.Equal(t, "quay.io/podman/machine-os@sha256:8b96f36deaf1d2713858eebd9ef2fee9610df8452fbd083bbfa7dca66d6fcd0b", named.String()) + + // Test invalid reference + _, err = stringToNamed("invalid::reference") + assert.Error(t, err) + + // Test empty string + _, err = stringToNamed("") + assert.Error(t, err) +} + +func TestNewArtifactStore(t *testing.T) { + // Test with valid absolute path + storePath := filepath.Join(t.TempDir(), "store") + sc := &types.SystemContext{} + + as, err := NewArtifactStore(storePath, sc) + assert.NoError(t, err) + assert.NotNil(t, as) + assert.Equal(t, storePath, as.storePath) + + // Verify the index file was created + indexPath := filepath.Join(storePath, "index.json") + _, err = os.Stat(indexPath) + assert.NoError(t, err) + + // Test with empty path + _, err = NewArtifactStore("", sc) + assert.Error(t, err) + assert.Contains(t, err.Error(), "store path cannot be empty") + + // Test with relative path + _, err = NewArtifactStore("relative/path", sc) + assert.Error(t, err) + assert.Contains(t, err.Error(), "must be absolute") +} diff --git a/common/pkg/libartifact/store/store.go b/common/pkg/libartifact/store/store.go index 0665cbe73c..d78c48aecb 100644 --- a/common/pkg/libartifact/store/store.go +++ b/common/pkg/libartifact/store/store.go @@ -23,7 +23,6 @@ import ( specV1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" "go.podman.io/common/libimage" - "go.podman.io/common/pkg/libartifact" libartTypes "go.podman.io/common/pkg/libartifact/types" "go.podman.io/image/v5/image" "go.podman.io/image/v5/manifest" @@ -84,32 +83,14 @@ func NewArtifactStore(storePath string, sc *types.SystemContext) (*ArtifactStore } // Remove an artifact from the local artifact store. -func (as ArtifactStore) Remove(ctx context.Context, name string) (*digest.Digest, error) { - if len(name) == 0 { - return nil, ErrEmptyArtifactName - } - +func (as ArtifactStore) Remove(ctx context.Context, ref ArtifactStoreReference) (*digest.Digest, error) { as.lock.Lock() defer as.lock.Unlock() - - // validate and see if the input is a digest - artifacts, err := as.getArtifacts(ctx, nil) - if err != nil { - return nil, err - } - - arty, nameIsDigest, err := artifacts.GetByNameOrDigest(name) - if err != nil { - return nil, err - } - if nameIsDigest { - name = arty.Name - } - ir, err := layout.NewReference(as.storePath, name) + ir, err := layout.NewReference(as.storePath, ref.ArtifactFromStore.Name) if err != nil { return nil, err } - artifactDigest, err := arty.GetDigest() + artifactDigest, err := ref.ArtifactFromStore.GetDigest() if err != nil { return nil, err } @@ -117,24 +98,17 @@ func (as ArtifactStore) Remove(ctx context.Context, name string) (*digest.Digest } // Inspect an artifact in a local store. -func (as ArtifactStore) Inspect(ctx context.Context, nameOrDigest string) (*libartifact.Artifact, error) { - if len(nameOrDigest) == 0 { - return nil, ErrEmptyArtifactName - } - +func (as ArtifactStore) Inspect(ctx context.Context, ref ArtifactStoreReference) (*Artifact, error) { as.lock.RLock() defer as.lock.Unlock() - artifacts, err := as.getArtifacts(ctx, nil) - if err != nil { - return nil, err - } - inspectData, _, err := artifacts.GetByNameOrDigest(nameOrDigest) - return inspectData, err + // Note: keeping the error here so in case something changes + // signatures won't have to change + return ref.ArtifactFromStore, nil } // List artifacts in the local store. -func (as ArtifactStore) List(ctx context.Context) (libartifact.ArtifactList, error) { +func (as ArtifactStore) List(ctx context.Context) (ArtifactList, error) { as.lock.RLock() defer as.lock.Unlock() @@ -142,11 +116,8 @@ func (as ArtifactStore) List(ctx context.Context) (libartifact.ArtifactList, err } // Pull an artifact from an image registry to a local store. -func (as ArtifactStore) Pull(ctx context.Context, name string, opts libimage.CopyOptions) (digest.Digest, error) { - if len(name) == 0 { - return "", ErrEmptyArtifactName - } - srcRef, err := alltransports.ParseImageName("docker://" + name) +func (as ArtifactStore) Pull(ctx context.Context, ref ArtifactReference, opts libimage.CopyOptions) (digest.Digest, error) { + srcRef, err := alltransports.ParseImageName("docker://" + ref.String()) if err != nil { return "", err } @@ -154,7 +125,7 @@ func (as ArtifactStore) Pull(ctx context.Context, name string, opts libimage.Cop as.lock.Lock() defer as.lock.Unlock() - destRef, err := layout.NewReference(as.storePath, name) + destRef, err := layout.NewReference(as.storePath, ref.String()) if err != nil { return "", err } @@ -174,11 +145,8 @@ func (as ArtifactStore) Pull(ctx context.Context, name string, opts libimage.Cop } // Push an artifact to an image registry. -func (as ArtifactStore) Push(ctx context.Context, src, dest string, opts libimage.CopyOptions) (digest.Digest, error) { - if len(dest) == 0 { - return "", ErrEmptyArtifactName - } - destRef, err := alltransports.ParseImageName("docker://" + dest) +func (as ArtifactStore) Push(ctx context.Context, src ArtifactStoreReference, dest ArtifactReference, opts libimage.CopyOptions) (digest.Digest, error) { + destRef, err := alltransports.ParseImageName("docker://" + dest.String()) if err != nil { return "", err } @@ -186,7 +154,7 @@ func (as ArtifactStore) Push(ctx context.Context, src, dest string, opts libimag as.lock.Lock() defer as.lock.Unlock() - srcRef, err := layout.NewReference(as.storePath, src) + srcRef, err := layout.NewReference(as.storePath, src.ArtifactFromStore.Name) if err != nil { return "", err } @@ -209,11 +177,7 @@ func (as ArtifactStore) Push(ctx context.Context, src, dest string, opts libimag // Add takes one or more artifact blobs and add them to the local artifact store. The empty // string input is for possible custom artifact types. -func (as ArtifactStore) Add(ctx context.Context, dest string, artifactBlobs []libartTypes.ArtifactBlob, options *libartTypes.AddOptions) (*digest.Digest, error) { - if len(dest) == 0 { - return nil, ErrEmptyArtifactName - } - +func (as ArtifactStore) Add(ctx context.Context, dest ArtifactReference, artifactBlobs []libartTypes.ArtifactBlob, options *libartTypes.AddOptions) (*digest.Digest, error) { if options.Append && len(options.ArtifactMIMEType) > 0 { return nil, errors.New("append option is not compatible with type option") } @@ -239,9 +203,17 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, artifactBlobs []li if !options.Append { // Check if artifact exists; in GetByName not getting an // error means it exists - _, _, err := artifacts.GetByNameOrDigest(dest) + existingArtifact, _, err := artifacts.getByNameOrDigest(dest.String()) if err == nil { - return nil, fmt.Errorf("%s: %w", dest, libartTypes.ErrArtifactAlreadyExists) + // Artifact exists + if !options.Replace { + return nil, fmt.Errorf("%s: %w", dest, libartTypes.ErrArtifactAlreadyExists) + } + // Replace mode: remove the existing artifact first + oldDigest, err = existingArtifact.GetDigest() + if err != nil { + return nil, err + } } // Set creation timestamp and other annotations @@ -261,7 +233,7 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, artifactBlobs []li Annotations: annotations, } } else { - artifact, _, err := artifacts.GetByNameOrDigest(dest) + artifact, _, err := artifacts.getByNameOrDigest(dest.String()) if err != nil { return nil, err } @@ -286,7 +258,7 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, artifactBlobs []li fileNames[fileName] = struct{}{} } - ir, err := layout.NewReference(as.storePath, dest) + ir, err := layout.NewReference(as.storePath, dest.String()) if err != nil { return nil, err } @@ -309,7 +281,10 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, artifactBlobs []li return nil, errors.New("Artifact.BlobFile or Artifact.BlobReader must be provided") } - annotations := maps.Clone(options.Annotations) + annotations := make(map[string]string) + if options.Annotations != nil { + annotations = maps.Clone(options.Annotations) + } if title, ok := annotations[specV1.AnnotationTitle]; ok { // Verify a duplicate AnnotationTitle is not in use in a different layer. for _, layer := range artifactManifest.Layers { @@ -403,50 +378,32 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, artifactBlobs []li return &artifactManifestDigest, nil } -func getArtifactAndImageSource(ctx context.Context, as ArtifactStore, nameOrDigest string, options *libartTypes.FilterBlobOptions) (*libartifact.Artifact, types.ImageSource, error) { +func (ref ArtifactStoreReference) toLocalImageSource(ctx context.Context, as ArtifactStore, options *libartTypes.FilterBlobOptions) (types.ImageSource, error) { if len(options.Digest) > 0 && len(options.Title) > 0 { - return nil, nil, errors.New("cannot specify both digest and title") - } - if len(nameOrDigest) == 0 { - return nil, nil, ErrEmptyArtifactName + return nil, errors.New("cannot specify both digest and title") } - artifacts, err := as.getArtifacts(ctx, nil) - if err != nil { - return nil, nil, err + if len(ref.ArtifactFromStore.Manifest.Layers) == 0 { + return nil, errors.New("the artifact has no blobs, nothing to extract") } - arty, nameIsDigest, err := artifacts.GetByNameOrDigest(nameOrDigest) + ir, err := layout.NewReference(as.storePath, ref.ArtifactFromStore.Name) if err != nil { - return nil, nil, err - } - name := nameOrDigest - if nameIsDigest { - name = arty.Name - } - - if len(arty.Manifest.Layers) == 0 { - return nil, nil, errors.New("the artifact has no blobs, nothing to extract") - } - - ir, err := layout.NewReference(as.storePath, name) - if err != nil { - return nil, nil, err + return nil, err } - imgSrc, err := ir.NewImageSource(ctx, as.SystemContext) - return arty, imgSrc, err + return ir.NewImageSource(ctx, as.SystemContext) } // BlobMountPaths allows the caller to access the file names from the store and how they should be mounted. -func (as ArtifactStore) BlobMountPaths(ctx context.Context, nameOrDigest string, options *libartTypes.BlobMountPathOptions) ([]libartTypes.BlobMountPath, error) { - arty, imgSrc, err := getArtifactAndImageSource(ctx, as, nameOrDigest, &options.FilterBlobOptions) +func (as ArtifactStore) BlobMountPaths(ctx context.Context, ref ArtifactStoreReference, options *libartTypes.BlobMountPathOptions) ([]libartTypes.BlobMountPath, error) { + imgSrc, err := ref.toLocalImageSource(ctx, as, &options.FilterBlobOptions) if err != nil { return nil, err } defer imgSrc.Close() if len(options.Digest) > 0 || len(options.Title) > 0 { - digest, err := findDigest(arty, &options.FilterBlobOptions) + digest, err := findDigest(ref.ArtifactFromStore, &options.FilterBlobOptions) if err != nil { return nil, err } @@ -469,8 +426,8 @@ func (as ArtifactStore) BlobMountPaths(ctx context.Context, nameOrDigest string, }}, nil } - mountPaths := make([]libartTypes.BlobMountPath, 0, len(arty.Manifest.Layers)) - for _, l := range arty.Manifest.Layers { + mountPaths := make([]libartTypes.BlobMountPath, 0, len(ref.ArtifactFromStore.Manifest.Layers)) + for _, l := range ref.ArtifactFromStore.Manifest.Layers { title := l.Annotations[specV1.AnnotationTitle] for _, mp := range mountPaths { if title == mp.Name { @@ -495,8 +452,8 @@ func (as ArtifactStore) BlobMountPaths(ctx context.Context, nameOrDigest string, } // Extract an artifact to local file or directory. -func (as ArtifactStore) Extract(ctx context.Context, nameOrDigest string, target string, options *libartTypes.ExtractOptions) error { - arty, imgSrc, err := getArtifactAndImageSource(ctx, as, nameOrDigest, &options.FilterBlobOptions) +func (as ArtifactStore) Extract(ctx context.Context, ref ArtifactStoreReference, target string, options *libartTypes.ExtractOptions) error { + imgSrc, err := ref.toLocalImageSource(ctx, as, &options.FilterBlobOptions) if err != nil { return err } @@ -513,23 +470,23 @@ func (as ArtifactStore) Extract(ctx context.Context, nameOrDigest string, target if destIsFile { var digest digest.Digest - if len(arty.Manifest.Layers) > 1 { + if len(ref.ArtifactFromStore.Manifest.Layers) > 1 { if len(options.Digest) == 0 && len(options.Title) == 0 { return fmt.Errorf("the artifact consists of several blobs and the target %q is not a directory and neither digest or title was specified to only copy a single blob", target) } - digest, err = findDigest(arty, &options.FilterBlobOptions) + digest, err = findDigest(ref.ArtifactFromStore, &options.FilterBlobOptions) if err != nil { return err } } else { - digest = arty.Manifest.Layers[0].Digest + digest = ref.ArtifactFromStore.Manifest.Layers[0].Digest } return copyTrustedImageBlobToFile(ctx, imgSrc, digest, target) } if len(options.Digest) > 0 || len(options.Title) > 0 { - digest, err := findDigest(arty, &options.FilterBlobOptions) + digest, err := findDigest(ref.ArtifactFromStore, &options.FilterBlobOptions) if err != nil { return err } @@ -545,7 +502,7 @@ func (as ArtifactStore) Extract(ctx context.Context, nameOrDigest string, target return copyTrustedImageBlobToFile(ctx, imgSrc, digest, filepath.Join(target, filename)) } - for _, l := range arty.Manifest.Layers { + for _, l := range ref.ArtifactFromStore.Manifest.Layers { title := l.Annotations[specV1.AnnotationTitle] filename, err := generateArtifactBlobName(title, l.Digest) if err != nil { @@ -562,12 +519,12 @@ func (as ArtifactStore) Extract(ctx context.Context, nameOrDigest string, target } // Extract an artifact to tar stream. -func (as ArtifactStore) ExtractTarStream(ctx context.Context, w io.Writer, nameOrDigest string, options *libartTypes.ExtractOptions) error { +func (as ArtifactStore) ExtractTarStream(ctx context.Context, w io.Writer, ref ArtifactStoreReference, options *libartTypes.ExtractOptions) error { if options == nil { options = &libartTypes.ExtractOptions{} } - arty, imgSrc, err := getArtifactAndImageSource(ctx, as, nameOrDigest, &options.FilterBlobOptions) + imgSrc, err := ref.toLocalImageSource(ctx, as, &options.FilterBlobOptions) if err != nil { return err } @@ -575,7 +532,7 @@ func (as ArtifactStore) ExtractTarStream(ctx context.Context, w io.Writer, nameO // Return early if only a single blob is requested via title or digest if len(options.Digest) > 0 || len(options.Title) > 0 { - digest, err := findDigest(arty, &options.FilterBlobOptions) + digest, err := findDigest(ref.ArtifactFromStore, &options.FilterBlobOptions) if err != nil { return err } @@ -603,7 +560,7 @@ func (as ArtifactStore) ExtractTarStream(ctx context.Context, w io.Writer, nameO return nil } - artifactBlobCount := len(arty.Manifest.Layers) + artifactBlobCount := len(ref.ArtifactFromStore.Manifest.Layers) type blob struct { name string @@ -612,7 +569,7 @@ func (as ArtifactStore) ExtractTarStream(ctx context.Context, w io.Writer, nameO blobs := make([]blob, 0, artifactBlobCount) // Gather blob details and return error on any illegal names - for _, l := range arty.Manifest.Layers { + for _, l := range ref.ArtifactFromStore.Manifest.Layers { title := l.Annotations[specV1.AnnotationTitle] digest := l.Digest var name string @@ -666,7 +623,7 @@ func generateArtifactBlobName(title string, digest digest.Digest) (string, error return filename, nil } -func findDigest(arty *libartifact.Artifact, options *libartTypes.FilterBlobOptions) (digest.Digest, error) { +func findDigest(arty *Artifact, options *libartTypes.FilterBlobOptions) (digest.Digest, error) { var digest digest.Digest for _, l := range arty.Manifest.Layers { if options.Digest == l.Digest.String() { @@ -780,8 +737,8 @@ func (as ArtifactStore) indexPath() string { // getArtifacts returns an ArtifactList based on the artifact's store. The return error and // unused opts is meant for future growth like filters, etc so the API does not change. -func (as ArtifactStore) getArtifacts(ctx context.Context, _ *libartTypes.GetArtifactOptions) (libartifact.ArtifactList, error) { - var al libartifact.ArtifactList +func (as ArtifactStore) getArtifacts(ctx context.Context, _ *libartTypes.GetArtifactOptions) (ArtifactList, error) { + var al ArtifactList lrs, err := layout.List(as.storePath) if err != nil { @@ -797,7 +754,7 @@ func (as ArtifactStore) getArtifacts(ctx context.Context, _ *libartTypes.GetArti if err != nil { return nil, err } - artifact := libartifact.Artifact{ + artifact := Artifact{ Manifest: manifest, } if val, ok := l.ManifestDescriptor.Annotations[specV1.AnnotationRefName]; ok { diff --git a/common/pkg/libartifact/store/store_test.go b/common/pkg/libartifact/store/store_test.go new file mode 100644 index 0000000000..ea344c6d02 --- /dev/null +++ b/common/pkg/libartifact/store/store_test.go @@ -0,0 +1,719 @@ +package store + +import ( + "bytes" + "context" + "crypto/rand" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/opencontainers/go-digest" + specV1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + libartTypes "go.podman.io/common/pkg/libartifact/types" + "go.podman.io/image/v5/types" + "go.step.sm/crypto/randutil" +) + +// setupNewStore is a test helper and wrapper to helperAddArtifact. Use +// this approach when you don't care what populates the store in terms +// of the filenames. +func setupNewStore(t *testing.T, refName string, fileNames map[string]int, options *libartTypes.AddOptions) (*ArtifactStore, *digest.Digest) { + storePath := filepath.Join(t.TempDir(), "store") + sc := &types.SystemContext{} + + // Create the store first + as, err := NewArtifactStore(storePath, sc) + require.NoError(t, err) + require.NotNil(t, as) + + d := helperAddArtifact(t, as, refName, fileNames, options) + require.NotEmpty(t, d) + return as, d +} + +// helperAddArtifact is a test helper that adds an artifact to the store. +// It creates temporary files with random content and adds them as blobs. +// fileNames maps filename to size in bytes of random content to generate. +// If options is nil, uses a default with ArtifactMIMEType set to "application/vnd.test+type". +func helperAddArtifact(t *testing.T, as *ArtifactStore, refName string, fileNames map[string]int, options *libartTypes.AddOptions) *digest.Digest { + t.Helper() + ctx := context.Background() + + // If options is nil, create default options + if options == nil { + options = &libartTypes.AddOptions{ + ArtifactMIMEType: "application/vnd.test+type", + } + } + + // if no specific files were passed, create a random file of 2k + if fileNames == nil { + filename, err := randutil.Alphanumeric(5) + assert.NoError(t, err) + fileNames = map[string]int{ + filename: 2, + } + } + + // Create artifact reference + ref, err := NewArtifactReference(refName) + require.NoError(t, err) + + // Create temporary files and artifact blobs + tempDir := t.TempDir() + blobs := make([]libartTypes.ArtifactBlob, 0, len(fileNames)) + + for fileName, size := range fileNames { + // Generate random content + content := make([]byte, size) + _, err := rand.Read(content) + require.NoError(t, err) + + filePath := filepath.Join(tempDir, fileName) + err = os.WriteFile(filePath, content, 0o644) + require.NoError(t, err) + + blobs = append(blobs, libartTypes.ArtifactBlob{ + BlobFilePath: filePath, + FileName: fileName, + }) + } + + // Add artifact + artifactDigest, err := as.Add(ctx, ref, blobs, options) + require.NoError(t, err) + require.NotNil(t, artifactDigest) + + return artifactDigest +} + +func TestArtifactStore_Add(t *testing.T) { + ctx := context.Background() + storePath := filepath.Join(t.TempDir(), "store") + sc := &types.SystemContext{} + + as, err := NewArtifactStore(storePath, sc) + require.NoError(t, err) + require.NotNil(t, as) + + // Add artifact using helper with nil options (uses default) + fileNames := map[string]int{ + "testfile.txt": 1024, + } + + refName := "quay.io/test/artifact:v1" + artifactDigest := helperAddArtifact(t, as, refName, fileNames, nil) + assert.NotEmpty(t, artifactDigest.String()) + + // Verify artifact was added to the store + artifacts, err := as.List(ctx) + require.NoError(t, err) + assert.Len(t, artifacts, 1) + + // Verify artifact properties + artifact := artifacts[0] + assert.Equal(t, refName, artifact.Name) + assert.Equal(t, "application/vnd.test+type", artifact.Manifest.ArtifactType) + assert.Len(t, artifact.Manifest.Layers, 1) + + // Append another file to the same artifact + appendFileNames := map[string]int{ + "appended.txt": 512, + } + appendOptions := &libartTypes.AddOptions{ + Append: true, + } + + appendDigest := helperAddArtifact(t, as, refName, appendFileNames, appendOptions) + assert.NotEmpty(t, appendDigest.String()) + + // Verify artifact now has 2 layers + artifacts, err = as.List(ctx) + require.NoError(t, err) + assert.Len(t, artifacts, 1) + + artifact = artifacts[0] + assert.Len(t, artifact.Manifest.Layers, 2) + + // Verify both files are present + foundFiles := make(map[string]bool) + for _, layer := range artifact.Manifest.Layers { + title := layer.Annotations[specV1.AnnotationTitle] + foundFiles[title] = true + } + assert.True(t, foundFiles["testfile.txt"]) + assert.True(t, foundFiles["appended.txt"]) + + // Replace the artifact with a completely new one + replaceFileNames := map[string]int{ + "replacement.bin": 2048, + } + replaceOptions := &libartTypes.AddOptions{ + Replace: true, + ArtifactMIMEType: "application/vnd.replaced+type", + } + + replaceDigest := helperAddArtifact(t, as, refName, replaceFileNames, replaceOptions) + assert.NotEmpty(t, replaceDigest.String()) + + // Verify artifact was replaced with only the new file + artifacts, err = as.List(ctx) + require.NoError(t, err) + assert.Len(t, artifacts, 1) + + artifact = artifacts[0] + assert.Len(t, artifact.Manifest.Layers, 1) + assert.Equal(t, "application/vnd.replaced+type", artifact.Manifest.ArtifactType) + + // Verify only the replacement file is present + assert.Equal(t, "replacement.bin", artifact.Manifest.Layers[0].Annotations[specV1.AnnotationTitle]) +} + +func TestArtifactStore_Add_MultipleFiles(t *testing.T) { + ctx := context.Background() + storePath := filepath.Join(t.TempDir(), "store") + sc := &types.SystemContext{} + + as, err := NewArtifactStore(storePath, sc) + require.NoError(t, err) + require.NotNil(t, as) + + // Add artifact with multiple files using helper with nil options (uses default) + fileNames := map[string]int{ + "file1.txt": 512, + "file2.bin": 1024, + "file3.dat": 2048, + } + refName := "quay.io/test/multifile:v1" + artifactDigest := helperAddArtifact(t, as, refName, fileNames, nil) + assert.NotEmpty(t, artifactDigest.String()) + + // Verify artifact was added to the store + artifacts, err := as.List(ctx) + require.NoError(t, err) + assert.Len(t, artifacts, 1) + + // Verify artifact has 3 layers + artifact := artifacts[0] + assert.Equal(t, refName, artifact.Name) + assert.Equal(t, "application/vnd.test+type", artifact.Manifest.ArtifactType) + assert.Len(t, artifact.Manifest.Layers, 3) + + // Verify all file names are present in layer annotations + foundFiles := make(map[string]bool) + for _, layer := range artifact.Manifest.Layers { + title := layer.Annotations[specV1.AnnotationTitle] + foundFiles[title] = true + } + assert.True(t, foundFiles["file1.txt"], "file1.txt should be present") + assert.True(t, foundFiles["file2.bin"], "file2.bin should be present") + assert.True(t, foundFiles["file3.dat"], "file3.dat should be present") + + // Verify layer sizes match expected sizes + for _, layer := range artifact.Manifest.Layers { + title := layer.Annotations[specV1.AnnotationTitle] + expectedSize := int64(fileNames[title]) + assert.Equal(t, expectedSize, layer.Size, "Layer size for %s should match", title) + } +} + +func TestArtifactStore_Add_CustomMIMEType(t *testing.T) { + ctx := context.Background() + storePath := filepath.Join(t.TempDir(), "store") + sc := &types.SystemContext{} + + as, err := NewArtifactStore(storePath, sc) + require.NoError(t, err) + require.NotNil(t, as) + + // Add artifact with custom MIME type + fileNames := map[string]int{ + "config.json": 256, + } + options := &libartTypes.AddOptions{ + ArtifactMIMEType: "application/vnd.custom+json", + } + + artifactDigest := helperAddArtifact(t, as, "quay.io/test/custom:v1", fileNames, options) + assert.NotEmpty(t, artifactDigest.String()) + + // Verify artifact uses custom MIME type + artifacts, err := as.List(ctx) + require.NoError(t, err) + assert.Len(t, artifacts, 1) + + artifact := artifacts[0] + assert.Equal(t, "application/vnd.custom+json", artifact.Manifest.ArtifactType) +} + +func TestArtifactStore_Remove(t *testing.T) { + ctx := context.Background() + storePath := filepath.Join(t.TempDir(), "store") + sc := &types.SystemContext{} + + as, err := NewArtifactStore(storePath, sc) + require.NoError(t, err) + require.NotNil(t, as) + + // Add multiple artifacts + fileNames1 := map[string]int{ + "file1.txt": 1024, + } + helperAddArtifact(t, as, "quay.io/test/artifact1:v1", fileNames1, nil) + + fileNames2 := map[string]int{ + "file2.txt": 2048, + } + helperAddArtifact(t, as, "quay.io/test/artifact2:v1", fileNames2, nil) + + // Verify both artifacts exist + artifacts, err := as.List(ctx) + require.NoError(t, err) + assert.Len(t, artifacts, 2) + + // Get the first artifact and create a reference with it + artifact1 := artifacts[0] + digest1, err := artifact1.GetDigest() + require.NoError(t, err) + + // Remove the first artifact by digest + ref, err := NewArtifactStorageReference(digest1.Encoded(), as) + require.NoError(t, err) + + removedDigest, err := as.Remove(ctx, ref) + require.NoError(t, err) + require.NotNil(t, removedDigest) + assert.NotEmpty(t, removedDigest.String()) + + // Verify only one artifact remains + artifacts, err = as.List(ctx) + require.NoError(t, err) + assert.Len(t, artifacts, 1) + + // Get the remaining artifact + artifact2 := artifacts[0] + digest2, err := artifact2.GetDigest() + require.NoError(t, err) + + // Remove the second artifact by digest + ref2, err := NewArtifactStorageReference(digest2.Encoded(), as) + require.NoError(t, err) + + removedDigest2, err := as.Remove(ctx, ref2) + require.NoError(t, err) + require.NotNil(t, removedDigest2) + + // Verify store is now empty + artifacts, err = as.List(ctx) + require.NoError(t, err) + assert.Empty(t, artifacts) +} + +func TestArtifactStore_Inspect(t *testing.T) { + ctx := context.Background() + storePath := filepath.Join(t.TempDir(), "store") + sc := &types.SystemContext{} + + as, err := NewArtifactStore(storePath, sc) + require.NoError(t, err) + require.NotNil(t, as) + + // Add an artifact with multiple files + fileNames := map[string]int{ + "file1.txt": 512, + "file2.bin": 1024, + "file3.dat": 2048, + } + options := &libartTypes.AddOptions{ + ArtifactMIMEType: "application/vnd.test+type", + Annotations: map[string]string{ + "custom.annotation": "test-value", + }, + } + + refName := "quay.io/test/inspect:v1" + helperAddArtifact(t, as, refName, fileNames, options) + + // Get the artifact from the list + artifacts, err := as.List(ctx) + require.NoError(t, err) + require.Len(t, artifacts, 1) + + // Create a reference using the artifact's digest + artifact := artifacts[0] + digest, err := artifact.GetDigest() + require.NoError(t, err) + + ref, err := NewArtifactStorageReference(digest.Encoded(), as) + require.NoError(t, err) + + // Inspect the artifact + inspectedArtifact, err := as.Inspect(ctx, ref) + require.NoError(t, err) + require.NotNil(t, inspectedArtifact) + + // Verify inspected artifact properties + assert.Equal(t, refName, inspectedArtifact.Name) + assert.Equal(t, "application/vnd.test+type", inspectedArtifact.Manifest.ArtifactType) + assert.Len(t, inspectedArtifact.Manifest.Layers, 3) + + // Verify custom annotation is present + assert.Equal(t, "test-value", inspectedArtifact.Manifest.Annotations["custom.annotation"]) + + // Verify all files are present in layers + foundFiles := make(map[string]int64) + for _, layer := range inspectedArtifact.Manifest.Layers { + title := layer.Annotations[specV1.AnnotationTitle] + foundFiles[title] = layer.Size + } + assert.Equal(t, int64(512), foundFiles["file1.txt"]) + assert.Equal(t, int64(1024), foundFiles["file2.bin"]) + assert.Equal(t, int64(2048), foundFiles["file3.dat"]) + + // Verify total size calculation + totalSize := inspectedArtifact.TotalSizeBytes() + expectedTotal := int64(512 + 1024 + 2048) + assert.Equal(t, expectedTotal, totalSize) +} + +func TestArtifactStore_Extract(t *testing.T) { + ctx := context.Background() + storePath := filepath.Join(t.TempDir(), "store") + sc := &types.SystemContext{} + + as, err := NewArtifactStore(storePath, sc) + require.NoError(t, err) + require.NotNil(t, as) + + // Add an artifact with multiple files + fileNames := map[string]int{ + "file1.txt": 512, + "file2.bin": 1024, + "file3.dat": 2048, + } + + helperAddArtifact(t, as, "quay.io/test/extract:v1", fileNames, nil) + + // Get the artifact from the list + artifacts, err := as.List(ctx) + require.NoError(t, err) + require.Len(t, artifacts, 1) + + // Create a reference using the artifact's digest + artifact := artifacts[0] + digest, err := artifact.GetDigest() + require.NoError(t, err) + + ref, err := NewArtifactStorageReference(digest.Encoded(), as) + require.NoError(t, err) + + // Extract to a directory + extractDir := t.TempDir() + err = as.Extract(ctx, ref, extractDir, &libartTypes.ExtractOptions{}) + require.NoError(t, err) + + // Verify all files were extracted + extractedFile1 := filepath.Join(extractDir, "file1.txt") + extractedFile2 := filepath.Join(extractDir, "file2.bin") + extractedFile3 := filepath.Join(extractDir, "file3.dat") + + stat1, err := os.Stat(extractedFile1) + require.NoError(t, err) + assert.Equal(t, int64(512), stat1.Size()) + + stat2, err := os.Stat(extractedFile2) + require.NoError(t, err) + assert.Equal(t, int64(1024), stat2.Size()) + + stat3, err := os.Stat(extractedFile3) + require.NoError(t, err) + assert.Equal(t, int64(2048), stat3.Size()) + + // Verify file contents are not empty (random data was written) + content1, err := os.ReadFile(extractedFile1) + require.NoError(t, err) + assert.Len(t, content1, 512) + + content2, err := os.ReadFile(extractedFile2) + require.NoError(t, err) + assert.Len(t, content2, 1024) + + content3, err := os.ReadFile(extractedFile3) + require.NoError(t, err) + assert.Len(t, content3, 2048) +} + +func TestArtifactStore_Extract_SingleFile(t *testing.T) { + ctx := context.Background() + storePath := filepath.Join(t.TempDir(), "store") + sc := &types.SystemContext{} + + as, err := NewArtifactStore(storePath, sc) + require.NoError(t, err) + require.NotNil(t, as) + + // Add an artifact with multiple files + fileNames := map[string]int{ + "file1.txt": 512, + "file2.bin": 1024, + } + + helperAddArtifact(t, as, "quay.io/test/extract-single:v1", fileNames, nil) + + // Get the artifact from the list + artifacts, err := as.List(ctx) + require.NoError(t, err) + require.Len(t, artifacts, 1) + + // Create a reference using the artifact's digest + artifact := artifacts[0] + digest, err := artifact.GetDigest() + require.NoError(t, err) + + ref, err := NewArtifactStorageReference(digest.Encoded(), as) + require.NoError(t, err) + + // Extract only one file by title + extractDir := t.TempDir() + err = as.Extract(ctx, ref, extractDir, &libartTypes.ExtractOptions{ + FilterBlobOptions: libartTypes.FilterBlobOptions{ + Title: "file1.txt", + }, + }) + require.NoError(t, err) + + // Verify only file1.txt was extracted + extractedFile1 := filepath.Join(extractDir, "file1.txt") + extractedFile2 := filepath.Join(extractDir, "file2.bin") + + stat1, err := os.Stat(extractedFile1) + require.NoError(t, err) + assert.Equal(t, int64(512), stat1.Size()) + + _, err = os.Stat(extractedFile2) + assert.True(t, os.IsNotExist(err)) +} + +func TestArtifactStore_List_Multiple(t *testing.T) { + ctx := context.Background() + storePath := filepath.Join(t.TempDir(), "store") + sc := &types.SystemContext{} + + as, err := NewArtifactStore(storePath, sc) + require.NoError(t, err) + require.NotNil(t, as) + + // Verify empty store returns empty list + artifacts, err := as.List(ctx) + require.NoError(t, err) + assert.Empty(t, artifacts) + + // Add multiple artifacts with different configurations + fileNames1 := map[string]int{ + "file1.txt": 512, + } + helperAddArtifact(t, as, "quay.io/test/artifact1:v1", fileNames1, nil) + + fileNames2 := map[string]int{ + "file2a.bin": 1024, + "file2b.dat": 2048, + } + options2 := &libartTypes.AddOptions{ + ArtifactMIMEType: "application/vnd.custom+type", + } + helperAddArtifact(t, as, "quay.io/test/artifact2:v2", fileNames2, options2) + + fileNames3 := map[string]int{ + "file3.json": 256, + } + helperAddArtifact(t, as, "docker.io/library/artifact3:latest", fileNames3, nil) + + // List all artifacts + artifacts, err = as.List(ctx) + require.NoError(t, err) + assert.Len(t, artifacts, 3) + + // Create a map of artifact names for easy lookup + artifactMap := make(map[string]*Artifact) + for _, artifact := range artifacts { + artifactMap[artifact.Name] = artifact + } + + // Verify first artifact + artifact1, exists := artifactMap["quay.io/test/artifact1:v1"] + require.True(t, exists) + assert.Equal(t, "application/vnd.test+type", artifact1.Manifest.ArtifactType) + assert.Len(t, artifact1.Manifest.Layers, 1) + assert.Equal(t, int64(512), artifact1.TotalSizeBytes()) + + // Verify second artifact + artifact2, exists := artifactMap["quay.io/test/artifact2:v2"] + require.True(t, exists) + assert.Equal(t, "application/vnd.custom+type", artifact2.Manifest.ArtifactType) + assert.Len(t, artifact2.Manifest.Layers, 2) + assert.Equal(t, int64(3072), artifact2.TotalSizeBytes()) + + // Verify third artifact + artifact3, exists := artifactMap["docker.io/library/artifact3:latest"] + require.True(t, exists) + assert.Equal(t, "application/vnd.test+type", artifact3.Manifest.ArtifactType) + assert.Len(t, artifact3.Manifest.Layers, 1) + assert.Equal(t, int64(256), artifact3.TotalSizeBytes()) + + // Verify all artifacts have valid digests + for _, artifact := range artifacts { + digest, err := artifact.GetDigest() + require.NoError(t, err) + assert.NotEmpty(t, digest.String()) + } +} + +func TestDetermineBlobMIMEType_FromFile(t *testing.T) { + tempDir := t.TempDir() + + // Test with plain text file + textFile := filepath.Join(tempDir, "test.txt") + err := os.WriteFile(textFile, []byte("Hello, World!"), 0o644) + require.NoError(t, err) + + blob := libartTypes.ArtifactBlob{ + BlobFilePath: textFile, + FileName: "test.txt", + } + + reader, mimeType, err := determineBlobMIMEType(blob) + require.NoError(t, err) + assert.Nil(t, reader) + assert.Equal(t, "text/plain; charset=utf-8", mimeType) + + // Test with JSON file + jsonFile := filepath.Join(tempDir, "test.json") + jsonContent := []byte(`{"key": "value", "number": 123}`) + err = os.WriteFile(jsonFile, jsonContent, 0o644) + require.NoError(t, err) + + blob = libartTypes.ArtifactBlob{ + BlobFilePath: jsonFile, + FileName: "test.json", + } + + reader, mimeType, err = determineBlobMIMEType(blob) + require.NoError(t, err) + assert.Nil(t, reader) + assert.Equal(t, "text/plain; charset=utf-8", mimeType) + + // Test with binary file + binaryFile := filepath.Join(tempDir, "test.bin") + binaryContent := []byte{0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46} + err = os.WriteFile(binaryFile, binaryContent, 0o644) + require.NoError(t, err) + + blob = libartTypes.ArtifactBlob{ + BlobFilePath: binaryFile, + FileName: "test.bin", + } + + reader, mimeType, err = determineBlobMIMEType(blob) + require.NoError(t, err) + assert.Nil(t, reader) + assert.Equal(t, "image/jpeg", mimeType) +} + +func TestDetermineBlobMIMEType_FromReader(t *testing.T) { + // Test with plain text reader + textContent := "This is plain text content" + blob := libartTypes.ArtifactBlob{ + BlobReader: strings.NewReader(textContent), + FileName: "test.txt", + } + + reader, mimeType, err := determineBlobMIMEType(blob) + require.NoError(t, err) + require.NotNil(t, reader) + assert.Equal(t, "text/plain; charset=utf-8", mimeType) + + // Verify the reader still has all the content + content, err := io.ReadAll(reader) + require.NoError(t, err) + assert.Equal(t, textContent, string(content)) + + // Test with HTML content + htmlContent := "Test" + blob = libartTypes.ArtifactBlob{ + BlobReader: strings.NewReader(htmlContent), + FileName: "test.html", + } + + reader, mimeType, err = determineBlobMIMEType(blob) + require.NoError(t, err) + require.NotNil(t, reader) + assert.Equal(t, "text/html; charset=utf-8", mimeType) + + // Test with binary content + binaryContent := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A} + blob = libartTypes.ArtifactBlob{ + BlobReader: bytes.NewReader(binaryContent), + FileName: "test.png", + } + + reader, mimeType, err = determineBlobMIMEType(blob) + require.NoError(t, err) + require.NotNil(t, reader) + assert.Equal(t, "image/png", mimeType) +} + +func TestDetermineBlobMIMEType_SmallFile(t *testing.T) { + tempDir := t.TempDir() + + // Test with file smaller than 512 bytes + smallFile := filepath.Join(tempDir, "small.txt") + smallContent := []byte("Small") + err := os.WriteFile(smallFile, smallContent, 0o644) + require.NoError(t, err) + + blob := libartTypes.ArtifactBlob{ + BlobFilePath: smallFile, + FileName: "small.txt", + } + + reader, mimeType, err := determineBlobMIMEType(blob) + require.NoError(t, err) + assert.Nil(t, reader) + assert.Equal(t, "text/plain; charset=utf-8", mimeType) +} + +func TestDetermineBlobMIMEType_Errors(t *testing.T) { + // Test with neither file path nor reader + blob := libartTypes.ArtifactBlob{ + FileName: "test.txt", + } + + _, _, err := determineBlobMIMEType(blob) + require.Error(t, err) + assert.Contains(t, err.Error(), "Artifact.BlobFile or Artifact.BlobReader must be provided") + + // Test with both file path and reader + blob = libartTypes.ArtifactBlob{ + BlobFilePath: "/tmp/test.txt", + BlobReader: strings.NewReader("content"), + FileName: "test.txt", + } + + _, _, err = determineBlobMIMEType(blob) + require.Error(t, err) + assert.Contains(t, err.Error(), "Artifact.BlobFile or Artifact.BlobReader must be provided") + + // Test with non-existent file + blob = libartTypes.ArtifactBlob{ + BlobFilePath: "/nonexistent/file.txt", + FileName: "file.txt", + } + + _, _, err = determineBlobMIMEType(blob) + require.Error(t, err) +} diff --git a/go.work.sum b/go.work.sum index 8193713933..7bccf4d248 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,17 +1,200 @@ +cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +chainguard.dev/go-grpc-kit v0.17.10/go.mod h1:RPyCEjTxWAxrODH5V4vJOLy/gHRjEjVF9dzWN+LmIvo= +chainguard.dev/sdk v0.1.32/go.mod h1:ma1I0De/7PYJz8pEhEvTPv3hbnEehZPUKSiytzv2rJU= +cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q= +cloud.google.com/go/auth v0.16.0/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/iam v1.5.0/go.mod h1:U+DOtKQltF/LxPEtcDLoobcsZMilSRwR7mgNL7knOpo= +cloud.google.com/go/kms v1.21.1/go.mod h1:s0wCyByc9LjTdCjG88toVs70U9W+cc6RKFc8zAqX7nE= +cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw= +cloud.google.com/go/security v1.18.5/go.mod h1:D1wuUkDwGqTKD0Nv7d4Fn2Dc53POJSmO4tlg1K1iS7s= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= +github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= +github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I= +github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= +github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alexflint/go-filemutex v1.3.0/go.mod h1:U0+VA/i30mGBlLCrFPGtTe9y6wGQfNAWPBTekHQ+c8A= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg= +github.com/aws/aws-sdk-go-v2/config v1.29.13/go.mod h1:NI28qs/IOUIRhsR7GQ/JdexoqRN9tDxkIrYZq0SOF44= +github.com/aws/aws-sdk-go-v2/credentials v1.17.66/go.mod h1:xQ5SusDmHb/fy55wU0QqTy0yNfLqxzec59YcsRZB+rI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.9/go.mod h1:z9VXZsWA2BvZNH1dT0ToUYwMu/CR9Skkj/TBX+mceZw= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.11/go.mod h1:5jHR79Tv+Ccq6rwYh+W7Nptmw++WiFafMfR42XhwNl8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.9/go.mod h1:9TzXX3MehQNGPwCZ3ka4CpwQsoAMWSF48/b+De9rfVM= +github.com/aws/aws-sdk-go-v2/service/kms v1.38.2/go.mod h1:cQn6tAF77Di6m4huxovNM7NVAozWTZLsDRp9t8Z/WYk= +github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1/go.mod h1:hWjsYGjVuqCgfoveVcVFPXIWgz0aByzwaxKlN1StKcM= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.18/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= +github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/chainguard-dev/clog v1.7.0/go.mod h1:4+WFhRMsGH79etYXY3plYdp+tCz/KCkU8fAr0HoaPvs= +github.com/checkpoint-restore/go-criu/v6 v6.3.0/go.mod h1:rrRTN/uSwY2X+BPRl/gkulo9gsKOSAeVp9/K2tv7xZI= +github.com/cilium/ebpf v0.17.3/go.mod h1:G5EDHij8yiLzaqn0WjyfJHvRa+3aDlReIaLVRMvOyJk= github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= +github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= +github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= +github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= +github.com/containers/storage v1.59.1/go.mod h1:KoAYHnAjP3/cTsRS+mmWZGkufSY2GACiKQ4V3ZLQnR0= +github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/eggsampler/acme/v3 v3.6.0/go.mod h1:/qh0rKC/Dh7Jj+p4So7DbWmFNzC4dpcpK53r226Fhuo= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-piv/piv-go/v2 v2.3.0/go.mod h1:ShZi74nnrWNQEdWzRUd/3cSig3uNOcEZp+EWl0oewnI= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/certificate-transparency-go v1.3.1/go.mod h1:gg+UQlx6caKEDQ9EElFOujyxEQEfOiQzAt6782Bvi8k= +github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= +github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= +github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= +github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic= +github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw= +github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= +github.com/letsencrypt/borp v0.0.0-20230707160741-6cc6ce580243/go.mod h1:podMDq5wDu2ZO6JMKYQcjD3QdqOfNLWtP2RDSy8CHUU= +github.com/letsencrypt/challtestsrv v1.2.1/go.mod h1:Ur4e4FvELUXLGhkMztHOsPIsvGxD/kzSJninOrkM+zc= +github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= +github.com/letsencrypt/validator/v10 v10.0.0-20230215210743-a0c7dfc17158/go.mod h1:ZFNBS3H6OEsprCRjscty6GCBe5ZiX44x6qY4s7+bDX0= github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4= +github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mndrix/tap-go v0.0.0-20171203230836-629fa407e90b/go.mod h1:pzzDgJWZ34fGzaAZGFW22KVZDfyrYW+QABMrWnJBnSs= +github.com/mrunalp/fileutils v0.5.1/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/networkplumbing/go-nft v0.4.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= +github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/safchain/ethtool v0.6.2/go.mod h1:VS7cn+bP3Px3rIq55xImBiZGHVLNyBh5dqG6dDQy8+I= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.9.3/go.mod h1:2D6TX/FEBMoaD86P5aYzhxRKUYPiWcOz+6EARsVnM3s= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.9.3/go.mod h1:nR4s/4sdbeHfe7RwEPL1NhwsC1ia72wDJOIMevxTMYY= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.9.3/go.mod h1:yZMHY5cEkNRkhZGGhMS6IAUgE0HcXja1xmil796wtqg= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.9.3/go.mod h1:AjN/gspnXeMDFTOXlHzRJDs8xbkd30kH8VN9D8g4CZM= +github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= +github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= +github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= +github.com/tink-crypto/tink-go-awskms/v2 v2.1.0/go.mod h1:PxSp9GlOkKL9rlybW804uspnHuO9nbD98V/fDX4uSis= +github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0/go.mod h1:jY5YN2BqD/KSCHM9SqZPIpJNG/u3zwfLXHgws4x2IRw= +github.com/tink-crypto/tink-go/v2 v2.4.0/go.mod h1:l//evrF2Y3MjdbpNDNGnKgCpo5zSmvUvnQ4MU+yE2sw= +github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= +github.com/weppos/publicsuffix-go v0.30.3-0.20240510084413-5f1d03393b3d/go.mod h1:vLdXKydr/OJssAXmjY0XBgLXUfivBMrNRIBljgtqCnw= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +github.com/zmap/zcrypto v0.0.0-20231219022726-a1f61fb1661c/go.mod h1:GSDpFDD4TASObxvfZfvpZZ3OWHIUHMlhVWlkOe4ewVk= +github.com/zmap/zlint/v3 v3.6.0/go.mod h1:NVgiIWssgzp0bNl8P4Gz94NHV2ep/4Jyj9V69uTmZyg= +go.etcd.io/gofail v0.2.0/go.mod h1:nL3ILMGfkXTekKI3clMBNazKnjUZjYLKmBHzsVAnC1o= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +goa.design/goa/v3 v3.20.1/go.mod h1:cLX3Y1JvnCabMWDAZxmfnjxM1f1l9g7Zf0C9CD9GIAQ= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= @@ -27,4 +210,13 @@ golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.230.0/go.mod h1:aqvtoMk7YkiXx+6U12arQFExiRV9D/ekvMCwCd/TksQ= google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE= +google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +sigs.k8s.io/knftables v0.0.18/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/release-utils v0.11.1/go.mod h1:ybR2V/uQAOGxYfzYtBenSYeXWkBGNP2qnEiX77ACtpc= +tags.cncf.io/container-device-interface/specs-go v1.0.0/go.mod h1:u86hoFWqnh3hWz3esofRFKbI261bUlvUfLKGrDhJkgQ= diff --git a/vendor/github.com/pkg/errors/.gitignore b/vendor/github.com/pkg/errors/.gitignore new file mode 100644 index 0000000000..daf913b1b3 --- /dev/null +++ b/vendor/github.com/pkg/errors/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml new file mode 100644 index 0000000000..9159de03e0 --- /dev/null +++ b/vendor/github.com/pkg/errors/.travis.yml @@ -0,0 +1,10 @@ +language: go +go_import_path: github.com/pkg/errors +go: + - 1.11.x + - 1.12.x + - 1.13.x + - tip + +script: + - make check diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 0000000000..835ba3e755 --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/errors/Makefile b/vendor/github.com/pkg/errors/Makefile new file mode 100644 index 0000000000..ce9d7cded6 --- /dev/null +++ b/vendor/github.com/pkg/errors/Makefile @@ -0,0 +1,44 @@ +PKGS := github.com/pkg/errors +SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS)) +GO := go + +check: test vet gofmt misspell unconvert staticcheck ineffassign unparam + +test: + $(GO) test $(PKGS) + +vet: | test + $(GO) vet $(PKGS) + +staticcheck: + $(GO) get honnef.co/go/tools/cmd/staticcheck + staticcheck -checks all $(PKGS) + +misspell: + $(GO) get github.com/client9/misspell/cmd/misspell + misspell \ + -locale GB \ + -error \ + *.md *.go + +unconvert: + $(GO) get github.com/mdempsky/unconvert + unconvert -v $(PKGS) + +ineffassign: + $(GO) get github.com/gordonklaus/ineffassign + find $(SRCDIRS) -name '*.go' | xargs ineffassign + +pedantic: check errcheck + +unparam: + $(GO) get mvdan.cc/unparam + unparam ./... + +errcheck: + $(GO) get github.com/kisielk/errcheck + errcheck $(PKGS) + +gofmt: + @echo Checking code is gofmted + @test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)" diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md new file mode 100644 index 0000000000..54dfdcb12e --- /dev/null +++ b/vendor/github.com/pkg/errors/README.md @@ -0,0 +1,59 @@ +# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge) + +Package errors provides simple error handling primitives. + +`go get github.com/pkg/errors` + +The traditional error handling idiom in Go is roughly akin to +```go +if err != nil { + return err +} +``` +which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. + +## Adding context to an error + +The errors.Wrap function returns a new error that adds context to the original error. For example +```go +_, err := ioutil.ReadAll(r) +if err != nil { + return errors.Wrap(err, "read failed") +} +``` +## Retrieving the cause of an error + +Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. +```go +type causer interface { + Cause() error +} +``` +`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: +```go +switch err := errors.Cause(err).(type) { +case *MyError: + // handle specifically +default: + // unknown error +} +``` + +[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). + +## Roadmap + +With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows: + +- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible) +- 1.0. Final release. + +## Contributing + +Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports. + +Before sending a PR, please discuss your change by raising an issue. + +## License + +BSD-2-Clause diff --git a/vendor/github.com/pkg/errors/appveyor.yml b/vendor/github.com/pkg/errors/appveyor.yml new file mode 100644 index 0000000000..a932eade02 --- /dev/null +++ b/vendor/github.com/pkg/errors/appveyor.yml @@ -0,0 +1,32 @@ +version: build-{build}.{branch} + +clone_folder: C:\gopath\src\github.com\pkg\errors +shallow_clone: true # for startup speed + +environment: + GOPATH: C:\gopath + +platform: + - x64 + +# http://www.appveyor.com/docs/installed-software +install: + # some helpful output for debugging builds + - go version + - go env + # pre-installed MinGW at C:\MinGW is 32bit only + # but MSYS2 at C:\msys64 has mingw64 + - set PATH=C:\msys64\mingw64\bin;%PATH% + - gcc --version + - g++ --version + +build_script: + - go install -v ./... + +test_script: + - set PATH=C:\gopath\bin;%PATH% + - go test -v ./... + +#artifacts: +# - path: '%GOPATH%\bin\*.exe' +deploy: off diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 0000000000..161aea2582 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,288 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which when applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, +// together with the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// If additional control is required, the errors.WithStack and +// errors.WithMessage functions destructure errors.Wrap into its component +// operations: annotating an error with a stack trace and with a message, +// respectively. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error that does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// Although the causer interface is not exported by this package, it is +// considered a part of its stable public interface. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported: +// +// %s print the error. If the error has a Cause it will be +// printed recursively. +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface: +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// The returned errors.StackTrace type is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d\n", f, f) +// } +// } +// +// Although the stackTracer interface is not exported by this package, it is +// considered a part of its stable public interface. +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func WithStack(err error) error { + if err == nil { + return nil + } + return &withStack{ + err, + callers(), + } +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withStack) Unwrap() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is called, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + } +} + +// WithMessagef annotates err with the format specifier. +// If err is nil, WithMessagef returns nil. +func WithMessagef(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withMessage) Unwrap() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/vendor/github.com/pkg/errors/go113.go b/vendor/github.com/pkg/errors/go113.go new file mode 100644 index 0000000000..be0d10d0c7 --- /dev/null +++ b/vendor/github.com/pkg/errors/go113.go @@ -0,0 +1,38 @@ +// +build go1.13 + +package errors + +import ( + stderrors "errors" +) + +// Is reports whether any error in err's chain matches target. +// +// The chain consists of err itself followed by the sequence of errors obtained by +// repeatedly calling Unwrap. +// +// An error is considered to match a target if it is equal to that target or if +// it implements a method Is(error) bool such that Is(target) returns true. +func Is(err, target error) bool { return stderrors.Is(err, target) } + +// As finds the first error in err's chain that matches target, and if so, sets +// target to that error value and returns true. +// +// The chain consists of err itself followed by the sequence of errors obtained by +// repeatedly calling Unwrap. +// +// An error matches target if the error's concrete value is assignable to the value +// pointed to by target, or if the error has a method As(interface{}) bool such that +// As(target) returns true. In the latter case, the As method is responsible for +// setting target. +// +// As will panic if target is not a non-nil pointer to either a type that implements +// error, or to any interface type. As returns false if err is nil. +func As(err error, target interface{}) bool { return stderrors.As(err, target) } + +// Unwrap returns the result of calling the Unwrap method on err, if err's +// type contains an Unwrap method returning error. +// Otherwise, Unwrap returns nil. +func Unwrap(err error) error { + return stderrors.Unwrap(err) +} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go new file mode 100644 index 0000000000..779a8348fb --- /dev/null +++ b/vendor/github.com/pkg/errors/stack.go @@ -0,0 +1,177 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strconv" + "strings" +) + +// Frame represents a program counter inside a stack frame. +// For historical reasons if Frame is interpreted as a uintptr +// its value represents the program counter + 1. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// name returns the name of this function, if known. +func (f Frame) name() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + return fn.Name() +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s function name and path of source file relative to the compile time +// GOPATH separated by \n\t (\n\t) +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + io.WriteString(s, f.name()) + io.WriteString(s, "\n\t") + io.WriteString(s, f.file()) + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + io.WriteString(s, strconv.Itoa(f.line())) + case 'n': + io.WriteString(s, funcname(f.name())) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// MarshalText formats a stacktrace Frame as a text string. The output is the +// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs. +func (f Frame) MarshalText() ([]byte, error) { + name := f.name() + if name == "unknown" { + return []byte(name), nil + } + return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +// Format formats the stack of Frames according to the fmt.Formatter interface. +// +// %s lists source files for each Frame in the stack +// %v lists the source file and line number for each Frame in the stack +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+v Prints filename, function, and line number for each Frame in the stack. +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + io.WriteString(s, "\n") + f.Format(s, verb) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + st.formatSlice(s, verb) + } + case 's': + st.formatSlice(s, verb) + } +} + +// formatSlice will format this StackTrace into the given buffer as a slice of +// Frame, only valid when called with '%s' or '%v'. +func (st StackTrace) formatSlice(s fmt.State, verb rune) { + io.WriteString(s, "[") + for i, f := range st { + if i > 0 { + io.WriteString(s, " ") + } + f.Format(s, verb) + } + io.WriteString(s, "]") +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} diff --git a/vendor/go.step.sm/crypto/LICENSE b/vendor/go.step.sm/crypto/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/vendor/go.step.sm/crypto/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/go.step.sm/crypto/randutil/random.go b/vendor/go.step.sm/crypto/randutil/random.go new file mode 100644 index 0000000000..dce7931b18 --- /dev/null +++ b/vendor/go.step.sm/crypto/randutil/random.go @@ -0,0 +1,113 @@ +// Package randutil provides methods to generate random strings and salts. +package randutil + +import ( + "crypto/rand" + "encoding/hex" + "io" + "math/big" + + "github.com/pkg/errors" +) + +var ascii string + +func init() { + // initialize the charcters in ascii + aciiBytes := make([]byte, 94) + for i := range aciiBytes { + aciiBytes[i] = byte(i + 33) + } + ascii = string(aciiBytes) +} + +// Salt generates a new random salt of the given size. +func Salt(size int) ([]byte, error) { + salt := make([]byte, size) + _, err := io.ReadFull(rand.Reader, salt) + if err != nil { + return nil, errors.Wrap(err, "error generating salt") + } + return salt, nil +} + +// Bytes generates a new byte slice of the given size. +func Bytes(size int) ([]byte, error) { + bytes := make([]byte, size) + _, err := io.ReadFull(rand.Reader, bytes) + if err != nil { + return nil, errors.Wrap(err, "error generating bytes") + } + return bytes, nil +} + +// String returns a random string of a given length using the characters in +// the given string. It splits the string on runes to support UTF-8 +// characters. +func String(length int, chars string) (string, error) { + result := make([]rune, length) + runes := []rune(chars) + x := int64(len(runes)) + for i := range result { + num, err := rand.Int(rand.Reader, big.NewInt(x)) + if err != nil { + return "", errors.Wrap(err, "error creating random number") + } + result[i] = runes[num.Int64()] + } + return string(result), nil +} + +// Hex returns a random string of the given length using the hexadecimal +// characters in lower case (0-9+a-f). +func Hex(length int) (string, error) { + return String(length, "0123456789abcdef") +} + +// Alphanumeric returns a random string of the given length using the 62 +// alphanumeric characters in the POSIX/C locale (a-z+A-Z+0-9). +func Alphanumeric(length int) (string, error) { + return String(length, "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") +} + +// ASCII returns a securely generated random ASCII string. It reads random +// numbers from crypto/rand and searches for printable characters. It will +// return an error if the system's secure random number generator fails to +// function correctly, in which case the caller must not continue. +func ASCII(length int) (string, error) { + return String(length, ascii) +} + +// Alphabet returns a random string of the given length using the 52 +// alphabetic characters in the POSIX/C locale (a-z+A-Z). +func Alphabet(length int) (string, error) { + return String(length, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") +} + +// UUIDv4 returns the string representation of a UUID version 4. Because 6 bits +// are used to indicate the version 4 and the variant 10, the randomly generated +// part has 122 bits. +func UUIDv4() (string, error) { + var uuid [16]byte + _, err := io.ReadFull(rand.Reader, uuid[:]) + if err != nil { + return "", errors.Wrap(err, "error generating uuid") + } + uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 + uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 + return encodeUUID(uuid), nil +} + +func encodeUUID(uuid [16]byte) string { + buf := make([]byte, 36) + hex.Encode(buf, uuid[:4]) + buf[8] = '-' + hex.Encode(buf[9:13], uuid[4:6]) + buf[13] = '-' + hex.Encode(buf[14:18], uuid[6:8]) + buf[18] = '-' + hex.Encode(buf[19:23], uuid[8:10]) + buf[23] = '-' + hex.Encode(buf[24:], uuid[10:]) + return string(buf) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 76c6112485..a779d2e677 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -394,6 +394,7 @@ github.com/opencontainers/selinux/go-selinux/label github.com/opencontainers/selinux/pkg/pwalkdir # github.com/pkg/errors v0.9.1 ## explicit +github.com/pkg/errors # github.com/pkg/sftp v1.13.10 ## explicit; go 1.23.0 github.com/pkg/sftp @@ -541,6 +542,9 @@ go.opentelemetry.io/otel/trace/noop ## explicit; go 1.24.0 # go.podman.io/storage v1.61.0 ## explicit; go 1.24.0 +# go.step.sm/crypto v0.57.0 +## explicit; go 1.22 +go.step.sm/crypto/randutil # go.yaml.in/yaml/v2 v2.4.2 ## explicit; go 1.15 go.yaml.in/yaml/v2