Commit f0a9e340 authored by Shinya Maeda's avatar Shinya Maeda

Introduce the explicit definition for production environments

This commit introduces the 'tier' column to 'environments' table
in order to explicit define environment tiers.
parent df5fcb4e
......@@ -39,6 +39,7 @@ class Environment < ApplicationRecord
before_validation :generate_slug, if: ->(env) { env.slug.blank? }
before_save :set_environment_type
before_save :ensure_environment_tier
after_save :clear_reactive_cache!
validates :name,
......@@ -87,6 +88,7 @@ class Environment < ApplicationRecord
end
scope :for_project, -> (project) { where(project_id: project) }
scope :for_tier, -> (tier) { where(tier: tier).where('tier IS NOT NULL') }
scope :with_deployment, -> (sha) { where('EXISTS (?)', Deployment.select(1).where('deployments.environment_id = environments.id').where(sha: sha)) }
scope :unfoldered, -> { where(environment_type: nil) }
scope :with_rank, -> do
......@@ -94,6 +96,14 @@ class Environment < ApplicationRecord
end
scope :for_id, -> (id) { where(id: id) }
enum tier: {
production: 0,
staging: 1,
testing: 2,
development: 3,
other: 4
}
state_machine :state, initial: :available do
event :start do
transition stopped: :available
......@@ -429,6 +439,24 @@ class Environment < ApplicationRecord
def generate_slug
self.slug = Gitlab::Slug::Environment.new(name).generate
end
def ensure_environment_tier
return unless ::Feature.enabled?(:environment_tier, project, default_enabled: :yaml)
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
end
Environment.prepend_if_ee('EE::Environment')
---
title: Add tier column to the environments table
merge_request: 55471
author:
type: added
---
name: environment_tier
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55471
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323166
milestone: '13.10'
type: development
group: group::release
default_enabled: false
# frozen_string_literal: true
class AddTierToEnvironments < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
add_column :environments, :tier, :smallint
end
end
def down
with_lock_retries do
remove_column :environments, :tier
end
end
end
# frozen_string_literal: true
class AddIndexToEnvironmentsTier < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
INDEX_NAME = 'index_environments_on_project_id_and_tier'
DOWNTIME = false
def up
add_concurrent_index :environments, [:project_id, :tier], where: 'tier IS NOT NULL', name: INDEX_NAME
end
def down
remove_concurrent_index :environments, :state, name: INDEX_NAME
end
end
6c52ab55814241b37014949976c4f3a0c63bea0a4f9a301735cc9f4c509f433d
\ No newline at end of file
e1641d84828e3d87aea626dbce6b3b2d231d08fcf1475991fe8d11714cdb0af0
\ No newline at end of file
......@@ -12144,7 +12144,8 @@ CREATE TABLE environments (
state character varying DEFAULT 'available'::character varying NOT NULL,
slug character varying NOT NULL,
auto_stop_at timestamp with time zone,
auto_delete_at timestamp with time zone
auto_delete_at timestamp with time zone,
tier smallint
);
CREATE SEQUENCE environments_id_seq
......@@ -22179,6 +22180,8 @@ CREATE UNIQUE INDEX index_environments_on_project_id_and_name ON environments US
CREATE UNIQUE INDEX index_environments_on_project_id_and_slug ON environments USING btree (project_id, slug);
CREATE INDEX index_environments_on_project_id_and_tier ON environments USING btree (project_id, tier) WHERE (tier IS NOT NULL);
CREATE INDEX index_environments_on_project_id_state_environment_type ON environments USING btree (project_id, state, environment_type);
CREATE INDEX index_environments_on_state_and_auto_stop_at ON environments USING btree (state, auto_stop_at) WHERE ((auto_stop_at IS NOT NULL) AND ((state)::text = 'available'::text));
......@@ -15,6 +15,22 @@ FactoryBot.define do
state { :stopped }
end
trait :production do
tier { :production }
end
trait :staging do
tier { :staging }
end
trait :testing do
tier { :testing }
end
trait :development do
tier { :development }
end
trait :with_review_app do |environment|
transient do
ref { 'master' }
......
......@@ -34,6 +34,39 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
it { is_expected.to validate_length_of(:external_url).is_at_most(255) }
describe '.before_save' do
it 'ensures environment tier when a new object is created' do
environment = build(:environment, name: 'gprd', tier: nil)
expect { environment.save }.to change { environment.tier }.from(nil).to('production')
end
it 'ensures environment tier when an existing object is updated' do
environment = create(:environment, name: 'gprd')
environment.update_column(:tier, nil)
expect { environment.stop! }.to change { environment.reload.tier }.from(nil).to('production')
end
it 'does not overwrite the existing environment tier' do
environment = create(:environment, name: 'gprd', tier: :production)
expect { environment.update!(name: 'gstg') }.not_to change { environment.reload.tier }
end
context 'when environment_tier feature flag is disabled' do
before do
stub_feature_flags(environment_tier: false)
end
it 'does not ensure environment tier' do
environment = build(:environment, name: 'gprd', tier: nil)
expect { environment.save }.not_to change { environment.tier }
end
end
end
describe '.order_by_last_deployed_at' do
let!(:environment1) { create(:environment, project: project) }
let!(:environment2) { create(:environment, project: project) }
......@@ -195,6 +228,62 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
end
end
describe '.for_tier' do
let_it_be(:environment) { create(:environment, :production) }
it 'returns the production environment when searching for production tier' do
expect(described_class.for_tier(:production)).to eq([environment])
end
it 'returns nothing when searching for staging tier' do
expect(described_class.for_tier(:staging)).to be_empty
end
end
describe '#guess_tier' do
using RSpec::Parameterized::TableSyntax
subject { environment.send(:guess_tier) }
let(:environment) { build(:environment, name: name) }
where(:name, :tier) do
'review/feature' | described_class.tiers[:development]
'review/product' | described_class.tiers[:development]
'DEV' | described_class.tiers[:development]
'development' | described_class.tiers[:development]
'trunk' | described_class.tiers[:development]
'test' | described_class.tiers[:testing]
'TEST' | described_class.tiers[:testing]
'testing' | described_class.tiers[:testing]
'testing-prd' | described_class.tiers[:testing]
'acceptance-testing' | described_class.tiers[:testing]
'QC' | described_class.tiers[:testing]
'gstg' | described_class.tiers[:staging]
'staging' | described_class.tiers[:staging]
'stage' | described_class.tiers[:staging]
'Model' | described_class.tiers[:staging]
'MODL' | described_class.tiers[:staging]
'Pre-production' | described_class.tiers[:staging]
'pre' | described_class.tiers[:staging]
'Demo' | described_class.tiers[:staging]
'gprd' | described_class.tiers[:production]
'gprd-cny' | described_class.tiers[:production]
'production' | described_class.tiers[:production]
'Production' | described_class.tiers[:production]
'prod' | described_class.tiers[:production]
'PROD' | described_class.tiers[:production]
'Live' | described_class.tiers[:production]
'canary' | described_class.tiers[:other]
'other' | described_class.tiers[:other]
'EXP' | described_class.tiers[:other]
end
with_them do
it { is_expected.to eq(tier) }
end
end
describe '#expire_etag_cache' do
let(:store) { Gitlab::EtagCaching::Store.new }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment