Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for GitHub Environments for Pro/Teams pricing plans #2611

Merged
merged 9 commits into from Jan 4, 2023
35 changes: 35 additions & 0 deletions github/repos_environments.go
Expand Up @@ -9,6 +9,7 @@ import (
"context"
"encoding/json"
"fmt"
"net/http"
)

// Environment represents a single environment in a repository.
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down
104 changes: 104 additions & 0 deletions github/repos_environments_test.go
Expand Up @@ -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()
Expand Down