mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-11 21:56:19 +10:00
Add environments and deployments This MR is a continuation of https://gitlab.com/gitlab-org/gitlab-ce/issues/17009. The current implementation is as follow: 1. We have two new tables: `environments` and `deployments`. 2. We have a new tab: `Environments` under `Pipelines` where you can see all you environments and add a new one. 3. We add a new option to `.gitlab-ci.yml` to track where we should create a deployment for environment. 4. If environment in `.gitlab-ci.yml` is specified it will create a deployment. **If environment does not exist it will be created.** (this got changed) 5. The deployment is always successful and shows the time of the action, in that case a build that presumably should do deployment. In the future we could extend deployment with statuses: success, failure. We could extend deployments with information that this is partial or full deployment. 6. User have to create environments that he will track first. 7. User can remove environments. 8. User can retry/rollback past deployment (in that case we retry past build). The new build when succeeds it will create a new deployment. 9. Currently environment have only one parameter: `name`. In the future it should have: `variables`, `credentials` and possibly `runners` and maybe other resources. 10. Currently deployment have this parameters: `sha`, `ref`, `deployable (in this case a build)`, `user (who triggered a deployment)`, `created_at`. The `.gitlab-ci.yml`: ``` deploy to production: stage: deploy script: dpl travis... environment: production ``` What needs to be done: - [x] Write initial implementation - [x] Improve implementation (@ayufan) - [x] Write tests (@ayufan) - [x] Improve UX of the forms (cc @markpundsack) - reviewed by @markpundsack - [x] Improve implementation of the views (cc @jschatz1) - done by @iamphill - [x] Write .gitlab-ci.yml documentation for `environments` - done by @ayufan - [ ] Write user documentation (@ayufan and @markpundsack) See merge request !4605
554 lines
14 KiB
Ruby
554 lines
14 KiB
Ruby
class Ability
|
|
class << self
|
|
def allowed(user, subject)
|
|
return anonymous_abilities(user, subject) if user.nil?
|
|
return [] unless user.is_a?(User)
|
|
return [] if user.blocked?
|
|
|
|
case subject
|
|
when CommitStatus then commit_status_abilities(user, subject)
|
|
when Project then project_abilities(user, subject)
|
|
when Issue then issue_abilities(user, subject)
|
|
when Note then note_abilities(user, subject)
|
|
when ProjectSnippet then project_snippet_abilities(user, subject)
|
|
when PersonalSnippet then personal_snippet_abilities(user, subject)
|
|
when MergeRequest then merge_request_abilities(user, subject)
|
|
when Group then group_abilities(user, subject)
|
|
when Namespace then namespace_abilities(user, subject)
|
|
when GroupMember then group_member_abilities(user, subject)
|
|
when ProjectMember then project_member_abilities(user, subject)
|
|
when User then user_abilities
|
|
when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project)
|
|
else []
|
|
end.concat(global_abilities(user))
|
|
end
|
|
|
|
# Given a list of users and a project this method returns the users that can
|
|
# read the given project.
|
|
def users_that_can_read_project(users, project)
|
|
if project.public?
|
|
users
|
|
else
|
|
users.select do |user|
|
|
if user.admin?
|
|
true
|
|
elsif project.internal? && !user.external?
|
|
true
|
|
elsif project.owner == user
|
|
true
|
|
elsif project.team.members.include?(user)
|
|
true
|
|
else
|
|
false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# List of possible abilities for anonymous user
|
|
def anonymous_abilities(user, subject)
|
|
if subject.is_a?(PersonalSnippet)
|
|
anonymous_personal_snippet_abilities(subject)
|
|
elsif subject.is_a?(ProjectSnippet)
|
|
anonymous_project_snippet_abilities(subject)
|
|
elsif subject.is_a?(CommitStatus)
|
|
anonymous_commit_status_abilities(subject)
|
|
elsif subject.is_a?(Project) || subject.respond_to?(:project)
|
|
anonymous_project_abilities(subject)
|
|
elsif subject.is_a?(Group) || subject.respond_to?(:group)
|
|
anonymous_group_abilities(subject)
|
|
elsif subject.is_a?(User)
|
|
anonymous_user_abilities
|
|
else
|
|
[]
|
|
end
|
|
end
|
|
|
|
def anonymous_project_abilities(subject)
|
|
project = if subject.is_a?(Project)
|
|
subject
|
|
else
|
|
subject.project
|
|
end
|
|
|
|
if project && project.public?
|
|
rules = [
|
|
:read_project,
|
|
:read_wiki,
|
|
:read_label,
|
|
:read_milestone,
|
|
:read_project_snippet,
|
|
:read_project_member,
|
|
:read_merge_request,
|
|
:read_note,
|
|
:read_pipeline,
|
|
:read_commit_status,
|
|
:read_container_image,
|
|
:download_code
|
|
]
|
|
|
|
# Allow to read builds by anonymous user if guests are allowed
|
|
rules << :read_build if project.public_builds?
|
|
|
|
# Allow to read issues by anonymous user if issue is not confidential
|
|
rules << :read_issue unless subject.is_a?(Issue) && subject.confidential?
|
|
|
|
rules - project_disabled_features_rules(project)
|
|
else
|
|
[]
|
|
end
|
|
end
|
|
|
|
def anonymous_commit_status_abilities(subject)
|
|
rules = anonymous_project_abilities(subject.project)
|
|
# If subject is Ci::Build which inherits from CommitStatus filter the abilities
|
|
rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
|
|
rules
|
|
end
|
|
|
|
def anonymous_group_abilities(subject)
|
|
rules = []
|
|
|
|
group = if subject.is_a?(Group)
|
|
subject
|
|
else
|
|
subject.group
|
|
end
|
|
|
|
rules << :read_group if group.public?
|
|
|
|
rules
|
|
end
|
|
|
|
def anonymous_personal_snippet_abilities(snippet)
|
|
if snippet.public?
|
|
[:read_personal_snippet]
|
|
else
|
|
[]
|
|
end
|
|
end
|
|
|
|
def anonymous_project_snippet_abilities(snippet)
|
|
if snippet.public?
|
|
[:read_project_snippet]
|
|
else
|
|
[]
|
|
end
|
|
end
|
|
|
|
def anonymous_user_abilities
|
|
[:read_user] unless restricted_public_level?
|
|
end
|
|
|
|
def global_abilities(user)
|
|
rules = []
|
|
rules << :create_group if user.can_create_group
|
|
rules << :read_users_list
|
|
rules
|
|
end
|
|
|
|
def project_abilities(user, project)
|
|
rules = []
|
|
key = "/user/#{user.id}/project/#{project.id}"
|
|
|
|
RequestStore.store[key] ||= begin
|
|
# Push abilities on the users team role
|
|
rules.push(*project_team_rules(project.team, user))
|
|
|
|
if project.owner == user ||
|
|
(project.group && project.group.has_owner?(user)) ||
|
|
user.admin?
|
|
|
|
rules.push(*project_owner_rules)
|
|
end
|
|
|
|
if project.public? || (project.internal? && !user.external?)
|
|
rules.push(*public_project_rules)
|
|
|
|
# Allow to read builds for internal projects
|
|
rules << :read_build if project.public_builds?
|
|
end
|
|
|
|
if project.archived?
|
|
rules -= project_archived_rules
|
|
end
|
|
|
|
rules - project_disabled_features_rules(project)
|
|
end
|
|
end
|
|
|
|
def project_team_rules(team, user)
|
|
# Rules based on role in project
|
|
if team.master?(user)
|
|
project_master_rules
|
|
elsif team.developer?(user)
|
|
project_dev_rules
|
|
elsif team.reporter?(user)
|
|
project_report_rules
|
|
elsif team.guest?(user)
|
|
project_guest_rules
|
|
else
|
|
[]
|
|
end
|
|
end
|
|
|
|
def public_project_rules
|
|
@public_project_rules ||= project_guest_rules + [
|
|
:download_code,
|
|
:fork_project,
|
|
:read_commit_status
|
|
]
|
|
end
|
|
|
|
def project_guest_rules
|
|
@project_guest_rules ||= [
|
|
:read_project,
|
|
:read_wiki,
|
|
:read_issue,
|
|
:read_label,
|
|
:read_milestone,
|
|
:read_project_snippet,
|
|
:read_project_member,
|
|
:read_merge_request,
|
|
:read_note,
|
|
:create_project,
|
|
:create_issue,
|
|
:create_note,
|
|
:upload_file
|
|
]
|
|
end
|
|
|
|
def project_report_rules
|
|
@project_report_rules ||= project_guest_rules + [
|
|
:download_code,
|
|
:fork_project,
|
|
:create_project_snippet,
|
|
:update_issue,
|
|
:admin_issue,
|
|
:admin_label,
|
|
:read_commit_status,
|
|
:read_build,
|
|
:read_container_image,
|
|
:read_pipeline,
|
|
:read_environment,
|
|
:read_deployment
|
|
]
|
|
end
|
|
|
|
def project_dev_rules
|
|
@project_dev_rules ||= project_report_rules + [
|
|
:admin_merge_request,
|
|
:update_merge_request,
|
|
:create_commit_status,
|
|
:update_commit_status,
|
|
:create_build,
|
|
:update_build,
|
|
:create_pipeline,
|
|
:update_pipeline,
|
|
:create_merge_request,
|
|
:create_wiki,
|
|
:push_code,
|
|
:create_container_image,
|
|
:update_container_image,
|
|
:create_environment,
|
|
:create_deployment
|
|
]
|
|
end
|
|
|
|
def project_archived_rules
|
|
@project_archived_rules ||= [
|
|
:create_merge_request,
|
|
:push_code,
|
|
:push_code_to_protected_branches,
|
|
:update_merge_request,
|
|
:admin_merge_request
|
|
]
|
|
end
|
|
|
|
def project_master_rules
|
|
@project_master_rules ||= project_dev_rules + [
|
|
:push_code_to_protected_branches,
|
|
:update_project_snippet,
|
|
:update_environment,
|
|
:update_deployment,
|
|
:admin_milestone,
|
|
:admin_project_snippet,
|
|
:admin_project_member,
|
|
:admin_merge_request,
|
|
:admin_note,
|
|
:admin_wiki,
|
|
:admin_project,
|
|
:admin_commit_status,
|
|
:admin_build,
|
|
:admin_container_image,
|
|
:admin_pipeline,
|
|
:admin_environment,
|
|
:admin_deployment
|
|
]
|
|
end
|
|
|
|
def project_owner_rules
|
|
@project_owner_rules ||= project_master_rules + [
|
|
:change_namespace,
|
|
:change_visibility_level,
|
|
:rename_project,
|
|
:remove_project,
|
|
:archive_project,
|
|
:remove_fork_project,
|
|
:destroy_merge_request,
|
|
:destroy_issue
|
|
]
|
|
end
|
|
|
|
def project_disabled_features_rules(project)
|
|
rules = []
|
|
|
|
unless project.issues_enabled
|
|
rules += named_abilities('issue')
|
|
end
|
|
|
|
unless project.merge_requests_enabled
|
|
rules += named_abilities('merge_request')
|
|
end
|
|
|
|
unless project.issues_enabled or project.merge_requests_enabled
|
|
rules += named_abilities('label')
|
|
rules += named_abilities('milestone')
|
|
end
|
|
|
|
unless project.snippets_enabled
|
|
rules += named_abilities('project_snippet')
|
|
end
|
|
|
|
unless project.wiki_enabled
|
|
rules += named_abilities('wiki')
|
|
end
|
|
|
|
unless project.builds_enabled
|
|
rules += named_abilities('build')
|
|
rules += named_abilities('pipeline')
|
|
rules += named_abilities('environment')
|
|
rules += named_abilities('deployment')
|
|
end
|
|
|
|
unless project.container_registry_enabled
|
|
rules += named_abilities('container_image')
|
|
end
|
|
|
|
rules
|
|
end
|
|
|
|
def group_abilities(user, group)
|
|
rules = []
|
|
rules << :read_group if can_read_group?(user, group)
|
|
|
|
# Only group masters and group owners can create new projects
|
|
if group.has_master?(user) || group.has_owner?(user) || user.admin?
|
|
rules += [
|
|
:create_projects,
|
|
:admin_milestones
|
|
]
|
|
end
|
|
|
|
# Only group owner and administrators can admin group
|
|
if group.has_owner?(user) || user.admin?
|
|
rules += [
|
|
:admin_group,
|
|
:admin_namespace,
|
|
:admin_group_member,
|
|
:change_visibility_level
|
|
]
|
|
end
|
|
|
|
rules.flatten
|
|
end
|
|
|
|
def can_read_group?(user, group)
|
|
return true if user.admin?
|
|
return true if group.public?
|
|
return true if group.internal? && !user.external?
|
|
return true if group.users.include?(user)
|
|
|
|
GroupProjectsFinder.new(group).execute(user).any?
|
|
end
|
|
|
|
def namespace_abilities(user, namespace)
|
|
rules = []
|
|
|
|
# Only namespace owner and administrators can admin it
|
|
if namespace.owner == user || user.admin?
|
|
rules += [
|
|
:create_projects,
|
|
:admin_namespace
|
|
]
|
|
end
|
|
|
|
rules.flatten
|
|
end
|
|
|
|
[:issue, :merge_request].each do |name|
|
|
define_method "#{name}_abilities" do |user, subject|
|
|
rules = []
|
|
|
|
if subject.author == user || (subject.respond_to?(:assignee) && subject.assignee == user)
|
|
rules += [
|
|
:"read_#{name}",
|
|
:"update_#{name}",
|
|
]
|
|
end
|
|
|
|
rules += project_abilities(user, subject.project)
|
|
rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue)
|
|
rules
|
|
end
|
|
end
|
|
|
|
def note_abilities(user, note)
|
|
rules = []
|
|
|
|
if note.author == user
|
|
rules += [
|
|
:read_note,
|
|
:update_note,
|
|
:admin_note
|
|
]
|
|
end
|
|
|
|
if note.respond_to?(:project) && note.project
|
|
rules += project_abilities(user, note.project)
|
|
end
|
|
|
|
rules
|
|
end
|
|
|
|
def personal_snippet_abilities(user, snippet)
|
|
rules = []
|
|
|
|
if snippet.author == user
|
|
rules += [
|
|
:read_personal_snippet,
|
|
:update_personal_snippet,
|
|
:admin_personal_snippet
|
|
]
|
|
end
|
|
|
|
if snippet.public? || (snippet.internal? && !user.external?)
|
|
rules << :read_personal_snippet
|
|
end
|
|
|
|
rules
|
|
end
|
|
|
|
def project_snippet_abilities(user, snippet)
|
|
rules = []
|
|
|
|
if snippet.author == user || user.admin?
|
|
rules += [
|
|
:read_project_snippet,
|
|
:update_project_snippet,
|
|
:admin_project_snippet
|
|
]
|
|
end
|
|
|
|
if snippet.public? || (snippet.internal? && !user.external?) || (snippet.private? && snippet.project.team.member?(user))
|
|
rules << :read_project_snippet
|
|
end
|
|
|
|
rules
|
|
end
|
|
|
|
def group_member_abilities(user, subject)
|
|
rules = []
|
|
target_user = subject.user
|
|
group = subject.group
|
|
|
|
unless group.last_owner?(target_user)
|
|
can_manage = group_abilities(user, group).include?(:admin_group_member)
|
|
|
|
if can_manage
|
|
rules << :update_group_member
|
|
rules << :destroy_group_member
|
|
elsif user == target_user
|
|
rules << :destroy_group_member
|
|
end
|
|
end
|
|
|
|
rules
|
|
end
|
|
|
|
def project_member_abilities(user, subject)
|
|
rules = []
|
|
target_user = subject.user
|
|
project = subject.project
|
|
|
|
unless target_user == project.owner
|
|
can_manage = project_abilities(user, project).include?(:admin_project_member)
|
|
|
|
if can_manage
|
|
rules << :update_project_member
|
|
rules << :destroy_project_member
|
|
elsif user == target_user
|
|
rules << :destroy_project_member
|
|
end
|
|
end
|
|
|
|
rules
|
|
end
|
|
|
|
def commit_status_abilities(user, subject)
|
|
rules = project_abilities(user, subject.project)
|
|
# If subject is Ci::Build which inherits from CommitStatus filter the abilities
|
|
rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
|
|
rules
|
|
end
|
|
|
|
def filter_build_abilities(rules)
|
|
# If we can't read build we should also not have that
|
|
# ability when looking at this in context of commit_status
|
|
%w(read create update admin).each do |rule|
|
|
rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build")
|
|
end
|
|
rules
|
|
end
|
|
|
|
def user_abilities
|
|
[:read_user]
|
|
end
|
|
|
|
def abilities
|
|
@abilities ||= begin
|
|
abilities = Six.new
|
|
abilities << self
|
|
abilities
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def restricted_public_level?
|
|
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
|
|
end
|
|
|
|
def named_abilities(name)
|
|
[
|
|
:"read_#{name}",
|
|
:"create_#{name}",
|
|
:"update_#{name}",
|
|
:"admin_#{name}"
|
|
]
|
|
end
|
|
|
|
def filter_confidential_issues_abilities(user, issue, rules)
|
|
return rules if user.admin? || !issue.confidential?
|
|
|
|
unless issue.author == user || issue.assignee == user || issue.project.team.member?(user, Gitlab::Access::REPORTER)
|
|
rules.delete(:admin_issue)
|
|
rules.delete(:read_issue)
|
|
rules.delete(:update_issue)
|
|
end
|
|
|
|
rules
|
|
end
|
|
end
|
|
end
|