Repository: xanzy/go-gitlab
Pull request #1780 (2023-08-22 14:19:47)

Add support for updating project protected environment
A new API for updating existing project protected environments was added to GitLab.  This change adds support for the update API.

https://docs.gitlab.com/ee/api/protected_environments.html#update-a-protected-environment


Patch:
diff --git a/protected_environments.go b/protected_environments.go
index 21820ab..3dc7fbd 100644
--- a/protected_environments.go
+++ b/protected_environments.go
@@ -191,6 +191,73 @@ func (s *ProtectedEnvironmentsService) ProtectRepositoryEnvironments(pid interfa
 	return pe, resp, nil
 }
 
+// UpdateProtectedEnvironmentsOptions represents the available
+// UpdateProtectedEnvironments() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/protected_environments.html#update-a-protected-environment
+type UpdateProtectedEnvironmentsOptions struct {
+	Name                  *string                                  `url:"name,omitempty" json:"name,omitempty"`
+	DeployAccessLevels    *[]*UpdateEnvironmentAccessOptions       `url:"deploy_access_levels,omitempty" json:"deploy_access_levels,omitempty"`
+	RequiredApprovalCount *int                                     `url:"required_approval_count,omitempty" json:"required_approval_count,omitempty"`
+	ApprovalRules         *[]*UpdateEnvironmentApprovalRuleOptions `url:"approval_rules,omitempty" json:"approval_rules,omitempty"`
+}
+
+// UpdateEnvironmentAccessOptions represents the options for updates to an
+// access decription for a protected environment.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/protected_environments.html#update-a-protected-environment
+type UpdateEnvironmentAccessOptions struct {
+	AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"`
+	ID          *int              `url:"id,omitempty" json:"id,omitempty"`
+	UserID      *int              `url:"user_id,omitempty" json:"user_id,omitempty"`
+	GroupID     *int              `url:"group_id,omitempty" json:"group_id,omitempty"`
+	Destroy     *bool             `url:"_destroy,omitempty" json:"_destroy,omitempty"`
+}
+
+// UpdateEnvironmentApprovalRuleOptions represents the updates to the approval
+// rules for a protected environment.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/protected_environments.html#update-a-protected-environment
+type UpdateEnvironmentApprovalRuleOptions struct {
+	ID                     *int              `url:"id,omitempty" json:"id,omitempty"`
+	UserID                 *int              `url:"user_id,omitempty" json:"user_id,omitempty"`
+	GroupID                *int              `url:"group_id,omitempty" json:"group_id,omitempty"`
+	AccessLevel            *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"`
+	AccessLevelDescription *string           `url:"access_level_description,omitempty" json:"access_level_description,omitempty"`
+	RequiredApprovalCount  *int              `url:"required_approvals,omitempty" json:"required_approvals,omitempty"`
+	GroupInheritanceType   *int              `url:"group_inheritance_type,omitempty" json:"group_inheritance_type,omitempty"`
+	Destroy                *bool             `url:"_destroy,omitempty" json:"_destroy,omitempty"`
+}
+
+// UpdateProtectedEnvironments updates a single repository environment or
+// several project repository environments using wildcard protected environment.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/protected_environments.html#update-a-protected-environment
+func (s *ProtectedEnvironmentsService) UpdateProtectedEnvironments(pid interface{}, environment string, opt *UpdateProtectedEnvironmentsOptions, options ...RequestOptionFunc) (*ProtectedEnvironment, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/protected_environments/%s", PathEscape(project), environment)
+
+	req, err := s.client.NewRequest(http.MethodPut, u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pe := new(ProtectedEnvironment)
+	resp, err := s.client.Do(req, pe)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pe, resp, nil
+}
+
 // UnprotectEnvironment unprotects the given protected environment or wildcard
 // protected environment.
 //


Test patch:
diff --git a/protected_environments_test.go b/protected_environments_test.go
index 4dd8a80..e4cf3b9 100644
--- a/protected_environments_test.go
+++ b/protected_environments_test.go
@@ -300,6 +300,263 @@ func TestProtectRepositoryEnvironments(t *testing.T) {
 	assert.Equal(t, expected, environment)
 }
 
+func TestUpdateProtectedEnvironments(t *testing.T) {
+	mux, client := setup(t)
+
+	// Test with DeployAccessLevels, RequiredApprovalCount, and ApprovalRules as if adding new to existing protected environment
+	environmentName := "dev-test"
+
+	mux.HandleFunc(fmt.Sprintf("/api/v4/projects/1/protected_environments/%s", environmentName), func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPut)
+		fmt.Fprintf(w, `{
+      "name":"%s",
+      "deploy_access_levels": [
+        {
+          "id": 42,
+          "access_level": 30,
+          "access_level_description": "Developers + Maintainers"
+        }
+      ],
+      "required_approval_count": 2,
+      "approval_rules": [
+        {
+           "id": 1,
+           "user_id": null,
+           "group_id": 10,
+           "access_level": 5,
+           "access_level_description": "devops",
+           "required_approvals": 0,
+           "group_inheritance_type": 0
+        }
+      ]
+    }`, environmentName)
+	})
+
+	expected := &ProtectedEnvironment{
+		Name: environmentName,
+		DeployAccessLevels: []*EnvironmentAccessDescription{
+			{
+				ID:                     42,
+				AccessLevel:            30,
+				AccessLevelDescription: "Developers + Maintainers",
+			},
+		},
+		RequiredApprovalCount: 2,
+		ApprovalRules: []*EnvironmentApprovalRule{
+			{
+				ID:                     1,
+				GroupID:                10,
+				AccessLevel:            5,
+				AccessLevelDescription: "devops",
+			},
+		},
+	}
+
+	opt := &UpdateProtectedEnvironmentsOptions{
+		Name: String(environmentName),
+		DeployAccessLevels: &[]*UpdateEnvironmentAccessOptions{
+			{AccessLevel: AccessLevel(30)},
+		},
+		RequiredApprovalCount: Int(2),
+		ApprovalRules: &[]*UpdateEnvironmentApprovalRuleOptions{
+			{
+				GroupID:                Int(10),
+				AccessLevel:            AccessLevel(0),
+				AccessLevelDescription: String("devops"),
+			},
+		},
+	}
+
+	environment, _, err := client.ProtectedEnvironments.UpdateProtectedEnvironments(1, environmentName, opt)
+	assert.NoError(t, err, "failed to get response")
+	assert.Equal(t, expected, environment)
+
+	// Test with DeployAccessLevels only, as if adding new to existing protected environment
+	mux.HandleFunc(fmt.Sprintf("/api/v4/projects/2/protected_environments/%s", environmentName), func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPut)
+		fmt.Fprintf(w, `{
+      "name":"%s",
+      "deploy_access_levels": [
+        {
+          "id": 42,
+          "access_level": 30,
+          "access_level_description": "Developers + Maintainers"
+        }
+      ]
+    }`, environmentName)
+	})
+
+	expected = &ProtectedEnvironment{
+		Name: environmentName,
+		DeployAccessLevels: []*EnvironmentAccessDescription{
+			{
+				ID:                     42,
+				AccessLevel:            30,
+				AccessLevelDescription: "Developers + Maintainers",
+			},
+		},
+	}
+
+	opt = &UpdateProtectedEnvironmentsOptions{
+		Name: String(environmentName),
+		DeployAccessLevels: &[]*UpdateEnvironmentAccessOptions{
+			{AccessLevel: AccessLevel(30)},
+		},
+	}
+	environment, _, err = client.ProtectedEnvironments.UpdateProtectedEnvironments(2, environmentName, opt)
+	assert.NoError(t, err, "failed to get response")
+	assert.Equal(t, expected, environment)
+
+	// Test update to DeployAccessLevel
+	mux.HandleFunc(fmt.Sprintf("/api/v4/projects/3/protected_environments/%s", environmentName), func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPut)
+		fmt.Fprintf(w, `{
+      "name":"%s",
+      "deploy_access_levels": [
+        {
+          "id": 42,
+          "access_level": 30,
+          "access_level_description": "Developers + Maintainers"
+        }
+      ],
+	  "required_approval_count": 2
+    }`, environmentName)
+	})
+
+	expected = &ProtectedEnvironment{
+		Name: environmentName,
+		DeployAccessLevels: []*EnvironmentAccessDescription{
+			{
+				ID:                     42,
+				AccessLevel:            30,
+				AccessLevelDescription: "Developers + Maintainers",
+			},
+		},
+		RequiredApprovalCount: 2,
+	}
+
+	opt = &UpdateProtectedEnvironmentsOptions{
+		Name: String(environmentName),
+		DeployAccessLevels: &[]*UpdateEnvironmentAccessOptions{
+			{
+				ID:          Int(42),
+				AccessLevel: AccessLevel(30),
+			},
+		},
+	}
+	environment, _, err = client.ProtectedEnvironments.UpdateProtectedEnvironments(3, environmentName, opt)
+	assert.NoError(t, err, "failed to get response")
+	assert.Equal(t, expected, environment)
+
+	// Test update to ApprovalRules
+	mux.HandleFunc(fmt.Sprintf("/api/v4/projects/4/protected_environments/%s", environmentName), func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPut)
+		fmt.Fprintf(w, `{
+      "name":"%s",
+      "deploy_access_levels": [
+        {
+          "id": 42,
+          "access_level": 30,
+          "access_level_description": "Developers + Maintainers"
+        }
+      ],
+      "required_approval_count": 2,
+      "approval_rules": [
+        {
+           "id": 1,
+           "user_id": null,
+           "group_id": 10,
+           "access_level": 5,
+           "access_level_description": "devops",
+           "required_approvals": 0,
+           "group_inheritance_type": 0
+        }
+      ]
+    }`, environmentName)
+	})
+
+	expected = &ProtectedEnvironment{
+		Name: environmentName,
+		DeployAccessLevels: []*EnvironmentAccessDescription{
+			{
+				ID:                     42,
+				AccessLevel:            30,
+				AccessLevelDescription: "Developers + Maintainers",
+			},
+		},
+		RequiredApprovalCount: 2,
+		ApprovalRules: []*EnvironmentApprovalRule{
+			{
+				ID:                     1,
+				GroupID:                10,
+				AccessLevel:            5,
+				AccessLevelDescription: "devops",
+			},
+		},
+	}
+
+	opt = &UpdateProtectedEnvironmentsOptions{
+		Name: String(environmentName),
+		ApprovalRules: &[]*UpdateEnvironmentApprovalRuleOptions{
+			{
+				ID:                     Int(1),
+				GroupID:                Int(10),
+				AccessLevel:            AccessLevel(0),
+				AccessLevelDescription: String("devops"),
+			},
+		},
+	}
+
+	environment, _, err = client.ProtectedEnvironments.UpdateProtectedEnvironments(4, environmentName, opt)
+	assert.NoError(t, err, "failed to get response")
+	assert.Equal(t, expected, environment)
+
+	// Test destroy ApprovalRule
+	mux.HandleFunc(fmt.Sprintf("/api/v4/projects/5/protected_environments/%s", environmentName), func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPut)
+		fmt.Fprintf(w, `{
+      "name":"%s",
+      "deploy_access_levels": [
+        {
+          "id": 42,
+          "access_level": 30,
+          "access_level_description": "Developers + Maintainers"
+        }
+      ],
+      "required_approval_count": 0,
+      "approval_rules": []
+    }`, environmentName)
+	})
+
+	expected = &ProtectedEnvironment{
+		Name: environmentName,
+		DeployAccessLevels: []*EnvironmentAccessDescription{
+			{
+				ID:                     42,
+				AccessLevel:            30,
+				AccessLevelDescription: "Developers + Maintainers",
+			},
+		},
+		RequiredApprovalCount: 0,
+		ApprovalRules:         []*EnvironmentApprovalRule{},
+	}
+
+	opt = &UpdateProtectedEnvironmentsOptions{
+		Name: String(environmentName),
+		ApprovalRules: &[]*UpdateEnvironmentApprovalRuleOptions{
+			{
+				ID:      Int(1),
+				Destroy: Bool(true),
+			},
+		},
+		RequiredApprovalCount: Int(0),
+	}
+
+	environment, _, err = client.ProtectedEnvironments.UpdateProtectedEnvironments(5, environmentName, opt)
+	assert.NoError(t, err, "failed to get response")
+	assert.Equal(t, expected, environment)
+}
+
 func TestUnprotectRepositoryEnvironments(t *testing.T) {
 	mux, client := setup(t)