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 ...@@ -138,6 +138,17 @@ module Ci
ExpandVariables.expand(environment, simple_variables) if environment ExpandVariables.expand(environment, simple_variables) if environment
end 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? def has_environment?
environment.present? environment.present?
end end
...@@ -198,9 +209,7 @@ module Ci ...@@ -198,9 +209,7 @@ module Ci
# All variables, including those dependent on other variables # All variables, including those dependent on other variables
def variables def variables
variables = simple_variables simple_variables.concat(persisted_environment_variables)
variables += persisted_environment.predefined_variables if persisted_environment.present?
variables
end end
def merge_request def merge_request
...@@ -462,6 +471,18 @@ module Ci ...@@ -462,6 +471,18 @@ module Ci
variables.concat(legacy_variables) variables.concat(legacy_variables)
end 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 def legacy_variables
variables = [ variables = [
{ key: 'CI_BUILD_ID', value: id.to_s, public: true }, { key: 'CI_BUILD_ID', value: id.to_s, public: true },
......
class CreateDeploymentService < BaseService class CreateDeploymentService
def execute(deployable = nil) attr_reader :job
delegate :expanded_environment_name,
:environment_url,
:project,
to: :job
def initialize(job)
@job = job
end
def execute
return unless executable? return unless executable?
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
@deployable = deployable environment.external_url = environment_url if environment_url
environment.fire_state_event(action)
@environment = environment return unless environment.save
@environment.external_url = expanded_url if expanded_url return if environment.stopped?
@environment.fire_state_event(action)
return unless @environment.save deploy.tap(&:update_merge_request_metrics!)
return if @environment.stopped?
deploy.tap do |deployment|
deployment.update_merge_request_metrics!
end
end end
end end
private private
def executable? def executable?
project && name.present? project && job.environment.present? && environment
end end
def deploy def deploy
project.deployments.create( project.deployments.create(
environment: @environment, environment: environment,
ref: params[:ref], ref: job.ref,
tag: params[:tag], tag: job.tag,
sha: params[:sha], sha: job.sha,
user: current_user, user: job.user,
deployable: @deployable, deployable: job,
on_stop: options[:on_stop]) on_stop: on_stop)
end end
def environment def environment
@environment ||= project.environments.find_or_create_by(name: expanded_name) @environment ||= job.persisted_environment
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]
end end
def options def environment_options
params[:options] || {} @environment_options ||= job.options&.dig(:environment) || {}
end end
def variables def on_stop
params[:variables] || [] environment_options[:on_stop]
end end
def action def action
options[:action] || 'start' environment_options[:action] || 'start'
end end
end end
...@@ -11,15 +11,6 @@ class BuildSuccessWorker ...@@ -11,15 +11,6 @@ class BuildSuccessWorker
private private
def create_deployment(build) def create_deployment(build)
service = CreateDeploymentService.new( CreateDeploymentService.new(build).execute
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)
end end
end end
---
title: Add $CI_ENVIRONMENT_URL to predefined variables for pipelines
merge_request: 11695
author:
...@@ -112,6 +112,10 @@ class Gitlab::Seeder::Pipelines ...@@ -112,6 +112,10 @@ class Gitlab::Seeder::Pipelines
setup_artifacts(build) setup_artifacts(build)
setup_build_log(build) setup_build_log(build)
build.project.environments.
find_or_create_by(name: build.expanded_environment_name)
build.save build.save
end end
end end
......
...@@ -212,12 +212,9 @@ class Gitlab::Seeder::CycleAnalytics ...@@ -212,12 +212,9 @@ class Gitlab::Seeder::CycleAnalytics
merge_requests.each do |merge_request| merge_requests.each do |merge_request|
Timecop.travel 12.hours.from_now Timecop.travel 12.hours.from_now
CreateDeploymentService.new(merge_request.project, @user, { job = merge_request.head_pipeline.builds.where.not(environment: nil).last
environment: 'production',
ref: 'master', CreateDeploymentService.new(job).execute
tag: false,
sha: @project.repository.commit('master').sha
}).execute
end end
end end
end end
......
...@@ -94,6 +94,12 @@ the name given in `.gitlab-ci.yml` (with any variables expanded), while the ...@@ -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, second is a "cleaned-up" version of the name, suitable for use in URLs, DNS,
etc. 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: To sum up, with the above `.gitlab-ci.yml` we have achieved that:
- All branches will run the `test` and `build` jobs. - All branches will run the `test` and `build` jobs.
......
...@@ -43,6 +43,7 @@ future GitLab releases.** ...@@ -43,6 +43,7 @@ future GitLab releases.**
| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled | | **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_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_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_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_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` | | **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 ...@@ -64,7 +64,8 @@ FactoryGirl.define do
trait :teardown_environment do trait :teardown_environment do
environment 'staging' environment 'staging'
options environment: { name: 'staging', options environment: { name: 'staging',
action: 'stop' } action: 'stop',
url: 'http://staging.example.com/$CI_JOB_NAME' }
end end
trait :allowed_to_fail do trait :allowed_to_fail do
......
...@@ -427,6 +427,42 @@ describe Ci::Build, :models do ...@@ -427,6 +427,42 @@ describe Ci::Build, :models do
end end
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 describe '#starts_environment?' do
subject { build.starts_environment? } subject { build.starts_environment? }
...@@ -918,6 +954,10 @@ describe Ci::Build, :models do ...@@ -918,6 +954,10 @@ describe Ci::Build, :models do
it { is_expected.to eq(environment) } it { is_expected.to eq(environment) }
end end
context 'when there is no environment' do
it { is_expected.to be_nil }
end
end end
describe '#play' do describe '#play' do
...@@ -1176,11 +1216,6 @@ describe Ci::Build, :models do ...@@ -1176,11 +1216,6 @@ describe Ci::Build, :models do
end end
context 'when build has an environment' do 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 let(:environment_variables) do
[ [
{ key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true }, { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true },
...@@ -1188,9 +1223,58 @@ describe Ci::Build, :models do ...@@ -1188,9 +1223,58 @@ describe Ci::Build, :models do
] ]
end end
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) } } it { environment_variables.each { |v| is_expected.to include(v) } }
end 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 context 'when build started manually' do
before do before do
build.update_attributes(when: :manual) build.update_attributes(when: :manual)
......
...@@ -296,5 +296,20 @@ describe Ci::CreatePipelineService, services: true do ...@@ -296,5 +296,20 @@ describe Ci::CreatePipelineService, services: true do
expect(Environment.find_by(name: "review/master")).not_to be_nil expect(Environment.find_by(name: "review/master")).not_to be_nil
end end
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
end end
...@@ -51,12 +51,43 @@ module CycleAnalyticsHelpers ...@@ -51,12 +51,43 @@ module CycleAnalyticsHelpers
end end
def deploy_master(environment: 'production') def deploy_master(environment: 'production')
CreateDeploymentService.new(project, user, { 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, environment: environment,
ref: 'master', ref: 'master',
tag: false, tag: false,
sha: project.repository.commit('master').sha name: 'dummy',
}).execute pipeline: dummy_pipeline)
end end
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