diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 1b06f78af..917ae7232 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -40,13 +40,13 @@ jobs:
postgrest
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install Go
- uses: buildjet/setup-go@555ce355a95ff01018ffcf8fbbd9c44654db8374 # v5.0.2
+ uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: 1.25.x
cache: false # Using custom cache action below for .bin directory
- name: Checkout code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- - uses: buildjet/cache@3e70d19e31d6a8030aeddf6ed8dbe601f94d09f4 # v4.0.2
+ - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/go/pkg/mod
@@ -76,13 +76,13 @@ jobs:
--health-retries 5
steps:
- name: Install Go
- uses: buildjet/setup-go@555ce355a95ff01018ffcf8fbbd9c44654db8374 # v5.0.2
+ uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: 1.25.x
cache: false # Using custom cache action below for .bin directory
- name: Checkout code
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
- - uses: buildjet/cache@3e70d19e31d6a8030aeddf6ed8dbe601f94d09f4 # v4.0.2
+ - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/go/pkg/mod
@@ -99,6 +99,30 @@ jobs:
DUTY_DB_DISABLE_RLS: "true"
LOKI_URL: http://localhost:3100
+ e2e-blobs:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Install Go
+ uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
+ with:
+ go-version: 1.25.x
+ cache: false
+ - name: Checkout code
+ uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ with:
+ path: |
+ ~/go/pkg/mod
+ ~/.cache/go-build
+ .bin
+ key: cache-${{ hashFiles('**/go.sum') }}-${{ hashFiles('.bin/*') }}
+ restore-keys: |
+ cache-
+ - name: E2E Blob Store Tests
+ run: make test-e2e-blobs
+ env:
+ DUTY_DB_DISABLE_RLS: "true"
+
migrate:
runs-on: ubuntu-latest
strategy:
@@ -115,11 +139,11 @@ jobs:
go build main.go && ./main --db-url 'postgres://postgres:postgres@localhost:5432/test?sslmode=disable'
steps:
- name: Install Go
- uses: buildjet/setup-go@555ce355a95ff01018ffcf8fbbd9c44654db8374 # v5.0.2
+ uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: 1.25.x
cache: false # Using custom cache action below for .bin directory
- - uses: buildjet/cache@3e70d19e31d6a8030aeddf6ed8dbe601f94d09f4 # v4.0.2
+ - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/go/pkg/mod
diff --git a/Makefile b/Makefile
index 98eff8460..11278699a 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,7 @@ ginkgo:
go install github.com/onsi/ginkgo/v2/ginkgo
test: ginkgo
- ginkgo -r --succinct --skip-package=tests/e2e,bench --label-filter "!e2e"
+ ginkgo -r --succinct --skip-package=tests/e2e,tests/e2e-blobs,bench --label-filter "!e2e"
test-concurrent: ginkgo
ginkgo -r -v --nodes=4 --skip-package=bench --label-filter "!e2e"
@@ -30,6 +30,10 @@ e2e-services: ## Run e2e test services in foreground with automatic cleanup on e
trap 'docker-compose down -v && docker-compose rm -f' EXIT INT TERM && \
docker-compose up --remove-orphans
+.PHONY: test-e2e-blobs
+test-e2e-blobs: ginkgo
+ ginkgo -v --label-filter="e2e" ./tests/e2e-blobs/
+
.PHONY: bench
bench:
go test -run ^$$ -bench=. -benchtime=10s -timeout 30m github.com/flanksource/duty/bench
diff --git a/artifact/blob_store.go b/artifact/blob_store.go
new file mode 100644
index 000000000..7e2c42846
--- /dev/null
+++ b/artifact/blob_store.go
@@ -0,0 +1,110 @@
+package artifact
+
+import (
+ "bytes"
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "io"
+
+ "github.com/flanksource/duty/models"
+ "github.com/google/uuid"
+ "gorm.io/gorm"
+)
+
+type BlobStore interface {
+ Write(data Data, artifact *models.Artifact) (*models.Artifact, error)
+ Read(artifactID uuid.UUID) (*Data, error)
+ io.Closer
+}
+
+type blobStore struct {
+ fs FilesystemRW
+ db *gorm.DB
+ backend string
+}
+
+func NewBlobStore(fs FilesystemRW, db *gorm.DB, backend string) BlobStore {
+ return &blobStore{fs: fs, db: db, backend: backend}
+}
+
+func (s *blobStore) Write(data Data, a *models.Artifact) (*models.Artifact, error) {
+ if a == nil {
+ a = &models.Artifact{}
+ }
+ if data.Content == nil {
+ return nil, fmt.Errorf("artifact data content is nil")
+ }
+ defer func() { _ = data.Content.Close() }()
+
+ checksum := sha256.New()
+ mimeReader := io.TeeReader(data.Content, checksum)
+
+ mw := &mimeWriter{Max: maxBytesForMimeDetection}
+ fileReader := io.TeeReader(mimeReader, mw)
+
+ info, err := s.fs.Write(s.db.Statement.Context, data.Filename, fileReader)
+ if err != nil {
+ return nil, fmt.Errorf("writing artifact %s: %w", data.Filename, err)
+ }
+
+ if data.ContentType == "" {
+ data.ContentType = mw.Detect().String()
+ }
+
+ // For inline store, the artifact already has content set
+ if inlineArt := InlineArtifact(info); inlineArt != nil {
+ a.Content = inlineArt.Content
+ a.CompressionType = inlineArt.CompressionType
+ }
+
+ a.Path = data.Filename
+ a.Filename = info.Name()
+ a.Size = info.Size()
+ a.ContentType = data.ContentType
+ a.Checksum = hex.EncodeToString(checksum.Sum(nil))
+
+ if err := s.db.Create(a).Error; err != nil {
+ return nil, fmt.Errorf("saving artifact to db: %w", err)
+ }
+
+ return a, nil
+}
+
+func (s *blobStore) Read(artifactID uuid.UUID) (*Data, error) {
+ var a models.Artifact
+ if err := s.db.Where("id = ?", artifactID).First(&a).Error; err != nil {
+ return nil, fmt.Errorf("finding artifact %s: %w", artifactID, err)
+ }
+
+ if a.IsInline() {
+ content, err := a.GetContent()
+ if err != nil {
+ return nil, fmt.Errorf("decompressing inline artifact %s: %w", artifactID, err)
+ }
+ return &Data{
+ Content: io.NopCloser(bytes.NewReader(content)),
+ ContentLength: a.Size,
+ Checksum: a.Checksum,
+ ContentType: a.ContentType,
+ Filename: a.Filename,
+ }, nil
+ }
+
+ r, err := s.fs.Read(s.db.Statement.Context, a.Path)
+ if err != nil {
+ return nil, fmt.Errorf("reading artifact %s from %s: %w", artifactID, a.Path, err)
+ }
+
+ return &Data{
+ Content: r,
+ ContentLength: a.Size,
+ Checksum: a.Checksum,
+ ContentType: a.ContentType,
+ Filename: a.Filename,
+ }, nil
+}
+
+func (s *blobStore) Close() error {
+ return s.fs.Close()
+}
diff --git a/artifact/clients/aws/doc.go b/artifact/clients/aws/doc.go
new file mode 100644
index 000000000..a1f9c0e3b
--- /dev/null
+++ b/artifact/clients/aws/doc.go
@@ -0,0 +1 @@
+package aws
diff --git a/artifact/clients/aws/fileinfo.go b/artifact/clients/aws/fileinfo.go
new file mode 100644
index 000000000..efc0bdb76
--- /dev/null
+++ b/artifact/clients/aws/fileinfo.go
@@ -0,0 +1,48 @@
+//go:build !fast
+
+package aws
+
+import (
+ "io/fs"
+ "strings"
+ "time"
+
+ "github.com/aws/aws-sdk-go-v2/service/s3/types"
+ "github.com/flanksource/commons/utils"
+ "github.com/samber/lo"
+)
+
+type S3FileInfo struct {
+ Object types.Object
+}
+
+func (obj S3FileInfo) Name() string {
+ if obj.Object.Key == nil {
+ return ""
+ }
+ return *obj.Object.Key
+}
+
+func (obj S3FileInfo) Size() int64 {
+ return utils.Deref(obj.Object.Size)
+}
+
+func (obj S3FileInfo) Mode() fs.FileMode {
+ return fs.FileMode(0644)
+}
+
+func (obj S3FileInfo) ModTime() time.Time {
+ return lo.FromPtr(obj.Object.LastModified)
+}
+
+func (obj S3FileInfo) FullPath() string {
+ return *obj.Object.Key
+}
+
+func (obj S3FileInfo) IsDir() bool {
+ return strings.HasSuffix(obj.Name(), "/")
+}
+
+func (obj S3FileInfo) Sys() interface{} {
+ return obj.Object
+}
diff --git a/artifact/clients/azure/fileinfo.go b/artifact/clients/azure/fileinfo.go
new file mode 100644
index 000000000..d656bcf8f
--- /dev/null
+++ b/artifact/clients/azure/fileinfo.go
@@ -0,0 +1,21 @@
+package azure
+
+import (
+ "io/fs"
+ "time"
+)
+
+type BlobFileInfo struct {
+ BlobName string
+ BlobSize int64
+ LastMod time.Time
+ ContentType string
+}
+
+func (f BlobFileInfo) Name() string { return f.BlobName }
+func (f BlobFileInfo) Size() int64 { return f.BlobSize }
+func (f BlobFileInfo) Mode() fs.FileMode { return 0644 }
+func (f BlobFileInfo) ModTime() time.Time { return f.LastMod }
+func (f BlobFileInfo) IsDir() bool { return false }
+func (f BlobFileInfo) Sys() any { return nil }
+func (f BlobFileInfo) FullPath() string { return f.BlobName }
diff --git a/artifact/clients/gcp/fileinfo.go b/artifact/clients/gcp/fileinfo.go
new file mode 100644
index 000000000..edbea82ab
--- /dev/null
+++ b/artifact/clients/gcp/fileinfo.go
@@ -0,0 +1,40 @@
+package gcp
+
+import (
+ "io/fs"
+ "time"
+
+ gcs "cloud.google.com/go/storage"
+)
+
+type GCSFileInfo struct {
+ Object *gcs.ObjectAttrs
+}
+
+func (GCSFileInfo) IsDir() bool {
+ return false
+}
+
+func (obj GCSFileInfo) ModTime() time.Time {
+ return obj.Object.Updated
+}
+
+func (obj GCSFileInfo) Mode() fs.FileMode {
+ return fs.FileMode(0644)
+}
+
+func (obj GCSFileInfo) Name() string {
+ return obj.Object.Name
+}
+
+func (obj GCSFileInfo) Size() int64 {
+ return obj.Object.Size
+}
+
+func (obj GCSFileInfo) Sys() interface{} {
+ return obj.Object
+}
+
+func (obj GCSFileInfo) FullPath() string {
+ return obj.Object.Name
+}
diff --git a/artifact/clients/sftp/sftp.go b/artifact/clients/sftp/sftp.go
new file mode 100644
index 000000000..18a4ae496
--- /dev/null
+++ b/artifact/clients/sftp/sftp.go
@@ -0,0 +1,35 @@
+package sftp
+
+import (
+ "time"
+
+ "github.com/pkg/sftp"
+ "golang.org/x/crypto/ssh"
+)
+
+// SSHConnect creates an SFTP client connection.
+// NOTE: Uses InsecureIgnoreHostKey because artifact storage targets are
+// configured by admins via trusted connection objects, not user input.
+func SSHConnect(host, user, password string) (*sftp.Client, error) {
+ config := &ssh.ClientConfig{
+ User: user,
+ Auth: []ssh.AuthMethod{
+ ssh.Password(password),
+ },
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
+ Timeout: 30 * time.Second,
+ }
+
+ conn, err := ssh.Dial("tcp", host, config)
+ if err != nil {
+ return nil, err
+ }
+
+ client, err := sftp.NewClient(conn)
+ if err != nil {
+ conn.Close()
+ return nil, err
+ }
+
+ return client, nil
+}
diff --git a/artifact/clients/smb/smb.go b/artifact/clients/smb/smb.go
new file mode 100644
index 000000000..8bac0b61a
--- /dev/null
+++ b/artifact/clients/smb/smb.go
@@ -0,0 +1,66 @@
+package smb
+
+import (
+ "net"
+
+ "github.com/flanksource/duty/types"
+ "github.com/hirochachacha/go-smb2"
+)
+
+type SMBSession struct {
+ net.Conn
+ *smb2.Session
+ *smb2.Share
+}
+
+func SMBConnect(server string, port, share string, auth types.Authentication) (*SMBSession, error) {
+ var err error
+ var smb *SMBSession
+ server = server + ":" + port
+ conn, err := net.Dial("tcp", server)
+ if err != nil {
+ return nil, err
+ }
+ smb = &SMBSession{
+ Conn: conn,
+ }
+
+ d := &smb2.Dialer{
+ Initiator: &smb2.NTLMInitiator{
+ User: auth.GetUsername(),
+ Password: auth.GetPassword(),
+ Domain: auth.GetDomain(),
+ },
+ }
+
+ s, err := d.Dial(conn)
+ if err != nil {
+ conn.Close()
+ return nil, err
+ }
+ smb.Session = s
+ fs, err := s.Mount(share)
+ if err != nil {
+ _ = s.Logoff()
+ conn.Close()
+ return nil, err
+ }
+
+ smb.Share = fs
+
+ return smb, err
+}
+
+func (s *SMBSession) Close() error {
+ if s.Conn != nil {
+ _ = s.Conn.Close()
+ }
+ if s.Session != nil {
+ _ = s.Logoff()
+ }
+ if s.Share != nil {
+ _ = s.Umount()
+ }
+
+ return nil
+}
diff --git a/artifact/data.go b/artifact/data.go
new file mode 100644
index 000000000..df7ec1feb
--- /dev/null
+++ b/artifact/data.go
@@ -0,0 +1,69 @@
+package artifact
+
+import (
+ "fmt"
+ "io"
+
+ "github.com/flanksource/clicky"
+ "github.com/flanksource/clicky/api"
+ "github.com/gabriel-vasile/mimetype"
+)
+
+type Data struct {
+ Content io.ReadCloser
+ ContentLength int64
+ Checksum string
+ ContentType string
+ Filename string
+}
+
+func (d Data) Pretty() api.Text {
+ s := clicky.Text(d.Filename, "font-bold")
+ if d.ContentType != "" {
+ s = s.AddText(" "+d.ContentType, "text-gray-500")
+ }
+ if d.ContentLength > 0 {
+ s = s.AddText(fmt.Sprintf(" (%s)", formatBytes(d.ContentLength)), "text-gray-400")
+ }
+ if d.Checksum != "" {
+ short := d.Checksum
+ if len(short) > 8 {
+ short = short[:8]
+ }
+ s = s.AddText(" sha:"+short, "text-yellow-500")
+ }
+ return s
+}
+
+func formatBytes(b int64) string {
+ switch {
+ case b >= 1<<20:
+ return fmt.Sprintf("%.1f MB", float64(b)/(1<<20))
+ case b >= 1<<10:
+ return fmt.Sprintf("%.1f KB", float64(b)/(1<<10))
+ default:
+ return fmt.Sprintf("%d B", b)
+ }
+}
+
+const maxBytesForMimeDetection = 512 * 1024
+
+type mimeWriter struct {
+ buffer []byte
+ Max int
+}
+
+func (t *mimeWriter) Write(bb []byte) (n int, err error) {
+ if len(t.buffer) < t.Max {
+ rem := t.Max - len(t.buffer)
+ if rem > len(bb) {
+ rem = len(bb)
+ }
+ t.buffer = append(t.buffer, bb[:rem]...)
+ }
+ return len(bb), nil
+}
+
+func (t *mimeWriter) Detect() *mimetype.MIME {
+ return mimetype.Detect(t.buffer)
+}
diff --git a/artifact/fs.go b/artifact/fs.go
new file mode 100644
index 000000000..1263f6b55
--- /dev/null
+++ b/artifact/fs.go
@@ -0,0 +1,20 @@
+package artifact
+
+import (
+ gocontext "context"
+ "io"
+ "os"
+)
+
+type FileInfo interface {
+ os.FileInfo
+ FullPath() string
+}
+
+type FilesystemRW interface {
+ io.Closer
+ Read(ctx gocontext.Context, path string) (io.ReadCloser, error)
+ Write(ctx gocontext.Context, path string, data io.Reader) (os.FileInfo, error)
+ ReadDir(name string) ([]FileInfo, error)
+ Stat(name string) (os.FileInfo, error)
+}
diff --git a/artifact/fs/azure.go b/artifact/fs/azure.go
new file mode 100644
index 000000000..906248302
--- /dev/null
+++ b/artifact/fs/azure.go
@@ -0,0 +1,105 @@
+package fs
+
+import (
+ "bytes"
+ gocontext "context"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+
+ "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
+ "github.com/flanksource/duty/artifact"
+ azureUtil "github.com/flanksource/duty/artifact/clients/azure"
+)
+
+type azureBlobFS struct {
+ client *azblob.Client
+ container string
+}
+
+func NewAzureBlobFS(client *azblob.Client, container string) *azureBlobFS {
+ return &azureBlobFS{client: client, container: container}
+}
+
+func (t *azureBlobFS) Close() error { return nil }
+
+func (t *azureBlobFS) Read(ctx gocontext.Context, path string) (io.ReadCloser, error) {
+ resp, err := t.client.DownloadStream(ctx, t.container, path, nil)
+ if err != nil {
+ return nil, fmt.Errorf("downloading blob %s: %w", path, err)
+ }
+ return resp.Body, nil
+}
+
+func (t *azureBlobFS) Write(ctx gocontext.Context, path string, data io.Reader) (os.FileInfo, error) {
+ content, err := io.ReadAll(data)
+ if err != nil {
+ return nil, fmt.Errorf("reading data for blob %s: %w", path, err)
+ }
+
+ if _, err := t.client.UploadBuffer(ctx, t.container, path, content, nil); err != nil {
+ return nil, fmt.Errorf("uploading blob %s: %w", path, err)
+ }
+
+ return t.Stat(path)
+}
+
+func (t *azureBlobFS) ReadDir(name string) ([]artifact.FileInfo, error) {
+ prefix := name
+ if strings.Contains(prefix, "*") {
+ prefix = strings.SplitN(prefix, "*", 2)[0]
+ }
+
+ pager := t.client.NewListBlobsFlatPager(t.container, &azblob.ListBlobsFlatOptions{
+ Prefix: &prefix,
+ })
+
+ var output []artifact.FileInfo
+ for pager.More() {
+ resp, err := pager.NextPage(gocontext.TODO())
+ if err != nil {
+ return nil, fmt.Errorf("listing blobs under %s: %w", name, err)
+ }
+ for _, blob := range resp.Segment.BlobItems {
+ output = append(output, azureUtil.BlobFileInfo{
+ BlobName: *blob.Name,
+ BlobSize: *blob.Properties.ContentLength,
+ LastMod: *blob.Properties.LastModified,
+ })
+ }
+ }
+ return output, nil
+}
+
+func (t *azureBlobFS) Stat(name string) (os.FileInfo, error) {
+ resp, err := t.client.DownloadStream(gocontext.TODO(), t.container, name, nil)
+ if err != nil {
+ return nil, fmt.Errorf("stat blob %s: %w", name, err)
+ }
+ _ = resp.Body.Close()
+
+ size := int64(0)
+ if resp.ContentLength != nil {
+ size = *resp.ContentLength
+ }
+
+ return azureUtil.BlobFileInfo{
+ BlobName: name,
+ BlobSize: size,
+ }, nil
+}
+
+// CreateContainer creates the blob container if it doesn't exist.
+func (t *azureBlobFS) CreateContainer(ctx gocontext.Context) error {
+ _, err := t.client.CreateContainer(ctx, t.container, nil)
+ if err != nil && !strings.Contains(err.Error(), "ContainerAlreadyExists") {
+ return err
+ }
+ return nil
+}
+
+// SaveArtifactInline is a helper that creates inline artifacts for testing.
+func SaveArtifactInline(ctx gocontext.Context, fs artifact.FilesystemRW, path string, data []byte) (os.FileInfo, error) {
+ return fs.Write(ctx, path, bytes.NewReader(data))
+}
diff --git a/artifact/fs/gcs.go b/artifact/fs/gcs.go
new file mode 100644
index 000000000..0e550a8a8
--- /dev/null
+++ b/artifact/fs/gcs.go
@@ -0,0 +1,86 @@
+package fs
+
+import (
+ gocontext "context"
+ "errors"
+ "io"
+ "os"
+ "strings"
+
+ gcs "cloud.google.com/go/storage"
+ "github.com/flanksource/duty/artifact"
+ gcpUtil "github.com/flanksource/duty/artifact/clients/gcp"
+ "google.golang.org/api/iterator"
+)
+
+type gcsFS struct {
+ *gcs.Client
+ Bucket string
+}
+
+func NewGCSFS(client *gcs.Client, bucket string) *gcsFS {
+ return &gcsFS{
+ Bucket: strings.TrimPrefix(bucket, "gcs://"),
+ Client: client,
+ }
+}
+
+func (t *gcsFS) Close() error {
+ return t.Client.Close()
+}
+
+func (t *gcsFS) ReadDir(name string) ([]artifact.FileInfo, error) {
+ bucket := t.Client.Bucket(t.Bucket)
+ objs := bucket.Objects(gocontext.TODO(), &gcs.Query{Prefix: name})
+
+ var output []artifact.FileInfo
+ for {
+ obj, err := objs.Next()
+ if err != nil {
+ if errors.Is(err, iterator.Done) {
+ break
+ }
+ return nil, err
+ }
+ if obj == nil {
+ break
+ }
+
+ output = append(output, gcpUtil.GCSFileInfo{Object: obj})
+ }
+
+ return output, nil
+}
+
+func (t *gcsFS) Stat(path string) (os.FileInfo, error) {
+ obj := t.Client.Bucket(t.Bucket).Object(path)
+ attrs, err := obj.Attrs(gocontext.TODO())
+ if err != nil {
+ return nil, err
+ }
+
+ return &gcpUtil.GCSFileInfo{Object: attrs}, nil
+}
+
+func (t *gcsFS) Read(ctx gocontext.Context, path string) (io.ReadCloser, error) {
+ return t.Client.Bucket(t.Bucket).Object(path).NewReader(ctx)
+}
+
+func (t *gcsFS) Write(ctx gocontext.Context, path string, data io.Reader) (os.FileInfo, error) {
+ obj := t.Client.Bucket(t.Bucket).Object(path)
+
+ content, err := io.ReadAll(data)
+ if err != nil {
+ return nil, err
+ }
+
+ writer := obj.NewWriter(ctx)
+ if _, err := writer.Write(content); err != nil {
+ return nil, err
+ }
+ if err := writer.Close(); err != nil {
+ return nil, err
+ }
+
+ return t.Stat(path)
+}
diff --git a/artifact/fs/local.go b/artifact/fs/local.go
new file mode 100644
index 000000000..3d761ab12
--- /dev/null
+++ b/artifact/fs/local.go
@@ -0,0 +1,135 @@
+package fs
+
+import (
+ gocontext "context"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/bmatcuk/doublestar/v4"
+ "github.com/flanksource/duty/artifact"
+)
+
+func (t *localFS) safePath(path string) (string, error) {
+ full := filepath.Join(t.base, path)
+ abs, err := filepath.Abs(full)
+ if err != nil {
+ return "", fmt.Errorf("resolving path: %w", err)
+ }
+ baseAbs, err := filepath.Abs(t.base)
+ if err != nil {
+ return "", fmt.Errorf("resolving base: %w", err)
+ }
+ if !strings.HasPrefix(abs, baseAbs) {
+ return "", fmt.Errorf("path %q escapes base directory", path)
+ }
+ return full, nil
+}
+
+type localFS struct {
+ base string
+}
+
+type localFileInfo struct {
+ os.FileInfo
+ fullpath string
+}
+
+func (t localFileInfo) FullPath() string {
+ return t.fullpath
+}
+
+func NewLocalFS(base string) *localFS {
+ return &localFS{base: base}
+}
+
+func (t *localFS) Close() error {
+ return nil
+}
+
+func (t *localFS) ReadDir(name string) ([]artifact.FileInfo, error) {
+ if strings.Contains(name, "*") {
+ return t.ReadDirGlob(name)
+ }
+
+ path := filepath.Join(t.base, name)
+ files, err := os.ReadDir(path)
+ if err != nil {
+ return nil, err
+ }
+
+ output := make([]artifact.FileInfo, 0, len(files))
+ for _, match := range files {
+ fullPath := filepath.Join(path, match.Name())
+ info, err := os.Stat(fullPath)
+ if err != nil {
+ return nil, err
+ }
+
+ output = append(output, localFileInfo{FileInfo: info, fullpath: fullPath})
+ }
+
+ return output, nil
+}
+
+func (t *localFS) ReadDirGlob(name string) ([]artifact.FileInfo, error) {
+ base, pattern := doublestar.SplitPattern(filepath.Join(t.base, name))
+ matches, err := doublestar.Glob(os.DirFS(base), pattern)
+ if err != nil {
+ return nil, err
+ }
+
+ output := make([]artifact.FileInfo, 0, len(matches))
+ for _, match := range matches {
+ fullPath := filepath.Join(base, match)
+ info, err := os.Stat(fullPath)
+ if err != nil {
+ return nil, err
+ }
+
+ output = append(output, localFileInfo{FileInfo: info, fullpath: fullPath})
+ }
+
+ return output, nil
+}
+
+func (t *localFS) Stat(name string) (os.FileInfo, error) {
+ p, err := t.safePath(name)
+ if err != nil {
+ return nil, err
+ }
+ return os.Stat(p)
+}
+
+func (t *localFS) Read(_ gocontext.Context, path string) (io.ReadCloser, error) {
+ p, err := t.safePath(path)
+ if err != nil {
+ return nil, err
+ }
+ return os.Open(p)
+}
+
+func (t *localFS) Write(_ gocontext.Context, path string, data io.Reader) (os.FileInfo, error) {
+ fullpath, err := t.safePath(path)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := os.MkdirAll(filepath.Dir(fullpath), os.ModePerm); err != nil {
+ return nil, fmt.Errorf("error creating base directory: %w", err)
+ }
+
+ f, err := os.Create(fullpath)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ if _, err = io.Copy(f, data); err != nil {
+ return nil, err
+ }
+
+ return t.Stat(path)
+}
diff --git a/artifact/fs/s3.go b/artifact/fs/s3.go
new file mode 100644
index 000000000..63bc7e138
--- /dev/null
+++ b/artifact/fs/s3.go
@@ -0,0 +1,139 @@
+package fs
+
+import (
+ gocontext "context"
+ "io"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/aws-sdk-go-v2/service/s3"
+ s3Types "github.com/aws/aws-sdk-go-v2/service/s3/types"
+ "github.com/bmatcuk/doublestar/v4"
+ "github.com/flanksource/commons/utils"
+ "github.com/flanksource/duty/artifact"
+ awsUtil "github.com/flanksource/duty/artifact/clients/aws"
+ "github.com/samber/lo"
+)
+
+const s3ListObjectMaxKeys = 1000
+
+type s3FS struct {
+ maxObjects int
+ Client *s3.Client
+ Bucket string
+}
+
+func NewS3FS(client *s3.Client, bucket string) *s3FS {
+ return &s3FS{
+ maxObjects: 50 * 10_000,
+ Client: client,
+ Bucket: strings.TrimPrefix(bucket, "s3://"),
+ }
+}
+
+func (t *s3FS) SetMaxListItems(max int) {
+ t.maxObjects = max
+}
+
+func (t *s3FS) Close() error {
+ return nil
+}
+
+func (t *s3FS) ReadDir(pattern string) ([]artifact.FileInfo, error) {
+ prefix, glob := doublestar.SplitPattern(pattern)
+ if prefix == "." {
+ prefix = ""
+ }
+
+ req := &s3.ListObjectsV2Input{
+ Bucket: aws.String(t.Bucket),
+ Prefix: aws.String(prefix),
+ }
+
+ if t.maxObjects < s3ListObjectMaxKeys {
+ req.MaxKeys = lo.ToPtr(int32(t.maxObjects))
+ }
+
+ hasGlob := glob != ""
+ var output []artifact.FileInfo
+ var numObjectsFetched int
+ for {
+ resp, err := t.Client.ListObjectsV2(gocontext.TODO(), req)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, obj := range resp.Contents {
+ if hasGlob {
+ if matched, err := doublestar.Match(pattern, *obj.Key); err != nil {
+ return nil, err
+ } else if !matched {
+ continue
+ }
+ }
+
+ fileInfo := &awsUtil.S3FileInfo{Object: obj}
+ output = append(output, fileInfo)
+ }
+
+ if resp.NextContinuationToken == nil {
+ break
+ }
+
+ numObjectsFetched += int(*resp.KeyCount)
+ if numObjectsFetched >= t.maxObjects {
+ break
+ }
+
+ req.ContinuationToken = resp.NextContinuationToken
+ }
+
+ return output, nil
+}
+
+func (t *s3FS) Stat(path string) (fs.FileInfo, error) {
+ headObject, err := t.Client.HeadObject(gocontext.TODO(), &s3.HeadObjectInput{
+ Bucket: aws.String(t.Bucket),
+ Key: aws.String(path),
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return &awsUtil.S3FileInfo{
+ Object: s3Types.Object{
+ Key: utils.Ptr(filepath.Base(path)),
+ Size: headObject.ContentLength,
+ LastModified: headObject.LastModified,
+ ETag: headObject.ETag,
+ },
+ }, nil
+}
+
+func (t *s3FS) Read(ctx gocontext.Context, key string) (io.ReadCloser, error) {
+ results, err := t.Client.GetObject(ctx, &s3.GetObjectInput{
+ Bucket: aws.String(t.Bucket),
+ Key: aws.String(key),
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return results.Body, nil
+}
+
+func (t *s3FS) Write(ctx gocontext.Context, path string, data io.Reader) (os.FileInfo, error) {
+ _, err := t.Client.PutObject(ctx, &s3.PutObjectInput{
+ Bucket: aws.String(t.Bucket),
+ Key: aws.String(path),
+ Body: data,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return t.Stat(path)
+}
diff --git a/artifact/fs/save.go b/artifact/fs/save.go
new file mode 100644
index 000000000..701de6258
--- /dev/null
+++ b/artifact/fs/save.go
@@ -0,0 +1,77 @@
+package fs
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "io"
+
+ "github.com/flanksource/duty/artifact"
+ "github.com/flanksource/duty/context"
+ "github.com/flanksource/duty/models"
+ "github.com/gabriel-vasile/mimetype"
+)
+
+type MIMEWriter struct {
+ buffer []byte
+ Max int
+}
+
+func (t *MIMEWriter) Write(bb []byte) (n int, err error) {
+ if len(t.buffer) < t.Max {
+ rem := t.Max - len(t.buffer)
+ if rem > len(bb) {
+ rem = len(bb)
+ }
+ t.buffer = append(t.buffer, bb[:rem]...)
+ }
+ return len(bb), nil
+}
+
+func (t *MIMEWriter) Detect() *mimetype.MIME {
+ return mimetype.Detect(t.buffer)
+}
+
+type Artifact struct {
+ ContentType string
+ Path string
+ Content io.ReadCloser
+}
+
+const maxBytesForMimeDetection = 512 * 1024
+
+func SaveArtifact(ctx context.Context, fs artifact.FilesystemRW, a *models.Artifact, data Artifact) error {
+ if a == nil {
+ return fmt.Errorf("artifact model is nil")
+ }
+ if data.Content == nil {
+ return fmt.Errorf("artifact data content is nil")
+ }
+ defer func() { _ = data.Content.Close() }()
+
+ checksum := sha256.New()
+ mimeReader := io.TeeReader(data.Content, checksum)
+
+ mimeWriter := &MIMEWriter{Max: maxBytesForMimeDetection}
+ fileReader := io.TeeReader(mimeReader, mimeWriter)
+
+ info, err := fs.Write(ctx, data.Path, fileReader)
+ if err != nil {
+ return fmt.Errorf("error writing artifact(%s): %w", data.Path, err)
+ }
+
+ if data.ContentType == "" {
+ data.ContentType = mimeWriter.Detect().String()
+ }
+
+ a.Path = data.Path
+ a.Filename = info.Name()
+ a.Size = info.Size()
+ a.ContentType = data.ContentType
+ a.Checksum = hex.EncodeToString(checksum.Sum(nil))
+ if err := ctx.DB().Create(a).Error; err != nil {
+ return fmt.Errorf("error saving artifact to db: %w", err)
+ }
+
+ return nil
+}
diff --git a/artifact/fs/smb.go b/artifact/fs/smb.go
new file mode 100644
index 000000000..4fa16ba42
--- /dev/null
+++ b/artifact/fs/smb.go
@@ -0,0 +1,102 @@
+package fs
+
+import (
+ gocontext "context"
+ "fmt"
+ "io"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/bmatcuk/doublestar/v4"
+ "github.com/flanksource/duty/artifact"
+ "github.com/flanksource/duty/artifact/clients/smb"
+ "github.com/flanksource/duty/types"
+)
+
+type smbFS struct {
+ *smb.SMBSession
+}
+
+type SMBFileInfo struct {
+ Base string
+ os.FileInfo
+}
+
+func (t *SMBFileInfo) FullPath() string {
+ return path.Join(t.Base, t.Name())
+}
+
+func NewSMBFS(server string, port, share string, auth types.Authentication) (*smbFS, error) {
+ if port == "" {
+ port = "445"
+ }
+
+ session, err := smb.SMBConnect(server, port, share, auth)
+ if err != nil {
+ return nil, err
+ }
+
+ return &smbFS{SMBSession: session}, nil
+}
+
+func (s *smbFS) Close() error {
+ return s.SMBSession.Close()
+}
+
+func (s *smbFS) Read(_ gocontext.Context, path string) (io.ReadCloser, error) {
+ return s.Open(path)
+}
+
+func (s *smbFS) Write(_ gocontext.Context, path string, data io.Reader) (os.FileInfo, error) {
+ f, err := s.Create(path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ if _, err = io.Copy(f, data); err != nil {
+ return nil, fmt.Errorf("error writing file: %w", err)
+ }
+
+ return f.Stat()
+}
+
+func (t *smbFS) ReadDir(name string) ([]artifact.FileInfo, error) {
+ if strings.Contains(name, "*") {
+ return t.ReadDirGlob(name)
+ }
+
+ fileInfos, err := t.SMBSession.ReadDir(name)
+ if err != nil {
+ return nil, err
+ }
+
+ output := make([]artifact.FileInfo, 0, len(fileInfos))
+ for _, fileInfo := range fileInfos {
+ output = append(output, &SMBFileInfo{Base: name, FileInfo: fileInfo})
+ }
+
+ return output, nil
+}
+
+func (t *smbFS) ReadDirGlob(name string) ([]artifact.FileInfo, error) {
+ base, pattern := doublestar.SplitPattern(name)
+ matches, err := doublestar.Glob(t.DirFS(base), pattern)
+ if err != nil {
+ return nil, fmt.Errorf("error globbing pattern %q: %w", pattern, err)
+ }
+
+ output := make([]artifact.FileInfo, 0, len(matches))
+ for _, match := range matches {
+ fullPath := filepath.Join(base, match)
+ info, err := t.Stat(fullPath)
+ if err != nil {
+ return nil, err
+ }
+ output = append(output, &SMBFileInfo{Base: base, FileInfo: info})
+ }
+
+ return output, nil
+}
diff --git a/artifact/fs/ssh.go b/artifact/fs/ssh.go
new file mode 100644
index 000000000..8835c0e94
--- /dev/null
+++ b/artifact/fs/ssh.go
@@ -0,0 +1,113 @@
+package fs
+
+import (
+ gocontext "context"
+ "fmt"
+ "io"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+
+ sftpClient "github.com/flanksource/duty/artifact/clients/sftp"
+ "github.com/flanksource/duty/artifact"
+ "github.com/pkg/sftp"
+)
+
+type sshFS struct {
+ *sftp.Client
+ wd string
+}
+
+type sshFileInfo struct {
+ fullpath string
+ fs.FileInfo
+}
+
+func (t *sshFileInfo) FullPath() string {
+ return t.fullpath
+}
+
+func NewSSHFS(host, user, password string) (*sshFS, error) {
+ client, err := sftpClient.SSHConnect(host, user, password)
+ if err != nil {
+ return nil, err
+ }
+
+ wd, err := client.Getwd()
+ if err != nil {
+ return nil, fmt.Errorf("failed to get working directory: %w", err)
+ }
+
+ return &sshFS{
+ wd: wd,
+ Client: client,
+ }, nil
+}
+
+func (t *sshFS) ReadDir(name string) ([]artifact.FileInfo, error) {
+ if strings.Contains(name, "*") {
+ return t.ReadDirGlob(name)
+ }
+
+ files, err := t.Client.ReadDir(name)
+ if err != nil {
+ return nil, err
+ }
+
+ output := make([]artifact.FileInfo, 0, len(files))
+ for _, file := range files {
+ base := name
+ if !strings.HasPrefix(name, "/") {
+ base = filepath.Join(t.wd, name)
+ }
+ output = append(output, &sshFileInfo{FileInfo: file, fullpath: filepath.Join(base, file.Name())})
+ }
+
+ return output, nil
+}
+
+func (t *sshFS) ReadDirGlob(name string) ([]artifact.FileInfo, error) {
+ entries, err := t.Glob(name)
+ if err != nil {
+ return nil, err
+ }
+
+ output := make([]artifact.FileInfo, 0, len(entries))
+ for _, entry := range entries {
+ info, err := t.Stat(entry)
+ if err != nil {
+ return nil, err
+ }
+ output = append(output, &sshFileInfo{FileInfo: info, fullpath: entry})
+ }
+
+ return output, nil
+}
+
+func (s *sshFS) Read(_ gocontext.Context, path string) (io.ReadCloser, error) {
+ return s.Open(path)
+}
+
+func (s *sshFS) Close() error {
+ return s.Client.Close()
+}
+
+func (s *sshFS) Write(_ gocontext.Context, path string, data io.Reader) (os.FileInfo, error) {
+ dir := filepath.Dir(path)
+ if err := s.MkdirAll(dir); err != nil {
+ return nil, fmt.Errorf("error creating directory: %w", err)
+ }
+
+ f, err := s.Create(path)
+ if err != nil {
+ return nil, fmt.Errorf("error creating file: %w", err)
+ }
+ defer f.Close()
+
+ if _, err = io.Copy(f, data); err != nil {
+ return nil, fmt.Errorf("error writing to file: %w", err)
+ }
+
+ return f.Stat()
+}
diff --git a/artifact/inline.go b/artifact/inline.go
new file mode 100644
index 000000000..033f61ffd
--- /dev/null
+++ b/artifact/inline.go
@@ -0,0 +1,134 @@
+package artifact
+
+import (
+ "bytes"
+ gocontext "context"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/flanksource/duty/models"
+ "gorm.io/gorm"
+)
+
+type InlineStore struct {
+ db *gorm.DB
+ compression string
+ maxSize int
+}
+
+func NewInlineStore(db *gorm.DB) *InlineStore {
+ return &InlineStore{db: db, compression: "gzip", maxSize: 1048576}
+}
+
+func (s *InlineStore) WithCompression(compression string) *InlineStore {
+ s.compression = compression
+ return s
+}
+
+func (s *InlineStore) WithMaxSize(maxSize int) *InlineStore {
+ s.maxSize = maxSize
+ return s
+}
+
+func (s *InlineStore) Write(_ gocontext.Context, path string, data io.Reader) (os.FileInfo, error) {
+ raw, err := io.ReadAll(data)
+ if err != nil {
+ return nil, fmt.Errorf("reading artifact data: %w", err)
+ }
+
+ a := models.Artifact{
+ Path: path,
+ Filename: path,
+ }
+ if err := a.SetContent(raw, s.compression, s.maxSize); err != nil {
+ return nil, fmt.Errorf("setting inline content for %s: %w", path, err)
+ }
+
+ return &inlineFileInfo{
+ name: a.Filename,
+ size: a.Size,
+ mod: time.Now(),
+ path: path,
+ artifact: &a,
+ }, nil
+}
+
+// InlineFileInfo returns the underlying artifact with inline content set.
+// Used by blobStore to persist the artifact with content.
+func InlineArtifact(info os.FileInfo) *models.Artifact {
+ if fi, ok := info.(*inlineFileInfo); ok {
+ return fi.artifact
+ }
+ return nil
+}
+
+func (s *InlineStore) Read(_ gocontext.Context, path string) (io.ReadCloser, error) {
+ var artifact models.Artifact
+ if err := s.db.Where("path = ?", path).First(&artifact).Error; err != nil {
+ return nil, fmt.Errorf("finding inline artifact %s: %w", path, err)
+ }
+
+ content, err := artifact.GetContent()
+ if err != nil {
+ return nil, fmt.Errorf("decompressing inline artifact %s: %w", path, err)
+ }
+
+ return io.NopCloser(bytes.NewReader(content)), nil
+}
+
+func (s *InlineStore) ReadDir(name string) ([]FileInfo, error) {
+ var artifacts []models.Artifact
+ pattern := strings.ReplaceAll(name, "*", "%")
+ if !strings.Contains(pattern, "%") {
+ pattern += "%"
+ }
+ if err := s.db.Where("path LIKE ?", pattern).Find(&artifacts).Error; err != nil {
+ return nil, fmt.Errorf("listing inline artifacts under %s: %w", name, err)
+ }
+
+ infos := make([]FileInfo, len(artifacts))
+ for i, a := range artifacts {
+ infos[i] = &inlineFileInfo{
+ name: a.Filename,
+ size: a.Size,
+ mod: a.CreatedAt,
+ path: a.Path,
+ }
+ }
+ return infos, nil
+}
+
+func (s *InlineStore) Stat(name string) (os.FileInfo, error) {
+ var artifact models.Artifact
+ if err := s.db.Where("path = ?", name).First(&artifact).Error; err != nil {
+ return nil, fmt.Errorf("stat inline artifact %s: %w", name, err)
+ }
+
+ return &inlineFileInfo{
+ name: artifact.Filename,
+ size: artifact.Size,
+ mod: artifact.CreatedAt,
+ path: artifact.Path,
+ }, nil
+}
+
+func (s *InlineStore) Close() error { return nil }
+
+type inlineFileInfo struct {
+ name string
+ size int64
+ mod time.Time
+ path string
+ artifact *models.Artifact
+}
+
+func (f *inlineFileInfo) Name() string { return f.name }
+func (f *inlineFileInfo) Size() int64 { return f.size }
+func (f *inlineFileInfo) Mode() os.FileMode { return 0444 }
+func (f *inlineFileInfo) ModTime() time.Time { return f.mod }
+func (f *inlineFileInfo) IsDir() bool { return false }
+func (f *inlineFileInfo) Sys() any { return nil }
+func (f *inlineFileInfo) FullPath() string { return f.path }
diff --git a/artifact/logged.go b/artifact/logged.go
new file mode 100644
index 000000000..fba21742e
--- /dev/null
+++ b/artifact/logged.go
@@ -0,0 +1,165 @@
+package artifact
+
+import (
+ gocontext "context"
+ "crypto/sha256"
+ "fmt"
+ "hash"
+ "io"
+ "os"
+ "time"
+
+ "github.com/flanksource/clicky"
+ "github.com/flanksource/commons/logger"
+ "github.com/flanksource/duty/models"
+ "github.com/google/uuid"
+)
+
+// LoggedBlobStore wraps a BlobStore with structured logging.
+type LoggedBlobStore struct {
+ inner BlobStore
+ logger logger.Logger
+ backend string
+}
+
+func NewLoggedBlobStore(inner BlobStore, log logger.Logger, backend string) BlobStore {
+ return &LoggedBlobStore{inner: inner, logger: log, backend: backend}
+}
+
+func (l *LoggedBlobStore) Write(data Data, a *models.Artifact) (*models.Artifact, error) {
+ l.logger.Debugf("%s", l.formatOp("Write", data.Filename))
+ start := time.Now()
+ result, err := l.inner.Write(data, a)
+ if v := l.logger.V(2); err == nil && result != nil {
+ v.Infof("%s", l.formatResult("Write", result.Filename, result.Size, result.Checksum, time.Since(start)))
+ }
+ return result, err
+}
+
+func (l *LoggedBlobStore) Read(artifactID uuid.UUID) (*Data, error) {
+ l.logger.Debugf("%s", l.formatOp("Read", artifactID.String()))
+ start := time.Now()
+ data, err := l.inner.Read(artifactID)
+ if v := l.logger.V(2); err == nil && data != nil {
+ v.Infof("%s %s %s", l.formatOp("Read", artifactID.String()), data.Pretty().String(), clicky.Text(time.Since(start).String(), "text-gray-500").String())
+ }
+ return data, err
+}
+
+func (l *LoggedBlobStore) Close() error {
+ l.logger.V(2).Infof("%s", l.formatOp("Close", ""))
+ return l.inner.Close()
+}
+
+func (l *LoggedBlobStore) formatOp(op, detail string) string {
+ return clicky.Text(fmt.Sprintf("[%s]", l.backend), "text-blue-500").
+ AddText(" "+op, "font-bold").
+ AddText(" "+detail, "text-gray-300").
+ String()
+}
+
+func (l *LoggedBlobStore) formatResult(op, filename string, size int64, checksum string, duration time.Duration) string {
+ s := clicky.Text(fmt.Sprintf("[%s]", l.backend), "text-blue-500").
+ AddText(" "+op, "font-bold").
+ AddText(" "+filename, "text-gray-300")
+ if size >= 0 {
+ s = s.AddText(fmt.Sprintf(" %s", formatBytes(size)), "text-gray-400")
+ }
+ if checksum != "" {
+ short := checksum
+ if len(short) > 8 {
+ short = short[:8]
+ }
+ s = s.AddText(" sha:"+short, "text-yellow-500")
+ }
+ s = s.AddText(fmt.Sprintf(" %s", duration), "text-gray-500")
+ return s.String()
+}
+
+// LoggedFS wraps a FilesystemRW with structured logging (used by e2e tests).
+type LoggedFS struct {
+ inner FilesystemRW
+ logger logger.Logger
+ backend string
+}
+
+func NewLoggedFS(inner FilesystemRW, log logger.Logger, backend string) *LoggedFS {
+ return &LoggedFS{inner: inner, logger: log, backend: backend}
+}
+
+func (l *LoggedFS) Read(ctx gocontext.Context, path string) (io.ReadCloser, error) {
+ l.logger.Debugf("[%s] Read %s", l.backend, path)
+ start := time.Now()
+ r, err := l.inner.Read(ctx, path)
+ if err != nil {
+ return nil, err
+ }
+ if v := l.logger.V(2); v.Enabled() {
+ return &checksumReader{
+ ReadCloser: r,
+ hash: sha256.New(),
+ onClose: func(h hash.Hash, n int64) {
+ v.Infof("[%s] Read %s (%s, sha:%x, %s)", l.backend, path, formatBytes(n), h.Sum(nil)[:4], time.Since(start))
+ },
+ }, nil
+ }
+ return r, nil
+}
+
+func (l *LoggedFS) Write(ctx gocontext.Context, path string, data io.Reader) (os.FileInfo, error) {
+ l.logger.Debugf("[%s] Write %s", l.backend, path)
+ start := time.Now()
+ info, err := l.inner.Write(ctx, path, data)
+ if v := l.logger.V(2); err == nil && info != nil {
+ v.Infof("[%s] Write %s (%s, %s)", l.backend, path, formatBytes(info.Size()), time.Since(start))
+ }
+ return info, err
+}
+
+func (l *LoggedFS) ReadDir(name string) ([]FileInfo, error) {
+ l.logger.Debugf("[%s] ReadDir %s", l.backend, name)
+ start := time.Now()
+ entries, err := l.inner.ReadDir(name)
+ if v := l.logger.V(2); err == nil {
+ v.Infof("[%s] ReadDir %s (%d entries, %s)", l.backend, name, len(entries), time.Since(start))
+ }
+ return entries, err
+}
+
+func (l *LoggedFS) Stat(name string) (os.FileInfo, error) {
+ l.logger.Debugf("[%s] Stat %s", l.backend, name)
+ info, err := l.inner.Stat(name)
+ if v := l.logger.V(2); err == nil && info != nil {
+ v.Infof("[%s] Stat %s (%s)", l.backend, name, formatBytes(info.Size()))
+ }
+ return info, err
+}
+
+func (l *LoggedFS) Close() error {
+ l.logger.V(2).Infof("[%s] Close", l.backend)
+ return l.inner.Close()
+}
+
+type checksumReader struct {
+ io.ReadCloser
+ hash hash.Hash
+ n int64
+ onClose func(hash.Hash, int64)
+}
+
+func (r *checksumReader) Read(p []byte) (int, error) {
+ n, err := r.ReadCloser.Read(p)
+ if n > 0 {
+ r.hash.Write(p[:n])
+ r.n += int64(n)
+ }
+ return n, err
+}
+
+func (r *checksumReader) Close() error {
+ err := r.ReadCloser.Close()
+ r.onClose(r.hash, r.n)
+ return err
+}
+
+var _ io.ReadCloser = (*checksumReader)(nil)
diff --git a/connection/aws.go b/connection/aws.go
index 08bb38def..346bb9380 100644
--- a/connection/aws.go
+++ b/connection/aws.go
@@ -153,7 +153,7 @@ func (t *AWSConnection) Client(ctx context.Context, opts ...types.ClientOption)
tr = harCollector.Middleware()(tr)
}
- if ctx.IsTrace() {
+ if ctx.IsTrace() && harCollector == nil {
httplogger := &httpretty.Logger{
Time: true,
TLS: ctx.Logger.IsLevelEnabled(7),
diff --git a/connection/blob.go b/connection/blob.go
new file mode 100644
index 000000000..0b6917fe2
--- /dev/null
+++ b/connection/blob.go
@@ -0,0 +1,165 @@
+package connection
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+ "strconv"
+
+ "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
+ "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
+ "github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/flanksource/commons/logger"
+ "github.com/flanksource/duty/artifact"
+ artifactFS "github.com/flanksource/duty/artifact/fs"
+ "github.com/flanksource/duty/context"
+ "github.com/flanksource/duty/models"
+ "github.com/flanksource/duty/types"
+ "github.com/henvic/httpretty"
+)
+
+var blobsLogger = logger.GetLogger("blobs")
+
+func init() {
+ context.BlobStoreProvider = getBlobStore
+}
+
+func getBlobStore(ctx context.Context, connURL string) (artifact.BlobStore, error) {
+ conn, err := ctx.HydrateConnectionByURL(connURL)
+ if err != nil {
+ return nil, fmt.Errorf("resolving artifact connection %q: %w", connURL, err)
+ }
+ if conn == nil {
+ return nil, fmt.Errorf("artifact connection %q not found", connURL)
+ }
+
+ fs, backend, err := getFSForConnection(ctx, *conn)
+ if err != nil {
+ return nil, err
+ }
+
+ blobsLogger.Infof("Initializing %s blob store", backend)
+ store := artifact.NewBlobStore(fs, ctx.DB(), backend)
+ return artifact.NewLoggedBlobStore(store, blobsLogger, backend), nil
+}
+
+func debugTransport() http.RoundTripper {
+ httpLogger := &httpretty.Logger{
+ Time: true,
+ TLS: true,
+ RequestHeader: true,
+ RequestBody: false,
+ ResponseHeader: true,
+ ResponseBody: false,
+ Colors: true,
+ Formatters: []httpretty.Formatter{&httpretty.JSONFormatter{}},
+ }
+ return httpLogger.RoundTripper(http.DefaultTransport)
+}
+
+func GetFSForConnection(ctx context.Context, c models.Connection) (artifact.FilesystemRW, error) {
+ fs, _, err := getFSForConnection(ctx, c)
+ return fs, err
+}
+
+func getFSForConnection(ctx context.Context, c models.Connection) (artifact.FilesystemRW, string, error) {
+ useDebugTransport := blobsLogger.V(3).Enabled()
+
+ switch c.Type {
+ case models.ConnectionTypeFolder:
+ return artifactFS.NewLocalFS(c.Properties["path"]), "local", nil
+
+ case models.ConnectionTypeS3:
+ var conn S3Connection
+ conn.ConnectionName = c.ID.String()
+ if err := conn.Populate(ctx); err != nil {
+ return nil, "", err
+ }
+
+ if c.Properties["bucket"] != "" {
+ conn.Bucket = c.Properties["bucket"]
+ }
+ if val, ok := c.Properties["usePathStyle"]; ok {
+ if b, err := strconv.ParseBool(val); err == nil {
+ conn.UsePathStyle = b
+ }
+ }
+
+ cfg, err := conn.Client(ctx)
+ if err != nil {
+ return nil, "", err
+ }
+
+ if useDebugTransport {
+ cfg.HTTPClient = &http.Client{Transport: debugTransport()}
+ }
+
+ client := s3.NewFromConfig(cfg, func(o *s3.Options) {
+ o.UsePathStyle = conn.UsePathStyle
+ if conn.Endpoint != "" {
+ o.BaseEndpoint = &conn.Endpoint
+ }
+ })
+
+ return artifactFS.NewS3FS(client, conn.Bucket), "s3", nil
+
+ case models.ConnectionTypeGCS:
+ var conn GCSConnection
+ conn.ConnectionName = c.ID.String()
+ if err := conn.HydrateConnection(ctx); err != nil {
+ return nil, "", err
+ }
+
+ if c.Properties["bucket"] != "" {
+ conn.Bucket = c.Properties["bucket"]
+ }
+
+ client, err := conn.Client(ctx)
+ if err != nil {
+ return nil, "", err
+ }
+
+ return artifactFS.NewGCSFS(client, conn.Bucket), "gcs", nil
+
+ case models.ConnectionTypeSFTP:
+ parsedURL, err := url.Parse(c.URL)
+ if err != nil {
+ return nil, "", err
+ }
+ port := c.Properties["port"]
+ if port == "" {
+ port = "22"
+ }
+ fs, err := artifactFS.NewSSHFS(fmt.Sprintf("%s:%s", parsedURL.Host, port), c.Username, c.Password)
+ return fs, "sftp", err
+
+ case models.ConnectionTypeSMB:
+ fs, err := artifactFS.NewSMBFS(c.URL, c.Properties["port"], c.Properties["share"], types.Authentication{
+ Username: types.EnvVar{ValueStatic: c.Username},
+ Password: types.EnvVar{ValueStatic: c.Password},
+ })
+ return fs, "smb", err
+
+ case models.ConnectionTypeAzure:
+ container := c.Properties["container"]
+ connStr := fmt.Sprintf("DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s;BlobEndpoint=%s",
+ c.Username, c.Password, c.URL)
+
+ var opts *azblob.ClientOptions
+ if useDebugTransport {
+ opts = &azblob.ClientOptions{
+ ClientOptions: policy.ClientOptions{
+ Transport: &http.Client{Transport: debugTransport()},
+ },
+ }
+ }
+
+ client, err := azblob.NewClientFromConnectionString(connStr, opts)
+ if err != nil {
+ return nil, "", fmt.Errorf("creating Azure Blob client: %w", err)
+ }
+ return artifactFS.NewAzureBlobFS(client, container), "azure", nil
+ }
+
+ return nil, "", fmt.Errorf("unsupported connection type %q for blob store", c.Type)
+}
diff --git a/connection/gcp.go b/connection/gcp.go
index 7ffc610a8..c1b3db5e8 100644
--- a/connection/gcp.go
+++ b/connection/gcp.go
@@ -45,7 +45,7 @@ func (t *GCPConnection) FromModel(connection models.Connection) {
}
func (g *GCPConnection) TokenSource(ctx context.Context, scopes ...string) (oauth2.TokenSource, error) {
- creds, err := google.CredentialsFromJSON(ctx, []byte(g.Credentials.ValueStatic), scopes...)
+ creds, err := google.CredentialsFromJSON(ctx, []byte(g.Credentials.ValueStatic), scopes...) //nolint:staticcheck
if err != nil {
return nil, err
}
diff --git a/connection/gcs.go b/connection/gcs.go
index 9c7bdc22b..b0c44aebd 100644
--- a/connection/gcs.go
+++ b/connection/gcs.go
@@ -62,7 +62,7 @@ func (g *GCSConnection) Client(ctx context.Context, opts ...types.ClientOption)
if err != nil {
return nil, err
}
- creds, err := google.CredentialsFromJSON(ctx, []byte(credential), gcs.ScopeReadWrite)
+ creds, err := google.CredentialsFromJSON(ctx, []byte(credential), gcs.ScopeReadWrite) //nolint:staticcheck
if err != nil {
return nil, err
}
diff --git a/connection/gke.go b/connection/gke.go
index 74f62f352..fb4086534 100644
--- a/connection/gke.go
+++ b/connection/gke.go
@@ -70,7 +70,7 @@ func (t *GKEConnection) Client(ctx context.Context, opts ...types.ClientOption)
if err != nil {
return nil, err
}
- creds, err := google.CredentialsFromJSON(ctx, []byte(credential), container.CloudPlatformScope)
+ creds, err := google.CredentialsFromJSON(ctx, []byte(credential), container.CloudPlatformScope) //nolint:staticcheck
if err != nil {
return nil, err
}
diff --git a/context/blobs.go b/context/blobs.go
new file mode 100644
index 000000000..85ccd5526
--- /dev/null
+++ b/context/blobs.go
@@ -0,0 +1,30 @@
+package context
+
+import (
+ "fmt"
+
+ "github.com/flanksource/commons/logger"
+ "github.com/flanksource/duty/artifact"
+)
+
+var blobsLogger = logger.GetLogger("blobs")
+
+// BlobStoreProvider resolves a connection URL and returns a BlobStore backed by an external FS.
+// It is set by the connection package during init().
+var BlobStoreProvider func(ctx Context, connURL string) (artifact.BlobStore, error)
+
+// Blobs returns the appropriate blob store for this context.
+// If an artifacts.connection property is configured and a provider is registered,
+// it returns the external backend. Otherwise it returns the inline DB-backed store.
+func (k Context) Blobs() (artifact.BlobStore, error) {
+ connURL := k.Properties().String("artifacts.connection", "")
+ if connURL == "" {
+ blobsLogger.Infof("Initializing inline blob store")
+ store := artifact.NewBlobStore(artifact.NewInlineStore(k.DB()), k.DB(), "inline")
+ return artifact.NewLoggedBlobStore(store, blobsLogger, "inline"), nil
+ }
+ if BlobStoreProvider == nil {
+ return nil, fmt.Errorf("artifacts.connection is configured as %q but no blob store provider is registered", connURL)
+ }
+ return BlobStoreProvider(k, connURL)
+}
diff --git a/go.mod b/go.mod
index f57209ba1..35cb3f5dc 100644
--- a/go.mod
+++ b/go.mod
@@ -12,6 +12,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0
github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery v1.2.0
+ github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4
github.com/Masterminds/squirrel v1.5.4
github.com/RaveNoX/go-jsonmerge v1.0.0
github.com/TomOnTime/utfutil v1.0.0
@@ -78,25 +79,26 @@ require (
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
+ github.com/testcontainers/testcontainers-go v0.41.0
github.com/timberio/go-datemath v0.1.0
github.com/zclconf/go-cty v1.17.0
go.opentelemetry.io/otel v1.41.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0
- go.opentelemetry.io/otel/sdk v1.40.0
+ go.opentelemetry.io/otel/sdk v1.41.0
go.opentelemetry.io/otel/trace v1.41.0
gocloud.dev v0.44.0
gocloud.dev/pubsub/kafkapubsub v0.44.0
gocloud.dev/pubsub/natspubsub v0.44.0
golang.org/x/crypto v0.48.0
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa
- golang.org/x/oauth2 v0.34.0
+ golang.org/x/oauth2 v0.35.0
golang.org/x/sync v0.19.0
gonum.org/v1/gonum v0.17.0
google.golang.org/api v0.262.0
- google.golang.org/grpc v1.78.0
+ google.golang.org/grpc v1.79.1
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/postgres v1.6.0
gorm.io/gorm v1.31.1
@@ -111,7 +113,29 @@ require (
)
require (
+ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
+ github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+ github.com/containerd/errdefs v1.0.0 // indirect
+ github.com/containerd/errdefs/pkg v0.3.0 // indirect
+ github.com/containerd/log v0.1.0 // indirect
+ github.com/containerd/platforms v0.2.1 // indirect
+ github.com/cpuguy83/dockercfg v0.3.2 // indirect
+ github.com/docker/docker v28.5.2+incompatible // indirect
+ github.com/docker/go-connections v0.6.0 // indirect
+ github.com/docker/go-units v0.5.0 // indirect
+ github.com/ebitengine/purego v0.10.0 // indirect
+ github.com/magiconair/properties v1.8.10 // indirect
+ github.com/moby/docker-image-spec v1.3.1 // indirect
+ github.com/moby/go-archive v0.2.0 // indirect
+ github.com/moby/patternmatcher v0.6.0 // indirect
+ github.com/moby/sys/sequential v0.6.0 // indirect
+ github.com/moby/sys/user v0.4.0 // indirect
+ github.com/moby/sys/userns v0.1.0 // indirect
+ github.com/moby/term v0.5.2 // indirect
+ github.com/morikuni/aec v1.0.0 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
+ github.com/opencontainers/image-spec v1.1.1 // indirect
+ github.com/shirou/gopsutil/v4 v4.26.2 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
)
@@ -160,7 +184,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 // indirect
github.com/aws/aws-sdk-go-v2/service/kms v1.49.5 // indirect
- github.com/aws/aws-sdk-go-v2/service/s3 v1.95.1 // indirect
+ github.com/aws/aws-sdk-go-v2/service/s3 v1.95.1
github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sns v1.39.11 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.10 // indirect
@@ -170,7 +194,7 @@ require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect
- github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
+ github.com/bmatcuk/doublestar/v4 v4.10.0
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cert-manager/cert-manager v1.19.4 // indirect
@@ -203,7 +227,7 @@ require (
github.com/flanksource/kubectl-neat v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
- github.com/gabriel-vasile/mimetype v1.4.12 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.12
github.com/geoffgarside/ber v1.2.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
@@ -251,11 +275,11 @@ require (
github.com/gosimple/slug v1.15.0 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
- github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.5 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hairyhenderson/toml v0.4.2-0.20210923231440-40456b8e66cf // indirect
github.com/hairyhenderson/yaml v0.0.0-20220618171115-2d35fca545ce // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
- github.com/hirochachacha/go-smb2 v1.1.0 // indirect
+ github.com/hirochachacha/go-smb2 v1.1.0
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/itchyny/timefmt-go v0.1.7 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
@@ -315,7 +339,7 @@ require (
github.com/pierrec/lz4/v4 v4.1.25 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
- github.com/pkg/sftp v1.13.10 // indirect
+ github.com/pkg/sftp v1.13.10
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/playwright-community/playwright-go v0.5700.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
@@ -370,7 +394,7 @@ require (
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect
- go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v1.41.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
diff --git a/go.sum b/go.sum
index 300fded01..c8e387f8d 100644
--- a/go.sum
+++ b/go.sum
@@ -19,26 +19,10 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
-cloud.google.com/go/accessapproval v1.8.8/go.mod h1:RFwPY9JDKseP4gJrX1BlAVsP5O6kI8NdGlTmaeDefmk=
-cloud.google.com/go/accesscontextmanager v1.9.7/go.mod h1:i6e0nd5CPcrh7+YwGq4bKvju5YB9sgoAip+mXU73aMM=
-cloud.google.com/go/aiplatform v1.113.0/go.mod h1:B8fcWtC2vSadapIQqweJrTATJe/odNDjk2uIA5kmXog=
-cloud.google.com/go/analytics v0.30.1/go.mod h1:V/FnINU5kMOsttZnKPnXfKi6clJUHTEXUKQjHxcNK8A=
-cloud.google.com/go/apigateway v1.7.7/go.mod h1:j1bCmrUK1BzVHpiIyTApxB7cRyhivKzltqLmp6j6i7U=
-cloud.google.com/go/apigeeconnect v1.7.7/go.mod h1:ftGK3nca0JePiVLl0A6alaMjKdOc5C+sAkFMyH2RH8U=
-cloud.google.com/go/apigeeregistry v0.10.0/go.mod h1:SAlF5OhKvyLDuwWAaFAIVJjrEqKRrGTPkJs+TWNnSqg=
-cloud.google.com/go/appengine v1.9.7/go.mod h1:y1XpGVeAhbsNzHida79cHbr3pFRsym0ob8xnC8yphbo=
-cloud.google.com/go/area120 v0.9.7/go.mod h1:5nJ0yksmjOMfc4Zpk+okWfJ3A1004FvB82rfia+ZLaY=
-cloud.google.com/go/artifactregistry v1.19.0/go.mod h1:UEAPCgHDFC1q+A8nnVxXHPEy9KCVOeavFBF1fEChQvU=
-cloud.google.com/go/asset v1.22.0/go.mod h1:q80JP2TeWWzMCazYnrAfDf36aQKf1QiKzzpNLflJwf8=
-cloud.google.com/go/assuredworkloads v1.13.0/go.mod h1:o/oHEOnUlribR+uJWTKQo8A5RhSl9K9FNeMOew4TJ3M=
cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs=
cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
-cloud.google.com/go/automl v1.15.0/go.mod h1:U9zOtQb8zVrFNGTuW3BfxeqmLyeleLgT9B12EaXfODg=
-cloud.google.com/go/baremetalsolution v1.4.0/go.mod h1:K6C6g4aS8LW95I0fEHZiBsBlh0UxwDLGf+S/vyfXbvg=
-cloud.google.com/go/batch v1.14.0/go.mod h1:oeQveyG6NDS/ks2ilOP4LzKRmuIaI7GLe0CkR7WF6pk=
-cloud.google.com/go/beyondcorp v1.2.0/go.mod h1:sszcgxpPPBEfLzbI0aYCTg6tT1tyt3CmKav3NZIUcvI=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -47,85 +31,25 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/bigquery v1.72.0 h1:D/yLju+3Ens2IXx7ou1DJ62juBm+/coBInn4VVOg5Cw=
cloud.google.com/go/bigquery v1.72.0/go.mod h1:GUbRtmeCckOE85endLherHD9RsujY+gS7i++c1CqssQ=
-cloud.google.com/go/bigtable v1.41.0/go.mod h1:JlaltP06LEFXaxQdZiarGR9tKsX/II0IkNAKMDrWspI=
-cloud.google.com/go/billing v1.21.0/go.mod h1:ZGairB3EVnb3i09E2SxFxo50p5unPaMTuo1jh6jW9js=
-cloud.google.com/go/binaryauthorization v1.10.0/go.mod h1:WOuiaQkI4PU/okwrcREjSAr2AUtjQgVe+PlrXKOmKKw=
-cloud.google.com/go/certificatemanager v1.9.6/go.mod h1:vWogV874jKZkSRDFCMM3r7wqybv8WXs3XhyNff6o/Zo=
-cloud.google.com/go/channel v1.21.0/go.mod h1:8v3TwHtgLmFxTpL2U+e10CLFOQN8u/Vr9RhYcJUS3y8=
-cloud.google.com/go/cloudbuild v1.25.0/go.mod h1:lCu+T6IPkobPo2Nw+vCE7wuaAl9HbXLzdPx/tcF+oWo=
-cloud.google.com/go/clouddms v1.8.8/go.mod h1:QtCyw+a73dlkDb2q20aTAPvfaTZCepDDi6Gb1AKq0a4=
cloud.google.com/go/cloudsqlconn v1.20.0 h1:5EBr98dktt5QStX6jacFTECTQ4rxfY6qpIUIV9YNRqo=
cloud.google.com/go/cloudsqlconn v1.20.0/go.mod h1:YCoWR0SWYTDf9npeqq8ODFN1WdGMGVC5G74+A3CXXP4=
-cloud.google.com/go/cloudtasks v1.13.7/go.mod h1:H0TThOUG+Ml34e2+ZtW6k6nt4i9KuH3nYAJ5mxh7OM4=
-cloud.google.com/go/compute v1.53.0/go.mod h1:zdogTa7daHhEtEX92+S5IARtQmi/RNVPUfoI8Jhl8Do=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
-cloud.google.com/go/contactcenterinsights v1.17.4/go.mod h1:kZe6yOnKDfpPz2GphDHynxk/Spx+53UX/pGf+SmWAKM=
-cloud.google.com/go/container v1.45.0/go.mod h1:eB6jUfJLjne9VsTDGcH7mnj6JyZK+KOUIA6KZnYE/ds=
-cloud.google.com/go/containeranalysis v0.14.2/go.mod h1:FjppROiUtP9cyMegdWdY/TsBSGc6kqh1GjA2NOJXXL8=
cloud.google.com/go/datacatalog v1.26.1 h1:bCRKA8uSQN8wGW3Tw0gwko4E9a64GRmbW1nCblhgC2k=
cloud.google.com/go/datacatalog v1.26.1/go.mod h1:2Qcq8vsHNxMDgjgadRFmFG47Y+uuIVsyEGUrlrKEdrg=
-cloud.google.com/go/dataflow v0.11.1/go.mod h1:3s6y/h5Qz7uuxTmKJKBifkYZ3zs63jS+6VGtSu8Cf7Y=
-cloud.google.com/go/dataform v0.12.1/go.mod h1:atGS8ReRjfNDUQib0X/o/7Gi2bqHI2G7/J86LKiGimE=
-cloud.google.com/go/datafusion v1.8.7/go.mod h1:4dkFb1la41qCEXh1AzYtFwl842bu2ikTUXyKhjvFCb0=
-cloud.google.com/go/datalabeling v0.9.7/go.mod h1:EEUVn+wNn3jl19P2S13FqE1s9LsKzRsPuuMRq2CMsOk=
-cloud.google.com/go/dataplex v1.28.0/go.mod h1:VB+xlYJiJ5kreonXsa2cHPj0A3CfPh/mgiHG4JFhbUA=
-cloud.google.com/go/dataproc/v2 v2.15.0/go.mod h1:tSdkodShfzrrUNPDVEL6MdH9/mIEvp/Z9s9PBdbsZg8=
-cloud.google.com/go/dataqna v0.9.8/go.mod h1:2lHKmGPOqzzuqCc5NI0+Xrd5om4ulxGwPpLB4AnFgpA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/datastore v1.21.0/go.mod h1:9l+KyAHO+YVVcdBbNQZJu8svF17Nw5sMKuFR0LYf1nY=
-cloud.google.com/go/datastream v1.15.1/go.mod h1:aV1Grr9LFon0YvqryE5/gF1XAhcau2uxN2OvQJPpqRw=
-cloud.google.com/go/deploy v1.27.3/go.mod h1:7LFIYYTSSdljYRqY3n+JSmIFdD4lv6aMD5xg0crB5iw=
-cloud.google.com/go/dialogflow v1.73.0/go.mod h1:vFkeDO7ishnfakWVLlbgIynQGTFJ/YaVMlYmSn5M+1o=
-cloud.google.com/go/dlp v1.28.0/go.mod h1:C3od1fIK8lf7Kr62aU1Uh0z4OL5Z8s3do3znAiEupAw=
-cloud.google.com/go/documentai v1.39.0/go.mod h1:KmlLO93F7GRU8dENXRxvt+7V8o7eCG6Y6WDitKbcYJs=
-cloud.google.com/go/domains v0.10.7/go.mod h1:T3WG/QUAO/52z4tUPooKS8AY7yXaFxPYn1V3F0/JbNQ=
-cloud.google.com/go/edgecontainer v1.4.4/go.mod h1:yyNVHsCKtsX/0mqFdbljQw0Uo660q2dlMPaiqYiC2Tg=
-cloud.google.com/go/errorreporting v0.4.0/go.mod h1:dZGEhqzdHZSRxxWLVjC3Ue5CVaROzvP58D9rU6zbBfw=
-cloud.google.com/go/essentialcontacts v1.7.7/go.mod h1:ytycWAEn/aKUMRKQPMVgMrAtphEMgjbzL8vFwM3tqXs=
-cloud.google.com/go/eventarc v1.18.0/go.mod h1:/6SDoqh5+9QNUqCX4/oQcJVK16fG/snHBSXu7lrJtO8=
-cloud.google.com/go/filestore v1.10.3/go.mod h1:94ZGyLTx9j+aWKozPQ6Wbq1DuImie/L/HIdGMshtwac=
-cloud.google.com/go/firestore v1.21.0/go.mod h1:1xH6HNcnkf/gGyR8udd6pFO4Z7GWJSwLKQMx/u6UrP4=
-cloud.google.com/go/functions v1.19.7/go.mod h1:xbcKfS7GoIcaXr2FSwmtn9NXal1JR4TV6iYZlgXffwA=
-cloud.google.com/go/gkebackup v1.8.1/go.mod h1:GAaAl+O5D9uISH5MnClUop2esQW4pDa2qe/95A4l7YQ=
-cloud.google.com/go/gkeconnect v0.12.5/go.mod h1:wMD2RXcsAWlkREZWJDVeDV70PYka1iEb9stFmgpw+5o=
-cloud.google.com/go/gkehub v0.16.0/go.mod h1:ADp27Ucor8v81wY+x/5pOxTorxkPj/xswH3AUpN62GU=
-cloud.google.com/go/gkemulticloud v1.6.0/go.mod h1:bGpd4o/Z5Z/XFlaojkgdVisHRwb+fLJvUPzsmV0I9ok=
-cloud.google.com/go/gsuiteaddons v1.7.8/go.mod h1:DBKNHH4YXAdd/rd6zVvtOGAJNGo0ekOh+nIjTUDEJ5U=
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
-cloud.google.com/go/iap v1.11.3/go.mod h1:+gXO0ClH62k2LVlfhHzrpiHQNyINlEVmGAE3+DB4ShU=
-cloud.google.com/go/ids v1.5.7/go.mod h1:N3ZQOIgIBwwOu2tzyhmh3JDT+kt8PcoKkn2BRT9Qe4A=
-cloud.google.com/go/iot v1.8.7/go.mod h1:HvVcypV8LPv1yTXSLCNK+YCtqGHhq+p0F3BXETfpN+U=
cloud.google.com/go/kms v1.25.0 h1:gVqvGGUmz0nYCmtoxWmdc1wli2L1apgP8U4fghPGSbQ=
cloud.google.com/go/kms v1.25.0/go.mod h1:XIdHkzfj0bUO3E+LvwPg+oc7s58/Ns8Nd8Sdtljihbk=
-cloud.google.com/go/language v1.14.6/go.mod h1:7y3J9OexQsfkWNGCxhT+7lb64pa60e12ZCoWDOHxJ1M=
-cloud.google.com/go/lifesciences v0.10.7/go.mod h1:v3AbTki9iWttEls/Wf4ag3EqeLRHofploOcpsLnu7iY=
cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY=
cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw=
cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8=
cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk=
-cloud.google.com/go/managedidentities v1.7.7/go.mod h1:nwNlMxtBo2YJMvsKXRtAD1bL41qiCI9npS7cbqrsJUs=
-cloud.google.com/go/maps v1.26.0/go.mod h1:+auempdONAP8emtm48aCfNo1ZC+3CJniRA1h8J4u7bY=
-cloud.google.com/go/mediatranslation v0.9.7/go.mod h1:mz3v6PR7+Fd/1bYrRxNFGnd+p4wqdc/fyutqC5QHctw=
-cloud.google.com/go/memcache v1.11.7/go.mod h1:AU1jYlUqCihxapcJ1GGMtlMWDVhzjbfUWBXqsXa4rBg=
-cloud.google.com/go/metastore v1.14.8/go.mod h1:h1XI2LpD4ohJhQYn9TwXqKb5sVt6KSo47ft96SiFF1s=
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
-cloud.google.com/go/networkconnectivity v1.19.1/go.mod h1:Q5v6uNNNz8BP232uuXM66XgWML9m379xhwv58Y+8Kb0=
-cloud.google.com/go/networkmanagement v1.21.0/go.mod h1:clG/5Yt0wQ57qSH6Yh7oehQYlobHw3F6nb3Pn4ig5hU=
-cloud.google.com/go/networksecurity v0.11.0/go.mod h1:JLgDsg4tOyJ3eMO8lypjqMftbfd60SJ+P7T+DUmWBsM=
-cloud.google.com/go/notebooks v1.12.7/go.mod h1:uR9pxAkKmlNloibMr9Q1t8WhIu4P2JeqJs7c064/0Mo=
-cloud.google.com/go/optimization v1.7.7/go.mod h1:OY2IAlX23o52qwMAZ0w65wibKuV12a4x6IHDTCq6kcU=
-cloud.google.com/go/orchestration v1.11.10/go.mod h1:tz7m1s4wNEvhNNIM3JOMH0lYxBssu9+7si5MCPw/4/0=
-cloud.google.com/go/orgpolicy v1.15.1/go.mod h1:bpvi9YIyU7wCW9WiXL/ZKT7pd2Ovegyr2xENIeRX5q0=
-cloud.google.com/go/osconfig v1.15.1/go.mod h1:NegylQQl0+5m+I+4Ey/g3HGeQxKkncQ1q+Il4DZ8PME=
-cloud.google.com/go/oslogin v1.14.7/go.mod h1:NB6NqBHfDMwznePdBVX+ILllc1oPCdNSGp5u/WIyndY=
-cloud.google.com/go/phishingprotection v0.9.7/go.mod h1:JTI4HNGyAbWolBoNOoCyCF0e3cqPNrYnlievHU49EwE=
-cloud.google.com/go/policytroubleshooter v1.11.7/go.mod h1:JP/aQ+bUkt4Gz6lQXBi/+A/6nyNRZ0Pvxui5Xl9ieyk=
-cloud.google.com/go/privatecatalog v0.10.8/go.mod h1:BkLHi+rtAGYBt5DocXLytHhF0n6F03Tegxgty40Y7aA=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@@ -134,23 +58,6 @@ cloud.google.com/go/pubsub v1.50.1 h1:fzbXpPyJnSGvWXF1jabhQeXyxdbCIkXTpjXHy7xviB
cloud.google.com/go/pubsub v1.50.1/go.mod h1:6YVJv3MzWJUVdvQXG081sFvS0dWQOdnV+oTo++q/xFk=
cloud.google.com/go/pubsub/v2 v2.3.0 h1:DgAN907x+sP0nScYfBzneRiIhWoXcpCD8ZAut8WX9vs=
cloud.google.com/go/pubsub/v2 v2.3.0/go.mod h1:O5f0KHG9zDheZAd3z5rlCRhxt2JQtB+t/IYLKK3Bpvw=
-cloud.google.com/go/pubsublite v1.8.2/go.mod h1:4r8GSa9NznExjuLPEJlF1VjOPOpgf3IT6k8x/YgaOPI=
-cloud.google.com/go/recaptchaenterprise/v2 v2.21.0/go.mod h1:HxQYqZC2/zl2CvKN7jJEv71vEdDi1GMGNUiZxnpiuVI=
-cloud.google.com/go/recommendationengine v0.9.7/go.mod h1:snZ/FL147u86Jqpv1j95R+CyU5NvL/UzYiyDo6UByTM=
-cloud.google.com/go/recommender v1.13.6/go.mod h1:y5/5womtdOaIM3xx+76vbsiA+8EBTIVfWnxHDFHBGJM=
-cloud.google.com/go/redis v1.18.3/go.mod h1:x8HtXZbvMBDNT6hMHaQ022Pos5d7SP7YsUH8fCJ2Wm4=
-cloud.google.com/go/resourcemanager v1.10.7/go.mod h1:rScGkr6j2eFwxAjctvOP/8sqnEpDbQ9r5CKwKfomqjs=
-cloud.google.com/go/resourcesettings v1.8.3/go.mod h1:BzgfXFHIWOOmHe6ZV9+r3OWfpHJgnqXy8jqwx4zTMLw=
-cloud.google.com/go/retail v1.25.1/go.mod h1:J75G8pd+DH0SHueL9IJw7Y5d2VhTsjFsk+F1t9f8jXc=
-cloud.google.com/go/run v1.14.0/go.mod h1:KStBOpjX7m47Yi1xStWSkvJcCqLr+PMUkz6p3po5/VA=
-cloud.google.com/go/scheduler v1.11.8/go.mod h1:bNKU7/f04eoM6iKQpwVLvFNBgGyJNS87RiFN73mIPik=
-cloud.google.com/go/secretmanager v1.16.0/go.mod h1://C/e4I8D26SDTz1f3TQcddhcmiC3rMEl0S1Cakvs3Q=
-cloud.google.com/go/security v1.19.2/go.mod h1:KXmf64mnOsLVKe8mk/bZpU1Rsvxqc0Ej0A6tgCeN93w=
-cloud.google.com/go/securitycenter v1.38.1/go.mod h1:Ge2D/SlG2lP1FrQD7wXHy8qyeloRenvKXeB4e7zO6z0=
-cloud.google.com/go/servicedirectory v1.12.7/go.mod h1:gOtN+qbuCMH6tj2dqlDY3qQL7w3V0+nkWaZElnJK8Ps=
-cloud.google.com/go/shell v1.8.7/go.mod h1:OTke7qc3laNEW5Jr5OV9VR3IwU5x5VqGOE6705zFex4=
-cloud.google.com/go/spanner v1.87.0/go.mod h1:tcj735Y2aqphB6/l+X5MmwG4NnV+X1NJIbFSZGaHYXw=
-cloud.google.com/go/speech v1.29.0/go.mod h1:wtUmIS/h0ZYU6cPA9klcyST3f6i2FdnvNDqENjrRDds=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
@@ -158,34 +65,15 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.59.1 h1:DXAZLcTimtiXdGqDSnebROVPd9QvRsFVVlptz02Wk58=
cloud.google.com/go/storage v1.59.1/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI=
-cloud.google.com/go/storagetransfer v1.13.1/go.mod h1:S858w5l383ffkdqAqrAA+BC7KlhCqeNieK3sFf5Bj4Y=
-cloud.google.com/go/talent v1.8.4/go.mod h1:3yukBXUTVFNyKcJpUExW/k5gqEy8qW6OCNj7WdN0MWo=
-cloud.google.com/go/texttospeech v1.16.0/go.mod h1:AeSkoH3ziPvapsuyI07TWY4oGxluAjntX+pF4PJ2jy0=
-cloud.google.com/go/tpu v1.8.4/go.mod h1:ul0cyWSHr6jHGZYElZe6HvQn35VY93RAlwpDiSBRnPA=
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
-cloud.google.com/go/translate v1.12.7/go.mod h1:wwJp14NZyWvcrFANhIXutXj0pOBkYciBHwSlUOykcjI=
-cloud.google.com/go/video v1.27.1/go.mod h1:xzfAC77B4vtnbi/TT3UUxEjCa/+Ehy5EA8w470ytOig=
-cloud.google.com/go/videointelligence v1.12.7/go.mod h1:XAk5hCMY+GihxJ55jNoMdwdXSNZnCl3wGs2+94gK7MA=
-cloud.google.com/go/vision/v2 v2.9.6/go.mod h1:lJC+vP15D5znJvHQYjEoTKnpToX1L93BUlvBmzM0gyg=
-cloud.google.com/go/vmmigration v1.10.0/go.mod h1:LDztCWEb+RwS1bPg4Xzt0fcJS9kVrFxa3ejhH7OW9vg=
-cloud.google.com/go/vmwareengine v1.3.6/go.mod h1:ps0rb+Skgpt9ppHYC0o5DqtJ5ld2FyS8sAqtbHH8t9s=
-cloud.google.com/go/vpcaccess v1.8.7/go.mod h1:9RYw5bVvk4Z51Rc8vwXT63yjEiMD/l7XyEaDyrNHgmk=
-cloud.google.com/go/webrisk v1.11.2/go.mod h1:yH44GeXz5iz4HFsIlGeoVvnjwnmfbni7Lwj1SelV4f0=
-cloud.google.com/go/websecurityscanner v1.7.7/go.mod h1:ng/PzARaus3Bj4Os4LpUnyYHsbtJky1HbBDmz148v1o=
-cloud.google.com/go/workflows v1.14.3/go.mod h1:CC9+YdVI2Kvp0L58WajHpEfKJxhrtRh3uQ0SYWcmAk4=
-codeberg.org/go-fonts/liberation v0.5.0/go.mod h1:zS/2e1354/mJ4pGzIIaEtm/59VFCFnYC7YV6YdGl5GU=
-codeberg.org/go-latex/latex v0.1.0/go.mod h1:LA0q/AyWIYrqVd+A9Upkgsb+IqPcmSTKc9Dny04MHMw=
-codeberg.org/go-pdf/fpdf v0.10.0/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU=
-cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
-git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94=
+github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
-github.com/Azure/azure-amqp-common-go/v3 v3.2.3/go.mod h1:7rPmbSfszeovxGfc5fSAXE4ehlXQZHpMja2OtxC2Tas=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
@@ -206,21 +94,20 @@ github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2
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 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw=
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/messaging/azservicebus v1.9.1/go.mod h1:NydgUaroiShkgOcb+X6OUdS3RalWBrvDNtOyFHJtsZY=
github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery v1.2.0 h1:s0SaQtHigowP0n3Kx4ieV94pNZAHlHhS+xjZyLCSVCQ=
github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery v1.2.0/go.mod h1:oI5SPI1vpNJYfP9MPWXthq7jDfh9xTAuQVBKPOu7DPo=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1/go.mod h1:Ng3urmn6dYe8gnbCMoHHVl5APYz2txho3koEkV2o2HA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:Q28U+75mpCaSCDowNEmhIo/rmgdkqmkmzI7N6TGR4UY=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w=
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 v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
-github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1/go.mod h1:8cl44BDmi+effbARHMQjgOKA2AYvcohNm7KEt42mSV8=
-github.com/Azure/go-amqp v1.4.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE=
-github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M=
-github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
+github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 h1:jWQK1GI+LeGGUKBADtcH2rRqPxYB1Ljwms5gFA2LqrM=
+github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4/go.mod h1:8mwH4klAm9DUgR2EEHyEEAQlRDvLPyg5fQry3y+cDew=
+github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
+github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
@@ -231,21 +118,16 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
-github.com/GoogleCloudPlatform/cloudsql-proxy v1.37.8/go.mod h1:exon/I6I+5u/ab7AHmGh0eCXGoYZO5cjqA3wHJlYFFQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.29.0/go.mod h1:rKOFVIPbNs2wZeh7ZeQ0D9p/XLgbNiTr5m7x6KuAshk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/propagator v0.53.0/go.mod h1:dtCRwgvytbGKWdlrjMOg9geBoRwRpCYWIOM/JhVsDIc=
github.com/IBM/sarama v1.46.3 h1:njRsX6jNlnR+ClJ8XmkO+CM4unbrNr/2vB5KK6UA+IE=
github.com/IBM/sarama v1.46.3/go.mod h1:GTUYiF9DMOZVe3FwyGT+dtSPceGFIgA+sPc5u6CBwko=
-github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
-github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
@@ -256,19 +138,15 @@ github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/RaveNoX/go-jsonmerge v1.0.0 h1:2e0nqnadoGUP8rAvcA0hkQelZreVO5X3BHomT2XMrAk=
github.com/RaveNoX/go-jsonmerge v1.0.0/go.mod h1:qYM/NA77LhO4h51JJM7Z+xBU3ovqrNIACZe+SkSNVFo=
-github.com/Snawoot/go-http-digest-auth-client v1.1.3/go.mod h1:WiwNiPXTRGyjTGpBtSQJlM2wDPRRPpFGhMkMWpV4uqg=
github.com/TomOnTime/utfutil v1.0.0 h1:/0Ivgo2OjXJxo8i7zgvs7ewSFZMLwCRGm3P5Umowb90=
github.com/TomOnTime/utfutil v1.0.0/go.mod h1:l9lZmOniizVSuIliSkEf87qivMRlSNzbdBFKjuLRg1c=
-github.com/Venafi/vcert/v5 v5.12.2/go.mod h1:x3l0pB0q0E6wuhPe7nzfkUEwwraK7amnBWQ4LtT1bbw=
github.com/WinterYukky/gorm-extra-clause-plugin v0.4.0 h1:e4gYsN9tNzoBMYKYBaGwwZpSljJhW231+1cBlYwv8YQ=
github.com/WinterYukky/gorm-extra-clause-plugin v0.4.0/go.mod h1:jNWq8AymgsVev9Kq6mke0b3o3yzY6bTSwjMDfTvZPPM=
-github.com/XSAM/otelsql v0.39.0/go.mod h1:uMOXLUX+wkuAuP0AR3B45NXX7E9lJS2mERa8gqdU8R0=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
@@ -277,15 +155,12 @@ github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
-github.com/akamai/AkamaiOPEN-edgegrid-golang/v12 v12.0.0/go.mod h1:Bf6hnZkloZnfL4I/gFGnMMMdMHiu/ERnSOWtFgnodDk=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE=
github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
-github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
-github.com/alecthomas/participle/v2 v2.1.0/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -294,17 +169,12 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
-github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
-github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
-github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE=
github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA=
-github.com/apache/thrift v0.17.0/go.mod h1:OLxhMRJxomX+1I/KUw03qoV3mMz16BwaKI+d4fPBx7Q=
-github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
@@ -312,7 +182,6 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdK
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go v1.44.263/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
-github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU=
github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
@@ -324,13 +193,9 @@ github.com/aws/aws-sdk-go-v2/config v1.32.9/go.mod h1:U+fCQ+9QKsLW786BCfEjYRj34V
github.com/aws/aws-sdk-go-v2/credentials v1.13.24/go.mod h1:jYPYi99wUOPIFi0rhiOvXeSEReVOzBqFNOX5bXYoG2o=
github.com/aws/aws-sdk-go-v2/credentials v1.19.9 h1:sWvTKsyrMlJGEuj/WgrwilpoJ6Xa1+KhIpGdzw7mMU8=
github.com/aws/aws-sdk-go-v2/credentials v1.19.9/go.mod h1:+J44MBhmfVY/lETFiKI+klz0Vym2aCmIjqgClMmW82w=
-github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.5/go.mod h1:VNM08cHlOsIbSHRqb6D/M2L4kKXfJv3A2/f0GNbOQSc=
-github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.7.87/go.mod h1:ZeQC4gVarhdcWeM1c90DyBLaBCNhEeAbKUXwVI/byvw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA=
-github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.5.13/go.mod h1:RxLhhGmjEidlLTRZyk1BLMigHONURhQakw2//prq+DA=
-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.3/go.mod h1:br7KA6edAAqDGUYJ+zVVPAyMrPhnN+zdt17yTUT6FPw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ=
@@ -344,15 +209,12 @@ github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 h1:JqcdRG//czea7Ppjb+g/n4o8i/R
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17/go.mod h1:CO+WeGmIdj/MlPel2KwID9Gt7CNq4M65HUfBW97liM0=
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.63.1 h1:l65dmgr7tO26EcHe6WMdseRnFLoJ2nqdkPz1nJdXfaw=
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.63.1/go.mod h1:wvnXh1w1pGS2UpEvPTKSjXYuxiXhuvob/IMaK2AWvek=
-github.com/aws/aws-sdk-go-v2/service/dynamodb v1.44.0/go.mod h1:mWB0GE1bqcVSvpW7OtFA0sKuHk52+IqtnsYU2jUfYAs=
-github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.26.0/go.mod h1:He/RikglWUczbkV+fkdpcV/3GdL/rTRNVy7VaUiezMo=
github.com/aws/aws-sdk-go-v2/service/eks v1.77.0 h1:Z5mTpmbJKU7jEM7xoXI5tO4Nm0JUZSgVSFkpYuu6Ic0=
github.com/aws/aws-sdk-go-v2/service/eks v1.77.0/go.mod h1:Qg678m+87sCuJhcsZojenz8mblYG+Tq86V4m3hjVz0s=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 h1:Z5EiPIzXKewUQK0QTMkutjiaPVeVYXX7KIqhXu/0fXs=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8/go.mod h1:FsTpJtvC4U1fyDXk7c71XoDv3HlRm8V3NiYLeYLh5YE=
-github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.17/go.mod h1:mC9qMbA6e1pwEq6X3zDGtZRXMG2YaElJkbJlMVHLs5I=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU=
@@ -360,17 +222,14 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 h1:bGeHBsGZx0Dvu
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17/go.mod h1:dcW24lbU0CzHusTE8LLHhRLI42ejmINN8Lcr22bwh/g=
github.com/aws/aws-sdk-go-v2/service/kms v1.49.5 h1:DKibav4XF66XSeaXcrn9GlWGHos6D/vJ4r7jsK7z5CE=
github.com/aws/aws-sdk-go-v2/service/kms v1.49.5/go.mod h1:1SdcmEGUEQE1mrU2sIgeHtcMSxHuybhPvuEPANzIDfI=
-github.com/aws/aws-sdk-go-v2/service/route53 v1.58.4/go.mod h1:xNLZLn4SusktBQ5moqUOgiDKGz3a7vHwF4W0KD+WBPc=
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.1 h1:C2dUPSnEpy4voWFIq3JNd8gN0Y5vYGDo44eUE58a/p8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.1/go.mod h1:5jggDlZ2CLQhwJBiZJb4vfk4f0GxWdEDruWKEJ1xOdo=
-github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.7/go.mod h1:1X1NotbcGHH7PCQJ98PsExSxsJj/VWzz8MfFz43+02M=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M=
github.com/aws/aws-sdk-go-v2/service/sns v1.39.11 h1:Ke7RS0NuP9Xwk31prXYcFGA1Qfn8QmNWcxyjKPcXZdc=
github.com/aws/aws-sdk-go-v2/service/sns v1.39.11/go.mod h1:hdZDKzao0PBfJJygT7T92x2uVcWc/htqlhrjFIjnHDM=
github.com/aws/aws-sdk-go-v2/service/sqs v1.42.21 h1:Oa0IhwDLVrcBHDlNo1aosG4CxO4HyvzDV5xUWqWcBc0=
github.com/aws/aws-sdk-go-v2/service/sqs v1.42.21/go.mod h1:t98Ssq+qtXKXl2SFtaSkuT6X42FSM//fnO6sfq5RqGM=
-github.com/aws/aws-sdk-go-v2/service/ssm v1.60.1/go.mod h1:IyVabkWrs8SNdOEZLyFFcW9bUltV4G6OQS0s6H20PHg=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.10 h1:+VTRawC4iVY58pS/lzpo0lnoa/SYNGF4/B/3/U5ro8Y=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.10/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw=
@@ -389,13 +248,10 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
-github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
-github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
@@ -406,8 +262,6 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
-github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
-github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
github.com/casbin/casbin/v2 v2.135.0 h1:6BLkMQiGotYyS5yYeWgW19vxqugUlvHFkFiLnLR/bxk=
github.com/casbin/casbin/v2 v2.135.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/gorm-adapter/v3 v3.39.0 h1:k15txH6vE4796MuA+LFcU8I1vMjutklyzMXfjDz7lzo=
@@ -415,6 +269,7 @@ github.com/casbin/gorm-adapter/v3 v3.39.0/go.mod h1:kjXoK8MqA3E/CcqEF2l3SCkhJj1Y
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0=
github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
+github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
@@ -426,7 +281,6 @@ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
@@ -441,15 +295,12 @@ github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSg
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/clarkmcc/gorm-sqlite v0.0.0-20240426202654-00ed082c0311 h1://GDWpsQ8pSg8u8SCavanukwPu5yE0Rz3uu7CuFVfFc=
github.com/clarkmcc/gorm-sqlite v0.0.0-20240426202654-00ed082c0311/go.mod h1:HrR53jwmQF7sTyNxEJ3rqfx9sRVnaTUqIo1nXn0KRho=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
-github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
@@ -459,11 +310,18 @@ github.com/cncf/xds/go v0.0.0-20260121142036-a486691bba94 h1:kkHPnzHm5Ln7WA0XYjr
github.com/cncf/xds/go v0.0.0-20260121142036-a486691bba94/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
-github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
-github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
+github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
+github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
+github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
+github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
+github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
+github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
+github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
+github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
+github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -479,14 +337,18 @@ github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+Zlfu
github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
-github.com/digitalocean/godo v1.165.1/go.mod h1:xQsWpVCCbkDrWisHA72hPzPlnC+4W5w/McZY5ij9uvU=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
-github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
+github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
+github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
+github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=
@@ -495,6 +357,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4A
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
+github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/eko/gocache/lib/v4 v4.2.3 h1:s78TFqEGAH3SbzP4N40D755JYT/aaGFKEPrsUtC1chU=
github.com/eko/gocache/lib/v4 v4.2.3/go.mod h1:Zus8mwmaPu1VYOzfomb+Dvx2wV7fT5jDRbHYtQM6MEY=
github.com/eko/gocache/store/go_cache/v4 v4.2.4 h1:toHpoIi4HhuXYv1bFOh5FiEQhpli4sWoSAN74j3/MXw=
@@ -510,8 +374,8 @@ github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM=
-github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs=
+github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
+github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
@@ -519,9 +383,6 @@ github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJP
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
-github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
github.com/exaring/otelpgx v0.10.0 h1:NGGegdoBQM3jNZDKG8ENhigUcgBN7d7943L0YlcIpZc=
github.com/exaring/otelpgx v0.10.0/go.mod h1:R5/M5LWsPPBZc1SrRE5e0DiU48bI78C1/GPTWs6I66U=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
@@ -548,7 +409,6 @@ github.com/flanksource/sandbox-runtime v1.0.1 h1:zBzNx9GoZILo1ot4qI2wd/gqny0vejv
github.com/flanksource/sandbox-runtime v1.0.1/go.mod h1:HCeOqw4QQOpvzDeN3hMdQpxIZ9yrp0/5ziXjiiOw5ec=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
-github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
@@ -566,10 +426,8 @@ github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZ
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
-github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
-github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
@@ -581,7 +439,6 @@ github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiy
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw=
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
@@ -591,7 +448,6 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
-github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
@@ -601,7 +457,6 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@@ -641,7 +496,6 @@ github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxE
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
-github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -655,7 +509,6 @@ github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUW
github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
-github.com/goccmack/gocc v1.0.2/go.mod h1:LXX2tFVUggS/Zgx/ICPOr3MLyusuM7EcbfkPvNsjdO8=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
@@ -664,8 +517,6 @@ github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
@@ -675,9 +526,7 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
-github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -719,7 +568,6 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo=
github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw=
-github.com/google/certificate-transparency-go v1.3.1/go.mod h1:gg+UQlx6caKEDQ9EElFOujyxEQEfOiQzAt6782Bvi8k=
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
@@ -741,7 +589,6 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v57 v57.0.0 h1:L+Y3UPTY8ALM8x+TV0lg+IEBI+upibemtBD8Q9u7zHs=
github.com/google/go-github/v57 v57.0.0/go.mod h1:s0omdnye0hvK/ecLvpsGfJMiRt85PimQh4oygmLIxHw=
-github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY=
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
github.com/google/go-replayers/grpcreplay v1.3.0 h1:1Keyy0m1sIpqstQmgz307zhiJ1pV4uIlFds5weTmxbo=
@@ -749,7 +596,6 @@ github.com/google/go-replayers/grpcreplay v1.3.0/go.mod h1:v6NgKtkijC0d3e3RW8il6
github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk=
github.com/google/go-replayers/httpreplay v1.2.0/go.mod h1:WahEFFZZ7a1P4VM1qEeHy+tME4bwyqPcwWbNlUI1Mcg=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gops v0.3.29 h1:n98J2qSOK1NJvRjdLDcjgDryjpIBGhbaqph1mXKL0rY=
github.com/google/gops v0.3.29/go.mod h1:8N3jZftuPazvUwtYY/ncG4iPrjp15ysNKLfq+QQPiwc=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
@@ -769,7 +615,6 @@ github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmI
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
-github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -792,27 +637,12 @@ github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
-github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU=
-github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.5 h1:jP1RStw811EvUDzsUQ9oESqw2e4RqCjSAD9qIL8eMns=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.5/go.mod h1:WXNBZ64q3+ZUemCMXD9kYnr56H7CgZxDBHCVwstfl3s=
-github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hairyhenderson/toml v0.4.2-0.20210923231440-40456b8e66cf h1:I1sbT4ZbIt9i+hB1zfKw2mE8C12TuGxPiW7YmtLbPa4=
github.com/hairyhenderson/toml v0.4.2-0.20210923231440-40456b8e66cf/go.mod h1:jDHmWDKZY6MIIYltYYfW4Rs7hQ50oS4qf/6spSiZAxY=
github.com/hairyhenderson/yaml v0.0.0-20220618171115-2d35fca545ce h1:cVkYhlWAxwuS2/Yp6qPtcl0fGpcWxuZNonywHZ6/I+s=
github.com/hairyhenderson/yaml v0.0.0-20220618171115-2d35fca545ce/go.mod h1:7TyiGlHI+IO+iJbqRZ82QbFtvgj/AIcFm5qc9DLn7Kc=
-github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo=
-github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
-github.com/hashicorp/go-hmac-drbg v0.0.0-20210916214228-a6e5a68489f6/go.mod h1:y+HSOcOGB48PkUxNyLAiCiY6rEENu+E+Ss4LG8QHwf4=
-github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
-github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
-github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
-github.com/hashicorp/go-secure-stdlib/cryptoutil v0.1.1/go.mod h1:hH8rgXHh9fPSDPerG6WzABHsHF+9ZpLhRI1LPk4JZ8c=
-github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
-github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
-github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
@@ -821,26 +651,19 @@ github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
-github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
-github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM=
-github.com/hashicorp/vault/sdk v0.20.0/go.mod h1:xEjAt/n/2lHBAkYiRPRmvf1d5B6HlisPh2pELlRCosk=
github.com/henvic/httpretty v0.1.4 h1:Jo7uwIRWVFxkqOnErcoYfH90o3ddQyVrSANeS4cxYmU=
github.com/henvic/httpretty v0.1.4/go.mod h1:Dn60sQTZfbt2dYsdUSNsCljyF4AfdqnuJFDLJA1I4AM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI=
github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
-github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
-github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
-github.com/itchyny/go-yaml v0.0.0-20251001235044-fca9a0999f15/go.mod h1:Tmbz8uw5I/I6NvVpEGuhzlElCGS5hPoXJkt7l+ul6LE=
github.com/itchyny/gojq v0.12.18 h1:gFGHyt/MLbG9n6dqnvlliiya2TaMMh6FFaR2b1H6Drc=
github.com/itchyny/gojq v0.12.18/go.mod h1:4hPoZ/3lN9fDL1D+aK7DY1f39XZpY9+1Xpjz8atrEkg=
github.com/itchyny/timefmt-go v0.1.7 h1:xyftit9Tbw+Dc/huSSPJaEmX1TVL8lw5vxjJLK4GMMA=
@@ -928,8 +751,6 @@ github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
-github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
-github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
@@ -945,13 +766,11 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
-github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
@@ -997,7 +816,8 @@ github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQ
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
-github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk=
+github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
+github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
@@ -1011,11 +831,9 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
-github.com/mattn/go-sqlite3 v1.14.30/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
@@ -1023,19 +841,30 @@ github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC
github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU=
github.com/microsoft/go-mssqldb v1.9.6 h1:1MNQg5UiSsokiPz3++K2KPx4moKrwIqly1wv+RyCKTw=
github.com/microsoft/go-mssqldb v1.9.6/go.mod h1:yYMPDufyoF2vVuVCUGtZARr06DKFIhMrluTcgWlXpr4=
-github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
-github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
-github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
-github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
+github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
+github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
+github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
+github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
+github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
+github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
+github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
+github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
+github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
+github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
+github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
+github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
+github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
+github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
+github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -1046,8 +875,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
-github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
-github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
+github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@@ -1069,7 +898,6 @@ github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
-github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
github.com/ohler55/ojg v1.28.0 h1:8xClBgMIRRJGDUC9xNe7NprP4kD2C3mQMeon3wY4KXA=
github.com/ohler55/ojg v1.28.0/go.mod h1:/Y5dGWkekv9ocnUixuETqiL58f+5pAsUfg5P8e7Pa2o=
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
@@ -1082,25 +910,23 @@ github.com/olekukonko/ll v0.1.7 h1:WyK1YZwOTUKHEXZz3VydBDT5t3zDqa9yI8iJg5PHon4=
github.com/olekukonko/ll v0.1.7/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw=
github.com/olekukonko/tablewriter v1.1.3 h1:VSHhghXxrP0JHl+0NnKid7WoEmd9/urKRJLysb70nnA=
github.com/olekukonko/tablewriter v1.1.3/go.mod h1:9VU0knjhmMkXjnMKrZ3+L2JhhtsQ/L38BbL3CRNE8tM=
-github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
github.com/onsi/ginkgo/v2 v2.28.0 h1:Rrf+lVLmtlBIKv6KrIGJCjyY8N36vDVcutbGJkyqjJc=
github.com/onsi/ginkgo/v2 v2.28.0/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
+github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opensearch-project/opensearch-go/v2 v2.3.0 h1:nQIEMr+A92CkhHrZgUhcfsrZjibvB3APXf2a1VwCmMQ=
github.com/opensearch-project/opensearch-go/v2 v2.3.0/go.mod h1:8LDr9FCgUTVoT+5ESjc2+iaZuldqE+23Iq0r1XeNue8=
github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c=
github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM=
-github.com/orisano/pixelmatch v0.0.0-20230914042517-fa304d1dc785/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
-github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0/go.mod h1:lAVhWwbNaveeJmxrxuSTxMgKpF6DjnuVpn6T8WiBwYQ=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
-github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=
github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
@@ -1179,7 +1005,6 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rodaine/table v1.3.0 h1:4/3S3SVkHnVZX91EHFvAMV7K42AnJ0XuymRR2C5HlGE=
github.com/rodaine/table v1.3.0/go.mod h1:47zRsHar4zw0jgxGxL9YtFfs7EGN6B/TaS+/Dmk4WxU=
-github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
@@ -1189,7 +1014,6 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/oops v1.21.0 h1:18atcO4oEigNFuGXqr3NZWZ6P0XOSEXyBSAMXdQRxTc=
@@ -1201,6 +1025,8 @@ github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
+github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
+github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/shoenig/go-m1cpu v0.1.7 h1:C76Yd0ObKR82W4vhfjZiCp0HxcSZ8Nqd84v+HZ0qyI0=
github.com/shoenig/go-m1cpu v0.1.7/go.mod h1:KkDOw6m3ZJQAPHbrzkZki4hnx+pDRR1Lo+ldA56wD5w=
github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk=
@@ -1218,9 +1044,6 @@ github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
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/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
-github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
-github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@@ -1229,7 +1052,6 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
-github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
@@ -1250,7 +1072,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-github.com/substrait-io/substrait-go v0.4.2/go.mod h1:qhpnLmrcvAnlZsUyPXZRqldiHapPTXC3t7xFgDi3aQg=
+github.com/testcontainers/testcontainers-go v0.41.0 h1:mfpsD0D36YgkxGj2LrIyxuwQ9i2wCKAD+ESsYM1wais=
+github.com/testcontainers/testcontainers-go v0.41.0/go.mod h1:pdFrEIfaPl24zmBjerWTTYaY0M6UHsqA1YSvsoU40MI=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@@ -1274,7 +1097,6 @@ github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYI
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
@@ -1290,9 +1112,6 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
-github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
-github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
-github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
@@ -1309,8 +1128,6 @@ github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
-github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
@@ -1319,7 +1136,6 @@ github.com/xuri/excelize/v2 v2.10.1 h1:V62UlqopMqha3kOpnlHy2CcRVw1V8E63jFoWUmMzx
github.com/xuri/excelize/v2 v2.10.1/go.mod h1:iG5tARpgaEeIhTqt3/fgXCGoBRt4hNXgCp3tfXKoOIc=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
-github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -1337,18 +1153,9 @@ github.com/zclconf/go-cty-yaml v1.2.0 h1:GDyL4+e/Qe/S0B7YaecMLbVvAR/Mp21CXMOSiCT
github.com/zclconf/go-cty-yaml v1.2.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
-github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
-go.einride.tech/aip v0.73.0/go.mod h1:Mj7rFbmXEgw0dq1dqJ7JGMvYCZZVxmGOR3S4ZcV5LvQ=
-go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
-go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ=
-go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk=
-go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo=
-go.etcd.io/etcd/pkg/v3 v3.6.5/go.mod h1:uqrXrzmMIJDEy5j00bCqhVLzR5jEJIwDp5wTlLwPGOU=
-go.etcd.io/etcd/server/v3 v3.6.5/go.mod h1:PLuhyVXz8WWRhzXDsl3A3zv/+aK9e4A9lpQkqawIaH0=
-go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -1358,33 +1165,30 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
-go.opentelemetry.io/contrib/detectors/aws/ec2 v1.37.0/go.mod h1:gs3y8jvJscW5D+FzrZvJZEsGj+xlMCF0S1x4R6ktiNo=
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
-go.opentelemetry.io/contrib/propagators/aws v1.37.0/go.mod h1:Cy8Hk2E2iSGEbsLnPUdeigrexaAOAGIAmBFK919EQs0=
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
-go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7jlYTzQKE+pnx/iNywFvTbuuI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0/go.mod h1:u3T6vz0gh/NVzgDgiwkgLxpsSF6PaPmo2il0apGJbls=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0 h1:inYW9ZhgqiDqh6BioM7DVHHzEGVq76Db5897WLGZ5Go=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0/go.mod h1:Izur+Wt8gClgMJqO/cZ8wdeeMryJ/xxiOVgFSSfpDTY=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g=
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
-go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
-go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
-go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
-go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
+go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=
+go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90=
+go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8=
+go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y=
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
@@ -1400,13 +1204,10 @@ go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
-go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
-go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
@@ -1533,8 +1334,8 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
-golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
-golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
+golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
+golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1597,6 +1398,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1696,8 +1498,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
-golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
-golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1706,11 +1506,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
-gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
-gonum.org/v1/plot v0.15.2/go.mod h1:DX+x+DWso3LTha+AdkJEv5Txvi+Tql3KAGkehP0/Ubg=
-gonum.org/v1/tools v0.0.0-20200318103217-c168b003ce8c/go.mod h1:fy6Otjqbk477ELp8IXTpw1cObQtLbRCBVonY+bTTfcM=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -1736,7 +1533,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -1770,7 +1566,6 @@ google.golang.org/genproto v0.0.0-20260126211449-d11affda4bed h1:qZW022+WR7NN5TK
google.golang.org/genproto v0.0.0-20260126211449-d11affda4bed/go.mod h1:SpjiK7gGN2j/djoQMxLl3QOe/J/XxNzC5M+YLecVVWU=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
-google.golang.org/genproto/googleapis/bytestream v0.0.0-20260120174246-409b4a993575/go.mod h1:Tej9lWiwVvQJP+b43pjJIsr/3mZycXWCIyoiXmbFf40=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -1786,9 +1581,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
-google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
-google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6/go.mod h1:6ytKWczdvnpnO+m+JiG9NjEDzR1FJfsnmJdG7B8QVZ8=
+google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
+google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1819,9 +1613,6 @@ gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWM
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
-gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
-gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
@@ -1841,7 +1632,6 @@ gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
-gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/driver/sqlserver v1.5.3 h1:rjupPS4PVw+rjJkfvr8jn2lJ8BMhT4UW5FwuJY0P3Z0=
gorm.io/driver/sqlserver v1.5.3/go.mod h1:B+CZ0/7oFJ6tAlefsKoyxdgDCXJKSgwS2bMOQZT0I00=
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
@@ -1868,27 +1658,18 @@ k8s.io/apiextensions-apiserver v0.35.2 h1:iyStXHoJZsUXPh/nFAsjC29rjJWdSgUmG1XpAp
k8s.io/apiextensions-apiserver v0.35.2/go.mod h1:OdyGvcO1FtMDWQ+rRh/Ei3b6X3g2+ZDHd0MSRGeS8rU=
k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=
k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
-k8s.io/apiserver v0.35.2/go.mod h1:CROJUAu0tfjZLyYgSeBsBan2T7LUJGh0ucWwTCSSk7g=
k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o=
k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g=
-k8s.io/code-generator v0.35.2/go.mod h1:id4XLCm0yAQq5nlvyfAKibMOKnMjzlesAwGw6kM3Adc=
-k8s.io/component-base v0.35.2/go.mod h1:B1iBJjooe6xIJYUucAxb26RwhAjzx0gHnqO9htWIX+0=
-k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
-k8s.io/kms v0.35.2/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ=
-k8s.io/kube-aggregator v0.34.1/go.mod h1:RU8j+5ERfp0h+gIvWtxRPfsa5nK7rboDm8RST8BJfYQ=
k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf h1:btPscg4cMql0XdYK2jLsJcNEKmACJz8l+U7geC06FiM=
k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf h1:rRz0YsF7VXj9fXRF6yQgFI7DzST+hsI3TeFSGupntu0=
layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf/go.mod h1:ivKkcY8Zxw5ba0jldhZCYYQfGdb2K6u9tbYK1AwMIBc=
-lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
-modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
-modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
@@ -1916,21 +1697,15 @@ modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
-rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
-sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
sigs.k8s.io/gateway-api v1.5.0 h1:duoo14Ky/fJXpjpmyMISE2RTBGnfCg8zICfTYLTnBJA=
sigs.k8s.io/gateway-api v1.5.0/go.mod h1:GvCETiaMAlLym5CovLxGjS0NysqFk3+Yuq3/rh6QL2o=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
-sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
-software.sslmate.com/src/go-pkcs12 v0.6.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
diff --git a/logs/gcpcloudlogging/cloud_logging.go b/logs/gcpcloudlogging/cloud_logging.go
index 52d06025b..19caf1900 100644
--- a/logs/gcpcloudlogging/cloud_logging.go
+++ b/logs/gcpcloudlogging/cloud_logging.go
@@ -42,7 +42,7 @@ func New(ctx context.Context, conn connection.GCPConnection, mappingConfig *logs
}
if conn.Credentials != nil && !conn.Credentials.IsEmpty() {
- c, err := google.CredentialsFromJSON(ctx, []byte(conn.Credentials.ValueStatic))
+ c, err := google.CredentialsFromJSON(ctx, []byte(conn.Credentials.ValueStatic)) //nolint:staticcheck
if err != nil {
return nil, fmt.Errorf("%w", err)
}
diff --git a/models/artifacts.go b/models/artifacts.go
index 89e8282aa..77f0d6f3f 100644
--- a/models/artifacts.go
+++ b/models/artifacts.go
@@ -1,8 +1,16 @@
package models
import (
+ "bytes"
+ "compress/gzip"
+ "crypto/sha256"
+ "encoding/base64"
+ "fmt"
+ "io"
"time"
+ "github.com/flanksource/clicky"
+ "github.com/flanksource/clicky/api"
"github.com/google/uuid"
"github.com/samber/lo"
"gorm.io/gorm"
@@ -22,6 +30,8 @@ type Artifact struct {
Size int64 `json:"size"` // Size in bytes
ContentType string `json:"content_type,omitempty"`
Checksum string `json:"checksum"`
+ Content []byte `json:"-" gorm:"type:bytea"`
+ CompressionType string `json:"compression_type,omitempty"`
CreatedAt time.Time `json:"created_at" yaml:"created_at" time_format:"postgres_timestamp"`
UpdatedAt time.Time `json:"updated_at" yaml:"updated_at" time_format:"postgres_timestamp"`
DeletedAt *time.Time `json:"deleted_at,omitempty" yaml:"deleted_at,omitempty" time_format:"postgres_timestamp"`
@@ -36,6 +46,175 @@ func (a Artifact) PK() string {
return a.ID.String()
}
+func (a Artifact) Pretty() api.Text {
+ s := clicky.Text(a.Filename, "font-bold")
+ if a.ContentType != "" {
+ s = s.AddText(" "+a.ContentType, "text-gray-500")
+ }
+ s = s.AddText(fmt.Sprintf(" (%s)", formatBytes(a.Size)), "text-gray-400")
+ if a.Checksum != "" {
+ short := a.Checksum
+ if len(short) > 12 {
+ short = short[:12]
+ }
+ s = s.AddText(" sha:"+short, "text-gray-400")
+ }
+ return s
+}
+
+func (a Artifact) Columns() []api.ColumnDef {
+ return []api.ColumnDef{
+ clicky.Column("Filename").Build(),
+ clicky.Column("Path").Build(),
+ clicky.Column("ContentType").Build(),
+ clicky.Column("Size").Build(),
+ clicky.Column("Checksum").Build(),
+ clicky.Column("CreatedAt").Build(),
+ }
+}
+
+func (a Artifact) Row() map[string]any {
+ checksum := a.Checksum
+ if len(checksum) > 12 {
+ checksum = checksum[:12] + "…"
+ }
+ return map[string]any{
+ "Filename": clicky.Text(a.Filename),
+ "Path": clicky.Text(a.Path),
+ "ContentType": clicky.Text(a.ContentType),
+ "Size": clicky.Text(formatBytes(a.Size)),
+ "Checksum": clicky.Text(checksum),
+ "CreatedAt": clicky.Text(a.CreatedAt.Format(time.RFC3339)),
+ }
+}
+
+func (a Artifact) RowDetail() api.Textable {
+ if a.ContentType == "" || !isImageContentType(a.ContentType) {
+ return nil
+ }
+ url := fmt.Sprintf("/artifacts/download/%s", a.ID)
+ img := artifactImage{
+ html: fmt.Sprintf(
+ `
`,
+ url, a.Filename,
+ ),
+ }
+ if data, err := a.GetContent(); err == nil && len(data) > 0 {
+ b64 := base64.StdEncoding.EncodeToString(data)
+ img.staticHTML = fmt.Sprintf(
+ `
`,
+ a.ContentType, b64, a.Filename,
+ )
+ }
+ return img
+}
+
+func isImageContentType(ct string) bool {
+ for _, prefix := range []string{"image/png", "image/jpeg", "image/gif", "image/webp", "image/svg"} {
+ if ct == prefix {
+ return true
+ }
+ }
+ return false
+}
+
+type artifactImage struct {
+ html string
+ staticHTML string
+}
+
+func (s artifactImage) String() string { return "[image]" }
+func (s artifactImage) ANSI() string { return "[image]" }
+func (s artifactImage) HTML() string { return s.html }
+func (s artifactImage) Markdown() string { return "[image]" }
+func (s artifactImage) StaticHTML() string {
+ if s.staticHTML != "" {
+ return s.staticHTML
+ }
+ return s.html
+}
+
+func formatBytes(b int64) string {
+ switch {
+ case b >= 1<<20:
+ return fmt.Sprintf("%.1f MB", float64(b)/(1<<20))
+ case b >= 1<<10:
+ return fmt.Sprintf("%.1f KB", float64(b)/(1<<10))
+ default:
+ return fmt.Sprintf("%d B", b)
+ }
+}
+
+func (a Artifact) IsInline() bool {
+ return a.Content != nil
+}
+
+// SetContent compresses data using the given compression type and sets the
+// Content and CompressionType fields. maxSize is checked against the
+// post-compression size; returns an error if exceeded.
+func (a *Artifact) SetContent(data []byte, compressionType string, maxSize int) error {
+ compressed, err := compress(data, compressionType)
+ if err != nil {
+ return fmt.Errorf("compressing artifact: %w", err)
+ }
+ if maxSize > 0 && len(compressed) > maxSize {
+ return fmt.Errorf("compressed artifact size %d exceeds max %d", len(compressed), maxSize)
+ }
+ a.Content = compressed
+ a.CompressionType = compressionType
+ a.Size = int64(len(data))
+ a.Checksum = fmt.Sprintf("%x", sha256Sum(data))
+ return nil
+}
+
+// GetContent decompresses and returns the inline content.
+func (a Artifact) GetContent() ([]byte, error) {
+ if a.Content == nil {
+ return nil, nil
+ }
+ return decompress(a.Content, a.CompressionType)
+}
+
+func sha256Sum(data []byte) []byte {
+ h := sha256.Sum256(data)
+ return h[:]
+}
+
+func compress(data []byte, compressionType string) ([]byte, error) {
+ switch compressionType {
+ case "gzip":
+ var buf bytes.Buffer
+ w := gzip.NewWriter(&buf)
+ if _, err := w.Write(data); err != nil {
+ return nil, err
+ }
+ if err := w.Close(); err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+ case "", "none":
+ return data, nil
+ default:
+ return nil, fmt.Errorf("unsupported compression type: %s", compressionType)
+ }
+}
+
+func decompress(data []byte, compressionType string) ([]byte, error) {
+ switch compressionType {
+ case "gzip":
+ r, err := gzip.NewReader(bytes.NewReader(data))
+ if err != nil {
+ return nil, err
+ }
+ defer r.Close()
+ return io.ReadAll(r)
+ case "", "none":
+ return data, nil
+ default:
+ return nil, fmt.Errorf("unsupported compression type: %s", compressionType)
+ }
+}
+
func (t Artifact) GetUnpushed(db *gorm.DB) ([]DBTable, error) {
var items []Artifact
err := db.Where("is_pushed IS FALSE").Find(&items).Error
diff --git a/models/artifacts_test.go b/models/artifacts_test.go
new file mode 100644
index 000000000..34d58afbd
--- /dev/null
+++ b/models/artifacts_test.go
@@ -0,0 +1,104 @@
+package models
+
+import (
+ "bytes"
+ "strings"
+ "testing"
+
+ "github.com/onsi/gomega"
+)
+
+func TestArtifact_IsInline(t *testing.T) {
+ g := gomega.NewWithT(t)
+
+ g.Expect(Artifact{}.IsInline()).To(gomega.BeFalse())
+ g.Expect(Artifact{Content: []byte("data")}.IsInline()).To(gomega.BeTrue())
+}
+
+func TestArtifact_SetContent_NoCompression(t *testing.T) {
+ g := gomega.NewWithT(t)
+
+ a := &Artifact{}
+ data := []byte("hello world")
+ g.Expect(a.SetContent(data, "none", 0)).To(gomega.Succeed())
+ g.Expect(a.Content).To(gomega.Equal(data))
+ g.Expect(a.CompressionType).To(gomega.Equal("none"))
+ g.Expect(a.Size).To(gomega.Equal(int64(len(data))))
+ g.Expect(a.IsInline()).To(gomega.BeTrue())
+}
+
+func TestArtifact_SetContent_Gzip(t *testing.T) {
+ g := gomega.NewWithT(t)
+
+ a := &Artifact{}
+ data := []byte(strings.Repeat("abcdefghij", 100))
+ g.Expect(a.SetContent(data, "gzip", 0)).To(gomega.Succeed())
+
+ g.Expect(a.CompressionType).To(gomega.Equal("gzip"))
+ g.Expect(a.Size).To(gomega.Equal(int64(len(data))))
+ g.Expect(len(a.Content)).To(gomega.BeNumerically("<", len(data)))
+}
+
+func TestArtifact_SetContent_RoundTrip(t *testing.T) {
+ g := gomega.NewWithT(t)
+
+ original := []byte(strings.Repeat("test data for compression ", 50))
+
+ for _, ct := range []string{"none", "gzip"} {
+ a := &Artifact{}
+ g.Expect(a.SetContent(original, ct, 0)).To(gomega.Succeed())
+
+ got, err := a.GetContent()
+ g.Expect(err).To(gomega.Succeed())
+ g.Expect(got).To(gomega.Equal(original))
+ }
+}
+
+func TestArtifact_SetContent_MaxSize_PostCompression(t *testing.T) {
+ g := gomega.NewWithT(t)
+
+ // Highly compressible data: 10KB of zeros compresses to ~30 bytes
+ data := bytes.Repeat([]byte{0}, 10*1024)
+
+ // With a maxSize larger than compressed output, should succeed
+ a := &Artifact{}
+ g.Expect(a.SetContent(data, "gzip", 1024)).To(gomega.Succeed())
+ g.Expect(len(a.Content)).To(gomega.BeNumerically("<", 1024))
+
+ // With no compression, 10KB exceeds 1KB max — should fail
+ a2 := &Artifact{}
+ err := a2.SetContent(data, "none", 1024)
+ g.Expect(err).To(gomega.HaveOccurred())
+ g.Expect(err.Error()).To(gomega.ContainSubstring("exceeds max"))
+}
+
+func TestArtifact_SetContent_UnsupportedCompression(t *testing.T) {
+ g := gomega.NewWithT(t)
+
+ a := &Artifact{}
+ err := a.SetContent([]byte("data"), "lz4", 0)
+ g.Expect(err).To(gomega.HaveOccurred())
+ g.Expect(err.Error()).To(gomega.ContainSubstring("unsupported compression"))
+}
+
+func TestArtifact_GetContent_Nil(t *testing.T) {
+ g := gomega.NewWithT(t)
+
+ a := Artifact{}
+ got, err := a.GetContent()
+ g.Expect(err).To(gomega.Succeed())
+ g.Expect(got).To(gomega.BeNil())
+}
+
+func TestArtifact_SetContent_Checksum(t *testing.T) {
+ g := gomega.NewWithT(t)
+
+ a1 := &Artifact{}
+ a2 := &Artifact{}
+ g.Expect(a1.SetContent([]byte("hello"), "gzip", 0)).To(gomega.Succeed())
+ g.Expect(a2.SetContent([]byte("hello"), "none", 0)).To(gomega.Succeed())
+
+ // Checksum is on original data, so should match regardless of compression
+ g.Expect(a1.Checksum).To(gomega.Equal(a2.Checksum))
+ g.Expect(a1.Checksum).ToNot(gomega.BeEmpty())
+}
diff --git a/schema/artifacts.hcl b/schema/artifacts.hcl
index 55d6dc3d0..b461cd555 100644
--- a/schema/artifacts.hcl
+++ b/schema/artifacts.hcl
@@ -42,6 +42,16 @@ table "artifacts" {
null = false
type = text
}
+ column "content" {
+ null = true
+ type = bytea
+ comment = "inline blob storage for artifact data when no external connection is configured"
+ }
+ column "compression_type" {
+ null = true
+ type = text
+ comment = "compression algorithm applied to content: gzip, zstd, or none"
+ }
column "is_pushed" {
null = false
default = false
diff --git a/tests/artifacts_test.go b/tests/artifacts_test.go
new file mode 100644
index 000000000..b2d7caa20
--- /dev/null
+++ b/tests/artifacts_test.go
@@ -0,0 +1,234 @@
+package tests
+
+import (
+ "bytes"
+ "io"
+ "strings"
+
+ "github.com/flanksource/duty/artifact"
+ "github.com/flanksource/duty/models"
+ "github.com/google/uuid"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Artifacts", Ordered, func() {
+ var inlineArtifactID uuid.UUID
+
+ Describe("inline blob storage", func() {
+ It("should store and retrieve uncompressed content", func() {
+ a := models.Artifact{
+ Path: "/test/inline",
+ Filename: "uncompressed.txt",
+ Checksum: "placeholder",
+ }
+ data := []byte("hello world uncompressed")
+ Expect(a.SetContent(data, "none", 0)).To(Succeed())
+
+ err := DefaultContext.DB().Create(&a).Error
+ Expect(err).ToNot(HaveOccurred())
+ Expect(a.ID).ToNot(Equal(uuid.Nil))
+
+ var fetched models.Artifact
+ err = DefaultContext.DB().Where("id = ?", a.ID).First(&fetched).Error
+ Expect(err).ToNot(HaveOccurred())
+
+ got, err := fetched.GetContent()
+ Expect(err).ToNot(HaveOccurred())
+ Expect(got).To(Equal(data))
+ Expect(fetched.IsInline()).To(BeTrue())
+ Expect(fetched.CompressionType).To(Equal("none"))
+ Expect(fetched.Size).To(Equal(int64(len(data))))
+ })
+
+ It("should store and retrieve gzip compressed content", func() {
+ a := models.Artifact{
+ Path: "/test/inline",
+ Filename: "compressed.txt",
+ Checksum: "placeholder",
+ }
+ data := []byte(strings.Repeat("compressible data ", 100))
+ Expect(a.SetContent(data, "gzip", 0)).To(Succeed())
+
+ err := DefaultContext.DB().Create(&a).Error
+ Expect(err).ToNot(HaveOccurred())
+ inlineArtifactID = a.ID
+
+ var fetched models.Artifact
+ err = DefaultContext.DB().Where("id = ?", a.ID).First(&fetched).Error
+ Expect(err).ToNot(HaveOccurred())
+
+ Expect(fetched.CompressionType).To(Equal("gzip"))
+ Expect(fetched.Size).To(Equal(int64(len(data))))
+ Expect(len(fetched.Content)).To(BeNumerically("<", len(data)))
+
+ got, err := fetched.GetContent()
+ Expect(err).ToNot(HaveOccurred())
+ Expect(got).To(Equal(data))
+ })
+
+ It("should store artifacts without inline content (external)", func() {
+ a := models.Artifact{
+ Path: "/test/external",
+ Filename: "external.txt",
+ Size: 4096,
+ Checksum: "abc123",
+ ConnectionID: uuid.New(),
+ }
+
+ err := DefaultContext.DB().Create(&a).Error
+ Expect(err).ToNot(HaveOccurred())
+
+ var fetched models.Artifact
+ err = DefaultContext.DB().Where("id = ?", a.ID).First(&fetched).Error
+ Expect(err).ToNot(HaveOccurred())
+
+ Expect(fetched.IsInline()).To(BeFalse())
+ Expect(fetched.Content).To(BeNil())
+ Expect(fetched.CompressionType).To(BeEmpty())
+ })
+ })
+
+ Describe("migration from inline to external", func() {
+ It("should simulate migrating inline content to external storage", func() {
+ var a models.Artifact
+ err := DefaultContext.DB().Where("id = ?", inlineArtifactID).First(&a).Error
+ Expect(err).ToNot(HaveOccurred())
+ Expect(a.IsInline()).To(BeTrue())
+
+ original, err := a.GetContent()
+ Expect(err).ToNot(HaveOccurred())
+ Expect(original).ToNot(BeEmpty())
+
+ a.ConnectionID = uuid.New()
+ a.Content = nil
+ a.CompressionType = ""
+
+ err = DefaultContext.DB().Save(&a).Error
+ Expect(err).ToNot(HaveOccurred())
+
+ var fetched models.Artifact
+ err = DefaultContext.DB().Where("id = ?", inlineArtifactID).First(&fetched).Error
+ Expect(err).ToNot(HaveOccurred())
+ Expect(fetched.IsInline()).To(BeFalse())
+ Expect(fetched.Content).To(BeNil())
+ Expect(fetched.ConnectionID).ToNot(Equal(uuid.Nil))
+ })
+ })
+
+ Describe("migration from external to inline", func() {
+ It("should simulate migrating external content to inline storage", func() {
+ a := models.Artifact{
+ Path: "/test/ext-to-inline",
+ Filename: "migrate-me.txt",
+ Size: 100,
+ Checksum: "old-checksum",
+ ConnectionID: uuid.New(),
+ }
+ err := DefaultContext.DB().Create(&a).Error
+ Expect(err).ToNot(HaveOccurred())
+
+ data := []byte(strings.Repeat("migrated content ", 20))
+ Expect(a.SetContent(data, "gzip", 0)).To(Succeed())
+ a.ConnectionID = uuid.Nil
+
+ err = DefaultContext.DB().Save(&a).Error
+ Expect(err).ToNot(HaveOccurred())
+
+ var fetched models.Artifact
+ err = DefaultContext.DB().Where("id = ?", a.ID).First(&fetched).Error
+ Expect(err).ToNot(HaveOccurred())
+ Expect(fetched.IsInline()).To(BeTrue())
+ Expect(fetched.ConnectionID).To(Equal(uuid.Nil))
+
+ got, err := fetched.GetContent()
+ Expect(err).ToNot(HaveOccurred())
+ Expect(got).To(Equal(data))
+ })
+ })
+
+ Describe("BlobStore", func() {
+ It("should write and read via BlobStore interface", func() {
+ store := artifact.NewBlobStore(
+ artifact.NewInlineStore(DefaultContext.DB()),
+ DefaultContext.DB(), "inline",
+ )
+
+ data := []byte(strings.Repeat("inline store test data ", 50))
+ artData := artifact.Data{
+ Content: io.NopCloser(bytes.NewReader(data)),
+ Filename: "/test/blobstore/write-read.txt",
+ }
+
+ a, err := store.Write(artData, &models.Artifact{})
+ Expect(err).ToNot(HaveOccurred())
+ Expect(a.Filename).To(Equal("/test/blobstore/write-read.txt"))
+ Expect(a.Size).To(Equal(int64(len(data))))
+ Expect(a.Checksum).ToNot(BeEmpty())
+
+ result, err := store.Read(a.ID)
+ Expect(err).ToNot(HaveOccurred())
+ defer result.Content.Close()
+
+ got, err := io.ReadAll(result.Content)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(got).To(Equal(data))
+ })
+
+ It("should respect max size post-compression", func() {
+ inlineFS := artifact.NewInlineStore(DefaultContext.DB()).
+ WithMaxSize(10).
+ WithCompression("none")
+ store := artifact.NewBlobStore(inlineFS, DefaultContext.DB(), "inline")
+
+ data := []byte(strings.Repeat("x", 200))
+ artData := artifact.Data{
+ Content: io.NopCloser(bytes.NewReader(data)),
+ Filename: "/test/blobstore/too-big.txt",
+ }
+ _, err := store.Write(artData, &models.Artifact{})
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("exceeds max"))
+ })
+
+ It("should return Pretty() on Data", func() {
+ d := artifact.Data{
+ Filename: "test.json",
+ ContentType: "application/json",
+ ContentLength: 1024,
+ Checksum: "abcdef1234567890",
+ }
+ pretty := d.Pretty().String()
+ Expect(pretty).To(ContainSubstring("test.json"))
+ Expect(pretty).To(ContainSubstring("abcdef12"))
+ })
+ })
+
+ Describe("context.Blobs()", func() {
+ It("should return inline store when no connection is configured", func() {
+ store, err := DefaultContext.Blobs()
+ Expect(err).ToNot(HaveOccurred())
+ Expect(store).ToNot(BeNil())
+ defer store.Close()
+
+ data := []byte("context blobs test")
+ artData := artifact.Data{
+ Content: io.NopCloser(bytes.NewReader(data)),
+ Filename: "/test/context-blobs/test.txt",
+ }
+
+ a, err := store.Write(artData, &models.Artifact{})
+ Expect(err).ToNot(HaveOccurred())
+ Expect(a.Checksum).ToNot(BeEmpty())
+
+ result, err := store.Read(a.ID)
+ Expect(err).ToNot(HaveOccurred())
+ defer result.Content.Close()
+
+ got, err := io.ReadAll(result.Content)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(got).To(Equal(data))
+ Expect(result.Filename).To(Equal(a.Filename))
+ })
+ })
+})
diff --git a/tests/e2e-blobs/blobs_test.go b/tests/e2e-blobs/blobs_test.go
new file mode 100644
index 000000000..f4685c338
--- /dev/null
+++ b/tests/e2e-blobs/blobs_test.go
@@ -0,0 +1,151 @@
+package e2e_blobs
+
+import (
+ "bytes"
+ gocontext "context"
+ "io"
+ "strings"
+
+ "github.com/flanksource/commons/logger"
+ "github.com/flanksource/duty/artifact"
+ artifactFS "github.com/flanksource/duty/artifact/fs"
+ "github.com/flanksource/duty/models"
+ "github.com/flanksource/duty/types"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+type testBackend struct {
+ name string
+ fs artifact.FilesystemRW
+}
+
+var testLogger = logger.GetLogger("e2e-blobs")
+
+func logged(name string, fs artifact.FilesystemRW) artifact.FilesystemRW {
+ return artifact.NewLoggedFS(fs, testLogger, name)
+}
+
+func getBackends() []testBackend {
+ backends := []testBackend{
+ {"s3", logged("s3", artifactFS.NewS3FS(s3Client, "test"))},
+ {"gcs", logged("gcs", artifactFS.NewGCSFS(gcsClient, "test"))},
+ {"azure", logged("azure", artifactFS.NewAzureBlobFS(azureClient, "test"))},
+ {"local", logged("local", artifactFS.NewLocalFS(GinkgoT().TempDir()))},
+ }
+
+ sshfs, err := artifactFS.NewSSHFS(sftpHost, "foo", "pass")
+ if err == nil {
+ backends = append(backends, testBackend{"sftp", logged("sftp", sshfs)})
+ }
+
+ smbfs, err := artifactFS.NewSMBFS(smbHost, smbPort, "users", types.Authentication{
+ Username: types.EnvVar{ValueStatic: "foo"},
+ Password: types.EnvVar{ValueStatic: "pass"},
+ })
+ if err == nil {
+ backends = append(backends, testBackend{"smb", logged("smb", smbfs)})
+ }
+
+ return backends
+}
+
+var testFiles = []struct {
+ name string
+ content string
+}{
+ {"first.json", `{"name": "first"}`},
+ {"second.json", `{"name": "second"}`},
+ {"third.yaml", "third"},
+ {"record-1.txt", "record-1"},
+ {"record-2.txt", "record-2"},
+}
+
+var _ = Describe("Blob Stores", Label("e2e"), func() {
+ for _, backend := range []string{"s3", "gcs", "azure", "local", "sftp", "smb"} {
+ backend := backend
+ Describe(backend, Ordered, func() {
+ var fs artifact.FilesystemRW
+
+ BeforeAll(func() {
+ for _, b := range getBackends() {
+ if b.name == backend {
+ fs = b.fs
+ break
+ }
+ }
+ if fs == nil {
+ Skip("backend not available: " + backend)
+ }
+ })
+
+ AfterAll(func() {
+ if fs != nil {
+ _ = fs.Close()
+ }
+ })
+
+ It("should write files", func() {
+ ctx := gocontext.Background()
+ for _, tf := range testFiles {
+ _, err := fs.Write(ctx, tf.name, strings.NewReader(tf.content))
+ Expect(err).ToNot(HaveOccurred())
+ }
+ })
+
+ It("should read files back correctly", func() {
+ reader, err := fs.Read(gocontext.Background(), "record-1.txt")
+ Expect(err).ToNot(HaveOccurred())
+ defer reader.Close()
+
+ content, err := io.ReadAll(reader)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(string(content)).To(Equal("record-1"))
+ })
+
+ It("should stat files", func() {
+ info, err := fs.Stat("first.json")
+ Expect(err).ToNot(HaveOccurred())
+ Expect(info.Size()).To(Equal(int64(len(`{"name": "first"}`))))
+ })
+
+ It("should list files", func() {
+ dir := ""
+ if backend == "sftp" || backend == "local" {
+ dir = "."
+ }
+ entries, err := fs.ReadDir(dir)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(len(entries)).To(BeNumerically(">=", 5))
+ })
+ })
+ }
+
+ Describe("ctx.Blobs()", Ordered, func() {
+ It("should write and read via BlobStore interface", func() {
+ store, err := DefaultContext.Blobs()
+ Expect(err).ToNot(HaveOccurred())
+ defer store.Close()
+
+ data := []byte("ctx blobs e2e test data")
+ artData := artifact.Data{
+ Content: io.NopCloser(bytes.NewReader(data)),
+ Filename: "/test/ctx-blobs/e2e-test.txt",
+ }
+
+ a, err := store.Write(artData, &models.Artifact{})
+ Expect(err).ToNot(HaveOccurred())
+ Expect(a.Checksum).ToNot(BeEmpty())
+ Expect(a.Size).To(Equal(int64(len(data))))
+
+ result, err := store.Read(a.ID)
+ Expect(err).ToNot(HaveOccurred())
+ defer result.Content.Close()
+
+ got, err := io.ReadAll(result.Content)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(got).To(Equal(data))
+ Expect(result.Pretty().String()).ToNot(BeEmpty())
+ })
+ })
+})
diff --git a/tests/e2e-blobs/containers.go b/tests/e2e-blobs/containers.go
new file mode 100644
index 000000000..e58544b97
--- /dev/null
+++ b/tests/e2e-blobs/containers.go
@@ -0,0 +1,203 @@
+package e2e_blobs
+
+import (
+ gocontext "context"
+ "fmt"
+ "net/http"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "time"
+
+ gcs "cloud.google.com/go/storage"
+ "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/aws-sdk-go-v2/config"
+ "github.com/aws/aws-sdk-go-v2/credentials"
+ "github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/wait"
+)
+
+func startMinio(ctx gocontext.Context) (*s3.Client, testcontainers.Container, error) {
+ req := testcontainers.ContainerRequest{
+ Image: "minio/minio",
+ ExposedPorts: []string{"9000/tcp"},
+ Env: map[string]string{
+ "MINIO_ROOT_USER": "minioadmin",
+ "MINIO_ROOT_PASSWORD": "minioadmin",
+ },
+ Cmd: []string{"server", "/data"},
+ WaitingFor: wait.ForHTTP("/minio/health/live").WithPort("9000").WithStartupTimeout(60 * time.Second),
+ }
+
+ container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
+ ContainerRequest: req,
+ Started: true,
+ })
+ if err != nil {
+ return nil, nil, fmt.Errorf("starting minio: %w", err)
+ }
+
+ endpoint, err := container.Endpoint(ctx, "http")
+ if err != nil {
+ return nil, container, err
+ }
+
+ cfg, err := config.LoadDefaultConfig(ctx,
+ config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider("minioadmin", "minioadmin", "")),
+ config.WithRegion("us-east-1"),
+ )
+ if err != nil {
+ return nil, container, err
+ }
+
+ client := s3.NewFromConfig(cfg, func(o *s3.Options) {
+ o.UsePathStyle = true
+ o.BaseEndpoint = &endpoint
+ })
+
+ _, _ = client.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String("test")})
+
+ return client, container, nil
+}
+
+func startFakeGCS(ctx gocontext.Context) (*gcs.Client, testcontainers.Container, error) {
+ req := testcontainers.ContainerRequest{
+ Image: "fsouza/fake-gcs-server:1.49.3",
+ ExposedPorts: []string{"8083/tcp"},
+ Cmd: []string{"-scheme", "http", "-port", "8083"},
+ WaitingFor: wait.ForHTTP("/storage/v1/b").WithPort("8083").WithStartupTimeout(30 * time.Second),
+ }
+
+ container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
+ ContainerRequest: req,
+ Started: true,
+ })
+ if err != nil {
+ return nil, nil, fmt.Errorf("starting fake-gcs-server: %w", err)
+ }
+
+ host, _ := container.Host(ctx)
+ port, _ := container.MappedPort(ctx, "8083")
+ emulatorHost := fmt.Sprintf("%s:%s", host, port.Port())
+ emulatorURL := fmt.Sprintf("http://%s", emulatorHost)
+
+ // Update external URL to match the dynamically mapped port
+ updateReq, _ := http.NewRequestWithContext(ctx, http.MethodPut,
+ emulatorURL+"/_internal/config",
+ strings.NewReader(fmt.Sprintf(`{"externalUrl": "%s"}`, emulatorURL)))
+ updateReq.Header.Set("Content-Type", "application/json")
+ if resp, err := http.DefaultClient.Do(updateReq); err == nil {
+ _ = resp.Body.Close()
+ }
+
+ os.Setenv("STORAGE_EMULATOR_HOST", emulatorHost)
+ client, err := gcs.NewClient(ctx, gcs.WithJSONReads())
+ if err != nil {
+ return nil, container, err
+ }
+
+ _ = client.Bucket("test").Create(ctx, "fake-project", nil)
+
+ return client, container, nil
+}
+
+func startAzurite(ctx gocontext.Context) (*azblob.Client, testcontainers.Container, error) {
+ req := testcontainers.ContainerRequest{
+ Image: "mcr.microsoft.com/azure-storage/azurite",
+ ExposedPorts: []string{"10000/tcp"},
+ Cmd: []string{"azurite-blob", "--blobHost", "0.0.0.0", "--skipApiVersionCheck"},
+ WaitingFor: wait.ForListeningPort("10000").WithStartupTimeout(30 * time.Second),
+ }
+
+ container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
+ ContainerRequest: req,
+ Started: true,
+ })
+ if err != nil {
+ return nil, nil, fmt.Errorf("starting azurite: %w", err)
+ }
+
+ host, err := container.Host(ctx)
+ if err != nil {
+ return nil, container, err
+ }
+ port, err := container.MappedPort(ctx, "10000")
+ if err != nil {
+ return nil, container, err
+ }
+
+ connStr := fmt.Sprintf(
+ "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://%s:%s/devstoreaccount1",
+ host, port.Port(),
+ )
+
+ client, err := azblob.NewClientFromConnectionString(connStr, nil)
+ if err != nil {
+ return nil, container, err
+ }
+
+ _, _ = client.CreateContainer(ctx, "test", nil)
+
+ return client, container, nil
+}
+
+func startSFTP(ctx gocontext.Context) (string, testcontainers.Container, error) {
+ _, filename, _, _ := runtime.Caller(0)
+ configPath := filepath.Join(filepath.Dir(filename), "sftp-configuration.json")
+
+ req := testcontainers.ContainerRequest{
+ Image: "emberstack/sftp",
+ ExposedPorts: []string{"22/tcp"},
+ Files: []testcontainers.ContainerFile{
+ {HostFilePath: configPath, ContainerFilePath: "/app/config/sftp.json", FileMode: 0644},
+ },
+ WaitingFor: wait.ForListeningPort("22").WithStartupTimeout(30 * time.Second),
+ }
+
+ container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
+ ContainerRequest: req,
+ Started: true,
+ })
+ if err != nil {
+ return "", nil, fmt.Errorf("starting sftp: %w", err)
+ }
+
+ host, err := container.Endpoint(ctx, "")
+ if err != nil {
+ return "", container, err
+ }
+
+ return host, container, nil
+}
+
+func startSMB(ctx gocontext.Context) (string, string, testcontainers.Container, error) {
+ req := testcontainers.ContainerRequest{
+ Image: "dperson/samba",
+ ExposedPorts: []string{"445/tcp"},
+ Cmd: []string{"-p", "-u", "foo;pass", "-s", "users;/srv;no;no;no;foo"},
+ WaitingFor: wait.ForListeningPort("445").WithStartupTimeout(30 * time.Second),
+ }
+
+ container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
+ ContainerRequest: req,
+ Started: true,
+ })
+ if err != nil {
+ return "", "", nil, fmt.Errorf("starting smb: %w", err)
+ }
+
+ host, err := container.Host(ctx)
+ if err != nil {
+ return "", "", container, err
+ }
+
+ port, err := container.MappedPort(ctx, "445")
+ if err != nil {
+ return "", "", container, err
+ }
+
+ return host, port.Port(), container, nil
+}
diff --git a/tests/e2e-blobs/sftp-configuration.json b/tests/e2e-blobs/sftp-configuration.json
new file mode 100644
index 000000000..180f5f933
--- /dev/null
+++ b/tests/e2e-blobs/sftp-configuration.json
@@ -0,0 +1 @@
+{"Global":{"Chroot":{"Directory":"%h","StartPath":"sftp"},"Directories":["sftp"]},"Users":[{"Username":"foo","Password":"pass"}]}
diff --git a/tests/e2e-blobs/suite_test.go b/tests/e2e-blobs/suite_test.go
new file mode 100644
index 000000000..cca50aad6
--- /dev/null
+++ b/tests/e2e-blobs/suite_test.go
@@ -0,0 +1,87 @@
+package e2e_blobs
+
+import (
+ gocontext "context"
+ "testing"
+ "time"
+
+ gcs "cloud.google.com/go/storage"
+ "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
+ "github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/flanksource/commons/logger"
+ "github.com/flanksource/duty/context"
+ "github.com/flanksource/duty/tests/setup"
+ ginkgo "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ "github.com/testcontainers/testcontainers-go"
+)
+
+var log = logger.GetLogger("e2e-blobs")
+
+var (
+ DefaultContext context.Context
+
+ s3Client *s3.Client
+ gcsClient *gcs.Client
+ azureClient *azblob.Client
+
+ allContainers []testcontainers.Container
+
+ sftpHost string
+ smbHost string
+ smbPort string
+)
+
+func TestBlobStores(t *testing.T) {
+ RegisterFailHandler(ginkgo.Fail)
+ ginkgo.RunSpecs(t, "Blob Stores E2E Suite")
+}
+
+var _ = ginkgo.BeforeSuite(func() {
+ DefaultContext = setup.BeforeSuiteFn(setup.WithoutDummyData)
+
+ ctx, cancel := gocontext.WithTimeout(gocontext.Background(), 2*time.Minute)
+ defer cancel()
+
+ var c testcontainers.Container
+ var err error
+
+ log.Infof("Starting MinIO container...")
+ s3Client, c, err = startMinio(ctx)
+ Expect(err).ToNot(HaveOccurred())
+ allContainers = append(allContainers, c)
+ log.Infof("MinIO ready")
+
+ log.Infof("Starting fake-gcs-server container...")
+ gcsClient, c, err = startFakeGCS(ctx)
+ Expect(err).ToNot(HaveOccurred())
+ allContainers = append(allContainers, c)
+ log.Infof("GCS ready")
+
+ log.Infof("Starting Azurite container...")
+ azureClient, c, err = startAzurite(ctx)
+ Expect(err).ToNot(HaveOccurred())
+ allContainers = append(allContainers, c)
+ log.Infof("Azurite ready")
+
+ log.Infof("Starting SFTP container...")
+ sftpHost, c, err = startSFTP(ctx)
+ Expect(err).ToNot(HaveOccurred())
+ allContainers = append(allContainers, c)
+ log.Infof("SFTP ready at %s", sftpHost)
+
+ log.Infof("Starting SMB container...")
+ smbHost, smbPort, c, err = startSMB(ctx)
+ Expect(err).ToNot(HaveOccurred())
+ allContainers = append(allContainers, c)
+ log.Infof("SMB ready at %s:%s", smbHost, smbPort)
+})
+
+var _ = ginkgo.AfterSuite(func() {
+ for _, c := range allContainers {
+ if c != nil {
+ _ = c.Terminate(gocontext.Background())
+ }
+ }
+ setup.AfterSuiteFn()
+})