diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md index 54f9bebf49d65ce3e5afde086837e77520ccd4b3..e1f23a9e3917247c6a29efc98853be54a8154b98 100644 --- a/doc/ci/variables/predefined_variables.md +++ b/doc/ci/variables/predefined_variables.md @@ -160,6 +160,9 @@ Predefined variables become available at three different phases of pipeline exec | `CI_SHARED_ENVIRONMENT` | Pipeline | Only available if the job is executed in a shared environment (something that is persisted across CI/CD invocations, like the `shell` or `ssh` executor). `true` when available. | | `CI_TEMPLATE_REGISTRY_HOST` | Pre-pipeline | The host of the registry used by CI/CD templates. Defaults to `registry.gitlab.com`. Introduced in GitLab 15.3. | | `CI_TRIGGER_SHORT_TOKEN` | Job-only | First 4 characters of the [trigger token](../triggers/_index.md#create-a-pipeline-trigger-token) of the current job. Only available if the pipeline was [triggered with a trigger token](../triggers/_index.md). For example, for a trigger token of `glptt-1234567890abcdefghij`, `CI_TRIGGER_SHORT_TOKEN` would be `1234`. Introduced in GitLab 17.0. | +| `CI_UPSTREAM_JOB_ID` | Pre-pipeline | ID of the upstream trigger job that triggered the current pipeline in a multi-project pipeline. Introduced in GitLab 18.7. | +| `CI_UPSTREAM_PIPELINE_ID` | Pre-pipeline | ID of the upstream pipeline that triggered the current pipeline in a multi-project pipeline. Introduced in GitLab 18.7. | +| `CI_UPSTREAM_PROJECT_ID` | Pre-pipeline | ID of the upstream project that triggered the current pipeline in a multi-project pipeline. Introduced in GitLab 18.7. | | `GITLAB_CI` | Pre-pipeline | Available for all jobs executed in CI/CD. `true` when available. | | `GITLAB_FEATURES` | Pre-pipeline | The comma-separated list of licensed features available for the GitLab instance and license. | | `GITLAB_USER_EMAIL` | Pipeline | The email of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the email of the user who started the job. | diff --git a/lib/gitlab/ci/variables/builder/pipeline.rb b/lib/gitlab/ci/variables/builder/pipeline.rb index d25c1a020d5e24bdc8ea7da6945eb466f4f1f2fd..1aecb87381973baaeaba39c513c225d47a9e9654 100644 --- a/lib/gitlab/ci/variables/builder/pipeline.rb +++ b/lib/gitlab/ci/variables/builder/pipeline.rb @@ -35,6 +35,11 @@ def predefined_variables if pipeline.external_pull_request_event? && pipeline.external_pull_request variables.concat(pipeline.external_pull_request.predefined_variables) end + + if pipeline.source_pipeline&.source_bridge.present? && + pipeline.source_pipeline.source_bridge.triggers_cross_project_pipeline? + variables.concat(predefined_upstream_variables) + end end end @@ -106,6 +111,15 @@ def predefined_merge_request_variables end strong_memoize_attr :predefined_merge_request_variables + def predefined_upstream_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + variables.append(key: 'CI_UPSTREAM_PIPELINE_ID', value: pipeline.source_pipeline.source_pipeline_id.to_s) + variables.append(key: 'CI_UPSTREAM_PROJECT_ID', value: pipeline.source_pipeline.source_project_id.to_s) + variables.append(key: 'CI_UPSTREAM_JOB_ID', value: pipeline.source_pipeline.source_job_id.to_s) + end + end + strong_memoize_attr :predefined_upstream_variables + def merge_request_diff pipeline.merge_request_diff end diff --git a/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb b/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb index c450f9d3df962767ab7ed5f4302e9db2d74d9a38..a50abb6b8f9b97607a1eddb011a2ee280f87da00 100644 --- a/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb +++ b/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb @@ -379,6 +379,28 @@ end end + context 'when pipeline triggered by upstream project' do + let_it_be(:upstream_project) { create(:project) } + let(:upstream_pipeline) { create(:ci_pipeline, project: upstream_project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:bridge) do + create(:ci_bridge, pipeline: upstream_pipeline, downstream_pipeline: pipeline, downstream: project) + end + + before do + create(:ci_sources_pipeline, source_job: bridge, pipeline: pipeline) + end + + it 'exposes upstream pipeline variables' do + expect(subject.to_hash) + .to include( + 'CI_UPSTREAM_PROJECT_ID' => upstream_pipeline.project_id.to_s, + 'CI_UPSTREAM_PIPELINE_ID' => upstream_pipeline.id.to_s, + 'CI_UPSTREAM_JOB_ID' => bridge.id.to_s + ) + end + end + context 'when source is external pull request' do let(:pipeline) do create(:ci_pipeline, source: :external_pull_request_event, external_pull_request: pull_request) diff --git a/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb b/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb index 8f7f19da5fbda4a677423d32cd3667bc5d8fb7e5..9293c23fa4f5e1409c24f2da8490729a8a4fa734 100644 --- a/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb +++ b/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb @@ -50,7 +50,7 @@ let(:bridge) do instance_double( - 'Ci::Bridge', + Ci::Bridge, variables: bridge_variables, forward_yaml_variables?: true, forward_pipeline_variables?: true, diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index 3476b4c9cb879824fd44d2d2fbc5ef4d1aeead7f..97445ddc701f07faeb758c55a7c84038788737f6 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -108,7 +108,9 @@ source: :pipeline, target_revision: { ref: downstream_project.default_branch, - variables_attributes: [] + variables_attributes: [ + { key: 'CI_UPSTREAM_PROJECT_ID', value: bridge.project.id.to_s } + ] }, execute_params: { ignore_skip_ci: true, @@ -519,11 +521,18 @@ # return the new variables due to memoized results from previous tests. let(:pipeline) { create(:ci_pipeline, project: project) } + let(:upstream_project_id_variable) do + { key: 'CI_UPSTREAM_PROJECT_ID', value: bridge.pipeline.project.id.to_s } + end + subject(:downstream_variables) { bridge.downstream_variables } it 'returns variables that are going to be passed downstream' do expect(bridge.downstream_variables) - .to contain_exactly(key: 'BRIDGE', value: 'cross') + .to contain_exactly( + upstream_project_id_variable, + { key: 'BRIDGE', value: 'cross' } + ) end context 'when using variables interpolation' do @@ -581,6 +590,7 @@ it 'does not expand file variable and forwards the file variable' do expected_vars = [ + upstream_project_id_variable, { key: 'EXPANDED_FILE', value: '$TEST_FILE_VAR' }, { key: 'TEST_FILE_VAR', value: 'test-file-value', variable_type: :file } ] @@ -596,7 +606,10 @@ it 'does not expand variable recursively' do expect(bridge.downstream_variables) - .to contain_exactly(key: 'EXPANDED', value: '$EXPANDED') + .to contain_exactly( + upstream_project_id_variable, + { key: 'EXPANDED', value: '$EXPANDED' } + ) end end @@ -632,7 +645,8 @@ end it 'returns variables according to the forward value' do - expect(bridge.downstream_variables.map { |v| v[:key] }).to contain_exactly(*variables) + expected_keys = ['CI_UPSTREAM_PROJECT_ID'] + variables + expect(bridge.downstream_variables.map { |v| v[:key] }).to contain_exactly(*expected_keys) end end @@ -647,7 +661,10 @@ end it 'uses the pipeline variable' do - expect(bridge.downstream_variables).to contain_exactly({ key: 'SHARED_KEY', value: 'new value' }) + expect(bridge.downstream_variables).to contain_exactly( + upstream_project_id_variable, + { key: 'SHARED_KEY', value: 'new value' } + ) end end @@ -664,7 +681,10 @@ # The current behaviour forwards the file variable as an environment variable. # TODO: decide whether to forward as a file var in https://gitlab.com/gitlab-org/gitlab/-/issues/416334 it 'forwards the pipeline file variable' do - expect(bridge.downstream_variables).to contain_exactly({ key: 'FILE_VAR', value: 'new value' }) + expect(bridge.downstream_variables).to contain_exactly( + upstream_project_id_variable, + { key: 'FILE_VAR', value: 'new value' } + ) end end @@ -682,6 +702,7 @@ it 'does not expand the scoped file variable and forwards the file variable' do expected_vars = [ + upstream_project_id_variable, { key: 'FILE_VAR', value: '$PROJECT_FILE_VAR' }, { key: 'YAML_VAR', value: '$PROJECT_FILE_VAR' }, { key: 'PROJECT_FILE_VAR', value: 'project file', variable_type: :file } @@ -705,6 +726,7 @@ it 'adds the schedule variable' do expected_vars = [ + upstream_project_id_variable, { key: 'BRIDGE', value: 'cross' }, { key: 'schedule_var_key', value: 'schedule var value' } ] @@ -730,7 +752,10 @@ # The current behaviour forwards the file variable as an environment variable. # TODO: decide whether to forward as a file var in https://gitlab.com/gitlab-org/gitlab/-/issues/416334 it 'forwards the schedule file variable' do - expect(bridge.downstream_variables).to contain_exactly({ key: 'schedule_var_key', value: 'schedule var value' }) + expect(bridge.downstream_variables).to contain_exactly( + upstream_project_id_variable, + { key: 'schedule_var_key', value: 'schedule var value' } + ) end end @@ -750,6 +775,7 @@ it 'does not expand the scoped file variable and forwards the file variable' do expected_vars = [ + upstream_project_id_variable, { key: 'schedule_var_key', value: '$PROJECT_FILE_VAR' }, { key: 'PROJECT_FILE_VAR', value: 'project file', variable_type: :file } ] @@ -800,6 +826,7 @@ it 'expands variables according to their raw attributes' do expect(downstream_variables).to contain_exactly( + upstream_project_id_variable, { key: 'BRIDGE', value: 'cross' }, { key: 'VAR1', value: 'value1' }, { key: 'VAR2', value: 'value2 value1' },