Skip to content
Snippets Groups Projects
Unverified Commit c4d5ac49 authored by Alishan Ladhani's avatar Alishan Ladhani
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
No related merge requests found
Showing
with 165 additions and 7 deletions
Loading
Loading
@@ -454,7 +454,7 @@ def cancelable?
end
 
def retryable?
return false if retried? || archived?
return false if retried? || archived? || deployment_rejected?
 
success? || failed? || canceled?
end
Loading
Loading
Loading
Loading
@@ -144,7 +144,7 @@ class CommitStatus < Ci::ApplicationRecord
end
 
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
 
event :success do
Loading
Loading
Loading
Loading
@@ -28,6 +28,7 @@ def self.failure_reasons
trace_size_exceeded: 19,
builds_disabled: 20,
environment_creation_failure: 21,
deployment_rejected: 22,
insufficient_bridge_permissions: 1_001,
downstream_bridge_project_not_found: 1_002,
invalid_bridge_trigger: 1_003,
Loading
Loading
Loading
Loading
@@ -46,9 +46,10 @@ class Deployment < ApplicationRecord
scope :for_project, -> (project_id) { where(project_id: project_id) }
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 :active, -> { where(status: %i[created running]) }
scope :upcoming, -> { where(status: %i[blocked running]) }
scope :older_than, -> (deployment) { where('deployments.id < ?', deployment.id) }
scope :with_api_entity_associations, -> { preload({ deployable: { runner: [], tags: [], user: [], job_artifacts_archive: [] } }) }
 
Loading
Loading
@@ -64,6 +65,10 @@ class Deployment < ApplicationRecord
transition created: :running
end
 
event :block do
transition created: :blocked
end
event :succeed do
transition any - [:success] => :success
end
Loading
Loading
@@ -136,7 +141,8 @@ class Deployment < ApplicationRecord
success: 2,
failed: 3,
canceled: 4,
skipped: 5
skipped: 5,
blocked: 6
}
 
def self.archivables_in(project, limit:)
Loading
Loading
@@ -387,6 +393,8 @@ def update_status!(status)
cancel!
when 'skipped'
skip!
when 'blocked'
block!
else
raise ArgumentError, "The status #{status.inspect} is invalid"
end
Loading
Loading
Loading
Loading
@@ -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_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
 
before_validation :generate_slug, if: ->(env) { env.slug.blank? }
Loading
Loading
Loading
Loading
@@ -29,7 +29,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
no_matching_runner: 'No matching runner available',
trace_size_exceeded: 'The job log size limit was reached',
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
 
TROUBLESHOOTING_DOC = {
Loading
Loading
---
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
Loading
Loading
@@ -13409,6 +13409,24 @@ CREATE SEQUENCE deploy_tokens_id_seq
 
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 (
deployment_id integer NOT NULL,
cluster_id integer NOT NULL,
Loading
Loading
@@ -18695,7 +18713,9 @@ CREATE TABLE protected_environments (
updated_at timestamp with time zone NOT NULL,
name character varying NOT NULL,
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
Loading
Loading
@@ -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 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 description_versions ALTER COLUMN id SET DEFAULT nextval('description_versions_id_seq'::regclass);
Loading
Loading
@@ -23062,6 +23084,9 @@ ALTER TABLE ONLY deploy_keys_projects
ALTER TABLE ONLY deploy_tokens
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
ADD CONSTRAINT deployment_clusters_pkey PRIMARY KEY (deployment_id);
 
Loading
Loading
@@ -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_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 INDEX index_deployment_merge_requests_on_merge_request_id ON deployment_merge_requests USING btree (merge_request_id);
Loading
Loading
@@ -28935,6 +28964,9 @@ ALTER TABLE ONLY lists
ALTER TABLE ONLY ci_unit_test_failures
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
ADD CONSTRAINT fk_0fd5b22688 FOREIGN KEY (pages_deployment_id) REFERENCES pages_deployments(id) ON DELETE SET NULL;
 
Loading
Loading
@@ -29043,6 +29075,9 @@ ALTER TABLE ONLY coverage_fuzzing_corpuses
ALTER TABLE ONLY agent_group_authorizations
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
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
Loading
Loading
@@ -7,10 +7,15 @@ module EE
# and be prepended in the `Deployment` model
module Deployment
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
 
prepended do
include UsageStatistics
 
delegate :needs_approval?, to: :environment
has_many :approvals, class_name: 'Deployments::Approval'
state_machine :status do
after_transition any => :success do |deployment|
deployment.run_after_commit do
Loading
Loading
@@ -25,5 +30,16 @@ module Deployment
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
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