Skip to content

Commit eaa7559

Browse files
authored
Add support for GitHub Environments for Pro/Teams pricing plans (#2611)
Fixes: #2602.
1 parent c5583e7 commit eaa7559

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed

github/repos_environments.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"context"
1010
"encoding/json"
1111
"fmt"
12+
"net/http"
1213
)
1314

1415
// Environment represents a single environment in a repository.
@@ -168,6 +169,13 @@ type CreateUpdateEnvironment struct {
168169
DeploymentBranchPolicy *BranchPolicy `json:"deployment_branch_policy"`
169170
}
170171

172+
// createUpdateEnvironmentNoEnterprise represents the fields accepted for Pro/Teams private repos.
173+
// Ref: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment
174+
// See https://github.com/google/go-github/issues/2602 for more information.
175+
type createUpdateEnvironmentNoEnterprise struct {
176+
DeploymentBranchPolicy *BranchPolicy `json:"deployment_branch_policy"`
177+
}
178+
171179
// CreateUpdateEnvironment create or update a new environment for a repository.
172180
//
173181
// 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
179187
return nil, nil, err
180188
}
181189

190+
e := new(Environment)
191+
resp, err := s.client.Do(ctx, req, e)
192+
if err != nil {
193+
// The API returns 422 when the pricing plan doesn't support all the fields sent.
194+
// This path will be executed for Pro/Teams private repos.
195+
// For public repos, regardless of the pricing plan, all fields supported.
196+
// For Free plan private repos the returned error code is 404.
197+
// We are checking that the user didn't try to send a value for unsupported fields,
198+
// and return an error if they did.
199+
if resp != nil && resp.StatusCode == http.StatusUnprocessableEntity && environment != nil && len(environment.Reviewers) == 0 && environment.GetWaitTimer() == 0 {
200+
return s.createNewEnvNoEnterprise(ctx, u, environment)
201+
}
202+
return nil, resp, err
203+
}
204+
return e, resp, nil
205+
}
206+
207+
// createNewEnvNoEnterprise is an internal function for cases where the original call returned 422.
208+
// Currently only the `deployment_branch_policy` parameter is supported for Pro/Team private repos.
209+
func (s *RepositoriesService) createNewEnvNoEnterprise(ctx context.Context, u string, environment *CreateUpdateEnvironment) (*Environment, *Response, error) {
210+
req, err := s.client.NewRequest("PUT", u, &createUpdateEnvironmentNoEnterprise{
211+
DeploymentBranchPolicy: environment.DeploymentBranchPolicy,
212+
})
213+
if err != nil {
214+
return nil, nil, err
215+
}
216+
182217
e := new(Environment)
183218
resp, err := s.client.Do(ctx, req, e)
184219
if err != nil {

github/repos_environments_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,110 @@ func TestRepositoriesService_CreateEnvironment(t *testing.T) {
220220
})
221221
}
222222

223+
func TestRepositoriesService_CreateEnvironment_noEnterprise(t *testing.T) {
224+
client, mux, _, teardown := setup()
225+
defer teardown()
226+
227+
input := &CreateUpdateEnvironment{}
228+
callCount := 0
229+
230+
mux.HandleFunc("/repos/o/r/environments/e", func(w http.ResponseWriter, r *http.Request) {
231+
v := new(CreateUpdateEnvironment)
232+
json.NewDecoder(r.Body).Decode(v)
233+
234+
testMethod(t, r, "PUT")
235+
if callCount == 0 {
236+
w.WriteHeader(http.StatusUnprocessableEntity)
237+
callCount++
238+
} else {
239+
want := &CreateUpdateEnvironment{}
240+
if !cmp.Equal(v, want) {
241+
t.Errorf("Request body = %+v, want %+v", v, want)
242+
}
243+
fmt.Fprint(w, `{"id": 1, "name": "staging", "protection_rules": []}`)
244+
}
245+
})
246+
247+
ctx := context.Background()
248+
release, _, err := client.Repositories.CreateUpdateEnvironment(ctx, "o", "r", "e", input)
249+
if err != nil {
250+
t.Errorf("Repositories.CreateUpdateEnvironment returned error: %v", err)
251+
}
252+
253+
want := &Environment{ID: Int64(1), Name: String("staging"), ProtectionRules: []*ProtectionRule{}}
254+
if !cmp.Equal(release, want) {
255+
t.Errorf("Repositories.CreateUpdateEnvironment returned %+v, want %+v", release, want)
256+
}
257+
}
258+
259+
func TestRepositoriesService_createNewEnvNoEnterprise(t *testing.T) {
260+
client, mux, _, teardown := setup()
261+
defer teardown()
262+
263+
input := &CreateUpdateEnvironment{
264+
DeploymentBranchPolicy: &BranchPolicy{
265+
ProtectedBranches: Bool(true),
266+
CustomBranchPolicies: Bool(false),
267+
},
268+
}
269+
270+
mux.HandleFunc("/repos/o/r/environments/e", func(w http.ResponseWriter, r *http.Request) {
271+
v := new(createUpdateEnvironmentNoEnterprise)
272+
json.NewDecoder(r.Body).Decode(v)
273+
274+
testMethod(t, r, "PUT")
275+
want := &createUpdateEnvironmentNoEnterprise{
276+
DeploymentBranchPolicy: &BranchPolicy{
277+
ProtectedBranches: Bool(true),
278+
CustomBranchPolicies: Bool(false),
279+
},
280+
}
281+
if !cmp.Equal(v, want) {
282+
t.Errorf("Request body = %+v, want %+v", v, want)
283+
}
284+
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}}`)
285+
})
286+
287+
ctx := context.Background()
288+
release, _, err := client.Repositories.createNewEnvNoEnterprise(ctx, "repos/o/r/environments/e", input)
289+
if err != nil {
290+
t.Errorf("Repositories.createNewEnvNoEnterprise returned error: %v", err)
291+
}
292+
293+
want := &Environment{
294+
ID: Int64(1),
295+
Name: String("staging"),
296+
ProtectionRules: []*ProtectionRule{
297+
{
298+
ID: Int64(1),
299+
NodeID: String("id"),
300+
Type: String("branch_policy"),
301+
},
302+
},
303+
DeploymentBranchPolicy: &BranchPolicy{
304+
ProtectedBranches: Bool(true),
305+
CustomBranchPolicies: Bool(false),
306+
},
307+
}
308+
if !cmp.Equal(release, want) {
309+
t.Errorf("Repositories.createNewEnvNoEnterprise returned %+v, want %+v", release, want)
310+
}
311+
312+
const methodName = "createNewEnvNoEnterprise"
313+
testBadOptions(t, methodName, func() (err error) {
314+
_, _, err = client.Repositories.createNewEnvNoEnterprise(ctx, "\n", input)
315+
return err
316+
})
317+
318+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
319+
got, resp, err := client.Repositories.createNewEnvNoEnterprise(ctx, "repos/o/r/environments/e", input)
320+
if got != nil {
321+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
322+
}
323+
return resp, err
324+
})
325+
}
326+
223327
func TestRepositoriesService_DeleteEnvironment(t *testing.T) {
224328
client, mux, _, teardown := setup()
225329
defer teardown()

0 commit comments

Comments
 (0)