Skip to content
Snippets Groups Projects
Verified Commit c4d5ac49 authored by Alishan Ladhani's avatar Alishan Ladhani :bangbang:
Browse files

Create data model for Deployment Approvals

This is the first MR working towards an MVC for the
new Deployment Approvals feature.

Changelog: added
parent 113dc497
No related branches found
No related tags found
1 merge request!74932Deployment Approvals data model
This commit is part of merge request !74932. Comments created here will be created in the context of that merge request.
Showing
with 165 additions and 7 deletions
...@@ -454,7 +454,7 @@ def cancelable? ...@@ -454,7 +454,7 @@ def cancelable?
end end
def retryable? def retryable?
return false if retried? || archived? return false if retried? || archived? || deployment_rejected?
success? || failed? || canceled? success? || failed? || canceled?
end end
......
...@@ -144,7 +144,7 @@ class CommitStatus < Ci::ApplicationRecord ...@@ -144,7 +144,7 @@ class CommitStatus < Ci::ApplicationRecord
end end
event :drop do event :drop do
transition [:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled] => :failed transition [:created, :waiting_for_resource, :preparing, :pending, :running, :manual, :scheduled] => :failed
end end
event :success do event :success do
......
...@@ -28,6 +28,7 @@ def self.failure_reasons ...@@ -28,6 +28,7 @@ def self.failure_reasons
trace_size_exceeded: 19, trace_size_exceeded: 19,
builds_disabled: 20, builds_disabled: 20,
environment_creation_failure: 21, environment_creation_failure: 21,
deployment_rejected: 22,
insufficient_bridge_permissions: 1_001, insufficient_bridge_permissions: 1_001,
downstream_bridge_project_not_found: 1_002, downstream_bridge_project_not_found: 1_002,
invalid_bridge_trigger: 1_003, invalid_bridge_trigger: 1_003,
......
...@@ -46,9 +46,10 @@ class Deployment < ApplicationRecord ...@@ -46,9 +46,10 @@ class Deployment < ApplicationRecord
scope :for_project, -> (project_id) { where(project_id: project_id) } scope :for_project, -> (project_id) { where(project_id: project_id) }
scope :for_projects, -> (projects) { where(project: projects) } scope :for_projects, -> (projects) { where(project: projects) }
scope :visible, -> { where(status: %i[running success failed canceled]) } scope :visible, -> { where(status: %i[running success failed canceled blocked]) }
scope :stoppable, -> { where.not(on_stop: nil).where.not(deployable_id: nil).success } scope :stoppable, -> { where.not(on_stop: nil).where.not(deployable_id: nil).success }
scope :active, -> { where(status: %i[created running]) } scope :active, -> { where(status: %i[created running]) }
scope :upcoming, -> { where(status: %i[blocked running]) }
scope :older_than, -> (deployment) { where('deployments.id < ?', deployment.id) } scope :older_than, -> (deployment) { where('deployments.id < ?', deployment.id) }
scope :with_api_entity_associations, -> { preload({ deployable: { runner: [], tags: [], user: [], job_artifacts_archive: [] } }) } scope :with_api_entity_associations, -> { preload({ deployable: { runner: [], tags: [], user: [], job_artifacts_archive: [] } }) }
...@@ -64,6 +65,10 @@ class Deployment < ApplicationRecord ...@@ -64,6 +65,10 @@ class Deployment < ApplicationRecord
transition created: :running transition created: :running
end end
event :block do
transition created: :blocked
end
event :succeed do event :succeed do
transition any - [:success] => :success transition any - [:success] => :success
end end
...@@ -136,7 +141,8 @@ class Deployment < ApplicationRecord ...@@ -136,7 +141,8 @@ class Deployment < ApplicationRecord
success: 2, success: 2,
failed: 3, failed: 3,
canceled: 4, canceled: 4,
skipped: 5 skipped: 5,
blocked: 6
} }
def self.archivables_in(project, limit:) def self.archivables_in(project, limit:)
...@@ -387,6 +393,8 @@ def update_status!(status) ...@@ -387,6 +393,8 @@ def update_status!(status)
cancel! cancel!
when 'skipped' when 'skipped'
skip! skip!
when 'blocked'
block!
else else
raise ArgumentError, "The status #{status.inspect} is invalid" raise ArgumentError, "The status #{status.inspect} is invalid"
end end
......
...@@ -31,7 +31,7 @@ class Environment < ApplicationRecord ...@@ -31,7 +31,7 @@ class Environment < ApplicationRecord
has_one :last_visible_deployable, through: :last_visible_deployment, source: 'deployable', source_type: 'CommitStatus', disable_joins: true has_one :last_visible_deployable, through: :last_visible_deployment, source: 'deployable', source_type: 'CommitStatus', disable_joins: true
has_one :last_visible_pipeline, through: :last_visible_deployable, source: 'pipeline', disable_joins: true has_one :last_visible_pipeline, through: :last_visible_deployable, source: 'pipeline', disable_joins: true
has_one :upcoming_deployment, -> { running.distinct_on_environment }, class_name: 'Deployment', inverse_of: :environment has_one :upcoming_deployment, -> { upcoming.distinct_on_environment }, class_name: 'Deployment', inverse_of: :environment
has_one :latest_opened_most_severe_alert, -> { order_severity_with_open_prometheus_alert }, class_name: 'AlertManagement::Alert', inverse_of: :environment has_one :latest_opened_most_severe_alert, -> { order_severity_with_open_prometheus_alert }, class_name: 'AlertManagement::Alert', inverse_of: :environment
before_validation :generate_slug, if: ->(env) { env.slug.blank? } before_validation :generate_slug, if: ->(env) { env.slug.blank? }
......
...@@ -29,7 +29,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated ...@@ -29,7 +29,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
no_matching_runner: 'No matching runner available', no_matching_runner: 'No matching runner available',
trace_size_exceeded: 'The job log size limit was reached', trace_size_exceeded: 'The job log size limit was reached',
builds_disabled: 'The CI/CD is disabled for this project', builds_disabled: 'The CI/CD is disabled for this project',
environment_creation_failure: 'This job could not be executed because it would create an environment with an invalid parameter.' environment_creation_failure: 'This job could not be executed because it would create an environment with an invalid parameter.',
deployment_rejected: 'This deployment job was rejected.'
}.freeze }.freeze
TROUBLESHOOTING_DOC = { TROUBLESHOOTING_DOC = {
......
---
name: deployment_approvals
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74932
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/347342
milestone: '14.6'
type: development
group: group::release
default_enabled: false
# frozen_string_literal: true
class AddRequiredApprovalCountToProtectedEnvironments < Gitlab::Database::Migration[1.0]
def change
add_column :protected_environments, :required_approval_count, :integer, default: 0, null: false
end
end
# frozen_string_literal: true
class CreateDeploymentApprovals < Gitlab::Database::Migration[1.0]
def change
create_table :deployment_approvals do |t|
t.bigint :deployment_id, null: false
t.bigint :user_id, null: false, index: true
t.timestamps_with_timezone null: false
t.integer :status, limit: 2, null: false
t.index [:deployment_id, :user_id], unique: true
end
end
end
# frozen_string_literal: true
class AddUserForeignKeyToDeploymentApprovals < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
add_concurrent_foreign_key :deployment_approvals, :users, column: :user_id
end
def down
with_lock_retries do
remove_foreign_key :deployment_approvals, :users
end
end
end
# frozen_string_literal: true
class AddDeploymentForeignKeyToDeploymentApprovals < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
add_concurrent_foreign_key :deployment_approvals, :deployments, column: :deployment_id
end
def down
with_lock_retries do
remove_foreign_key :deployment_approvals, :deployments
end
end
end
# frozen_string_literal: true
class AddProtectedEnvironmentsRequiredApprovalCountCheckConstraint < Gitlab::Database::Migration[1.0]
CONSTRAINT_NAME = 'protected_environments_required_approval_count_positive'
disable_ddl_transaction!
def up
add_check_constraint :protected_environments, 'required_approval_count >= 0', CONSTRAINT_NAME
end
def down
remove_check_constraint :protected_environments, CONSTRAINT_NAME
end
end
ac2e376ad32f0e2fd45d8695f13a0b46c2d5964b881f79e3a30a51ac85d4359b
\ No newline at end of file
caaf92f12bf0ed144d99f629c9e5d64fd45832a90bbd743e40febcdc4802cd59
\ No newline at end of file
ac21109099642d5934c16b3f0130736a587c4f20143552545c2b524062ff71e0
\ No newline at end of file
61c949b42338b248a0950cfafc82d58816c3fec44a2bf41c4ecb4cf09340a424
\ No newline at end of file
d1ed3ddf51c0bcebbac2a8dee05aa168daa35129110a463ac296ff2e640b0dbd
\ No newline at end of file
...@@ -13409,6 +13409,24 @@ CREATE SEQUENCE deploy_tokens_id_seq ...@@ -13409,6 +13409,24 @@ CREATE SEQUENCE deploy_tokens_id_seq
   
ALTER SEQUENCE deploy_tokens_id_seq OWNED BY deploy_tokens.id; ALTER SEQUENCE deploy_tokens_id_seq OWNED BY deploy_tokens.id;
   
CREATE TABLE deployment_approvals (
id bigint NOT NULL,
deployment_id bigint NOT NULL,
user_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
status smallint NOT NULL
);
CREATE SEQUENCE deployment_approvals_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE deployment_approvals_id_seq OWNED BY deployment_approvals.id;
CREATE TABLE deployment_clusters ( CREATE TABLE deployment_clusters (
deployment_id integer NOT NULL, deployment_id integer NOT NULL,
cluster_id integer NOT NULL, cluster_id integer NOT NULL,
...@@ -18695,7 +18713,9 @@ CREATE TABLE protected_environments ( ...@@ -18695,7 +18713,9 @@ CREATE TABLE protected_environments (
updated_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL,
name character varying NOT NULL, name character varying NOT NULL,
group_id bigint, group_id bigint,
CONSTRAINT protected_environments_project_or_group_existence CHECK (((project_id IS NULL) <> (group_id IS NULL))) required_approval_count integer DEFAULT 0 NOT NULL,
CONSTRAINT protected_environments_project_or_group_existence CHECK (((project_id IS NULL) <> (group_id IS NULL))),
CONSTRAINT protected_environments_required_approval_count_positive CHECK ((required_approval_count >= 0))
); );
   
CREATE SEQUENCE protected_environments_id_seq CREATE SEQUENCE protected_environments_id_seq
...@@ -21495,6 +21515,8 @@ ALTER TABLE ONLY deploy_keys_projects ALTER COLUMN id SET DEFAULT nextval('deplo ...@@ -21495,6 +21515,8 @@ ALTER TABLE ONLY deploy_keys_projects ALTER COLUMN id SET DEFAULT nextval('deplo
   
ALTER TABLE ONLY deploy_tokens ALTER COLUMN id SET DEFAULT nextval('deploy_tokens_id_seq'::regclass); ALTER TABLE ONLY deploy_tokens ALTER COLUMN id SET DEFAULT nextval('deploy_tokens_id_seq'::regclass);
   
ALTER TABLE ONLY deployment_approvals ALTER COLUMN id SET DEFAULT nextval('deployment_approvals_id_seq'::regclass);
ALTER TABLE ONLY deployments ALTER COLUMN id SET DEFAULT nextval('deployments_id_seq'::regclass); ALTER TABLE ONLY deployments ALTER COLUMN id SET DEFAULT nextval('deployments_id_seq'::regclass);
   
ALTER TABLE ONLY description_versions ALTER COLUMN id SET DEFAULT nextval('description_versions_id_seq'::regclass); ALTER TABLE ONLY description_versions ALTER COLUMN id SET DEFAULT nextval('description_versions_id_seq'::regclass);
...@@ -23062,6 +23084,9 @@ ALTER TABLE ONLY deploy_keys_projects ...@@ -23062,6 +23084,9 @@ ALTER TABLE ONLY deploy_keys_projects
ALTER TABLE ONLY deploy_tokens ALTER TABLE ONLY deploy_tokens
ADD CONSTRAINT deploy_tokens_pkey PRIMARY KEY (id); ADD CONSTRAINT deploy_tokens_pkey PRIMARY KEY (id);
   
ALTER TABLE ONLY deployment_approvals
ADD CONSTRAINT deployment_approvals_pkey PRIMARY KEY (id);
ALTER TABLE ONLY deployment_clusters ALTER TABLE ONLY deployment_clusters
ADD CONSTRAINT deployment_clusters_pkey PRIMARY KEY (deployment_id); ADD CONSTRAINT deployment_clusters_pkey PRIMARY KEY (deployment_id);
   
...@@ -25804,6 +25829,10 @@ CREATE INDEX index_deploy_tokens_on_token_and_expires_at_and_id ON deploy_tokens ...@@ -25804,6 +25829,10 @@ CREATE INDEX index_deploy_tokens_on_token_and_expires_at_and_id ON deploy_tokens
   
CREATE UNIQUE INDEX index_deploy_tokens_on_token_encrypted ON deploy_tokens USING btree (token_encrypted); CREATE UNIQUE INDEX index_deploy_tokens_on_token_encrypted ON deploy_tokens USING btree (token_encrypted);
   
CREATE UNIQUE INDEX index_deployment_approvals_on_deployment_id_and_user_id ON deployment_approvals USING btree (deployment_id, user_id);
CREATE INDEX index_deployment_approvals_on_user_id ON deployment_approvals USING btree (user_id);
CREATE UNIQUE INDEX index_deployment_clusters_on_cluster_id_and_deployment_id ON deployment_clusters USING btree (cluster_id, deployment_id); CREATE UNIQUE INDEX index_deployment_clusters_on_cluster_id_and_deployment_id ON deployment_clusters USING btree (cluster_id, deployment_id);
   
CREATE INDEX index_deployment_merge_requests_on_merge_request_id ON deployment_merge_requests USING btree (merge_request_id); CREATE INDEX index_deployment_merge_requests_on_merge_request_id ON deployment_merge_requests USING btree (merge_request_id);
...@@ -28935,6 +28964,9 @@ ALTER TABLE ONLY lists ...@@ -28935,6 +28964,9 @@ ALTER TABLE ONLY lists
ALTER TABLE ONLY ci_unit_test_failures ALTER TABLE ONLY ci_unit_test_failures
ADD CONSTRAINT fk_0f09856e1f FOREIGN KEY (build_id) REFERENCES ci_builds(id) ON DELETE CASCADE; ADD CONSTRAINT fk_0f09856e1f FOREIGN KEY (build_id) REFERENCES ci_builds(id) ON DELETE CASCADE;
   
ALTER TABLE ONLY deployment_approvals
ADD CONSTRAINT fk_0f58311058 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY project_pages_metadata ALTER TABLE ONLY project_pages_metadata
ADD CONSTRAINT fk_0fd5b22688 FOREIGN KEY (pages_deployment_id) REFERENCES pages_deployments(id) ON DELETE SET NULL; ADD CONSTRAINT fk_0fd5b22688 FOREIGN KEY (pages_deployment_id) REFERENCES pages_deployments(id) ON DELETE SET NULL;
   
...@@ -29043,6 +29075,9 @@ ALTER TABLE ONLY coverage_fuzzing_corpuses ...@@ -29043,6 +29075,9 @@ ALTER TABLE ONLY coverage_fuzzing_corpuses
ALTER TABLE ONLY agent_group_authorizations ALTER TABLE ONLY agent_group_authorizations
ADD CONSTRAINT fk_2c9f941965 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; ADD CONSTRAINT fk_2c9f941965 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
   
ALTER TABLE ONLY deployment_approvals
ADD CONSTRAINT fk_2d060dfc73 FOREIGN KEY (deployment_id) REFERENCES deployments(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_freeze_periods ALTER TABLE ONLY ci_freeze_periods
ADD CONSTRAINT fk_2e02bbd1a6 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; ADD CONSTRAINT fk_2e02bbd1a6 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
   
# frozen_string_literal: true
module Deployments
class Approval < ApplicationRecord
self.table_name = 'deployment_approvals'
belongs_to :deployment
belongs_to :user
validates :user, presence: true, uniqueness: { scope: :deployment_id }
validates :deployment, presence: true
validates :status, presence: true
enum status: {
approved: 0,
rejected: 1
}
end
end
...@@ -7,10 +7,15 @@ module EE ...@@ -7,10 +7,15 @@ module EE
# and be prepended in the `Deployment` model # and be prepended in the `Deployment` model
module Deployment module Deployment
extend ActiveSupport::Concern extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
prepended do prepended do
include UsageStatistics include UsageStatistics
delegate :needs_approval?, to: :environment
has_many :approvals, class_name: 'Deployments::Approval'
state_machine :status do state_machine :status do
after_transition any => :success do |deployment| after_transition any => :success do |deployment|
deployment.run_after_commit do deployment.run_after_commit do
...@@ -25,5 +30,16 @@ module Deployment ...@@ -25,5 +30,16 @@ module Deployment
end end
end end
end end
override :sync_status_with
def sync_status_with(build)
return update_status!('blocked') if build.status == 'manual' && needs_approval?
super
end
def pending_approval_count
environment.required_approval_count - approvals.approved.count
end
end end
end end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment