diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 18e653c8..2ec1f751 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -4,7 +4,7 @@ steps: command: .buildkite/steps/lint.sh plugins: - docker#v5.9.0: - image: "golangci/golangci-lint:v2.0-alpine" + image: "golangci/golangci-lint:v2.8-alpine" - name: ":go::test_tube: Test" key: test @@ -12,7 +12,7 @@ steps: artifact_paths: junit-*.xml plugins: - docker#v5.9.0: - image: "golang:1.24" + image: "golang:1.25" propagate-environment: true - artifacts#v1.9.0: upload: "cover.{html,out}" diff --git a/buildkite.go b/buildkite.go index 0862b410..ec67532f 100644 --- a/buildkite.go +++ b/buildkite.go @@ -48,6 +48,7 @@ type Client struct { Clusters *ClustersService ClusterQueues *ClusterQueuesService ClusterTokens *ClusterTokensService + ClusterSecrets *ClusterSecretsService FlakyTests *FlakyTestsService Jobs *JobsService Organizations *OrganizationsService @@ -164,6 +165,7 @@ func (c *Client) populateDefaultServices() { c.Clusters = &ClustersService{c} c.ClusterQueues = &ClusterQueuesService{c} c.ClusterTokens = &ClusterTokensService{c} + c.ClusterSecrets = &ClusterSecretsService{c} c.FlakyTests = &FlakyTestsService{c} c.Jobs = &JobsService{c} c.Organizations = &OrganizationsService{c} diff --git a/cluster_secrets.go b/cluster_secrets.go new file mode 100644 index 00000000..9e151ddf --- /dev/null +++ b/cluster_secrets.go @@ -0,0 +1,139 @@ +package buildkite + +import ( + "context" + "fmt" +) + +// ClusterSecretsService handles communication with cluster secret related +// methods of the Buildkite API. +// +// Buildkite API docs: https://buildkite.com/docs/apis/rest-api/clusters#cluster-secrets +type ClusterSecretsService struct { + client *Client +} + +type ClusterSecret struct { + ID string `json:"id,omitempty"` + GraphQLID string `json:"graphql_id,omitempty"` + Key string `json:"key,omitempty"` + Description string `json:"description,omitempty"` + Policy string `json:"policy,omitempty"` + URL string `json:"url,omitempty"` + ClusterURL string `json:"cluster_url,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + CreatedBy ClusterCreator `json:"created_by"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + UpdatedBy ClusterCreator `json:"updated_by"` + LastReadAt *Timestamp `json:"last_read_at,omitempty"` + Organization string `json:"organization,omitempty"` +} + +type ClusterSecretCreate struct { + Key string `json:"key"` + Value string `json:"value,omitempty"` + Description string `json:"description,omitempty"` + Policy string `json:"policy,omitempty"` +} + +type ClusterSecretUpdate struct { + Description string `json:"description,omitempty"` + Policy string `json:"policy,omitempty"` +} + +type ClusterSecretValueUpdate struct { + Value string `json:"value"` +} + +type ClusterSecretsListOptions struct { + ListOptions +} + +func (css *ClusterSecretsService) List(ctx context.Context, org, clusterID string, opt *ClusterSecretsListOptions) ([]ClusterSecret, *Response, error) { + u := fmt.Sprintf("v2/organizations/%s/clusters/%s/secrets", org, clusterID) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := css.client.NewRequest(ctx, "GET", u, nil) + if err != nil { + return nil, nil, err + } + + var secrets []ClusterSecret + resp, err := css.client.Do(req, &secrets) + if err != nil { + return nil, resp, err + } + + return secrets, resp, err +} + +func (css *ClusterSecretsService) Get(ctx context.Context, org, clusterID, secretID string) (ClusterSecret, *Response, error) { + u := fmt.Sprintf("v2/organizations/%s/clusters/%s/secrets/%s", org, clusterID, secretID) + req, err := css.client.NewRequest(ctx, "GET", u, nil) + if err != nil { + return ClusterSecret{}, nil, err + } + + var secret ClusterSecret + resp, err := css.client.Do(req, &secret) + if err != nil { + return ClusterSecret{}, resp, err + } + + return secret, resp, err +} + +func (css *ClusterSecretsService) Create(ctx context.Context, org, clusterID string, input ClusterSecretCreate) (ClusterSecret, *Response, error) { + u := fmt.Sprintf("v2/organizations/%s/clusters/%s/secrets", org, clusterID) + req, err := css.client.NewRequest(ctx, "POST", u, input) + if err != nil { + return ClusterSecret{}, nil, err + } + + var secret ClusterSecret + resp, err := css.client.Do(req, &secret) + if err != nil { + return ClusterSecret{}, resp, err + } + + return secret, resp, err +} + +func (css *ClusterSecretsService) Update(ctx context.Context, org, clusterID, secretID string, input ClusterSecretUpdate) (ClusterSecret, *Response, error) { + u := fmt.Sprintf("v2/organizations/%s/clusters/%s/secrets/%s", org, clusterID, secretID) + req, err := css.client.NewRequest(ctx, "PUT", u, input) + if err != nil { + return ClusterSecret{}, nil, err + } + + var secret ClusterSecret + resp, err := css.client.Do(req, &secret) + if err != nil { + return ClusterSecret{}, resp, err + } + + return secret, resp, err +} + +func (css *ClusterSecretsService) UpdateValue(ctx context.Context, org, clusterID, secretID string, input ClusterSecretValueUpdate) (*Response, error) { + u := fmt.Sprintf("v2/organizations/%s/clusters/%s/secrets/%s/value", org, clusterID, secretID) + req, err := css.client.NewRequest(ctx, "PUT", u, input) + if err != nil { + return nil, err + } + + return css.client.Do(req, nil) +} + +func (css *ClusterSecretsService) Delete(ctx context.Context, org, clusterID, secretID string) (*Response, error) { + u := fmt.Sprintf("v2/organizations/%s/clusters/%s/secrets/%s", org, clusterID, secretID) + req, err := css.client.NewRequest(ctx, "DELETE", u, nil) + if err != nil { + return nil, err + } + + return css.client.Do(req, nil) +} diff --git a/cluster_secrets_test.go b/cluster_secrets_test.go new file mode 100644 index 00000000..819b114f --- /dev/null +++ b/cluster_secrets_test.go @@ -0,0 +1,319 @@ +package buildkite + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "testing" + "time" + + "github.com/google/go-cmp/cmp" +) + +func TestClusterSecretsService_List(t *testing.T) { + t.Parallel() + + server, client, teardown := newMockServerAndClient(t) + t.Cleanup(teardown) + + server.HandleFunc("/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57/secrets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, _ = fmt.Fprint(w, + ` + [ + { + "id": "a1e2d345-6789-0abc-def1-234567890abc", + "graphql_id": "Q2x1c3RlclNlY3JldC0tLWExZTJkMzQ1LTY3ODktMGFiYy1kZWYxLTIzNDU2Nzg5MGFiYw==", + "key": "SSH_PRIVATE_KEY", + "description": "SSH key for deployment", + "policy": "any", + "url": "https://api.buildkite.com/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57/secrets/a1e2d345-6789-0abc-def1-234567890abc", + "cluster_url": "https://api.buildkite.com/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57", + "created_at": "2023-06-07T08:01:02.951Z", + "created_by": { + "id": "7da07e25-0383-4aff-a7cf-14d1a9aa098f", + "graphql_id": "VXNlci0tLTdkYTA3ZTI1LTAzODMtNGFmZi1hN2NmLTE0ZDFhOWFhMDk4Zg==", + "name": "Joe Smith", + "email": "jsmith@example.com", + "avatar_url": "https://www.gravatar.com/avatar/593nf93m405mf744n3kg9456jjph9grt4", + "created_at": "2023-02-20T03:00:05.824Z" + } + }, + { + "id": "b2f3e456-7890-1bcd-ef12-345678901bcd", + "graphql_id": "Q2x1c3RlclNlY3JldC0tLWIyZjNlNDU2LTc4OTAtMWJjZC1lZjEyLTM0NTY3ODkwMWJjZA==", + "key": "DEPLOY_TOKEN", + "description": "Deployment access token", + "policy": "block", + "url": "https://api.buildkite.com/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57/secrets/b2f3e456-7890-1bcd-ef12-345678901bcd", + "cluster_url": "https://api.buildkite.com/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57", + "created_at": "2023-06-07T08:05:00.755Z", + "created_by": { + "id": "7da07e25-0383-4aff-a7cf-14d1a9aa098f", + "graphql_id": "VXNlci0tLTdkYTA3ZTI1LTAzODMtNGFmZi1hN2NmLTE0ZDFhOWFhMDk4Zg==", + "name": "Joe Smith", + "email": "jsmith@example.com", + "avatar_url": "https://www.gravatar.com/avatar/593nf93m405mf744n3kg9456jjph9grt4", + "created_at": "2023-02-20T03:00:05.824Z" + } + } + ]`) + }) + + secrets, _, err := client.ClusterSecrets.List(context.Background(), "my-great-org", "b7c9bc4f-526f-4c18-a3be-dc854ab75d57", nil) + if err != nil { + t.Errorf("TestClusterSecrets.List returned error: %v", err) + } + + sshKeyCreatedAt := must(time.Parse(BuildKiteDateFormat, "2023-06-07T08:01:02.951Z")) + deployTokenCreatedAt := must(time.Parse(BuildKiteDateFormat, "2023-06-07T08:05:00.755Z")) + userCreatedAt := must(time.Parse(BuildKiteDateFormat, "2023-02-20T03:00:05.824Z")) + + clusterCreator := ClusterCreator{ + ID: "7da07e25-0383-4aff-a7cf-14d1a9aa098f", + GraphQLID: "VXNlci0tLTdkYTA3ZTI1LTAzODMtNGFmZi1hN2NmLTE0ZDFhOWFhMDk4Zg==", + Name: "Joe Smith", + Email: "jsmith@example.com", + AvatarURL: "https://www.gravatar.com/avatar/593nf93m405mf744n3kg9456jjph9grt4", + CreatedAt: NewTimestamp(userCreatedAt), + } + + want := []ClusterSecret{ + { + ID: "a1e2d345-6789-0abc-def1-234567890abc", + GraphQLID: "Q2x1c3RlclNlY3JldC0tLWExZTJkMzQ1LTY3ODktMGFiYy1kZWYxLTIzNDU2Nzg5MGFiYw==", + Key: "SSH_PRIVATE_KEY", + Description: "SSH key for deployment", + Policy: "any", + URL: "https://api.buildkite.com/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57/secrets/a1e2d345-6789-0abc-def1-234567890abc", + ClusterURL: "https://api.buildkite.com/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57", + CreatedAt: NewTimestamp(sshKeyCreatedAt), + CreatedBy: clusterCreator, + }, + { + ID: "b2f3e456-7890-1bcd-ef12-345678901bcd", + GraphQLID: "Q2x1c3RlclNlY3JldC0tLWIyZjNlNDU2LTc4OTAtMWJjZC1lZjEyLTM0NTY3ODkwMWJjZA==", + Key: "DEPLOY_TOKEN", + Description: "Deployment access token", + Policy: "block", + URL: "https://api.buildkite.com/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57/secrets/b2f3e456-7890-1bcd-ef12-345678901bcd", + ClusterURL: "https://api.buildkite.com/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57", + CreatedAt: NewTimestamp(deployTokenCreatedAt), + CreatedBy: clusterCreator, + }, + } + + if diff := cmp.Diff(secrets, want); diff != "" { + t.Errorf("TestClusterSecrets.List diff: (-got +want)\n%s", diff) + } +} + +func TestClusterSecretsService_Get(t *testing.T) { + t.Parallel() + + server, client, teardown := newMockServerAndClient(t) + t.Cleanup(teardown) + + server.HandleFunc("/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57/secrets/a1e2d345-6789-0abc-def1-234567890abc", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, _ = fmt.Fprint(w, + ` + { + "id": "a1e2d345-6789-0abc-def1-234567890abc", + "graphql_id": "Q2x1c3RlclNlY3JldC0tLWExZTJkMzQ1LTY3ODktMGFiYy1kZWYxLTIzNDU2Nzg5MGFiYw==", + "key": "SSH_PRIVATE_KEY", + "description": "SSH key for deployment", + "policy": "any", + "url": "https://api.buildkite.com/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57/secrets/a1e2d345-6789-0abc-def1-234567890abc", + "cluster_url": "https://api.buildkite.com/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57", + "created_at": "2023-06-07T08:01:02.951Z", + "created_by": { + "id": "7da07e25-0383-4aff-a7cf-14d1a9aa098f", + "graphql_id": "VXNlci0tLTdkYTA3ZTI1LTAzODMtNGFmZi1hN2NmLTE0ZDFhOWFhMDk4Zg==", + "name": "Joe Smith", + "email": "jsmith@example.com", + "avatar_url": "https://www.gravatar.com/avatar/593nf93m405mf744n3kg9456jjph9grt4", + "created_at": "2023-02-20T03:00:05.824Z" + } + }`) + }) + + secret, _, err := client.ClusterSecrets.Get(context.Background(), "my-great-org", "b7c9bc4f-526f-4c18-a3be-dc854ab75d57", "a1e2d345-6789-0abc-def1-234567890abc") + if err != nil { + t.Errorf("TestClusterSecrets.Get returned error: %v", err) + } + + secretCreatedAt := must(time.Parse(BuildKiteDateFormat, "2023-06-07T08:01:02.951Z")) + userCreatedAt := must(time.Parse(BuildKiteDateFormat, "2023-02-20T03:00:05.824Z")) + + clusterCreator := ClusterCreator{ + ID: "7da07e25-0383-4aff-a7cf-14d1a9aa098f", + GraphQLID: "VXNlci0tLTdkYTA3ZTI1LTAzODMtNGFmZi1hN2NmLTE0ZDFhOWFhMDk4Zg==", + Name: "Joe Smith", + Email: "jsmith@example.com", + AvatarURL: "https://www.gravatar.com/avatar/593nf93m405mf744n3kg9456jjph9grt4", + CreatedAt: NewTimestamp(userCreatedAt), + } + + want := ClusterSecret{ + ID: "a1e2d345-6789-0abc-def1-234567890abc", + GraphQLID: "Q2x1c3RlclNlY3JldC0tLWExZTJkMzQ1LTY3ODktMGFiYy1kZWYxLTIzNDU2Nzg5MGFiYw==", + Key: "SSH_PRIVATE_KEY", + Description: "SSH key for deployment", + Policy: "any", + URL: "https://api.buildkite.com/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57/secrets/a1e2d345-6789-0abc-def1-234567890abc", + ClusterURL: "https://api.buildkite.com/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57", + CreatedAt: NewTimestamp(secretCreatedAt), + CreatedBy: clusterCreator, + } + + if diff := cmp.Diff(secret, want); diff != "" { + t.Errorf("TestClusterSecrets.Get diff: (-got +want)\n%s", diff) + } +} + +func TestClusterSecretsService_Create(t *testing.T) { + t.Parallel() + + server, client, teardown := newMockServerAndClient(t) + t.Cleanup(teardown) + + input := ClusterSecretCreate{ + Key: "SSH_PRIVATE_KEY", + Value: "supersecret", + Description: "SSH key for deployment", + Policy: "any", + } + + server.HandleFunc("/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57/secrets", func(w http.ResponseWriter, r *http.Request) { + var v ClusterSecretCreate + err := json.NewDecoder(r.Body).Decode(&v) + if err != nil { + t.Fatalf("Error parsing json body: %v", err) + } + + testMethod(t, r, "POST") + + if diff := cmp.Diff(v, input); diff != "" { + t.Errorf("Request body diff: (-got +want)\n%s", diff) + } + + _, _ = fmt.Fprint(w, + ` + { + "id": "a1e2d345-6789-0abc-def1-234567890abc", + "key": "SSH_PRIVATE_KEY", + "description": "SSH key for deployment", + "policy": "any" + }`) + }) + + secret, _, err := client.ClusterSecrets.Create(context.Background(), "my-great-org", "b7c9bc4f-526f-4c18-a3be-dc854ab75d57", input) + if err != nil { + t.Errorf("TestClusterSecrets.Create returned error: %v", err) + } + + want := ClusterSecret{ + ID: "a1e2d345-6789-0abc-def1-234567890abc", + Key: "SSH_PRIVATE_KEY", + Description: "SSH key for deployment", + Policy: "any", + } + if diff := cmp.Diff(secret, want); diff != "" { + t.Errorf("TestClusterSecrets.Create diff: (-got +want)\n%s", diff) + } +} + +func TestClusterSecretsService_Update(t *testing.T) { + t.Parallel() + + server, client, teardown := newMockServerAndClient(t) + t.Cleanup(teardown) + + server.HandleFunc("/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57/secrets/a1e2d345-6789-0abc-def1-234567890abc", func(w http.ResponseWriter, r *http.Request) { + var v ClusterSecretUpdate + err := json.NewDecoder(r.Body).Decode(&v) + if err != nil { + t.Fatalf("Error parsing json body: %v", err) + } + + testMethod(t, r, "PUT") + + _, _ = fmt.Fprint(w, + ` + { + "id": "a1e2d345-6789-0abc-def1-234567890abc", + "key": "SSH_PRIVATE_KEY", + "description": "Updated SSH key description", + "policy": "block" + }`) + }) + + update := ClusterSecretUpdate{ + Description: "Updated SSH key description", + Policy: "block", + } + + got, _, err := client.ClusterSecrets.Update(context.Background(), "my-great-org", "b7c9bc4f-526f-4c18-a3be-dc854ab75d57", "a1e2d345-6789-0abc-def1-234567890abc", update) + if err != nil { + t.Errorf("TestClusterSecrets.Update returned error: %v", err) + } + + want := ClusterSecret{ + ID: "a1e2d345-6789-0abc-def1-234567890abc", + Key: "SSH_PRIVATE_KEY", + Description: "Updated SSH key description", + Policy: "block", + } + + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("TestClusterSecrets.Update diff: (-got +want)\n%s", diff) + } +} + +func TestClusterSecretsService_UpdateValue(t *testing.T) { + t.Parallel() + + server, client, teardown := newMockServerAndClient(t) + t.Cleanup(teardown) + + server.HandleFunc("/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57/secrets/a1e2d345-6789-0abc-def1-234567890abc/value", func(w http.ResponseWriter, r *http.Request) { + var v ClusterSecretValueUpdate + err := json.NewDecoder(r.Body).Decode(&v) + if err != nil { + t.Fatalf("Error parsing json body: %v", err) + } + + testMethod(t, r, "PUT") + + if v.Value != "new-secret-value" { + t.Errorf("Request body value = %q, want %q", v.Value, "new-secret-value") + } + + w.WriteHeader(http.StatusNoContent) + }) + + input := ClusterSecretValueUpdate{Value: "new-secret-value"} + + _, err := client.ClusterSecrets.UpdateValue(context.Background(), "my-great-org", "b7c9bc4f-526f-4c18-a3be-dc854ab75d57", "a1e2d345-6789-0abc-def1-234567890abc", input) + if err != nil { + t.Errorf("TestClusterSecrets.UpdateValue returned error: %v", err) + } +} + +func TestClusterSecretsService_Delete(t *testing.T) { + t.Parallel() + + server, client, teardown := newMockServerAndClient(t) + t.Cleanup(teardown) + + server.HandleFunc("/v2/organizations/my-great-org/clusters/b7c9bc4f-526f-4c18-a3be-dc854ab75d57/secrets/a1e2d345-6789-0abc-def1-234567890abc", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.ClusterSecrets.Delete(context.Background(), "my-great-org", "b7c9bc4f-526f-4c18-a3be-dc854ab75d57", "a1e2d345-6789-0abc-def1-234567890abc") + if err != nil { + t.Errorf("TestClusterSecrets.Delete returned error: %v", err) + } +} diff --git a/go.mod b/go.mod index 827adaf1..e08e9a00 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/buildkite/go-buildkite/v4 -go 1.23.0 - -toolchain go1.24.0 +go 1.25 require ( github.com/cenkalti/backoff v1.1.1-0.20171020064038-309aa717adbf @@ -15,5 +13,6 @@ require ( require ( github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect + github.com/stretchr/testify v1.11.1 // indirect golang.org/x/net v0.38.0 // indirect ) diff --git a/go.sum b/go.sum index c5374be9..19b56a89 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,9 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2c github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/cenkalti/backoff v1.1.1-0.20171020064038-309aa717adbf h1:yxlp0s+Sge9UsKEK0Bsvjiopb9XRk+vxylmZ9eGBfm8= github.com/cenkalti/backoff v1.1.1-0.20171020064038-309aa717adbf/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= @@ -15,12 +16,14 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/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=