Commit f7110642 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch '25680-CI_ENVIRONMENT_URL' into 'master'

Add `$CI_ENVIRONMENT_URL` as a job variable

Closes #25680

See merge request !11695
parents 761e3764 2fa766e1
......@@ -138,6 +138,17 @@ module Ci
ExpandVariables.expand(environment, simple_variables) if environment
end
def environment_url
return @environment_url if defined?(@environment_url)
@environment_url =
if unexpanded_url = options&.dig(:environment, :url)
ExpandVariables.expand(unexpanded_url, simple_variables)
else
persisted_environment&.external_url
end
end
def has_environment?
environment.present?
end
......@@ -198,9 +209,7 @@ module Ci
# All variables, including those dependent on other variables
def variables
variables = simple_variables
variables += persisted_environment.predefined_variables if persisted_environment.present?
variables
simple_variables.concat(persisted_environment_variables)
end
def merge_request
......@@ -462,6 +471,18 @@ module Ci
variables.concat(legacy_variables)
end
def persisted_environment_variables
return [] unless persisted_environment
variables = persisted_environment.predefined_variables
if url = environment_url
variables << { key: 'CI_ENVIRONMENT_URL', value: url, public: true }
end
variables
end
def legacy_variables
variables = [
{ key: 'CI_BUILD_ID', value: id.to_s, public: true },
......
class CreateDeploymentService < BaseService
def execute(deployable = nil)
class CreateDeploymentService
attr_reader :job
delegate :expanded_environment_name,
:environment_url,
:project,
to: :job
def initialize(job)
@job = job
end
def execute
return unless executable?
ActiveRecord::Base.transaction do
@deployable = deployable
environment.external_url = environment_url if environment_url
environment.fire_state_event(action)
@environment = environment
@environment.external_url = expanded_url if expanded_url
@environment.fire_state_event(action)
return unless environment.save
return if environment.stopped?
return unless @environment.save
return if @environment.stopped?
deploy.tap do |deployment|
deployment.update_merge_request_metrics!
end
deploy.tap(&:update_merge_request_metrics!)
end
end
private
def executable?
project && name.present?
project && job.environment.present? && environment
end
def deploy
project.deployments.create(
environment: @environment,
ref: params[:ref],
tag: params[:tag],
sha: params[:sha],
user: current_user,
deployable: @deployable,
on_stop: options[:on_stop])
environment: environment,
ref: job.ref,
tag: job.tag,
sha: job.sha,
user: job.user,
deployable: job,
on_stop: on_stop)
end
def environment
@environment ||= project.environments.find_or_create_by(name: expanded_name)
end
def expanded_name
ExpandVariables.expand(name, variables)
end
def expanded_url
return unless url
@expanded_url ||= ExpandVariables.expand(url, variables)
end
def name
params[:environment]
end
def url
options[:url]
@environment ||= job.persisted_environment
end
def options
params[:options] || {}
def environment_options
@environment_options ||= job.options&.dig(:environment) || {}
end
def variables
params[:variables] || []
def on_stop
environment_options[:on_stop]
end
def action
options[:action] || 'start'
environment_options[:action] || 'start'
end
end
......@@ -11,15 +11,6 @@ class BuildSuccessWorker
private
def create_deployment(build)
service = CreateDeploymentService.new(
build.project, build.user,
environment: build.environment,
sha: build.sha,
ref: build.ref,
tag: build.tag,
options: build.options.to_h[:environment],
variables: build.variables)
service.execute(build)
CreateDeploymentService.new(build).execute
end
end
---
title: Add $CI_ENVIRONMENT_URL to predefined variables for pipelines
merge_request: 11695
author:
......@@ -112,6 +112,10 @@ class Gitlab::Seeder::Pipelines
setup_artifacts(build)
setup_build_log(build)
build.project.environments.
find_or_create_by(name: build.expanded_environment_name)
build.save
end
end
......
......@@ -212,12 +212,9 @@ class Gitlab::Seeder::CycleAnalytics
merge_requests.each do |merge_request|
Timecop.travel 12.hours.from_now
CreateDeploymentService.new(merge_request.project, @user, {
environment: 'production',
ref: 'master',
tag: false,
sha: @project.repository.commit('master').sha
}).execute
job = merge_request.head_pipeline.builds.where.not(environment: nil).last
CreateDeploymentService.new(job).execute
end
end
end
......
......@@ -94,6 +94,12 @@ the name given in `.gitlab-ci.yml` (with any variables expanded), while the
second is a "cleaned-up" version of the name, suitable for use in URLs, DNS,
etc.
>**Note:**
Starting with GitLab 9.3, the environment URL is exposed to the Runner via
`$CI_ENVIRONMENT_URL`. The URL would be expanded from `.gitlab-ci.yml`, or if
the URL was not defined there, the external URL from the environment would be
used.
To sum up, with the above `.gitlab-ci.yml` we have achieved that:
- All branches will run the `test` and `build` jobs.
......
......@@ -43,6 +43,7 @@ future GitLab releases.**
| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled |
| **CI_ENVIRONMENT_NAME** | 8.15 | all | The name of the environment for this job |
| **CI_ENVIRONMENT_SLUG** | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. |
| **CI_ENVIRONMENT_URL** | 9.3 | all | The URL of the environment for this job |
| **CI_JOB_ID** | 9.0 | all | The unique id of the current job that GitLab CI uses internally |
| **CI_JOB_MANUAL** | 8.12 | all | The flag to indicate that job was manually started |
| **CI_JOB_NAME** | 9.0 | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
......
......@@ -64,7 +64,8 @@ FactoryGirl.define do
trait :teardown_environment do
environment 'staging'
options environment: { name: 'staging',
action: 'stop' }
action: 'stop',
url: 'http://staging.example.com/$CI_JOB_NAME' }
end
trait :allowed_to_fail do
......
......@@ -427,6 +427,42 @@ describe Ci::Build, :models do
end
end
describe '#environment_url' do
subject { job.environment_url }
context 'when yaml environment uses $CI_COMMIT_REF_NAME' do
let(:job) do
create(:ci_build,
ref: 'master',
options: { environment: { url: 'http://review/$CI_COMMIT_REF_NAME' } })
end
it { is_expected.to eq('http://review/master') }
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' }],
options: { environment: { 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') }
let!(:environment) do
create(:environment, project: job.project, name: job.environment)
end
it 'returns the external_url from persisted environment' do
is_expected.to eq(environment.external_url)
end
end
end
describe '#starts_environment?' do
subject { build.starts_environment? }
......@@ -918,6 +954,10 @@ describe Ci::Build, :models do
it { is_expected.to eq(environment) }
end
context 'when there is no environment' do
it { is_expected.to be_nil }
end
end
describe '#play' do
......@@ -1176,11 +1216,6 @@ describe Ci::Build, :models do
end
context 'when build has an environment' do
before do
build.update(environment: 'production')
create(:environment, project: build.project, name: 'production', slug: 'prod-slug')
end
let(:environment_variables) do
[
{ key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true },
......@@ -1188,7 +1223,56 @@ describe Ci::Build, :models do
]
end
it { environment_variables.each { |v| is_expected.to include(v) } }
let!(:environment) do
create(:environment,
project: build.project,
name: 'production',
slug: 'prod-slug',
external_url: '')
end
before do
build.update(environment: 'production')
end
shared_examples 'containing environment variables' do
it { environment_variables.each { |v| is_expected.to include(v) } }
end
context 'when no URL was set' do
it_behaves_like 'containing environment variables'
it 'does not have CI_ENVIRONMENT_URL' do
keys = subject.map { |var| var[:key] }
expect(keys).not_to include('CI_ENVIRONMENT_URL')
end
end
context 'when an URL was set' do
let(:url) { 'http://host/test' }
before do
environment_variables <<
{ key: 'CI_ENVIRONMENT_URL', value: url, public: true }
end
context 'when the URL was set from the job' do
before do
build.update(options: { environment: { url: 'http://host/$CI_JOB_NAME' } })
end
it_behaves_like 'containing environment variables'
end
context 'when the URL was not set from the job, but environment' do
before do
environment.update(external_url: url)
end
it_behaves_like 'containing environment variables'
end
end
end
context 'when build started manually' do
......
......@@ -296,5 +296,20 @@ describe Ci::CreatePipelineService, services: true do
expect(Environment.find_by(name: "review/master")).not_to be_nil
end
end
context 'when environment with invalid name' do
before do
config = YAML.dump(deploy: { environment: { name: 'name,with,commas' }, script: 'ls' })
stub_ci_pipeline_yaml_file(config)
end
it 'does not create an environment' do
expect do
result = execute_service
expect(result).to be_persisted
end.not_to change { Environment.count }
end
end
end
end
......@@ -51,12 +51,43 @@ module CycleAnalyticsHelpers
end
def deploy_master(environment: 'production')
CreateDeploymentService.new(project, user, {
environment: environment,
ref: 'master',
tag: false,
sha: project.repository.commit('master').sha
}).execute
dummy_job =
case environment
when 'production'
dummy_production_job
when 'staging'
dummy_staging_job
else
raise ArgumentError
end
CreateDeploymentService.new(dummy_job).execute
end
def dummy_production_job
@dummy_job ||= new_dummy_job('production')
end
def dummy_staging_job
@dummy_job ||= new_dummy_job('staging')
end
def dummy_pipeline
@dummy_pipeline ||=
Ci::Pipeline.new(sha: project.repository.commit('master').sha)
end
def new_dummy_job(environment)
project.environments.find_or_create_by(name: environment)
Ci::Build.new(
project: project,
user: user,
environment: environment,
ref: 'master',
tag: false,
name: 'dummy',
pipeline: dummy_pipeline)
end
end
......
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