diff --git a/example/codespaces/newreposecretwithxcrypto/main.go b/example/codespaces/newreposecretwithxcrypto/main.go new file mode 100644 index 0000000000..fcc547b3a4 --- /dev/null +++ b/example/codespaces/newreposecretwithxcrypto/main.go @@ -0,0 +1,164 @@ +// Copyright 2023 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// newreposecretwithxcrypto creates a new secret in GitHub for a given owner/repo. +// newreposecretwithxcrypto uses x/crypto/nacl/box instead of sodium. +// It does not depend on any native libraries and is easier to cross-compile for different platforms. +// Quite possibly there is a performance penalty due to this. +// +// newreposecretwithxcrypto has two required flags for owner and repo, and takes in one argument for the name of the secret to add. +// The secret value is pulled from an environment variable based on the secret name. +// To authenticate with GitHub, provide your token via an environment variable GITHUB_AUTH_TOKEN. +// +// To verify the new secret, navigate to GitHub Repository > Settings > left side options bar > Secrets. +// +// Usage: +// +// export GITHUB_AUTH_TOKEN= +// export SECRET_VARIABLE= +// go run main.go -owner -repo SECRET_VARIABLE +// +// Example: +// +// export GITHUB_AUTH_TOKEN=0000000000000000 +// export SECRET_VARIABLE="my-secret" +// go run main.go -owner google -repo go-github SECRET_VARIABLE +package main + +import ( + "context" + crypto_rand "crypto/rand" + "encoding/base64" + "flag" + "fmt" + "log" + "os" + + "github.com/google/go-github/v53/github" + "golang.org/x/crypto/nacl/box" +) + +var ( + repo = flag.String("repo", "", "The repo that the secret should be added to, ex. go-github") + owner = flag.String("owner", "", "The owner of there repo this should be added to, ex. google") +) + +func main() { + flag.Parse() + + token := os.Getenv("GITHUB_AUTH_TOKEN") + if token == "" { + log.Fatal("please provide a GitHub API token via env variable GITHUB_AUTH_TOKEN") + } + + if *repo == "" { + log.Fatal("please provide required flag --repo to specify GitHub repository ") + } + + if *owner == "" { + log.Fatal("please provide required flag --owner to specify GitHub user/org owner") + } + + secretName, err := getSecretName() + if err != nil { + log.Fatal(err) + } + + secretValue, err := getSecretValue(secretName) + if err != nil { + log.Fatal(err) + } + + ctx := context.Background() + client := github.NewTokenClient(ctx, token) + + if err := addRepoSecret(ctx, client, *owner, *repo, secretName, secretValue); err != nil { + log.Fatal(err) + } + + fmt.Printf("Added secret %q to the repo %v/%v\n", secretName, *owner, *repo) +} + +func getSecretName() (string, error) { + secretName := flag.Arg(0) + if secretName == "" { + return "", fmt.Errorf("missing argument secret name") + } + return secretName, nil +} + +func getSecretValue(secretName string) (string, error) { + secretValue := os.Getenv(secretName) + if secretValue == "" { + return "", fmt.Errorf("secret value not found under env variable %q", secretName) + } + return secretValue, nil +} + +// addRepoSecret will add a secret to a GitHub repo for use in GitHub Codespaces. +// +// The secretName and secretValue will determine the name of the secret added and it's corresponding value. +// +// The actual transmission of the secret value to GitHub using the api requires that the secret value is encrypted +// using the public key of the target repo. This encryption is done using x/crypto/nacl/box. +// +// First, the public key of the repo is retrieved. The public key comes base64 +// encoded, so it must be decoded prior to use. +// +// Second, the decode key is converted into a fixed size byte array. +// +// Third, the secret value is converted into a slice of bytes. +// +// Fourth, the secret is encrypted with box.SealAnonymous using the repo's decoded public key. +// +// Fifth, the encrypted secret is encoded as a base64 string to be used in a github.EncodedSecret type. +// +// Sixth, The other two properties of the github.EncodedSecret type are determined. The name of the secret to be added +// (string not base64), and the KeyID of the public key used to encrypt the secret. +// This can be retrieved via the public key's GetKeyID method. +// +// Finally, the github.EncodedSecret is passed into the GitHub client.Codespaces.CreateOrUpdateRepoSecret method to +// populate the secret in the GitHub repo. +func addRepoSecret(ctx context.Context, client *github.Client, owner string, repo, secretName string, secretValue string) error { + publicKey, _, err := client.Codespaces.GetRepoPublicKey(ctx, owner, repo) + if err != nil { + return err + } + + encryptedSecret, err := encryptSecretWithPublicKey(publicKey, secretName, secretValue) + if err != nil { + return err + } + + if _, err := client.Codespaces.CreateOrUpdateRepoSecret(ctx, owner, repo, encryptedSecret); err != nil { + return fmt.Errorf("Codespaces.CreateOrUpdateRepoSecret returned error: %v", err) + } + + return nil +} + +func encryptSecretWithPublicKey(publicKey *github.PublicKey, secretName string, secretValue string) (*github.EncryptedSecret, error) { + decodedPublicKey, err := base64.StdEncoding.DecodeString(publicKey.GetKey()) + if err != nil { + return nil, fmt.Errorf("base64.StdEncoding.DecodeString was unable to decode public key: %v", err) + } + + var boxKey [32]byte + copy(boxKey[:], decodedPublicKey) + secretBytes := []byte(secretValue) + encryptedBytes, err := box.SealAnonymous([]byte{}, secretBytes, &boxKey, crypto_rand.Reader) + if err != nil { + return nil, fmt.Errorf("box.SealAnonymous failed with error %w", err) + } + + encryptedString := base64.StdEncoding.EncodeToString(encryptedBytes) + keyID := publicKey.GetKeyID() + encryptedSecret := &github.EncryptedSecret{ + Name: secretName, + KeyID: keyID, + EncryptedValue: encryptedString, + } + return encryptedSecret, nil +} diff --git a/example/codespaces/newusersecretwithxcrypto/main.go b/example/codespaces/newusersecretwithxcrypto/main.go new file mode 100644 index 0000000000..dd230c1da0 --- /dev/null +++ b/example/codespaces/newusersecretwithxcrypto/main.go @@ -0,0 +1,171 @@ +// Copyright 2023 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// newusersecretwithxcrypto creates a new secret in GitHub for a given user. +// newusersecretwithxcrypto uses x/crypto/nacl/box instead of sodium. +// It does not depend on any native libraries and is easier to cross-compile for different platforms. +// Quite possibly there is a performance penalty due to this. +// +// newusersecretwithxcrypto takes in one argument for the name of the secret to add, and 2 flags owner, repo. +// If owner/repo are defined then it adds the secret to that repository +// The secret value is pulled from an environment variable based on the secret name. +// To authenticate with GitHub, provide your token via an environment variable GITHUB_AUTH_TOKEN. +// +// To verify the new secret, navigate to GitHub User > Settings > left side options bar > Codespaces > Secrets. +// +// Usage: +// +// export GITHUB_AUTH_TOKEN= +// export SECRET_VARIABLE= +// go run main.go SECRET_VARIABLE +// +// Example: +// +// export GITHUB_AUTH_TOKEN=0000000000000000 +// export SECRET_VARIABLE="my-secret" +// go run main.go SECRET_VARIABLE +package main + +import ( + "context" + crypto_rand "crypto/rand" + "encoding/base64" + "flag" + "fmt" + "log" + "os" + + "github.com/google/go-github/v53/github" + "golang.org/x/crypto/nacl/box" +) + +var ( + repo = flag.String("repo", "", "The repo that the secret should be added to, ex. go-github") + owner = flag.String("owner", "", "The owner of there repo this should be added to, ex. google") +) + +func main() { + flag.Parse() + + token := os.Getenv("GITHUB_AUTH_TOKEN") + if token == "" { + log.Fatal("please provide a GitHub API token via env variable GITHUB_AUTH_TOKEN") + } + + secretName, err := getSecretName() + if err != nil { + log.Fatal(err) + } + + secretValue, err := getSecretValue(secretName) + if err != nil { + log.Fatal(err) + } + + ctx := context.Background() + client := github.NewTokenClient(ctx, token) + + if err := addUserSecret(ctx, client, secretName, secretValue, *owner, *repo); err != nil { + log.Fatal(err) + } + + fmt.Printf("Added secret %q to the authenticated user\n", secretName) +} + +func getSecretName() (string, error) { + secretName := flag.Arg(0) + if secretName == "" { + return "", fmt.Errorf("missing argument secret name") + } + return secretName, nil +} + +func getSecretValue(secretName string) (string, error) { + secretValue := os.Getenv(secretName) + if secretValue == "" { + return "", fmt.Errorf("secret value not found under env variable %q", secretName) + } + return secretValue, nil +} + +// addUserSecret will add a secret to a GitHub user for use in GitHub Codespaces. +// +// The secretName and secretValue will determine the name of the secret added and it's corresponding value. +// +// The actual transmission of the secret value to GitHub using the api requires that the secret value is encrypted +// using the public key of the target user. This encryption is done using x/crypto/nacl/box. +// +// First, the public key of the user is retrieved. The public key comes base64 +// encoded, so it must be decoded prior to use. +// +// Second, the decode key is converted into a fixed size byte array. +// +// Third, the secret value is converted into a slice of bytes. +// +// Fourth, the secret is encrypted with box.SealAnonymous using the user's decoded public key. +// +// Fifth, the encrypted secret is encoded as a base64 string to be used in a github.EncodedSecret type. +// +// Sixth, The other two properties of the github.EncodedSecret type are determined. The name of the secret to be added +// (string not base64), and the KeyID of the public key used to encrypt the secret. +// This can be retrieved via the public key's GetKeyID method. +// +// Seventh, the github.EncodedSecret is passed into the GitHub client.Codespaces.CreateOrUpdateUserSecret method to +// populate the secret in the GitHub user. +// +// Finally, if a repo and owner are passed in, it adds the repo to the user secret. +func addUserSecret(ctx context.Context, client *github.Client, secretName, secretValue, owner, repo string) error { + publicKey, _, err := client.Codespaces.GetUserPublicKey(ctx) + if err != nil { + return err + } + + encryptedSecret, err := encryptSecretWithPublicKey(publicKey, secretName, secretValue) + if err != nil { + return err + } + + if _, err := client.Codespaces.CreateOrUpdateUserSecret(ctx, encryptedSecret); err != nil { + return fmt.Errorf("Codespaces.CreateOrUpdateUserSecret returned error: %v", err) + } + + if owner != "" && repo != "" { + r, _, err := client.Repositories.Get(ctx, owner, repo) + if err != nil { + return fmt.Errorf("Repositories.Get returned error: %v", err) + } + _, err = client.Codespaces.AddSelectedRepoToUserSecret(ctx, encryptedSecret.Name, r) + if err != nil { + return fmt.Errorf("Codespaces.AddSelectedRepoToUserSecret returned error: %v", err) + } + fmt.Printf("Added secret %q to %v/%v\n", secretName, owner, repo) + } + + return nil +} + +func encryptSecretWithPublicKey(publicKey *github.PublicKey, secretName string, secretValue string) (*github.EncryptedSecret, error) { + decodedPublicKey, err := base64.StdEncoding.DecodeString(publicKey.GetKey()) + if err != nil { + return nil, fmt.Errorf("base64.StdEncoding.DecodeString was unable to decode public key: %v", err) + } + + var boxKey [32]byte + copy(boxKey[:], decodedPublicKey) + secretBytes := []byte(secretValue) + encryptedBytes, err := box.SealAnonymous([]byte{}, secretBytes, &boxKey, crypto_rand.Reader) + if err != nil { + return nil, fmt.Errorf("box.SealAnonymous failed with error %w", err) + } + + encryptedString := base64.StdEncoding.EncodeToString(encryptedBytes) + keyID := publicKey.GetKeyID() + encryptedSecret := &github.EncryptedSecret{ + Name: secretName, + KeyID: keyID, + EncryptedValue: encryptedString, + } + return encryptedSecret, nil +} diff --git a/example/newreposecretwithxcrypto/main.go b/example/newreposecretwithxcrypto/main.go index d6e329295b..564fc06959 100644 --- a/example/newreposecretwithxcrypto/main.go +++ b/example/newreposecretwithxcrypto/main.go @@ -99,7 +99,7 @@ func getSecretValue(secretName string) (string, error) { // addRepoSecret will add a secret to a GitHub repo for use in GitHub Actions. // -// Finally, the secretName and secretValue will determine the name of the secret added and it's corresponding value. +// The secretName and secretValue will determine the name of the secret added and it's corresponding value. // // The actual transmission of the secret value to GitHub using the api requires that the secret value is encrypted // using the public key of the target repo. This encryption is done using x/crypto/nacl/box. @@ -115,7 +115,7 @@ func getSecretValue(secretName string) (string, error) { // // Fifth, the encrypted secret is encoded as a base64 string to be used in a github.EncodedSecret type. // -// Sixt, The other two properties of the github.EncodedSecret type are determined. The name of the secret to be added +// Sixth, The other two properties of the github.EncodedSecret type are determined. The name of the secret to be added // (string not base64), and the KeyID of the public key used to encrypt the secret. // This can be retrieved via the public key's GetKeyID method. // diff --git a/github/codespaces.go b/github/codespaces.go new file mode 100644 index 0000000000..a260c227de --- /dev/null +++ b/github/codespaces.go @@ -0,0 +1,254 @@ +// Copyright 2023 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// CodespacesService handles communication with the Codespaces related +// methods of the GitHub API. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/ +type CodespacesService service + +// Codespace represents a codespace. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces +type Codespace struct { + ID *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + DisplayName *string `json:"display_name,omitempty"` + EnvironmentID *string `json:"environment_id,omitempty"` + Owner *User `json:"owner,omitempty"` + BillableOwner *User `json:"billable_owner,omitempty"` + Repository *Repository `json:"repository,omitempty"` + Machine *CodespacesMachine `json:"machine,omitempty"` + DevcontainerPath *string `json:"devcontainer_path,omitempty"` + Prebuild *bool `json:"prebuild,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + LastUsedAt *Timestamp `json:"last_used_at,omitempty"` + State *string `json:"state,omitempty"` + URL *string `json:"url,omitempty"` + GitStatus *CodespacesGitStatus `json:"git_status,omitempty"` + Location *string `json:"location,omitempty"` + IdleTimeoutMinutes *int `json:"idle_timeout_minutes,omitempty"` + WebURL *string `json:"web_url,omitempty"` + MachinesURL *string `json:"machines_url,omitempty"` + StartURL *string `json:"start_url,omitempty"` + StopURL *string `json:"stop_url,omitempty"` + PullsURL *string `json:"pulls_url,omitempty"` + RecentFolders []string `json:"recent_folders,omitempty"` + RuntimeConstraints *CodespacesRuntimeConstraints `json:"runtime_constraints,omitempty"` + PendingOperation *bool `json:"pending_operation,omitempty"` + PendingOperationDisabledReason *string `json:"pending_operation_disabled_reason,omitempty"` + IdleTimeoutNotice *string `json:"idle_timeout_notice,omitempty"` + RetentionPeriodMinutes *int `json:"retention_period_minutes,omitempty"` + RetentionExpiresAt *Timestamp `json:"retention_expires_at,omitempty"` + LastKnownStopNotice *string `json:"last_known_stop_notice,omitempty"` +} + +// CodespacesGitStatus represents the git status of a codespace. +type CodespacesGitStatus struct { + Ahead *int `json:"ahead,omitempty"` + Behind *int `json:"behind,omitempty"` + HasUnpushedChanges *bool `json:"has_unpushed_changes,omitempty"` + HasUncommittedChanges *bool `json:"has_uncommitted_changes,omitempty"` + Ref *string `json:"ref,omitempty"` +} + +// CodespacesMachine represents the machine type of a codespace. +type CodespacesMachine struct { + Name *string `json:"name,omitempty"` + DisplayName *string `json:"display_name,omitempty"` + OperatingSystem *string `json:"operating_system,omitempty"` + StorageInBytes *int64 `json:"storage_in_bytes,omitempty"` + MemoryInBytes *int64 `json:"memory_in_bytes,omitempty"` + CPUs *int `json:"cpus,omitempty"` + PrebuildAvailability *string `json:"prebuild_availability,omitempty"` +} + +// CodespacesRuntimeConstraints represents the runtime constraints of a codespace. +type CodespacesRuntimeConstraints struct { + AllowedPortPrivacySettings []string `json:"allowed_port_privacy_settings,omitempty"` +} + +// ListCodespaces represents the response from the list codespaces endpoints. +type ListCodespaces struct { + TotalCount *int `json:"total_count,omitempty"` + Codespaces []*Codespace `json:"codespaces"` +} + +// ListInRepo lists codespaces for a user in a repository. +// +// Lists the codespaces associated with a specified repository and the authenticated user. +// You must authenticate using an access token with the codespace scope to use this endpoint. +// GitHub Apps must have read access to the codespaces repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/codespaces?apiVersion=2022-11-28#list-codespaces-in-a-repository-for-the-authenticated-user +func (s *CodespacesService) ListInRepo(ctx context.Context, owner, repo string, opts *ListOptions) (*ListCodespaces, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/codespaces", owner, repo) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var codespaces *ListCodespaces + resp, err := s.client.Do(ctx, req, &codespaces) + if err != nil { + return nil, resp, err + } + + return codespaces, resp, nil +} + +// ListOptions represents the options for listing codespaces for a user. +type ListCodespacesOptions struct { + ListOptions + RepositoryID int64 `url:"repository_id,omitempty"` +} + +// List lists codespaces for an authenticated user. +// +// Lists the authenticated user's codespaces. +// You must authenticate using an access token with the codespace scope to use this endpoint. +// GitHub Apps must have read access to the codespaces repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/codespaces?apiVersion=2022-11-28#list-codespaces-for-the-authenticated-user +func (s *CodespacesService) List(ctx context.Context, opts *ListCodespacesOptions) (*ListCodespaces, *Response, error) { + u := fmt.Sprint("user/codespaces") + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var codespaces *ListCodespaces + resp, err := s.client.Do(ctx, req, &codespaces) + if err != nil { + return nil, resp, err + } + + return codespaces, resp, nil +} + +// CreateCodespaceOptions represents options for the creation of a codespace in a repository. +type CreateCodespaceOptions struct { + Ref *string `json:"ref,omitempty"` + // Geo represents the geographic area for this codespace. + // If not specified, the value is assigned by IP. + // This property replaces location, which is being deprecated. + // Geo can be one of: `EuropeWest`, `SoutheastAsia`, `UsEast`, `UsWest`. + Geo *string `json:"geo,omitempty"` + ClientIP *string `json:"client_ip,omitempty"` + Machine *string `json:"machine,omitempty"` + DevcontainerPath *string `json:"devcontainer_path,omitempty"` + MultiRepoPermissionsOptOut *bool `json:"multi_repo_permissions_opt_out,omitempty"` + WorkingDirectory *string `json:"working_directory,omitempty"` + IdleTimeoutMinutes *int `json:"idle_timeout_minutes,omitempty"` + DisplayName *string `json:"display_name,omitempty"` + // RetentionPeriodMinutes represents the duration in minutes after codespace has gone idle in which it will be deleted. + // Must be integer minutes between 0 and 43200 (30 days). + RetentionPeriodMinutes *int `json:"retention_period_minutes,omitempty"` +} + +// CreateInRepo creates a codespace in a repository. +// +// Creates a codespace owned by the authenticated user in the specified repository. +// You must authenticate using an access token with the codespace scope to use this endpoint. +// GitHub Apps must have write access to the codespaces repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/codespaces?apiVersion=2022-11-28#create-a-codespace-in-a-repository +func (s *CodespacesService) CreateInRepo(ctx context.Context, owner, repo string, request *CreateCodespaceOptions) (*Codespace, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/codespaces", owner, repo) + + req, err := s.client.NewRequest("POST", u, request) + if err != nil { + return nil, nil, err + } + + var codespace *Codespace + resp, err := s.client.Do(ctx, req, &codespace) + if err != nil { + return nil, resp, err + } + + return codespace, resp, nil +} + +// Start starts a codespace. +// +// You must authenticate using an access token with the codespace scope to use this endpoint. +// GitHub Apps must have write access to the codespaces_lifecycle_admin repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/codespaces?apiVersion=2022-11-28#start-a-codespace-for-the-authenticated-user +func (s *CodespacesService) Start(ctx context.Context, codespaceName string) (*Codespace, *Response, error) { + u := fmt.Sprintf("user/codespaces/%v/start", codespaceName) + + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + var codespace *Codespace + resp, err := s.client.Do(ctx, req, &codespace) + if err != nil { + return nil, resp, err + } + + return codespace, resp, nil +} + +// Stop stops a codespace. +// +// You must authenticate using an access token with the codespace scope to use this endpoint. +// GitHub Apps must have write access to the codespaces_lifecycle_admin repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/codespaces?apiVersion=2022-11-28#stop-a-codespace-for-the-authenticated-user +func (s *CodespacesService) Stop(ctx context.Context, codespaceName string) (*Codespace, *Response, error) { + u := fmt.Sprintf("user/codespaces/%v/stop", codespaceName) + + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + var codespace *Codespace + resp, err := s.client.Do(ctx, req, &codespace) + if err != nil { + return nil, resp, err + } + + return codespace, resp, nil +} + +// Delete deletes a codespace. +// +// You must authenticate using an access token with the codespace scope to use this endpoint. +// GitHub Apps must have write access to the codespaces repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/codespaces?apiVersion=2022-11-28#delete-a-codespace-for-the-authenticated-user +func (s *CodespacesService) Delete(ctx context.Context, codespaceName string) (*Response, error) { + u := fmt.Sprintf("user/codespaces/%v", codespaceName) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/github/codespaces_secrets.go b/github/codespaces_secrets.go new file mode 100644 index 0000000000..e11c679c66 --- /dev/null +++ b/github/codespaces_secrets.go @@ -0,0 +1,405 @@ +// Copyright 2023 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ListUserSecrets list all secrets available for a users codespace +// +// Lists all secrets available for a user's Codespaces without revealing their encrypted values +// You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint +// GitHub Apps must have read access to the codespaces_user_secrets user permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#list-secrets-for-the-authenticated-user +func (s *CodespacesService) ListUserSecrets(ctx context.Context, opts *ListOptions) (*Secrets, *Response, error) { + u, err := addOptions("user/codespaces/secrets", opts) + if err != nil { + return nil, nil, err + } + return s.listSecrets(ctx, u) +} + +// ListOrgSecrets list all secrets available to an org +// +// Lists all Codespaces secrets available at the organization-level without revealing their encrypted values. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#list-organization-secrets +func (s *CodespacesService) ListOrgSecrets(ctx context.Context, org string, opts *ListOptions) (*Secrets, *Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets", org) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + return s.listSecrets(ctx, u) +} + +// ListRepoSecrets list all secrets available to a repo +// +// Lists all secrets available in a repository without revealing their encrypted values. You must authenticate using an access token with the repo scope to use this endpoint. GitHub Apps must have write access to the codespaces_secrets repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/repository-secrets?apiVersion=2022-11-28#list-repository-secrets +func (s *CodespacesService) ListRepoSecrets(ctx context.Context, owner, repo string, opts *ListOptions) (*Secrets, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/codespaces/secrets", owner, repo) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + return s.listSecrets(ctx, u) +} + +func (s *CodespacesService) listSecrets(ctx context.Context, url string) (*Secrets, *Response, error) { + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + var secrets *Secrets + resp, err := s.client.Do(ctx, req, &secrets) + if err != nil { + return nil, resp, err + } + + return secrets, resp, nil +} + +// GetUserPublicKey gets the users public key for encrypting codespace secrets +// +// Gets your public key, which you need to encrypt secrets. You need to encrypt a secret before you can create or update secrets. +// You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint. +// GitHub Apps must have read access to the codespaces_user_secrets user permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#get-public-key-for-the-authenticated-user +func (s *CodespacesService) GetUserPublicKey(ctx context.Context) (*PublicKey, *Response, error) { + return s.getPublicKey(ctx, "user/codespaces/secrets/public-key") +} + +// GetOrgPublicKey gets the org public key for encrypting codespace secrets +// +// Gets a public key for an organization, which is required in order to encrypt secrets. You need to encrypt the value of a secret before you can create or update secrets. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#get-an-organization-public-key +func (s *CodespacesService) GetOrgPublicKey(ctx context.Context, org string) (*PublicKey, *Response, error) { + return s.getPublicKey(ctx, fmt.Sprintf("orgs/%v/codespaces/secrets/public-key", org)) +} + +// GetRepoPublicKey gets the repo public key for encrypting codespace secrets +// +// Gets your public key, which you need to encrypt secrets. You need to encrypt a secret before you can create or update secrets. Anyone with read access to the repository can use this endpoint. If the repository is private you must use an access token with the repo scope. GitHub Apps must have write access to the codespaces_secrets repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/repository-secrets?apiVersion=2022-11-28#get-a-repository-public-key +func (s *CodespacesService) GetRepoPublicKey(ctx context.Context, owner, repo string) (*PublicKey, *Response, error) { + return s.getPublicKey(ctx, fmt.Sprintf("repos/%v/%v/codespaces/secrets/public-key", owner, repo)) +} + +func (s *CodespacesService) getPublicKey(ctx context.Context, url string) (*PublicKey, *Response, error) { + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + var publicKey *PublicKey + resp, err := s.client.Do(ctx, req, &publicKey) + if err != nil { + return nil, resp, err + } + + return publicKey, resp, nil +} + +// GetUserSecret gets a users codespace secret +// +// Gets a secret available to a user's codespaces without revealing its encrypted value. +// You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint. +// GitHub Apps must have read access to the codespaces_user_secrets user permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#get-a-secret-for-the-authenticated-user +func (s *CodespacesService) GetUserSecret(ctx context.Context, name string) (*Secret, *Response, error) { + u := fmt.Sprintf("user/codespaces/secrets/%v", name) + return s.getSecret(ctx, u) +} + +// GetOrgSecret gets an org codespace secret +// +// Gets an organization secret without revealing its encrypted value. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#get-an-organization-secret +func (s *CodespacesService) GetOrgSecret(ctx context.Context, org, name string) (*Secret, *Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets/%v", org, name) + return s.getSecret(ctx, u) +} + +// GetRepoSecret gets a repo codespace secret +// +// Gets a single repository secret without revealing its encrypted value. You must authenticate using an access token with the repo scope to use this endpoint. GitHub Apps must have write access to the codespaces_secrets repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/repository-secrets?apiVersion=2022-11-28#get-a-repository-secret +func (s *CodespacesService) GetRepoSecret(ctx context.Context, owner, repo, name string) (*Secret, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/codespaces/secrets/%v", owner, repo, name) + return s.getSecret(ctx, u) +} + +func (s *CodespacesService) getSecret(ctx context.Context, url string) (*Secret, *Response, error) { + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + var secret *Secret + resp, err := s.client.Do(ctx, req, &secret) + if err != nil { + return nil, resp, err + } + + return secret, resp, nil +} + +// CreateOrUpdateUserSecret creates or updates a users codespace secret +// +// Creates or updates a secret for a user's codespace with an encrypted value. Encrypt your secret using LibSodium. +// You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must also have Codespaces access to use this endpoint. +// GitHub Apps must have write access to the codespaces_user_secrets user permission and codespaces_secrets repository permission on all referenced repositories to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#create-or-update-a-secret-for-the-authenticated-user +func (s *CodespacesService) CreateOrUpdateUserSecret(ctx context.Context, eSecret *EncryptedSecret) (*Response, error) { + u := fmt.Sprintf("user/codespaces/secrets/%v", eSecret.Name) + return s.createOrUpdateSecret(ctx, u, eSecret) +} + +// CreateOrUpdateOrgSecret creates or updates an orgs codespace secret +// +// Creates or updates an organization secret with an encrypted value. Encrypt your secret using LibSodium. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#create-or-update-an-organization-secret +func (s *CodespacesService) CreateOrUpdateOrgSecret(ctx context.Context, org string, eSecret *EncryptedSecret) (*Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets/%v", org, eSecret.Name) + return s.createOrUpdateSecret(ctx, u, eSecret) +} + +// CreateOrUpdateRepoSecret creates or updates a repos codespace secret +// +// Creates or updates a repository secret with an encrypted value. Encrypt your secret using LibSodium. You must authenticate using an access token with the repo scope to use this endpoint. GitHub Apps must have write access to the codespaces_secrets repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/repository-secrets?apiVersion=2022-11-28#create-or-update-a-repository-secret +func (s *CodespacesService) CreateOrUpdateRepoSecret(ctx context.Context, owner, repo string, eSecret *EncryptedSecret) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/codespaces/secrets/%v", owner, repo, eSecret.Name) + return s.createOrUpdateSecret(ctx, u, eSecret) +} + +func (s *CodespacesService) createOrUpdateSecret(ctx context.Context, url string, eSecret *EncryptedSecret) (*Response, error) { + req, err := s.client.NewRequest("PUT", url, eSecret) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// DeleteUserSecret deletes a users codespace secret +// +// Deletes a secret from a user's codespaces using the secret name. Deleting the secret will remove access from all codespaces that were allowed to access the secret. +// You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint. +// GitHub Apps must have write access to the codespaces_user_secrets user permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#delete-a-secret-for-the-authenticated-user +func (s *CodespacesService) DeleteUserSecret(ctx context.Context, name string) (*Response, error) { + u := fmt.Sprintf("user/codespaces/secrets/%v", name) + return s.deleteSecret(ctx, u) +} + +// DeleteOrgSecret deletes an orgs codespace secret +// +// Deletes an organization secret using the secret name. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#delete-an-organization-secret +func (s *CodespacesService) DeleteOrgSecret(ctx context.Context, org, name string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets/%v", org, name) + return s.deleteSecret(ctx, u) +} + +// DeleteRepoSecret deletes a repos codespace secret +// +// Deletes a secret in a repository using the secret name. You must authenticate using an access token with the repo scope to use this endpoint. GitHub Apps must have write access to the codespaces_secrets repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/repository-secrets?apiVersion=2022-11-28#delete-a-repository-secret +func (s *CodespacesService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/codespaces/secrets/%v", owner, repo, name) + return s.deleteSecret(ctx, u) +} + +func (s *CodespacesService) deleteSecret(ctx context.Context, url string) (*Response, error) { + req, err := s.client.NewRequest("DELETE", url, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// ListSelectedReposForUserSecret lists the repositories that have been granted the ability to use a user's codespace secret. +// +// You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint. +// GitHub Apps must have read access to the codespaces_user_secrets user permission and write access to the codespaces_secrets repository permission on all referenced repositories to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#list-selected-repositories-for-a-user-secret +func (s *CodespacesService) ListSelectedReposForUserSecret(ctx context.Context, name string, opts *ListOptions) (*SelectedReposList, *Response, error) { + u := fmt.Sprintf("user/codespaces/secrets/%v/repositories", name) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + return s.listSelectedReposForSecret(ctx, u) +} + +// ListSelectedReposForOrgSecret lists the repositories that have been granted the ability to use an organization's codespace secret. +// +// Lists all repositories that have been selected when the visibility for repository access to a secret is set to selected. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#list-selected-repositories-for-an-organization-secret +func (s *CodespacesService) ListSelectedReposForOrgSecret(ctx context.Context, org, name string, opts *ListOptions) (*SelectedReposList, *Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets/%v/repositories", org, name) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + return s.listSelectedReposForSecret(ctx, u) +} + +func (s *CodespacesService) listSelectedReposForSecret(ctx context.Context, url string) (*SelectedReposList, *Response, error) { + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + var repositories *SelectedReposList + resp, err := s.client.Do(ctx, req, &repositories) + if err != nil { + return nil, resp, err + } + + return repositories, resp, nil +} + +// SetSelectedReposForUserSecret sets the repositories that have been granted the ability to use a user's codespace secret. +// +// You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint. +// GitHub Apps must have write access to the codespaces_user_secrets user permission and write access to the codespaces_secrets repository permission on all referenced repositories to use this endpoint. +// +// Github API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#set-selected-repositories-for-a-user-secret +func (s *CodespacesService) SetSelectedReposForUserSecret(ctx context.Context, name string, ids SelectedRepoIDs) (*Response, error) { + u := fmt.Sprintf("user/codespaces/secrets/%v/repositories", name) + return s.setSelectedRepoForSecret(ctx, u, ids) +} + +// SetSelectedReposForOrgSecret sets the repositories that have been granted the ability to use a user's codespace secret. +// +// Replaces all repositories for an organization secret when the visibility for repository access is set to selected. The visibility is set when you Create or update an organization secret. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// Github API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#set-selected-repositories-for-a-user-secret +func (s *CodespacesService) SetSelectedReposForOrgSecret(ctx context.Context, org, name string, ids SelectedRepoIDs) (*Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets/%v/repositories", org, name) + return s.setSelectedRepoForSecret(ctx, u, ids) +} + +func (s *CodespacesService) setSelectedRepoForSecret(ctx context.Context, url string, ids SelectedRepoIDs) (*Response, error) { + type repoIDs struct { + SelectedIDs SelectedRepoIDs `json:"selected_repository_ids"` + } + + req, err := s.client.NewRequest("PUT", url, repoIDs{SelectedIDs: ids}) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// AddSelectedRepoToUserSecret adds a repository to the list of repositories that have been granted the ability to use a user's codespace secret. +// +// Adds a repository to the selected repositories for a user's codespace secret. You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint. GitHub Apps must have write access to the codespaces_user_secrets user permission and write access to the codespaces_secrets repository permission on the referenced repository to use this endpoint. +// +// Github API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#add-a-selected-repository-to-a-user-secret +func (s *CodespacesService) AddSelectedRepoToUserSecret(ctx context.Context, name string, repo *Repository) (*Response, error) { + u := fmt.Sprintf("user/codespaces/secrets/%v/repositories/%v", name, *repo.ID) + return s.addSelectedRepoToSecret(ctx, u) +} + +// AddSelectedRepoToOrgSecret adds a repository to the list of repositories that have been granted the ability to use an organization's codespace secret. +// +// Adds a repository to an organization secret when the visibility for repository access is set to selected. The visibility is set when you Create or update an organization secret. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// Github API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#add-selected-repository-to-an-organization-secret +func (s *CodespacesService) AddSelectedRepoToOrgSecret(ctx context.Context, org, name string, repo *Repository) (*Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets/%v/repositories/%v", org, name, *repo.ID) + return s.addSelectedRepoToSecret(ctx, u) +} + +func (s *CodespacesService) addSelectedRepoToSecret(ctx context.Context, url string) (*Response, error) { + req, err := s.client.NewRequest("PUT", url, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// RemoveSelectedRepoFromUserSecret removes a repository from the list of repositories that have been granted the ability to use a user's codespace secret. +// +// Removes a repository from the selected repositories for a user's codespace secret. You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint. GitHub Apps must have write access to the codespaces_user_secrets user permission to use this endpoint. +// +// Github API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#remove-a-selected-repository-from-a-user-secret +func (s *CodespacesService) RemoveSelectedRepoFromUserSecret(ctx context.Context, name string, repo *Repository) (*Response, error) { + u := fmt.Sprintf("user/codespaces/secrets/%v/repositories/%v", name, *repo.ID) + return s.removeSelectedRepoFromSecret(ctx, u) +} + +// RemoveSelectedRepoFromOrgSecret removes a repository from the list of repositories that have been granted the ability to use an organization's codespace secret. +// +// Removes a repository from an organization secret when the visibility for repository access is set to selected. The visibility is set when you Create or update an organization secret. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// Github API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#remove-selected-repository-from-an-organization-secret +func (s *CodespacesService) RemoveSelectedRepoFromOrgSecret(ctx context.Context, org, name string, repo *Repository) (*Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets/%v/repositories/%v", org, name, *repo.ID) + return s.removeSelectedRepoFromSecret(ctx, u) +} + +func (s *CodespacesService) removeSelectedRepoFromSecret(ctx context.Context, url string) (*Response, error) { + req, err := s.client.NewRequest("DELETE", url, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} diff --git a/github/codespaces_secrets_test.go b/github/codespaces_secrets_test.go new file mode 100644 index 0000000000..33c0ad99c8 --- /dev/null +++ b/github/codespaces_secrets_test.go @@ -0,0 +1,803 @@ +// Copyright 2023 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "testing" + "time" + + "github.com/google/go-cmp/cmp" +) + +func TestCodespacesService_ListSecrets(t *testing.T) { + type test struct { + name string + handleFunc func(*http.ServeMux) + call func(context.Context, *Client) (*Secrets, *Response, error) + badCall func(context.Context, *Client) (*Secrets, *Response, error) + methodName string + } + opts := &ListOptions{Page: 2, PerPage: 2} + tests := []test{ + { + name: "User", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/user/codespaces/secrets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"per_page": "2", "page": "2"}) + fmt.Fprint(w, `{"total_count":4,"secrets":[{"name":"A","created_at":"2019-01-02T15:04:05Z","updated_at":"2020-01-02T15:04:05Z"},{"name":"B","created_at":"2019-01-02T15:04:05Z","updated_at":"2020-01-02T15:04:05Z"}]}`) + }) + }, + call: func(ctx context.Context, client *Client) (*Secrets, *Response, error) { + return client.Codespaces.ListUserSecrets(ctx, opts) + }, + methodName: "ListUserSecrets", + }, + { + name: "Org", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/orgs/o/codespaces/secrets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"per_page": "2", "page": "2"}) + fmt.Fprint(w, `{"total_count":4,"secrets":[{"name":"A","created_at":"2019-01-02T15:04:05Z","updated_at":"2020-01-02T15:04:05Z"},{"name":"B","created_at":"2019-01-02T15:04:05Z","updated_at":"2020-01-02T15:04:05Z"}]}`) + }) + }, + call: func(ctx context.Context, client *Client) (*Secrets, *Response, error) { + return client.Codespaces.ListOrgSecrets(ctx, "o", opts) + }, + badCall: func(ctx context.Context, client *Client) (*Secrets, *Response, error) { + return client.Codespaces.ListOrgSecrets(ctx, "\n", opts) + }, + methodName: "ListOrgSecrets", + }, + { + name: "Repo", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/repos/o/r/codespaces/secrets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"per_page": "2", "page": "2"}) + fmt.Fprint(w, `{"total_count":4,"secrets":[{"name":"A","created_at":"2019-01-02T15:04:05Z","updated_at":"2020-01-02T15:04:05Z"},{"name":"B","created_at":"2019-01-02T15:04:05Z","updated_at":"2020-01-02T15:04:05Z"}]}`) + }) + }, + call: func(ctx context.Context, client *Client) (*Secrets, *Response, error) { + return client.Codespaces.ListRepoSecrets(ctx, "o", "r", opts) + }, + badCall: func(ctx context.Context, client *Client) (*Secrets, *Response, error) { + return client.Codespaces.ListRepoSecrets(ctx, "\n", "\n", opts) + }, + methodName: "ListRepoSecrets", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + tt.handleFunc(mux) + + ctx := context.Background() + secrets, _, err := tt.call(ctx, client) + if err != nil { + t.Errorf("Codespaces.%v returned error: %v", tt.methodName, err) + } + + want := &Secrets{ + TotalCount: 4, + Secrets: []*Secret{ + {Name: "A", CreatedAt: Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, UpdatedAt: Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}}, + {Name: "B", CreatedAt: Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, UpdatedAt: Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}}, + }, + } + if !cmp.Equal(secrets, want) { + t.Errorf("Codespaces.%v returned %+v, want %+v", tt.methodName, secrets, want) + } + + if tt.badCall != nil { + testBadOptions(t, tt.methodName, func() (err error) { + _, _, err = tt.badCall(ctx, client) + return err + }) + } + + testNewRequestAndDoFailure(t, tt.methodName, client, func() (*Response, error) { + got, resp, err := tt.call(ctx, client) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", tt.methodName, got) + } + return resp, err + }) + }) + } +} + +func TestCodespacesService_GetSecret(t *testing.T) { + type test struct { + name string + handleFunc func(*http.ServeMux) + call func(context.Context, *Client) (*Secret, *Response, error) + badCall func(context.Context, *Client) (*Secret, *Response, error) + methodName string + } + tests := []test{ + { + name: "User", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/user/codespaces/secrets/NAME", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"name":"A","created_at":"2019-01-02T15:04:05Z","updated_at":"2020-01-02T15:04:05Z"}`) + }) + }, + call: func(ctx context.Context, client *Client) (*Secret, *Response, error) { + return client.Codespaces.GetUserSecret(ctx, "NAME") + }, + methodName: "GetUserSecret", + }, + { + name: "Org", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/orgs/o/codespaces/secrets/NAME", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"name":"A","created_at":"2019-01-02T15:04:05Z","updated_at":"2020-01-02T15:04:05Z"}`) + }) + }, + call: func(ctx context.Context, client *Client) (*Secret, *Response, error) { + return client.Codespaces.GetOrgSecret(ctx, "o", "NAME") + }, + badCall: func(ctx context.Context, client *Client) (*Secret, *Response, error) { + return client.Codespaces.GetOrgSecret(ctx, "\n", "\n") + }, + methodName: "GetOrgSecret", + }, + { + name: "Repo", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/repos/o/r/codespaces/secrets/NAME", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"name":"A","created_at":"2019-01-02T15:04:05Z","updated_at":"2020-01-02T15:04:05Z"}`) + }) + }, + call: func(ctx context.Context, client *Client) (*Secret, *Response, error) { + return client.Codespaces.GetRepoSecret(ctx, "o", "r", "NAME") + }, + badCall: func(ctx context.Context, client *Client) (*Secret, *Response, error) { + return client.Codespaces.GetRepoSecret(ctx, "\n", "\n", "\n") + }, + methodName: "GetRepoSecret", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + tt.handleFunc(mux) + + ctx := context.Background() + secret, _, err := tt.call(ctx, client) + if err != nil { + t.Errorf("Codespaces.%v returned error: %v", tt.methodName, err) + } + + want := &Secret{Name: "A", CreatedAt: Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, UpdatedAt: Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}} + if !cmp.Equal(secret, want) { + t.Errorf("Codespaces.%v returned %+v, want %+v", tt.methodName, secret, want) + } + + if tt.badCall != nil { + testBadOptions(t, tt.methodName, func() (err error) { + _, _, err = tt.badCall(ctx, client) + return err + }) + } + + testNewRequestAndDoFailure(t, tt.methodName, client, func() (*Response, error) { + got, resp, err := tt.call(ctx, client) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", tt.methodName, got) + } + return resp, err + }) + }) + } +} + +func TestCodespacesService_CreateOrUpdateSecret(t *testing.T) { + type test struct { + name string + handleFunc func(*http.ServeMux) + call func(context.Context, *Client, *EncryptedSecret) (*Response, error) + badCall func(context.Context, *Client, *EncryptedSecret) (*Response, error) + methodName string + } + tests := []test{ + { + name: "User", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/user/codespaces/secrets/NAME", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testHeader(t, r, "Content-Type", "application/json") + testBody(t, r, `{"key_id":"1234","encrypted_value":"QIv="}`+"\n") + w.WriteHeader(http.StatusCreated) + }) + }, + call: func(ctx context.Context, client *Client, e *EncryptedSecret) (*Response, error) { + return client.Codespaces.CreateOrUpdateUserSecret(ctx, e) + }, + methodName: "CreateOrUpdateUserSecret", + }, + { + name: "Org", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/orgs/o/codespaces/secrets/NAME", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testHeader(t, r, "Content-Type", "application/json") + testBody(t, r, `{"key_id":"1234","encrypted_value":"QIv="}`+"\n") + w.WriteHeader(http.StatusCreated) + }) + }, + call: func(ctx context.Context, client *Client, e *EncryptedSecret) (*Response, error) { + return client.Codespaces.CreateOrUpdateOrgSecret(ctx, "o", e) + }, + badCall: func(ctx context.Context, client *Client, e *EncryptedSecret) (*Response, error) { + return client.Codespaces.CreateOrUpdateOrgSecret(ctx, "\n", e) + }, + methodName: "CreateOrUpdateOrgSecret", + }, + { + name: "Repo", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/repos/o/r/codespaces/secrets/NAME", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testHeader(t, r, "Content-Type", "application/json") + testBody(t, r, `{"key_id":"1234","encrypted_value":"QIv="}`+"\n") + w.WriteHeader(http.StatusCreated) + }) + }, + call: func(ctx context.Context, client *Client, e *EncryptedSecret) (*Response, error) { + return client.Codespaces.CreateOrUpdateRepoSecret(ctx, "o", "r", e) + }, + badCall: func(ctx context.Context, client *Client, e *EncryptedSecret) (*Response, error) { + return client.Codespaces.CreateOrUpdateRepoSecret(ctx, "\n", "\n", e) + }, + methodName: "CreateOrUpdateRepoSecret", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + tt.handleFunc(mux) + + input := &EncryptedSecret{ + Name: "NAME", + EncryptedValue: "QIv=", + KeyID: "1234", + } + ctx := context.Background() + _, err := tt.call(ctx, client, input) + if err != nil { + t.Errorf("Codespaces.%v returned error: %v", tt.methodName, err) + } + + if tt.badCall != nil { + testBadOptions(t, tt.methodName, func() (err error) { + _, err = tt.badCall(ctx, client, input) + return err + }) + } + + testNewRequestAndDoFailure(t, tt.methodName, client, func() (*Response, error) { + return tt.call(ctx, client, input) + }) + }) + } +} + +func TestCodespacesService_DeleteSecret(t *testing.T) { + type test struct { + name string + handleFunc func(*http.ServeMux) + call func(context.Context, *Client) (*Response, error) + badCall func(context.Context, *Client) (*Response, error) + methodName string + } + tests := []test{ + { + name: "User", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/user/codespaces/secrets/NAME", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + }, + call: func(ctx context.Context, client *Client) (*Response, error) { + return client.Codespaces.DeleteUserSecret(ctx, "NAME") + }, + methodName: "DeleteUserSecret", + }, + { + name: "Org", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/orgs/o/codespaces/secrets/NAME", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + }, + call: func(ctx context.Context, client *Client) (*Response, error) { + return client.Codespaces.DeleteOrgSecret(ctx, "o", "NAME") + }, + badCall: func(ctx context.Context, client *Client) (*Response, error) { + return client.Codespaces.DeleteOrgSecret(ctx, "\n", "\n") + }, + methodName: "DeleteOrgSecret", + }, + { + name: "Repo", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/repos/o/r/codespaces/secrets/NAME", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + }, + call: func(ctx context.Context, client *Client) (*Response, error) { + return client.Codespaces.DeleteRepoSecret(ctx, "o", "r", "NAME") + }, + badCall: func(ctx context.Context, client *Client) (*Response, error) { + return client.Codespaces.DeleteRepoSecret(ctx, "\n", "\n", "\n") + }, + methodName: "DeleteRepoSecret", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + tt.handleFunc(mux) + + ctx := context.Background() + _, err := tt.call(ctx, client) + if err != nil { + t.Errorf("Codespaces.%v returned error: %v", tt.methodName, err) + } + + if tt.badCall != nil { + testBadOptions(t, tt.methodName, func() (err error) { + _, err = tt.badCall(ctx, client) + return err + }) + } + + testNewRequestAndDoFailure(t, tt.methodName, client, func() (*Response, error) { + return tt.call(ctx, client) + }) + }) + } +} + +func TestCodespacesService_GetPublicKey(t *testing.T) { + type test struct { + name string + handleFunc func(*http.ServeMux) + call func(context.Context, *Client) (*PublicKey, *Response, error) + badCall func(context.Context, *Client) (*PublicKey, *Response, error) + methodName string + } + + tests := []test{ + { + name: "User", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/user/codespaces/secrets/public-key", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"key_id":"1234","key":"2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"}`) + }) + }, + call: func(ctx context.Context, client *Client) (*PublicKey, *Response, error) { + return client.Codespaces.GetUserPublicKey(ctx) + }, + methodName: "GetUserPublicKey", + }, + { + name: "Org", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/orgs/o/codespaces/secrets/public-key", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"key_id":"1234","key":"2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"}`) + }) + }, + call: func(ctx context.Context, client *Client) (*PublicKey, *Response, error) { + return client.Codespaces.GetOrgPublicKey(ctx, "o") + }, + badCall: func(ctx context.Context, client *Client) (*PublicKey, *Response, error) { + return client.Codespaces.GetOrgPublicKey(ctx, "\n") + }, + methodName: "GetOrgPublicKey", + }, + { + name: "Repo", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/repos/o/r/codespaces/secrets/public-key", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"key_id":"1234","key":"2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234"}`) + }) + }, + call: func(ctx context.Context, client *Client) (*PublicKey, *Response, error) { + return client.Codespaces.GetRepoPublicKey(ctx, "o", "r") + }, + badCall: func(ctx context.Context, client *Client) (*PublicKey, *Response, error) { + return client.Codespaces.GetRepoPublicKey(ctx, "\n", "\n") + }, + methodName: "GetRepoPublicKey", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + tt.handleFunc(mux) + + ctx := context.Background() + key, _, err := tt.call(ctx, client) + if err != nil { + t.Errorf("Codespaces.%v returned error: %v", tt.methodName, err) + } + + want := &PublicKey{KeyID: String("1234"), Key: String("2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvv1234")} + if !cmp.Equal(key, want) { + t.Errorf("Codespaces.%v returned %+v, want %+v", tt.methodName, key, want) + } + + if tt.badCall != nil { + testBadOptions(t, tt.methodName, func() (err error) { + _, _, err = tt.badCall(ctx, client) + return err + }) + } + + testNewRequestAndDoFailure(t, tt.methodName, client, func() (*Response, error) { + got, resp, err := tt.call(ctx, client) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", tt.methodName, got) + } + return resp, err + }) + }) + } +} + +func TestCodespacesService_ListSelectedReposForSecret(t *testing.T) { + type test struct { + name string + handleFunc func(*http.ServeMux) + call func(context.Context, *Client) (*SelectedReposList, *Response, error) + badCall func(context.Context, *Client) (*SelectedReposList, *Response, error) + methodName string + } + opts := &ListOptions{Page: 2, PerPage: 2} + tests := []test{ + { + name: "User", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/user/codespaces/secrets/NAME/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `{"total_count":1,"repositories":[{"id":1}]}`) + }) + }, + call: func(ctx context.Context, client *Client) (*SelectedReposList, *Response, error) { + return client.Codespaces.ListSelectedReposForUserSecret(ctx, "NAME", opts) + }, + methodName: "ListSelectedReposForUserSecret", + }, + { + name: "Org", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/orgs/o/codespaces/secrets/NAME/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `{"total_count":1,"repositories":[{"id":1}]}`) + }) + }, + call: func(ctx context.Context, client *Client) (*SelectedReposList, *Response, error) { + return client.Codespaces.ListSelectedReposForOrgSecret(ctx, "o", "NAME", opts) + }, + badCall: func(ctx context.Context, client *Client) (*SelectedReposList, *Response, error) { + return client.Codespaces.ListSelectedReposForOrgSecret(ctx, "\n", "\n", opts) + }, + methodName: "ListSelectedReposForOrgSecret", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + tt.handleFunc(mux) + + ctx := context.Background() + repos, _, err := tt.call(ctx, client) + if err != nil { + t.Errorf("Codespaces.%v returned error: %v", tt.methodName, err) + } + + want := &SelectedReposList{ + TotalCount: Int(1), + Repositories: []*Repository{ + {ID: Int64(1)}, + }, + } + + if !cmp.Equal(repos, want) { + t.Errorf("Codespaces.%v returned %+v, want %+v", tt.methodName, repos, want) + } + + if tt.badCall != nil { + testBadOptions(t, tt.methodName, func() (err error) { + _, _, err = tt.badCall(ctx, client) + return err + }) + } + + testNewRequestAndDoFailure(t, tt.methodName, client, func() (*Response, error) { + got, resp, err := tt.call(ctx, client) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", tt.methodName, got) + } + return resp, err + }) + }) + } +} + +func TestCodespacesService_SetSelectedReposForSecret(t *testing.T) { + type test struct { + name string + handleFunc func(*http.ServeMux) + call func(context.Context, *Client) (*Response, error) + badCall func(context.Context, *Client) (*Response, error) + methodName string + } + ids := SelectedRepoIDs{64780797} + tests := []test{ + { + name: "User", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/user/codespaces/secrets/NAME/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testHeader(t, r, "Content-Type", "application/json") + testBody(t, r, `{"selected_repository_ids":[64780797]}`+"\n") + }) + }, + call: func(ctx context.Context, client *Client) (*Response, error) { + return client.Codespaces.SetSelectedReposForUserSecret(ctx, "NAME", ids) + }, + methodName: "SetSelectedReposForUserSecret", + }, + { + name: "Org", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/orgs/o/codespaces/secrets/NAME/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testHeader(t, r, "Content-Type", "application/json") + testBody(t, r, `{"selected_repository_ids":[64780797]}`+"\n") + }) + }, + call: func(ctx context.Context, client *Client) (*Response, error) { + return client.Codespaces.SetSelectedReposForOrgSecret(ctx, "o", "NAME", ids) + }, + badCall: func(ctx context.Context, client *Client) (*Response, error) { + return client.Codespaces.SetSelectedReposForOrgSecret(ctx, "\n", "\n", ids) + }, + methodName: "SetSelectedReposForOrgSecret", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + tt.handleFunc(mux) + + ctx := context.Background() + _, err := tt.call(ctx, client) + if err != nil { + t.Errorf("Codespaces.%v returned error: %v", tt.methodName, err) + } + + if tt.badCall != nil { + testBadOptions(t, tt.methodName, func() (err error) { + _, err = tt.badCall(ctx, client) + return err + }) + } + + testNewRequestAndDoFailure(t, tt.methodName, client, func() (*Response, error) { + return tt.call(ctx, client) + }) + }) + } +} + +func TestCodespacesService_AddSelectedReposForSecret(t *testing.T) { + type test struct { + name string + handleFunc func(*http.ServeMux) + call func(context.Context, *Client) (*Response, error) + badCall func(context.Context, *Client) (*Response, error) + methodName string + } + repo := &Repository{ID: Int64(1234)} + tests := []test{ + { + name: "User", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/user/codespaces/secrets/NAME/repositories/1234", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + }) + }, + call: func(ctx context.Context, client *Client) (*Response, error) { + return client.Codespaces.AddSelectedRepoToUserSecret(ctx, "NAME", repo) + }, + methodName: "AddSelectedRepoToUserSecret", + }, + { + name: "Org", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/orgs/o/codespaces/secrets/NAME/repositories/1234", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + }) + }, + call: func(ctx context.Context, client *Client) (*Response, error) { + return client.Codespaces.AddSelectedRepoToOrgSecret(ctx, "o", "NAME", repo) + }, + badCall: func(ctx context.Context, client *Client) (*Response, error) { + return client.Codespaces.AddSelectedRepoToOrgSecret(ctx, "\n", "\n", repo) + }, + methodName: "AddSelectedRepoToOrgSecret", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + tt.handleFunc(mux) + + ctx := context.Background() + _, err := tt.call(ctx, client) + if err != nil { + t.Errorf("Codespaces.%v returned error: %v", tt.methodName, err) + } + + if tt.badCall != nil { + testBadOptions(t, tt.methodName, func() (err error) { + _, err = tt.badCall(ctx, client) + return err + }) + } + + testNewRequestAndDoFailure(t, tt.methodName, client, func() (*Response, error) { + return tt.call(ctx, client) + }) + }) + } +} + +func TestCodespacesService_RemoveSelectedReposFromSecret(t *testing.T) { + type test struct { + name string + handleFunc func(*http.ServeMux) + call func(context.Context, *Client) (*Response, error) + badCall func(context.Context, *Client) (*Response, error) + methodName string + } + repo := &Repository{ID: Int64(1234)} + tests := []test{ + { + name: "User", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/user/codespaces/secrets/NAME/repositories/1234", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + }, + call: func(ctx context.Context, client *Client) (*Response, error) { + return client.Codespaces.RemoveSelectedRepoFromUserSecret(ctx, "NAME", repo) + }, + methodName: "RemoveSelectedRepoFromUserSecret", + }, + { + name: "Org", + handleFunc: func(mux *http.ServeMux) { + mux.HandleFunc("/orgs/o/codespaces/secrets/NAME/repositories/1234", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + }, + call: func(ctx context.Context, client *Client) (*Response, error) { + return client.Codespaces.RemoveSelectedRepoFromOrgSecret(ctx, "o", "NAME", repo) + }, + badCall: func(ctx context.Context, client *Client) (*Response, error) { + return client.Codespaces.RemoveSelectedRepoFromOrgSecret(ctx, "\n", "\n", repo) + }, + methodName: "RemoveSelectedRepoFromOrgSecret", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + tt.handleFunc(mux) + + ctx := context.Background() + _, err := tt.call(ctx, client) + if err != nil { + t.Errorf("Codespaces.%v returned error: %v", tt.methodName, err) + } + + if tt.badCall != nil { + testBadOptions(t, tt.methodName, func() (err error) { + _, err = tt.badCall(ctx, client) + return err + }) + } + + testNewRequestAndDoFailure(t, tt.methodName, client, func() (*Response, error) { + return tt.call(ctx, client) + }) + }) + } +} + +// func TestActionsService_ListSelectedReposForOrgSecret(t *testing.T) { +// client, mux, _, teardown := setup() +// defer teardown() + +// mux.HandleFunc("/orgs/o/actions/secrets/NAME/repositories", func(w http.ResponseWriter, r *http.Request) { +// testMethod(t, r, "GET") +// fmt.Fprintf(w, `{"total_count":1,"repositories":[{"id":1}]}`) +// }) + +// opts := &ListOptions{Page: 2, PerPage: 2} +// ctx := context.Background() +// repos, _, err := client.Actions.ListSelectedReposForOrgSecret(ctx, "o", "NAME", opts) +// if err != nil { +// t.Errorf("Actions.ListSelectedReposForOrgSecret returned error: %v", err) +// } + +// want := &SelectedReposList{ +// TotalCount: Int(1), +// Repositories: []*Repository{ +// {ID: Int64(1)}, +// }, +// } +// if !cmp.Equal(repos, want) { +// t.Errorf("Actions.ListSelectedReposForOrgSecret returned %+v, want %+v", repos, want) +// } + +// const methodName = "ListSelectedReposForOrgSecret" +// testBadOptions(t, methodName, func() (err error) { +// _, _, err = client.Actions.ListSelectedReposForOrgSecret(ctx, "\n", "\n", opts) +// return err +// }) + +// testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { +// got, resp, err := client.Actions.ListSelectedReposForOrgSecret(ctx, "o", "NAME", opts) +// if got != nil { +// t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) +// } +// return resp, err +// }) +// } diff --git a/github/codespaces_test.go b/github/codespaces_test.go new file mode 100644 index 0000000000..8b75731be6 --- /dev/null +++ b/github/codespaces_test.go @@ -0,0 +1,291 @@ +// Copyright 2023 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "testing" + "time" + + "github.com/google/go-cmp/cmp" +) + +func TestCodespacesService_ListInRepo(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/owner/repo/codespaces", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `{"total_count":2,"codespaces":[{"id":1,"name":"monalisa-octocat-hello-world-g4wpq6h95q","environment_id":"26a7c758-7299-4a73-b978-5a92a7ae98a0","owner":{"login":"octocat"},"billable_owner":{"login":"octocat"},"repository":{"id":1296269},"machine":{"name":"standardLinux","display_name":"4 cores, 8 GB RAM, 64 GB storage","operating_system":"linux","storage_in_bytes":68719476736,"memory_in_bytes":8589934592,"cpus":4},"prebuild":false,"devcontainer_path":".devcontainer/devcontainer.json","created_at":"2021-10-14T00:53:30-06:00","updated_at":"2021-10-14T00:53:32-06:00","last_used_at":"2021-10-14T00:53:30-06:00","state":"Available","url":"https://api.github.com/user/codespaces/monalisa-octocat-hello-world-g4wpq6h95q","git_status":{"ahead":0,"behind":0,"has_unpushed_changes":false,"has_uncommitted_changes":false,"ref":"main"},"location":"WestUs2","idle_timeout_minutes":60,"web_url":"https://monalisa-octocat-hello-world-g4wpq6h95q.github.dev","machines_url":"https://api.github.com/user/codespaces/monalisa-octocat-hello-world-g4wpq6h95q/machines","start_url":"https://api.github.com/user/codespaces/monalisa-octocat-hello-world-g4wpq6h95q/start","stop_url":"https://api.github.com/user/codespaces/monalisa-octocat-hello-world-g4wpq6h95q/stop","recent_folders":["testfolder1","testfolder2"]},{"id":2}]}`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + ctx := context.Background() + codespaces, _, err := client.Codespaces.ListInRepo(ctx, "owner", "repo", opt) + if err != nil { + t.Errorf("Codespaces.ListInRepo returned error: %v", err) + } + + want := &ListCodespaces{TotalCount: Int(2), Codespaces: []*Codespace{ + { + ID: Int64(1), + Name: String("monalisa-octocat-hello-world-g4wpq6h95q"), + EnvironmentID: String("26a7c758-7299-4a73-b978-5a92a7ae98a0"), + Owner: &User{ + Login: String("octocat"), + }, + BillableOwner: &User{ + Login: String("octocat"), + }, + Repository: &Repository{ + ID: Int64(1296269), + }, + Machine: &CodespacesMachine{ + Name: String("standardLinux"), + DisplayName: String("4 cores, 8 GB RAM, 64 GB storage"), + OperatingSystem: String("linux"), + StorageInBytes: Int64(68719476736), + MemoryInBytes: Int64(8589934592), + CPUs: Int(4), + }, + Prebuild: Bool(false), + DevcontainerPath: String(".devcontainer/devcontainer.json"), + CreatedAt: &Timestamp{time.Date(2021, 10, 14, 0, 53, 30, 0, time.FixedZone("", -6*60*60))}, + UpdatedAt: &Timestamp{time.Date(2021, 10, 14, 0, 53, 32, 0, time.FixedZone("", -6*60*60))}, + LastUsedAt: &Timestamp{time.Date(2021, 10, 14, 0, 53, 30, 0, time.FixedZone("", -6*60*60))}, + State: String("Available"), + URL: String("https://api.github.com/user/codespaces/monalisa-octocat-hello-world-g4wpq6h95q"), + GitStatus: &CodespacesGitStatus{ + Ahead: Int(0), + Behind: Int(0), + HasUnpushedChanges: Bool(false), + HasUncommittedChanges: Bool(false), + Ref: String("main"), + }, + Location: String("WestUs2"), + IdleTimeoutMinutes: Int(60), + WebURL: String("https://monalisa-octocat-hello-world-g4wpq6h95q.github.dev"), + MachinesURL: String("https://api.github.com/user/codespaces/monalisa-octocat-hello-world-g4wpq6h95q/machines"), + StartURL: String("https://api.github.com/user/codespaces/monalisa-octocat-hello-world-g4wpq6h95q/start"), + StopURL: String("https://api.github.com/user/codespaces/monalisa-octocat-hello-world-g4wpq6h95q/stop"), + RecentFolders: []string{ + "testfolder1", + "testfolder2", + }, + }, + { + ID: Int64(2), + }, + }} + if !cmp.Equal(codespaces, want) { + t.Errorf("Codespaces.ListInRepo returned %+v, want %+v", codespaces, want) + } + + const methodName = "ListInRepo" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Codespaces.ListInRepo(ctx, "", "", nil) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestCodespacesService_List(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/codespaces", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + "repository_id": "1296269", + }) + fmt.Fprint(w, `{"total_count":1,"codespaces":[{"id":1, "repository": {"id": 1296269}}]}`) + }) + + opt := &ListCodespacesOptions{ListOptions: ListOptions{Page: 1, PerPage: 2}, RepositoryID: 1296269} + ctx := context.Background() + codespaces, _, err := client.Codespaces.List(ctx, opt) + if err != nil { + t.Errorf("Codespaces.List returned error: %v", err) + } + + want := &ListCodespaces{TotalCount: Int(1), Codespaces: []*Codespace{ + { + ID: Int64(1), + Repository: &Repository{ + ID: Int64(1296269), + }, + }, + }} + if !cmp.Equal(codespaces, want) { + t.Errorf("Codespaces.ListInRepo returned %+v, want %+v", codespaces, want) + } + + const methodName = "List" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Codespaces.List(ctx, nil) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestCodespacesService_CreateInRepo(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/repos/owner/repo/codespaces", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Content-Type", "application/json") + testBody(t, r, `{"ref":"main","geo":"WestUs2","machine":"standardLinux","idle_timeout_minutes":60}`+"\n") + fmt.Fprint(w, `{"id":1, "repository": {"id": 1296269}}`) + }) + input := &CreateCodespaceOptions{ + Ref: String("main"), + Geo: String("WestUs2"), + Machine: String("standardLinux"), + IdleTimeoutMinutes: Int(60), + } + ctx := context.Background() + codespace, _, err := client.Codespaces.CreateInRepo(ctx, "owner", "repo", input) + if err != nil { + t.Errorf("Codespaces.CreateInRepo returned error: %v", err) + } + want := &Codespace{ + ID: Int64(1), + Repository: &Repository{ + ID: Int64(1296269), + }, + } + + if !cmp.Equal(codespace, want) { + t.Errorf("Codespaces.CreateInRepo returned %+v, want %+v", codespace, want) + } + + const methodName = "CreateInRepo" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Codespaces.CreateInRepo(ctx, "\n", "", input) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Codespaces.CreateInRepo(ctx, "o", "r", input) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestCodespacesService_Start(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/user/codespaces/codespace_1/start", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{"id":1, "repository": {"id": 1296269}}`) + }) + ctx := context.Background() + codespace, _, err := client.Codespaces.Start(ctx, "codespace_1") + if err != nil { + t.Errorf("Codespaces.Start returned error: %v", err) + } + want := &Codespace{ + ID: Int64(1), + Repository: &Repository{ + ID: Int64(1296269), + }, + } + + if !cmp.Equal(codespace, want) { + t.Errorf("Codespaces.Start returned %+v, want %+v", codespace, want) + } + + const methodName = "Start" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Codespaces.Start(ctx, "\n") + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Codespaces.Start(ctx, "o") + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestCodespacesService_Stop(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/user/codespaces/codespace_1/stop", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{"id":1, "repository": {"id": 1296269}}`) + }) + ctx := context.Background() + codespace, _, err := client.Codespaces.Stop(ctx, "codespace_1") + if err != nil { + t.Errorf("Codespaces.Stop returned error: %v", err) + } + want := &Codespace{ + ID: Int64(1), + Repository: &Repository{ + ID: Int64(1296269), + }, + } + + if !cmp.Equal(codespace, want) { + t.Errorf("Codespaces.Stop returned %+v, want %+v", codespace, want) + } + + const methodName = "Stop" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Codespaces.Stop(ctx, "\n") + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Codespaces.Stop(ctx, "o") + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestCodespacesService_Delete(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/codespaces/codespace_1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + ctx := context.Background() + _, err := client.Codespaces.Delete(ctx, "codespace_1") + if err != nil { + t.Errorf("Codespaces.Delete return error: %v", err) + } + + const methodName = "Delete" + testBadOptions(t, methodName, func() (err error) { + _, err = client.Codespaces.Delete(ctx, "\n") + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.Codespaces.Delete(ctx, "codespace_1") + }) +} diff --git a/github/github-accessors.go b/github/github-accessors.go index a9aaee814a..158613a48a 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -2886,6 +2886,342 @@ func (c *CodeSearchResult) GetTotal() int { return *c.Total } +// GetBillableOwner returns the BillableOwner field. +func (c *Codespace) GetBillableOwner() *User { + if c == nil { + return nil + } + return c.BillableOwner +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (c *Codespace) GetCreatedAt() Timestamp { + if c == nil || c.CreatedAt == nil { + return Timestamp{} + } + return *c.CreatedAt +} + +// GetDevcontainerPath returns the DevcontainerPath field if it's non-nil, zero value otherwise. +func (c *Codespace) GetDevcontainerPath() string { + if c == nil || c.DevcontainerPath == nil { + return "" + } + return *c.DevcontainerPath +} + +// GetDisplayName returns the DisplayName field if it's non-nil, zero value otherwise. +func (c *Codespace) GetDisplayName() string { + if c == nil || c.DisplayName == nil { + return "" + } + return *c.DisplayName +} + +// GetEnvironmentID returns the EnvironmentID field if it's non-nil, zero value otherwise. +func (c *Codespace) GetEnvironmentID() string { + if c == nil || c.EnvironmentID == nil { + return "" + } + return *c.EnvironmentID +} + +// GetGitStatus returns the GitStatus field. +func (c *Codespace) GetGitStatus() *CodespacesGitStatus { + if c == nil { + return nil + } + return c.GitStatus +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (c *Codespace) GetID() int64 { + if c == nil || c.ID == nil { + return 0 + } + return *c.ID +} + +// GetIdleTimeoutMinutes returns the IdleTimeoutMinutes field if it's non-nil, zero value otherwise. +func (c *Codespace) GetIdleTimeoutMinutes() int { + if c == nil || c.IdleTimeoutMinutes == nil { + return 0 + } + return *c.IdleTimeoutMinutes +} + +// GetIdleTimeoutNotice returns the IdleTimeoutNotice field if it's non-nil, zero value otherwise. +func (c *Codespace) GetIdleTimeoutNotice() string { + if c == nil || c.IdleTimeoutNotice == nil { + return "" + } + return *c.IdleTimeoutNotice +} + +// GetLastKnownStopNotice returns the LastKnownStopNotice field if it's non-nil, zero value otherwise. +func (c *Codespace) GetLastKnownStopNotice() string { + if c == nil || c.LastKnownStopNotice == nil { + return "" + } + return *c.LastKnownStopNotice +} + +// GetLastUsedAt returns the LastUsedAt field if it's non-nil, zero value otherwise. +func (c *Codespace) GetLastUsedAt() Timestamp { + if c == nil || c.LastUsedAt == nil { + return Timestamp{} + } + return *c.LastUsedAt +} + +// GetLocation returns the Location field if it's non-nil, zero value otherwise. +func (c *Codespace) GetLocation() string { + if c == nil || c.Location == nil { + return "" + } + return *c.Location +} + +// GetMachine returns the Machine field. +func (c *Codespace) GetMachine() *CodespacesMachine { + if c == nil { + return nil + } + return c.Machine +} + +// GetMachinesURL returns the MachinesURL field if it's non-nil, zero value otherwise. +func (c *Codespace) GetMachinesURL() string { + if c == nil || c.MachinesURL == nil { + return "" + } + return *c.MachinesURL +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (c *Codespace) GetName() string { + if c == nil || c.Name == nil { + return "" + } + return *c.Name +} + +// GetOwner returns the Owner field. +func (c *Codespace) GetOwner() *User { + if c == nil { + return nil + } + return c.Owner +} + +// GetPendingOperation returns the PendingOperation field if it's non-nil, zero value otherwise. +func (c *Codespace) GetPendingOperation() bool { + if c == nil || c.PendingOperation == nil { + return false + } + return *c.PendingOperation +} + +// GetPendingOperationDisabledReason returns the PendingOperationDisabledReason field if it's non-nil, zero value otherwise. +func (c *Codespace) GetPendingOperationDisabledReason() string { + if c == nil || c.PendingOperationDisabledReason == nil { + return "" + } + return *c.PendingOperationDisabledReason +} + +// GetPrebuild returns the Prebuild field if it's non-nil, zero value otherwise. +func (c *Codespace) GetPrebuild() bool { + if c == nil || c.Prebuild == nil { + return false + } + return *c.Prebuild +} + +// GetPullsURL returns the PullsURL field if it's non-nil, zero value otherwise. +func (c *Codespace) GetPullsURL() string { + if c == nil || c.PullsURL == nil { + return "" + } + return *c.PullsURL +} + +// GetRepository returns the Repository field. +func (c *Codespace) GetRepository() *Repository { + if c == nil { + return nil + } + return c.Repository +} + +// GetRetentionExpiresAt returns the RetentionExpiresAt field if it's non-nil, zero value otherwise. +func (c *Codespace) GetRetentionExpiresAt() Timestamp { + if c == nil || c.RetentionExpiresAt == nil { + return Timestamp{} + } + return *c.RetentionExpiresAt +} + +// GetRetentionPeriodMinutes returns the RetentionPeriodMinutes field if it's non-nil, zero value otherwise. +func (c *Codespace) GetRetentionPeriodMinutes() int { + if c == nil || c.RetentionPeriodMinutes == nil { + return 0 + } + return *c.RetentionPeriodMinutes +} + +// GetRuntimeConstraints returns the RuntimeConstraints field. +func (c *Codespace) GetRuntimeConstraints() *CodespacesRuntimeConstraints { + if c == nil { + return nil + } + return c.RuntimeConstraints +} + +// GetStartURL returns the StartURL field if it's non-nil, zero value otherwise. +func (c *Codespace) GetStartURL() string { + if c == nil || c.StartURL == nil { + return "" + } + return *c.StartURL +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (c *Codespace) GetState() string { + if c == nil || c.State == nil { + return "" + } + return *c.State +} + +// GetStopURL returns the StopURL field if it's non-nil, zero value otherwise. +func (c *Codespace) GetStopURL() string { + if c == nil || c.StopURL == nil { + return "" + } + return *c.StopURL +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (c *Codespace) GetUpdatedAt() Timestamp { + if c == nil || c.UpdatedAt == nil { + return Timestamp{} + } + return *c.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (c *Codespace) GetURL() string { + if c == nil || c.URL == nil { + return "" + } + return *c.URL +} + +// GetWebURL returns the WebURL field if it's non-nil, zero value otherwise. +func (c *Codespace) GetWebURL() string { + if c == nil || c.WebURL == nil { + return "" + } + return *c.WebURL +} + +// GetAhead returns the Ahead field if it's non-nil, zero value otherwise. +func (c *CodespacesGitStatus) GetAhead() int { + if c == nil || c.Ahead == nil { + return 0 + } + return *c.Ahead +} + +// GetBehind returns the Behind field if it's non-nil, zero value otherwise. +func (c *CodespacesGitStatus) GetBehind() int { + if c == nil || c.Behind == nil { + return 0 + } + return *c.Behind +} + +// GetHasUncommittedChanges returns the HasUncommittedChanges field if it's non-nil, zero value otherwise. +func (c *CodespacesGitStatus) GetHasUncommittedChanges() bool { + if c == nil || c.HasUncommittedChanges == nil { + return false + } + return *c.HasUncommittedChanges +} + +// GetHasUnpushedChanges returns the HasUnpushedChanges field if it's non-nil, zero value otherwise. +func (c *CodespacesGitStatus) GetHasUnpushedChanges() bool { + if c == nil || c.HasUnpushedChanges == nil { + return false + } + return *c.HasUnpushedChanges +} + +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (c *CodespacesGitStatus) GetRef() string { + if c == nil || c.Ref == nil { + return "" + } + return *c.Ref +} + +// GetCPUs returns the CPUs field if it's non-nil, zero value otherwise. +func (c *CodespacesMachine) GetCPUs() int { + if c == nil || c.CPUs == nil { + return 0 + } + return *c.CPUs +} + +// GetDisplayName returns the DisplayName field if it's non-nil, zero value otherwise. +func (c *CodespacesMachine) GetDisplayName() string { + if c == nil || c.DisplayName == nil { + return "" + } + return *c.DisplayName +} + +// GetMemoryInBytes returns the MemoryInBytes field if it's non-nil, zero value otherwise. +func (c *CodespacesMachine) GetMemoryInBytes() int64 { + if c == nil || c.MemoryInBytes == nil { + return 0 + } + return *c.MemoryInBytes +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (c *CodespacesMachine) GetName() string { + if c == nil || c.Name == nil { + return "" + } + return *c.Name +} + +// GetOperatingSystem returns the OperatingSystem field if it's non-nil, zero value otherwise. +func (c *CodespacesMachine) GetOperatingSystem() string { + if c == nil || c.OperatingSystem == nil { + return "" + } + return *c.OperatingSystem +} + +// GetPrebuildAvailability returns the PrebuildAvailability field if it's non-nil, zero value otherwise. +func (c *CodespacesMachine) GetPrebuildAvailability() string { + if c == nil || c.PrebuildAvailability == nil { + return "" + } + return *c.PrebuildAvailability +} + +// GetStorageInBytes returns the StorageInBytes field if it's non-nil, zero value otherwise. +func (c *CodespacesMachine) GetStorageInBytes() int64 { + if c == nil || c.StorageInBytes == nil { + return 0 + } + return *c.StorageInBytes +} + // GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. func (c *CollaboratorInvitation) GetCreatedAt() Timestamp { if c == nil || c.CreatedAt == nil { @@ -4006,6 +4342,86 @@ func (c *CreateCheckSuiteOptions) GetHeadBranch() string { return *c.HeadBranch } +// GetClientIP returns the ClientIP field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetClientIP() string { + if c == nil || c.ClientIP == nil { + return "" + } + return *c.ClientIP +} + +// GetDevcontainerPath returns the DevcontainerPath field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetDevcontainerPath() string { + if c == nil || c.DevcontainerPath == nil { + return "" + } + return *c.DevcontainerPath +} + +// GetDisplayName returns the DisplayName field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetDisplayName() string { + if c == nil || c.DisplayName == nil { + return "" + } + return *c.DisplayName +} + +// GetGeo returns the Geo field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetGeo() string { + if c == nil || c.Geo == nil { + return "" + } + return *c.Geo +} + +// GetIdleTimeoutMinutes returns the IdleTimeoutMinutes field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetIdleTimeoutMinutes() int { + if c == nil || c.IdleTimeoutMinutes == nil { + return 0 + } + return *c.IdleTimeoutMinutes +} + +// GetMachine returns the Machine field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetMachine() string { + if c == nil || c.Machine == nil { + return "" + } + return *c.Machine +} + +// GetMultiRepoPermissionsOptOut returns the MultiRepoPermissionsOptOut field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetMultiRepoPermissionsOptOut() bool { + if c == nil || c.MultiRepoPermissionsOptOut == nil { + return false + } + return *c.MultiRepoPermissionsOptOut +} + +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetRef() string { + if c == nil || c.Ref == nil { + return "" + } + return *c.Ref +} + +// GetRetentionPeriodMinutes returns the RetentionPeriodMinutes field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetRetentionPeriodMinutes() int { + if c == nil || c.RetentionPeriodMinutes == nil { + return 0 + } + return *c.RetentionPeriodMinutes +} + +// GetWorkingDirectory returns the WorkingDirectory field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetWorkingDirectory() string { + if c == nil || c.WorkingDirectory == nil { + return "" + } + return *c.WorkingDirectory +} + // GetDescription returns the Description field if it's non-nil, zero value otherwise. func (c *CreateEvent) GetDescription() string { if c == nil || c.Description == nil { @@ -9454,6 +9870,14 @@ func (l *ListCheckSuiteResults) GetTotal() int { return *l.Total } +// GetTotalCount returns the TotalCount field if it's non-nil, zero value otherwise. +func (l *ListCodespaces) GetTotalCount() int { + if l == nil || l.TotalCount == nil { + return 0 + } + return *l.TotalCount +} + // GetAffiliation returns the Affiliation field if it's non-nil, zero value otherwise. func (l *ListCollaboratorOptions) GetAffiliation() string { if l == nil || l.Affiliation == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index a29baa0f0c..043663668d 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -3419,6 +3419,408 @@ func TestCodeSearchResult_GetTotal(tt *testing.T) { c.GetTotal() } +func TestCodespace_GetBillableOwner(tt *testing.T) { + c := &Codespace{} + c.GetBillableOwner() + c = nil + c.GetBillableOwner() +} + +func TestCodespace_GetCreatedAt(tt *testing.T) { + var zeroValue Timestamp + c := &Codespace{CreatedAt: &zeroValue} + c.GetCreatedAt() + c = &Codespace{} + c.GetCreatedAt() + c = nil + c.GetCreatedAt() +} + +func TestCodespace_GetDevcontainerPath(tt *testing.T) { + var zeroValue string + c := &Codespace{DevcontainerPath: &zeroValue} + c.GetDevcontainerPath() + c = &Codespace{} + c.GetDevcontainerPath() + c = nil + c.GetDevcontainerPath() +} + +func TestCodespace_GetDisplayName(tt *testing.T) { + var zeroValue string + c := &Codespace{DisplayName: &zeroValue} + c.GetDisplayName() + c = &Codespace{} + c.GetDisplayName() + c = nil + c.GetDisplayName() +} + +func TestCodespace_GetEnvironmentID(tt *testing.T) { + var zeroValue string + c := &Codespace{EnvironmentID: &zeroValue} + c.GetEnvironmentID() + c = &Codespace{} + c.GetEnvironmentID() + c = nil + c.GetEnvironmentID() +} + +func TestCodespace_GetGitStatus(tt *testing.T) { + c := &Codespace{} + c.GetGitStatus() + c = nil + c.GetGitStatus() +} + +func TestCodespace_GetID(tt *testing.T) { + var zeroValue int64 + c := &Codespace{ID: &zeroValue} + c.GetID() + c = &Codespace{} + c.GetID() + c = nil + c.GetID() +} + +func TestCodespace_GetIdleTimeoutMinutes(tt *testing.T) { + var zeroValue int + c := &Codespace{IdleTimeoutMinutes: &zeroValue} + c.GetIdleTimeoutMinutes() + c = &Codespace{} + c.GetIdleTimeoutMinutes() + c = nil + c.GetIdleTimeoutMinutes() +} + +func TestCodespace_GetIdleTimeoutNotice(tt *testing.T) { + var zeroValue string + c := &Codespace{IdleTimeoutNotice: &zeroValue} + c.GetIdleTimeoutNotice() + c = &Codespace{} + c.GetIdleTimeoutNotice() + c = nil + c.GetIdleTimeoutNotice() +} + +func TestCodespace_GetLastKnownStopNotice(tt *testing.T) { + var zeroValue string + c := &Codespace{LastKnownStopNotice: &zeroValue} + c.GetLastKnownStopNotice() + c = &Codespace{} + c.GetLastKnownStopNotice() + c = nil + c.GetLastKnownStopNotice() +} + +func TestCodespace_GetLastUsedAt(tt *testing.T) { + var zeroValue Timestamp + c := &Codespace{LastUsedAt: &zeroValue} + c.GetLastUsedAt() + c = &Codespace{} + c.GetLastUsedAt() + c = nil + c.GetLastUsedAt() +} + +func TestCodespace_GetLocation(tt *testing.T) { + var zeroValue string + c := &Codespace{Location: &zeroValue} + c.GetLocation() + c = &Codespace{} + c.GetLocation() + c = nil + c.GetLocation() +} + +func TestCodespace_GetMachine(tt *testing.T) { + c := &Codespace{} + c.GetMachine() + c = nil + c.GetMachine() +} + +func TestCodespace_GetMachinesURL(tt *testing.T) { + var zeroValue string + c := &Codespace{MachinesURL: &zeroValue} + c.GetMachinesURL() + c = &Codespace{} + c.GetMachinesURL() + c = nil + c.GetMachinesURL() +} + +func TestCodespace_GetName(tt *testing.T) { + var zeroValue string + c := &Codespace{Name: &zeroValue} + c.GetName() + c = &Codespace{} + c.GetName() + c = nil + c.GetName() +} + +func TestCodespace_GetOwner(tt *testing.T) { + c := &Codespace{} + c.GetOwner() + c = nil + c.GetOwner() +} + +func TestCodespace_GetPendingOperation(tt *testing.T) { + var zeroValue bool + c := &Codespace{PendingOperation: &zeroValue} + c.GetPendingOperation() + c = &Codespace{} + c.GetPendingOperation() + c = nil + c.GetPendingOperation() +} + +func TestCodespace_GetPendingOperationDisabledReason(tt *testing.T) { + var zeroValue string + c := &Codespace{PendingOperationDisabledReason: &zeroValue} + c.GetPendingOperationDisabledReason() + c = &Codespace{} + c.GetPendingOperationDisabledReason() + c = nil + c.GetPendingOperationDisabledReason() +} + +func TestCodespace_GetPrebuild(tt *testing.T) { + var zeroValue bool + c := &Codespace{Prebuild: &zeroValue} + c.GetPrebuild() + c = &Codespace{} + c.GetPrebuild() + c = nil + c.GetPrebuild() +} + +func TestCodespace_GetPullsURL(tt *testing.T) { + var zeroValue string + c := &Codespace{PullsURL: &zeroValue} + c.GetPullsURL() + c = &Codespace{} + c.GetPullsURL() + c = nil + c.GetPullsURL() +} + +func TestCodespace_GetRepository(tt *testing.T) { + c := &Codespace{} + c.GetRepository() + c = nil + c.GetRepository() +} + +func TestCodespace_GetRetentionExpiresAt(tt *testing.T) { + var zeroValue Timestamp + c := &Codespace{RetentionExpiresAt: &zeroValue} + c.GetRetentionExpiresAt() + c = &Codespace{} + c.GetRetentionExpiresAt() + c = nil + c.GetRetentionExpiresAt() +} + +func TestCodespace_GetRetentionPeriodMinutes(tt *testing.T) { + var zeroValue int + c := &Codespace{RetentionPeriodMinutes: &zeroValue} + c.GetRetentionPeriodMinutes() + c = &Codespace{} + c.GetRetentionPeriodMinutes() + c = nil + c.GetRetentionPeriodMinutes() +} + +func TestCodespace_GetRuntimeConstraints(tt *testing.T) { + c := &Codespace{} + c.GetRuntimeConstraints() + c = nil + c.GetRuntimeConstraints() +} + +func TestCodespace_GetStartURL(tt *testing.T) { + var zeroValue string + c := &Codespace{StartURL: &zeroValue} + c.GetStartURL() + c = &Codespace{} + c.GetStartURL() + c = nil + c.GetStartURL() +} + +func TestCodespace_GetState(tt *testing.T) { + var zeroValue string + c := &Codespace{State: &zeroValue} + c.GetState() + c = &Codespace{} + c.GetState() + c = nil + c.GetState() +} + +func TestCodespace_GetStopURL(tt *testing.T) { + var zeroValue string + c := &Codespace{StopURL: &zeroValue} + c.GetStopURL() + c = &Codespace{} + c.GetStopURL() + c = nil + c.GetStopURL() +} + +func TestCodespace_GetUpdatedAt(tt *testing.T) { + var zeroValue Timestamp + c := &Codespace{UpdatedAt: &zeroValue} + c.GetUpdatedAt() + c = &Codespace{} + c.GetUpdatedAt() + c = nil + c.GetUpdatedAt() +} + +func TestCodespace_GetURL(tt *testing.T) { + var zeroValue string + c := &Codespace{URL: &zeroValue} + c.GetURL() + c = &Codespace{} + c.GetURL() + c = nil + c.GetURL() +} + +func TestCodespace_GetWebURL(tt *testing.T) { + var zeroValue string + c := &Codespace{WebURL: &zeroValue} + c.GetWebURL() + c = &Codespace{} + c.GetWebURL() + c = nil + c.GetWebURL() +} + +func TestCodespacesGitStatus_GetAhead(tt *testing.T) { + var zeroValue int + c := &CodespacesGitStatus{Ahead: &zeroValue} + c.GetAhead() + c = &CodespacesGitStatus{} + c.GetAhead() + c = nil + c.GetAhead() +} + +func TestCodespacesGitStatus_GetBehind(tt *testing.T) { + var zeroValue int + c := &CodespacesGitStatus{Behind: &zeroValue} + c.GetBehind() + c = &CodespacesGitStatus{} + c.GetBehind() + c = nil + c.GetBehind() +} + +func TestCodespacesGitStatus_GetHasUncommittedChanges(tt *testing.T) { + var zeroValue bool + c := &CodespacesGitStatus{HasUncommittedChanges: &zeroValue} + c.GetHasUncommittedChanges() + c = &CodespacesGitStatus{} + c.GetHasUncommittedChanges() + c = nil + c.GetHasUncommittedChanges() +} + +func TestCodespacesGitStatus_GetHasUnpushedChanges(tt *testing.T) { + var zeroValue bool + c := &CodespacesGitStatus{HasUnpushedChanges: &zeroValue} + c.GetHasUnpushedChanges() + c = &CodespacesGitStatus{} + c.GetHasUnpushedChanges() + c = nil + c.GetHasUnpushedChanges() +} + +func TestCodespacesGitStatus_GetRef(tt *testing.T) { + var zeroValue string + c := &CodespacesGitStatus{Ref: &zeroValue} + c.GetRef() + c = &CodespacesGitStatus{} + c.GetRef() + c = nil + c.GetRef() +} + +func TestCodespacesMachine_GetCPUs(tt *testing.T) { + var zeroValue int + c := &CodespacesMachine{CPUs: &zeroValue} + c.GetCPUs() + c = &CodespacesMachine{} + c.GetCPUs() + c = nil + c.GetCPUs() +} + +func TestCodespacesMachine_GetDisplayName(tt *testing.T) { + var zeroValue string + c := &CodespacesMachine{DisplayName: &zeroValue} + c.GetDisplayName() + c = &CodespacesMachine{} + c.GetDisplayName() + c = nil + c.GetDisplayName() +} + +func TestCodespacesMachine_GetMemoryInBytes(tt *testing.T) { + var zeroValue int64 + c := &CodespacesMachine{MemoryInBytes: &zeroValue} + c.GetMemoryInBytes() + c = &CodespacesMachine{} + c.GetMemoryInBytes() + c = nil + c.GetMemoryInBytes() +} + +func TestCodespacesMachine_GetName(tt *testing.T) { + var zeroValue string + c := &CodespacesMachine{Name: &zeroValue} + c.GetName() + c = &CodespacesMachine{} + c.GetName() + c = nil + c.GetName() +} + +func TestCodespacesMachine_GetOperatingSystem(tt *testing.T) { + var zeroValue string + c := &CodespacesMachine{OperatingSystem: &zeroValue} + c.GetOperatingSystem() + c = &CodespacesMachine{} + c.GetOperatingSystem() + c = nil + c.GetOperatingSystem() +} + +func TestCodespacesMachine_GetPrebuildAvailability(tt *testing.T) { + var zeroValue string + c := &CodespacesMachine{PrebuildAvailability: &zeroValue} + c.GetPrebuildAvailability() + c = &CodespacesMachine{} + c.GetPrebuildAvailability() + c = nil + c.GetPrebuildAvailability() +} + +func TestCodespacesMachine_GetStorageInBytes(tt *testing.T) { + var zeroValue int64 + c := &CodespacesMachine{StorageInBytes: &zeroValue} + c.GetStorageInBytes() + c = &CodespacesMachine{} + c.GetStorageInBytes() + c = nil + c.GetStorageInBytes() +} + func TestCollaboratorInvitation_GetCreatedAt(tt *testing.T) { var zeroValue Timestamp c := &CollaboratorInvitation{CreatedAt: &zeroValue} @@ -4714,6 +5116,106 @@ func TestCreateCheckSuiteOptions_GetHeadBranch(tt *testing.T) { c.GetHeadBranch() } +func TestCreateCodespaceOptions_GetClientIP(tt *testing.T) { + var zeroValue string + c := &CreateCodespaceOptions{ClientIP: &zeroValue} + c.GetClientIP() + c = &CreateCodespaceOptions{} + c.GetClientIP() + c = nil + c.GetClientIP() +} + +func TestCreateCodespaceOptions_GetDevcontainerPath(tt *testing.T) { + var zeroValue string + c := &CreateCodespaceOptions{DevcontainerPath: &zeroValue} + c.GetDevcontainerPath() + c = &CreateCodespaceOptions{} + c.GetDevcontainerPath() + c = nil + c.GetDevcontainerPath() +} + +func TestCreateCodespaceOptions_GetDisplayName(tt *testing.T) { + var zeroValue string + c := &CreateCodespaceOptions{DisplayName: &zeroValue} + c.GetDisplayName() + c = &CreateCodespaceOptions{} + c.GetDisplayName() + c = nil + c.GetDisplayName() +} + +func TestCreateCodespaceOptions_GetGeo(tt *testing.T) { + var zeroValue string + c := &CreateCodespaceOptions{Geo: &zeroValue} + c.GetGeo() + c = &CreateCodespaceOptions{} + c.GetGeo() + c = nil + c.GetGeo() +} + +func TestCreateCodespaceOptions_GetIdleTimeoutMinutes(tt *testing.T) { + var zeroValue int + c := &CreateCodespaceOptions{IdleTimeoutMinutes: &zeroValue} + c.GetIdleTimeoutMinutes() + c = &CreateCodespaceOptions{} + c.GetIdleTimeoutMinutes() + c = nil + c.GetIdleTimeoutMinutes() +} + +func TestCreateCodespaceOptions_GetMachine(tt *testing.T) { + var zeroValue string + c := &CreateCodespaceOptions{Machine: &zeroValue} + c.GetMachine() + c = &CreateCodespaceOptions{} + c.GetMachine() + c = nil + c.GetMachine() +} + +func TestCreateCodespaceOptions_GetMultiRepoPermissionsOptOut(tt *testing.T) { + var zeroValue bool + c := &CreateCodespaceOptions{MultiRepoPermissionsOptOut: &zeroValue} + c.GetMultiRepoPermissionsOptOut() + c = &CreateCodespaceOptions{} + c.GetMultiRepoPermissionsOptOut() + c = nil + c.GetMultiRepoPermissionsOptOut() +} + +func TestCreateCodespaceOptions_GetRef(tt *testing.T) { + var zeroValue string + c := &CreateCodespaceOptions{Ref: &zeroValue} + c.GetRef() + c = &CreateCodespaceOptions{} + c.GetRef() + c = nil + c.GetRef() +} + +func TestCreateCodespaceOptions_GetRetentionPeriodMinutes(tt *testing.T) { + var zeroValue int + c := &CreateCodespaceOptions{RetentionPeriodMinutes: &zeroValue} + c.GetRetentionPeriodMinutes() + c = &CreateCodespaceOptions{} + c.GetRetentionPeriodMinutes() + c = nil + c.GetRetentionPeriodMinutes() +} + +func TestCreateCodespaceOptions_GetWorkingDirectory(tt *testing.T) { + var zeroValue string + c := &CreateCodespaceOptions{WorkingDirectory: &zeroValue} + c.GetWorkingDirectory() + c = &CreateCodespaceOptions{} + c.GetWorkingDirectory() + c = nil + c.GetWorkingDirectory() +} + func TestCreateEvent_GetDescription(tt *testing.T) { var zeroValue string c := &CreateEvent{Description: &zeroValue} @@ -11086,6 +11588,16 @@ func TestListCheckSuiteResults_GetTotal(tt *testing.T) { l.GetTotal() } +func TestListCodespaces_GetTotalCount(tt *testing.T) { + var zeroValue int + l := &ListCodespaces{TotalCount: &zeroValue} + l.GetTotalCount() + l = &ListCodespaces{} + l.GetTotalCount() + l = nil + l.GetTotalCount() +} + func TestListCollaboratorOptions_GetAffiliation(tt *testing.T) { var zeroValue string l := &ListCollaboratorOptions{Affiliation: &zeroValue} diff --git a/github/github.go b/github/github.go index 7d8aef5302..910b786d86 100644 --- a/github/github.go +++ b/github/github.go @@ -187,6 +187,7 @@ type Client struct { Billing *BillingService Checks *ChecksService CodeScanning *CodeScanningService + Codespaces *CodespacesService Dependabot *DependabotService Enterprise *EnterpriseService Gists *GistsService @@ -325,6 +326,7 @@ func NewClient(httpClient *http.Client) *Client { c.Billing = (*BillingService)(&c.common) c.Checks = (*ChecksService)(&c.common) c.CodeScanning = (*CodeScanningService)(&c.common) + c.Codespaces = (*CodespacesService)(&c.common) c.Dependabot = (*DependabotService)(&c.common) c.Enterprise = (*EnterpriseService)(&c.common) c.Gists = (*GistsService)(&c.common)