From eaa75597cd219c376a2354948d85aa34a98bf4a0 Mon Sep 17 00:00:00 2001 From: AnitaErnszt <56001200+AnitaErnszt@users.noreply.github.com> Date: Wed, 4 Jan 2023 05:18:56 +0100 Subject: [PATCH] Add support for GitHub Environments for Pro/Teams pricing plans (#2611) Fixes: #2602. --- github/repos_environments.go | 35 ++++++++++ github/repos_environments_test.go | 104 ++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/github/repos_environments.go b/github/repos_environments.go index 365f8d9202..2e85fdf99c 100644 --- a/github/repos_environments.go +++ b/github/repos_environments.go @@ -9,6 +9,7 @@ import ( "context" "encoding/json" "fmt" + "net/http" ) // Environment represents a single environment in a repository. @@ -168,6 +169,13 @@ type CreateUpdateEnvironment struct { DeploymentBranchPolicy *BranchPolicy `json:"deployment_branch_policy"` } +// createUpdateEnvironmentNoEnterprise represents the fields accepted for Pro/Teams private repos. +// Ref: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment +// See https://github.com/google/go-github/issues/2602 for more information. +type createUpdateEnvironmentNoEnterprise struct { + DeploymentBranchPolicy *BranchPolicy `json:"deployment_branch_policy"` +} + // CreateUpdateEnvironment create or update a new environment for a repository. // // GitHub API docs: https://docs.github.com/en/rest/deployments/environments#create-or-update-an-environment @@ -179,6 +187,33 @@ func (s *RepositoriesService) CreateUpdateEnvironment(ctx context.Context, owner return nil, nil, err } + e := new(Environment) + resp, err := s.client.Do(ctx, req, e) + if err != nil { + // The API returns 422 when the pricing plan doesn't support all the fields sent. + // This path will be executed for Pro/Teams private repos. + // For public repos, regardless of the pricing plan, all fields supported. + // For Free plan private repos the returned error code is 404. + // We are checking that the user didn't try to send a value for unsupported fields, + // and return an error if they did. + if resp != nil && resp.StatusCode == http.StatusUnprocessableEntity && environment != nil && len(environment.Reviewers) == 0 && environment.GetWaitTimer() == 0 { + return s.createNewEnvNoEnterprise(ctx, u, environment) + } + return nil, resp, err + } + return e, resp, nil +} + +// createNewEnvNoEnterprise is an internal function for cases where the original call returned 422. +// Currently only the `deployment_branch_policy` parameter is supported for Pro/Team private repos. +func (s *RepositoriesService) createNewEnvNoEnterprise(ctx context.Context, u string, environment *CreateUpdateEnvironment) (*Environment, *Response, error) { + req, err := s.client.NewRequest("PUT", u, &createUpdateEnvironmentNoEnterprise{ + DeploymentBranchPolicy: environment.DeploymentBranchPolicy, + }) + if err != nil { + return nil, nil, err + } + e := new(Environment) resp, err := s.client.Do(ctx, req, e) if err != nil { diff --git a/github/repos_environments_test.go b/github/repos_environments_test.go index 93d0fc25eb..0b27da933d 100644 --- a/github/repos_environments_test.go +++ b/github/repos_environments_test.go @@ -220,6 +220,110 @@ func TestRepositoriesService_CreateEnvironment(t *testing.T) { }) } +func TestRepositoriesService_CreateEnvironment_noEnterprise(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &CreateUpdateEnvironment{} + callCount := 0 + + mux.HandleFunc("/repos/o/r/environments/e", func(w http.ResponseWriter, r *http.Request) { + v := new(CreateUpdateEnvironment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if callCount == 0 { + w.WriteHeader(http.StatusUnprocessableEntity) + callCount++ + } else { + want := &CreateUpdateEnvironment{} + if !cmp.Equal(v, want) { + t.Errorf("Request body = %+v, want %+v", v, want) + } + fmt.Fprint(w, `{"id": 1, "name": "staging", "protection_rules": []}`) + } + }) + + ctx := context.Background() + release, _, err := client.Repositories.CreateUpdateEnvironment(ctx, "o", "r", "e", input) + if err != nil { + t.Errorf("Repositories.CreateUpdateEnvironment returned error: %v", err) + } + + want := &Environment{ID: Int64(1), Name: String("staging"), ProtectionRules: []*ProtectionRule{}} + if !cmp.Equal(release, want) { + t.Errorf("Repositories.CreateUpdateEnvironment returned %+v, want %+v", release, want) + } +} + +func TestRepositoriesService_createNewEnvNoEnterprise(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &CreateUpdateEnvironment{ + DeploymentBranchPolicy: &BranchPolicy{ + ProtectedBranches: Bool(true), + CustomBranchPolicies: Bool(false), + }, + } + + mux.HandleFunc("/repos/o/r/environments/e", func(w http.ResponseWriter, r *http.Request) { + v := new(createUpdateEnvironmentNoEnterprise) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + want := &createUpdateEnvironmentNoEnterprise{ + DeploymentBranchPolicy: &BranchPolicy{ + ProtectedBranches: Bool(true), + CustomBranchPolicies: Bool(false), + }, + } + if !cmp.Equal(v, want) { + t.Errorf("Request body = %+v, want %+v", v, want) + } + fmt.Fprint(w, `{"id": 1, "name": "staging", "protection_rules": [{"id": 1, "node_id": "id", "type": "branch_policy"}], "deployment_branch_policy": {"protected_branches": true, "custom_branch_policies": false}}`) + }) + + ctx := context.Background() + release, _, err := client.Repositories.createNewEnvNoEnterprise(ctx, "repos/o/r/environments/e", input) + if err != nil { + t.Errorf("Repositories.createNewEnvNoEnterprise returned error: %v", err) + } + + want := &Environment{ + ID: Int64(1), + Name: String("staging"), + ProtectionRules: []*ProtectionRule{ + { + ID: Int64(1), + NodeID: String("id"), + Type: String("branch_policy"), + }, + }, + DeploymentBranchPolicy: &BranchPolicy{ + ProtectedBranches: Bool(true), + CustomBranchPolicies: Bool(false), + }, + } + if !cmp.Equal(release, want) { + t.Errorf("Repositories.createNewEnvNoEnterprise returned %+v, want %+v", release, want) + } + + const methodName = "createNewEnvNoEnterprise" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Repositories.createNewEnvNoEnterprise(ctx, "\n", input) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Repositories.createNewEnvNoEnterprise(ctx, "repos/o/r/environments/e", input) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + func TestRepositoriesService_DeleteEnvironment(t *testing.T) { client, mux, _, teardown := setup() defer teardown()