2018-07-25 09:30:33 +00:00
# frozen_string_literal: true
2019-03-28 13:17:42 +00:00
class Environment < ApplicationRecord
2018-12-20 09:39:09 +00:00
include Gitlab :: Utils :: StrongMemoize
2019-05-22 18:55:15 +00:00
include ReactiveCaching
2020-03-25 06:07:58 +00:00
include FastDestroyAll :: Helpers
2020-10-13 06:09:09 +00:00
include Presentable
2021-07-23 06:08:47 +00:00
include NullifyIfBlank
2019-05-22 18:55:15 +00:00
2019-11-18 12:06:03 +00:00
self . reactive_cache_refresh_interval = 1 . minute
self . reactive_cache_lifetime = 55 . seconds
2020-02-10 15:08:54 +00:00
self . reactive_cache_hard_limit = 10 . megabytes
2020-04-24 21:09:48 +00:00
self . reactive_cache_work_type = :external_dependency
2019-11-18 12:06:03 +00:00
2018-02-16 00:37:33 +00:00
belongs_to :project , required : true
2016-06-10 21:36:54 +00:00
2020-03-25 06:07:58 +00:00
use_fast_destroy :all_deployments
2021-07-23 06:08:47 +00:00
nullify_if_blank :external_url
2020-03-25 06:07:58 +00:00
has_many :all_deployments , class_name : 'Deployment'
has_many :deployments , - > { visible }
2019-10-16 18:08:01 +00:00
has_many :successful_deployments , - > { success } , class_name : 'Deployment'
2020-02-17 09:08:52 +00:00
has_many :active_deployments , - > { active } , class_name : 'Deployment'
2020-02-06 18:08:54 +00:00
has_many :prometheus_alerts , inverse_of : :environment
2020-04-07 18:09:19 +00:00
has_many :metrics_dashboard_annotations , class_name : 'Metrics::Dashboard::Annotation' , inverse_of : :environment
2020-03-23 12:09:47 +00:00
has_many :self_managed_prometheus_alert_events , inverse_of : :environment
2020-06-26 18:09:03 +00:00
has_many :alert_management_alerts , class_name : 'AlertManagement::Alert' , inverse_of : :environment
2017-09-13 12:43:03 +00:00
2021-05-03 00:10:40 +00:00
has_one :last_deployment , - > { success . distinct_on_environment } , class_name : 'Deployment' , inverse_of : :environment
2019-11-14 03:06:25 +00:00
has_one :last_visible_deployment , - > { visible . distinct_on_environment } , inverse_of : :environment , class_name : 'Deployment'
2021-09-15 09:09:47 +00:00
has_one :last_visible_deployable , through : :last_visible_deployment , source : 'deployable' , source_type : 'CommitStatus' , disable_joins : - > { :: Feature . enabled? ( :environment_last_visible_pipeline_disable_joins , default_enabled : :yaml ) }
has_one :last_visible_pipeline , through : :last_visible_deployable , source : 'pipeline' , disable_joins : - > { :: Feature . enabled? ( :environment_last_visible_pipeline_disable_joins , default_enabled : :yaml ) }
2021-05-03 00:10:40 +00:00
has_one :upcoming_deployment , - > { running . distinct_on_environment } , class_name : 'Deployment' , inverse_of : :environment
2020-08-13 18:10:36 +00:00
has_one :latest_opened_most_severe_alert , - > { order_severity_with_open_prometheus_alert } , class_name : 'AlertManagement::Alert' , inverse_of : :environment
2016-06-10 21:36:54 +00:00
2016-12-08 01:09:18 +00:00
before_validation :generate_slug , if : - > ( env ) { env . slug . blank? }
2016-09-13 12:14:55 +00:00
before_save :set_environment_type
2021-03-05 00:09:24 +00:00
before_save :ensure_environment_tier
2019-05-22 18:55:15 +00:00
after_save :clear_reactive_cache!
2016-07-26 12:19:37 +00:00
2016-06-14 11:04:21 +00:00
validates :name ,
presence : true ,
2016-06-14 16:34:48 +00:00
uniqueness : { scope : :project_id } ,
2016-12-02 12:54:57 +00:00
length : { maximum : 255 } ,
2016-06-14 11:04:21 +00:00
format : { with : Gitlab :: Regex . environment_name_regex ,
message : Gitlab :: Regex . environment_name_regex_message }
2016-06-10 21:36:54 +00:00
2016-12-08 01:09:18 +00:00
validates :slug ,
presence : true ,
uniqueness : { scope : :project_id } ,
length : { maximum : 24 } ,
format : { with : Gitlab :: Regex . environment_slug_regex ,
message : Gitlab :: Regex . environment_slug_regex_message }
2016-07-26 07:35:47 +00:00
validates :external_url ,
2016-07-26 12:19:37 +00:00
length : { maximum : 255 } ,
allow_nil : true ,
2019-04-11 06:29:07 +00:00
addressable_url : true
2016-07-26 07:35:47 +00:00
2016-11-21 12:47:18 +00:00
delegate :stop_action , :manual_actions , to : :last_deployment , allow_nil : true
2020-11-20 06:09:10 +00:00
delegate :auto_rollback_enabled? , to : :project
2016-10-06 11:10:50 +00:00
2016-10-17 19:06:10 +00:00
scope :available , - > { with_state ( :available ) }
scope :stopped , - > { with_state ( :stopped ) }
2019-12-18 18:08:04 +00:00
2017-02-07 00:06:46 +00:00
scope :order_by_last_deployed_at , - > do
order ( Gitlab :: Database . nulls_first_order ( " ( #{ max_deployment_id_sql } ) " , 'ASC' ) )
end
2019-12-18 18:08:04 +00:00
scope :order_by_last_deployed_at_desc , - > do
order ( Gitlab :: Database . nulls_last_order ( " ( #{ max_deployment_id_sql } ) " , 'DESC' ) )
end
2020-10-19 06:09:08 +00:00
scope :order_by_name , - > { order ( 'environments.name ASC' ) }
2019-12-18 18:08:04 +00:00
2017-06-15 10:50:45 +00:00
scope :in_review_folder , - > { where ( environment_type : " review " ) }
2018-10-12 14:10:34 +00:00
scope :for_name , - > ( name ) { where ( name : name ) }
2019-08-07 04:40:29 +00:00
scope :preload_cluster , - > { preload ( last_deployment : :cluster ) }
2021-09-01 06:09:00 +00:00
scope :preload_project , - > { preload ( :project ) }
2020-02-14 00:09:07 +00:00
scope :auto_stoppable , - > ( limit ) { available . where ( 'auto_stop_at < ?' , Time . zone . now ) . limit ( limit ) }
2021-07-29 15:09:48 +00:00
scope :auto_deletable , - > ( limit ) { stopped . where ( 'auto_delete_at < ?' , Time . zone . now ) . limit ( limit ) }
2019-02-05 07:14:30 +00:00
##
# Search environments which have names like the given query.
# Do not set a large limit unless you've confirmed that it works on gitlab.com scale.
scope :for_name_like , - > ( query , limit : 5 ) do
2019-09-18 14:02:45 +00:00
where ( arel_table [ :name ] . matches ( " #{ sanitize_sql_like query } % " ) ) . limit ( limit )
2019-02-05 07:14:30 +00:00
end
2018-10-12 14:10:34 +00:00
scope :for_project , - > ( project ) { where ( project_id : project ) }
2021-04-01 21:09:22 +00:00
scope :for_tier , - > ( tier ) { where ( tier : tier ) . where . not ( tier : nil ) }
2018-11-07 05:29:16 +00:00
scope :with_deployment , - > ( sha ) { where ( 'EXISTS (?)' , Deployment . select ( 1 ) . where ( 'deployments.environment_id = environments.id' ) . where ( sha : sha ) ) }
2019-11-08 00:05:58 +00:00
scope :unfoldered , - > { where ( environment_type : nil ) }
2019-11-14 03:06:25 +00:00
scope :with_rank , - > do
select ( 'environments.*, rank() OVER (PARTITION BY project_id ORDER BY id DESC)' )
end
2020-10-02 18:08:56 +00:00
scope :for_id , - > ( id ) { where ( id : id ) }
2016-10-17 19:06:10 +00:00
2021-03-12 03:08:56 +00:00
scope :stopped_review_apps , - > ( before , limit ) do
stopped
. in_review_folder
. where ( " created_at < ? " , before )
. order ( " created_at ASC " )
. limit ( limit )
end
scope :scheduled_for_deletion , - > do
where . not ( auto_delete_at : nil )
end
scope :not_scheduled_for_deletion , - > do
where ( auto_delete_at : nil )
end
2021-03-05 00:09:24 +00:00
enum tier : {
production : 0 ,
staging : 1 ,
testing : 2 ,
development : 3 ,
other : 4
}
2016-10-17 10:45:31 +00:00
state_machine :state , initial : :available do
event :start do
transition stopped : :available
2016-10-06 11:10:50 +00:00
end
2016-10-17 10:45:31 +00:00
event :stop do
transition available : :stopped
2016-10-06 11:10:50 +00:00
end
2016-10-17 10:45:31 +00:00
state :available
state :stopped
2017-05-12 13:19:27 +00:00
2021-09-01 06:09:00 +00:00
before_transition any = > :stopped do | environment |
environment . auto_stop_at = nil
end
2017-05-12 13:19:27 +00:00
after_transition do | environment |
environment . expire_etag_cache
end
2016-10-06 11:10:50 +00:00
end
2020-02-20 15:08:44 +00:00
def self . for_id_and_slug ( id , slug )
find_by ( id : id , slug : slug )
end
2019-12-18 18:08:04 +00:00
def self . max_deployment_id_sql
Deployment . select ( Deployment . arel_table [ :id ] . maximum )
. where ( Deployment . arel_table [ :environment_id ] . eq ( arel_table [ :id ] ) )
. to_sql
end
2019-02-05 07:14:30 +00:00
def self . pluck_names
pluck ( :name )
end
2020-10-19 06:09:08 +00:00
def self . pluck_unique_names
pluck ( 'DISTINCT(environments.name)' )
end
2019-10-16 18:08:01 +00:00
def self . find_or_create_by_name ( name )
find_or_create_by ( name : name )
end
2020-04-08 00:09:30 +00:00
def self . valid_states
self . state_machine . states . map ( & :name )
end
2021-03-12 03:08:56 +00:00
def self . schedule_to_delete ( at_time = 1 . week . from_now )
update_all ( auto_delete_at : at_time )
end
2020-02-14 00:09:07 +00:00
class << self
2020-04-28 09:09:34 +00:00
def count_by_state
environments_count_by_state = group ( :state ) . count
valid_states . each_with_object ( { } ) do | state , count_hash |
count_hash [ state ] = environments_count_by_state [ state . to_s ] || 0
end
end
2020-02-14 00:09:07 +00:00
end
2021-09-15 09:09:47 +00:00
def last_deployable
last_deployment & . deployable
end
# NOTE: Below assocation overrides is a workaround for issue https://gitlab.com/gitlab-org/gitlab/-/issues/339908
# It helps to avoid cross joins with the CI database.
# Caveat: It also overrides and losses the default AR caching mechanism.
# Read - https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68870#note_677227727
# NOTE: Association Preloads does not use the overriden definitions below.
# Association Preloads when preloading uses the original definitions from the relationships above.
# https://github.com/rails/rails/blob/75ac626c4e21129d8296d4206a1960563cc3d4aa/activerecord/lib/active_record/associations/preloader.rb#L158
# But after preloading, when they are called it is using the overriden methods below.
# So we are checking for `association_cached?(:association_name)` in the overridden methods and calling `super` which inturn fetches the preloaded values.
# Overriding association
def last_visible_deployable
return super if association_cached? ( :last_visible_deployable ) || :: Feature . disabled? ( :environment_last_visible_pipeline_disable_joins , default_enabled : :yaml )
last_visible_deployment & . deployable
end
# Overriding association
def last_visible_pipeline
return super if association_cached? ( :last_visible_pipeline ) || :: Feature . disabled? ( :environment_last_visible_pipeline_disable_joins , default_enabled : :yaml )
last_visible_deployable & . pipeline
end
2020-02-06 18:08:54 +00:00
def clear_prometheus_reactive_cache! ( query_name )
cluster_prometheus_adapter & . clear_prometheus_reactive_cache! ( query_name , self )
end
def cluster_prometheus_adapter
@cluster_prometheus_adapter || = :: Gitlab :: Prometheus :: Adapter . new ( project , deployment_platform & . cluster ) . cluster_prometheus_adapter
end
2016-12-08 16:21:16 +00:00
def predefined_variables
2018-03-14 10:15:18 +00:00
Gitlab :: Ci :: Variables :: Collection . new
. append ( key : 'CI_ENVIRONMENT_NAME' , value : name )
. append ( key : 'CI_ENVIRONMENT_SLUG' , value : slug )
2021-06-09 21:10:34 +00:00
. append ( key : 'CI_ENVIRONMENT_TIER' , value : tier )
2016-12-08 16:21:16 +00:00
end
2016-11-16 09:54:20 +00:00
def recently_updated_on_branch? ( ref )
2016-11-16 10:26:36 +00:00
ref . to_s == last_deployment . try ( :ref )
2016-11-15 13:48:14 +00:00
end
2016-09-13 12:14:55 +00:00
def set_environment_type
names = name . split ( '/' )
2017-08-21 15:46:45 +00:00
self . environment_type = names . many? ? names . first : nil
2016-09-13 12:14:55 +00:00
end
2016-08-09 13:11:14 +00:00
def includes_commit? ( commit )
2016-08-03 11:37:39 +00:00
return false unless last_deployment
2016-08-02 12:01:22 +00:00
2016-08-09 13:11:14 +00:00
last_deployment . includes_commit? ( commit )
2016-08-02 12:01:22 +00:00
end
2016-09-20 09:36:54 +00:00
2017-01-31 00:26:40 +00:00
def last_deployed_at
2018-11-07 05:36:42 +00:00
last_deployment . try ( :created_at )
2017-01-31 00:26:40 +00:00
end
2016-09-30 13:45:27 +00:00
def ref_path
2017-11-02 22:32:22 +00:00
" refs/ #{ Repository :: REF_ENVIRONMENTS } / #{ slug } "
2016-09-30 13:45:27 +00:00
end
2016-10-12 13:21:01 +00:00
def formatted_external_url
2019-02-08 12:19:53 +00:00
return unless external_url
2016-10-12 13:21:01 +00:00
2018-01-27 05:35:53 +00:00
external_url . gsub ( %r{ \ A.*?:// } , '' )
2016-10-12 13:21:01 +00:00
end
2016-10-17 14:13:19 +00:00
2018-07-12 10:22:11 +00:00
def stop_action_available?
2016-10-17 14:13:19 +00:00
available? && stop_action . present?
end
2016-10-18 10:02:50 +00:00
2020-06-26 12:08:51 +00:00
def cancel_deployment_jobs!
2020-07-09 00:09:11 +00:00
jobs = active_deployments . with_deployable
jobs . each do | deployment |
2021-03-02 15:10:57 +00:00
Gitlab :: OptimisticLocking . retry_lock ( deployment . deployable , name : 'environment_cancel_deployment_jobs' ) do | deployable |
2020-07-09 00:09:11 +00:00
deployable . cancel! if deployable & . cancelable?
end
2021-04-26 12:09:44 +00:00
rescue StandardError = > e
2020-07-09 00:09:11 +00:00
Gitlab :: ErrorTracking . track_exception ( e , environment_id : id , deployment_id : deployment . id )
end
2020-06-26 12:08:51 +00:00
end
2017-02-06 15:50:03 +00:00
def stop_with_action! ( current_user )
2016-11-10 12:59:26 +00:00
return unless available?
2016-10-18 10:02:50 +00:00
2017-02-06 15:50:03 +00:00
stop!
2017-02-13 12:02:53 +00:00
stop_action & . play ( current_user )
2016-10-18 10:02:50 +00:00
end
2016-11-21 12:47:18 +00:00
2019-12-10 07:53:40 +00:00
def reset_auto_stop
update_column ( :auto_stop_at , nil )
end
2016-11-21 12:47:18 +00:00
def actions_for ( environment )
return [ ] unless manual_actions
2016-11-21 16:26:35 +00:00
manual_actions . select do | action |
action . expanded_environment_name == environment
2016-11-10 12:59:26 +00:00
end
2016-10-18 10:02:50 +00:00
end
2016-12-08 01:09:18 +00:00
2016-11-22 19:55:56 +00:00
def has_terminals?
2019-06-19 11:59:47 +00:00
available? && deployment_platform . present? && last_deployment . present?
2016-11-22 19:55:56 +00:00
end
def terminals
2019-05-22 18:55:15 +00:00
with_reactive_cache do | data |
deployment_platform . terminals ( self , data )
end
end
def calculate_reactive_cache
return unless has_terminals? && ! project . pending_delete?
deployment_platform . calculate_reactive_cache_for ( self )
end
def deployment_namespace
strong_memoize ( :kubernetes_namespace ) do
2019-08-07 04:40:29 +00:00
deployment_platform . cluster . kubernetes_namespace_for ( self ) if deployment_platform
2019-05-22 18:55:15 +00:00
end
2016-11-22 19:55:56 +00:00
end
2017-03-07 16:57:42 +00:00
def has_metrics?
2019-12-24 03:07:52 +00:00
available? && ( prometheus_adapter & . configured? || has_sample_metrics? )
end
def has_sample_metrics?
! ! ENV [ 'USE_SAMPLE_METRICS' ]
2017-03-07 16:57:42 +00:00
end
2020-08-13 18:10:36 +00:00
def has_opened_alert?
latest_opened_most_severe_alert . present?
end
2020-11-09 03:09:03 +00:00
def has_running_deployments?
all_deployments . running . exists?
end
2017-03-07 16:57:42 +00:00
def metrics
2019-12-20 12:07:40 +00:00
prometheus_adapter . query ( :environment , self ) if has_metrics_and_can_query?
2017-06-07 00:36:59 +00:00
end
2019-03-15 11:20:59 +00:00
def additional_metrics ( * args )
2019-12-20 12:07:40 +00:00
return unless has_metrics_and_can_query?
2019-03-15 11:20:59 +00:00
prometheus_adapter . query ( :additional_metrics_environment , self , * args . map ( & :to_f ) )
2017-05-10 09:25:30 +00:00
end
2018-03-05 18:34:59 +00:00
def prometheus_adapter
2020-01-08 09:07:53 +00:00
@prometheus_adapter || = Gitlab :: Prometheus :: Adapter . new ( project , deployment_platform & . cluster ) . prometheus_adapter
2018-03-05 18:34:59 +00:00
end
2017-11-02 22:32:22 +00:00
def slug
super . presence || generate_slug
end
2017-01-29 19:38:00 +00:00
def external_url_for ( path , commit_sha )
return unless self . external_url
public_path = project . public_path_for_source_path ( path , commit_sha )
return unless public_path
2019-08-01 09:56:33 +00:00
[ external_url . delete_suffix ( '/' ) , public_path . delete_prefix ( '/' ) ] . join ( '/' )
2017-01-29 19:38:00 +00:00
end
2017-05-12 13:19:27 +00:00
def expire_etag_cache
Gitlab :: EtagCaching :: Store . new . tap do | store |
2017-05-30 21:56:26 +00:00
store . touch ( etag_cache_key )
2017-05-12 13:19:27 +00:00
end
end
2017-05-30 21:56:26 +00:00
def etag_cache_key
2017-06-29 17:06:35 +00:00
Gitlab :: Routing . url_helpers . project_environments_path (
2017-06-12 07:09:51 +00:00
project ,
format : :json )
2017-05-30 21:56:26 +00:00
end
2017-08-21 15:46:45 +00:00
def folder_name
self . environment_type || self . name
end
2019-02-26 19:13:09 +00:00
def name_without_type
@name_without_type || = name . delete_prefix ( " #{ environment_type } / " )
end
2018-02-26 18:57:11 +00:00
def deployment_platform
2018-12-20 09:39:09 +00:00
strong_memoize ( :deployment_platform ) do
project . deployment_platform ( environment : self . name )
end
2018-02-26 18:57:11 +00:00
end
2019-08-07 04:40:29 +00:00
def knative_services_finder
if last_deployment & . cluster
Clusters :: KnativeServicesFinder . new ( last_deployment . cluster , self )
end
end
2019-12-10 07:53:40 +00:00
def auto_stop_in
2020-05-22 09:08:09 +00:00
auto_stop_at - Time . current if auto_stop_at
2019-12-10 07:53:40 +00:00
end
def auto_stop_in = ( value )
return unless value
return unless parsed_result = ChronicDuration . parse ( value )
self . auto_stop_at = parsed_result . seconds . from_now
end
2020-03-11 15:09:37 +00:00
def elastic_stack_available?
2021-05-14 03:10:11 +00:00
! ! deployment_platform & . cluster & . elastic_stack_available?
2020-03-11 15:09:37 +00:00
end
2020-11-30 18:09:46 +00:00
def rollout_status
return unless rollout_status_available?
result = rollout_status_with_reactive_cache
result || :: Gitlab :: Kubernetes :: RolloutStatus . loading
end
def ingresses
return unless rollout_status_available?
deployment_platform . ingresses ( deployment_namespace )
end
def patch_ingress ( ingress , data )
return unless rollout_status_available?
deployment_platform . patch_ingress ( deployment_namespace , ingress , data )
end
2021-01-05 12:10:36 +00:00
def clear_all_caches
expire_etag_cache
clear_reactive_cache!
end
2016-12-08 01:09:18 +00:00
private
2020-11-30 18:09:46 +00:00
def rollout_status_available?
has_terminals?
end
def rollout_status_with_reactive_cache
with_reactive_cache do | data |
deployment_platform . rollout_status ( self , data )
end
end
2019-12-20 12:07:40 +00:00
def has_metrics_and_can_query?
has_metrics? && prometheus_adapter . can_query?
end
2019-07-10 03:55:32 +00:00
def generate_slug
self . slug = Gitlab :: Slug :: Environment . new ( name ) . generate
2016-12-08 01:09:18 +00:00
end
2021-03-05 00:09:24 +00:00
def ensure_environment_tier
self . tier || = guess_tier
end
# Guessing the tier of the environment if it's not explicitly specified by users.
# See https://en.wikipedia.org/wiki/Deployment_environment for industry standard deployment environments
def guess_tier
case name
when %r{ dev|review|trunk }i then self . class . tiers [ :development ]
when %r{ test|qc }i then self . class . tiers [ :testing ]
when %r{ st(a|)g|mod(e|)l|pre|demo }i then self . class . tiers [ :staging ]
when %r{ pr(o|)d|live }i then self . class . tiers [ :production ]
else self . class . tiers [ :other ]
end
end
2016-06-10 21:36:54 +00:00
end
2019-09-13 13:26:31 +00:00
2021-05-11 21:10:21 +00:00
Environment . prepend_mod_with ( 'Environment' )