From cfa7cc5be93cb18b7bff3f0c0b13db0fb29a11ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 11:47:40 -0400 Subject: [PATCH 01/15] chore(deps): bump github.com/anchore/stereoscope (#3991) Bumps [github.com/anchore/stereoscope](https://github.com/anchore/stereoscope) from 0.1.5-0.20250604132324-344e29f37f05 to 0.1.5. - [Release notes](https://github.com/anchore/stereoscope/releases) - [Changelog](https://github.com/anchore/stereoscope/blob/main/RELEASE.md) - [Commits](https://github.com/anchore/stereoscope/commits/v0.1.5) --- updated-dependencies: - dependency-name: github.com/anchore/stereoscope dependency-version: 0.1.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1dd579b8b4d..b6ebeffed6e 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 - github.com/anchore/stereoscope v0.1.5-0.20250604132324-344e29f37f05 + github.com/anchore/stereoscope v0.1.5 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be github.com/aquasecurity/go-pep440-version v0.0.1 github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef diff --git a/go.sum b/go.sum index 9c4b72000e9..978fca3ea1a 100644 --- a/go.sum +++ b/go.sum @@ -122,8 +122,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY= github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI= -github.com/anchore/stereoscope v0.1.5-0.20250604132324-344e29f37f05 h1:MKTDwRrC7A+eRZvGFJ8TzLiKytFH1GBpPdjLTlcoO4A= -github.com/anchore/stereoscope v0.1.5-0.20250604132324-344e29f37f05/go.mod h1:S5xxMIo1BK+V+p+6SF/wzS4pZ2cTnpk6L+UJbf5IjsQ= +github.com/anchore/stereoscope v0.1.5 h1:/wdjSoerdbYtXNvezReNrmzWeBOFJeHt7hLXz6Xg/WE= +github.com/anchore/stereoscope v0.1.5/go.mod h1:S5xxMIo1BK+V+p+6SF/wzS4pZ2cTnpk6L+UJbf5IjsQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ= github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= From 79b6d5daa4d5553e935865b81ab8d6251b8980d9 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 10 Jun 2025 15:03:50 -0400 Subject: [PATCH 02/15] Allow decoding of anchorectl json files (#3973) * allow decoding of import sbom file shape Signed-off-by: Alex Goodman * address formatting Signed-off-by: Alex Goodman * add file mode and type processing Signed-off-by: Alex Goodman * use type to interpret the raw value Signed-off-by: Alex Goodman * safe mode convert should use uint32 Signed-off-by: Alex Goodman * simpler decoder type Signed-off-by: Alex Goodman --------- Signed-off-by: Alex Goodman --- syft/format/syftjson/model/document.go | 24 ++ syft/format/syftjson/model/document_test.go | 99 +++++++ syft/format/syftjson/model/file.go | 68 +++++ syft/format/syftjson/model/file_test.go | 277 ++++++++++++++++++++ syft/format/syftjson/to_syft_model.go | 7 +- syft/format/syftjson/to_syft_model_test.go | 6 + 6 files changed, 477 insertions(+), 4 deletions(-) create mode 100644 syft/format/syftjson/model/document_test.go create mode 100644 syft/format/syftjson/model/file_test.go diff --git a/syft/format/syftjson/model/document.go b/syft/format/syftjson/model/document.go index a9886ba4b5d..a650af4fb5c 100644 --- a/syft/format/syftjson/model/document.go +++ b/syft/format/syftjson/model/document.go @@ -1,5 +1,10 @@ package model +import ( + "encoding/json" + "fmt" +) + // Document represents the syft cataloging findings as a JSON document type Document struct { Artifacts []Package `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog @@ -11,6 +16,25 @@ type Document struct { Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape } +func (d *Document) UnmarshalJSON(data []byte) error { + type Alias *Document + aux := Alias(d) + + if err := json.Unmarshal(data, aux); err != nil { + return fmt.Errorf("could not unmarshal syft JSON document: %w", err) + } + + // in previous versions of anchorectl, the file modes were stored as decimal values instead of octal. + if d.Schema.Version == "1.0.0" && d.Descriptor.Name == "anchorectl" { + // convert all file modes from decimal to octal + for i := range d.Files { + d.Files[i].Metadata.Mode = convertFileModeToBase8(d.Files[i].Metadata.Mode) + } + } + + return nil +} + // Descriptor describes what created the document as well as surrounding metadata type Descriptor struct { Name string `json:"name"` diff --git a/syft/format/syftjson/model/document_test.go b/syft/format/syftjson/model/document_test.go new file mode 100644 index 00000000000..141644020a8 --- /dev/null +++ b/syft/format/syftjson/model/document_test.go @@ -0,0 +1,99 @@ +package model + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDocumentUnmarshalJSON_SchemaDetection(t *testing.T) { + tests := []struct { + name string + jsonData string + modes []int + }{ + { + name: "schema version 1.0.0 + anchorectl", + jsonData: `{ + "files": [ + {"metadata": {"mode": 493}}, + {"metadata": {"mode": 420}} + ], + "schema": {"version": "1.0.0"}, + "descriptor": { + "name": "anchorectl" + } + }`, + modes: []int{755, 644}, + }, + { + name: "schema version 1.0.0 + syft", + jsonData: `{ + "files": [ + {"metadata": {"mode": 755}}, + {"metadata": {"mode": 644}} + ], + "schema": {"version": "1.0.0"}, + "descriptor": { + "name": "syft" + } + }`, + modes: []int{755, 644}, + }, + { + name: "schema version 2.0.0 + anchorectl", + jsonData: `{ + "files": [ + {"metadata": {"mode": 755}}, + {"metadata": {"mode": 644}} + ], + "schema": {"version": "2.0.0"}, + "descriptor": { + "name": "anchorectl" + } + }`, + modes: []int{755, 644}, + }, + { + name: "missing schema version should not convert modes", + jsonData: `{ + "files": [ + {"metadata": {"mode": 755}} + ], + "schema": {} + }`, + modes: []int{755}, + }, + { + name: "empty files array with version 1.0.0", + jsonData: `{ + "files": [], + "schema": {"version": "1.0.0"}, + "descriptor": { + "name": "anchorectl" + } + }`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var doc Document + + err := json.Unmarshal([]byte(tt.jsonData), &doc) + if err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + + var modes []int + for _, file := range doc.Files { + modes = append(modes, file.Metadata.Mode) + } + + require.Len(t, doc.Files, len(tt.modes), "Unexpected number of files") + assert.Equal(t, tt.modes, modes, "File modes do not match expected values") + }) + } +} diff --git a/syft/format/syftjson/model/file.go b/syft/format/syftjson/model/file.go index 0ce0d92b306..be406d2ba34 100644 --- a/syft/format/syftjson/model/file.go +++ b/syft/format/syftjson/model/file.go @@ -1,6 +1,11 @@ package model import ( + "encoding/json" + "fmt" + "strconv" + + stereoFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/license" ) @@ -26,6 +31,44 @@ type FileMetadataEntry struct { Size int64 `json:"size"` } +func (f *FileMetadataEntry) UnmarshalJSON(data []byte) error { + type Alias FileMetadataEntry + aux := (*Alias)(f) + + if err := json.Unmarshal(data, aux); err == nil { + // we should have at least one field set to a non-zero value... otherwise this is a legacy entry + if f.Mode != 0 || f.Type != "" || f.LinkDestination != "" || + f.UserID != 0 || f.GroupID != 0 || f.MIMEType != "" || f.Size != 0 { + return nil + } + } + + var legacy sbomImportLegacyFileMetadataEntry + if err := json.Unmarshal(data, &legacy); err != nil { + return err + } + + f.Mode = legacy.Mode + f.Type = string(legacy.Type) + f.LinkDestination = legacy.LinkDestination + f.UserID = legacy.UserID + f.GroupID = legacy.GroupID + f.MIMEType = legacy.MIMEType + f.Size = legacy.Size + + return nil +} + +type sbomImportLegacyFileMetadataEntry struct { + Mode int `json:"Mode"` + Type intOrStringFileType `json:"Type"` + LinkDestination string `json:"LinkDestination"` + UserID int `json:"UserID"` + GroupID int `json:"GroupID"` + MIMEType string `json:"MIMEType"` + Size int64 `json:"Size"` +} + type FileLicense struct { Value string `json:"value"` SPDXExpression string `json:"spdxExpression"` @@ -38,3 +81,28 @@ type FileLicenseEvidence struct { Offset int `json:"offset"` Extent int `json:"extent"` } + +type intOrStringFileType string + +func (lt *intOrStringFileType) UnmarshalJSON(data []byte) error { + var str string + if err := json.Unmarshal(data, &str); err == nil { + *lt = intOrStringFileType(str) + return nil + } + + var num stereoFile.Type + if err := json.Unmarshal(data, &num); err != nil { + return fmt.Errorf("file.Type must be either string or int, got: %s", string(data)) + } + + *lt = intOrStringFileType(num.String()) + return nil +} + +func convertFileModeToBase8(rawMode int) int { + octalStr := fmt.Sprintf("%o", rawMode) + // we don't need to check that this is a valid octal string since the input is always an integer + result, _ := strconv.Atoi(octalStr) + return result +} diff --git a/syft/format/syftjson/model/file_test.go b/syft/format/syftjson/model/file_test.go new file mode 100644 index 00000000000..e2aee3ed357 --- /dev/null +++ b/syft/format/syftjson/model/file_test.go @@ -0,0 +1,277 @@ +package model + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_FileMetadataEntry_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + jsonData []byte + expected FileMetadataEntry + }{ + { + name: "unmarshal current format", + jsonData: []byte(`{ + "mode": 644, + "type": "RegularFile", + "linkDestination": "/usr/bin/python3", + "userID": 1000, + "groupID": 1000, + "mimeType": "text/plain", + "size": 10174 + }`), + expected: FileMetadataEntry{ + Mode: 644, + Type: "RegularFile", + LinkDestination: "/usr/bin/python3", + UserID: 1000, + GroupID: 1000, + MIMEType: "text/plain", + Size: 10174, + }, + }, + { + name: "unmarshal legacy sbom import format", + jsonData: []byte(`{ + "Mode": 644, + "Type": "RegularFile", + "LinkDestination": "/usr/bin/python3", + "UserID": 1000, + "GroupID": 1000, + "MIMEType": "text/plain", + "Size": 10174 + }`), + expected: FileMetadataEntry{ + Mode: 644, + Type: "RegularFile", + LinkDestination: "/usr/bin/python3", + UserID: 1000, + GroupID: 1000, + MIMEType: "text/plain", + Size: 10174, + }, + }, + { + name: "unmarshal legacy sbom import format - integer type", + jsonData: []byte(`{ + "Mode": 644, + "Type": 0, + "LinkDestination": "/usr/bin/python3", + "UserID": 1000, + "GroupID": 1000, + "MIMEType": "text/plain", + "Size": 10174 + }`), + expected: FileMetadataEntry{ + Mode: 644, + Type: "RegularFile", + LinkDestination: "/usr/bin/python3", + UserID: 1000, + GroupID: 1000, + MIMEType: "text/plain", + Size: 10174, + }, + }, + { + name: "unmarshal minimal current format", + jsonData: []byte(`{ + "mode": 0, + "type": "RegularFile", + "userID": 0, + "groupID": 0, + "size": 0 + }`), + expected: FileMetadataEntry{ + Type: "RegularFile", + }, + }, + { + name: "unmarshal minimal legacy format", + jsonData: []byte(`{ + "Mode": 0, + "Type": "RegularFile", + "UserID": 0, + "GroupID": 0, + "Size": 0 + }`), + expected: FileMetadataEntry{ + Type: "RegularFile", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var actual FileMetadataEntry + err := actual.UnmarshalJSON(test.jsonData) + require.NoError(t, err) + + if diff := cmp.Diff(test.expected, actual); diff != "" { + t.Errorf("FileMetadataEntry mismatch (-expected +actual):\n%s", diff) + } + }) + } +} + +func Test_intOrStringFileType_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + jsonData []byte + expected string + wantErr require.ErrorAssertionFunc + }{ + // string inputs - should pass through unchanged + { + name: "string RegularFile", + jsonData: []byte(`"RegularFile"`), + expected: "RegularFile", + }, + { + name: "string HardLink", + jsonData: []byte(`"HardLink"`), + expected: "HardLink", + }, + { + name: "string Directory", + jsonData: []byte(`"Directory"`), + expected: "Directory", + }, + { + name: "string custom value", + jsonData: []byte(`"CustomFileType"`), + expected: "CustomFileType", + }, + // integer inputs - should convert to string representation + { + name: "int 0 (TypeRegular)", + jsonData: []byte(`0`), + expected: "RegularFile", + }, + { + name: "int 1 (TypeHardLink)", + jsonData: []byte(`1`), + expected: "HardLink", + }, + { + name: "int 2 (TypeSymLink)", + jsonData: []byte(`2`), + expected: "SymbolicLink", + }, + { + name: "int 3 (TypeCharacterDevice)", + jsonData: []byte(`3`), + expected: "CharacterDevice", + }, + { + name: "int 4 (TypeBlockDevice)", + jsonData: []byte(`4`), + expected: "BlockDevice", + }, + { + name: "int 5 (TypeDirectory)", + jsonData: []byte(`5`), + expected: "Directory", + }, + { + name: "int 6 (TypeFIFO)", + jsonData: []byte(`6`), + expected: "FIFONode", + }, + { + name: "int 7 (TypeSocket)", + jsonData: []byte(`7`), + expected: "Socket", + }, + { + name: "int 8 (TypeIrregular)", + jsonData: []byte(`8`), + expected: "IrregularFile", + }, + { + name: "unknown int", + jsonData: []byte(`99`), + expected: "Unknown", + }, + { + name: "negative int", + jsonData: []byte(`-1`), + expected: "Unknown", + }, + { + name: "null value", + jsonData: []byte(`null`), + }, + { + name: "invalid JSON", + jsonData: []byte(`{`), + wantErr: require.Error, + }, + { + name: "boolean value", + jsonData: []byte(`true`), + wantErr: require.Error, + }, + { + name: "array value", + jsonData: []byte(`[]`), + wantErr: require.Error, + }, + { + name: "object value", + jsonData: []byte(`{}`), + wantErr: require.Error, + }, + { + name: "float value", + jsonData: []byte(`1.5`), + wantErr: require.Error, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.wantErr == nil { + test.wantErr = require.NoError + } + var ft intOrStringFileType + err := ft.UnmarshalJSON(test.jsonData) + test.wantErr(t, err) + if err != nil { + return + } + assert.Equal(t, test.expected, string(ft)) + }) + } +} + +func Test_convertFileModeToBase8(t *testing.T) { + tests := []struct { + name string + input int + expected int + }{ + { + name: "no permissions", + input: 0, + expected: 0, + }, + { + name: "symlink + rwxrwxrwx", + input: 134218239, + expected: 1000000777, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := convertFileModeToBase8(tt.input) + + require.Equal(t, tt.expected, actual) + }) + } +} diff --git a/syft/format/syftjson/to_syft_model.go b/syft/format/syftjson/to_syft_model.go index 503f9ba4539..143a639a66f 100644 --- a/syft/format/syftjson/to_syft_model.go +++ b/syft/format/syftjson/to_syft_model.go @@ -144,13 +144,12 @@ func toSyftFiles(files []model.File) sbom.Artifacts { } func safeFileModeConvert(val int) (fs.FileMode, error) { - if val < math.MinInt32 || val > math.MaxInt32 { - // Value is out of the range that int32 can represent + mode, err := strconv.ParseInt(strconv.Itoa(val), 8, 64) + if mode < 0 || mode > math.MaxUint32 { + // value is out of the range that int32 can represent return 0, fmt.Errorf("value %d is out of the range that int32 can represent", val) } - // Safe to convert to os.FileMode - mode, err := strconv.ParseInt(strconv.Itoa(val), 8, 64) if err != nil { return 0, err } diff --git a/syft/format/syftjson/to_syft_model_test.go b/syft/format/syftjson/to_syft_model_test.go index babba6345ff..1c84fba4a94 100644 --- a/syft/format/syftjson/to_syft_model_test.go +++ b/syft/format/syftjson/to_syft_model_test.go @@ -491,6 +491,12 @@ func Test_safeFileModeConvert(t *testing.T) { want: os.FileMode(511), // 777 in octal equals 511 in decimal wantErr: false, }, + { + name: "valid perm with symlink type", + val: 1000000777, // symlink + rwxrwxrwx + want: os.FileMode(0o1000000777), // 134218239 + wantErr: false, + }, { name: "outside int32 high", val: int(math.MaxInt32) + 1, From 96c34ffc43c6b4090de8b2d6f24de0c6260c6345 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Wed, 11 Jun 2025 13:11:40 -0400 Subject: [PATCH 03/15] account for non-import shapes (#3997) Signed-off-by: Alex Goodman --- syft/format/syftjson/model/document.go | 2 +- syft/format/syftjson/model/file.go | 48 ++++++--- syft/format/syftjson/model/file_test.go | 129 ++++++++++++++++-------- 3 files changed, 123 insertions(+), 56 deletions(-) diff --git a/syft/format/syftjson/model/document.go b/syft/format/syftjson/model/document.go index a650af4fb5c..c1cb0e38199 100644 --- a/syft/format/syftjson/model/document.go +++ b/syft/format/syftjson/model/document.go @@ -28,7 +28,7 @@ func (d *Document) UnmarshalJSON(data []byte) error { if d.Schema.Version == "1.0.0" && d.Descriptor.Name == "anchorectl" { // convert all file modes from decimal to octal for i := range d.Files { - d.Files[i].Metadata.Mode = convertFileModeToBase8(d.Files[i].Metadata.Mode) + d.Files[i].Metadata.Mode = convertBase10ToBase8(d.Files[i].Metadata.Mode) } } diff --git a/syft/format/syftjson/model/file.go b/syft/format/syftjson/model/file.go index be406d2ba34..87b4754e4b4 100644 --- a/syft/format/syftjson/model/file.go +++ b/syft/format/syftjson/model/file.go @@ -31,14 +31,21 @@ type FileMetadataEntry struct { Size int64 `json:"size"` } -func (f *FileMetadataEntry) UnmarshalJSON(data []byte) error { - type Alias FileMetadataEntry - aux := (*Alias)(f) +type auxFileMetadataEntry FileMetadataEntry +type fileMetadataEntryWithLegacyHint struct { + *auxFileMetadataEntry `json:",inline"` + LegacyHint any `json:"FileInfo"` +} - if err := json.Unmarshal(data, aux); err == nil { - // we should have at least one field set to a non-zero value... otherwise this is a legacy entry - if f.Mode != 0 || f.Type != "" || f.LinkDestination != "" || - f.UserID != 0 || f.GroupID != 0 || f.MIMEType != "" || f.Size != 0 { +func (f *FileMetadataEntry) UnmarshalJSON(data []byte) error { + aux := fileMetadataEntryWithLegacyHint{ + auxFileMetadataEntry: (*auxFileMetadataEntry)(f), + } + if err := json.Unmarshal(data, &aux); err == nil { + fieldsSpecified := f.Mode != 0 || f.Type != "" || f.LinkDestination != "" || + f.UserID != 0 || f.GroupID != 0 || f.MIMEType != "" || f.Size != 0 + if aux.LegacyHint == nil && fieldsSpecified { + // we should have at least one field set to a non-zero value... (this is not a legacy shape) return nil } } @@ -48,8 +55,14 @@ func (f *FileMetadataEntry) UnmarshalJSON(data []byte) error { return err } + if !legacy.Type.WasInt { + // this occurs for document shapes from a non-import path and indicates that the mode has already been converted to octal. + // That being said, we want to handle all legacy shapes the same, so we will convert this to base 10 for consistency. + legacy.Mode = convertBase8ToBase10(legacy.Mode) + } + f.Mode = legacy.Mode - f.Type = string(legacy.Type) + f.Type = legacy.Type.Value f.LinkDestination = legacy.LinkDestination f.UserID = legacy.UserID f.GroupID = legacy.GroupID @@ -82,12 +95,15 @@ type FileLicenseEvidence struct { Extent int `json:"extent"` } -type intOrStringFileType string +type intOrStringFileType struct { + Value string + WasInt bool +} func (lt *intOrStringFileType) UnmarshalJSON(data []byte) error { var str string if err := json.Unmarshal(data, &str); err == nil { - *lt = intOrStringFileType(str) + lt.Value = str return nil } @@ -96,13 +112,21 @@ func (lt *intOrStringFileType) UnmarshalJSON(data []byte) error { return fmt.Errorf("file.Type must be either string or int, got: %s", string(data)) } - *lt = intOrStringFileType(num.String()) + lt.Value = num.String() + lt.WasInt = true return nil } -func convertFileModeToBase8(rawMode int) int { +func convertBase10ToBase8(rawMode int) int { octalStr := fmt.Sprintf("%o", rawMode) // we don't need to check that this is a valid octal string since the input is always an integer result, _ := strconv.Atoi(octalStr) return result } + +func convertBase8ToBase10(octalMode int) int { + octalStr := strconv.Itoa(octalMode) + result, _ := strconv.ParseInt(octalStr, 8, 64) + + return int(result) +} diff --git a/syft/format/syftjson/model/file_test.go b/syft/format/syftjson/model/file_test.go index e2aee3ed357..f2ec1f18f52 100644 --- a/syft/format/syftjson/model/file_test.go +++ b/syft/format/syftjson/model/file_test.go @@ -36,8 +36,9 @@ func Test_FileMetadataEntry_UnmarshalJSON(t *testing.T) { }, }, { - name: "unmarshal legacy sbom import format", + name: "unmarshal legacy image add internal document format", jsonData: []byte(`{ + "FileInfo": {}, "Mode": 644, "Type": "RegularFile", "LinkDestination": "/usr/bin/python3", @@ -47,7 +48,7 @@ func Test_FileMetadataEntry_UnmarshalJSON(t *testing.T) { "Size": 10174 }`), expected: FileMetadataEntry{ - Mode: 644, + Mode: 420, // important! we convert this to base 10 so that all documents are consistent Type: "RegularFile", LinkDestination: "/usr/bin/python3", UserID: 1000, @@ -57,8 +58,9 @@ func Test_FileMetadataEntry_UnmarshalJSON(t *testing.T) { }, }, { - name: "unmarshal legacy sbom import format - integer type", + name: "unmarshal legacy sbom import format", jsonData: []byte(`{ + "FileInfo": {}, "Mode": 644, "Type": 0, "LinkDestination": "/usr/bin/python3", @@ -93,6 +95,7 @@ func Test_FileMetadataEntry_UnmarshalJSON(t *testing.T) { { name: "unmarshal minimal legacy format", jsonData: []byte(`{ + "FileInfo": {}, "Mode": 0, "Type": "RegularFile", "UserID": 0, @@ -120,10 +123,11 @@ func Test_FileMetadataEntry_UnmarshalJSON(t *testing.T) { func Test_intOrStringFileType_UnmarshalJSON(t *testing.T) { tests := []struct { - name string - jsonData []byte - expected string - wantErr require.ErrorAssertionFunc + name string + jsonData []byte + expected string + expectedWasInt bool + wantErr require.ErrorAssertionFunc }{ // string inputs - should pass through unchanged { @@ -148,59 +152,70 @@ func Test_intOrStringFileType_UnmarshalJSON(t *testing.T) { }, // integer inputs - should convert to string representation { - name: "int 0 (TypeRegular)", - jsonData: []byte(`0`), - expected: "RegularFile", + name: "int 0 (TypeRegular)", + jsonData: []byte(`0`), + expected: "RegularFile", + expectedWasInt: true, }, { - name: "int 1 (TypeHardLink)", - jsonData: []byte(`1`), - expected: "HardLink", + name: "int 1 (TypeHardLink)", + jsonData: []byte(`1`), + expected: "HardLink", + expectedWasInt: true, }, { - name: "int 2 (TypeSymLink)", - jsonData: []byte(`2`), - expected: "SymbolicLink", + name: "int 2 (TypeSymLink)", + jsonData: []byte(`2`), + expected: "SymbolicLink", + expectedWasInt: true, }, { - name: "int 3 (TypeCharacterDevice)", - jsonData: []byte(`3`), - expected: "CharacterDevice", + name: "int 3 (TypeCharacterDevice)", + jsonData: []byte(`3`), + expected: "CharacterDevice", + expectedWasInt: true, }, { - name: "int 4 (TypeBlockDevice)", - jsonData: []byte(`4`), - expected: "BlockDevice", + name: "int 4 (TypeBlockDevice)", + jsonData: []byte(`4`), + expected: "BlockDevice", + expectedWasInt: true, }, { - name: "int 5 (TypeDirectory)", - jsonData: []byte(`5`), - expected: "Directory", + name: "int 5 (TypeDirectory)", + jsonData: []byte(`5`), + expected: "Directory", + expectedWasInt: true, }, { - name: "int 6 (TypeFIFO)", - jsonData: []byte(`6`), - expected: "FIFONode", + name: "int 6 (TypeFIFO)", + jsonData: []byte(`6`), + expected: "FIFONode", + expectedWasInt: true, }, { - name: "int 7 (TypeSocket)", - jsonData: []byte(`7`), - expected: "Socket", + name: "int 7 (TypeSocket)", + jsonData: []byte(`7`), + expected: "Socket", + expectedWasInt: true, }, { - name: "int 8 (TypeIrregular)", - jsonData: []byte(`8`), - expected: "IrregularFile", + name: "int 8 (TypeIrregular)", + jsonData: []byte(`8`), + expected: "IrregularFile", + expectedWasInt: true, }, { - name: "unknown int", - jsonData: []byte(`99`), - expected: "Unknown", + name: "unknown int", + jsonData: []byte(`99`), + expected: "Unknown", + expectedWasInt: true, }, { - name: "negative int", - jsonData: []byte(`-1`), - expected: "Unknown", + name: "negative int", + jsonData: []byte(`-1`), + expected: "Unknown", + expectedWasInt: true, }, { name: "null value", @@ -244,12 +259,13 @@ func Test_intOrStringFileType_UnmarshalJSON(t *testing.T) { if err != nil { return } - assert.Equal(t, test.expected, string(ft)) + assert.Equal(t, test.expected, ft.Value) + assert.Equal(t, test.expectedWasInt, ft.WasInt) }) } } -func Test_convertFileModeToBase8(t *testing.T) { +func Test_convertBase10ToBase8(t *testing.T) { tests := []struct { name string input int @@ -269,7 +285,34 @@ func Test_convertFileModeToBase8(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - actual := convertFileModeToBase8(tt.input) + actual := convertBase10ToBase8(tt.input) + + require.Equal(t, tt.expected, actual) + }) + } +} + +func Test_convertBase8ToBase10(t *testing.T) { + tests := []struct { + name string + input int + expected int + }{ + { + name: "no permissions", + input: 0, + expected: 0, + }, + { + name: "symlink + rwxrwxrwx", + input: 1000000777, + expected: 134218239, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := convertBase8ToBase10(tt.input) require.Equal(t, tt.expected, actual) }) From 10f06317104361183b4cb803c0e6ac1727257551 Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Wed, 11 Jun 2025 17:00:55 -0400 Subject: [PATCH 04/15] fix: provide separate nonroot image (#3998) Signed-off-by: Keith Zantow --- .goreleaser.yaml | 125 +++++++++++++++++++++++++++++++++++++-------- Dockerfile | 8 +-- Dockerfile.nonroot | 27 ++++++++++ 3 files changed, 135 insertions(+), 25 deletions(-) create mode 100644 Dockerfile.nonroot diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 03da7e4dcdd..de69b0ebead 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -126,6 +126,59 @@ dockers: - "--build-arg=VCS_REF={{.FullCommit}}" - "--build-arg=VCS_URL={{.GitURL}}" + # nonroot images... + - image_templates: + - anchore/syft:{{.Tag}}-nonroot-amd64 + - ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64 + goarch: amd64 + dockerfile: Dockerfile.nonroot + use: buildx + build_flag_templates: + - "--platform=linux/amd64" + - "--build-arg=BUILD_DATE={{.Date}}" + - "--build-arg=BUILD_VERSION={{.Version}}" + - "--build-arg=VCS_REF={{.FullCommit}}" + - "--build-arg=VCS_URL={{.GitURL}}" + + - image_templates: + - anchore/syft:{{.Tag}}-nonroot-arm64v8 + - ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8 + goarch: arm64 + dockerfile: Dockerfile.nonroot + use: buildx + build_flag_templates: + - "--platform=linux/arm64/v8" + - "--build-arg=BUILD_DATE={{.Date}}" + - "--build-arg=BUILD_VERSION={{.Version}}" + - "--build-arg=VCS_REF={{.FullCommit}}" + - "--build-arg=VCS_URL={{.GitURL}}" + + - image_templates: + - anchore/syft:{{.Tag}}-nonroot-ppc64le + - ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le + goarch: ppc64le + dockerfile: Dockerfile.nonroot + use: buildx + build_flag_templates: + - "--platform=linux/ppc64le" + - "--build-arg=BUILD_DATE={{.Date}}" + - "--build-arg=BUILD_VERSION={{.Version}}" + - "--build-arg=VCS_REF={{.FullCommit}}" + - "--build-arg=VCS_URL={{.GitURL}}" + + - image_templates: + - anchore/syft:{{.Tag}}-nonroot-s390x + - ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x + goarch: s390x + dockerfile: Dockerfile.nonroot + use: buildx + build_flag_templates: + - "--platform=linux/s390x" + - "--build-arg=BUILD_DATE={{.Date}}" + - "--build-arg=BUILD_VERSION={{.Version}}" + - "--build-arg=VCS_REF={{.FullCommit}}" + - "--build-arg=VCS_URL={{.GitURL}}" + # debug images... - image_templates: - anchore/syft:{{.Tag}}-debug-amd64 @@ -180,7 +233,6 @@ dockers: - "--build-arg=VCS_URL={{.GitURL}}" docker_manifests: - # anchore/syft manifests... - name_template: anchore/syft:latest image_templates: - anchore/syft:{{.Tag}}-amd64 @@ -188,6 +240,13 @@ docker_manifests: - anchore/syft:{{.Tag}}-ppc64le - anchore/syft:{{.Tag}}-s390x + - name_template: ghcr.io/anchore/syft:latest + image_templates: + - ghcr.io/anchore/syft:{{.Tag}}-amd64 + - ghcr.io/anchore/syft:{{.Tag}}-arm64v8 + - ghcr.io/anchore/syft:{{.Tag}}-ppc64le + - ghcr.io/anchore/syft:{{.Tag}}-s390x + - name_template: anchore/syft:{{.Tag}} image_templates: - anchore/syft:{{.Tag}}-amd64 @@ -195,34 +254,49 @@ docker_manifests: - anchore/syft:{{.Tag}}-ppc64le - anchore/syft:{{.Tag}}-s390x - - name_template: anchore/syft:debug - image_templates: - - anchore/syft:{{.Tag}}-debug-amd64 - - anchore/syft:{{.Tag}}-debug-arm64v8 - - anchore/syft:{{.Tag}}-debug-ppc64le - - anchore/syft:{{.Tag}}-debug-s390x - - - name_template: anchore/syft:{{.Tag}}-debug - image_templates: - - anchore/syft:{{.Tag}}-debug-amd64 - - anchore/syft:{{.Tag}}-debug-arm64v8 - - anchore/syft:{{.Tag}}-debug-ppc64le - - anchore/syft:{{.Tag}}-debug-s390x - - # ghcr.io/anchore/syft manifests... - - name_template: ghcr.io/anchore/syft:latest + - name_template: ghcr.io/anchore/syft:{{.Tag}} image_templates: - ghcr.io/anchore/syft:{{.Tag}}-amd64 - ghcr.io/anchore/syft:{{.Tag}}-arm64v8 - ghcr.io/anchore/syft:{{.Tag}}-ppc64le - ghcr.io/anchore/syft:{{.Tag}}-s390x - - name_template: ghcr.io/anchore/syft:{{.Tag}} + # nonroot images... + - name_template: anchore/syft:nonroot image_templates: - - ghcr.io/anchore/syft:{{.Tag}}-amd64 - - ghcr.io/anchore/syft:{{.Tag}}-arm64v8 - - ghcr.io/anchore/syft:{{.Tag}}-ppc64le - - ghcr.io/anchore/syft:{{.Tag}}-s390x + - anchore/syft:{{.Tag}}-nonroot-amd64 + - anchore/syft:{{.Tag}}-nonroot-arm64v8 + - anchore/syft:{{.Tag}}-nonroot-ppc64le + - anchore/syft:{{.Tag}}-nonroot-s390x + + - name_template: ghcr.io/anchore/syft:nonroot + image_templates: + - ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64 + - ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8 + - ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le + - ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x + + - name_template: anchore/syft:{{.Tag}}-nonroot + image_templates: + - anchore/syft:{{.Tag}}-nonroot-amd64 + - anchore/syft:{{.Tag}}-nonroot-arm64v8 + - anchore/syft:{{.Tag}}-nonroot-ppc64le + - anchore/syft:{{.Tag}}-nonroot-s390x + + - name_template: ghcr.io/anchore/syft:{{.Tag}}-nonroot + image_templates: + - ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64 + - ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8 + - ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le + - ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x + + # debug images... + - name_template: anchore/syft:debug + image_templates: + - anchore/syft:{{.Tag}}-debug-amd64 + - anchore/syft:{{.Tag}}-debug-arm64v8 + - anchore/syft:{{.Tag}}-debug-ppc64le + - anchore/syft:{{.Tag}}-debug-s390x - name_template: ghcr.io/anchore/syft:debug image_templates: @@ -231,6 +305,13 @@ docker_manifests: - ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le - ghcr.io/anchore/syft:{{.Tag}}-debug-s390x + - name_template: anchore/syft:{{.Tag}}-debug + image_templates: + - anchore/syft:{{.Tag}}-debug-amd64 + - anchore/syft:{{.Tag}}-debug-arm64v8 + - anchore/syft:{{.Tag}}-debug-ppc64le + - anchore/syft:{{.Tag}}-debug-s390x + - name_template: ghcr.io/anchore/syft:{{.Tag}}-debug image_templates: - ghcr.io/anchore/syft:{{.Tag}}-debug-amd64 diff --git a/Dockerfile b/Dockerfile index 86b6b643ffb..9e682b2d6b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,14 @@ -FROM gcr.io/distroless/static-debian12:nonroot +FROM gcr.io/distroless/static-debian12:latest AS build + +FROM scratch +# needed for version check HTTPS request +COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt # create the /tmp dir, which is needed for image content cache WORKDIR /tmp COPY syft / -USER nonroot - ARG BUILD_DATE ARG BUILD_VERSION ARG VCS_REF diff --git a/Dockerfile.nonroot b/Dockerfile.nonroot new file mode 100644 index 00000000000..86b6b643ffb --- /dev/null +++ b/Dockerfile.nonroot @@ -0,0 +1,27 @@ +FROM gcr.io/distroless/static-debian12:nonroot + +# create the /tmp dir, which is needed for image content cache +WORKDIR /tmp + +COPY syft / + +USER nonroot + +ARG BUILD_DATE +ARG BUILD_VERSION +ARG VCS_REF +ARG VCS_URL + +LABEL org.opencontainers.image.created=$BUILD_DATE +LABEL org.opencontainers.image.title="syft" +LABEL org.opencontainers.image.description="CLI tool and library for generating a Software Bill of Materials from container images and filesystems" +LABEL org.opencontainers.image.source=$VCS_URL +LABEL org.opencontainers.image.revision=$VCS_REF +LABEL org.opencontainers.image.vendor="Anchore, Inc." +LABEL org.opencontainers.image.version=$BUILD_VERSION +LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL io.artifacthub.package.readme-url="https://raw.githubusercontent.com/anchore/syft/main/README.md" +LABEL io.artifacthub.package.logo-url="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png" +LABEL io.artifacthub.package.license="Apache-2.0" + +ENTRYPOINT ["/syft"] From c19558dd73d430bb7c527af595359d4e80bab3a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 10:37:53 -0400 Subject: [PATCH 05/15] chore(deps): bump github/codeql-action from 3.28.19 to 3.29.0 (#4000) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.19 to 3.29.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/fca7ace96b7d713c7035871441bd52efbe39e27e...ce28f5bb42b7a9f2c824e633a3f6ee835bab6858) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 2f8fbb0244b..334c2da2ec8 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -45,7 +45,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e #v3.28.19 + uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 #v3.29.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -56,7 +56,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@fca7ace96b7d713c7035871441bd52efbe39e27e #v3.28.19 + uses: github/codeql-action/autobuild@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 #v3.29.0 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -70,4 +70,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e #v3.28.19 + uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 #v3.29.0 From 181e180284ea0ed2458e92f06279cd4184ce2053 Mon Sep 17 00:00:00 2001 From: "anchore-actions-token-generator[bot]" <102182147+anchore-actions-token-generator[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 10:38:44 -0400 Subject: [PATCH 06/15] chore(deps): update tools to latest versions (#3992) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com> --- .binny.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.binny.yaml b/.binny.yaml index 188d141de79..e96a3bb5a3a 100644 --- a/.binny.yaml +++ b/.binny.yaml @@ -98,7 +98,7 @@ tools: # used for triggering a release - name: gh version: - want: v2.74.0 + want: v2.74.1 method: github-release with: repo: cli/cli From 72f9c4256280d8f5bba825fadae93c8b8c52789d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 11:15:22 -0400 Subject: [PATCH 07/15] chore(deps): bump github.com/google/go-containerregistry (#4009) Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.20.5 to 0.20.6. - [Release notes](https://github.com/google/go-containerregistry/releases) - [Changelog](https://github.com/google/go-containerregistry/blob/main/.goreleaser.yml) - [Commits](https://github.com/google/go-containerregistry/compare/v0.20.5...v0.20.6) --- updated-dependencies: - dependency-name: github.com/google/go-containerregistry dependency-version: 0.20.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 22 ++++++++++++---------- go.sum | 50 ++++++++++++++++++++++++++------------------------ 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index b6ebeffed6e..b7fa0f786c2 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.2.1 github.com/gohugoio/hashstructure v0.5.0 github.com/google/go-cmp v0.7.0 - github.com/google/go-containerregistry v0.20.5 + github.com/google/go-containerregistry v0.20.6 github.com/google/licensecheck v0.3.1 github.com/google/uuid v1.6.0 github.com/gookit/color v1.5.4 @@ -124,18 +124,18 @@ require ( github.com/containerd/containerd v1.7.27 // indirect github.com/containerd/containerd/api v1.8.0 // indirect github.com/containerd/continuity v0.4.4 // indirect - github.com/containerd/errdefs v0.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect github.com/containerd/ttrpc v1.2.7 // indirect - github.com/containerd/typeurl/v2 v2.1.1 // indirect + github.com/containerd/typeurl/v2 v2.2.0 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/docker/cli v28.2.2+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v28.1.1+incompatible // indirect + github.com/docker/docker v28.2.2+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect @@ -150,7 +150,7 @@ require ( github.com/gkampitakis/ciinfo v0.3.2 // indirect github.com/gkampitakis/go-diff v1.3.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-restruct/restruct v1.2.0-alpha // indirect github.com/goccy/go-yaml v1.18.0 // indirect @@ -235,10 +235,10 @@ require ( github.com/zclconf/go-cty v1.13.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect @@ -247,7 +247,7 @@ require ( golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.26.0 // indirect - golang.org/x/tools v0.33.0 // indirect + golang.org/x/tools v0.34.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect @@ -259,6 +259,8 @@ require ( modernc.org/memory v1.11.0 // indirect ) +require github.com/containerd/errdefs/pkg v0.3.0 // indirect + retract ( v1.25.0 // published with a replace directive (confusing for API users) v0.53.2 diff --git a/go.sum b/go.sum index 978fca3ea1a..a28b46f9c91 100644 --- a/go.sum +++ b/go.sum @@ -231,8 +231,10 @@ github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVM github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= -github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= -github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +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/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -243,8 +245,8 @@ github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRcc github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= -github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= -github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= +github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso= +github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -268,8 +270,8 @@ github.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsy github.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= -github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= +github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -351,8 +353,8 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 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-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +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-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc= @@ -428,8 +430,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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-containerregistry v0.20.5 h1:4RnlYcDs5hoA++CeFjlbZ/U9Yp1EuWr+UhhTyYQjOP0= -github.com/google/go-containerregistry v0.20.5/go.mod h1:Q14vdOOzug02bwnhMkZKD4e30pDaD9W65qzXpyzF49E= +github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU= +github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs= github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY= @@ -885,22 +887,22 @@ 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.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= @@ -1201,8 +1203,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 0b57d039586288f8c6a8e6e6cac9e10d71b2ebc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 11:15:37 -0400 Subject: [PATCH 08/15] chore(deps): bump anchore/sbom-action from 0.20.0 to 0.20.1 (#4008) Bumps [anchore/sbom-action](https://github.com/anchore/sbom-action) from 0.20.0 to 0.20.1. - [Release notes](https://github.com/anchore/sbom-action/releases) - [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md) - [Commits](https://github.com/anchore/sbom-action/compare/e11c554f704a0b820cbf8c51673f6945e0731532...9246b90769f852b3a8921f330c59e0b3f439d6e9) --- updated-dependencies: - dependency-name: anchore/sbom-action dependency-version: 0.20.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6ae2f2d0b1d..0e7ea1f8549 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -148,7 +148,7 @@ jobs: # for updating brew formula in anchore/homebrew-syft GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }} - - uses: anchore/sbom-action@e11c554f704a0b820cbf8c51673f6945e0731532 #v0.20.0 + - uses: anchore/sbom-action@9246b90769f852b3a8921f330c59e0b3f439d6e9 #v0.20.1 continue-on-error: true with: file: go.mod From 0bfda2c514118073afc83aa57bce0c45deb06c4b Mon Sep 17 00:00:00 2001 From: "anchore-actions-token-generator[bot]" <102182147+anchore-actions-token-generator[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 11:15:50 -0400 Subject: [PATCH 09/15] chore(deps): update CPE dictionary index (#4007) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com> --- .../dictionary/data/cpe-index.json | 63 ++++++++++++++++--- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json b/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json index 95449b238cb..de475fb8ed4 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json +++ b/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json @@ -8654,6 +8654,9 @@ "bookshelf": [ "cpe:2.3:a:bookshelf_project:bookshelf:*:*:*:*:*:wordpress:*:*" ], + "bookster": [ + "cpe:2.3:a:wpbookster:bookster:*:*:*:*:*:wordpress:*:*" + ], "boostify-header-footer-builder": [ "cpe:2.3:a:woostify:boostify_header_footer_builder_for_elementor:*:*:*:*:*:wordpress:*:*" ], @@ -9023,6 +9026,9 @@ "cars-seller-auto-classifieds-script": [ "cpe:2.3:a:cars-seller-auto-classifieds-script_project:cars-seller-auto-classifieds-script:*:*:*:*:*:wordpress:*:*" ], + "cart-tracking-for-woocommerce": [ + "cpe:2.3:a:wpdever:cart_tracking_for_woocommerce:*:*:*:*:*:wordpress:*:*" + ], "cart66-lite": [ "cpe:2.3:a:reality66:cart66_lite:*:*:*:*:*:wordpress:*:*" ], @@ -10123,6 +10129,9 @@ "cww-companion": [ "cpe:2.3:a:codeworkweb:cww_companion:*:*:*:*:*:wordpress:*:*" ], + "cyan-backup": [ + "cpe:2.3:a:toolstack:cyan_backup:*:*:*:*:*:wordpress:*:*" + ], "cybersoldier": [ "cpe:2.3:a:webbigt:cybersoldier:*:*:*:*:*:wordpress:*:*" ], @@ -11450,6 +11459,9 @@ "flat-preloader": [ "cpe:2.3:a:flat_preloader_project:flat_preloader:*:*:*:*:*:wordpress:*:*" ], + "flatpm-wp": [ + "cpe:2.3:a:mehanoid:flatpm:*:*:*:*:*:wordpress:*:*" + ], "flexi": [ "cpe:2.3:a:odude:flexi:*:*:*:*:*:wordpress:*:*" ], @@ -11728,6 +11740,9 @@ "funnel-builder": [ "cpe:2.3:a:funnelkit:funnel_builder:*:*:*:*:*:wordpress:*:*" ], + "funnel-builder-pro": [ + "cpe:2.3:a:funnelkit:funnel_builder:*:*:*:*:*:wordpress:*:*" + ], "funnelforms-free": [ "cpe:2.3:a:funnelforms:funnelforms_free:*:*:*:*:*:wordpress:*:*" ], @@ -11837,6 +11852,9 @@ "gdpr-data-request-form": [ "cpe:2.3:a:whodunit:gdpr_data_request_form:*:*:*:*:*:wordpress:*:*" ], + "gdpr-framework": [ + "cpe:2.3:a:data443:gdpr_framework:*:*:*:*:*:wordpress:*:*" + ], "gecka-terms-thumbnails": [ "cpe:2.3:a:gecka:terms_thumbnails:*:*:*:*:*:wordpress:*:*" ], @@ -12126,7 +12144,7 @@ "cpe:2.3:a:greeklish-permalink_project:greeklish-permalink:*:*:*:*:*:wordpress:*:*" ], "greenshift-animation-and-page-builder-blocks": [ - "cpe:2.3:a:greenshiftwp:greenshift_-_animation_and_page_builder_blocks:*:*:*:*:*:wordpress:*:*" + "cpe:2.3:a:wpsoul:greenshift:*:*:*:*:*:wordpress:*:*" ], "greenwallet-gateway": [ "cpe:2.3:a:envision\\\u0026company:woocommerce_green_wallet_gateway:*:*:*:*:*:wordpress:*:*" @@ -13226,7 +13244,8 @@ "cpe:2.3:a:wplearnmanager:wp_learn_manager:*:*:*:*:*:wordpress:*:*" ], "learning-management-system": [ - "cpe:2.3:a:masteriyo:masteriyo:*:*:*:*:*:wordpress:*:*" + "cpe:2.3:a:themegrill:masteriyo:*:*:*:*:free:wordpress:*:*", + "cpe:2.3:a:themegrill:masteriyo:*:*:*:*:pro:wordpress:*:*" ], "learnpress": [ "cpe:2.3:a:thimpress:learnpress:*:*:*:*:*:wordpress:*:*", @@ -13477,6 +13496,9 @@ "luckywp-table-of-contents": [ "cpe:2.3:a:theluckywp:luckywp_table_of_contents:*:*:*:*:*:wordpress:*:*" ], + "lupsonline-link-netwerk": [ + "cpe:2.3:a:lupsonline:seo_flow:*:*:*:*:*:wordpress:*:*" + ], "lws-affiliation": [ "cpe:2.3:a:lws:affiliation:*:*:*:*:*:wordpress:*:*" ], @@ -13716,6 +13738,9 @@ "maz-loader": [ "cpe:2.3:a:feataholic:maz_loader:*:*:*:*:*:wordpress:*:*" ], + "mb-custom-post-type": [ + "cpe:2.3:a:deluxeblogtips:mb_custom_post_types_\\\u0026_custom_taxonomies:*:*:*:*:*:wordpress:*:*" + ], "mdc-youtube-downloader": [ "cpe:2.3:a:mdc_youtube_downloader_project:mdc_youtube_downloader:*:*:*:*:*:wordpress:*:*" ], @@ -13772,9 +13797,7 @@ "cpe:2.3:a:mediavine:create:*:*:*:*:*:wordpress:*:*" ], "meeting-scheduler-by-vcita": [ - "cpe:2.3:a:vcita:online_booking_\\\u0026_scheduling_calendar:*:*:*:*:*:wordpress:*:*", - "cpe:2.3:a:vcita:online_booking_\\\u0026_scheduling_calendar_for_wordpress:*:*:*:*:*:wordpress:*:*", - "cpe:2.3:a:vcita:online_booking_\\\u0026_scheduling_calendar_for_wordpress_by_vcita:*:*:*:*:*:wordpress:*:*" + "cpe:2.3:a:vcita:online_booking_\\\u0026_scheduling_calendar:*:*:*:*:*:wordpress:*:*" ], "mega-addons-for-visual-composer": [ "cpe:2.3:a:topdigitaltrends:mega_addons_for_wpbakery_page_builder:*:*:*:*:*:wordpress:*:*" @@ -13824,6 +13847,9 @@ "membership-simplified-for-oap-members-only": [ "cpe:2.3:a:membership_simplified_project:membership_simplified:*:*:*:*:*:wordpress:*:*" ], + "memberspace": [ + "cpe:2.3:a:memberspace:memberspace:*:*:*:*:*:wordpress:*:*" + ], "memphis-documents-library": [ "cpe:2.3:a:memphis_documents_library_project:memphis_documents_library:*:*:*:*:*:wordpress:*:*" ], @@ -13950,6 +13976,9 @@ "mobile-browser-color-select": [ "cpe:2.3:a:script:mobile_browser_color_select:*:*:*:*:*:wordpress:*:*" ], + "mobile-contact-bar": [ + "cpe:2.3:a:annabansaghi:mobile_contact_bar:*:*:*:*:*:wordpress:*:*" + ], "mobile-events-manager": [ "cpe:2.3:a:mobile_events_manager_project:mobile_events_manager:*:*:*:*:*:wordpress:*:*", "cpe:2.3:a:mobileeventsmanager:mobile_events_manager:*:*:*:*:*:wordpress:*:*" @@ -15186,6 +15215,9 @@ "cpe:2.3:a:post_pay_counter_project:post_pay_counter:*:*:*:*:*:wordpress:*:*", "cpe:2.3:a:thecrowned:post_pay_counter:*:*:*:*:*:wordpress:*:*" ], + "post-slider-and-carousel": [ + "cpe:2.3:a:infornweb:post_slider_and_post_carousel:*:*:*:*:free:wordpress:*:*" + ], "post-slider-carousel": [ "cpe:2.3:a:i13websolution:post_sliders_\\\u0026_post_grids:*:*:*:*:*:wordpress:*:*" ], @@ -16132,7 +16164,8 @@ "cpe:2.3:a:scoutnet:kalender:*:*:*:*:*:wordpress:*:*" ], "scratch-win-giveaways-for-website-facebook": [ - "cpe:2.3:a:akashmalik:scratch_\\\u0026_win:*:*:*:*:*:wordpress:*:*" + "cpe:2.3:a:akashmalik:scratch_\\\u0026_win:*:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:appsmav:scratch_\\\u0026_win:*:*:*:*:*:wordpress:*:*" ], "scribble-maps": [ "cpe:2.3:a:scribblemaps:scribble_maps:*:*:*:*:*:wordpress:*:*" @@ -16564,6 +16597,9 @@ "simple-form": [ "cpe:2.3:a:devsabbirahmed:simple_form:*:*:*:*:*:wordpress:*:*" ], + "simple-gallery-with-filter": [ + "cpe:2.3:a:come2theweb:simple_gallery_with_filter:*:*:*:*:*:wordpress:*:*" + ], "simple-history": [ "cpe:2.3:a:simple_history_project:simple_history:*:*:*:*:*:wordpress:*:*" ], @@ -18040,6 +18076,9 @@ "ultimate-classified-listings": [ "cpe:2.3:a:webcodingplace:ultimate_classified_listings:*:*:*:*:*:wordpress:*:*" ], + "ultimate-coming-soon": [ + "cpe:2.3:a:rstheme:ultimate_coming_soon_\\\u0026_maintenance:*:*:*:*:*:wordpress:*:*" + ], "ultimate-dashboard": [ "cpe:2.3:a:davidvongries:ultimate_dashboard:*:*:*:*:*:wordpress:*:*" ], @@ -18206,6 +18245,9 @@ "user-activity-log": [ "cpe:2.3:a:solwininfotech:user_activity_log:*:*:*:*:*:wordpress:*:*" ], + "user-activity-tracking-and-log": [ + "cpe:2.3:a:mooveagency:user_activity_tracking_and_log:*:*:*:*:*:wordpress:*:*" + ], "user-avatar-reloaded": [ "cpe:2.3:a:wpexperts:user_avatar-reloaded:*:*:*:*:*:wordpress:*:*" ], @@ -18483,6 +18525,9 @@ "wc-affiliate": [ "cpe:2.3:a:codexpert:wc_affiliate:*:*:*:*:*:wordpress:*:*" ], + "wc-checkout-getnet": [ + "cpe:2.3:a:coffee-code:plugin_oficial:*:*:*:*:*:wordpress:*:*" + ], "wc-dynamic-pricing-and-discounts": [ "cpe:2.3:a:rightpress:woocommerce_dynamic_pricing_\\\u0026_discounts:*:*:*:*:*:wordpress:*:*" ], @@ -18683,6 +18728,9 @@ "whizzy": [ "cpe:2.3:a:upqode:whizzy:*:*:*:*:*:wordpress:*:*" ], + "wholesale-market": [ + "cpe:2.3:a:cedcommerce:wholesale_market:*:*:*:*:*:wordpress:*:*" + ], "wholesale-pricing-woocommerce": [ "cpe:2.3:a:wpfactory:quantity_dynamic_pricing_\\\u0026_bulk_discounts_for_woocommerce:*:*:*:*:free:wordpress:*:*" ], @@ -19150,7 +19198,8 @@ "cpe:2.3:a:wordpress_popular_posts_project:wordpress_popular_posts:*:*:*:*:*:wordpress:*:*" ], "wordpress-popup": [ - "cpe:2.3:a:incsub:hustle:*:*:*:*:*:wordpress:*:*" + "cpe:2.3:a:incsub:hustle:*:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:wpmudev:hustle:*:*:*:*:*:wordpress:*:*" ], "wordpress-seo": [ "cpe:2.3:a:yoast:wordpress_seo:*:*:*:*:*:wordpress:*:*", From b52b13c03c6adaea27a335a729bdb3b19e63f4c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 11:34:58 -0400 Subject: [PATCH 10/15] chore(deps): bump sigstore/cosign-installer from 3.8.2 to 3.9.0 (#4015) Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.8.2 to 3.9.0. - [Release notes](https://github.com/sigstore/cosign-installer/releases) - [Commits](https://github.com/sigstore/cosign-installer/compare/v3.8.2...v3.9.0) --- updated-dependencies: - dependency-name: sigstore/cosign-installer dependency-version: 3.9.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/validations.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml index 7b19f5064bf..662589efdf8 100644 --- a/.github/workflows/validations.yaml +++ b/.github/workflows/validations.yaml @@ -198,7 +198,7 @@ jobs: runs-on: macos-latest steps: - name: Install Cosign - uses: sigstore/cosign-installer@v3.8.2 + uses: sigstore/cosign-installer@v3.9.0 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 From 32a30f76c68edfb94c1a23111b93868a0b1ac737 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 11:35:09 -0400 Subject: [PATCH 11/15] chore(deps): bump github.com/go-viper/mapstructure/v2 (#4014) Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.2.1 to 2.3.0. - [Release notes](https://github.com/go-viper/mapstructure/releases) - [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md) - [Commits](https://github.com/go-viper/mapstructure/compare/v2.2.1...v2.3.0) --- updated-dependencies: - dependency-name: github.com/go-viper/mapstructure/v2 dependency-version: 2.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b7fa0f786c2..b8fc4bf9259 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( github.com/go-git/go-billy/v5 v5.6.2 github.com/go-git/go-git/v5 v5.16.2 github.com/go-test/deep v1.1.1 - github.com/go-viper/mapstructure/v2 v2.2.1 + github.com/go-viper/mapstructure/v2 v2.3.0 github.com/gohugoio/hashstructure v0.5.0 github.com/google/go-cmp v0.7.0 github.com/google/go-containerregistry v0.20.6 diff --git a/go.sum b/go.sum index a28b46f9c91..60775755f99 100644 --- a/go.sum +++ b/go.sum @@ -363,8 +363,8 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= +github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= From d9eb1d7c1b6a6169d49a4231817a158acd1e0dcb Mon Sep 17 00:00:00 2001 From: "anchore-actions-token-generator[bot]" <102182147+anchore-actions-token-generator[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 11:35:22 -0400 Subject: [PATCH 12/15] chore(deps): update tools to latest versions (#4012) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com> --- .binny.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.binny.yaml b/.binny.yaml index e96a3bb5a3a..1099ac4e501 100644 --- a/.binny.yaml +++ b/.binny.yaml @@ -42,7 +42,7 @@ tools: # used for signing the checksums file at release - name: cosign version: - want: v2.5.0 + want: v2.5.1 method: github-release with: repo: sigstore/cosign From 49115355d47c6386f7c8d8a9022d4f557721fa36 Mon Sep 17 00:00:00 2001 From: "anchore-actions-token-generator[bot]" <102182147+anchore-actions-token-generator[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 14:02:03 -0400 Subject: [PATCH 13/15] chore(deps): update tools to latest versions (#4016) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com> --- .binny.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.binny.yaml b/.binny.yaml index 1099ac4e501..ceb635a4f12 100644 --- a/.binny.yaml +++ b/.binny.yaml @@ -42,7 +42,7 @@ tools: # used for signing the checksums file at release - name: cosign version: - want: v2.5.1 + want: v2.5.2 method: github-release with: repo: sigstore/cosign @@ -98,7 +98,7 @@ tools: # used for triggering a release - name: gh version: - want: v2.74.1 + want: v2.74.2 method: github-release with: repo: cli/cli From 4eb8ba457570f7ed6659d03577564ee2ad0aae6e Mon Sep 17 00:00:00 2001 From: "anchore-actions-token-generator[bot]" <102182147+anchore-actions-token-generator[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 11:30:19 -0400 Subject: [PATCH 14/15] chore(deps): update CPE dictionary index (#4021) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com> --- .../cpegenerate/dictionary/data/cpe-index.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json b/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json index de475fb8ed4..6ff0060f83d 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json +++ b/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json @@ -4116,6 +4116,9 @@ "cpe:2.3:a:mysql_project:mysql:*:*:*:*:*:node.js:*:*", "cpe:2.3:a:mysqljs:mysql:*:*:*:*:*:node.js:*:*" ], + "mysql2": [ + "cpe:2.3:a:sidorares:mysql2:*:*:*:*:*:node.js:*:*" + ], "mysqljs": [ "cpe:2.3:a:mysqljs_project:mysqljs:*:*:*:*:*:node.js:*:*" ], @@ -8153,6 +8156,9 @@ "auto-post-thumbnail": [ "cpe:2.3:a:cm-wp:auto_featured_image:*:*:*:*:*:wordpress:*:*" ], + "auto-prune-posts": [ + "cpe:2.3:a:klarned:auto_prune_posts:*:*:*:*:*:wordpress:*:*" + ], "auto-refresh-single-page": [ "cpe:2.3:a:rymera:auto_refresh_single_page:*:*:*:*:*:wordpress:*:*" ], @@ -13418,6 +13424,9 @@ "loco-translate": [ "cpe:2.3:a:loco_translate_project:loco_translate:*:*:*:*:*:wordpress:*:*" ], + "logdash-activity-log": [ + "cpe:2.3:a:deryckoe:logdash_activity_log:*:*:*:*:*:wordpress:*:*" + ], "loggedin": [ "cpe:2.3:a:duckdev:loggedin:*:*:*:*:*:wordpress:*:*" ], @@ -14798,6 +14807,9 @@ "payment-forms-for-paystack": [ "cpe:2.3:a:paystack:payment_forms_for_paystack:*:*:*:*:*:wordpress:*:*" ], + "payment-gateway-for-telcell": [ + "cpe:2.3:a:hkdigital:payment_gateway_for_telcell:*:*:*:*:*:wordpress:*:*" + ], "payment-gateway-stripe-and-woocommerce-integration": [ "cpe:2.3:a:webtoffee:stripe_payment_plugin_for_woocommerce:*:*:*:*:*:wordpress:*:*" ], @@ -21443,6 +21455,9 @@ "shapely": [ "cpe:2.3:a:colorlib:shapely:*:*:*:*:*:wordpress:*:*" ], + "simplecharm": [ + "cpe:2.3:a:kmfoysal06:simplecharm:*:*:*:*:*:wordpress:*:*" + ], "sinatra": [ "cpe:2.3:a:sinatrateam:sinatra:*:*:*:*:*:wordpress:*:*" ], From 2bda0864236a1579f2ecf5f6a56581067c8af03f Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Wed, 25 Jun 2025 16:53:35 -0400 Subject: [PATCH 15/15] Add ability to scan snaps (as a source) (#3929) --- .../handle_pull_source_test.snap | 8 + cmd/syft/cli/ui/handle_cataloger_task_test.go | 12 +- cmd/syft/cli/ui/handle_pull_source.go | 37 + cmd/syft/cli/ui/handle_pull_source_test.go | 121 ++ cmd/syft/cli/ui/handler.go | 1 + cmd/syft/internal/clio_setup_config.go | 2 +- go.mod | 30 +- go.sum | 1021 ++++++++++++++++- internal/bus/helpers.go | 34 +- internal/file/getter.go | 154 +++ internal/file/getter_test.go | 268 +++++ internal/file/squashfs.go | 89 ++ internal/file/squashfs_test.go | 250 ++++ internal/string_helpers.go | 11 + internal/string_helpers_test.go | 57 + internal/task/executor.go | 2 +- internal/task/executor_test.go | 2 +- syft/create_sbom.go | 6 +- syft/create_sbom_config.go | 2 + syft/event/event.go | 4 + syft/event/monitor/cataloger_task_progress.go | 15 - syft/event/monitor/generic_task.go | 10 + syft/event/parsers/parsers.go | 20 + syft/file/cataloger/executable/cataloger.go | 2 +- syft/file/cataloger/filecontent/cataloger.go | 2 +- syft/file/cataloger/filedigest/cataloger.go | 2 +- syft/file/cataloger/filemetadata/cataloger.go | 2 +- .../common/spdxhelpers/to_format_model.go | 13 + .../spdxhelpers/to_format_model_test.go | 76 ++ syft/format/github/internal/model/model.go | 5 + .../github/internal/model/model_test.go | 6 + .../spdxutil/helpers/document_name_test.go | 9 + .../spdxutil/helpers/document_namespace.go | 3 + .../helpers/document_namespace_test.go | 10 +- syft/format/syftjson/model/source_test.go | 45 + syft/format/syftjson/to_format_model_test.go | 30 + syft/format/syftjson/to_syft_model_test.go | 30 + syft/get_source.go | 36 +- syft/get_source_test.go | 208 ++++ syft/internal/fileresolver/directory.go | 17 +- .../fileresolver/directory_indexer.go | 35 +- .../fileresolver/directory_indexer_test.go | 2 +- syft/internal/fileresolver/file.go | 17 +- syft/internal/fileresolver/file_indexer.go | 13 +- .../fileresolver/filetree_resolver.go | 78 +- .../fileresolver/filetree_resolver_test.go | 8 +- syft/internal/sourcemetadata/generated.go | 2 +- syft/internal/sourcemetadata/names.go | 1 + syft/internal/unionreader/union_reader.go | 73 +- .../internal/unionreader/union_reader_test.go | 274 +++++ syft/pkg/cataloger/python/parse_wheel_egg.go | 37 +- .../python/parse_wheel_egg_record.go | 22 +- .../python/parse_wheel_egg_record_test.go | 5 +- syft/source/snap_metadata.go | 23 + syft/source/snapsource/manifest.go | 56 + syft/source/snapsource/snap.go | 249 ++++ syft/source/snapsource/snap_source.go | 378 ++++++ .../source/snapsource/snap_source_provider.go | 54 + syft/source/snapsource/snap_source_test.go | 86 ++ syft/source/snapsource/snap_test.go | 612 ++++++++++ syft/source/snapsource/snapcraft_api.go | 159 +++ syft/source/snapsource/snapcraft_api_test.go | 383 +++++++ .../sourceproviders/source_providers.go | 16 +- 63 files changed, 5060 insertions(+), 175 deletions(-) create mode 100755 cmd/syft/cli/ui/__snapshots__/handle_pull_source_test.snap create mode 100644 cmd/syft/cli/ui/handle_pull_source.go create mode 100644 cmd/syft/cli/ui/handle_pull_source_test.go create mode 100644 internal/file/getter.go create mode 100644 internal/file/getter_test.go create mode 100644 internal/file/squashfs.go create mode 100644 internal/file/squashfs_test.go delete mode 100644 syft/event/monitor/cataloger_task_progress.go create mode 100644 syft/get_source_test.go create mode 100644 syft/source/snap_metadata.go create mode 100644 syft/source/snapsource/manifest.go create mode 100644 syft/source/snapsource/snap.go create mode 100644 syft/source/snapsource/snap_source.go create mode 100644 syft/source/snapsource/snap_source_provider.go create mode 100644 syft/source/snapsource/snap_source_test.go create mode 100644 syft/source/snapsource/snap_test.go create mode 100644 syft/source/snapsource/snapcraft_api.go create mode 100644 syft/source/snapsource/snapcraft_api_test.go diff --git a/cmd/syft/cli/ui/__snapshots__/handle_pull_source_test.snap b/cmd/syft/cli/ui/__snapshots__/handle_pull_source_test.snap new file mode 100755 index 00000000000..c04614f74ca --- /dev/null +++ b/cmd/syft/cli/ui/__snapshots__/handle_pull_source_test.snap @@ -0,0 +1,8 @@ + +[TestHandler_handlePullSourceStarted/snap_download_in_progress - 1] + ⠋ Downloading snap file... ━━━━━━━━━━━━━━━━━━━━ example-app_1.0_amd64.snap +--- + +[TestHandler_handlePullSourceStarted/snap_download_complete - 1] + ✔ Snap downloaded successfully example-app_1.0_amd64.snap +--- diff --git a/cmd/syft/cli/ui/handle_cataloger_task_test.go b/cmd/syft/cli/ui/handle_cataloger_task_test.go index 3efb0fdac21..27ef32ce434 100644 --- a/cmd/syft/cli/ui/handle_cataloger_task_test.go +++ b/cmd/syft/cli/ui/handle_cataloger_task_test.go @@ -26,7 +26,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) { { name: "cataloging task in progress", eventFn: func(t *testing.T) partybus.Event { - value := &monitor.CatalogerTaskProgress{ + value := &monitor.TaskProgress{ AtomicStage: progress.NewAtomicStage("some stage"), Manual: progress.NewManual(100), } @@ -48,7 +48,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) { { name: "cataloging sub task in progress", eventFn: func(t *testing.T) partybus.Event { - value := &monitor.CatalogerTaskProgress{ + value := &monitor.TaskProgress{ AtomicStage: progress.NewAtomicStage("some stage"), Manual: progress.NewManual(100), } @@ -71,7 +71,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) { { name: "cataloging sub task complete", eventFn: func(t *testing.T) partybus.Event { - value := &monitor.CatalogerTaskProgress{ + value := &monitor.TaskProgress{ AtomicStage: progress.NewAtomicStage("some stage"), Manual: progress.NewManual(100), } @@ -94,7 +94,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) { { name: "cataloging sub task complete -- hide stage", eventFn: func(t *testing.T) partybus.Event { - value := &monitor.CatalogerTaskProgress{ + value := &monitor.TaskProgress{ AtomicStage: progress.NewAtomicStage("some stage"), Manual: progress.NewManual(100), } @@ -117,7 +117,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) { { name: "cataloging sub task complete with removal", eventFn: func(t *testing.T) partybus.Event { - value := &monitor.CatalogerTaskProgress{ + value := &monitor.TaskProgress{ AtomicStage: progress.NewAtomicStage("some stage"), Manual: progress.NewManual(100), } @@ -162,7 +162,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) { } // note: this line / event is not under test, only needed to show a sub status - kickoffEvent := &monitor.CatalogerTaskProgress{ + kickoffEvent := &monitor.TaskProgress{ AtomicStage: progress.NewAtomicStage(""), Manual: progress.NewManual(-1), } diff --git a/cmd/syft/cli/ui/handle_pull_source.go b/cmd/syft/cli/ui/handle_pull_source.go new file mode 100644 index 00000000000..b77f41308cb --- /dev/null +++ b/cmd/syft/cli/ui/handle_pull_source.go @@ -0,0 +1,37 @@ +package ui + +import ( + tea "github.com/charmbracelet/bubbletea" + "github.com/wagoodman/go-partybus" + + "github.com/anchore/bubbly/bubbles/taskprogress" + "github.com/anchore/syft/internal/log" + syftEventParsers "github.com/anchore/syft/syft/event/parsers" +) + +func (m *Handler) handlePullSourceStarted(e partybus.Event) []tea.Model { + prog, info, err := syftEventParsers.ParsePullSourceStarted(e) + if err != nil { + log.WithFields("error", err).Debug("unable to parse event") + return nil + } + + tsk := m.newTaskProgress( + taskprogress.Title{ + Default: info.Title.Default, + Running: info.Title.WhileRunning, + Success: info.Title.OnSuccess, + }, + taskprogress.WithStagedProgressable(prog), + ) + + tsk.HideOnSuccess = info.HideOnSuccess + tsk.HideStageOnSuccess = info.HideStageOnSuccess + tsk.HideProgressOnSuccess = true + + if info.Context != "" { + tsk.Context = []string{info.Context} + } + + return []tea.Model{tsk} +} diff --git a/cmd/syft/cli/ui/handle_pull_source_test.go b/cmd/syft/cli/ui/handle_pull_source_test.go new file mode 100644 index 00000000000..cfc9f4d7d84 --- /dev/null +++ b/cmd/syft/cli/ui/handle_pull_source_test.go @@ -0,0 +1,121 @@ +package ui + +import ( + "testing" + "time" + + tea "github.com/charmbracelet/bubbletea" + "github.com/gkampitakis/go-snaps/snaps" + "github.com/stretchr/testify/require" + "github.com/wagoodman/go-partybus" + "github.com/wagoodman/go-progress" + + "github.com/anchore/bubbly/bubbles/taskprogress" + "github.com/anchore/syft/syft/event" + "github.com/anchore/syft/syft/event/monitor" +) + +func TestHandler_handlePullSourceStarted(t *testing.T) { + + tests := []struct { + name string + eventFn func(*testing.T) partybus.Event + iterations int + }{ + { + name: "snap download in progress", + eventFn: func(t *testing.T) partybus.Event { + stage := progress.NewAtomicStage("") + manual := progress.NewManual(0) + manual.SetTotal(1000000) // 1MB file + manual.Set(250000) // 25% downloaded + + taskProg := &monitor.TaskProgress{ + AtomicStage: stage, + Manual: manual, + } + + genericTask := monitor.GenericTask{ + Title: monitor.Title{ + Default: "Downloading snap", + WhileRunning: "Downloading snap file...", + OnSuccess: "Snap downloaded", + }, + Context: "example-app_1.0_amd64.snap", + HideOnSuccess: false, + HideStageOnSuccess: true, + ID: "snap-download-123", + } + + return partybus.Event{ + Type: event.PullSourceStarted, + Source: genericTask, + Value: taskProg, + } + }, + iterations: 5, + }, + { + name: "snap download complete", + eventFn: func(t *testing.T) partybus.Event { + stage := progress.NewAtomicStage("") + manual := progress.NewManual(0) + manual.SetTotal(1000000) // 1MB file + manual.Set(1000000) // 100% downloaded + manual.SetCompleted() + + taskProg := &monitor.TaskProgress{ + AtomicStage: stage, + Manual: manual, + } + + genericTask := monitor.GenericTask{ + Title: monitor.Title{ + Default: "Downloading snap", + WhileRunning: "Downloading snap file...", + OnSuccess: "Snap downloaded successfully", + }, + Context: "example-app_1.0_amd64.snap", + HideOnSuccess: false, + HideStageOnSuccess: true, + ID: "snap-download-123", + } + + return partybus.Event{ + Type: event.PullSourceStarted, + Source: genericTask, + Value: taskProg, + } + }, + iterations: 3, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + event := tt.eventFn(t) + handler := New(DefaultHandlerConfig()) + handler.WindowSize = tea.WindowSizeMsg{ + Width: 100, + Height: 80, + } + + models := handler.handlePullSourceStarted(event) + require.Len(t, models, 1) + model := models[0] + + tsk, ok := model.(taskprogress.Model) + require.True(t, ok) + + gotModel := runModel(t, tsk, tt.iterations, taskprogress.TickMsg{ + Time: time.Now(), + Sequence: tsk.Sequence(), + ID: tsk.ID(), + }) + + got := gotModel.View() + + t.Log(got) + snaps.MatchSnapshot(t, got) + }) + } +} diff --git a/cmd/syft/cli/ui/handler.go b/cmd/syft/cli/ui/handler.go index aefc471f3a1..56135b78675 100644 --- a/cmd/syft/cli/ui/handler.go +++ b/cmd/syft/cli/ui/handler.go @@ -57,6 +57,7 @@ func New(cfg HandlerConfig) *Handler { stereoscopeEvent.FetchImage: simpleHandler(h.handleFetchImage), syftEvent.FileIndexingStarted: simpleHandler(h.handleFileIndexingStarted), syftEvent.AttestationStarted: simpleHandler(h.handleAttestationStarted), + syftEvent.PullSourceStarted: simpleHandler(h.handlePullSourceStarted), syftEvent.CatalogerTaskStarted: h.handleCatalogerTaskStarted, }) diff --git a/cmd/syft/internal/clio_setup_config.go b/cmd/syft/internal/clio_setup_config.go index 446b7335ce3..060c89447c1 100644 --- a/cmd/syft/internal/clio_setup_config.go +++ b/cmd/syft/internal/clio_setup_config.go @@ -44,7 +44,7 @@ func AppClioSetupConfig(id clio.Identification, out io.Writer) *clio.SetupConfig redact.Set(state.RedactStore) log.Set(state.Logger) - stereoscope.SetLogger(state.Logger) + stereoscope.SetLogger(state.Logger.Nested("from", "stereoscope")) return nil }, ). diff --git a/go.mod b/go.mod index b8fc4bf9259..ab0fdff4685 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/charmbracelet/lipgloss v1.1.0 github.com/dave/jennifer v1.7.1 github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da + github.com/diskfs/go-diskfs v1.6.1-0.20250601133945-2af1c7ece24c github.com/distribution/reference v0.6.0 github.com/dustin/go-humanize v1.0.1 github.com/elliotchance/phpserialize v1.4.0 @@ -51,6 +52,8 @@ require ( github.com/google/licensecheck v0.3.1 github.com/google/uuid v1.6.0 github.com/gookit/color v1.5.4 + github.com/hashicorp/go-cleanhttp v0.5.2 + github.com/hashicorp/go-getter v1.7.8 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/hcl/v2 v2.23.0 github.com/iancoleman/strcase v0.3.0 @@ -93,6 +96,12 @@ require ( ) require ( + cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go/auth v0.9.9 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect + cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/iam v1.2.2 // indirect + cloud.google.com/go/storage v1.43.0 // indirect dario.cat/mergo v1.0.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect @@ -103,14 +112,17 @@ require ( github.com/ProtonMail/go-crypto v1.2.0 // indirect github.com/STARRY-S/zip v0.2.1 // indirect github.com/agext/levenshtein v1.2.1 // indirect; indirectt + github.com/anchore/go-lzo v0.1.0 // indirect github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aquasecurity/go-version v0.0.1 // indirect github.com/atotto/clipboard v0.1.4 // indirect + github.com/aws/aws-sdk-go v1.44.122 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/becheran/wildmatch-go v1.0.0 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/sevenzip v1.6.0 // indirect github.com/bodgit/windows v1.0.1 // indirect @@ -153,17 +165,23 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-restruct/restruct v1.2.0-alpha // indirect - github.com/goccy/go-yaml v1.18.0 // indirect + github.com/goccy/go-yaml v1.18.0 github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect + github.com/google/s2a-go v0.1.8 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect @@ -180,6 +198,7 @@ require ( github.com/minio/minlz v1.0.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -203,6 +222,7 @@ require ( github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/profile v1.7.0 // indirect + github.com/pkg/xattr v0.4.9 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect @@ -210,13 +230,14 @@ require ( github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/sorairolake/lzip-go v0.3.5 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/viper v1.20.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/sylabs/sif/v2 v2.21.1 // indirect github.com/sylabs/squashfs v1.0.6 // indirect @@ -235,6 +256,7 @@ require ( github.com/zclconf/go-cty v1.13.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel v1.36.0 // indirect go.opentelemetry.io/otel/metric v1.36.0 // indirect @@ -243,13 +265,17 @@ require ( go.uber.org/multierr v1.9.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/crypto v0.39.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.15.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.26.0 // indirect + golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.34.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + google.golang.org/api v0.203.0 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect google.golang.org/grpc v1.67.3 // indirect google.golang.org/protobuf v1.36.4 // indirect diff --git a/go.sum b/go.sum index 60775755f99..c01d4584491 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -15,6 +16,7 @@ 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.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -27,27 +29,590 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= +cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= +cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= +cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= +cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= +cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/auth v0.9.9 h1:BmtbpNQozo8ZwW2t7QJjnrQtdganSdmqeIBxHxNkEZQ= +cloud.google.com/go/auth v0.9.9/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= 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= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= +cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= +cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= +cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= +cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= 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.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= +cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= +cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= 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= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= +cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= 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= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= +cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= +cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= +cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= +cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA= @@ -64,6 +629,7 @@ github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6 github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= 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.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= @@ -90,6 +656,10 @@ github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -108,6 +678,8 @@ github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d h1:gT69osH9Asdp github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d/go.mod h1:PhSnuFYknwPZkOWKB1jXBNToChBA+l0FjwOxtViIc50= github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 h1:2SqmFgE7h+Ql4VyBzhjLkRF/3gDrcpUBj8LjvvO6OOM= github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722/go.mod h1:oFuE8YuTCM+spgMXhePGzk3asS94yO9biUfDzVTFqNw= +github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs= +github.com/anchore/go-lzo v0.1.0/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk= github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU= github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb/go.mod h1:DmTY2Mfcv38hsHbG78xMiTDdxFtkHpgYNVDPsF2TgHk= github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec h1:SjjPMOXTzpuU1ZME4XeoHyek+dry3/C7I8gzaCo02eg= @@ -125,11 +697,15 @@ github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1: github.com/anchore/stereoscope v0.1.5 h1:/wdjSoerdbYtXNvezReNrmzWeBOFJeHt7hLXz6Xg/WE= github.com/anchore/stereoscope v0.1.5/go.mod h1:S5xxMIo1BK+V+p+6SF/wzS4pZ2cTnpk6L+UJbf5IjsQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ= github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 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/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= @@ -147,6 +723,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo= +github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= @@ -156,6 +734,8 @@ github.com/becheran/wildmatch-go v1.0.0/go.mod h1:gbMvj0NtVdJ15Mg/mH9uxk2R1QCist 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/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef h1:TSFnfbbu2oAOuWbeDDTtwXWE6z+PmpgbSsMBeV7l0ww= github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef/go.mod h1:9iglf1GG4oNRJ39bZ5AZrjgAFD2RwQbXw6Qf7Cs47wo= @@ -169,16 +749,20 @@ github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc= github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= @@ -199,6 +783,7 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payR github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= @@ -217,12 +802,16 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= @@ -264,8 +853,12 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= +github.com/diskfs/go-diskfs v1.6.1-0.20250601133945-2af1c7ece24c h1:Vg+RNk+3Kwbe3wUYsgXsk43+7oyOJ5tRDzOrcpV40yQ= +github.com/diskfs/go-diskfs v1.6.1-0.20250601133945-2af1c7ece24c/go.mod h1:LhQyXqOugWFRahYUSw47NyZJPezFzB9UELwhpszLP/k= 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/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= +github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= github.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A= github.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= @@ -280,15 +873,19 @@ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= 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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 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/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/elliotchance/phpserialize v1.4.0 h1:cAp/9+KSnEbUC8oYCE32n2n84BeW8HOY3HMDI8hG2OY= github.com/elliotchance/phpserialize v1.4.0/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lcLUAeS/AnGZ2e49TZs= +github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY= +github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -300,8 +897,14 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/facebookincubator/flog v0.0.0-20190930132826-d2511d0ce33c/go.mod h1:QGzNH9ujQ2ZUr/CjDGZGWeDAVStrWNjHeEcjJL96Nuk= @@ -317,6 +920,8 @@ github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -337,6 +942,11 @@ github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5z 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-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= 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.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= @@ -350,6 +960,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= 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-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -357,6 +969,8 @@ 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-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc= github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -368,6 +982,7 @@ github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -376,7 +991,10 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gohugoio/hashstructure v0.5.0 h1:G2fjSBU36RdwEJBWJ+919ERvOVqAg9tfcYp47K9swqg= github.com/gohugoio/hashstructure v0.5.0/go.mod h1:Ser0TniXuu/eauYmrwM4o64EBvySxNzITEOLlm4igec= +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.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= 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= @@ -409,11 +1027,15 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -426,6 +1048,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -435,10 +1059,14 @@ github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs= github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -448,6 +1076,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -458,18 +1087,41 @@ github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0Z github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= 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= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= @@ -479,7 +1131,10 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-getter v1.7.8 h1:mshVHx1Fto0/MydBekWan5zUipGq7jO0novchgMmSiY= +github.com/hashicorp/go-getter v1.7.8/go.mod h1:2c6CboOEb9jG6YvmC9xdD+tyAFsrUaJPedwXDGr0TM4= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -491,10 +1146,14 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= @@ -532,6 +1191,10 @@ github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPIt github.com/jedib0t/go-pretty/v6 v6.6.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/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/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -540,16 +1203,23 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 h1:WdAeg/imY2JFPc/9CST4bZ80nNJbiBFCAdSZCSgrS5Y= github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953/go.mod h1:6o+UrvuZWc4UTyBhQf0LGjW9Ld7qJxLz/OqvSOWWlEc= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 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.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -557,6 +1227,8 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -572,6 +1244,9 @@ github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/z github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -596,9 +1271,11 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw= github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -607,6 +1284,8 @@ github.com/mholt/archives v0.1.2/go.mod h1:D7QzTHgw3ctfS6wgOO9dN+MFgdZpbksGCxprU github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +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/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ= github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= @@ -615,6 +1294,8 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= 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 v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -686,6 +1367,10 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= @@ -698,6 +1383,9 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= +github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -711,6 +1399,7 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -720,6 +1409,7 @@ github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+Pymzi github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -727,12 +1417,15 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c h1:8gOLsYwaY2JwlTMT4brS5/9XJdrdIbmk2obvQ748CC0= github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c/go.mod h1:kwM/7r/rVluTE8qJbHAffduuqmSv4knVQT2IajGvSiA= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= @@ -760,8 +1453,8 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+D github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0= +github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg= @@ -775,6 +1468,7 @@ github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XO github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -807,6 +1501,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= @@ -834,6 +1529,7 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vbatts/go-mtree v0.5.4 h1:OMAb8jaCyiFA7zXj0Zc/oARcxBDBoeu2LizjB8BVJl0= @@ -866,11 +1562,14 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0= github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1 h1:V+UsotZpAVvfj3X/LMoEytoLzSiP6Lg0F7wdVyu9gGg= github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1/go.mod h1:ly2RBz4mnz1yeuVbQA/VFwGjK3mnHGRj1JuoG336Bis= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= @@ -887,6 +1586,8 @@ 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.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= @@ -904,6 +1605,8 @@ go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRa go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -926,25 +1629,47 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -969,7 +1694,14 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1006,6 +1738,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= @@ -1013,9 +1746,32 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1035,6 +1791,21 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= 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= @@ -1046,7 +1817,15 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1094,13 +1873,16 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1114,21 +1896,57 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/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-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1140,16 +1958,31 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1163,6 +1996,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1192,25 +2026,45 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= 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= @@ -1243,6 +2097,35 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU= +google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1285,10 +2168,13 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= @@ -1313,6 +2199,76 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f h1:M65LEviCfuZTfrfzwwEoxVtgvfkFkBUbFnRbxCXuXhU= @@ -1346,6 +2302,20 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8= google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= @@ -1362,6 +2332,11 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -1370,6 +2345,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= @@ -1380,6 +2356,7 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -1394,31 +2371,67 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s= modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA= modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= +modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc= modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI= modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +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/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/bus/helpers.go b/internal/bus/helpers.go index 363d9754d53..f13364c2e0e 100644 --- a/internal/bus/helpers.go +++ b/internal/bus/helpers.go @@ -36,8 +36,8 @@ func Notify(message string) { }) } -func StartCatalogerTask(info monitor.GenericTask, size int64, initialStage string) *monitor.CatalogerTaskProgress { - t := &monitor.CatalogerTaskProgress{ +func StartCatalogerTask(info monitor.GenericTask, size int64, initialStage string) *monitor.TaskProgress { + t := &monitor.TaskProgress{ AtomicStage: progress.NewAtomicStage(initialStage), Manual: progress.NewManual(size), } @@ -50,3 +50,33 @@ func StartCatalogerTask(info monitor.GenericTask, size int64, initialStage strin return t } + +func StartPullSourceTask(info monitor.GenericTask, size int64, initialStage string) *monitor.TaskProgress { + t := &monitor.TaskProgress{ + AtomicStage: progress.NewAtomicStage(initialStage), + Manual: progress.NewManual(size), + } + + Publish(partybus.Event{ + Type: event.PullSourceStarted, + Source: info, + Value: progress.StagedProgressable(t), + }) + + return t +} + +func StartIndexingFiles(path string) *monitor.TaskProgress { + t := &monitor.TaskProgress{ + AtomicStage: progress.NewAtomicStage(""), + Manual: progress.NewManual(-1), + } + + Publish(partybus.Event{ + Type: event.FileIndexingStarted, + Source: path, + Value: progress.StagedProgressable(t), + }) + + return t +} diff --git a/internal/file/getter.go b/internal/file/getter.go new file mode 100644 index 00000000000..fec6ec731e2 --- /dev/null +++ b/internal/file/getter.go @@ -0,0 +1,154 @@ +package file + +import ( + "fmt" + "io" + "net/http" + + "github.com/hashicorp/go-getter" + "github.com/hashicorp/go-getter/helper/url" + "github.com/wagoodman/go-progress" + + "github.com/anchore/clio" + "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/syft/internal" +) + +var ( + archiveExtensions = getterDecompressorNames() + ErrNonArchiveSource = fmt.Errorf("non-archive sources are not supported for directory destinations") +) + +type Getter interface { + // GetFile downloads the give URL into the given path. The URL must reference a single file. + GetFile(dst, src string, monitor ...*progress.Manual) error + + // GetToDir downloads the resource found at the `src` URL into the given `dst` directory. + // The directory must already exist, and the remote resource MUST BE AN ARCHIVE (e.g. `.tar.gz`). + GetToDir(dst, src string, monitor ...*progress.Manual) error +} + +type HashiGoGetter struct { + httpGetter getter.HttpGetter +} + +// NewGetter creates and returns a new Getter. Providing an http.Client is optional. If one is provided, +// it will be used for all HTTP(S) getting; otherwise, go-getter's default getters will be used. +func NewGetter(id clio.Identification, httpClient *http.Client) *HashiGoGetter { + return &HashiGoGetter{ + httpGetter: getter.HttpGetter{ + Client: httpClient, + Header: http.Header{ + "User-Agent": []string{fmt.Sprintf("%v %v", id.Name, id.Version)}, + }, + }, + } +} + +func (g HashiGoGetter) GetFile(dst, src string, monitors ...*progress.Manual) error { + if len(monitors) > 1 { + return fmt.Errorf("multiple monitors provided, which is not allowed") + } + + return getterClient(dst, src, false, g.httpGetter, monitors).Get() +} + +func (g HashiGoGetter) GetToDir(dst, src string, monitors ...*progress.Manual) error { + // though there are multiple getters, only the http/https getter requires extra validation + if err := validateHTTPSource(src); err != nil { + return err + } + if len(monitors) > 1 { + return fmt.Errorf("multiple monitors provided, which is not allowed") + } + + return getterClient(dst, src, true, g.httpGetter, monitors).Get() +} + +func validateHTTPSource(src string) error { + // we are ignoring any sources that are not destined to use the http getter object + if !internal.HasAnyOfPrefixes(src, "http://", "https://") { + return nil + } + + u, err := url.Parse(src) + if err != nil { + return fmt.Errorf("bad URL provided %q: %w", src, err) + } + // only allow for sources with archive extensions + if !internal.HasAnyOfSuffixes(u.Path, archiveExtensions...) { + return ErrNonArchiveSource + } + return nil +} + +func getterClient(dst, src string, dir bool, httpGetter getter.HttpGetter, monitors []*progress.Manual) *getter.Client { + client := &getter.Client{ + Src: src, + Dst: dst, + Dir: dir, + Getters: map[string]getter.Getter{ + "http": &httpGetter, + "https": &httpGetter, + // note: these are the default getters from https://github.com/hashicorp/go-getter/blob/v1.5.9/get.go#L68-L74 + // it is possible that other implementations need to account for custom httpclient injection, however, + // that has not been accounted for at this time. + "file": new(getter.FileGetter), + "git": new(getter.GitGetter), + "gcs": new(getter.GCSGetter), + "hg": new(getter.HgGetter), + "s3": new(getter.S3Getter), + }, + Options: mapToGetterClientOptions(monitors), + } + + return client +} + +func withProgress(monitor *progress.Manual) func(client *getter.Client) error { + return getter.WithProgress( + &progressAdapter{monitor: monitor}, + ) +} + +func mapToGetterClientOptions(monitors []*progress.Manual) []getter.ClientOption { + var result []getter.ClientOption + + for _, monitor := range monitors { + result = append(result, withProgress(monitor)) + } + + // derived from https://github.com/hashicorp/go-getter/blob/v2.2.3/decompress.go#L23-L63 + fileSizeLimit := int64(5 * file.GB) + + dec := getter.LimitedDecompressors(0, fileSizeLimit) + + result = append(result, getter.WithDecompressors(dec)) + + return result +} + +type readCloser struct { + progress.Reader +} + +func (c *readCloser) Close() error { return nil } + +type progressAdapter struct { + monitor *progress.Manual +} + +func (a *progressAdapter) TrackProgress(_ string, currentSize, totalSize int64, stream io.ReadCloser) io.ReadCloser { + a.monitor.Set(currentSize) + a.monitor.SetTotal(totalSize) + return &readCloser{ + Reader: *progress.NewProxyReader(stream, a.monitor), + } +} + +func getterDecompressorNames() (names []string) { + for name := range getter.Decompressors { + names = append(names, name) + } + return names +} diff --git a/internal/file/getter_test.go b/internal/file/getter_test.go new file mode 100644 index 00000000000..a8f47ce816b --- /dev/null +++ b/internal/file/getter_test.go @@ -0,0 +1,268 @@ +package file + +import ( + "archive/tar" + "bytes" + "context" + "crypto/x509" + "fmt" + "net" + "net/http" + "net/http/httptest" + "net/url" + "path" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/anchore/clio" +) + +func TestGetter_GetFile(t *testing.T) { + testCases := []struct { + name string + prepareClient func(*http.Client) + assert assert.ErrorAssertionFunc + }{ + { + name: "client trusts server's CA", + assert: assert.NoError, + }, + { + name: "client doesn't trust server's CA", + prepareClient: removeTrustedCAs, + assert: assertUnknownAuthorityError, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + requestPath := "/foo" + + server := newTestServer(t, withResponseForPath(t, requestPath, testFileContent)) + t.Cleanup(server.Close) + + httpClient := getClient(t, server) + if tc.prepareClient != nil { + tc.prepareClient(httpClient) + } + + getter := NewGetter(testID, httpClient) + requestURL := createRequestURL(t, server, requestPath) + + tempDir := t.TempDir() + tempFile := path.Join(tempDir, "some-destination-file") + + err := getter.GetFile(tempFile, requestURL) + tc.assert(t, err) + }) + } +} + +func TestGetter_GetToDir_FilterNonArchivesWired(t *testing.T) { + testCases := []struct { + name string + source string + assert assert.ErrorAssertionFunc + }{ + { + name: "error out on non-archive sources", + source: "http://localhost/something.txt", + assert: assertErrNonArchiveSource, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + test.assert(t, NewGetter(testID, nil).GetToDir(t.TempDir(), test.source)) + }) + } +} + +func TestGetter_validateHttpSource(t *testing.T) { + testCases := []struct { + name string + source string + assert assert.ErrorAssertionFunc + }{ + { + name: "error out on non-archive sources", + source: "http://localhost/something.txt", + assert: assertErrNonArchiveSource, + }, + { + name: "filter out non-archive sources with get param", + source: "https://localhost/vulnerability-db_v3_2021-11-21T08:15:44Z.txt?checksum=sha256%3Ac402d01fa909a3fa85a5c6733ef27a3a51a9105b6c62b9152adbd24c08358911", + assert: assertErrNonArchiveSource, + }, + { + name: "ignore non http-https input", + source: "s3://bucket/something.txt", + assert: assert.NoError, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + test.assert(t, validateHTTPSource(test.source)) + }) + } +} + +func TestGetter_GetToDir_CertConcerns(t *testing.T) { + testCases := []struct { + name string + prepareClient func(*http.Client) + assert assert.ErrorAssertionFunc + }{ + + { + name: "client trusts server's CA", + assert: assert.NoError, + }, + { + name: "client doesn't trust server's CA", + prepareClient: removeTrustedCAs, + assert: assertUnknownAuthorityError, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + requestPath := "/foo.tar" + tarball := createTarball("foo", testFileContent) + + server := newTestServer(t, withResponseForPath(t, requestPath, tarball)) + t.Cleanup(server.Close) + + httpClient := getClient(t, server) + if tc.prepareClient != nil { + tc.prepareClient(httpClient) + } + + getter := NewGetter(testID, httpClient) + requestURL := createRequestURL(t, server, requestPath) + + tempDir := t.TempDir() + + err := getter.GetToDir(tempDir, requestURL) + tc.assert(t, err) + }) + } +} + +func assertUnknownAuthorityError(t assert.TestingT, err error, _ ...interface{}) bool { + return assert.ErrorAs(t, err, &x509.UnknownAuthorityError{}) +} + +func assertErrNonArchiveSource(t assert.TestingT, err error, _ ...interface{}) bool { + return assert.ErrorIs(t, err, ErrNonArchiveSource) +} + +func removeTrustedCAs(client *http.Client) { + client.Transport.(*http.Transport).TLSClientConfig.RootCAs = x509.NewCertPool() +} + +// createTarball makes a single-file tarball and returns it as a byte slice. +func createTarball(filename string, content []byte) []byte { + tarBuffer := new(bytes.Buffer) + tarWriter := tar.NewWriter(tarBuffer) + tarWriter.WriteHeader(&tar.Header{ + Name: filename, + Size: int64(len(content)), + Mode: 0600, + }) + tarWriter.Write(content) + tarWriter.Close() + + return tarBuffer.Bytes() +} + +type muxOption func(mux *http.ServeMux) + +func withResponseForPath(t *testing.T, path string, response []byte) muxOption { + t.Helper() + + return func(mux *http.ServeMux) { + mux.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) { + t.Logf("server handling request: %s %s", req.Method, req.URL) + + _, err := w.Write(response) + if err != nil { + t.Fatal(err) + } + }) + } +} + +var testID = clio.Identification{ + Name: "test-app", + Version: "v0.5.3", +} + +func newTestServer(t *testing.T, muxOptions ...muxOption) *httptest.Server { + t.Helper() + + mux := http.NewServeMux() + for _, option := range muxOptions { + option(mux) + } + + server := httptest.NewTLSServer(mux) + t.Logf("new TLS server listening at %s", getHost(t, server)) + + return server +} + +func createRequestURL(t *testing.T, server *httptest.Server, path string) string { + t.Helper() + + // TODO: Figure out how to get this value from the server without hardcoding it here + const testServerCertificateName = "example.com" + + serverURL, err := url.Parse(server.URL) + if err != nil { + t.Fatal(err) + } + + // Set URL hostname to value from TLS certificate + serverURL.Host = fmt.Sprintf("%s:%s", testServerCertificateName, serverURL.Port()) + + serverURL.Path = path + + return serverURL.String() +} + +// getClient returns an http.Client that can be used to contact the test TLS server. +func getClient(t *testing.T, server *httptest.Server) *http.Client { + t.Helper() + + httpClient := server.Client() + transport := httpClient.Transport.(*http.Transport) + + serverHost := getHost(t, server) + + transport.DialContext = func(_ context.Context, _, addr string) (net.Conn, error) { + t.Logf("client dialing %q for host %q", serverHost, addr) + + // Ensure the client dials our test server + return net.Dial("tcp", serverHost) + } + + return httpClient +} + +// getHost extracts the host value from a server URL string. +// e.g. given a server with URL "http://1.2.3.4:5000/foo", getHost returns "1.2.3.4:5000" +func getHost(t *testing.T, server *httptest.Server) string { + t.Helper() + + u, err := url.Parse(server.URL) + if err != nil { + t.Fatal(err) + } + + return u.Hostname() + ":" + u.Port() +} + +var testFileContent = []byte("This is the content of a test file!\n") diff --git a/internal/file/squashfs.go b/internal/file/squashfs.go new file mode 100644 index 00000000000..f7bf1717056 --- /dev/null +++ b/internal/file/squashfs.go @@ -0,0 +1,89 @@ +package file + +import ( + "errors" + "io/fs" + "os" + "path/filepath" + + "github.com/diskfs/go-diskfs/filesystem" +) + +type WalkDiskDirFunc func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error + +// WalkDiskDir walks the file tree within the go-diskfs filesystem at root, calling fn for each file or directory in the tree, including root. +// This is meant to mimic the behavior of fs.WalkDir in the standard library. +func WalkDiskDir(fsys filesystem.FileSystem, root string, fn WalkDiskDirFunc) error { + infos, err := fsys.ReadDir(root) + + if err != nil { + return err + } + + if len(infos) == 0 { + return nil + } + + for _, info := range infos { + p := filepath.Join(root, info.Name()) + err = walkDiskDir(fsys, p, info, fn) + if err != nil { + if errors.Is(err, fs.SkipDir) { + continue + } + if errors.Is(err, fs.SkipAll) { + return nil + } + return err + } + } + + return err +} + +func walkDiskDir(fsys filesystem.FileSystem, name string, d os.FileInfo, walkDirFn WalkDiskDirFunc) error { + if err := walkDirFn(fsys, name, d, nil); err != nil { + if errors.Is(err, fs.SkipDir) && (d == nil || d.IsDir()) { + return nil + } + return err + } + + isDir := d != nil && d.IsDir() + if d == nil { + _, err := fsys.ReadDir(name) + if err != nil { + return nil + } + isDir = true + } + + if !isDir { + return nil + } + + dirs, err := fsys.ReadDir(name) + if err != nil { + err = walkDirFn(fsys, name, d, err) + if err != nil { + if errors.Is(err, fs.SkipDir) { + return nil + } + return err + } + } + + for _, d1 := range dirs { + name1 := filepath.Join(name, d1.Name()) + if err := walkDiskDir(fsys, name1, d1, walkDirFn); err != nil { + if errors.Is(err, fs.SkipDir) { + break + } + if errors.Is(err, fs.SkipAll) { + return err + } + return err + } + } + return nil +} diff --git a/internal/file/squashfs_test.go b/internal/file/squashfs_test.go new file mode 100644 index 00000000000..3b086864be2 --- /dev/null +++ b/internal/file/squashfs_test.go @@ -0,0 +1,250 @@ +package file + +import ( + "io/fs" + "os" + "path/filepath" + "testing" + + "github.com/diskfs/go-diskfs/backend/file" + "github.com/diskfs/go-diskfs/filesystem" + "github.com/diskfs/go-diskfs/filesystem/squashfs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func createTestFS(t *testing.T) filesystem.FileSystem { + dir := t.TempDir() + + filename := "test.squashfs" + f, err := os.Create(filepath.Join(dir, filename)) + require.NoError(t, err) + + b := file.New(f, false) + fsys, err := squashfs.Create(b, 0, 0, 4096) + require.NoError(t, err) + + testFiles := []struct { + path string + content string + isDir bool + }{ + {"/file1.txt", "content of file1", false}, + {"/file2.txt", "content of file2", false}, + {"/dir1", "", true}, + {"/dir1/subfile1.txt", "content of subfile1", false}, + {"/dir1/subfile2.txt", "content of subfile2", false}, + {"/dir1/subdir1", "", true}, + {"/dir1/subdir1/deepfile.txt", "deep content", false}, + {"/dir2", "", true}, + {"/dir2/anotherfile.txt", "another content", false}, + {"/emptydir", "", true}, + } + + for _, tf := range testFiles { + if tf.isDir { + err := fsys.Mkdir(tf.path) + require.NoError(t, err) + } else { + f, err := fsys.OpenFile(tf.path, os.O_CREATE|os.O_RDWR) + require.NoError(t, err) + _, err = f.Write([]byte(tf.content)) + require.NoError(t, err) + f.Close() + } + } + + return fsys +} + +func TestWalkDiskDir_CompleteTraversal(t *testing.T) { + fsys := createTestFS(t) + + var visitedPaths []string + err := WalkDiskDir(fsys, "/", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error { + require.NoError(t, err) + visitedPaths = append(visitedPaths, path) + return nil + }) + + require.NoError(t, err) + + expectedPaths := []string{ + "/file1.txt", + "/file2.txt", + "/dir1", + "/dir1/subfile1.txt", + "/dir1/subfile2.txt", + "/dir1/subdir1", + "/dir1/subdir1/deepfile.txt", + "/dir2", + "/dir2/anotherfile.txt", + "/emptydir", + } + + assert.ElementsMatch(t, expectedPaths, visitedPaths) +} + +func TestWalkDiskDir_FileInfoCorrect(t *testing.T) { + fsys := createTestFS(t) + + var fileInfos []struct { + path string + isDir bool + name string + } + + err := WalkDiskDir(fsys, "/", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error { + require.NoError(t, err) + require.NotNil(t, d) + fileInfos = append(fileInfos, struct { + path string + isDir bool + name string + }{ + path: path, + isDir: d.IsDir(), + name: d.Name(), + }) + return nil + }) + + require.NoError(t, err) + + for _, fi := range fileInfos { + expectedName := filepath.Base(fi.path) + assert.Equal(t, expectedName, fi.name) + + if fi.path == "/dir1" || fi.path == "/dir2" || fi.path == "/emptydir" || fi.path == "/dir1/subdir1" { + assert.True(t, fi.isDir, "Expected %s to be directory", fi.path) + } else { + assert.False(t, fi.isDir, "Expected %s to be file", fi.path) + } + } +} + +func TestWalkDiskDir_SkipDir(t *testing.T) { + fsys := createTestFS(t) + + var visitedPaths []string + err := WalkDiskDir(fsys, "/", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error { + require.NoError(t, err) + visitedPaths = append(visitedPaths, path) + if path == "/dir1" { + return fs.SkipDir + } + return nil + }) + + require.NoError(t, err) + + assert.Contains(t, visitedPaths, "/dir1") + assert.NotContains(t, visitedPaths, "/dir1/subfile1.txt") + assert.NotContains(t, visitedPaths, "/dir1/subfile2.txt") + assert.NotContains(t, visitedPaths, "/dir1/subdir1") + assert.NotContains(t, visitedPaths, "/dir1/subdir1/deepfile.txt") + + assert.Contains(t, visitedPaths, "/dir2") + assert.Contains(t, visitedPaths, "/dir2/anotherfile.txt") +} + +func TestWalkDiskDir_SkipAll(t *testing.T) { + fsys := createTestFS(t) + + var visitedPaths []string + err := WalkDiskDir(fsys, "/", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error { + require.NoError(t, err) + visitedPaths = append(visitedPaths, path) + if path == "/dir1" { + return fs.SkipAll + } + return nil + }) + + require.NoError(t, err) + + assert.Contains(t, visitedPaths, "/dir1") + + assert.NotContains(t, visitedPaths, "/file1.txt") + assert.NotContains(t, visitedPaths, "/file2.txt") + assert.NotContains(t, visitedPaths, "/dir1/subfile1.txt") + assert.NotContains(t, visitedPaths, "/dir2") + assert.NotContains(t, visitedPaths, "/dir2/anotherfile.txt") + assert.NotContains(t, visitedPaths, "/emptydir") +} + +func TestWalkDiskDir_EmptyDirectory(t *testing.T) { + fs := createTestFS(t) + + var visitedPaths []string + err := WalkDiskDir(fs, "/emptydir", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error { + require.NoError(t, err) + visitedPaths = append(visitedPaths, path) + return nil + }) + + require.NoError(t, err) + assert.Empty(t, visitedPaths) +} + +func TestWalkDiskDir_NonexistentPath(t *testing.T) { + fs := createTestFS(t) + + err := WalkDiskDir(fs, "/nonexistent", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error { + return nil + }) + + assert.Error(t, err) +} + +func TestWalkDiskDir_WalkFunctionError(t *testing.T) { + fs := createTestFS(t) + + customErr := assert.AnError + err := WalkDiskDir(fs, "/", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error { + if path == "/file1.txt" { + return customErr + } + return nil + }) + + assert.Error(t, err) + assert.Equal(t, customErr, err) +} + +func TestWalkDiskDir_SubdirectoryTraversal(t *testing.T) { + fs := createTestFS(t) + + var visitedPaths []string + err := WalkDiskDir(fs, "/dir1", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error { + require.NoError(t, err) + visitedPaths = append(visitedPaths, path) + return nil + }) + + require.NoError(t, err) + + expectedPaths := []string{ + "/dir1/subfile1.txt", + "/dir1/subfile2.txt", + "/dir1/subdir1", + "/dir1/subdir1/deepfile.txt", + } + + assert.ElementsMatch(t, expectedPaths, visitedPaths) +} + +func TestWalkDiskDir_SingleFile(t *testing.T) { + fs := createTestFS(t) + + var visitedPaths []string + err := WalkDiskDir(fs, "/file1.txt", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error { + require.NoError(t, err) + visitedPaths = append(visitedPaths, path) + return nil + }) + + // we are providing a file path, not a directory + require.Error(t, err) + assert.Empty(t, visitedPaths) +} diff --git a/internal/string_helpers.go b/internal/string_helpers.go index f43d5918d30..8977ceb5bb1 100644 --- a/internal/string_helpers.go +++ b/internal/string_helpers.go @@ -13,6 +13,17 @@ func HasAnyOfPrefixes(input string, prefixes ...string) bool { return false } +// HasAnyOfSuffixes returns an indication if the given string has any of the given suffixes. +func HasAnyOfSuffixes(input string, suffixes ...string) bool { + for _, suffix := range suffixes { + if strings.HasSuffix(input, suffix) { + return true + } + } + + return false +} + func TruncateMiddleEllipsis(input string, maxLen int) string { if len(input) <= maxLen { return input diff --git a/internal/string_helpers_test.go b/internal/string_helpers_test.go index 401b28dc30f..6833b04eb3d 100644 --- a/internal/string_helpers_test.go +++ b/internal/string_helpers_test.go @@ -7,6 +7,63 @@ import ( "github.com/stretchr/testify/assert" ) +func TestHasAnyOfSuffixes(t *testing.T) { + tests := []struct { + name string + input string + suffixes []string + expected bool + }{ + { + name: "go case", + input: "this has something", + suffixes: []string{ + "has something", + "has NOT something", + }, + expected: true, + }, + { + name: "no match", + input: "this has something", + suffixes: []string{ + "has NOT something", + }, + expected: false, + }, + { + name: "empty", + input: "this has something", + suffixes: []string{}, + expected: false, + }, + { + name: "positive match last", + input: "this has something", + suffixes: []string{ + "that does not have", + "something", + }, + expected: true, + }, + { + name: "empty input", + input: "", + suffixes: []string{ + "that does not have", + "this has", + }, + expected: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.expected, HasAnyOfSuffixes(test.input, test.suffixes...)) + }) + } +} + func TestHasAnyOfPrefixes(t *testing.T) { tests := []struct { name string diff --git a/internal/task/executor.go b/internal/task/executor.go index f078ed689e7..6cee1242fea 100644 --- a/internal/task/executor.go +++ b/internal/task/executor.go @@ -15,7 +15,7 @@ import ( "github.com/anchore/syft/syft/sbom" ) -func RunTask(ctx context.Context, tsk Task, resolver file.Resolver, s sbomsync.Builder, prog *monitor.CatalogerTaskProgress) error { +func RunTask(ctx context.Context, tsk Task, resolver file.Resolver, s sbomsync.Builder, prog *monitor.TaskProgress) error { err := runTaskSafely(ctx, tsk, resolver, s) unknowns, remainingErrors := unknown.ExtractCoordinateErrors(err) if len(unknowns) > 0 { diff --git a/internal/task/executor_test.go b/internal/task/executor_test.go index c739d7e6b40..527066cd0ef 100644 --- a/internal/task/executor_test.go +++ b/internal/task/executor_test.go @@ -17,7 +17,7 @@ func Test_TaskExecutor_PanicHandling(t *testing.T) { panic("something bad happened") }) - err := RunTask(context.Background(), tsk, nil, nil, &monitor.CatalogerTaskProgress{ + err := RunTask(context.Background(), tsk, nil, nil, &monitor.TaskProgress{ Manual: progress.NewManual(-1), }) diff --git a/syft/create_sbom.go b/syft/create_sbom.go index 32c34c00ba3..126015f2b32 100644 --- a/syft/create_sbom.go +++ b/syft/create_sbom.go @@ -144,14 +144,14 @@ func setContextExecutors(ctx context.Context, cfg *CreateSBOMConfig) context.Con return ctx } -func monitorPackageCount(prog *monitor.CatalogerTaskProgress) func(s *sbom.SBOM) { +func monitorPackageCount(prog *monitor.TaskProgress) func(s *sbom.SBOM) { return func(s *sbom.SBOM) { count := humanize.Comma(int64(s.Artifacts.Packages.PackageCount())) prog.AtomicStage.Set(fmt.Sprintf("%s packages", count)) } } -func monitorPackageCatalogingTask() *monitor.CatalogerTaskProgress { +func monitorPackageCatalogingTask() *monitor.TaskProgress { info := monitor.GenericTask{ Title: monitor.Title{ Default: "Packages", @@ -164,7 +164,7 @@ func monitorPackageCatalogingTask() *monitor.CatalogerTaskProgress { return bus.StartCatalogerTask(info, -1, "") } -func monitorCatalogingTask(srcID artifact.ID, tasks [][]task.Task) *monitor.CatalogerTaskProgress { +func monitorCatalogingTask(srcID artifact.ID, tasks [][]task.Task) *monitor.TaskProgress { info := monitor.GenericTask{ Title: monitor.Title{ Default: "Catalog contents", diff --git a/syft/create_sbom_config.go b/syft/create_sbom_config.go index 80d96843375..4eb323f548a 100644 --- a/syft/create_sbom_config.go +++ b/syft/create_sbom_config.go @@ -464,6 +464,8 @@ func findDefaultTags(src source.Description) ([]string, error) { return []string{pkgcataloging.ImageTag, filecataloging.FileTag}, nil case source.FileMetadata, source.DirectoryMetadata: return []string{pkgcataloging.DirectoryTag, filecataloging.FileTag}, nil + case source.SnapMetadata: + return []string{pkgcataloging.InstalledTag, filecataloging.FileTag}, nil default: return nil, fmt.Errorf("unable to determine default cataloger tag for source type=%T", m) } diff --git a/syft/event/event.go b/syft/event/event.go index 6c760af66aa..930bd4e07d7 100644 --- a/syft/event/event.go +++ b/syft/event/event.go @@ -23,6 +23,10 @@ const ( // CatalogerTaskStarted is a partybus event that occurs when starting a task within a cataloger CatalogerTaskStarted partybus.EventType = typePrefix + "-cataloger-task-started" + // PullSourceStarted is a partybus event that occurs when starting to pull a source (does not overlap with stereoscope image pull events, + // this covers any additional sources such as snap and git repos). + PullSourceStarted partybus.EventType = typePrefix + "-pull-source-started" + // Events exclusively for the CLI // CLIAppUpdateAvailable is a partybus event that occurs when an application update is available diff --git a/syft/event/monitor/cataloger_task_progress.go b/syft/event/monitor/cataloger_task_progress.go deleted file mode 100644 index 49f438fd038..00000000000 --- a/syft/event/monitor/cataloger_task_progress.go +++ /dev/null @@ -1,15 +0,0 @@ -package monitor - -import ( - "github.com/wagoodman/go-progress" -) - -const ( - TopLevelCatalogingTaskID = "cataloging" - PackageCatalogingTaskID = "package-cataloging" -) - -type CatalogerTaskProgress struct { - *progress.AtomicStage - *progress.Manual -} diff --git a/syft/event/monitor/generic_task.go b/syft/event/monitor/generic_task.go index b4ed48d2d7d..1dc46216657 100644 --- a/syft/event/monitor/generic_task.go +++ b/syft/event/monitor/generic_task.go @@ -6,6 +6,11 @@ import ( "github.com/wagoodman/go-progress" ) +const ( + TopLevelCatalogingTaskID = "cataloging" + PackageCatalogingTaskID = "package-cataloging" +) + type ShellProgress struct { io.Reader progress.Progressable @@ -34,3 +39,8 @@ type GenericTask struct { ParentID string Context string } + +type TaskProgress struct { + *progress.AtomicStage + *progress.Manual +} diff --git a/syft/event/parsers/parsers.go b/syft/event/parsers/parsers.go index 52a11d30922..f3034066780 100644 --- a/syft/event/parsers/parsers.go +++ b/syft/event/parsers/parsers.go @@ -77,6 +77,26 @@ func ParseCatalogerTaskStarted(e partybus.Event) (progress.StagedProgressable, * return mon, &source, nil } +func ParsePullSourceStarted(e partybus.Event) (progress.StagedProgressable, *monitor.GenericTask, error) { + if err := checkEventType(e.Type, event.PullSourceStarted); err != nil { + return nil, nil, err + } + + var mon progress.StagedProgressable + + source, ok := e.Source.(monitor.GenericTask) + if !ok { + return nil, nil, newPayloadErr(e.Type, "Source", e.Source) + } + + mon, ok = e.Value.(progress.StagedProgressable) + if !ok { + mon = nil + } + + return mon, &source, nil +} + func ParseAttestationStartedEvent(e partybus.Event) (io.Reader, progress.Progressable, *monitor.GenericTask, error) { if err := checkEventType(e.Type, event.AttestationStarted); err != nil { return nil, nil, nil, err diff --git a/syft/file/cataloger/executable/cataloger.go b/syft/file/cataloger/executable/cataloger.go index 05d29b009e0..f6cdb5e6598 100644 --- a/syft/file/cataloger/executable/cataloger.go +++ b/syft/file/cataloger/executable/cataloger.go @@ -106,7 +106,7 @@ func processExecutableLocation(loc file.Location, resolver file.Resolver) (*file return processExecutable(loc, uReader) } -func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress { +func catalogingProgress(locations int64) *monitor.TaskProgress { info := monitor.GenericTask{ Title: monitor.Title{ Default: "Executables", diff --git a/syft/file/cataloger/filecontent/cataloger.go b/syft/file/cataloger/filecontent/cataloger.go index b88257308e0..5e57a94e874 100644 --- a/syft/file/cataloger/filecontent/cataloger.go +++ b/syft/file/cataloger/filecontent/cataloger.go @@ -113,7 +113,7 @@ func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Locati return buf.String(), nil } -func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress { +func catalogingProgress(locations int64) *monitor.TaskProgress { info := monitor.GenericTask{ Title: monitor.Title{ Default: "File contents", diff --git a/syft/file/cataloger/filedigest/cataloger.go b/syft/file/cataloger/filedigest/cataloger.go index 15a770ef154..9ca6e78674b 100644 --- a/syft/file/cataloger/filedigest/cataloger.go +++ b/syft/file/cataloger/filedigest/cataloger.go @@ -112,7 +112,7 @@ func (i *Cataloger) catalogLocation(ctx context.Context, resolver file.Resolver, return digests, nil } -func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress { +func catalogingProgress(locations int64) *monitor.TaskProgress { info := monitor.GenericTask{ Title: monitor.Title{ Default: "File digests", diff --git a/syft/file/cataloger/filemetadata/cataloger.go b/syft/file/cataloger/filemetadata/cataloger.go index b158e751ddc..f22383a6e5a 100644 --- a/syft/file/cataloger/filemetadata/cataloger.go +++ b/syft/file/cataloger/filemetadata/cataloger.go @@ -76,7 +76,7 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin return results, errs } -func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress { +func catalogingProgress(locations int64) *monitor.TaskProgress { info := monitor.GenericTask{ Title: monitor.Title{ Default: "File metadata", diff --git a/syft/format/common/spdxhelpers/to_format_model.go b/syft/format/common/spdxhelpers/to_format_model.go index 48d2c3ace35..4e214edc179 100644 --- a/syft/format/common/spdxhelpers/to_format_model.go +++ b/syft/format/common/spdxhelpers/to_format_model.go @@ -37,6 +37,7 @@ const ( prefixImage = "Image" prefixDirectory = "Directory" prefixFile = "File" + prefixSnap = "Snap" prefixUnknown = "Unknown" ) @@ -228,6 +229,18 @@ func toRootPackage(s source.Description) *spdx.Package { Value: d.Value, }) } + + case source.SnapMetadata: + prefix = prefixSnap + purpose = spdxPrimaryPurposeContainer + + for _, d := range m.Digests { + checksums = append(checksums, spdx.Checksum{ + Algorithm: toChecksumAlgorithm(d.Algorithm), + Value: d.Value, + }) + } + default: prefix = prefixUnknown purpose = spdxPrimaryPurposeOther diff --git a/syft/format/common/spdxhelpers/to_format_model_test.go b/syft/format/common/spdxhelpers/to_format_model_test.go index 4a3182f067a..b6bf7fff709 100644 --- a/syft/format/common/spdxhelpers/to_format_model_test.go +++ b/syft/format/common/spdxhelpers/to_format_model_test.go @@ -237,6 +237,82 @@ func Test_toFormatModel(t *testing.T) { }, }, }, + { + name: "snap", + in: sbom.SBOM{ + Source: source.Description{ + Name: "etcd", + Version: "3.4.36", + Metadata: source.SnapMetadata{ + Summary: "Distributed reliable key-value store", + Base: "core18", + Grade: "stable", + Confinement: "strict", + Architectures: []string{ + "amd64", + }, + Digests: []file.Digest{ + { + Algorithm: "sha256", + Value: "d34db33f", + }, + }, + }, + }, + Artifacts: sbom.Artifacts{ + Packages: pkg.NewCollection(pkg.Package{ + Name: "pkg-1", + Version: "version-1", + }), + }, + }, + expected: &spdx.Document{ + SPDXIdentifier: "DOCUMENT", + SPDXVersion: spdx.Version, + DataLicense: spdx.DataLicense, + DocumentName: "etcd", + Packages: []*spdx.Package{ + { + PackageSPDXIdentifier: "Package-pkg-1-pkg-1", + PackageName: "pkg-1", + PackageVersion: "version-1", + PackageSupplier: &spdx.Supplier{ + Supplier: "NOASSERTION", + }, + }, + { + PackageSPDXIdentifier: "DocumentRoot-Snap-etcd", + PackageName: "etcd", + PackageVersion: "3.4.36", + PrimaryPackagePurpose: "CONTAINER", + PackageChecksums: []spdx.Checksum{{Algorithm: "SHA256", Value: "d34db33f"}}, + PackageSupplier: &spdx.Supplier{ + Supplier: "NOASSERTION", + }, + }, + }, + Relationships: []*spdx.Relationship{ + { + RefA: spdx.DocElementID{ + ElementRefID: "DocumentRoot-Snap-etcd", + }, + RefB: spdx.DocElementID{ + ElementRefID: "Package-pkg-1-pkg-1", + }, + Relationship: spdx.RelationshipContains, + }, + { + RefA: spdx.DocElementID{ + ElementRefID: "DOCUMENT", + }, + RefB: spdx.DocElementID{ + ElementRefID: "DocumentRoot-Snap-etcd", + }, + Relationship: spdx.RelationshipDescribes, + }, + }, + }, + }, } for _, test := range tests { diff --git a/syft/format/github/internal/model/model.go b/syft/format/github/internal/model/model.go index 9421763147d..7d67e87630c 100644 --- a/syft/format/github/internal/model/model.go +++ b/syft/format/github/internal/model/model.go @@ -130,6 +130,11 @@ func toPath(s source.Description, p pkg.Package) string { return fmt.Sprintf("%s/%s", path, packagePath) } return packagePath + case source.SnapMetadata: + if inputPath != "" { + return fmt.Sprintf("%s:/%s", inputPath, packagePath) + } + return packagePath } } return inputPath diff --git a/syft/format/github/internal/model/model_test.go b/syft/format/github/internal/model/model_test.go index f6fe6701deb..4d0c2dcce2f 100644 --- a/syft/format/github/internal/model/model_test.go +++ b/syft/format/github/internal/model/model_test.go @@ -173,6 +173,11 @@ func Test_toGithubModel(t *testing.T) { metadata: source.FileMetadata{Path: "./archive.tar.gz"}, testPath: "archive.tar.gz:/etc", }, + { + name: "snap", + metadata: source.SnapMetadata{}, + testPath: "name:/etc", + }, } for _, test := range tests { @@ -180,6 +185,7 @@ func Test_toGithubModel(t *testing.T) { s := sbomFixture() if test.metadata != nil { + s.Source.Name = "name" s.Source.Metadata = test.metadata } actual := ToGithubModel(&s) diff --git a/syft/format/internal/spdxutil/helpers/document_name_test.go b/syft/format/internal/spdxutil/helpers/document_name_test.go index 1e1c9e72ec3..1d73ecf383a 100644 --- a/syft/format/internal/spdxutil/helpers/document_name_test.go +++ b/syft/format/internal/spdxutil/helpers/document_name_test.go @@ -45,6 +45,15 @@ func Test_DocumentName(t *testing.T) { }, expected: "some/path/to/place", }, + { + name: "snap", + srcMetadata: source.Description{ + Name: "some/name", + // there is nothing in the snap metadata that indicates a name + Metadata: source.SnapMetadata{}, + }, + expected: "some/name", + }, { name: "named", srcMetadata: source.Description{ diff --git a/syft/format/internal/spdxutil/helpers/document_namespace.go b/syft/format/internal/spdxutil/helpers/document_namespace.go index 766b22071b3..7215efebd39 100644 --- a/syft/format/internal/spdxutil/helpers/document_namespace.go +++ b/syft/format/internal/spdxutil/helpers/document_namespace.go @@ -16,6 +16,7 @@ const ( InputImage = "image" InputDirectory = "dir" InputFile = "file" + InputSnap = "snap" ) func DocumentNameAndNamespace(src source.Description, desc sbom.Descriptor) (string, string) { @@ -33,6 +34,8 @@ func DocumentNamespace(name string, src source.Description, desc sbom.Descriptor input = InputDirectory case source.FileMetadata: input = InputFile + case source.SnapMetadata: + input = InputSnap } uniqueID := uuid.Must(uuid.NewRandom()) diff --git a/syft/format/internal/spdxutil/helpers/document_namespace_test.go b/syft/format/internal/spdxutil/helpers/document_namespace_test.go index 1c40b27693b..3deeb807a33 100644 --- a/syft/format/internal/spdxutil/helpers/document_namespace_test.go +++ b/syft/format/internal/spdxutil/helpers/document_namespace_test.go @@ -12,7 +12,7 @@ import ( "github.com/anchore/syft/syft/source" ) -func Test_documentNamespace(t *testing.T) { +func Test_DocumentNamespace(t *testing.T) { tracker := sourcemetadata.NewCompletionTester(t) tests := []struct { @@ -53,6 +53,14 @@ func Test_documentNamespace(t *testing.T) { }, expected: "https://anchore.com/syft/file/my-name-", }, + { + name: "snap", + inputName: "my-name", + src: source.Description{ + Metadata: source.SnapMetadata{}, + }, + expected: "https://anchore.com/syft/snap/my-name-", + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/syft/format/syftjson/model/source_test.go b/syft/format/syftjson/model/source_test.go index ce8b5bf0464..bd2b7034fe2 100644 --- a/syft/format/syftjson/model/source_test.go +++ b/syft/format/syftjson/model/source_test.go @@ -145,6 +145,51 @@ func TestSource_UnmarshalJSON(t *testing.T) { }, }, }, + { + name: "snap", + input: []byte(`{ + "id": "foobar", + "name": "etcd", + "version": "3.4.36", + "type": "snap", + "metadata": { + "summary": "Distributed reliable key-value store", + "base": "core18", + "grade": "stable", + "confinement": "strict", + "architectures": [ + "amd64" + ], + "digests": [ + { + "algorithm": "sha256", + "value": "6700d789d2c38b0f7513058ddcea8f9a275e2206b4621a772eb065e12069956e" + } + ] + } +}`), + expected: &Source{ + ID: "foobar", + Name: "etcd", + Version: "3.4.36", + Type: "snap", + Metadata: source.SnapMetadata{ + Summary: "Distributed reliable key-value store", + Base: "core18", + Grade: "stable", + Confinement: "strict", + Architectures: []string{ + "amd64", + }, + Digests: []file.Digest{ + { + Algorithm: "sha256", + Value: "6700d789d2c38b0f7513058ddcea8f9a275e2206b4621a772eb065e12069956e", + }, + }, + }, + }, + }, { name: "unknown source type", input: []byte(`{ diff --git a/syft/format/syftjson/to_format_model_test.go b/syft/format/syftjson/to_format_model_test.go index 5450439ca5f..9b40e36c688 100644 --- a/syft/format/syftjson/to_format_model_test.go +++ b/syft/format/syftjson/to_format_model_test.go @@ -127,6 +127,36 @@ func Test_toSourceModel(t *testing.T) { }, }, }, + { + name: "snap", + src: source.Description{ + ID: "test-id", + Name: "some-name", + Version: "some-version", + Metadata: source.SnapMetadata{ + Summary: "some summary", + Base: "some/base", + Grade: "some grade", + Confinement: "some confinement", + Architectures: []string{"x86_64", "arm64"}, + Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}}, + }, + }, + expected: model.Source{ + ID: "test-id", + Name: "some-name", + Version: "some-version", + Type: "snap", + Metadata: source.SnapMetadata{ + Summary: "some summary", + Base: "some/base", + Grade: "some grade", + Confinement: "some confinement", + Architectures: []string{"x86_64", "arm64"}, + Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}}, + }, + }, + }, // below are regression tests for when the name/version are not provided // historically we've hoisted up the name/version from the metadata, now it is a simple pass-through { diff --git a/syft/format/syftjson/to_syft_model_test.go b/syft/format/syftjson/to_syft_model_test.go index 1c84fba4a94..3aed50c0db1 100644 --- a/syft/format/syftjson/to_syft_model_test.go +++ b/syft/format/syftjson/to_syft_model_test.go @@ -100,6 +100,36 @@ func Test_toSyftSourceData(t *testing.T) { }, }, }, + { + name: "snap", + src: model.Source{ + ID: "the-id", + Name: "some-name", + Version: "some-version", + Type: "snap", + Metadata: source.SnapMetadata{ + Summary: "something!", + Base: "base!", + Grade: "grade!", + Confinement: "confined!", + Architectures: []string{"arch!"}, + Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest!"}}, + }, + }, + expected: &source.Description{ + ID: "the-id", + Name: "some-name", + Version: "some-version", + Metadata: source.SnapMetadata{ + Summary: "something!", + Base: "base!", + Grade: "grade!", + Confinement: "confined!", + Architectures: []string{"arch!"}, + Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest!"}}, + }, + }, + }, // below are regression tests for when the name/version are not provided // historically we've hoisted up the name/version from the metadata, now it is a simple pass-through { diff --git a/syft/get_source.go b/syft/get_source.go index 8bd7e8801a5..e70f4cfe5a3 100644 --- a/syft/get_source.go +++ b/syft/get_source.go @@ -34,26 +34,44 @@ func GetSource(ctx context.Context, userInput string, cfg *GetSourceConfig) (sou errs = append(errs, fmt.Errorf("%s: %w", p.Name(), err)) } } + if err := validateSourcePlatform(src, cfg); err != nil { + return nil, err + } if src != nil { - // if we have a non-image type and platform is specified, it's an error - if cfg.SourceProviderConfig.Platform != nil { - meta := src.Describe().Metadata - switch meta.(type) { - case *source.ImageMetadata, source.ImageMetadata: - default: - return src, fmt.Errorf("platform specified with non-image source") - } - } return src, nil } } + if len(errs) == 0 { + return nil, fmt.Errorf("no source providers were able to resolve the input %q", userInput) + } + if len(fileNotFoundProviders) > 0 { errs = append(errs, fmt.Errorf("additionally, the following providers failed with %w: %s", os.ErrNotExist, strings.Join(fileNotFoundProviders, ", "))) } + return nil, sourceError(userInput, errs...) } +func validateSourcePlatform(src source.Source, cfg *GetSourceConfig) error { + if src == nil { + return nil + } + if cfg == nil || cfg.SourceProviderConfig == nil || cfg.SourceProviderConfig.Platform == nil { + return nil + } + + meta := src.Describe().Metadata + switch meta.(type) { + case *source.ImageMetadata, source.ImageMetadata: + return nil + case *source.SnapMetadata, source.SnapMetadata: + return nil + default: + return fmt.Errorf("platform is not supported for this source type") + } +} + func sourceError(userInput string, errs ...error) error { switch len(errs) { case 0: diff --git a/syft/get_source_test.go b/syft/get_source_test.go new file mode 100644 index 00000000000..dcef0edcc66 --- /dev/null +++ b/syft/get_source_test.go @@ -0,0 +1,208 @@ +package syft + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/anchore/stereoscope/pkg/image" + "github.com/anchore/syft/syft/internal/sourcemetadata" + "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/source/sourceproviders" +) + +type mockSource struct { + source.Source + desc source.Description +} + +func (s mockSource) Describe() source.Description { + return s.desc +} + +func TestValidateSourcePlatform_NilSource(t *testing.T) { + cfg := &GetSourceConfig{ + SourceProviderConfig: &sourceproviders.Config{ + Platform: &image.Platform{ + Architecture: "amd64", + OS: "linux", + }, + }, + } + + err := validateSourcePlatform(nil, cfg) + if err != nil { + t.Errorf("Expected no error for nil source, got: %v", err) + } +} + +func TestValidateSourcePlatform_NilPlatformConfig(t *testing.T) { + tests := []struct { + name string + cfg *GetSourceConfig + }{ + { + name: "nil config", + cfg: nil, + }, + { + name: "nil SourceProviderConfig", + cfg: &GetSourceConfig{ + SourceProviderConfig: nil, + }, + }, + { + name: "nil Platform", + cfg: &GetSourceConfig{ + SourceProviderConfig: &sourceproviders.Config{ + Platform: nil, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + src := mockSource{ + desc: source.Description{ + Metadata: &source.ImageMetadata{}, + }, + } + + err := validateSourcePlatform(src, tt.cfg) + if err != nil { + t.Errorf("Expected no error for nil platform, got: %v", err) + } + }) + } +} + +func TestValidateSourcePlatform_SupportedMetadataTypes(t *testing.T) { + tracker := sourcemetadata.NewCompletionTester(t) + cfg := &GetSourceConfig{ + SourceProviderConfig: &sourceproviders.Config{ + Platform: &image.Platform{ + Architecture: "amd64", + OS: "linux", + }, + }, + } + + tests := []struct { + name string + metadata any + wantErr require.ErrorAssertionFunc + }{ + { + name: "image", + metadata: source.ImageMetadata{}, + }, + { + name: "snap", + metadata: source.SnapMetadata{}, + }, + { + name: "dir", + metadata: source.DirectoryMetadata{}, + wantErr: require.Error, + }, + { + name: "file", + metadata: source.FileMetadata{}, + wantErr: require.Error, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + tracker.Tested(t, tt.metadata) + + src := mockSource{ + desc: source.Description{ + Metadata: tt.metadata, + }, + } + + err := validateSourcePlatform(src, cfg) + tt.wantErr(t, err, "Expected no error for %s, got: %v", tt.name, err) + }) + } +} + +func TestValidateSourcePlatform_UnsupportedMetadataTypes(t *testing.T) { + cfg := &GetSourceConfig{ + SourceProviderConfig: &sourceproviders.Config{ + Platform: &image.Platform{ + Architecture: "amd64", + OS: "linux", + }, + }, + } + + tests := []struct { + name string + metadata interface{} + }{ + { + name: "string metadata", + metadata: "unsupported", + }, + { + name: "int metadata", + metadata: 42, + }, + { + name: "nil metadata", + metadata: nil, + }, + { + name: "custom struct", + metadata: struct{ Name string }{Name: "test"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + src := mockSource{ + desc: source.Description{ + Metadata: tt.metadata, + }, + } + + err := validateSourcePlatform(src, cfg) + if err == nil { + t.Errorf("Expected error for %s, got nil", tt.name) + } + + expectedMsg := "platform is not supported for this source type" + if err.Error() != expectedMsg { + t.Errorf("Expected error message %q, got %q", expectedMsg, err.Error()) + } + }) + } +} + +func TestValidateSourcePlatform_ValidCombination(t *testing.T) { + cfg := &GetSourceConfig{ + SourceProviderConfig: &sourceproviders.Config{ + Platform: &image.Platform{ + Architecture: "amd64", + OS: "linux", + }, + }, + } + + src := mockSource{ + desc: source.Description{ + Metadata: &source.ImageMetadata{}, + }, + } + + err := validateSourcePlatform(src, cfg) + if err != nil { + t.Errorf("Expected no error for valid combination, got: %v", err) + } +} diff --git a/syft/internal/fileresolver/directory.go b/syft/internal/fileresolver/directory.go index 0f2df32b3f2..55882b802d8 100644 --- a/syft/internal/fileresolver/directory.go +++ b/syft/internal/fileresolver/directory.go @@ -14,7 +14,7 @@ var _ file.Resolver = (*Directory)(nil) // Directory implements path and content access for the directory data source. type Directory struct { - filetreeResolver + FiletreeResolver path string indexer *directoryIndexer } @@ -39,10 +39,11 @@ func newFromDirectoryWithoutIndex(root string, base string, pathFilters ...PathI return &Directory{ path: cleanRoot, - filetreeResolver: filetreeResolver{ - chroot: *chroot, - tree: filetree.New(), - index: filetree.NewIndex(), + FiletreeResolver: FiletreeResolver{ + Chroot: *chroot, + Tree: filetree.New(), + Index: filetree.NewIndex(), + Opener: nativeOSFileOpener, }, indexer: newDirectoryIndexer(cleanRoot, cleanBase, pathFilters...), }, nil @@ -57,9 +58,9 @@ func (r *Directory) buildIndex() error { return err } - r.tree = tree - r.index = index - r.searchContext = filetree.NewSearchContext(tree, index) + r.Tree = tree + r.Index = index + r.SearchContext = filetree.NewSearchContext(tree, index) return nil } diff --git a/syft/internal/fileresolver/directory_indexer.go b/syft/internal/fileresolver/directory_indexer.go index 0e350965576..1facdc44b3c 100644 --- a/syft/internal/fileresolver/directory_indexer.go +++ b/syft/internal/fileresolver/directory_indexer.go @@ -9,14 +9,12 @@ import ( "path/filepath" "strings" - "github.com/wagoodman/go-partybus" "github.com/wagoodman/go-progress" "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree" "github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/log" - "github.com/anchore/syft/syft/event" "github.com/anchore/syft/syft/internal/windows" ) @@ -64,14 +62,14 @@ func (r *directoryIndexer) build() (filetree.Reader, filetree.IndexReader, error return r.tree, r.index, indexAllRoots(r.path, r.indexTree) } -func indexAllRoots(root string, indexer func(string, *progress.Stage) ([]string, error)) error { +func indexAllRoots(root string, indexer func(string, *progress.AtomicStage) ([]string, error)) error { // why account for multiple roots? To cover cases when there is a symlink that references above the root path, // in which case we need to additionally index where the link resolves to. it's for this reason why the filetree // must be relative to the root of the filesystem (and not just relative to the given path). pathsToIndex := []string{root} fullPathsMap := map[string]struct{}{} - stager, prog := indexingProgress(root) + prog := bus.StartIndexingFiles(root) defer prog.SetCompleted() loop: for { @@ -85,7 +83,7 @@ loop: currentPath, pathsToIndex = pathsToIndex[0], pathsToIndex[1:] } - additionalRoots, err := indexer(currentPath, stager) + additionalRoots, err := indexer(currentPath, prog.AtomicStage) if err != nil { return fmt.Errorf("unable to index filesystem path=%q: %w", currentPath, err) } @@ -101,7 +99,7 @@ loop: return nil } -func (r *directoryIndexer) indexTree(root string, stager *progress.Stage) ([]string, error) { +func (r *directoryIndexer) indexTree(root string, stager *progress.AtomicStage) ([]string, error) { log.WithFields("path", root).Trace("indexing filetree") var roots []string @@ -144,7 +142,7 @@ func (r *directoryIndexer) indexTree(root string, stager *progress.Stage) ([]str err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error { - stager.Current = path + stager.Set(path) newRoot, err := r.indexPath(path, info, err) @@ -179,7 +177,7 @@ func isRealPath(root string) (bool, error) { return rootParent == realRootParent, nil } -func (r *directoryIndexer) indexBranch(root string, stager *progress.Stage) ([]string, error) { +func (r *directoryIndexer) indexBranch(root string, stager *progress.AtomicStage) ([]string, error) { rootRealPath, err := filepath.EvalSymlinks(root) if err != nil { var pathErr *os.PathError @@ -213,7 +211,7 @@ func (r *directoryIndexer) indexBranch(root string, stager *progress.Stage) ([]s targetPath = p } - stager.Current = targetPath + stager.Set(targetPath) lstat, err := os.Lstat(targetPath) newRoot, err := r.indexPath(targetPath, lstat, err) @@ -492,22 +490,3 @@ func requireFileInfo(_, _ string, info os.FileInfo, _ error) error { } return nil } - -func indexingProgress(path string) (*progress.Stage, *progress.Manual) { - stage := &progress.Stage{} - prog := progress.NewManual(-1) - - bus.Publish(partybus.Event{ - Type: event.FileIndexingStarted, - Source: path, - Value: struct { - progress.Stager - progress.Progressable - }{ - Stager: progress.Stager(stage), - Progressable: prog, - }, - }) - - return stage, prog -} diff --git a/syft/internal/fileresolver/directory_indexer_test.go b/syft/internal/fileresolver/directory_indexer_test.go index 1fe77106a47..6ccfe1f49cc 100644 --- a/syft/internal/fileresolver/directory_indexer_test.go +++ b/syft/internal/fileresolver/directory_indexer_test.go @@ -25,7 +25,7 @@ type indexerMock struct { additionalRoots map[string][]string } -func (m *indexerMock) indexer(s string, _ *progress.Stage) ([]string, error) { +func (m *indexerMock) indexer(s string, _ *progress.AtomicStage) ([]string, error) { m.observedRoots = append(m.observedRoots, s) return m.additionalRoots[s], nil } diff --git a/syft/internal/fileresolver/file.go b/syft/internal/fileresolver/file.go index c0bd30889e2..114612f82e4 100644 --- a/syft/internal/fileresolver/file.go +++ b/syft/internal/fileresolver/file.go @@ -12,7 +12,7 @@ var _ file.Resolver = (*File)(nil) // File implements path and content access for the file data source. type File struct { - filetreeResolver + FiletreeResolver path string indexer *fileIndexer } @@ -29,10 +29,11 @@ func NewFromFile(parent, path string, pathFilters ...PathIndexVisitor) (*File, e file := &File{ path: path, - filetreeResolver: filetreeResolver{ - chroot: *chroot, - tree: filetree.New(), - index: filetree.NewIndex(), + FiletreeResolver: FiletreeResolver{ + Chroot: *chroot, + Tree: filetree.New(), + Index: filetree.NewIndex(), + Opener: nativeOSFileOpener, }, indexer: newFileIndexer(path, cleanBase, pathFilters...), } @@ -49,9 +50,9 @@ func (r *File) buildIndex() error { return err } - r.tree = tree - r.index = index - r.searchContext = filetree.NewSearchContext(tree, index) + r.Tree = tree + r.Index = index + r.SearchContext = filetree.NewSearchContext(tree, index) return nil } diff --git a/syft/internal/fileresolver/file_indexer.go b/syft/internal/fileresolver/file_indexer.go index bc6d660ac6a..cf257dc95f5 100644 --- a/syft/internal/fileresolver/file_indexer.go +++ b/syft/internal/fileresolver/file_indexer.go @@ -9,6 +9,7 @@ import ( "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree" + "github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/internal/windows" ) @@ -49,14 +50,14 @@ func (r *fileIndexer) build() (filetree.Reader, filetree.IndexReader, error) { // Index file at the given path // A file indexer simply indexes the file and its directory. -func index(path string, indexer func(string, *progress.Stage) error) error { +func index(path string, indexer func(string, *progress.AtomicStage) error) error { // We want to index the file at the provided path and its parent directory. // We need to probably check that we have file access // We also need to determine what to do when the file itself is a symlink. - stager, prog := indexingProgress(path) + prog := bus.StartIndexingFiles(path) defer prog.SetCompleted() - err := indexer(path, stager) + err := indexer(path, prog.AtomicStage) if err != nil { return fmt.Errorf("unable to index filesystem path=%q: %w", path, err) } @@ -70,7 +71,7 @@ func index(path string, indexer func(string, *progress.Stage) error) error { // permissions errors on the file at path or its parent directory will return an error. // Filter functions provided to the indexer are honoured, so if the path provided (or its parent // directory) is filtered by a filter function, an error is returned. -func (r *fileIndexer) indexPath(path string, stager *progress.Stage) error { +func (r *fileIndexer) indexPath(path string, stager *progress.AtomicStage) error { log.WithFields("path", path).Trace("indexing file path") absPath, err := filepath.Abs(path) @@ -105,14 +106,14 @@ func (r *fileIndexer) indexPath(path string, stager *progress.Stage) error { return fmt.Errorf("unable to stat parent of file=%q: %w", absSymlinkFreeParent, err) } - stager.Current = absSymlinkFreeParent + stager.Set(absSymlinkFreeParent) indexParentErr := r.filterAndIndex(absSymlinkFreeParent, parentFi) if indexParentErr != nil { return indexParentErr } // We have indexed the parent successfully, now attempt to index the file. - stager.Current = absSymlinkFreeFilePath + stager.Set(absSymlinkFreeFilePath) indexFileErr := r.filterAndIndex(absSymlinkFreeFilePath, fi) if indexFileErr != nil { return indexFileErr diff --git a/syft/internal/fileresolver/filetree_resolver.go b/syft/internal/fileresolver/filetree_resolver.go index 15d051166a3..292349c7d92 100644 --- a/syft/internal/fileresolver/filetree_resolver.go +++ b/syft/internal/fileresolver/filetree_resolver.go @@ -14,33 +14,46 @@ import ( "github.com/anchore/syft/syft/internal/windows" ) -type filetreeResolver struct { - chroot ChrootContext - tree filetree.Reader - index filetree.IndexReader - searchContext filetree.Searcher +// TODO: consider making a constructor for this +type FiletreeResolver struct { + Chroot ChrootContext + Tree filetree.Reader + Index filetree.IndexReader + SearchContext filetree.Searcher + Opener func(stereoscopeFile.Reference) (io.ReadCloser, error) } -func (r *filetreeResolver) requestPath(userPath string) (string, error) { - return r.chroot.ToNativePath(userPath) +func nativeOSFileOpener(ref stereoscopeFile.Reference) (io.ReadCloser, error) { + // RealPath is posix so for windows file resolver we need to translate + // to its true on disk path. + filePath := string(ref.RealPath) + if windows.HostRunningOnWindows() { + filePath = windows.FromPosix(filePath) + } + + return stereoscopeFile.NewLazyReadCloser(filePath), nil +} + +func (r *FiletreeResolver) requestPath(userPath string) (string, error) { + return r.Chroot.ToNativePath(userPath) } // responsePath takes a path from the underlying fs domain and converts it to a path that is relative to the root of the file resolver. -func (r filetreeResolver) responsePath(path string) string { - return r.chroot.ToChrootPath(path) +func (r FiletreeResolver) responsePath(path string) string { + return r.Chroot.ToChrootPath(path) } // HasPath indicates if the given path exists in the underlying source. -func (r *filetreeResolver) HasPath(userPath string) bool { +func (r *FiletreeResolver) HasPath(userPath string) bool { requestPath, err := r.requestPath(userPath) if err != nil { return false } - return r.tree.HasPath(stereoscopeFile.Path(requestPath)) + return r.Tree.HasPath(stereoscopeFile.Path(requestPath)) } // FilesByPath returns all file.References that match the given paths from the file index. -func (r filetreeResolver) FilesByPath(userPaths ...string) ([]file.Location, error) { +func (r FiletreeResolver) FilesByPath(userPaths ...string) ([]file.Location, error) { var references = make([]file.Location, 0) for _, userPath := range userPaths { @@ -51,7 +64,7 @@ func (r filetreeResolver) FilesByPath(userPaths ...string) ([]file.Location, err } // we should be resolving symlinks and preserving this information as a AccessPath to the real file - ref, err := r.searchContext.SearchByPath(userStrPath, filetree.FollowBasenameLinks) + ref, err := r.SearchContext.SearchByPath(userStrPath, filetree.FollowBasenameLinks) if err != nil { log.Tracef("unable to evaluate symlink for path=%q : %+v", userPath, err) continue @@ -61,7 +74,7 @@ func (r filetreeResolver) FilesByPath(userPaths ...string) ([]file.Location, err continue } - entry, err := r.index.Get(*ref.Reference) + entry, err := r.Index.Get(*ref.Reference) if err != nil { log.Warnf("unable to get file by path=%q : %+v", userPath, err) continue @@ -90,12 +103,12 @@ func (r filetreeResolver) FilesByPath(userPaths ...string) ([]file.Location, err return references, nil } -func (r filetreeResolver) requestGlob(pattern string) (string, error) { - return r.chroot.ToNativeGlob(pattern) +func (r FiletreeResolver) requestGlob(pattern string) (string, error) { + return r.Chroot.ToNativeGlob(pattern) } // FilesByGlob returns all file.References that match the given path glob pattern from any layer in the image. -func (r filetreeResolver) FilesByGlob(patterns ...string) ([]file.Location, error) { +func (r FiletreeResolver) FilesByGlob(patterns ...string) ([]file.Location, error) { uniqueFileIDs := stereoscopeFile.NewFileReferenceSet() uniqueLocations := make([]file.Location, 0) @@ -104,7 +117,7 @@ func (r filetreeResolver) FilesByGlob(patterns ...string) ([]file.Location, erro if err != nil { return nil, err } - refVias, err := r.searchContext.SearchByGlob(requestGlob, filetree.FollowBasenameLinks) + refVias, err := r.SearchContext.SearchByGlob(requestGlob, filetree.FollowBasenameLinks) if err != nil { return nil, err } @@ -112,7 +125,7 @@ func (r filetreeResolver) FilesByGlob(patterns ...string) ([]file.Location, erro if !refVia.HasReference() || uniqueFileIDs.Contains(*refVia.Reference) { continue } - entry, err := r.index.Get(*refVia.Reference) + entry, err := r.Index.Get(*refVia.Reference) if err != nil { return nil, fmt.Errorf("unable to get file metadata for reference %s: %w", refVia.RealPath, err) } @@ -137,7 +150,7 @@ func (r filetreeResolver) FilesByGlob(patterns ...string) ([]file.Location, erro // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference. // This is helpful when attempting to find a file that is in the same layer or lower as another file. -func (r *filetreeResolver) RelativeFileByPath(_ file.Location, path string) *file.Location { +func (r *FiletreeResolver) RelativeFileByPath(_ file.Location, path string) *file.Location { paths, err := r.FilesByPath(path) if err != nil { return nil @@ -151,12 +164,12 @@ func (r *filetreeResolver) RelativeFileByPath(_ file.Location, path string) *fil // FileContentsByLocation fetches file contents for a single file reference relative to a directory. // If the path does not exist an error is returned. -func (r filetreeResolver) FileContentsByLocation(location file.Location) (io.ReadCloser, error) { +func (r FiletreeResolver) FileContentsByLocation(location file.Location) (io.ReadCloser, error) { if location.RealPath == "" { return nil, errors.New("empty path given") } - entry, err := r.index.Get(location.Reference()) + entry, err := r.Index.Get(location.Reference()) if err != nil { return nil, err } @@ -166,21 +179,14 @@ func (r filetreeResolver) FileContentsByLocation(location file.Location) (io.Rea return nil, fmt.Errorf("cannot read contents of non-file %q", location.Reference().RealPath) } - // RealPath is posix so for windows file resolver we need to translate - // to its true on disk path. - filePath := string(location.Reference().RealPath) - if windows.HostRunningOnWindows() { - filePath = windows.FromPosix(filePath) - } - - return stereoscopeFile.NewLazyReadCloser(filePath), nil + return r.Opener(location.Reference()) } -func (r *filetreeResolver) AllLocations(ctx context.Context) <-chan file.Location { +func (r *FiletreeResolver) AllLocations(ctx context.Context) <-chan file.Location { results := make(chan file.Location) go func() { defer close(results) - for _, ref := range r.tree.AllFiles(stereoscopeFile.AllTypes()...) { + for _, ref := range r.Tree.AllFiles(stereoscopeFile.AllTypes()...) { select { case <-ctx.Done(): return @@ -192,8 +198,8 @@ func (r *filetreeResolver) AllLocations(ctx context.Context) <-chan file.Locatio return results } -func (r *filetreeResolver) FileMetadataByLocation(location file.Location) (file.Metadata, error) { - entry, err := r.index.Get(location.Reference()) +func (r *FiletreeResolver) FileMetadataByLocation(location file.Location) (file.Metadata, error) { + entry, err := r.Index.Get(location.Reference()) if err != nil { return file.Metadata{}, fmt.Errorf("location: %+v : %w", location, os.ErrNotExist) } @@ -201,11 +207,11 @@ func (r *filetreeResolver) FileMetadataByLocation(location file.Location) (file. return entry.Metadata, nil } -func (r *filetreeResolver) FilesByMIMEType(types ...string) ([]file.Location, error) { +func (r *FiletreeResolver) FilesByMIMEType(types ...string) ([]file.Location, error) { uniqueFileIDs := stereoscopeFile.NewFileReferenceSet() uniqueLocations := make([]file.Location, 0) - refVias, err := r.searchContext.SearchByMIMEType(types...) + refVias, err := r.SearchContext.SearchByMIMEType(types...) if err != nil { return nil, err } diff --git a/syft/internal/fileresolver/filetree_resolver_test.go b/syft/internal/fileresolver/filetree_resolver_test.go index adbc77502f1..a3c5209e177 100644 --- a/syft/internal/fileresolver/filetree_resolver_test.go +++ b/syft/internal/fileresolver/filetree_resolver_test.go @@ -971,7 +971,7 @@ func Test_directoryResolver_FileContentsByLocation(t *testing.T) { r, err := NewFromDirectory(".", "") require.NoError(t, err) - exists, existingPath, err := r.tree.File(stereoscopeFile.Path(filepath.Join(cwd, "test-fixtures/image-simple/file-1.txt"))) + exists, existingPath, err := r.Tree.File(stereoscopeFile.Path(filepath.Join(cwd, "test-fixtures/image-simple/file-1.txt"))) require.True(t, exists) require.NoError(t, err) require.True(t, existingPath.HasReference()) @@ -1271,7 +1271,7 @@ func TestDirectoryResolver_FilesContents_errorOnDirRequest(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() for loc := range resolver.AllLocations(ctx) { - entry, err := resolver.index.Get(loc.Reference()) + entry, err := resolver.Index.Get(loc.Reference()) require.NoError(t, err) if entry.Metadata.IsDir() { dirLoc = &loc @@ -1500,7 +1500,7 @@ func Test_fileResolver_FileContentsByLocation(t *testing.T) { r, err := NewFromFile(parentPath, filePath) require.NoError(t, err) - exists, existingPath, err := r.tree.File(stereoscopeFile.Path(filepath.Join(cwd, "test-fixtures/image-simple/file-1.txt"))) + exists, existingPath, err := r.Tree.File(stereoscopeFile.Path(filepath.Join(cwd, "test-fixtures/image-simple/file-1.txt"))) require.True(t, exists) require.NoError(t, err) require.True(t, existingPath.HasReference()) @@ -1553,7 +1553,7 @@ func TestFileResolver_AllLocations_errorOnDirRequest(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() for loc := range resolver.AllLocations(ctx) { - entry, err := resolver.index.Get(loc.Reference()) + entry, err := resolver.Index.Get(loc.Reference()) require.NoError(t, err) if dirLoc == nil && entry.Metadata.IsDir() { dirLoc = &loc diff --git a/syft/internal/sourcemetadata/generated.go b/syft/internal/sourcemetadata/generated.go index ee622ef7f27..ffcd9c88202 100644 --- a/syft/internal/sourcemetadata/generated.go +++ b/syft/internal/sourcemetadata/generated.go @@ -6,5 +6,5 @@ import "github.com/anchore/syft/syft/source" // AllTypes returns a list of all source metadata types that syft supports (that are represented in the source.Description.Metadata field). func AllTypes() []any { - return []any{source.DirectoryMetadata{}, source.FileMetadata{}, source.ImageMetadata{}} + return []any{source.DirectoryMetadata{}, source.FileMetadata{}, source.ImageMetadata{}, source.SnapMetadata{}} } diff --git a/syft/internal/sourcemetadata/names.go b/syft/internal/sourcemetadata/names.go index 58e56fe8f2f..353b798edaa 100644 --- a/syft/internal/sourcemetadata/names.go +++ b/syft/internal/sourcemetadata/names.go @@ -11,6 +11,7 @@ var jsonNameFromType = map[reflect.Type][]string{ reflect.TypeOf(source.DirectoryMetadata{}): {"directory", "dir"}, reflect.TypeOf(source.FileMetadata{}): {"file"}, reflect.TypeOf(source.ImageMetadata{}): {"image"}, + reflect.TypeOf(source.SnapMetadata{}): {"snap"}, } func AllTypeNames() []string { diff --git a/syft/internal/unionreader/union_reader.go b/syft/internal/unionreader/union_reader.go index 5f2a2938fdc..96bf558b27f 100644 --- a/syft/internal/unionreader/union_reader.go +++ b/syft/internal/unionreader/union_reader.go @@ -4,6 +4,9 @@ import ( "bytes" "fmt" "io" + "sync" + + "github.com/diskfs/go-diskfs/filesystem/squashfs" macho "github.com/anchore/go-macholibre" "github.com/anchore/syft/internal/log" @@ -48,12 +51,15 @@ func GetUnionReader(readerCloser io.ReadCloser) (UnionReader, error) { // file.LocationReadCloser embeds a ReadCloser, which is likely // to implement UnionReader. Check whether the embedded read closer // implements UnionReader, and just return that if so. - r, ok := readerCloser.(file.LocationReadCloser) - if ok { - ur, ok := r.ReadCloser.(UnionReader) - if ok { - return ur, nil - } + + if r, ok := readerCloser.(file.LocationReadCloser); ok { + return GetUnionReader(r.ReadCloser) + } + + if r, ok := readerCloser.(*squashfs.File); ok { + // seeking is implemented, but not io.ReaderAt. Lets wrap it to prevent from degrading performance + // by copying all data. + return newReaderAtAdapter(r), nil } b, err := io.ReadAll(readerCloser) @@ -75,3 +81,58 @@ func GetUnionReader(readerCloser io.ReadCloser) (UnionReader, error) { return reader, nil } + +type readerAtAdapter struct { + io.ReadSeekCloser + mu *sync.Mutex +} + +func newReaderAtAdapter(rs io.ReadSeekCloser) UnionReader { + return &readerAtAdapter{ + ReadSeekCloser: rs, + mu: &sync.Mutex{}, + } +} + +func (r *readerAtAdapter) Read(p []byte) (n int, err error) { + r.mu.Lock() + defer r.mu.Unlock() + return r.ReadSeekCloser.Read(p) +} + +func (r *readerAtAdapter) Seek(offset int64, whence int) (int64, error) { + r.mu.Lock() + defer r.mu.Unlock() + return r.ReadSeekCloser.Seek(offset, whence) +} + +func (r *readerAtAdapter) ReadAt(p []byte, off int64) (n int, err error) { + r.mu.Lock() + defer r.mu.Unlock() + + currentPos, err := r.ReadSeekCloser.Seek(0, io.SeekCurrent) // save current pos + if err != nil { + return 0, err + } + + _, err = r.ReadSeekCloser.Seek(off, io.SeekStart) // seek to absolute position `off` + if err != nil { + return 0, err + } + + n, err = r.ReadSeekCloser.Read(p) // read from that absolute position + + // restore the position for the stateful read/seek operations + if restoreErr := r.restorePosition(currentPos); restoreErr != nil { + if err == nil { + err = restoreErr + } + } + + return n, err +} + +func (r *readerAtAdapter) restorePosition(pos int64) error { + _, err := r.ReadSeekCloser.Seek(pos, io.SeekStart) + return err +} diff --git a/syft/internal/unionreader/union_reader_test.go b/syft/internal/unionreader/union_reader_test.go index f4353f1a99d..961e4561cd3 100644 --- a/syft/internal/unionreader/union_reader_test.go +++ b/syft/internal/unionreader/union_reader_test.go @@ -1,8 +1,10 @@ package unionreader import ( + "bytes" "io" "strings" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -61,3 +63,275 @@ func Test_getUnionReader_fileLocationReadCloser(t *testing.T) { require.NoError(t, err) require.Equal(t, p, ur) } + +func TestReaderAtAdapter_ReadAt(t *testing.T) { + testData := "Hello, World! This is a test string for ReadAt." + + t.Run("basic functionality", func(t *testing.T) { + reader := newReadSeekCloser(strings.NewReader(testData)) + adapter := newReaderAtAdapter(reader) + + tests := []struct { + name string + offset int64 + length int + expected string + }{ + {name: "read from beginning", offset: 0, length: 5, expected: "Hello"}, + {name: "read from middle", offset: 7, length: 5, expected: "World"}, + {name: "read from end", offset: int64(len(testData) - 4), length: 4, expected: "dAt."}, + {name: "read single character", offset: 12, length: 1, expected: "!"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + buf := make([]byte, tt.length) + n, err := adapter.ReadAt(buf, tt.offset) + + if err != nil && err != io.EOF { + t.Fatalf("Unexpected error: %v", err) + } + + result := string(buf[:n]) + if result != tt.expected { + t.Errorf("Expected %q, got %q", tt.expected, result) + } + }) + } + }) + + t.Run("edge cases", func(t *testing.T) { + tests := []struct { + name string + data string + offset int64 + bufSize int + expectedN int + expectedErr error + expectedStr string + }{ + { + name: "beyond EOF", + data: "Hello", + offset: 10, + bufSize: 5, + expectedN: 0, + expectedErr: io.EOF, + expectedStr: "", + }, + { + name: "partial read", + data: "Hello", + offset: 2, + bufSize: 10, + expectedN: 3, + expectedErr: nil, + expectedStr: "llo", + }, + { + name: "empty buffer", + data: "Hello", + offset: 0, + bufSize: 0, + expectedN: 0, + expectedErr: nil, + expectedStr: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := newReadSeekCloser(strings.NewReader(tt.data)) + adapter := newReaderAtAdapter(reader) + + buf := make([]byte, tt.bufSize) + n, err := adapter.ReadAt(buf, tt.offset) + + if err != tt.expectedErr { + t.Errorf("Expected error %v, got %v", tt.expectedErr, err) + } + + if n != tt.expectedN { + t.Errorf("Expected %d bytes read, got %d", tt.expectedN, n) + } + + result := string(buf[:n]) + if result != tt.expectedStr { + t.Errorf("Expected %q, got %q", tt.expectedStr, result) + } + }) + } + }) + + t.Run("multiple reads from same position", func(t *testing.T) { + reader := newReadSeekCloser(strings.NewReader(testData)) + adapter := newReaderAtAdapter(reader) + + // read the same data multiple times + for i := 0; i < 3; i++ { + buf := make([]byte, 5) + n, err := adapter.ReadAt(buf, 7) + + if err != nil && err != io.EOF { + t.Fatalf("ReadAt %d failed: %v", i, err) + } + + result := string(buf[:n]) + if result != "World" { + t.Errorf("ReadAt %d: expected 'World', got %q", i, result) + } + } + }) + + t.Run("concurrent access", func(t *testing.T) { + td := "0123456789abcdefghijklmnopqrstuvwxyz" + reader := newReadSeekCloser(strings.NewReader(td)) + adapter := newReaderAtAdapter(reader) + + const numGoroutines = 10 + const numReads = 100 + + var wg sync.WaitGroup + results := make(chan bool, numGoroutines*numReads) + + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + go func(goroutineID int) { + defer wg.Done() + + for j := 0; j < numReads; j++ { + offset := int64(goroutineID % len(td)) + buf := make([]byte, 1) + + n, err := adapter.ReadAt(buf, offset) + if err != nil && err != io.EOF { + results <- false + return + } + + if n > 0 { + expected := td[offset] + if buf[0] != expected { + results <- false + return + } + } + results <- true + } + }(i) + } + + wg.Wait() + close(results) + + successCount := 0 + totalCount := 0 + for success := range results { + totalCount++ + if success { + successCount++ + } + } + + if successCount != totalCount { + t.Errorf("Concurrent reads failed: %d/%d successful", successCount, totalCount) + } + }) +} + +func TestReaderAtAdapter_PositionHandling(t *testing.T) { + testData := "Hello, World!" + + t.Run("preserves position after ReadAt", func(t *testing.T) { + reader := newReadSeekCloser(strings.NewReader(testData)) + adapter := newReaderAtAdapter(reader) + + // move to a specific position + initialPos := int64(7) + _, err := adapter.Seek(initialPos, io.SeekStart) + if err != nil { + t.Fatalf("Failed to seek: %v", err) + } + + // read using ReadAt + buf := make([]byte, 5) + _, err = adapter.ReadAt(buf, 0) + if err != nil && err != io.EOF { + t.Fatalf("ReadAt failed: %v", err) + } + + // verify position is preserved + currentPos, err := adapter.Seek(0, io.SeekCurrent) + if err != nil { + t.Fatalf("Failed to get current position: %v", err) + } + + if currentPos != initialPos { + t.Errorf("Position not preserved. Expected %d, got %d", initialPos, currentPos) + } + }) + + t.Run("does not affect regular reads", func(t *testing.T) { + reader := newReadSeekCloser(strings.NewReader(testData)) + adapter := newReaderAtAdapter(reader) + + // read first few bytes normally + normalBuf := make([]byte, 5) + n, err := adapter.Read(normalBuf) + if err != nil { + t.Fatalf("Normal read failed: %v", err) + } + if string(normalBuf[:n]) != "Hello" { + t.Errorf("Expected 'Hello', got %q", string(normalBuf[:n])) + } + + // use ReadAt to read from a different position + readAtBuf := make([]byte, 5) + n, err = adapter.ReadAt(readAtBuf, 7) + if err != nil && err != io.EOF { + t.Fatalf("ReadAt failed: %v", err) + } + if string(readAtBuf[:n]) != "World" { + t.Errorf("Expected 'World', got %q", string(readAtBuf[:n])) + } + + // continue normal reading - should pick up where we left off + continueBuf := make([]byte, 2) + n, err = adapter.Read(continueBuf) + if err != nil { + t.Fatalf("Continue read failed: %v", err) + } + if string(continueBuf[:n]) != ", " { + t.Errorf("Expected ', ', got %q", string(continueBuf[:n])) + } + }) +} + +func TestReaderAtAdapter_Close(t *testing.T) { + reader := newReadSeekCloser(bytes.NewReader([]byte("test data"))) + adapter := newReaderAtAdapter(reader) + + // test that adapter can be closed + err := adapter.Close() + if err != nil { + t.Errorf("Close failed: %v", err) + } + + if !reader.closed { + t.Error("Underlying reader was not closed") + } +} + +type readSeekCloser struct { + io.ReadSeeker + closed bool +} + +func newReadSeekCloser(rs io.ReadSeeker) *readSeekCloser { + return &readSeekCloser{ReadSeeker: rs} +} + +func (r *readSeekCloser) Close() error { + r.closed = true + return nil +} diff --git a/syft/pkg/cataloger/python/parse_wheel_egg.go b/syft/pkg/cataloger/python/parse_wheel_egg.go index e4da35b674e..0b92dd8302d 100644 --- a/syft/pkg/cataloger/python/parse_wheel_egg.go +++ b/syft/pkg/cataloger/python/parse_wheel_egg.go @@ -4,7 +4,6 @@ import ( "bufio" "context" "encoding/json" - "fmt" "io" "path" "path/filepath" @@ -16,6 +15,7 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/licenses" "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/internal/unknown" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" @@ -26,17 +26,15 @@ import ( // fields are governed by the PyPA core metadata specification (https://packaging.python.org/en/latest/specifications/core-metadata/). func parseWheelOrEgg(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { pd, sources, err := assembleEggOrWheelMetadata(resolver, reader.Location) - if err != nil { - return nil, nil, err - } + if pd == nil { - return nil, nil, nil + return nil, nil, err } // This can happen for Python 2.7 where it is reported from an egg-info, but Python is // the actual runtime, it isn't a "package". The special-casing here allows to skip it if pd.Name == "Python" { - return nil, nil, nil + return nil, nil, err } pkgs := []pkg.Package{ @@ -47,11 +45,11 @@ func parseWheelOrEgg(ctx context.Context, resolver file.Resolver, _ *generic.Env ), } - return pkgs, nil, nil + return pkgs, nil, err } // fetchInstalledFiles finds a corresponding installed-files.txt file for the given python package metadata file and returns the set of file records contained. -func fetchInstalledFiles(resolver file.Resolver, metadataLocation file.Location, sitePackagesRootPath string) (files []pkg.PythonFileRecord, sources []file.Location, err error) { +func fetchInstalledFiles(resolver file.Resolver, metadataLocation file.Location, sitePackagesRootPath string) (files []pkg.PythonFileRecord, sources []file.Location, retErr error) { // we've been given a file reference to a specific wheel METADATA file. note: this may be for a directory // or for an image... for an image the METADATA file may be present within multiple layers, so it is important // to reconcile the installed-files.txt path to the same layer (or the next adjacent lower layer). @@ -72,8 +70,7 @@ func fetchInstalledFiles(resolver file.Resolver, metadataLocation file.Location, // parse the installed-files contents installedFiles, err := parseInstalledFiles(installedFilesContents, metadataLocation.RealPath, sitePackagesRootPath) if err != nil { - log.WithFields("error", err, "path", metadataLocation.RealPath).Trace("unable to parse installed-files.txt for python package") - return files, sources, nil + retErr = unknown.Newf(*installedFilesRef, "unable to parse installed-files.txt for python package: %w", retErr) } files = append(files, installedFiles...) @@ -82,7 +79,7 @@ func fetchInstalledFiles(resolver file.Resolver, metadataLocation file.Location, } // fetchRecordFiles finds a corresponding RECORD file for the given python package metadata file and returns the set of file records contained. -func fetchRecordFiles(resolver file.Resolver, metadataLocation file.Location) (files []pkg.PythonFileRecord, sources []file.Location, err error) { +func fetchRecordFiles(resolver file.Resolver, metadataLocation file.Location) (files []pkg.PythonFileRecord, sources []file.Location, retErr error) { // we've been given a file reference to a specific wheel METADATA file. note: this may be for a directory // or for an image... for an image the METADATA file may be present within multiple layers, so it is important // to reconcile the RECORD path to the same layer (or the next adjacent lower layer). @@ -101,11 +98,12 @@ func fetchRecordFiles(resolver file.Resolver, metadataLocation file.Location) (f defer internal.CloseAndLogError(recordContents, recordPath) // parse the record contents - records := parseWheelOrEggRecord(recordContents) + var records []pkg.PythonFileRecord + records, retErr = parseWheelOrEggRecord(file.NewLocationReadCloser(*recordRef, recordContents)) files = append(files, records...) } - return files, sources, nil + return files, sources, retErr } // fetchTopLevelPackages finds a corresponding top_level.txt file for the given python package metadata file and returns the set of package names contained. @@ -133,7 +131,7 @@ func fetchTopLevelPackages(resolver file.Resolver, metadataLocation file.Locatio } if err := scanner.Err(); err != nil { - return nil, nil, fmt.Errorf("could not read python package top_level.txt: %w", err) + return nil, nil, err } return pkgs, sources, nil @@ -216,14 +214,15 @@ func assembleEggOrWheelMetadata(resolver file.Resolver, metadataLocation file.Lo } // attach any python files found for the given wheel/egg installation + var errs error r, s, err := fetchRecordFiles(resolver, metadataLocation) if err != nil { - return nil, nil, err + errs = unknown.Joinf(errs, "could not read python package RECORD file: %w", err) } if len(r) == 0 { r, s, err = fetchInstalledFiles(resolver, metadataLocation, pd.SitePackagesRootPath) if err != nil { - return nil, nil, err + errs = unknown.Joinf(errs, "could not read python package installed-files.txt: %w", err) } } @@ -233,7 +232,7 @@ func assembleEggOrWheelMetadata(resolver file.Resolver, metadataLocation file.Lo // attach any top-level package names found for the given wheel/egg installation p, s, err := fetchTopLevelPackages(resolver, metadataLocation) if err != nil { - return nil, nil, err + errs = unknown.Joinf(errs, "could not read python package top_level.txt: %w", err) } sources = append(sources, s...) pd.TopLevelPackages = p @@ -241,12 +240,12 @@ func assembleEggOrWheelMetadata(resolver file.Resolver, metadataLocation file.Lo // attach any direct-url package data found for the given wheel/egg installation d, s, err := fetchDirectURLData(resolver, metadataLocation) if err != nil { - return nil, nil, err + errs = unknown.Joinf(errs, "could not read python package direct_url.json: %w", err) } sources = append(sources, s...) pd.DirectURLOrigin = d - return &pd, sources, nil + return &pd, sources, errs } func findLicenses(ctx context.Context, resolver file.Resolver, m parsedData) pkg.LicenseSet { diff --git a/syft/pkg/cataloger/python/parse_wheel_egg_record.go b/syft/pkg/cataloger/python/parse_wheel_egg_record.go index 27e0260ccdb..94982f10b5f 100644 --- a/syft/pkg/cataloger/python/parse_wheel_egg_record.go +++ b/syft/pkg/cataloger/python/parse_wheel_egg_record.go @@ -10,23 +10,31 @@ import ( "strings" "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/internal/unknown" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" ) // parseWheelOrEggRecord takes a Python Egg or Wheel (which share the same format and values for our purposes), // returning all Python packages listed. -func parseWheelOrEggRecord(reader io.Reader) []pkg.PythonFileRecord { +func parseWheelOrEggRecord(reader file.LocationReadCloser) ([]pkg.PythonFileRecord, error) { var records []pkg.PythonFileRecord r := csv.NewReader(reader) for { recordList, err := r.Read() - if errors.Is(err, io.EOF) { - break - } if err != nil { - log.Debugf("unable to read python record file: %w", err) - continue + if errors.Is(err, io.EOF) { + break + } + var parseErr *csv.ParseError + if errors.As(err, &parseErr) { + log.WithFields("error", parseErr).Debug("unable to read python record entry (skipping entry)") + continue + } + + // probably an I/O error... we could have missed some package content, so we include this location as an unknown + return records, unknown.Newf(reader.Coordinates, "unable to read python record file: %w", err) } if len(recordList) != 3 { @@ -62,7 +70,7 @@ func parseWheelOrEggRecord(reader io.Reader) []pkg.PythonFileRecord { records = append(records, record) } - return records + return records, nil } func parseInstalledFiles(reader io.Reader, location, sitePackagesRootPath string) ([]pkg.PythonFileRecord, error) { diff --git a/syft/pkg/cataloger/python/parse_wheel_egg_record_test.go b/syft/pkg/cataloger/python/parse_wheel_egg_record_test.go index 0815c06b0d5..a705378b125 100644 --- a/syft/pkg/cataloger/python/parse_wheel_egg_record_test.go +++ b/syft/pkg/cataloger/python/parse_wheel_egg_record_test.go @@ -5,7 +5,9 @@ import ( "testing" "github.com/go-test/deep" + "github.com/stretchr/testify/require" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" ) @@ -46,7 +48,8 @@ func TestParseWheelEggRecord(t *testing.T) { t.Fatalf("failed to open fixture: %+v", err) } - actual := parseWheelOrEggRecord(fixture) + actual, err := parseWheelOrEggRecord(file.NewLocationReadCloser(file.NewLocation(test.Fixture), fixture)) + require.NoError(t, err, "failed to parse: %+v", err) for _, d := range deep.Equal(actual, test.ExpectedMetadata) { t.Errorf("diff: %+v", d) diff --git a/syft/source/snap_metadata.go b/syft/source/snap_metadata.go new file mode 100644 index 00000000000..f5b1fab39be --- /dev/null +++ b/syft/source/snap_metadata.go @@ -0,0 +1,23 @@ +package source + +import "github.com/anchore/syft/syft/file" + +type SnapMetadata struct { + // Summary is a brief description of the snap package + Summary string `yaml:"summary" json:"summary,omitempty"` + + // Base is the base snap this package builds upon + Base string `yaml:"base" json:"base,omitempty"` + + // Grade is the development stage (stable, candidate, beta, edge) + Grade string `yaml:"grade" json:"grade,omitempty"` + + // Confinement is the security isolation level (strict, classic, devmode) + Confinement string `yaml:"confinement" json:"confinement,omitempty"` + + // Architectures are the supported CPU architectures + Architectures []string `yaml:"architectures" json:"architectures,omitempty"` + + // Digests are hashes of the snap squashfs files + Digests []file.Digest `yaml:"digests" json:"digests,omitempty"` +} diff --git a/syft/source/snapsource/manifest.go b/syft/source/snapsource/manifest.go new file mode 100644 index 00000000000..8d6c6161185 --- /dev/null +++ b/syft/source/snapsource/manifest.go @@ -0,0 +1,56 @@ +package snapsource + +import ( + "fmt" + + "github.com/goccy/go-yaml" + + "github.com/anchore/syft/internal" + "github.com/anchore/syft/syft/file" +) + +type snapManifest struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + Summary string `yaml:"summary"` + Base string `yaml:"base"` + Grade string `yaml:"grade"` + Confinement string `yaml:"confinement"` + Architectures []string `yaml:"architectures"` +} + +const manifestLocation = "/meta/snap.yaml" + +func parseManifest(resolver file.Resolver) (*snapManifest, error) { + locations, err := resolver.FilesByPath(manifestLocation) + if err != nil { + return nil, fmt.Errorf("unable to find snap manifest file: %w", err) + } + + if len(locations) == 0 { + return nil, fmt.Errorf("no snap manifest file found") + } + + if len(locations) > 1 { + return nil, fmt.Errorf("multiple snap manifest files found") + } + + manifestFile := locations[0] + + reader, err := resolver.FileContentsByLocation(manifestFile) + if err != nil { + return nil, fmt.Errorf("unable to read snap manifest file: %w", err) + } + defer internal.CloseAndLogError(reader, manifestFile.RealPath) + + var manifest snapManifest + if err := yaml.NewDecoder(reader).Decode(&manifest); err != nil { + return nil, fmt.Errorf("unable to decode snap manifest file: %w", err) + } + + if manifest.Name == "" || manifest.Version == "" { + return nil, fmt.Errorf("invalid snap manifest file: missing name or version") + } + + return &manifest, nil +} diff --git a/syft/source/snapsource/snap.go b/syft/source/snapsource/snap.go new file mode 100644 index 00000000000..930cee37542 --- /dev/null +++ b/syft/source/snapsource/snap.go @@ -0,0 +1,249 @@ +package snapsource + +import ( + "context" + "crypto" + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "github.com/spf13/afero" + + stereoFile "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/syft/internal/bus" + intFile "github.com/anchore/syft/internal/file" + "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/event/monitor" + "github.com/anchore/syft/syft/file" +) + +type snapFile struct { + Path string + Digests []file.Digest + MimeType string + Cleanup func() error +} + +type remoteSnap struct { + snapIdentity + URL string +} + +type snapIdentity struct { + Name string + Channel string + Architecture string +} + +func (s snapIdentity) String() string { + parts := []string{s.Name} + + if s.Channel != "" { + parts = append(parts, fmt.Sprintf("@%s", s.Channel)) + } + + if s.Architecture != "" { + parts = append(parts, fmt.Sprintf(" (%s)", s.Architecture)) + } + + return strings.Join(parts, "") +} + +func getRemoteSnapFile(ctx context.Context, fs afero.Fs, getter intFile.Getter, cfg Config) (*snapFile, error) { + if cfg.Request == "" { + return nil, fmt.Errorf("invalid request: %q", cfg.Request) + } + + var architecture string + if cfg.Platform != nil { + architecture = cfg.Platform.Architecture + } + + info, err := resolveRemoteSnap(cfg.Request, architecture) + if err != nil { + return nil, err + } + + return newSnapFileFromRemote(ctx, fs, cfg, getter, info) +} + +func newSnapFileFromRemote(ctx context.Context, fs afero.Fs, cfg Config, getter intFile.Getter, info *remoteSnap) (*snapFile, error) { + t, err := afero.TempDir(fs, "", "syft-snap-") + if err != nil { + return nil, fmt.Errorf("failed to create temp directory: %w", err) + } + + snapFilePath := path.Join(t, path.Base(info.URL)) + err = downloadSnap(getter, info, snapFilePath) + if err != nil { + return nil, fmt.Errorf("failed to download snap file: %w", err) + } + + closer := func() error { + return fs.RemoveAll(t) + } + + mimeType, digests, err := getSnapFileInfo(ctx, fs, snapFilePath, cfg.DigestAlgorithms) + if err != nil { + return nil, err + } + + return &snapFile{ + Path: snapFilePath, + Digests: digests, + MimeType: mimeType, + Cleanup: closer, + }, nil +} + +func newSnapFromFile(ctx context.Context, fs afero.Fs, cfg Config) (*snapFile, error) { + var architecture string + if cfg.Platform != nil { + architecture = cfg.Platform.Architecture + } + + if architecture != "" { + return nil, fmt.Errorf("architecture cannot be specified for local snap files: %q", cfg.Request) + } + + absPath, err := filepath.Abs(cfg.Request) + if err != nil { + return nil, fmt.Errorf("unable to get absolute path of snap: %w", err) + } + + mimeType, digests, err := getSnapFileInfo(ctx, fs, absPath, cfg.DigestAlgorithms) + if err != nil { + return nil, err + } + + return &snapFile{ + Path: absPath, + Digests: digests, + MimeType: mimeType, + // note: we have no closer since this is the user's file (never delete it) + }, nil +} + +func getSnapFileInfo(ctx context.Context, fs afero.Fs, path string, hashes []crypto.Hash) (string, []file.Digest, error) { + fileMeta, err := fs.Stat(path) + if err != nil { + return "", nil, fmt.Errorf("unable to stat path=%q: %w", path, err) + } + + if fileMeta.IsDir() { + return "", nil, fmt.Errorf("given path is a directory, not a snap file: %q", path) + } + + fh, err := fs.Open(path) + if err != nil { + return "", nil, fmt.Errorf("unable to open file=%q: %w", path, err) + } + defer fh.Close() + + mimeType := stereoFile.MIMEType(fh) + if !isSquashFSFile(mimeType, path) { + return "", nil, fmt.Errorf("not a valid squashfs/snap file: %q (mime-type=%q)", path, mimeType) + } + + var digests []file.Digest + if len(hashes) > 0 { + if _, err := fh.Seek(0, 0); err != nil { + return "", nil, fmt.Errorf("unable to reset file position: %w", err) + } + + digests, err = intFile.NewDigestsFromFile(ctx, fh, hashes) + if err != nil { + return "", nil, fmt.Errorf("unable to calculate digests for file=%q: %w", path, err) + } + } + + return mimeType, digests, nil +} + +// resolveRemoteSnap parses a snap request and returns the appropriate path or URL +// The request can be: +// - A snap name (e.g., "etcd") +// - A snap name with channel (e.g., "etcd@beta" or "etcd@2.3/stable") +func resolveRemoteSnap(request, architecture string) (*remoteSnap, error) { + if architecture == "" { + architecture = defaultArchitecture + } + + snapName, channel := parseSnapRequest(request) + + id := snapIdentity{ + Name: snapName, + Channel: channel, + Architecture: architecture, + } + + client := newSnapcraftClient() + + downloadURL, err := client.GetSnapDownloadURL(id) + if err != nil { + return nil, err + } + + log.WithFields("url", downloadURL, "name", snapName, "channel", channel, "architecture", architecture).Debugf("snap resolved") + + return &remoteSnap{ + snapIdentity: id, + URL: downloadURL, + }, nil +} + +// parseSnapRequest parses a snap request into name and channel +// Examples: +// - "etcd" -> name="etcd", channel="stable" (default) +// - "etcd@beta" -> name="etcd", channel="beta" +// - "etcd@2.3/stable" -> name="etcd", channel="2.3/stable" +func parseSnapRequest(request string) (name, channel string) { + parts := strings.SplitN(request, "@", 2) + name = parts[0] + + if len(parts) == 2 { + channel = parts[1] + } + + if channel == "" { + channel = defaultChannel + } + + return name, channel +} + +func downloadSnap(getter intFile.Getter, info *remoteSnap, dest string) error { + log.WithFields("url", info.URL, "destination", dest).Debug("downloading snap file") + + prog := bus.StartPullSourceTask(monitor.GenericTask{ + Title: monitor.Title{ + Default: "Download snap", + WhileRunning: "Downloading snap", + OnSuccess: "Downloaded snap", + }, + HideOnSuccess: false, + HideStageOnSuccess: true, + ID: "", + ParentID: "", + Context: info.String(), + }, -1, "") + + if err := getter.GetFile(dest, info.URL, prog.Manual); err != nil { + prog.SetError(err) + return fmt.Errorf("failed to download snap file at %q: %w", info.URL, err) + } + + prog.SetCompleted() + return nil +} + +// fileExists checks if a file exists and is not a directory +func fileExists(fs afero.Fs, path string) bool { + info, err := fs.Stat(path) + if os.IsNotExist(err) { + return false + } + return err == nil && !info.IsDir() +} diff --git a/syft/source/snapsource/snap_source.go b/syft/source/snapsource/snap_source.go new file mode 100644 index 00000000000..e52302adf04 --- /dev/null +++ b/syft/source/snapsource/snap_source.go @@ -0,0 +1,378 @@ +package snapsource + +import ( + "context" + "crypto" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/OneOfOne/xxhash" + diskFile "github.com/diskfs/go-diskfs/backend/file" + "github.com/diskfs/go-diskfs/filesystem" + "github.com/diskfs/go-diskfs/filesystem/squashfs" + "github.com/hashicorp/go-cleanhttp" + "github.com/opencontainers/go-digest" + "github.com/spf13/afero" + + "github.com/anchore/clio" + "github.com/anchore/go-homedir" + stereoFile "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/filetree" + "github.com/anchore/stereoscope/pkg/image" + "github.com/anchore/syft/internal/bus" + intFile "github.com/anchore/syft/internal/file" + "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/event/monitor" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/internal/fileresolver" + "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/source/internal" +) + +var _ source.Source = (*snapSource)(nil) + +type Config struct { + ID clio.Identification + + Request string + Platform *image.Platform + Exclude source.ExcludeConfig + DigestAlgorithms []crypto.Hash + Alias source.Alias + + fs afero.Fs +} + +type snapSource struct { + id artifact.ID + config Config + resolver file.Resolver + mutex *sync.Mutex + manifest snapManifest + digests []file.Digest + fs filesystem.FileSystem + squashfsPath string + squashFileCloser func() error + closer func() error +} + +func NewFromLocal(cfg Config) (source.Source, error) { + f, err := getLocalSnapFile(&cfg) + if err != nil { + return nil, err + } + return newFromPath(cfg, f) +} + +func getLocalSnapFile(cfg *Config) (*snapFile, error) { + expandedPath, err := homedir.Expand(cfg.Request) + if err != nil { + return nil, fmt.Errorf("unable to expand path %q: %w", cfg.Request, err) + } + cfg.Request = filepath.Clean(expandedPath) + + if cfg.fs == nil { + cfg.fs = afero.NewOsFs() + } + + if !fileExists(cfg.fs, cfg.Request) { + return nil, fmt.Errorf("snap file %q does not exist", cfg.Request) + } + + log.WithFields("path", cfg.Request).Debug("snap is a local file") + + return newSnapFromFile(context.Background(), cfg.fs, *cfg) +} + +func NewFromRemote(cfg Config) (source.Source, error) { + expandedPath, err := homedir.Expand(cfg.Request) + if err != nil { + return nil, fmt.Errorf("unable to expand path %q: %w", cfg.Request, err) + } + cfg.Request = filepath.Clean(expandedPath) + + if cfg.fs == nil { + cfg.fs = afero.NewOsFs() + } + + client := intFile.NewGetter(cfg.ID, cleanhttp.DefaultClient()) + f, err := getRemoteSnapFile(context.Background(), cfg.fs, client, cfg) + if err != nil { + return nil, err + } + + return newFromPath(cfg, f) +} + +func newFromPath(cfg Config, f *snapFile) (source.Source, error) { + s := &snapSource{ + id: deriveID(cfg.Request, cfg.Alias.Name, cfg.Alias.Version, f.Digests), + config: cfg, + mutex: &sync.Mutex{}, + digests: f.Digests, + squashfsPath: f.Path, + closer: f.Cleanup, + } + + return s, s.extractManifest() +} + +func (s *snapSource) extractManifest() error { + r, err := s.FileResolver(source.SquashedScope) + if err != nil { + return fmt.Errorf("unable to create snap file resolver: %w", err) + } + + manifest, err := parseManifest(r) + if err != nil { + return fmt.Errorf("unable to parse snap manifest file: %w", err) + } + + if manifest != nil { + s.manifest = *manifest + } + return nil +} + +func (s snapSource) ID() artifact.ID { + return s.id +} + +func (s snapSource) NameVersion() (string, string) { + name := s.manifest.Name + version := s.manifest.Version + if !s.config.Alias.IsEmpty() { + a := s.config.Alias + if a.Name != "" { + name = a.Name + } + + if a.Version != "" { + version = a.Version + } + } + return name, version +} + +func (s snapSource) Describe() source.Description { + name, version := s.NameVersion() + return source.Description{ + ID: string(s.id), + Name: name, + Version: version, + Metadata: source.SnapMetadata{ + Summary: s.manifest.Summary, + Base: s.manifest.Base, + Grade: s.manifest.Grade, + Confinement: s.manifest.Confinement, + Architectures: s.manifest.Architectures, + Digests: s.digests, + }, + } +} + +func (s *snapSource) Close() error { + if s.squashFileCloser != nil { + if err := s.squashFileCloser(); err != nil { + return fmt.Errorf("unable to close snap resolver: %w", err) + } + s.squashFileCloser = nil + } + s.resolver = nil + if s.fs != nil { + if err := s.fs.Close(); err != nil { + return fmt.Errorf("unable to close snap squashfs: %w", err) + } + } + if s.closer != nil { + if err := s.closer(); err != nil { + return fmt.Errorf("unable to close snap source: %w", err) + } + } + return nil +} + +func (s *snapSource) FileResolver(_ source.Scope) (file.Resolver, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.resolver != nil { + return s.resolver, nil + } + + log.Debugf("parsing squashfs file: %s", s.squashfsPath) + + f, err := os.Open(s.squashfsPath) + if err != nil { + return nil, fmt.Errorf("unable to open squashfs file: %w", err) + } + + s.squashFileCloser = func() error { + if err := f.Close(); err != nil { + return fmt.Errorf("unable to close squashfs file: %w", err) + } + return nil + } + + fileMeta, err := f.Stat() + if err != nil { + return nil, fmt.Errorf("unable to stat squashfs file: %w", err) + } + + size := fileMeta.Size() + + fileCatalog := image.NewFileCatalog() + + prog := bus.StartIndexingFiles(filepath.Base(s.squashfsPath)) + + b := diskFile.New(f, true) + fs, err := squashfs.Read(b, fileMeta.Size(), 0, 0) + if err != nil { + err := fmt.Errorf("unable to open squashfs file: %w", err) + prog.SetError(err) + return nil, err + } + + tree := filetree.New() + if err := intFile.WalkDiskDir(fs, "/", squashfsVisitor(tree, fileCatalog, &size, prog)); err != nil { + err := fmt.Errorf("failed to walk squashfs file=%q: %w", s.squashfsPath, err) + prog.SetError(err) + return nil, err + } + + prog.SetCompleted() + + s.resolver = &fileresolver.FiletreeResolver{ + Chroot: fileresolver.ChrootContext{}, + Tree: tree, + Index: fileCatalog.Index, + SearchContext: filetree.NewSearchContext(tree, fileCatalog.Index), + Opener: func(ref stereoFile.Reference) (io.ReadCloser, error) { + return fileCatalog.Open(ref) + }, + } + + s.fs = fs + + return s.resolver, nil +} + +type linker interface { + Readlink() (string, error) +} + +func squashfsVisitor(ft filetree.Writer, fileCatalog *image.FileCatalog, size *int64, prog *monitor.TaskProgress) intFile.WalkDiskDirFunc { + builder := filetree.NewBuilder(ft, fileCatalog.Index) + + return func(fsys filesystem.FileSystem, path string, d os.FileInfo, walkErr error) error { + if walkErr != nil { + log.WithFields("error", walkErr, "path", path).Trace("unable to walk squash file path") + return walkErr + } + + prog.AtomicStage.Set(path) + + var f filesystem.File + var mimeType string + var err error + + if !d.IsDir() { + f, err = fsys.OpenFile(path, os.O_RDONLY) + if err != nil { + log.WithFields("error", err, "path", path).Trace("unable to open squash file path") + } else { + defer f.Close() + mimeType = stereoFile.MIMEType(f) + } + } + + var ty stereoFile.Type + var linkPath string + switch { + case d.IsDir(): + // in some implementations, the mode does not indicate a directory, so we check the FileInfo type explicitly + ty = stereoFile.TypeDirectory + default: + ty = stereoFile.TypeFromMode(d.Mode()) + if ty == stereoFile.TypeSymLink && f != nil { + if l, ok := f.(linker); ok { + linkPath, _ = l.Readlink() + } + } + } + + metadata := stereoFile.Metadata{ + FileInfo: d, + Path: path, + LinkDestination: linkPath, + Type: ty, + MIMEType: mimeType, + } + + fileReference, err := builder.Add(metadata) + if err != nil { + return err + } + + if fileReference == nil { + return nil + } + + if size != nil { + *(size) += metadata.Size() + } + fileCatalog.AssociateOpener(*fileReference, func() (io.ReadCloser, error) { + return fsys.OpenFile(path, os.O_RDONLY) + }) + + prog.Increment() + return nil + } +} + +func isSquashFSFile(mimeType, path string) bool { + if mimeType == "application/vnd.squashfs" || mimeType == "application/x-squashfs" { + return true + } + + ext := filepath.Ext(path) + return ext == ".snap" || ext == ".squashfs" +} + +func deriveID(path, name, version string, digests []file.Digest) artifact.ID { + var xxhDigest string + for _, d := range digests { + if strings.ToLower(strings.ReplaceAll(d.Algorithm, "-", "")) == "xxh64" { + xxhDigest = d.Value + break + } + } + + if xxhDigest == "" { + xxhDigest = digestOfFileContents(path) + } + + info := fmt.Sprintf("%s:%s@%s", xxhDigest, name, version) + return internal.ArtifactIDFromDigest(digest.SHA256.FromString(info).String()) +} + +// return the xxhash64 of the file contents, or the xxhash64 of the path if the file cannot be read +func digestOfFileContents(path string) string { + f, err := os.Open(path) + if err != nil { + return digestOfReader(strings.NewReader(path)) + } + defer f.Close() + return digestOfReader(f) +} + +func digestOfReader(r io.Reader) string { + hasher := xxhash.New64() + _, _ = io.Copy(hasher, r) + return fmt.Sprintf("%x", hasher.Sum(nil)) +} diff --git a/syft/source/snapsource/snap_source_provider.go b/syft/source/snapsource/snap_source_provider.go new file mode 100644 index 00000000000..64ae1570912 --- /dev/null +++ b/syft/source/snapsource/snap_source_provider.go @@ -0,0 +1,54 @@ +package snapsource + +import ( + "context" + "crypto" + + "github.com/anchore/syft/syft/source" +) + +type snapSourceProvider struct { + local bool + path string + exclude source.ExcludeConfig + digestAlgorithms []crypto.Hash + alias source.Alias +} + +// NewLocalSourceProvider creates a new provider for snap files from a local path. +func NewLocalSourceProvider(path string, exclude source.ExcludeConfig, digestAlgorithms []crypto.Hash, alias source.Alias) source.Provider { + return &snapSourceProvider{ + local: true, + path: path, + exclude: exclude, + digestAlgorithms: digestAlgorithms, + alias: alias, + } +} + +// NewRemoteSourceProvider creates a new provider for snap files from a remote location. +func NewRemoteSourceProvider(path string, exclude source.ExcludeConfig, digestAlgorithms []crypto.Hash, alias source.Alias) source.Provider { + return &snapSourceProvider{ + path: path, + exclude: exclude, + digestAlgorithms: digestAlgorithms, + alias: alias, + } +} + +func (p snapSourceProvider) Name() string { + return "snap" +} + +func (p snapSourceProvider) Provide(_ context.Context) (source.Source, error) { + cfg := Config{ + Request: p.path, + Exclude: p.exclude, + DigestAlgorithms: p.digestAlgorithms, + Alias: p.alias, + } + if p.local { + return NewFromLocal(cfg) + } + return NewFromRemote(cfg) +} diff --git a/syft/source/snapsource/snap_source_test.go b/syft/source/snapsource/snap_source_test.go new file mode 100644 index 00000000000..f74f59e571e --- /dev/null +++ b/syft/source/snapsource/snap_source_test.go @@ -0,0 +1,86 @@ +package snapsource + +import ( + "crypto" + "fmt" + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/go-homedir" + "github.com/anchore/stereoscope/pkg/image" +) + +func TestNewFromLocal(t *testing.T) { + tests := []struct { + name string + cfg Config + setup func(fs afero.Fs) + wantRequest string + wantErr assert.ErrorAssertionFunc + }{ + { + name: "local file exists", + cfg: Config{ + Request: "/test/local.snap", + DigestAlgorithms: []crypto.Hash{crypto.SHA256}, + }, + setup: func(fs afero.Fs) { + require.NoError(t, createMockSquashfsFile(fs, "/test/local.snap")) + }, + wantRequest: "/test/local.snap", + }, + { + name: "resolve home dir exists", + cfg: Config{ + Request: "~/test/local.snap", + DigestAlgorithms: []crypto.Hash{crypto.SHA256}, + }, + wantErr: assert.Error, + wantRequest: func() string { + homeDir, err := homedir.Expand("~/test/local.snap") + require.NoError(t, err, "failed to expand home directory") + require.NotContains(t, homeDir, "~") + return homeDir + }(), + }, + { + name: "local file with architecture specified", + cfg: Config{ + Request: "/test/local.snap", + Platform: &image.Platform{ + Architecture: "arm64", + }, + }, + setup: func(fs afero.Fs) { + require.NoError(t, createMockSquashfsFile(fs, "/test/local.snap")) + }, + wantErr: func(t assert.TestingT, err error, msgAndArgs ...interface{}) bool { + return assert.Error(t, err, msgAndArgs...) && assert.Contains(t, err.Error(), "architecture cannot be specified for local snap files", msgAndArgs...) + }, + wantRequest: "/test/local.snap", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = assert.NoError + } + tt.cfg.fs = afero.NewMemMapFs() // Use an in-memory filesystem for testing + if tt.setup != nil { + tt.setup(tt.cfg.fs) + } + got, err := getLocalSnapFile(&tt.cfg) + tt.wantErr(t, err, fmt.Sprintf("NewFromLocal(%v)", tt.cfg)) + assert.Equal(t, tt.wantRequest, tt.cfg.Request, "expected request path to match") + if err != nil { + require.Nil(t, got, "expected nil source on error") + return + } + require.NotNil(t, got, "expected non-nil source on success") + + }) + } +} diff --git a/syft/source/snapsource/snap_test.go b/syft/source/snapsource/snap_test.go new file mode 100644 index 00000000000..24f01a42a18 --- /dev/null +++ b/syft/source/snapsource/snap_test.go @@ -0,0 +1,612 @@ +package snapsource + +import ( + "context" + "crypto" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/wagoodman/go-progress" + + "github.com/anchore/stereoscope/pkg/image" + "github.com/anchore/syft/internal/file" +) + +func TestSnapIdentity_String(t *testing.T) { + tests := []struct { + name string + identity snapIdentity + expected string + }{ + { + name: "name only", + identity: snapIdentity{ + Name: "etcd", + }, + expected: "etcd", + }, + { + name: "name with channel", + identity: snapIdentity{ + Name: "etcd", + Channel: "stable", + }, + expected: "etcd@stable", + }, + { + name: "name with architecture", + identity: snapIdentity{ + Name: "etcd", + Architecture: "amd64", + }, + expected: "etcd (amd64)", + }, + { + name: "name with channel and architecture", + identity: snapIdentity{ + Name: "etcd", + Channel: "beta", + Architecture: "arm64", + }, + expected: "etcd@beta (arm64)", + }, + { + name: "empty channel with architecture", + identity: snapIdentity{ + Name: "mysql", + Channel: "", + Architecture: "amd64", + }, + expected: "mysql (amd64)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.identity.String() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestFileExists(t *testing.T) { + fs := afero.NewMemMapFs() + + tests := []struct { + name string + setup func() string + expected bool + }{ + { + name: "file exists", + setup: func() string { + path := "/test/file.snap" + require.NoError(t, createMockSquashfsFile(fs, path)) + return path + }, + expected: true, + }, + { + name: "file does not exist", + setup: func() string { + return "/nonexistent/file.snap" + }, + expected: false, + }, + { + name: "path is directory", + setup: func() string { + path := "/test/dir" + require.NoError(t, fs.MkdirAll(path, 0755)) + return path + }, + expected: false, + }, + { + name: "file exists in subdirectory", + setup: func() string { + path := "/deep/nested/path/file.snap" + require.NoError(t, createMockSquashfsFile(fs, path)) + return path + }, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + path := tt.setup() + result := fileExists(fs, path) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestNewSnapFromFile(t *testing.T) { + ctx := context.Background() + fs := afero.NewMemMapFs() + + tests := []struct { + name string + cfg Config + setup func() string + expectError bool + errorMsg string + }{ + { + name: "valid local snap file", + cfg: Config{ + DigestAlgorithms: []crypto.Hash{crypto.SHA256}, + }, + setup: func() string { + path := "/test/valid.snap" + require.NoError(t, createMockSquashfsFile(fs, path)) + return path + }, + expectError: false, + }, + { + name: "architecture specified for local file", + cfg: Config{ + Platform: &image.Platform{ + Architecture: "arm64", + }, + }, + setup: func() string { + path := "/test/valid.snap" + require.NoError(t, createMockSquashfsFile(fs, path)) + return path + }, + expectError: true, + errorMsg: "architecture cannot be specified for local snap files", + }, + { + name: "file does not exist", + cfg: Config{}, + setup: func() string { + return "/nonexistent/file.snap" + }, + expectError: true, + errorMsg: "unable to stat path", + }, + { + name: "path is directory", + cfg: Config{}, + setup: func() string { + path := "/test/directory" + require.NoError(t, fs.MkdirAll(path, 0755)) + return path + }, + expectError: true, + errorMsg: "given path is a directory", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + path := tt.setup() + tt.cfg.Request = path + + result, err := newSnapFromFile(ctx, fs, tt.cfg) + + if tt.expectError { + assert.Error(t, err) + if tt.errorMsg != "" { + assert.Contains(t, err.Error(), tt.errorMsg) + } + assert.Nil(t, result) + } else { + assert.NoError(t, err) + assert.NotNil(t, result) + assert.Equal(t, path, result.Path) + assert.NotEmpty(t, result.MimeType) + assert.NotEmpty(t, result.Digests) + assert.Nil(t, result.Cleanup) // Local files don't have cleanup + } + }) + } +} + +func TestNewSnapFileFromRemote(t *testing.T) { + ctx := context.Background() + + tests := []struct { + name string + cfg Config + info *remoteSnap + setupMock func(*mockFileGetter, afero.Fs) + expectError bool + errorMsg string + validate func(t *testing.T, result *snapFile, fs afero.Fs) + }{ + { + name: "successful remote snap download", + cfg: Config{ + DigestAlgorithms: []crypto.Hash{crypto.SHA256}, + }, + info: &remoteSnap{ + snapIdentity: snapIdentity{ + Name: "etcd", + Channel: "stable", + Architecture: "amd64", + }, + URL: "https://api.snapcraft.io/download/etcd_123.snap", + }, + setupMock: func(mockGetter *mockFileGetter, fs afero.Fs) { + mockGetter.On("GetFile", mock.MatchedBy(func(dst string) bool { + // expect destination to end with etcd_123.snap + return filepath.Base(dst) == "etcd_123.snap" + }), "https://api.snapcraft.io/download/etcd_123.snap", mock.Anything).Run(func(args mock.Arguments) { + // simulate successful download by creating the file + dst := args.String(0) + require.NoError(t, createMockSquashfsFile(fs, dst)) + }).Return(nil) + }, + expectError: false, + validate: func(t *testing.T, result *snapFile, fs afero.Fs) { + assert.NotNil(t, result) + assert.Contains(t, result.Path, "etcd_123.snap") + assert.NotEmpty(t, result.MimeType) + assert.NotEmpty(t, result.Digests) + assert.NotNil(t, result.Cleanup) + + _, err := fs.Stat(result.Path) + assert.NoError(t, err) + + err = result.Cleanup() + require.NoError(t, err) + + _, err = fs.Stat(result.Path) + assert.True(t, os.IsNotExist(err)) + }, + }, + { + name: "successful download with no digest algorithms", + cfg: Config{ + DigestAlgorithms: []crypto.Hash{}, // no digests requested + }, + info: &remoteSnap{ + snapIdentity: snapIdentity{ + Name: "mysql", + Channel: "8.0/stable", + Architecture: "arm64", + }, + URL: "https://api.snapcraft.io/download/mysql_456.snap", + }, + setupMock: func(mockGetter *mockFileGetter, fs afero.Fs) { + mockGetter.On("GetFile", mock.MatchedBy(func(dst string) bool { + return filepath.Base(dst) == "mysql_456.snap" + }), "https://api.snapcraft.io/download/mysql_456.snap", mock.Anything).Run(func(args mock.Arguments) { + dst := args.String(0) + require.NoError(t, createMockSquashfsFile(fs, dst)) + }).Return(nil) + }, + expectError: false, + validate: func(t *testing.T, result *snapFile, fs afero.Fs) { + assert.NotNil(t, result) + assert.Contains(t, result.Path, "mysql_456.snap") + assert.NotEmpty(t, result.MimeType) + assert.Empty(t, result.Digests) // no digests requested + assert.NotNil(t, result.Cleanup) + }, + }, + { + name: "download fails", + cfg: Config{ + DigestAlgorithms: []crypto.Hash{crypto.SHA256}, + }, + info: &remoteSnap{ + snapIdentity: snapIdentity{ + Name: "failing-snap", + Channel: "stable", + Architecture: "amd64", + }, + URL: "https://api.snapcraft.io/download/failing_snap.snap", + }, + setupMock: func(mockGetter *mockFileGetter, fs afero.Fs) { + mockGetter.On("GetFile", mock.AnythingOfType("string"), "https://api.snapcraft.io/download/failing_snap.snap", mock.Anything).Return(fmt.Errorf("network timeout")) + }, + expectError: true, + errorMsg: "failed to download snap file", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fs := afero.NewOsFs() + mockGetter := &mockFileGetter{} + + if tt.setupMock != nil { + tt.setupMock(mockGetter, fs) + } + + result, err := newSnapFileFromRemote(ctx, fs, tt.cfg, mockGetter, tt.info) + + if tt.expectError { + require.Error(t, err) + if tt.errorMsg != "" { + assert.Contains(t, err.Error(), tt.errorMsg) + } + assert.Nil(t, result) + } else { + require.NoError(t, err) + if tt.validate != nil { + tt.validate(t, result, fs) + } + } + + mockGetter.AssertExpectations(t) + }) + } +} + +func TestGetSnapFileInfo(t *testing.T) { + ctx := context.Background() + fs := afero.NewMemMapFs() + + tests := []struct { + name string + setup func() string + hashes []crypto.Hash + expectError bool + errorMsg string + }{ + { + name: "valid squashfs file with hashes", + setup: func() string { + path := "/test/valid.snap" + require.NoError(t, createMockSquashfsFile(fs, path)) + return path + }, + hashes: []crypto.Hash{crypto.SHA256, crypto.MD5}, + expectError: false, + }, + { + name: "valid squashfs file without hashes", + setup: func() string { + path := "/test/valid.snap" + require.NoError(t, createMockSquashfsFile(fs, path)) + return path + }, + hashes: []crypto.Hash{}, + expectError: false, + }, + { + name: "file does not exist", + setup: func() string { + return "/nonexistent/file.snap" + }, + expectError: true, + errorMsg: "unable to stat path", + }, + { + name: "path is directory", + setup: func() string { + path := "/test/directory" + require.NoError(t, fs.MkdirAll(path, 0755)) + return path + }, + expectError: true, + errorMsg: "given path is a directory", + }, + { + name: "invalid file format", + setup: func() string { + path := "/test/invalid.txt" + require.NoError(t, fs.MkdirAll(filepath.Dir(path), 0755)) + file, err := fs.Create(path) + require.NoError(t, err) + defer file.Close() + _, err = file.Write([]byte("not a squashfs file")) + require.NoError(t, err) + return path + }, + expectError: true, + errorMsg: "not a valid squashfs/snap file", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + path := tt.setup() + + mimeType, digests, err := getSnapFileInfo(ctx, fs, path, tt.hashes) + + if tt.expectError { + assert.Error(t, err) + if tt.errorMsg != "" { + assert.Contains(t, err.Error(), tt.errorMsg) + } + } else { + assert.NoError(t, err) + assert.NotEmpty(t, mimeType) + if len(tt.hashes) > 0 { + assert.Len(t, digests, len(tt.hashes)) + } else { + assert.Empty(t, digests) + } + } + }) + } +} + +func TestDownloadSnap(t *testing.T) { + mockGetter := &mockFileGetter{} + + tests := []struct { + name string + info *remoteSnap + dest string + setupMock func() + expectError bool + errorMsg string + }{ + { + name: "successful download", + info: &remoteSnap{ + snapIdentity: snapIdentity{ + Name: "etcd", + Channel: "stable", + Architecture: "amd64", + }, + URL: "https://example.com/etcd.snap", + }, + dest: "/tmp/etcd.snap", + setupMock: func() { + mockGetter.On("GetFile", "/tmp/etcd.snap", "https://example.com/etcd.snap", mock.AnythingOfType("[]*progress.Manual")).Return(nil) + }, + expectError: false, + }, + { + name: "download fails", + info: &remoteSnap{ + snapIdentity: snapIdentity{ + Name: "etcd", + Channel: "stable", + Architecture: "amd64", + }, + URL: "https://example.com/etcd.snap", + }, + dest: "/tmp/etcd.snap", + setupMock: func() { + mockGetter.On("GetFile", "/tmp/etcd.snap", "https://example.com/etcd.snap", mock.AnythingOfType("[]*progress.Manual")).Return(fmt.Errorf("network error")) + }, + expectError: true, + errorMsg: "failed to download snap file", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // reset mock for each test + mockGetter.ExpectedCalls = nil + if tt.setupMock != nil { + tt.setupMock() + } + + err := downloadSnap(mockGetter, tt.info, tt.dest) + + if tt.expectError { + assert.Error(t, err) + if tt.errorMsg != "" { + assert.Contains(t, err.Error(), tt.errorMsg) + } + } else { + assert.NoError(t, err) + } + + mockGetter.AssertExpectations(t) + }) + } +} + +func TestParseSnapRequest(t *testing.T) { + tests := []struct { + name string + request string + expectedName string + expectedChannel string + }{ + { + name: "snap name only - uses default channel", + request: "etcd", + expectedName: "etcd", + expectedChannel: "stable", + }, + { + name: "snap with beta channel", + request: "etcd@beta", + expectedName: "etcd", + expectedChannel: "beta", + }, + { + name: "snap with edge channel", + request: "etcd@edge", + expectedName: "etcd", + expectedChannel: "edge", + }, + { + name: "snap with version track", + request: "etcd@2.3/stable", + expectedName: "etcd", + expectedChannel: "2.3/stable", + }, + { + name: "snap with complex channel path", + request: "mysql@8.0/candidate", + expectedName: "mysql", + expectedChannel: "8.0/candidate", + }, + { + name: "snap with multiple @ symbols - only first is delimiter", + request: "app@beta@test", + expectedName: "app", + expectedChannel: "beta@test", + }, + { + name: "empty snap name with channel", + request: "@stable", + expectedName: "", + expectedChannel: "stable", + }, + { + name: "snap name with empty channel - uses default", + request: "etcd@", + expectedName: "etcd", + expectedChannel: "stable", + }, + { + name: "hyphenated snap name", + request: "hello-world@stable", + expectedName: "hello-world", + expectedChannel: "stable", + }, + { + name: "snap name with numbers", + request: "app123", + expectedName: "app123", + expectedChannel: "stable", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + name, channel := parseSnapRequest(tt.request) + assert.Equal(t, tt.expectedName, name) + assert.Equal(t, tt.expectedChannel, channel) + }) + } +} + +type mockFileGetter struct { + mock.Mock + file.Getter +} + +func (m *mockFileGetter) GetFile(dst, src string, monitor ...*progress.Manual) error { + args := m.Called(dst, src, monitor) + return args.Error(0) +} + +func createMockSquashfsFile(fs afero.Fs, path string) error { + dir := filepath.Dir(path) + if err := fs.MkdirAll(dir, 0755); err != nil { + return err + } + + file, err := fs.Create(path) + if err != nil { + return err + } + defer file.Close() + + // write squashfs magic header + _, err = file.Write([]byte("hsqs")) + return err +} diff --git a/syft/source/snapsource/snapcraft_api.go b/syft/source/snapsource/snapcraft_api.go new file mode 100644 index 00000000000..8c00dcbcada --- /dev/null +++ b/syft/source/snapsource/snapcraft_api.go @@ -0,0 +1,159 @@ +package snapsource + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/anchore/syft/internal/log" +) + +const ( + defaultChannel = "stable" + defaultArchitecture = "amd64" + defaultSeries = "16" +) + +// snapcraftClient handles interactions with the Snapcraft API +type snapcraftClient struct { + InfoAPIURL string + FindAPIURL string + HTTPClient *http.Client +} + +// newSnapcraftClient creates a new Snapcraft API client with default settings +func newSnapcraftClient() *snapcraftClient { + return &snapcraftClient{ + InfoAPIURL: "https://api.snapcraft.io/v2/snaps/info/", + FindAPIURL: "https://api.snapcraft.io/v2/snaps/find", + HTTPClient: &http.Client{}, + } +} + +// snapcraftInfo represents the response from the snapcraft info API +type snapcraftInfo struct { + ChannelMap []snapChannelMapEntry `json:"channel-map"` +} + +type snapChannelMapEntry struct { + Channel snapChannel `json:"channel"` + Download snapDownload `json:"download"` +} +type snapChannel struct { + Architecture string `json:"architecture"` + Name string `json:"name"` +} + +type snapDownload struct { + URL string `json:"url"` +} + +// snapFindResponse represents the response from the snapcraft find API (search v2) +type snapFindResponse struct { + Results []struct { + Name string `json:"name"` + SnapID string `json:"snap-id"` + Snap struct{} `json:"snap"` + } `json:"results"` +} + +// GetSnapDownloadURL retrieves the download URL for a snap package +func (c *snapcraftClient) GetSnapDownloadURL(id snapIdentity) (string, error) { + apiURL := c.InfoAPIURL + id.Name + + log.WithFields("name", id.Name, "channel", id.Channel, "architecture", id.Architecture).Trace("requesting snap info") + + req, err := http.NewRequest(http.MethodGet, apiURL, nil) + if err != nil { + return "", fmt.Errorf("failed to create HTTP request: %w", err) + } + + req.Header.Set("Snap-Device-Series", defaultSeries) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return "", fmt.Errorf("failed to send HTTP request: %w", err) + } + defer resp.Body.Close() + + // handle 404 case - check if snap exists via find API + if resp.StatusCode == http.StatusNotFound { + log.WithFields("name", id.Name).Debug("snap info not found, checking if snap exists via find API") + + exists, snapID, findErr := c.CheckSnapExists(id.Name) + if findErr != nil { + return "", fmt.Errorf("failed to check if snap exists: %w", findErr) + } + + if exists { + return "", fmt.Errorf("found snap '%s' (id=%s) but it is unavailable for download", id.Name, snapID) + } + return "", fmt.Errorf("no snap found with name '%s'", id.Name) + } + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("API request failed with status code %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("failed to read response body: %w", err) + } + + var info snapcraftInfo + if err := json.Unmarshal(body, &info); err != nil { + return "", fmt.Errorf("failed to parse JSON response: %w", err) + } + + for _, cm := range info.ChannelMap { + if cm.Channel.Architecture == id.Architecture && cm.Channel.Name == id.Channel { + return cm.Download.URL, nil + } + } + + return "", fmt.Errorf("no matching snap found for %s", id.String()) +} + +// CheckSnapExists uses the find API (search v2) to check if a snap exists +func (c *snapcraftClient) CheckSnapExists(snapName string) (bool, string, error) { + req, err := http.NewRequest(http.MethodGet, c.FindAPIURL, nil) + if err != nil { + return false, "", fmt.Errorf("failed to create find request: %w", err) + } + + q := req.URL.Query() + q.Add("name-startswith", snapName) + req.URL.RawQuery = q.Encode() + + req.Header.Set("Snap-Device-Series", defaultSeries) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return false, "", fmt.Errorf("failed to send find request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return false, "", fmt.Errorf("find API request failed with status code %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return false, "", fmt.Errorf("failed to read find response body: %w", err) + } + + var findResp snapFindResponse + if err := json.Unmarshal(body, &findResp); err != nil { + return false, "", fmt.Errorf("failed to parse find JSON response: %w", err) + } + + // Look for exact name match + for _, result := range findResp.Results { + if result.Name == snapName { + return true, result.SnapID, nil + } + } + + return false, "", nil +} diff --git a/syft/source/snapsource/snapcraft_api_test.go b/syft/source/snapsource/snapcraft_api_test.go new file mode 100644 index 00000000000..c13516d1214 --- /dev/null +++ b/syft/source/snapsource/snapcraft_api_test.go @@ -0,0 +1,383 @@ +package snapsource + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSnapcraftClient_CheckSnapExists(t *testing.T) { + tests := []struct { + name string + snapName string + mockResponse snapFindResponse + statusCode int + expectedExists bool + expectedSnapID string + expectError require.ErrorAssertionFunc + errorContains string + }{ + { + name: "snap exists", + snapName: "jp-ledger", + statusCode: http.StatusOK, + mockResponse: snapFindResponse{ + Results: []struct { + Name string `json:"name"` + SnapID string `json:"snap-id"` + Snap struct{} `json:"snap"` + }{ + { + Name: "jp-ledger", + SnapID: "jyDlMmifyQhSWGPM9fnKc1HSD7E6c47e", + Snap: struct{}{}, + }, + }, + }, + expectedExists: true, + expectedSnapID: "jyDlMmifyQhSWGPM9fnKc1HSD7E6c47e", + expectError: require.NoError, + }, + { + name: "snap does not exist", + snapName: "nonexistent-snap", + statusCode: http.StatusOK, + mockResponse: snapFindResponse{ + Results: []struct { + Name string `json:"name"` + SnapID string `json:"snap-id"` + Snap struct{} `json:"snap"` + }{}, + }, + expectedExists: false, + expectedSnapID: "", + expectError: require.NoError, + }, + { + name: "multiple results - exact match found", + snapName: "test-snap", + statusCode: http.StatusOK, + mockResponse: snapFindResponse{ + Results: []struct { + Name string `json:"name"` + SnapID string `json:"snap-id"` + Snap struct{} `json:"snap"` + }{ + { + Name: "test-snap-extra", + SnapID: "wrong-id", + Snap: struct{}{}, + }, + { + Name: "test-snap", + SnapID: "correct-id", + Snap: struct{}{}, + }, + }, + }, + expectedExists: true, + expectedSnapID: "correct-id", + expectError: require.NoError, + }, + { + name: "find API returns 404", + snapName: "test", + statusCode: http.StatusNotFound, + expectError: require.Error, + errorContains: "find API request failed with status code 404", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + findServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, defaultSeries, r.Header.Get("Snap-Device-Series")) + assert.Equal(t, tt.snapName, r.URL.Query().Get("name-startswith")) + + w.WriteHeader(tt.statusCode) + if tt.statusCode == http.StatusOK { + responseBytes, err := json.Marshal(tt.mockResponse) + require.NoError(t, err) + w.Write(responseBytes) + } + })) + defer findServer.Close() + + client := &snapcraftClient{ + FindAPIURL: findServer.URL, + HTTPClient: &http.Client{}, + } + + exists, snapID, err := client.CheckSnapExists(tt.snapName) + tt.expectError(t, err) + if err != nil && tt.errorContains != "" { + assert.Contains(t, err.Error(), tt.errorContains) + return + } + + assert.Equal(t, tt.expectedExists, exists) + assert.Equal(t, tt.expectedSnapID, snapID) + }) + } +} + +func TestSnapcraftClient_GetSnapDownloadURL(t *testing.T) { + tests := []struct { + name string + snapID snapIdentity + infoResponse snapcraftInfo + infoStatusCode int + findResponse *snapFindResponse + findStatusCode int + expectedURL string + expectError require.ErrorAssertionFunc + errorContains string + }{ + { + name: "successful download URL retrieval", + snapID: snapIdentity{ + Name: "etcd", + Channel: "stable", + Architecture: "amd64", + }, + infoStatusCode: http.StatusOK, + infoResponse: snapcraftInfo{ + ChannelMap: []snapChannelMapEntry{ + { + Channel: snapChannel{ + Architecture: "amd64", + Name: "stable", + }, + Download: snapDownload{ + URL: "https://api.snapcraft.io/api/v1/snaps/download/etcd_123.snap", + }, + }, + }, + }, + expectedURL: "https://api.snapcraft.io/api/v1/snaps/download/etcd_123.snap", + expectError: require.NoError, + }, + { + name: "region-locked snap - exists but unavailable", + snapID: snapIdentity{ + Name: "jp-ledger", + Channel: "stable", + Architecture: "amd64", + }, + infoStatusCode: http.StatusNotFound, + findStatusCode: http.StatusOK, + findResponse: &snapFindResponse{ + Results: []struct { + Name string `json:"name"` + SnapID string `json:"snap-id"` + Snap struct{} `json:"snap"` + }{ + { + Name: "jp-ledger", + SnapID: "jyDlMmifyQhSWGPM9fnKc1HSD7E6c47e", + Snap: struct{}{}, + }, + }, + }, + expectError: require.Error, + errorContains: "found snap 'jp-ledger' (id=jyDlMmifyQhSWGPM9fnKc1HSD7E6c47e) but it is unavailable for download", + }, + { + name: "snap truly does not exist", + snapID: snapIdentity{ + Name: "nonexistent", + Channel: "stable", + Architecture: "amd64", + }, + infoStatusCode: http.StatusNotFound, + findStatusCode: http.StatusOK, + findResponse: &snapFindResponse{ + Results: []struct { + Name string `json:"name"` + SnapID string `json:"snap-id"` + Snap struct{} `json:"snap"` + }{}, + }, + expectError: require.Error, + errorContains: "no snap found with name 'nonexistent'", + }, + { + name: "multiple architectures - find correct one", + snapID: snapIdentity{ + Name: "mysql", + Channel: "stable", + Architecture: "arm64", + }, + infoStatusCode: http.StatusOK, + infoResponse: snapcraftInfo{ + ChannelMap: []snapChannelMapEntry{ + { + Channel: snapChannel{ + Architecture: "amd64", + Name: "stable", + }, + Download: snapDownload{ + URL: "https://api.snapcraft.io/api/v1/snaps/download/mysql_amd64.snap", + }, + }, + { + Channel: snapChannel{ + Architecture: "arm64", + Name: "stable", + }, + Download: snapDownload{ + URL: "https://api.snapcraft.io/api/v1/snaps/download/mysql_arm64.snap", + }, + }, + }, + }, + expectedURL: "https://api.snapcraft.io/api/v1/snaps/download/mysql_arm64.snap", + expectError: require.NoError, + }, + { + name: "snap not found - no matching architecture", + snapID: snapIdentity{ + Name: "etcd", + Channel: "stable", + Architecture: "s390x", + }, + infoStatusCode: http.StatusOK, + infoResponse: snapcraftInfo{ + ChannelMap: []snapChannelMapEntry{ + { + Channel: snapChannel{ + Architecture: "amd64", + Name: "stable", + }, + Download: snapDownload{ + URL: "https://api.snapcraft.io/api/v1/snaps/download/etcd_123.snap", + }, + }, + }, + }, + expectError: require.Error, + errorContains: "no matching snap found", + }, + { + name: "API returns 500", + snapID: snapIdentity{ + Name: "etcd", + Channel: "stable", + Architecture: "amd64", + }, + infoStatusCode: http.StatusInternalServerError, + expectError: require.Error, + errorContains: "API request failed with status code 500", + }, + { + name: "find API fails when checking 404", + snapID: snapIdentity{ + Name: "test-snap", + Channel: "stable", + Architecture: "amd64", + }, + infoStatusCode: http.StatusNotFound, + findStatusCode: http.StatusInternalServerError, + expectError: require.Error, + errorContains: "failed to check if snap exists", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.expectError == nil { + tt.expectError = require.NoError + } + + infoServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, defaultSeries, r.Header.Get("Snap-Device-Series")) + + expectedPath := "/" + tt.snapID.Name + assert.Equal(t, expectedPath, r.URL.Path) + + w.WriteHeader(tt.infoStatusCode) + + if tt.infoStatusCode == http.StatusOK { + responseBytes, err := json.Marshal(tt.infoResponse) + require.NoError(t, err) + w.Write(responseBytes) + } + })) + defer infoServer.Close() + + var findServer *httptest.Server + if tt.findResponse != nil || tt.findStatusCode != 0 { + findServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, defaultSeries, r.Header.Get("Snap-Device-Series")) + assert.Equal(t, tt.snapID.Name, r.URL.Query().Get("name-startswith")) + + statusCode := tt.findStatusCode + if statusCode == 0 { + statusCode = http.StatusOK + } + w.WriteHeader(statusCode) + + if tt.findResponse != nil && statusCode == http.StatusOK { + responseBytes, err := json.Marshal(tt.findResponse) + require.NoError(t, err) + w.Write(responseBytes) + } + })) + defer findServer.Close() + } + + client := &snapcraftClient{ + InfoAPIURL: infoServer.URL + "/", + HTTPClient: &http.Client{}, + } + if findServer != nil { + client.FindAPIURL = findServer.URL + } + + url, err := client.GetSnapDownloadURL(tt.snapID) + tt.expectError(t, err) + if err != nil { + if tt.errorContains != "" { + assert.Contains(t, err.Error(), tt.errorContains) + } + return + } + assert.Equal(t, tt.expectedURL, url) + }) + } +} + +func TestSnapcraftClient_GetSnapDownloadURL_InvalidJSON(t *testing.T) { + infoServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("invalid json")) + })) + defer infoServer.Close() + + client := &snapcraftClient{ + InfoAPIURL: infoServer.URL + "/", + HTTPClient: &http.Client{}, + } + + snapID := snapIdentity{ + Name: "etcd", + Channel: "stable", + Architecture: "amd64", + } + + _, err := client.GetSnapDownloadURL(snapID) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse JSON response") +} + +func TestNewSnapcraftClient(t *testing.T) { + client := newSnapcraftClient() + + assert.Equal(t, "https://api.snapcraft.io/v2/snaps/info/", client.InfoAPIURL) + assert.Equal(t, "https://api.snapcraft.io/v2/snaps/find", client.FindAPIURL) + assert.NotNil(t, client.HTTPClient) +} diff --git a/syft/source/sourceproviders/source_providers.go b/syft/source/sourceproviders/source_providers.go index 107f3ad4825..6da749bc585 100644 --- a/syft/source/sourceproviders/source_providers.go +++ b/syft/source/sourceproviders/source_providers.go @@ -7,6 +7,7 @@ import ( "github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source/directorysource" "github.com/anchore/syft/syft/source/filesource" + "github.com/anchore/syft/syft/source/snapsource" "github.com/anchore/syft/syft/source/stereoscopesource" ) @@ -14,6 +15,7 @@ const ( FileTag = stereoscope.FileTag DirTag = stereoscope.DirTag PullTag = stereoscope.PullTag + SnapTag = "snap" ) // All returns all the configured source providers known to syft @@ -24,13 +26,25 @@ func All(userInput string, cfg *Config) []collections.TaggedValue[source.Provide stereoscopeProviders := stereoscopeSourceProviders(userInput, cfg) return collections.TaggedValueSet[source.Provider]{}. + // 1. try all specific, local sources first... + // --from file, dir, oci-archive, etc. Join(stereoscopeProviders.Select(FileTag, DirTag)...). + + // --from snap (local only) + Join(tagProvider(snapsource.NewLocalSourceProvider(userInput, cfg.Exclude, cfg.DigestAlgorithms, cfg.Alias), SnapTag)). + + // 2. try unspecific, local sources after other local sources last... Join(tagProvider(filesource.NewSourceProvider(userInput, cfg.Exclude, cfg.DigestAlgorithms, cfg.Alias), FileTag)). Join(tagProvider(directorysource.NewSourceProvider(userInput, cfg.Exclude, cfg.Alias, cfg.BasePath), DirTag)). + // 3. try remote sources after everything else... + // --from docker, registry, etc. - Join(stereoscopeProviders.Select(PullTag)...) + Join(stereoscopeProviders.Select(PullTag)...). + + // --from snap (remote only) + Join(tagProvider(snapsource.NewRemoteSourceProvider(userInput, cfg.Exclude, cfg.DigestAlgorithms, cfg.Alias), SnapTag)) } func stereoscopeSourceProviders(userInput string, cfg *Config) collections.TaggedValueSet[source.Provider] {