From 689eca0b0f35528fb4c17f74bdaa90331934e55d Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 7 Nov 2018 15:37:25 +0900 Subject: Revert "Revert add action column changes" This reverts commit 9c811566f85d18bc9eb4a85c6a343cf1bfa4fbd2. --- app/controllers/projects/deployments_controller.rb | 4 +- .../projects/environments_controller.rb | 2 +- app/finders/environments_finder.rb | 2 +- app/models/concerns/deployable.rb | 5 +- app/models/deployment.rb | 21 +- app/models/environment.rb | 15 +- app/models/environment_status.rb | 2 +- app/models/project.rb | 2 +- app/services/start_environment_service.rb | 50 +++++ app/services/stop_environment_service.rb | 18 ++ app/services/update_deployment_service.rb | 53 ----- app/workers/build_success_worker.rb | 7 - app/workers/deployments/success_worker.rb | 8 +- db/fixtures/development/14_pipelines.rb | 8 +- db/fixtures/development/19_environments.rb | 20 +- .../20181030150739_add_action_to_deployments.rb | 24 +++ .../20181106135939_add_index_to_deployments.rb | 29 +-- db/schema.rb | 47 ++++- lib/api/deployments.rb | 4 +- lib/gitlab/cycle_analytics/summary/deploy.rb | 2 +- spec/factories/deployments.rb | 8 + .../user_sees_deployment_widget_spec.rb | 120 ++++++++---- .../projects/environments/environment_spec.rb | 13 ++ .../projects/environments/environments_spec.rb | 17 ++ spec/models/concerns/deployable_spec.rb | 10 +- spec/models/deployment_spec.rb | 144 ++++++++++++++ spec/models/environment_spec.rb | 44 +++-- spec/models/environment_status_spec.rb | 2 +- spec/services/start_environment_service_spec.rb | 217 +++++++++++++++++++++ spec/services/stop_environment_service_spec.rb | 43 ++++ spec/services/update_deployment_service_spec.rb | 217 --------------------- spec/workers/build_success_worker_spec.rb | 13 -- spec/workers/deployments/success_worker_spec.rb | 51 +++-- 33 files changed, 802 insertions(+), 420 deletions(-) create mode 100644 app/services/start_environment_service.rb create mode 100644 app/services/stop_environment_service.rb delete mode 100644 app/services/update_deployment_service.rb create mode 100644 db/migrate/20181030150739_add_action_to_deployments.rb create mode 100644 spec/services/start_environment_service_spec.rb create mode 100644 spec/services/stop_environment_service_spec.rb delete mode 100644 spec/services/update_deployment_service_spec.rb diff --git a/app/controllers/projects/deployments_controller.rb b/app/controllers/projects/deployments_controller.rb index 0a009477d61..bd4102d4531 100644 --- a/app/controllers/projects/deployments_controller.rb +++ b/app/controllers/projects/deployments_controller.rb @@ -6,7 +6,7 @@ class Projects::DeploymentsController < Projects::ApplicationController # rubocop: disable CodeReuse/ActiveRecord def index - deployments = environment.deployments.reorder(created_at: :desc) + deployments = environment.deployments.deployed.reorder(created_at: :desc) deployments = deployments.where('created_at > ?', params[:after].to_time) if params[:after]&.to_time render json: { deployments: DeploymentSerializer.new(project: project) @@ -47,7 +47,7 @@ class Projects::DeploymentsController < Projects::ApplicationController # rubocop: disable CodeReuse/ActiveRecord def deployment - @deployment ||= environment.deployments.find_by(iid: params[:id]) + @deployment ||= environment.deployments.deployed.find_by(iid: params[:id]) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index de10783df1a..ab3ad9bf090 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -58,7 +58,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController # rubocop: disable CodeReuse/ActiveRecord def show - @deployments = environment.deployments.order(id: :desc).page(params[:page]) + @deployments = environment.deployments.deployed.order(id: :desc).page(params[:page]) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/finders/environments_finder.rb b/app/finders/environments_finder.rb index 32f41314359..156d49745c9 100644 --- a/app/finders/environments_finder.rb +++ b/app/finders/environments_finder.rb @@ -9,7 +9,7 @@ class EnvironmentsFinder # rubocop: disable CodeReuse/ActiveRecord def execute - deployments = project.deployments.success + deployments = project.deployments.deployed deployments = if ref deployments_query = params[:with_tags] ? 'ref = :ref OR tag IS TRUE' : 'ref = :ref' diff --git a/app/models/concerns/deployable.rb b/app/models/concerns/deployable.rb index f4f1989f0a9..d85d624fff3 100644 --- a/app/models/concerns/deployable.rb +++ b/app/models/concerns/deployable.rb @@ -7,7 +7,7 @@ module Deployable after_create :create_deployment def create_deployment - return unless starts_environment? && !has_deployment? + return unless has_environment? && !has_deployment? environment = project.environments.find_or_create_by( name: expanded_environment_name @@ -21,7 +21,8 @@ module Deployable sha: sha, user: user, deployable: self, - on_stop: on_stop).tap do |_| + on_stop: on_stop, + action: environment_action).tap do |_| self.reload # Reload relationships end end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 305922611a6..322b3139759 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -10,9 +10,7 @@ class Deployment < ActiveRecord::Base belongs_to :user belongs_to :deployable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations - has_internal_id :iid, scope: :project, init: ->(s) do - Deployment.where(project: s.project).maximum(:iid) if s&.project - end + has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.deployments&.maximum(:iid) } validates :sha, presence: true validates :ref, presence: true @@ -20,6 +18,8 @@ class Deployment < ActiveRecord::Base delegate :name, to: :environment, prefix: true scope :for_environment, -> (environment) { where(environment_id: environment) } + scope :deployed, -> { success.start } + scope :stopped, -> { success.stop } state_machine :status, initial: :created do event :run do @@ -57,6 +57,11 @@ class Deployment < ActiveRecord::Base canceled: 4 } + enum action: { + start: 1, + stop: 2 + } + def self.last_for_environment(environment) ids = self .for_environment(environment) @@ -132,7 +137,7 @@ class Deployment < ActiveRecord::Base def previous_deployment @previous_deployment ||= - project.deployments.success.joins(:environment) + project.deployments.deployed.joins(:environment) .where(environments: { name: self.environment.name }, ref: self.ref) .where.not(id: self.id) .take @@ -177,6 +182,14 @@ class Deployment < ActiveRecord::Base metrics&.merge(deployment_time: finished_at.to_i) || {} end + def deployed? + success? && start? + end + + def stopped? + success? && stop? + end + private def prometheus_adapter diff --git a/app/models/environment.rb b/app/models/environment.rb index 581c870b2c5..1a535ba6b18 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -8,9 +8,9 @@ class Environment < ActiveRecord::Base belongs_to :project, required: true - has_many :deployments, -> { success }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :deployments, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment' + has_one :last_deployment, -> { deployed.order('deployments.id DESC') }, class_name: 'Deployment' before_validation :nullify_external_url before_validation :generate_slug, if: ->(env) { env.slug.blank? } @@ -50,7 +50,7 @@ class Environment < ActiveRecord::Base scope :in_review_folder, -> { where(environment_type: "review") } scope :for_name, -> (name) { where(name: name) } scope :for_project, -> (project) { where(project_id: project) } - scope :with_deployment, -> (sha) { where('EXISTS (?)', Deployment.select(1).where('deployments.environment_id = environments.id').where(sha: sha)) } + scope :with_deployment, -> (sha) { where('EXISTS (?)', Deployment.select(1).start.where('deployments.environment_id = environments.id').where(sha: sha)) } state_machine :state, initial: :available do event :start do @@ -103,15 +103,6 @@ class Environment < ActiveRecord::Base folder_name == "production" end - def first_deployment_for(commit_sha) - ref = project.repository.ref_name_for_sha(ref_path, commit_sha) - - return nil unless ref - - deployment_iid = ref.split('/').last - deployments.find_by(iid: deployment_iid) - end - def ref_path "refs/#{Repository::REF_ENVIRONMENTS}/#{slug}" end diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb index 76fd0241239..b33c4f2738d 100644 --- a/app/models/environment_status.rb +++ b/app/models/environment_status.rb @@ -28,7 +28,7 @@ class EnvironmentStatus def deployment strong_memoize(:deployment) do - Deployment.where(environment: environment).find_by_sha(sha) + environment.deployments.start.find_by_sha(sha) end end diff --git a/app/models/project.rb b/app/models/project.rb index d5a4ae79c47..d3b148d0ac0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -254,7 +254,7 @@ class Project < ActiveRecord::Base has_many :variables, class_name: 'Ci::Variable' has_many :triggers, class_name: 'Ci::Trigger' has_many :environments - has_many :deployments, -> { success } + has_many :deployments has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule' has_many :project_deploy_tokens has_many :deploy_tokens, through: :project_deploy_tokens diff --git a/app/services/start_environment_service.rb b/app/services/start_environment_service.rb new file mode 100644 index 00000000000..138ee55f6dc --- /dev/null +++ b/app/services/start_environment_service.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +class StartEnvironmentService + attr_reader :deployment + attr_reader :deployable + + delegate :environment, to: :deployment + delegate :variables, to: :deployable + + def initialize(deployment) + @deployment = deployment + @deployable = deployment.deployable + end + + def execute + return unless deployment.deployed? + + deployment.create_ref + deployment.invalidate_cache + + ActiveRecord::Base.transaction do + environment.external_url = expanded_environment_url if + expanded_environment_url + + environment.fire_state_event(:start) + + break unless environment.save + + deployment.tap(&:update_merge_request_metrics!) + end + end + + private + + def environment_options + @environment_options ||= deployable.options&.dig(:environment) || {} + end + + def expanded_environment_url + return @expanded_environment_url if defined?(@expanded_environment_url) + return unless environment_url + + @expanded_environment_url = + ExpandVariables.expand(environment_url, variables) + end + + def environment_url + environment_options[:url] + end +end diff --git a/app/services/stop_environment_service.rb b/app/services/stop_environment_service.rb new file mode 100644 index 00000000000..79dbf292123 --- /dev/null +++ b/app/services/stop_environment_service.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class StopEnvironmentService + attr_reader :deployment + + delegate :environment, to: :deployment + + def initialize(deployment) + @deployment = deployment + end + + def execute + return unless deployment.stopped? + + environment.fire_state_event(:stop) + environment.expire_etag_cache + end +end diff --git a/app/services/update_deployment_service.rb b/app/services/update_deployment_service.rb deleted file mode 100644 index aa7fcca1e2a..00000000000 --- a/app/services/update_deployment_service.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -class UpdateDeploymentService - attr_reader :deployment - attr_reader :deployable - - delegate :environment, to: :deployment - delegate :variables, to: :deployable - - def initialize(deployment) - @deployment = deployment - @deployable = deployment.deployable - end - - def execute - deployment.create_ref - deployment.invalidate_cache - - ActiveRecord::Base.transaction do - environment.external_url = expanded_environment_url if - expanded_environment_url - - environment.fire_state_event(action) - - break unless environment.save - break if environment.stopped? - - deployment.tap(&:update_merge_request_metrics!) - end - end - - private - - def environment_options - @environment_options ||= deployable.options&.dig(:environment) || {} - end - - def expanded_environment_url - return @expanded_environment_url if defined?(@expanded_environment_url) - return unless environment_url - - @expanded_environment_url = - ExpandVariables.expand(environment_url, variables) - end - - def environment_url - environment_options[:url] - end - - def action - environment_options[:action] || 'start' - end -end diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb index 9a865fea621..f3530317090 100644 --- a/app/workers/build_success_worker.rb +++ b/app/workers/build_success_worker.rb @@ -10,7 +10,6 @@ class BuildSuccessWorker def perform(build_id) Ci::Build.find_by(id: build_id).try do |build| create_deployment(build) if build.has_environment? - stop_environment(build) if build.stops_environment? end end # rubocop: enable CodeReuse/ActiveRecord @@ -27,10 +26,4 @@ class BuildSuccessWorker deployment.succeed end end - - ## - # TODO: This should be processed in DeploymentSuccessWorker once we started storing `action` value in `deployments` records - def stop_environment(build) - build.persisted_environment.fire_state_event(:stop) - end end diff --git a/app/workers/deployments/success_worker.rb b/app/workers/deployments/success_worker.rb index da517f3fb26..68fe8997f12 100644 --- a/app/workers/deployments/success_worker.rb +++ b/app/workers/deployments/success_worker.rb @@ -8,9 +8,11 @@ module Deployments def perform(deployment_id) Deployment.find_by_id(deployment_id).try do |deployment| - break unless deployment.success? - - UpdateDeploymentService.new(deployment).execute + if deployment.deployed? + StartEnvironmentService.new(deployment).execute + elsif deployment.stopped? + StopEnvironmentService.new(deployment).execute + end end end end diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index 5af77c49913..e4272cbee33 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -43,12 +43,14 @@ class Gitlab::Seeder::Pipelines # deploy stage { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success, - options: { environment: { action: 'start', on_stop: 'stop staging' } }, + options: { environment: { name: 'staging', action: 'start', on_stop: 'stop staging' } }, queued_at: 7.hour.ago, started_at: 6.hour.ago, finished_at: 4.hour.ago }, { name: 'stop staging', stage: 'deploy', environment: 'staging', - when: 'manual', status: :skipped }, + when: 'manual', status: :skipped, + options: { environment: { name: 'staging', action: 'stop' } } }, { name: 'production', stage: 'deploy', environment: 'production', - when: 'manual', status: :skipped }, + when: 'manual', status: :skipped, + options: { environment: { name: 'production' } } }, # notify stage { name: 'slack', stage: 'notify', when: 'manual', status: :success }, diff --git a/db/fixtures/development/19_environments.rb b/db/fixtures/development/19_environments.rb index 3e227928a29..031a04ac76b 100644 --- a/db/fixtures/development/19_environments.rb +++ b/db/fixtures/development/19_environments.rb @@ -45,14 +45,18 @@ class Gitlab::Seeder::Environments end def create_deployment!(project, name, ref, sha) - environment = find_or_create_environment!(project, name) - environment.deployments.create!( - project: project, - ref: ref, - sha: sha, - tag: false, - deployable: find_deployable(project, name) - ) + find_deployable(project, name).try do |deployable| + environment = find_or_create_environment!(project, name) + environment.deployments.create!( + project: project, + ref: ref, + sha: sha, + tag: false, + deployable: deployable + ).tap do |deployment| + deployment.succeed! + end + end end def find_or_create_environment!(project, name) diff --git a/db/migrate/20181030150739_add_action_to_deployments.rb b/db/migrate/20181030150739_add_action_to_deployments.rb new file mode 100644 index 00000000000..a8c3a8ee69c --- /dev/null +++ b/db/migrate/20181030150739_add_action_to_deployments.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class AddActionToDeployments < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DEPLOYMENT_ACTION_START = 1 # Equivalent to Deployment.actions['start'] + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default(:deployments, + :action, + :integer, + limit: 2, + default: DEPLOYMENT_ACTION_START, + allow_null: false) + end + + def down + remove_column(:deployments, :action) + end +end diff --git a/db/migrate/20181106135939_add_index_to_deployments.rb b/db/migrate/20181106135939_add_index_to_deployments.rb index 999c4a9e575..47c704a03ae 100644 --- a/db/migrate/20181106135939_add_index_to_deployments.rb +++ b/db/migrate/20181106135939_add_index_to_deployments.rb @@ -4,26 +4,27 @@ class AddIndexToDeployments < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers DOWNTIME = false + SHORT_INDEX_NAME_FOR_ENVIRONMENT_ID = 'index_deployments_on_env_deployed_for_id' + SHORT_INDEX_NAME_FOR_ENVIRONMENT_IID = 'index_deployments_on_env_deployed_for_iid' + SHORT_INDEX_NAME_FOR_PROJECT_FINISHED_AT = 'index_deployments_on_prj_deployed_for_finished_at' disable_ddl_transaction! def up - remove_concurrent_index :deployments, [:project_id, :status] - remove_concurrent_index :deployments, [:environment_id, :status] - add_concurrent_index :deployments, [:project_id, :status, :id] - add_concurrent_index :deployments, [:project_id, :status, :iid] - add_concurrent_index :deployments, [:environment_id, :status, :id] - add_concurrent_index :deployments, [:environment_id, :status, :iid] - add_concurrent_index :deployments, [:environment_id, :sha] + add_concurrent_index :deployments, [:project_id, :action, :status, :id] + add_concurrent_index :deployments, [:project_id, :action, :status, :iid] + add_concurrent_index :deployments, [:project_id, :action, :status, :finished_at], name: SHORT_INDEX_NAME_FOR_PROJECT_FINISHED_AT + add_concurrent_index :deployments, [:environment_id, :action, :status, :id], name: SHORT_INDEX_NAME_FOR_ENVIRONMENT_ID + add_concurrent_index :deployments, [:environment_id, :action, :status, :iid], name: SHORT_INDEX_NAME_FOR_ENVIRONMENT_IID + add_concurrent_index :deployments, [:environment_id, :action, :sha] end def down - add_concurrent_index :deployments, [:project_id, :status] - add_concurrent_index :deployments, [:environment_id, :status] - remove_concurrent_index :deployments, [:project_id, :status, :id] - remove_concurrent_index :deployments, [:project_id, :status, :iid] - remove_concurrent_index :deployments, [:environment_id, :status, :id] - remove_concurrent_index :deployments, [:environment_id, :status, :iid] - remove_concurrent_index :deployments, [:environment_id, :sha] + remove_concurrent_index :deployments, [:project_id, :action, :status, :id] + remove_concurrent_index :deployments, [:project_id, :action, :status, :iid] + remove_concurrent_index_by_name(:deployments, SHORT_INDEX_NAME_FOR_PROJECT_FINISHED_AT) + remove_concurrent_index_by_name(:deployments, SHORT_INDEX_NAME_FOR_ENVIRONMENT_ID) + remove_concurrent_index_by_name(:deployments, SHORT_INDEX_NAME_FOR_ENVIRONMENT_IID) + remove_concurrent_index :deployments, [:environment_id, :action, :sha] end end diff --git a/db/schema.rb b/db/schema.rb index d31f6f09b78..ae9b1f24a4b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -827,19 +827,23 @@ ActiveRecord::Schema.define(version: 20181106135939) do t.string "on_stop" t.integer "status", limit: 2, default: 2, null: false t.datetime_with_timezone "finished_at" + t.integer "action", limit: 2, default: 1, null: false end add_index "deployments", ["created_at"], name: "index_deployments_on_created_at", using: :btree add_index "deployments", ["deployable_type", "deployable_id"], name: "index_deployments_on_deployable_type_and_deployable_id", using: :btree + add_index "deployments", ["environment_id", "action", "sha"], name: "index_deployments_on_environment_id_and_action_and_sha", using: :btree + add_index "deployments", ["environment_id", "action", "status", "id"], name: "index_deployments_on_env_deployed_for_id", using: :btree + add_index "deployments", ["environment_id", "action", "status", "iid"], name: "index_deployments_on_env_deployed_for_iid", using: :btree add_index "deployments", ["environment_id", "id"], name: "index_deployments_on_environment_id_and_id", using: :btree add_index "deployments", ["environment_id", "iid", "project_id"], name: "index_deployments_on_environment_id_and_iid_and_project_id", using: :btree - add_index "deployments", ["environment_id", "sha"], name: "index_deployments_on_environment_id_and_sha", using: :btree - add_index "deployments", ["environment_id", "status", "id"], name: "index_deployments_on_environment_id_and_status_and_id", using: :btree - add_index "deployments", ["environment_id", "status", "iid"], name: "index_deployments_on_environment_id_and_status_and_iid", using: :btree + add_index "deployments", ["environment_id", "status"], name: "index_deployments_on_environment_id_and_status", using: :btree add_index "deployments", ["id"], name: "partial_index_deployments_for_legacy_successful_deployments", where: "((finished_at IS NULL) AND (status = 2))", using: :btree + add_index "deployments", ["project_id", "action", "status", "finished_at"], name: "index_deployments_on_prj_deployed_for_finished_at", using: :btree + add_index "deployments", ["project_id", "action", "status", "id"], name: "index_deployments_on_project_id_and_action_and_status_and_id", using: :btree + add_index "deployments", ["project_id", "action", "status", "iid"], name: "index_deployments_on_project_id_and_action_and_status_and_iid", using: :btree add_index "deployments", ["project_id", "iid"], name: "index_deployments_on_project_id_and_iid", unique: true, using: :btree - add_index "deployments", ["project_id", "status", "id"], name: "index_deployments_on_project_id_and_status_and_id", using: :btree - add_index "deployments", ["project_id", "status", "iid"], name: "index_deployments_on_project_id_and_status_and_iid", using: :btree + add_index "deployments", ["project_id", "status"], name: "index_deployments_on_project_id_and_status", using: :btree create_table "emails", force: :cascade do |t| t.integer "user_id", null: false @@ -927,6 +931,35 @@ ActiveRecord::Schema.define(version: 20181106135939) do add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree + create_table "gcp_clusters", force: :cascade do |t| + t.integer "project_id", null: false + t.integer "user_id" + t.integer "service_id" + t.integer "status" + t.integer "gcp_cluster_size", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.boolean "enabled", default: true + t.text "status_reason" + t.string "project_namespace" + t.string "endpoint" + t.text "ca_cert" + t.text "encrypted_kubernetes_token" + t.string "encrypted_kubernetes_token_iv" + t.string "username" + t.text "encrypted_password" + t.string "encrypted_password_iv" + t.string "gcp_project_id", null: false + t.string "gcp_cluster_zone", null: false + t.string "gcp_cluster_name", null: false + t.string "gcp_machine_type" + t.string "gcp_operation_id" + t.text "encrypted_gcp_token" + t.string "encrypted_gcp_token_iv" + end + + add_index "gcp_clusters", ["project_id"], name: "index_gcp_clusters_on_project_id", unique: true, using: :btree + create_table "gpg_key_subkeys", force: :cascade do |t| t.integer "gpg_key_id", null: false t.binary "keyid" @@ -1827,7 +1860,6 @@ ActiveRecord::Schema.define(version: 20181106135939) do end add_index "redirect_routes", ["path"], name: "index_redirect_routes_on_path", unique: true, using: :btree - add_index "redirect_routes", ["path"], name: "index_redirect_routes_on_path_text_pattern_ops", using: :btree, opclasses: {"path"=>"varchar_pattern_ops"} add_index "redirect_routes", ["source_type", "source_id"], name: "index_redirect_routes_on_source_type_and_source_id", using: :btree create_table "releases", force: :cascade do |t| @@ -2411,6 +2443,9 @@ ActiveRecord::Schema.define(version: 20181106135939) do add_foreign_key "fork_network_members", "projects", on_delete: :cascade add_foreign_key "fork_networks", "projects", column: "root_project_id", name: "fk_e7b436b2b5", on_delete: :nullify add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade + add_foreign_key "gcp_clusters", "projects", on_delete: :cascade + add_foreign_key "gcp_clusters", "services", on_delete: :nullify + add_foreign_key "gcp_clusters", "users", on_delete: :nullify add_foreign_key "gpg_key_subkeys", "gpg_keys", on_delete: :cascade add_foreign_key "gpg_keys", "users", on_delete: :cascade add_foreign_key "gpg_signatures", "gpg_key_subkeys", on_delete: :nullify diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb index 6747e2e5005..2fb8551543a 100644 --- a/lib/api/deployments.rb +++ b/lib/api/deployments.rb @@ -24,7 +24,7 @@ module API get ':id/deployments' do authorize! :read_deployment, user_project - present paginate(user_project.deployments.order(params[:order_by] => params[:sort])), with: Entities::Deployment + present paginate(user_project.deployments.deployed.order(params[:order_by] => params[:sort])), with: Entities::Deployment end # rubocop: enable CodeReuse/ActiveRecord @@ -38,7 +38,7 @@ module API get ':id/deployments/:deployment_id' do authorize! :read_deployment, user_project - deployment = user_project.deployments.find(params[:deployment_id]) + deployment = user_project.deployments.deployed.find(params[:deployment_id]) present deployment, with: Entities::Deployment end diff --git a/lib/gitlab/cycle_analytics/summary/deploy.rb b/lib/gitlab/cycle_analytics/summary/deploy.rb index 39fdf5074da..b1c644f77f3 100644 --- a/lib/gitlab/cycle_analytics/summary/deploy.rb +++ b/lib/gitlab/cycle_analytics/summary/deploy.rb @@ -7,7 +7,7 @@ module Gitlab end def value - @value ||= @project.deployments.success.where("created_at > ?", @from).count + @value ||= @project.deployments.deployed.where("finished_at > ?", @from).count end end end diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb index 011c98599a3..483a906e31f 100644 --- a/spec/factories/deployments.rb +++ b/spec/factories/deployments.rb @@ -47,5 +47,13 @@ FactoryBot.define do deployment.succeed! end end + + trait :start do + action :start + end + + trait :stop do + action :stop + end end end diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb index 74290c0fff9..53598b10fb1 100644 --- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb +++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb @@ -18,55 +18,107 @@ describe 'Merge request > User sees deployment widget', :js do sign_in(user) end - context 'when deployment succeeded' do - let(:build) { create(:ci_build, :success, pipeline: pipeline) } - let!(:deployment) { create(:deployment, :succeed, environment: environment, sha: sha, ref: ref, deployable: build) } + context 'when deployment is to start an environment' do + context 'when deployment succeeded' do + let(:build) { create(:ci_build, :success, pipeline: pipeline) } + let!(:deployment) { create(:deployment, :succeed, environment: environment, sha: sha, ref: ref, deployable: build) } - it 'displays that the environment is deployed' do - visit project_merge_request_path(project, merge_request) - wait_for_requests + it 'displays that the environment is deployed' do + visit project_merge_request_path(project, merge_request) + wait_for_requests - expect(page).to have_content("Deployed to #{environment.name}") - expect(find('.js-deploy-time')['data-original-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium)) + expect(page).to have_content("Deployed to #{environment.name}") + expect(find('.js-deploy-time')['data-original-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium)) + end end - end - context 'when deployment failed' do - let(:build) { create(:ci_build, :failed, pipeline: pipeline) } - let!(:deployment) { create(:deployment, :failed, environment: environment, sha: sha, ref: ref, deployable: build) } + context 'when deployment failed' do + let(:build) { create(:ci_build, :failed, pipeline: pipeline) } + let!(:deployment) { create(:deployment, :failed, environment: environment, sha: sha, ref: ref, deployable: build) } - it 'displays that the deployment failed' do - visit project_merge_request_path(project, merge_request) - wait_for_requests + it 'displays that the deployment failed' do + visit project_merge_request_path(project, merge_request) + wait_for_requests + + expect(page).to have_content("Failed to deploy to #{environment.name}") + expect(page).not_to have_css('.js-deploy-time') + end + end + + context 'when deployment running' do + let(:build) { create(:ci_build, :running, pipeline: pipeline) } + let!(:deployment) { create(:deployment, :running, environment: environment, sha: sha, ref: ref, deployable: build) } - expect(page).to have_content("Failed to deploy to #{environment.name}") - expect(page).not_to have_css('.js-deploy-time') + it 'displays that the running deployment' do + visit project_merge_request_path(project, merge_request) + wait_for_requests + + expect(page).to have_content("Deploying to #{environment.name}") + expect(page).not_to have_css('.js-deploy-time') + end end - end - context 'when deployment running' do - let(:build) { create(:ci_build, :running, pipeline: pipeline) } - let!(:deployment) { create(:deployment, :running, environment: environment, sha: sha, ref: ref, deployable: build) } + context 'when deployment will happen' do + let(:build) { create(:ci_build, :created, pipeline: pipeline) } + let!(:deployment) { create(:deployment, environment: environment, sha: sha, ref: ref, deployable: build) } - it 'displays that the running deployment' do - visit project_merge_request_path(project, merge_request) - wait_for_requests + it 'displays that the environment name' do + visit project_merge_request_path(project, merge_request) + wait_for_requests - expect(page).to have_content("Deploying to #{environment.name}") - expect(page).not_to have_css('.js-deploy-time') + expect(page).to have_content("Deploying to #{environment.name}") + expect(page).not_to have_css('.js-deploy-time') + end end end - context 'when deployment will happen' do - let(:build) { create(:ci_build, :created, pipeline: pipeline) } - let!(:deployment) { create(:deployment, environment: environment, sha: sha, ref: ref, deployable: build) } + context 'when deployment is to stop an environment' do + context 'when the stop action succeeded' do + let(:build) { create(:ci_build, :success, pipeline: pipeline) } + let!(:deployment) { create(:deployment, :succeed, :stop, environment: environment, sha: sha, ref: ref, deployable: build) } - it 'displays that the environment name' do - visit project_merge_request_path(project, merge_request) - wait_for_requests + it 'does not display deployment info' do + visit project_merge_request_path(project, merge_request) + wait_for_requests + + expect(page).not_to have_css('.deployment-info') + end + end + + context 'when the stop action failed' do + let(:build) { create(:ci_build, :failed, pipeline: pipeline) } + let!(:deployment) { create(:deployment, :failed, :stop, environment: environment, sha: sha, ref: ref, deployable: build) } + + it 'does not display deployment info' do + visit project_merge_request_path(project, merge_request) + wait_for_requests + + expect(page).not_to have_css('.deployment-info') + end + end + + context 'when the stop action is running' do + let(:build) { create(:ci_build, :running, pipeline: pipeline) } + let!(:deployment) { create(:deployment, :running, :stop, environment: environment, sha: sha, ref: ref, deployable: build) } + + it 'does not display deployment info' do + visit project_merge_request_path(project, merge_request) + wait_for_requests - expect(page).to have_content("Deploying to #{environment.name}") - expect(page).not_to have_css('.js-deploy-time') + expect(page).not_to have_css('.deployment-info') + end + end + + context 'when the stop action will happen' do + let(:build) { create(:ci_build, :created, pipeline: pipeline) } + let!(:deployment) { create(:deployment, :stop, environment: environment, sha: sha, ref: ref, deployable: build) } + + it 'does not display deployment info' do + visit project_merge_request_path(project, merge_request) + wait_for_requests + + expect(page).not_to have_css('.deployment-info') + end end end diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 0d82df6dc8e..d12fe24714b 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -82,6 +82,19 @@ describe 'Environment' do end end + context 'when there is a successful stop action' do + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, :success, pipeline: pipeline) } + + let(:deployment) do + create(:deployment, :success, :stop, environment: environment, deployable: build) + end + + it 'does show no deployments' do + expect(page).to have_content('You don\'t have any deployments right now.') + end + end + context 'with related deployable present' do let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline) } diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 89954d35f91..b3eabcb7903 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -344,6 +344,23 @@ describe 'Environments page', :js do expect(page).to have_content('No deployments yet') end end + + context 'when there is a successful stop action' do + let(:project) { create(:project, :repository) } + + let!(:deployment) do + create(:deployment, :success, + :stop, + environment: environment, + sha: project.commit.id) + end + + it 'does not show deployments' do + visit_environments(project) + + expect(page).to have_content('No deployments yet') + end + end end it 'does have a new environment button' do diff --git a/spec/models/concerns/deployable_spec.rb b/spec/models/concerns/deployable_spec.rb index ac79c75a55e..9551ff4b13b 100644 --- a/spec/models/concerns/deployable_spec.rb +++ b/spec/models/concerns/deployable_spec.rb @@ -22,13 +22,17 @@ describe Deployable do expect(deployment.on_stop).to eq('stop_review_app') expect(environment.name).to eq('review/master') end + + it 'updates action column to start' do + expect(deployment).to be_start + end end - context 'when the deployable object will stop an environment' do + context 'when the deployable object will stop the review app' do let!(:job) { create(:ci_build, :stop_review_app) } - it 'does not create a deployment record' do - expect(deployment).to be_nil + it 'updates action column to stop' do + expect(deployment).to be_stop end end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index 270b2767c68..b5696c73339 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -191,6 +191,150 @@ describe Deployment do end end + describe '.deployed' do + subject { described_class.deployed } + + context 'when deployment starts an environment' do + context 'when the action was successful' do + let!(:deployment) { create(:deployment, :start, :success) } + + it 'returns the deployment' do + is_expected.to eq([deployment]) + end + end + + context 'when the action failed' do + let!(:deployment) { create(:deployment, :start, :failed) } + + it 'returns nothing' do + is_expected.to be_empty + end + end + end + + context 'when deployment stops an environment' do + context 'when the action was successful' do + let!(:deployment) { create(:deployment, :stop, :success) } + + it 'returns nothing' do + is_expected.to be_empty + end + end + + context 'when the action failed' do + let!(:deployment) { create(:deployment, :stop, :failed) } + + it 'returns nothing' do + is_expected.to be_empty + end + end + end + end + + describe '.stopped' do + subject { described_class.stopped } + + context 'when deployment starts an environment' do + context 'when the action was successful' do + let!(:deployment) { create(:deployment, :start, :success) } + + it 'returns nothing' do + is_expected.to be_empty + end + end + + context 'when the action failed' do + let!(:deployment) { create(:deployment, :start, :failed) } + + it 'returns nothing' do + is_expected.to be_empty + end + end + end + + context 'when deployment stops an environment' do + context 'when the action was successful' do + let!(:deployment) { create(:deployment, :stop, :success) } + + it 'returns the deployment' do + is_expected.to eq([deployment]) + end + end + + context 'when the action failed' do + let!(:deployment) { create(:deployment, :stop, :failed) } + + it 'returns nothing' do + is_expected.to be_empty + end + end + end + end + + describe '#deployed?' do + subject { deployment.deployed? } + + context 'when deployment starts an environment' do + context 'when the action was successful' do + let!(:deployment) { create(:deployment, :start, :success) } + + it { is_expected.to be_truthy } + end + + context 'when the action failed' do + let!(:deployment) { create(:deployment, :start, :failed) } + + it { is_expected.to be_falsy } + end + end + + context 'when deployment stops an environment' do + context 'when the action was successful' do + let!(:deployment) { create(:deployment, :stop, :success) } + + it { is_expected.to be_falsy } + end + + context 'when the action failed' do + let!(:deployment) { create(:deployment, :stop, :failed) } + + it { is_expected.to be_falsy } + end + end + end + + describe '#stopped?' do + subject { deployment.stopped? } + + context 'when deployment starts an environment' do + context 'when the action was successful' do + let!(:deployment) { create(:deployment, :start, :success) } + + it { is_expected.to be_falsy } + end + + context 'when the action failed' do + let!(:deployment) { create(:deployment, :start, :failed) } + + it { is_expected.to be_falsy } + end + end + + context 'when deployment stops an environment' do + context 'when the action was successful' do + let!(:deployment) { create(:deployment, :stop, :success) } + + it { is_expected.to be_truthy } + end + + context 'when the action failed' do + let!(:deployment) { create(:deployment, :stop, :failed) } + + it { is_expected.to be_falsy } + end + end + end + describe '#deployed_at' do subject { deployment.deployed_at } diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index ac814c85c3c..55ec1bc9e90 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -59,14 +59,22 @@ describe Environment do let(:environment) { create(:environment) } let(:sha) { RepoHelpers.sample_commit.id } - context 'when deployment has the specified sha' do - let!(:deployment) { create(:deployment, environment: environment, sha: sha) } + context 'when deployment starts environment' do + context 'when deployment has the specified sha' do + let!(:deployment) { create(:deployment, :start, environment: environment, sha: sha) } - it { is_expected.to eq([environment]) } + it { is_expected.to eq([environment]) } + end + + context 'when deployment does not have the specified sha' do + let!(:deployment) { create(:deployment, :start, environment: environment, sha: 'abc') } + + it { is_expected.to be_empty } + end end - context 'when deployment does not have the specified sha' do - let!(:deployment) { create(:deployment, environment: environment, sha: 'abc') } + context 'when deployment stops environment' do + let!(:deployment) { create(:deployment, :stop, environment: environment) } it { is_expected.to be_empty } end @@ -77,20 +85,28 @@ describe Environment do let(:environment) { create(:environment) } - context 'when the latest deployment is successful' do - let!(:deployment) { create(:deployment, :success, environment: environment) } + context 'when the latest deployment is for starting an environment' do + context 'when the latest deployment is successful' do + let!(:deployment) { create(:deployment, :start, :success, environment: environment) } - it { expect(subject).to be_within(1.second).of(deployment.finished_at) } - end + it { expect(subject).to be_within(1.second).of(deployment.finished_at) } + end - context 'when the latest deployment failed' do - let!(:deployment) { create(:deployment, :failed, environment: environment) } + context 'when the latest deployment failed' do + let!(:deployment) { create(:deployment, :start, :failed, environment: environment) } - it { is_expected.to be_nil } + it { is_expected.to be_nil } + end + + context 'when the latest deployment is running' do + let!(:deployment) { create(:deployment, :start, :running, environment: environment) } + + it { is_expected.to be_nil } + end end - context 'when the latest deployment is running' do - let!(:deployment) { create(:deployment, :running, environment: environment) } + context 'when the latest deployment is for stopping environment' do + let!(:deployment) { create(:deployment, :stop, :success, environment: environment) } it { is_expected.to be_nil } end diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb index 9fc8c5c6357..a88c6203b52 100644 --- a/spec/models/environment_status_spec.rb +++ b/spec/models/environment_status_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe EnvironmentStatus do include ProjectForksHelper - let(:deployment) { create(:deployment, :succeed, :review_app) } + let(:deployment) { create(:deployment, :succeed, :start, :review_app) } let(:environment) { deployment.environment } let(:project) { deployment.project } let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) } diff --git a/spec/services/start_environment_service_spec.rb b/spec/services/start_environment_service_spec.rb new file mode 100644 index 00000000000..105d69c6b8e --- /dev/null +++ b/spec/services/start_environment_service_spec.rb @@ -0,0 +1,217 @@ +require 'spec_helper' + +describe StartEnvironmentService do + let(:user) { create(:user) } + let(:options) { { name: 'production' } } + + let(:job) do + create(:ci_build, + ref: 'master', + tag: false, + environment: 'production', + options: { environment: options }, + project: project) + end + + let(:project) { create(:project, :repository) } + let(:environment) { deployment.environment } + let(:deployment) { job.deployment } + let(:service) { described_class.new(deployment) } + + before do + job.success! # Create/Succeed deployment + end + + describe '#execute' do + subject { service.execute } + + let(:store) { Gitlab::EtagCaching::Store.new } + + it 'invalidates the environment etag cache' do + old_value = store.get(environment.etag_cache_key) + + subject + + expect(store.get(environment.etag_cache_key)).not_to eq(old_value) + end + + it 'creates ref' do + expect_any_instance_of(Repository) + .to receive(:create_ref) + .with(deployment.ref, deployment.send(:ref_path)) + + subject + end + + it 'updates merge request metrics' do + expect_any_instance_of(Deployment) + .to receive(:update_merge_request_metrics!) + + subject + end + + context 'when start action is defined' do + let(:options) { { name: 'production', action: 'start' } } + + context 'and environment is stopped' do + before do + environment.stop + end + + it 'makes environment available' do + subject + + expect(environment.reload).to be_available + end + end + end + + context 'when variables are used' do + let(:options) do + { name: 'review-apps/$CI_COMMIT_REF_NAME', + url: 'http://$CI_COMMIT_REF_NAME.review-apps.gitlab.com' } + end + + before do + environment.update(name: 'review-apps/master') + job.update(environment: 'review-apps/$CI_COMMIT_REF_NAME') + end + + it 'does not create a new environment' do + expect { subject }.not_to change { Environment.count } + end + + it 'updates external url' do + subject + + expect(environment.reload.name).to eq('review-apps/master') + expect(environment.reload.external_url).to eq('http://master.review-apps.gitlab.com') + end + end + end + + describe '#expanded_environment_url' do + subject { service.send(:expanded_environment_url) } + + context 'when yaml environment uses $CI_COMMIT_REF_NAME' do + let(:job) do + create(:ci_build, + ref: 'master', + environment: 'production', + project: project, + options: { environment: { name: 'production', url: 'http://review/$CI_COMMIT_REF_NAME' } }) + end + + it { is_expected.to eq('http://review/master') } + end + + context 'when yaml environment uses $CI_ENVIRONMENT_SLUG' do + let(:job) do + create(:ci_build, + ref: 'master', + environment: 'prod-slug', + project: project, + options: { environment: { name: 'prod-slug', url: 'http://review/$CI_ENVIRONMENT_SLUG' } }) + end + + it { is_expected.to eq('http://review/prod-slug') } + end + + context 'when yaml environment uses yaml_variables containing symbol keys' do + let(:job) do + create(:ci_build, + yaml_variables: [{ key: :APP_HOST, value: 'host' }], + environment: 'production', + project: project, + options: { environment: { name: 'production', url: 'http://review/$APP_HOST' } }) + end + + it { is_expected.to eq('http://review/host') } + end + + context 'when yaml environment does not have url' do + let(:job) { create(:ci_build, environment: 'staging', project: project) } + + it 'returns the external_url from persisted environment' do + is_expected.to be_nil + end + end + end + + describe "merge request metrics" do + let(:merge_request) { create(:merge_request, target_branch: 'master', source_branch: 'feature', source_project: project) } + + context "while updating the 'first_deployed_to_production_at' time" do + before do + merge_request.metrics.update!(merged_at: 1.hour.ago) + end + + context "for merge requests merged before the current deploy" do + it "sets the time if the deploy's environment is 'production'" do + service.execute + + expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(deployment.finished_at) + end + + context 'when job deploys to staging' do + let(:job) do + create(:ci_build, + ref: 'master', + tag: false, + environment: 'staging', + options: { environment: { name: 'staging' } }, + project: project) + end + + it "doesn't set the time if the deploy's environment is not 'production'" do + service.execute + + expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_nil + end + end + + it 'does not raise errors if the merge request does not have a metrics record' do + merge_request.metrics.destroy + + expect(merge_request.reload.metrics).to be_nil + expect { service.execute }.not_to raise_error + end + end + + context "for merge requests merged before the previous deploy" do + context "if the 'first_deployed_to_production_at' time is already set" do + it "does not overwrite the older 'first_deployed_to_production_at' time" do + # Previous deploy + service.execute + + expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(deployment.finished_at) + + # Current deploy + Timecop.travel(12.hours.from_now) do + service.execute + + expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(deployment.finished_at) + end + end + end + + context "if the 'first_deployed_to_production_at' time is not already set" do + it "does not overwrite the older 'first_deployed_to_production_at' time" do + # Previous deploy + time = 5.minutes.from_now + Timecop.freeze(time) { service.execute } + + expect(merge_request.reload.metrics.merged_at).to be < merge_request.reload.metrics.first_deployed_to_production_at + + previous_time = merge_request.reload.metrics.first_deployed_to_production_at + + # Current deploy + Timecop.freeze(time + 12.hours) { service.execute } + + expect(merge_request.reload.metrics.first_deployed_to_production_at).to eq(previous_time) + end + end + end + end + end +end diff --git a/spec/services/stop_environment_service_spec.rb b/spec/services/stop_environment_service_spec.rb new file mode 100644 index 00000000000..c41e010d717 --- /dev/null +++ b/spec/services/stop_environment_service_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe StopEnvironmentService do + let(:service) { described_class.new(deployment) } + + describe '#execute' do + subject { service.execute } + + context 'when environment is available' do + let(:environment) { create(:environment, state: :available) } + + context 'when deployment was successful' do + let(:deployment) { create(:deployment, :success, :stop, environment: environment) } + + it 'stops the environment' do + subject + + expect(environment.reload).to be_stopped + end + end + + context 'when deployment failed' do + let(:deployment) { create(:deployment, :failed, :stop, environment: environment) } + + it 'does not stop the environment' do + subject + + expect(environment.reload).to be_available + end + end + end + + context 'when environment is stopped' do + let(:deployment) { create(:deployment, :success, :stop, environment: environment) } + let(:environment) { create(:environment, state: :stopped) } + + it 'does not raise an error' do + expect { subject }.not_to raise_error + expect(environment.reload).to be_stopped + end + end + end +end diff --git a/spec/services/update_deployment_service_spec.rb b/spec/services/update_deployment_service_spec.rb deleted file mode 100644 index 3c55dd9659a..00000000000 --- a/spec/services/update_deployment_service_spec.rb +++ /dev/null @@ -1,217 +0,0 @@ -require 'spec_helper' - -describe UpdateDeploymentService do - let(:user) { create(:user) } - let(:options) { { name: 'production' } } - - let(:job) do - create(:ci_build, - ref: 'master', - tag: false, - environment: 'production', - options: { environment: options }, - project: project) - end - - let(:project) { create(:project, :repository) } - let(:environment) { deployment.environment } - let(:deployment) { job.deployment } - let(:service) { described_class.new(deployment) } - - before do - job.success! # Create/Succeed deployment - end - - describe '#execute' do - subject { service.execute } - - let(:store) { Gitlab::EtagCaching::Store.new } - - it 'invalidates the environment etag cache' do - old_value = store.get(environment.etag_cache_key) - - subject - - expect(store.get(environment.etag_cache_key)).not_to eq(old_value) - end - - it 'creates ref' do - expect_any_instance_of(Repository) - .to receive(:create_ref) - .with(deployment.ref, deployment.send(:ref_path)) - - subject - end - - it 'updates merge request metrics' do - expect_any_instance_of(Deployment) - .to receive(:update_merge_request_metrics!) - - subject - end - - context 'when start action is defined' do - let(:options) { { name: 'production', action: 'start' } } - - context 'and environment is stopped' do - before do - environment.stop - end - - it 'makes environment available' do - subject - - expect(environment.reload).to be_available - end - end - end - - context 'when variables are used' do - let(:options) do - { name: 'review-apps/$CI_COMMIT_REF_NAME', - url: 'http://$CI_COMMIT_REF_NAME.review-apps.gitlab.com' } - end - - before do - environment.update(name: 'review-apps/master') - job.update(environment: 'review-apps/$CI_COMMIT_REF_NAME') - end - - it 'does not create a new environment' do - expect { subject }.not_to change { Environment.count } - end - - it 'updates external url' do - subject - - expect(subject.environment.name).to eq('review-apps/master') - expect(subject.environment.external_url).to eq('http://master.review-apps.gitlab.com') - end - end - end - - describe '#expanded_environment_url' do - subject { service.send(:expanded_environment_url) } - - context 'when yaml environment uses $CI_COMMIT_REF_NAME' do - let(:job) do - create(:ci_build, - ref: 'master', - environment: 'production', - project: project, - options: { environment: { name: 'production', url: 'http://review/$CI_COMMIT_REF_NAME' } }) - end - - it { is_expected.to eq('http://review/master') } - end - - context 'when yaml environment uses $CI_ENVIRONMENT_SLUG' do - let(:job) do - create(:ci_build, - ref: 'master', - environment: 'prod-slug', - project: project, - options: { environment: { name: 'prod-slug', url: 'http://review/$CI_ENVIRONMENT_SLUG' } }) - end - - it { is_expected.to eq('http://review/prod-slug') } - end - - context 'when yaml environment uses yaml_variables containing symbol keys' do - let(:job) do - create(:ci_build, - yaml_variables: [{ key: :APP_HOST, value: 'host' }], - environment: 'production', - project: project, - options: { environment: { name: 'production', url: 'http://review/$APP_HOST' } }) - end - - it { is_expected.to eq('http://review/host') } - end - - context 'when yaml environment does not have url' do - let(:job) { create(:ci_build, environment: 'staging', project: project) } - - it 'returns the external_url from persisted environment' do - is_expected.to be_nil - end - end - end - - describe "merge request metrics" do - let(:merge_request) { create(:merge_request, target_branch: 'master', source_branch: 'feature', source_project: project) } - - context "while updating the 'first_deployed_to_production_at' time" do - before do - merge_request.metrics.update!(merged_at: 1.hour.ago) - end - - context "for merge requests merged before the current deploy" do - it "sets the time if the deploy's environment is 'production'" do - service.execute - - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(deployment.finished_at) - end - - context 'when job deploys to staging' do - let(:job) do - create(:ci_build, - ref: 'master', - tag: false, - environment: 'staging', - options: { environment: { name: 'staging' } }, - project: project) - end - - it "doesn't set the time if the deploy's environment is not 'production'" do - service.execute - - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_nil - end - end - - it 'does not raise errors if the merge request does not have a metrics record' do - merge_request.metrics.destroy - - expect(merge_request.reload.metrics).to be_nil - expect { service.execute }.not_to raise_error - end - end - - context "for merge requests merged before the previous deploy" do - context "if the 'first_deployed_to_production_at' time is already set" do - it "does not overwrite the older 'first_deployed_to_production_at' time" do - # Previous deploy - service.execute - - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(deployment.finished_at) - - # Current deploy - Timecop.travel(12.hours.from_now) do - service.execute - - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(deployment.finished_at) - end - end - end - - context "if the 'first_deployed_to_production_at' time is not already set" do - it "does not overwrite the older 'first_deployed_to_production_at' time" do - # Previous deploy - time = 5.minutes.from_now - Timecop.freeze(time) { service.execute } - - expect(merge_request.reload.metrics.merged_at).to be < merge_request.reload.metrics.first_deployed_to_production_at - - previous_time = merge_request.reload.metrics.first_deployed_to_production_at - - # Current deploy - Timecop.freeze(time + 12.hours) { service.execute } - - expect(merge_request.reload.metrics.first_deployed_to_production_at).to eq(previous_time) - end - end - end - end - end -end diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb index 5eb9709ded9..8e9b178728e 100644 --- a/spec/workers/build_success_worker_spec.rb +++ b/spec/workers/build_success_worker_spec.rb @@ -47,19 +47,6 @@ describe BuildSuccessWorker do expect(build.reload).not_to be_has_deployment end end - - context 'when the build will stop an environment' do - let!(:build) { create(:ci_build, :stop_review_app, environment: environment.name, project: environment.project) } - let(:environment) { create(:environment, state: :available) } - - it 'stops the environment' do - expect(environment).to be_available - - subject - - expect(environment.reload).to be_stopped - end - end end context 'when build does not exist' do diff --git a/spec/workers/deployments/success_worker_spec.rb b/spec/workers/deployments/success_worker_spec.rb index ba7d45eca01..57aaa929a37 100644 --- a/spec/workers/deployments/success_worker_spec.rb +++ b/spec/workers/deployments/success_worker_spec.rb @@ -3,34 +3,51 @@ require 'spec_helper' describe Deployments::SuccessWorker do subject { described_class.new.perform(deployment&.id) } - context 'when successful deployment' do - let(:deployment) { create(:deployment, :success) } + context 'when deployment starts environment' do + context 'when deployment was successful' do + let(:deployment) { create(:deployment, :start, :success) } - it 'executes UpdateDeploymentService' do - expect(UpdateDeploymentService) - .to receive(:new).with(deployment).and_call_original + it 'executes StartEnvironmentService' do + expect(StartEnvironmentService) + .to receive(:new).with(deployment).and_call_original - subject + subject + end end - end - context 'when canceled deployment' do - let(:deployment) { create(:deployment, :canceled) } + context 'when deployment failed' do + let(:deployment) { create(:deployment, :start, :failed) } - it 'does not execute UpdateDeploymentService' do - expect(UpdateDeploymentService).not_to receive(:new) + it 'does not execute StartEnvironmentService' do + expect(StartEnvironmentService) + .not_to receive(:new).with(deployment).and_call_original - subject + subject + end end end - context 'when deploy record does not exist' do - let(:deployment) { nil } + context 'when deployment stops environment' do + context 'when deployment was successful' do + let(:deployment) { create(:deployment, :stop, :success) } + + it 'executes StopEnvironmentService' do + expect(StopEnvironmentService) + .to receive(:new).with(deployment).and_call_original + + subject + end + end + + context 'when deployment failed' do + let(:deployment) { create(:deployment, :stop, :failed) } - it 'does not execute UpdateDeploymentService' do - expect(UpdateDeploymentService).not_to receive(:new) + it 'does not execute StopEnvironmentService' do + expect(StopEnvironmentService) + .not_to receive(:new).with(deployment).and_call_original - subject + subject + end end end end -- cgit v1.2.1