Group-level Protected Environment [RUN ALL RSPEC] [RUN AS-IF-FOSS]
NOTE: This is a blocker against the critical issue for important prospects, thus the priority is relatively high
What does this MR do?
This MR adds the feature - Group-level Protected Environments. Specifically, it changes the following things:
- Modify
protected_environments
table to havegroup_id
FK relationship, which points to a group where the Protected Environment configuration resides. - Previously, an environment (A row of
environments
table) can only have one protection, because environments and protection are unique on theproject_id
andname
. In the new architecture, an environment can have multiple protections that inherited from ancestor groups.- When a user is authorized to access to an environment, the user must pass all protections. This logic is already generalized in Environment model by the previous refactoring.
- At group-level configuration, the environment tier name is used for matching instead of its bare name. This is a flexible feature design that can cover variety of production names. You can see the issue about more of the context of this feature design.
- There are three types of access-level authorization, role-based, user-based and group-based. These are working in the same way at the group-level too.
- The registrable user/group to deploy access levels are slightly different due to the conceptual differences between project and group:
- user-based:
- Project-level config: The given user must be a project member with Developer role or above.
- Group-level config: The given user must be group member with Maintainer role or above.
- group-based:
- Project-level config: The given group must be invited in the project.
- Group-level config: The given group must be under the group.
- user-based:
- The registrable user/group to deploy access levels are slightly different due to the conceptual differences between project and group:
- Add Group-level v4 Rest API. This is straightforward that mirrored from project-level API. We'd follow-up to refactor for making it DRY at next.
- This feature is behind
group_level_protected_environments
feature flag, which is disabled by default. This feature will be enabled for a specific prospect only and would NOT be released in this milestone without their confirmation. See timeline of this.
If you're interested, see the issue about the product concept/design.
This MR is built on top of Follow up refactoring for group-level protected environments.
Manual QA
Here are the various test results that performed on a local development instance.
Authorization test
Preparation
- Create a group
- Create a development project under the group
- Create a sub group as an operator group
- Administrator of the group creates a group-level protected environment with the following access level settings:
- Allow the operator group to deploy to production tier environments
Expectation
- User-A who is a part of operator group
- can deploy to production tier environments.
- can NOT deploy to other environments.
- User-B who is a maintainer of the development project
- can NOT deploy to production tier environments.
- can deploy to other environments.
Date: Thu 20 May 2021 06:45:23 AM UTC
Result: PASSED
API tests
Get the list of group-level protected environments
shinya@shinya-B550-VISION-D:~/workspace/thin-gdk/services/rails/src$ curl -H 'Private-token: vQBNNAPV_6UHdn3VJk3e' http://local.gitlab.test:8181/api/v4/groups/202/protected_environments
[{"name":"production","deploy_access_levels":[{"access_level":40,"access_level_description":"Maintainers","user_id":null,"group_id":null}]}]
Get the list of group-level protected environments
shinya@shinya-B550-VISION-D:~/workspace/thin-gdk/services/rails/src$ curl -H 'Private-token: vQBNNAPV_6UHdn3VJk3e' http://local.gitlab.test:8181/api/v4/groups/202/protected_environments/production
{"name":"production","deploy_access_levels":[{"access_level":40,"access_level_description":"Maintainers","user_id":null,"group_id":null}]}
Protect environments under a group
Role-base authorization
shinya@shinya-B550-VISION-D:~/workspace/thin-gdk/services/rails/src$ curl -H 'Private-token: vQBNNAPV_6UHdn3VJk3e' -H 'Content-type: application/json' -d '{"name": "production", "deploy_access_levels": [{"access_level": 40}]}' http://local.gitlab.test:8181/api/v4/groups/202/protected_environments
{"name":"production","deploy_access_levels":[{"access_level":40,"access_level_description":"Maintainers","user_id":null,"group_id":null}]}
User-base authorization
shinya@shinya-B550-VISION-D:~/workspace/thin-gdk/services/rails/src$ curl -H 'Private-token: vQBNNAPV_6UHdn3VJk3e' -H 'Content-type: application/json' -d '{"name": "production", "deploy_access_levels": [{"user_id": 1}]}' http://local.gitlab.test:8181/api/v4/groups/202/protected_environments
{"name":"production","deploy_access_levels":[{"access_level":40,"access_level_description":"Administrator","user_id":1,"group_id":null}]}
Group-base authorization
shinya@shinya-B550-VISION-D:~/workspace/thin-gdk/services/rails/src$ curl -H 'Private-token: vQBNNAPV_6UHdn3VJk3e' -H 'Content-type: application/json' -d '{"name": "production", "deploy_access_levels": [{"group_id": 203}]}' http://local.gitlab.test:8181/api/v4/groups/202/protected_environments
{"name":"production","deploy_access_levels":[{"access_level":40,"access_level_description":"a-group","user_id":null,"group_id":203}]}
Unprotect environments under a group
shinya@shinya-B550-VISION-D:~/workspace/thin-gdk/services/rails/src$ curl -H 'Private-token: vQBNNAPV_6UHdn3VJk3e' -X DELETE http://local.gitlab.test:8181/api/v4/groups/202/protected_environments/production
Database Migration
shinya@shinya-B550-VISION-D:~/workspace/thin-gdk/services/rails/src$ tre bin/rails db:migrate:redo VERSION=20210520102039
INFO: This script is a predefined script in devkitkat.
== 20210520102039 GroupProtectedEnvironmentsAddColumn: reverting ==============
-- change_column_null(:protected_environments, :project_id, false)
-> 0.0013s
-- remove_column(:protected_environments, :group_id)
-> 0.0007s
== 20210520102039 GroupProtectedEnvironmentsAddColumn: reverted (0.0020s) =====
== 20210520102039 GroupProtectedEnvironmentsAddColumn: migrating ==============
-- add_column(:protected_environments, :group_id, :bigint)
-> 0.0012s
-- change_column_null(:protected_environments, :project_id, true)
-> 0.0005s
== 20210520102039 GroupProtectedEnvironmentsAddColumn: migrated (0.0017s) =====
shinya@shinya-B550-VISION-D:~/workspace/thin-gdk/services/rails/src$ tre bin/rails db:migrate:redo VERSION=20210601090039
INFO: This script is a predefined script in devkitkat.
== 20210601090039 GroupProtectedEnvironmentsAddIndexAndConstraint: reverting ==
-- execute(" DELETE FROM protected_environments WHERE group_id IS NOT NULL\n")
-> 0.0012s
-- execute("ALTER TABLE protected_environments\nDROP CONSTRAINT IF EXISTS protected_environments_project_or_group_existence\n")
-> 0.0011s
-- foreign_keys(:protected_environments)
-> 0.0019s
-- remove_foreign_key(:protected_environments, {:column=>:group_id})
-> 0.0036s
-- transaction_open?()
-> 0.0000s
-- indexes(:protected_environments)
-> 0.0017s
-- remove_index(:protected_environments, {:algorithm=>:concurrently, :name=>"index_protected_environments_on_group_id_and_name"})
-> 0.0013s
== 20210601090039 GroupProtectedEnvironmentsAddIndexAndConstraint: reverted (0.0175s)
== 20210601090039 GroupProtectedEnvironmentsAddIndexAndConstraint: migrating ==
-- transaction_open?()
-> 0.0000s
-- index_exists?(:protected_environments, [:group_id, :name], {:unique=>true, :name=>"index_protected_environments_on_group_id_and_name", :where=>"group_id IS NOT NULL", :algorithm=>:concurrently})
-> 0.0015s
-- add_index(:protected_environments, [:group_id, :name], {:unique=>true, :name=>"index_protected_environments_on_group_id_and_name", :where=>"group_id IS NOT NULL", :algorithm=>:concurrently})
-> 0.0040s
-- transaction_open?()
-> 0.0000s
-- foreign_keys(:protected_environments)
-> 0.0017s
-- execute("ALTER TABLE protected_environments\nADD CONSTRAINT fk_9e112565b7\nFOREIGN KEY (group_id)\nREFERENCES namespaces (id)\nON DELETE CASCADE\nNOT VALID;\n")
-> 0.0009s
-- execute("ALTER TABLE protected_environments VALIDATE CONSTRAINT fk_9e112565b7;")
-> 0.0014s
-- transaction_open?()
-> 0.0000s
-- current_schema()
-> 0.0001s
-- execute("ALTER TABLE protected_environments\nADD CONSTRAINT protected_environments_project_or_group_existence\nCHECK ( ((project_id IS NULL) != (group_id IS NULL)) )\nNOT VALID;\n")
-> 0.0005s
-- current_schema()
-> 0.0001s
-- execute("ALTER TABLE protected_environments VALIDATE CONSTRAINT protected_environments_project_or_group_existence;")
-> 0.0008s
== 20210601090039 GroupProtectedEnvironmentsAddIndexAndConstraint: migrated (0.0173s)
Does this MR meet the acceptance criteria?
Conformity
-
I have included a changelog entry, or it's not needed. (Does this MR need a changelog?) -
I have added/updated documentation, or it's not needed. (Is documentation required?) -
I have properly separated EE content from FOSS, or this MR is FOSS only. (Where should EE code go?) -
I have added information for database reviewers in the MR description, or it's not needed. (Does this MR have database related changes?) -
I have self-reviewed this MR per code review guidelines. -
This MR does not harm performance, or I have asked a reviewer to help assess the performance impact. (Merge request performance guidelines) -
I have followed the style guides.
Availability and Testing
-
I have added/updated tests following the Testing Guide, or it's not needed. (Consider all test levels. See the Test Planning Process.) -
I have tested this MR in all supported browsers, or it's not needed. -
I have informed the Infrastructure department of a default or new setting change per definition of done, or it's not needed.
Edited by Shinya Maeda