diff --git a/detect.go b/detect.go index 2cca130e0..7ea314354 100644 --- a/detect.go +++ b/detect.go @@ -31,6 +31,7 @@ func init() { new(BitBucketDetector), new(S3Detector), new(GCSDetector), + new(OSSDetector), new(FileDetector), } } diff --git a/detect_oss.go b/detect_oss.go new file mode 100644 index 000000000..b0e5bd82c --- /dev/null +++ b/detect_oss.go @@ -0,0 +1,39 @@ +package getter + +import ( + "fmt" + "net/url" + "strings" +) + +// OSSDetector implements Detector to detect OSS URLs and turn +// them into URLs that the OSSGetter can understand. +type OSSDetector struct{} + +func (d *OSSDetector) Detect(src, _ string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + if strings.Contains(src, ".aliyuncs.com/") { + return d.detectHTTP(src) + } + + return "", false, nil +} + +func (d *OSSDetector) detectHTTP(src string) (string, bool, error) { + parts := strings.Split(src, "/") + if len(parts) < 2 { + return "", false, fmt.Errorf( + "URL is not a valid OSS URL") + } + + urlStr := fmt.Sprintf("https://%s", strings.Join(parts, "/")) + url, err := url.Parse(urlStr) + if err != nil { + return "", true, fmt.Errorf("error parsing OSS URL: %s", err) + } + + return "oss::" + url.String(), true, nil +} diff --git a/detect_oss_test.go b/detect_oss_test.go new file mode 100644 index 000000000..39945f8a6 --- /dev/null +++ b/detect_oss_test.go @@ -0,0 +1,41 @@ +package getter + +import ( + "testing" +) + +func TestOSSDetector(t *testing.T) { + cases := []struct { + Input string + Output string + }{ + { + "go-getter.oss-ap-southeast-1.aliyuncs.com/foo", + "oss::https://go-getter.oss-ap-southeast-1.aliyuncs.com/foo", + }, + { + "go-getter.oss-ap-southeast-1.aliyuncs.com/foo/bar", + "oss::https://go-getter.oss-ap-southeast-1.aliyuncs.com/foo/bar", + }, + { + "go-getter.oss-ap-southeast-1.aliyuncs.com/foo/bar.baz", + "oss::https://go-getter.oss-ap-southeast-1.aliyuncs.com/foo/bar.baz", + }, + } + + pwd := "/pwd" + f := new(OSSDetector) + for i, tc := range cases { + output, ok, err := f.Detect(tc.Input, pwd) + if err != nil { + t.Fatalf("err: %s", err) + } + if !ok { + t.Fatal("not ok") + } + + if output != tc.Output { + t.Fatalf("%d: bad: %#v", i, output) + } + } +} diff --git a/get.go b/get.go index 4fbcfbff7..d9a45a5af 100644 --- a/get.go +++ b/get.go @@ -73,6 +73,7 @@ func init() { "gcs": new(GCSGetter), "hg": new(HgGetter), "s3": new(S3Getter), + "oss": new(OSSGetter), "http": httpGetter, "https": httpGetter, } diff --git a/get_oss.go b/get_oss.go new file mode 100644 index 000000000..f29a43271 --- /dev/null +++ b/get_oss.go @@ -0,0 +1,268 @@ +package getter + +import ( + "context" + "fmt" + "net/url" + "os" + "path/filepath" + "strings" + "time" + + "github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss" + "github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials" + + openapicred "github.com/aliyun/credentials-go/credentials" +) + +// OSSGetter is a Getter implementation that will download a module from +// an OSS bucket. +type OSSGetter struct { + getter + + // Timeout sets a deadline which all OSS operations should + // complete within. + // + // The zero value means timeout. + Timeout time.Duration +} + +func (g *OSSGetter) ClientMode(u *url.URL) (ClientMode, error) { + // Parse URL + ctx := g.Context() + + if g.Timeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, g.Timeout) + defer cancel() + } + + region, bucket, path, _, err := g.parseUrl(u) + if err != nil { + return 0, err + } + + // Create client config + client, err := g.newOSSClient(region, u) + if err != nil { + return 0, err + } + + // List the object(s) at the given prefix + request := &oss.ListObjectsV2Request{ + Bucket: oss.Ptr(bucket), + Prefix: oss.Ptr(path), + } + + p := client.NewListObjectsV2Paginator(request) + + var i int + for p.HasNext() { + i++ + page, err := p.NextPage(ctx) + if err != nil { + return 0, err + } + for _, obj := range page.Contents { + // Use file mode on exact match. + if *obj.Key == path { + return ClientModeFile, nil + } + + // Use dir mode if child keys are found. + if strings.HasPrefix(*obj.Key, path+"/") { + return ClientModeDir, nil + } + } + } + + // There was no match, so just return file mode. The download is going + // to fail but we will let OSS return the proper error later. + return ClientModeFile, nil +} + +func (g *OSSGetter) Get(dst string, u *url.URL) error { + ctx := g.Context() + + if g.Timeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, g.Timeout) + defer cancel() + } + + region, bucket, path, version, err := g.parseUrl(u) + if err != nil { + return err + } + + // Remove destination if it already exists + _, err = os.Stat(dst) + if err != nil && !os.IsNotExist(err) { + return err + } + + if err == nil { + // Remove the destination + if err := os.RemoveAll(dst); err != nil { + return err + } + } + + // Create all the parent directories + if err := os.MkdirAll(filepath.Dir(dst), g.client.mode(0755)); err != nil { + return err + } + + // Create client config + client, err := g.newOSSClient(region, u) + if err != nil { + return err + } + + // List files in path, keep listing until no more objects are found + request := &oss.ListObjectsV2Request{ + Bucket: oss.Ptr(bucket), + Prefix: oss.Ptr(path), + } + + p := client.NewListObjectsV2Paginator(request) + + var i int + for p.HasNext() { + i++ + page, err := p.NextPage(ctx) + if err != nil { + return err + } + for _, obj := range page.Contents { + objPath := *obj.Key + + // If the key ends with a backslash assume it is a directory and ignore + if strings.HasSuffix(objPath, "/") { + continue + } + + // Get the object destination path + objDst, err := filepath.Rel(path, objPath) + if err != nil { + return err + } + objDst = filepath.Join(dst, objDst) + + if err := g.getObject(ctx, client, objDst, bucket, objPath, version); err != nil { + return err + } + + } + } + + return nil +} + +func (g *OSSGetter) GetFile(dst string, u *url.URL) error { + ctx := g.Context() + + if g.Timeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, g.Timeout) + defer cancel() + } + + region, bucket, path, version, err := g.parseUrl(u) + if err != nil { + return err + } + + client, err := g.newOSSClient(region, u) + if err != nil { + return err + } + + return g.getObject(ctx, client, dst, bucket, path, version) +} + +func (g *OSSGetter) getObject(ctx context.Context, client *oss.Client, dst, bucket, object, version string) error { + request := &oss.GetObjectRequest{ + Bucket: oss.Ptr(bucket), + Key: oss.Ptr(object), + } + + if version != "" { + request.VersionId = oss.Ptr(version) + } + + result, err := client.GetObject(ctx, request) + if err != nil { + return err + } + + // Create all the parent directories + if err := os.MkdirAll(filepath.Dir(dst), g.client.mode(0755)); err != nil { + return err + } + + body := result.Body + + // There is no limit set for the size of an object from OSS + return copyReader(dst, body, 0666, g.client.umask(), 0) +} + +func (g *OSSGetter) parseUrl(u *url.URL) (region, bucket, path, version string, err error) { + if strings.Contains(u.Host, "aliyuncs.com") { + hostParts := strings.Split(u.Host, ".") + + switch len(hostParts) { + // path-style + case 4: + bucket = hostParts[0] + region = strings.TrimPrefix(hostParts[1], "oss-") + region = strings.TrimSuffix(region, "-internal") + + case 5: + bucket = hostParts[0] + region = hostParts[1] + } + + pathParts := strings.SplitN(u.Path, "/", 2) + if len(pathParts) != 2 { + err = fmt.Errorf("URL is not a valid OSS URL") + return + } + path = pathParts[1] + + if len(hostParts) < 4 || len(hostParts) > 5 { + err = fmt.Errorf("URL is not a valid OSS URL") + return + } + + version = u.Query().Get("version") + } + return +} + +func (g *OSSGetter) newOSSClient(region string, url *url.URL) (*oss.Client, error) { + + arnCredential, gerr := openapicred.NewCredential(nil) + provider := credentials.CredentialsProviderFunc(func(ctx context.Context) (credentials.Credentials, error) { + if gerr != nil { + return credentials.Credentials{}, gerr + } + cred, err := arnCredential.GetCredential() + if err != nil { + return credentials.Credentials{}, err + } + return credentials.Credentials{ + AccessKeyID: *cred.AccessKeyId, + AccessKeySecret: *cred.AccessKeySecret, + SecurityToken: *cred.SecurityToken, + }, nil + }) + + cfg := oss.LoadDefaultConfig(). + WithCredentialsProvider(provider). + WithRegion(region) + + client := oss.NewClient(cfg) + + return client, nil +} diff --git a/get_oss_test.go b/get_oss_test.go new file mode 100644 index 000000000..a164dcd3e --- /dev/null +++ b/get_oss_test.go @@ -0,0 +1,238 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package getter + +import ( + "net/url" + "os" + "path/filepath" + "testing" +) + +// Note for external contributors: In order to run the OSS test suite, you will only be able to be run +// in GitHub Actions when you open a PR. + +func TestOSSGetter_impl(t *testing.T) { + var _ Getter = new(OSSGetter) +} + +func TestOSSGetter(t *testing.T) { + g := new(OSSGetter) + dst := tempDir(t) + + // With a dir exists + err := g.Get( + dst, testURL("https://hc-go-getter-test.oss-ap-southeast-1.aliyuncs.com/go-getter/folder")) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Verify the main file exists + mainPath := filepath.Join(dst, "main.tf") + if _, err := os.Stat(mainPath); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestOSSGetter_subdir(t *testing.T) { + g := new(OSSGetter) + dst := tempDir(t) + + // With a dir exists + err := g.Get( + dst, testURL("https://hc-go-getter-test.oss-ap-southeast-1.aliyuncs.com/go-getter/folder/subfolder")) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Verify the main file exists + subPath := filepath.Join(dst, "sub.tf") + if _, err := os.Stat(subPath); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestOSSGetter_GetFile(t *testing.T) { + g := new(OSSGetter) + dst := tempTestFile(t) + defer os.RemoveAll(filepath.Dir(dst)) + + // Download + err := g.GetFile( + dst, testURL("https://hc-go-getter-test.oss-ap-southeast-1.aliyuncs.com/go-getter/folder/main.tf")) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Verify the main file exists + if _, err := os.Stat(dst); err != nil { + t.Fatalf("err: %s", err) + } + assertContents(t, dst, "# Main\n") +} + +func TestOSSGetter_GetFile_notfound(t *testing.T) { + g := new(OSSGetter) + dst := tempTestFile(t) + defer os.RemoveAll(filepath.Dir(dst)) + + // Download + err := g.GetFile( + dst, testURL("https://hc-go-getter-test.oss-ap-southeast-1.aliyuncs.com/go-getter/folder/404.tf")) + if err == nil { + t.Fatalf("expected error, got none") + } +} + +func TestOSSGetter_ClientMode_dir(t *testing.T) { + g := new(OSSGetter) + + // Check client mode on a key prefix with only a single key. + mode, err := g.ClientMode( + testURL("https://hc-go-getter-test.oss-ap-southeast-1.aliyuncs.com/go-getter/folder")) + if err != nil { + t.Fatalf("err: %s", err) + } + if mode != ClientModeDir { + t.Fatal("expect ClientModeDir") + } +} + +func TestOSSGetter_ClientMode_file(t *testing.T) { + g := new(OSSGetter) + + // Check client mode on a key prefix which contains sub-keys. + mode, err := g.ClientMode( + testURL("https://hc-go-getter-test.oss-ap-southeast-1.aliyuncs.com/go-getter/folder/main.tf")) + if err != nil { + t.Fatalf("err: %s", err) + } + if mode != ClientModeFile { + t.Fatal("expect ClientModeFile") + } +} + +func TestOSSGetter_ClientMode_notfound(t *testing.T) { + g := new(OSSGetter) + + // Check the client mode when a non-existent key is looked up. This does not + // return an error, but rather should just return the file mode so that OSS + // can return an appropriate error later on. This also checks that the + // prefix is handled properly (e.g., "/fold" and "/folder" don't put the + // client mode into "dir". + mode, err := g.ClientMode( + testURL("https://hc-go-getter-test.oss-ap-southeast-1.aliyuncs.com/go-getter/fold")) + if err != nil { + t.Fatalf("err: %s", err) + } + if mode != ClientModeFile { + t.Fatal("expect ClientModeFile") + } +} + +func TestOSSGetter_ClientMode_collision(t *testing.T) { + g := new(OSSGetter) + + // Check that the client mode is "file" if there is both an object and a + // folder with a common prefix (i.e., a "collision" in the namespace). + mode, err := g.ClientMode( + testURL("https://hc-go-getter-test.oss-ap-southeast-1.aliyuncs.com/go-getter/collision/foo")) + if err != nil { + t.Fatalf("err: %s", err) + } + if mode != ClientModeFile { + t.Fatal("expect ClientModeFile") + } +} + +func TestOSSGetter_Url(t *testing.T) { + var OSStests = []struct { + name string + url string + region string + bucket string + path string + version string + expectedErr string + }{ + { + name: "OSSVhostDash", + url: "oss::https://bucket.oss-ap-southeast-1.aliyuncs.com/foo/bar.baz", + region: "ap-southeast-1", + bucket: "bucket", + path: "foo/bar.baz", + version: "", + }, + { + name: "OSSVhostDash", + url: "oss::https://bucket.oss-cn-hangzhou-internal.aliyuncs.com/foo/bar.baz", + region: "cn-hangzhou", + bucket: "bucket", + path: "foo/bar.baz", + version: "", + }, + { + name: "OSSVhostIPv6", + url: "oss::https://bucket.cn-hangzhou.oss.aliyuncs.com/foo/bar.baz", + region: "cn-hangzhou", + bucket: "bucket", + path: "foo/bar.baz", + version: "", + }, + { + name: "OSSv1234", + url: "oss::https://bucket.oss-ap-southeast-1.aliyuncs.com/foo/bar.baz?version=1234", + region: "ap-southeast-1", + bucket: "bucket", + path: "foo/bar.baz", + version: "1234", + }, + { + name: "malformed OSS url", + url: "oss::https://bucket-ap-southeast-1.aliyuncs.com/foo/bar.baz?version=1234", + expectedErr: "URL is not a valid OSS URL", + }, + } + + for i, pt := range OSStests { + t.Run(pt.name, func(t *testing.T) { + g := new(OSSGetter) + forced, src := getForcedGetter(pt.url) + u, err := url.Parse(src) + + if err != nil { + t.Errorf("test %d: unexpected error: %s", i, err) + } + if forced != "OSS" { + t.Fatalf("expected forced protocol to be OSS") + } + + region, bucket, path, version, err := g.parseUrl(u) + + if err != nil { + if pt.expectedErr == "" { + t.Fatalf("err: %s", err) + } + if err.Error() != pt.expectedErr { + t.Fatalf("expected %s, got %s", pt.expectedErr, err.Error()) + } + return + } else if pt.expectedErr != "" { + t.Fatalf("expected error, got none") + } + if region != pt.region { + t.Fatalf("expected %s, got %s", pt.region, region) + } + if bucket != pt.bucket { + t.Fatalf("expected %s, got %s", pt.bucket, bucket) + } + if path != pt.path { + t.Fatalf("expected %s, got %s", pt.path, path) + } + if version != pt.version { + t.Fatalf("expected %s, got %s", pt.version, version) + } + }) + } +} diff --git a/go.mod b/go.mod index 69f941f20..cf832a24d 100644 --- a/go.mod +++ b/go.mod @@ -82,4 +82,18 @@ require ( gopkg.in/cheggaaa/pb.v1 v1.0.27 // indirect ) +require ( + github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.2.3 + github.com/aliyun/credentials-go v1.4.7 +) + +require ( + github.com/alibabacloud-go/debug v1.0.1 // indirect + github.com/alibabacloud-go/tea v1.2.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect +) + go 1.24.6 diff --git a/go.sum b/go.sum index 127f8349d..6d12f09dd 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,15 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= +github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg= +github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU= +github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= +github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.2.3 h1:LyeTJauAchnWdre3sAyterGrzaAtZ4dSNoIvDvaWfo4= +github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.2.3/go.mod h1:FTzydeQVmR24FI0D6XWUOMKckjXehM/jgMn1xC+DA9M= +github.com/aliyun/credentials-go v1.4.7 h1:T17dLqEtPUFvjDRRb5giVvLh6dFT8IcNFJJb7MeyCxw= +github.com/aliyun/credentials-go v1.4.7/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4= github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= @@ -74,6 +83,8 @@ github.com/cheggaaa/pb v1.0.27 h1:wIkZHkNfC7R6GI5w7l/PdAdzXzlrbcI3p8OAlnkTsnc= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= @@ -99,6 +110,7 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= @@ -115,8 +127,12 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -125,16 +141,26 @@ github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +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= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= @@ -155,20 +181,58 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.258.0 h1:IKo1j5FBlN74fe5isA2PVozN3Y5pwNKriEgAXPOkDAc= @@ -183,7 +247,13 @@ google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.27 h1:kJdccidYzt3CaHD1crCFTS1hxyhSi059NhOFUf03YFo= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=