diff --git a/app/services/ci/components/fetch_service.rb b/app/services/ci/components/fetch_service.rb index fe734ab59d52db2862d1ff14eea54a3e41b45b9a..c8a35bf13f0f09e4b16f090dcdd9ef55cf37c1e0 100644 --- a/app/services/ci/components/fetch_service.rb +++ b/app/services/ci/components/fetch_service.rb @@ -32,7 +32,8 @@ def execute project: component_path.project, sha: component_path.sha, name: component_path.component_name, - version: component_path.matched_version + version: component_path.matched_version, + reference: component_path.reference }) elsif component_path.invalid_usage_for_latest? ServiceResponse.error( diff --git a/app/validators/json_schemas/catalog_resource_component_spec.json b/app/validators/json_schemas/catalog_resource_component_spec.json index b48a93a4521194cf0468441ee69ea232363a47df..c6794ff76fa8c7d613457b608df74f9818b2e7bb 100644 --- a/app/validators/json_schemas/catalog_resource_component_spec.json +++ b/app/validators/json_schemas/catalog_resource_component_spec.json @@ -36,7 +36,8 @@ "enum": [ "name", "sha", - "version" + "version", + "reference" ] }, "uniqueItems": true diff --git a/lib/gitlab/ci/components/instance_path.rb b/lib/gitlab/ci/components/instance_path.rb index 56e84d292c7663a366710b76906def6575fcf1a9..3eb063c918dd89be0ac3abdfcb48526891b8084f 100644 --- a/lib/gitlab/ci/components/instance_path.rb +++ b/lib/gitlab/ci/components/instance_path.rb @@ -7,7 +7,7 @@ class InstancePath include Gitlab::Utils::StrongMemoize include ::Gitlab::LoopHelpers - attr_reader :component_name + attr_reader :component_name, :reference SHORTHAND_SEMVER_PATTERN = /^\d+(\.\d+)?$/ LATEST = '~latest' @@ -21,7 +21,7 @@ def self.fqdn_prefix end def initialize(address:) - @full_path, @version = address.to_s.split('@', 2) + @full_path, @reference = address.to_s.split('@', 2) end def fetch_content!(current_user:) @@ -52,19 +52,17 @@ def matched_version strong_memoize_attr :matched_version def invalid_usage_for_latest? - version == LATEST && project && project.catalog_resource.nil? + reference == LATEST && project && project.catalog_resource.nil? end def invalid_usage_for_partial_semver? - version.match?(SHORTHAND_SEMVER_PATTERN) && project && project.catalog_resource.nil? + reference.match?(SHORTHAND_SEMVER_PATTERN) && project && project.catalog_resource.nil? end private - attr_reader :version - def find_version_sha - return find_latest_sha if version == LATEST + return find_latest_sha if reference == LATEST sha_by_shorthand_semver || sha_by_released_tag || sha_by_ref end @@ -72,12 +70,12 @@ def find_version_sha def find_catalog_version return unless project.catalog_resource - if version == LATEST + if reference == LATEST catalog_resource_version_latest - elsif version.match?(SHORTHAND_SEMVER_PATTERN) + elsif reference.match?(SHORTHAND_SEMVER_PATTERN) catalog_resource_version_by_short_semver else - project.catalog_resource.versions.by_name(version).first + project.catalog_resource.versions.by_name(reference).first end end @@ -88,7 +86,7 @@ def find_latest_sha end def sha_by_shorthand_semver - return unless version.match?(SHORTHAND_SEMVER_PATTERN) + return unless reference.match?(SHORTHAND_SEMVER_PATTERN) return unless project.catalog_resource catalog_resource_version_by_short_semver&.sha @@ -100,17 +98,17 @@ def catalog_resource_version_latest strong_memoize_attr :catalog_resource_version_latest def catalog_resource_version_by_short_semver - major, minor = version.split(".") + major, minor = reference.split(".") project.catalog_resource.versions.latest(major, minor) end strong_memoize_attr :catalog_resource_version_by_short_semver def sha_by_released_tag - project.releases.find_by_tag(version)&.sha + project.releases.find_by_tag(reference)&.sha end def sha_by_ref - project.commit(version)&.id + project.commit(reference)&.id end def find_project_by_component_path(path) diff --git a/lib/gitlab/ci/config/external/file/component.rb b/lib/gitlab/ci/config/external/file/component.rb index 77b835fdd27399cb0a5539419b241e1fef19615a..6cd69660a3068117fcd1c1b7decd32daf4dd698e 100644 --- a/lib/gitlab/ci/config/external/file/component.rb +++ b/lib/gitlab/ci/config/external/file/component.rb @@ -118,7 +118,8 @@ def component_attrs project: component_payload.fetch(:project), sha: component_payload.fetch(:sha), name: component_payload.fetch(:name), - version: component_payload.fetch(:version) + version: component_payload.fetch(:version), + reference: component_payload.fetch(:reference) } end diff --git a/lib/gitlab/ci/config/header/component.rb b/lib/gitlab/ci/config/header/component.rb index debbc5af75001281765e640ef9702ebe3aee3813..d611e595ac039da5d0a7106e7b7930656c244fc1 100644 --- a/lib/gitlab/ci/config/header/component.rb +++ b/lib/gitlab/ci/config/header/component.rb @@ -13,7 +13,7 @@ module Header class Component < ::Gitlab::Config::Entry::Node include ::Gitlab::Config::Entry::Validatable - ALLOWED_VALUES = %i[name sha version].freeze + ALLOWED_VALUES = %i[name sha version reference].freeze ALLOWED_VALUES_TO_S = ALLOWED_VALUES.map(&:to_s).freeze validations do diff --git a/spec/lib/gitlab/ci/components/instance_path_spec.rb b/spec/lib/gitlab/ci/components/instance_path_spec.rb index 0f5515e2fc7c63282a6709a6e6236ae80adac452..dd8973faa19ff315e9e0f259786b99bf78a4e84b 100644 --- a/spec/lib/gitlab/ci/components/instance_path_spec.rb +++ b/spec/lib/gitlab/ci/components/instance_path_spec.rb @@ -13,6 +13,38 @@ allow(Gitlab.config.gitlab).to receive(:server_fqdn).and_return(server_fqdn) end + shared_context 'with catalog resource project with components' do + let_it_be(:project) do + create( + :project, :custom_repo, + files: { + 'templates/secret-detection.yml' => 'image: alpine_1', + 'templates/dast/template.yml' => 'image: alpine_2', + 'templates/dast/another-template.yml' => 'image: alpine_3', + 'templates/dast/another-folder/template.yml' => 'image: alpine_4' + } + ) + end + + let_it_be(:catalog_resource) { create(:ci_catalog_resource, :published, project: project) } + let_it_be(:commit) { project.repository.commit } + let_it_be(:project_path) { project.full_path } + + before_all do + project.add_maintainer(user) + + create( + :release, :with_catalog_resource_version, + project: project, tag: '0.1.0', author: user, sha: commit.id + ) + + create( + :release, :with_catalog_resource_version, + project: project, tag: '0.1.2', author: user, sha: commit.id + ) + end + end + describe '#fetch_content!' do let(:version) { 'master' } let(:project_path) { project.full_path } @@ -298,37 +330,49 @@ end describe '#matched_version' do - let_it_be(:project) do - create( - :project, :custom_repo, - files: { - 'templates/secret-detection.yml' => 'image: alpine_1', - 'templates/dast/template.yml' => 'image: alpine_2', - 'templates/dast/another-template.yml' => 'image: alpine_3', - 'templates/dast/another-folder/template.yml' => 'image: alpine_4' - } - ) + include_context 'with catalog resource project with components' + + subject(:matched_version) { path.matched_version } + + context 'when using a full semantic version' do + let(:address) { "acme.com/#{project_path}/secret-detection@0.1.0" } + + it { is_expected.to eq '0.1.0' } end - let_it_be(:catalog_resource) { create(:ci_catalog_resource, :published, project: project) } - let_it_be(:commit) { project.repository.commit } - let_it_be(:project_path) { project.full_path } + context 'when using a partial semantic version' do + let(:address) { "acme.com/#{project_path}/secret-detection@0.1" } - before_all do - project.add_maintainer(user) + it { is_expected.to eq '0.1.2' } + end - create( - :release, :with_catalog_resource_version, - project: project, tag: '0.1.0', author: user, sha: commit.id - ) + context 'when using ~latest' do + let(:address) { "acme.com/#{project_path}/secret-detection@~latest" } - create( - :release, :with_catalog_resource_version, - project: project, tag: '0.1.2', author: user, sha: commit.id - ) + it { is_expected.to eq '0.1.2' } end - subject(:matched_version) { path.matched_version } + context 'when using a SHA' do + let(:address) { "acme.com/#{project_path}/secret-detection@#{commit.id}" } + + it { is_expected.to be_nil } + end + + context 'when project is not a catalog resource' do + let(:address) { "acme.com/#{project_path}/secret-detection@0.1.0" } + + before do + catalog_resource.destroy! + end + + it { is_expected.to be_nil } + end + end + + describe '#reference' do + include_context 'with catalog resource project with components' + + subject(:reference) { path.reference } context 'when using a full semantic version' do let(:address) { "acme.com/#{project_path}/secret-detection@0.1.0" } @@ -339,19 +383,19 @@ context 'when using a partial semantic version' do let(:address) { "acme.com/#{project_path}/secret-detection@0.1" } - it { is_expected.to eq '0.1.2' } + it { is_expected.to eq '0.1' } end context 'when using ~latest' do let(:address) { "acme.com/#{project_path}/secret-detection@~latest" } - it { is_expected.to eq '0.1.2' } + it { is_expected.to eq '~latest' } end context 'when using a SHA' do let(:address) { "acme.com/#{project_path}/secret-detection@#{commit.id}" } - it { is_expected.to be_nil } + it { is_expected.to eq commit.id } end context 'when project is not a catalog resource' do @@ -361,7 +405,7 @@ catalog_resource.destroy! end - it { is_expected.to be_nil } + it { is_expected.to eq '0.1.0' } end end diff --git a/spec/lib/gitlab/ci/config/external/context_spec.rb b/spec/lib/gitlab/ci/config/external/context_spec.rb index c197901282ddc4817375e024b69fbf1d927e8836..0d50ae176630612dcc84cd469d78eaf7ef7ed44b 100644 --- a/spec/lib/gitlab/ci/config/external/context_spec.rb +++ b/spec/lib/gitlab/ci/config/external/context_spec.rb @@ -47,7 +47,7 @@ end context 'with component_data' do - let(:component_data) { { name: 'my-component', sha: 'abc123', version: '1.0.0' } } + let(:component_data) { { name: 'my-component', sha: 'abc123', version: '1.0.0', reference: '1.0.0' } } let(:attributes) do { project: project, diff --git a/spec/lib/gitlab/ci/config/external/file/component_spec.rb b/spec/lib/gitlab/ci/config/external/file/component_spec.rb index 3ba1ec0289ef2e9f1def71e406b811f91a70f009..0467308654f13b3dbe3b84d6f89ea760aba50046 100644 --- a/spec/lib/gitlab/ci/config/external/file/component_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/component_spec.rb @@ -36,7 +36,8 @@ project: project, sha: 'my_component_sha', name: 'my_component', - version: nil + version: nil, + reference: nil }) end @@ -172,7 +173,8 @@ project: project, sha: 'my_component_sha', name: 'my_component', - version: nil + version: nil, + reference: nil } ) end @@ -190,7 +192,8 @@ component_data: { name: 'my_component', sha: 'my_component_sha', - version: nil + version: nil, + reference: nil } ) end diff --git a/spec/lib/gitlab/ci/config/header/component_spec.rb b/spec/lib/gitlab/ci/config/header/component_spec.rb index 2e890a2d9a5ec5d2bae24c73c382bc9ab6ba3e69..5000de8ef1c420c2f562c9c1236f4a7ab174c117 100644 --- a/spec/lib/gitlab/ci/config/header/component_spec.rb +++ b/spec/lib/gitlab/ci/config/header/component_spec.rb @@ -7,7 +7,7 @@ describe 'validations' do context 'when config is an array of valid strings' do - let(:config) { %w[name sha version] } + let(:config) { %w[name sha version reference] } it 'passes validations' do expect(component).to be_valid @@ -65,9 +65,9 @@ subject(:value) { component.value } context 'when config is valid' do - let(:config) { %w[name sha version] } + let(:config) { %w[name sha version reference] } - it { is_expected.to match_array([:name, :sha, :version]) } + it { is_expected.to match_array([:name, :sha, :version, :reference]) } end context 'when config has duplicate values' do diff --git a/spec/lib/gitlab/ci/config/header/root_spec.rb b/spec/lib/gitlab/ci/config/header/root_spec.rb index 7b7d7d2b87f0112bd334f89714549b35bbf9ffe5..17e66f735eee2ea5fc10b75c4c20c3639e66dc81 100644 --- a/spec/lib/gitlab/ci/config/header/root_spec.rb +++ b/spec/lib/gitlab/ci/config/header/root_spec.rb @@ -136,13 +136,13 @@ let(:header_hash) do { spec: { - component: %w[name sha version] + component: %w[name sha version reference] } } end it 'returns the component value as symbols' do - expect(config.spec_component_value).to match_array([:name, :sha, :version]) + expect(config.spec_component_value).to match_array([:name, :sha, :version, :reference]) end end diff --git a/spec/lib/gitlab/ci/config/header/spec_spec.rb b/spec/lib/gitlab/ci/config/header/spec_spec.rb index f318c39880d3750c98aa205fc4f8b85a131a5d7d..5925c67af0b6930f68091b230f8ca7763d46a617 100644 --- a/spec/lib/gitlab/ci/config/header/spec_spec.rb +++ b/spec/lib/gitlab/ci/config/header/spec_spec.rb @@ -31,7 +31,7 @@ context 'when spec contains component configuration' do let(:spec_hash) do { - component: %w[name sha version] + component: %w[name sha version reference] } end @@ -41,7 +41,7 @@ end it 'returns the component value' do - expect(config.component_value).to match_array([:name, :sha, :version]) + expect(config.component_value).to match_array([:name, :sha, :version, :reference]) end end diff --git a/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb b/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb index bba6c41aaccf15413b536f1e364462ae0d1fc23b..942948a68ed8f516cdc5e865625443ba601fec4d 100644 --- a/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb +++ b/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb @@ -176,17 +176,18 @@ let(:yaml_context) do ::Gitlab::Ci::Config::Yaml::Context.new( variables: [], - component: { name: 'my-component', sha: 'abc123', version: '1.0.0' } + component: { name: 'my-component', sha: 'abc123', version: '1.0.0', reference: '1.0' } ) end context 'when component values are specified in spec' do let(:header) do - { spec: { component: %w[name sha version] } } + { spec: { component: %w[name sha version reference] } } end let(:content) do - { test: 'Component $[[ component.name ]] at $[[ component.sha ]] version $[[ component.version ]]' } + { test: 'Component $[[ component.name ]] at $[[ component.sha ]] version $[[ component.version ]] ' \ + 'reference $[[ component.reference ]]' } end let(:arguments) { {} } @@ -196,7 +197,7 @@ expect(subject).to be_interpolated expect(subject).to be_valid - expect(subject.to_hash).to eq({ test: 'Component my-component at abc123 version 1.0.0' }) + expect(subject.to_hash).to eq({ test: 'Component my-component at abc123 version 1.0.0 reference 1.0' }) end end diff --git a/spec/lib/gitlab/ci/config/yaml/context_spec.rb b/spec/lib/gitlab/ci/config/yaml/context_spec.rb index 372a5a224bafde7a4b86c4ac5d8d63c733f18a92..37b8a04948887fe7ba952fa7aaae754be2e38936 100644 --- a/spec/lib/gitlab/ci/config/yaml/context_spec.rb +++ b/spec/lib/gitlab/ci/config/yaml/context_spec.rb @@ -29,7 +29,7 @@ context 'with component data provided' do let(:component_data) do - { name: 'my-component', sha: 'abc123', version: '1.0.0' } + { name: 'my-component', sha: 'abc123', version: '1.0.0', reference: '1.0' } end subject(:context) { described_class.new(component: component_data) } @@ -56,7 +56,7 @@ describe '#component' do let(:component_data) do - { name: 'test-component', sha: 'def456', version: '2.0.0' } + { name: 'test-component', sha: 'def456', version: '2.0.0', reference: '2.0' } end subject(:context) { described_class.new(component: component_data) } diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 11d1d13e0773cf9362e7261449fdaf2c0d3b5e5b..1c8bd7c2077530c7e296173769fdb4b1d9be317b 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -211,8 +211,8 @@ it 'returns only components included with `include:component`' do expect(config.metadata[:includes].size).to eq(3) expect(included_components).to contain_exactly( - { project: project1, sha: project1.commit.sha, name: 'dast', version: nil }, - { project: project2, sha: project2.commit.sha, name: 'template', version: nil } + { project: project1, sha: project1.commit.sha, name: 'dast', version: nil, reference: '1.0.0' }, + { project: project2, sha: project2.commit.sha, name: 'template', version: nil, reference: '1.0.0' } ) end @@ -228,7 +228,7 @@ it 'returns only unique components' do expect(config.metadata[:includes].size).to eq(2) expect(included_components).to contain_exactly( - { project: project1, sha: project1.commit.sha, name: 'dast', version: nil } + { project: project1, sha: project1.commit.sha, name: 'dast', version: nil, reference: '1.0.0' } ) end end diff --git a/spec/services/ci/components/fetch_service_spec.rb b/spec/services/ci/components/fetch_service_spec.rb index d62f37163fb651772d408688459d617730d80ce1..f6d855d33005a7616f69337048f9fab420dd4bf8 100644 --- a/spec/services/ci/components/fetch_service_spec.rb +++ b/spec/services/ci/components/fetch_service_spec.rb @@ -108,6 +108,11 @@ let(:version) { project.default_branch } it_behaves_like 'component address' + + it 'returns the reference' do + expect(result).to be_success + expect(result.payload[:reference]).to eq(version) + end end context 'when version is a tag name' do @@ -119,12 +124,22 @@ expect(result).to be_success expect(result.payload[:version]).to eq(version) end + + it 'returns the reference' do + expect(result).to be_success + expect(result.payload[:reference]).to eq(version) + end end context 'when version is a commit sha' do let(:version) { project.repository.tags.first.id } it_behaves_like 'component address' + + it 'returns the reference' do + expect(result).to be_success + expect(result.payload[:reference]).to eq(version) + end end context 'when version is not provided' do @@ -138,7 +153,7 @@ context 'when version is ~latest' do let(:version) { '~latest' } - let_it_be(:project) { create(:project) } + let_it_be(:project) { create(:project, :empty_repo) } context 'and the project is not a catalog resource' do it 'returns an error' do @@ -164,7 +179,7 @@ context 'when version is a partial semantic version' do let(:version) { '1.0' } - let_it_be(:project) { create(:project) } + let_it_be(:project) { create(:project, :empty_repo) } context 'and the project is not a catalog resource' do it 'returns an error' do diff --git a/spec/services/ci/create_pipeline_service/component_interpolation_spec.rb b/spec/services/ci/create_pipeline_service/component_interpolation_spec.rb index 93d3200cb1ff46f2af74948831d1ba70debfeff8..e38f3cd7eaea6b6ca93b899aa684247bac09727c 100644 --- a/spec/services/ci/create_pipeline_service/component_interpolation_spec.rb +++ b/spec/services/ci/create_pipeline_service/component_interpolation_spec.rb @@ -20,7 +20,7 @@ let_it_be(:component_yaml) do <<~YAML spec: - component: [name, sha, version] + component: [name, sha, version, reference] inputs: compiler: default: gcc @@ -33,7 +33,7 @@ test: script: - echo "Building with $[[ inputs.compiler ]] and optimization level $[[ inputs.optimization_level ]]" - - echo "Component $[[ component.name ]] / $[[ component.sha ]] / $[[ component.version ]]" + - echo "Component $[[ component.name ]] / $[[ component.sha ]] / $[[ component.version ]] / $[[ component.reference ]]" YAML end @@ -80,7 +80,7 @@ test_job = pipeline.builds.find { |build| build.name == 'test' } expect(test_job.options[:script]).to eq([ 'echo "Building with gcc and optimization level 2"', - "echo \"Component #{component_name} / #{component_sha} / #{component_version}\"" + "echo \"Component #{component_name} / #{component_sha} / #{component_version} / #{component_version}\"" ]) end end @@ -102,7 +102,7 @@ test_job = pipeline.builds.find { |build| build.name == 'test' } expect(test_job.options[:script]).to eq([ 'echo "Building with gcc and optimization level 2"', - "echo \"Component #{component_name} / #{component_sha} / #{component_version}\"" + "echo \"Component #{component_name} / #{component_sha} / #{component_version} / 0.1\"" ]) end end @@ -124,7 +124,7 @@ test_job = pipeline.builds.find { |build| build.name == 'test' } expect(test_job.options[:script]).to eq([ 'echo "Building with gcc and optimization level 2"', - "echo \"Component #{component_name} / #{component_sha} / #{component_version}\"" + "echo \"Component #{component_name} / #{component_sha} / #{component_version} / ~latest\"" ]) end end @@ -146,7 +146,7 @@ test_job = pipeline.builds.find { |build| build.name == 'test' } expect(test_job.options[:script]).to eq([ 'echo "Building with gcc and optimization level 2"', - "echo \"Component #{component_name} / #{component_sha} / \"" + "echo \"Component #{component_name} / #{component_sha} / / #{component_sha}\"" ]) end