Commit f201a735 authored by Aishwarya Subramanian's avatar Aishwarya Subramanian Committed by Fabio Pitino

Evaluate group level compliance pipeline configuration

If a compliance framework label has been applied
to a project, and the framework as a compliance pipeline
configuration path defined - we trigger the compliance
pipeline for the project. The compliance pipeline
configuration can in turn include the project's
.gitlab-ci.yml file to be run after the compliance runs.
parent 523db65a
......@@ -71,7 +71,8 @@ module Enums
remote_source: 4,
external_project_source: 5,
bridge_source: 6,
parameter_source: 7
parameter_source: 7,
compliance_source: 8
}
end
end
......
......@@ -3948,7 +3948,7 @@ type ComplianceFramework {
"""
Full path of the compliance pipeline configuration stored in a project
repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`.
repository, such as `.gitlab/.compliance-gitlab-ci.yml@compliance/hippa`.
"""
pipelineConfigurationFullPath: String
}
......@@ -4006,7 +4006,7 @@ input ComplianceFrameworkInput {
"""
Full path of the compliance pipeline configuration stored in a project
repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`.
repository, such as `.gitlab/.compliance-gitlab-ci.yml@compliance/hippa`.
"""
pipelineConfigurationFullPath: String
}
......@@ -18093,7 +18093,7 @@ type Pipeline {
"""
Configuration source of the pipeline (UNKNOWN_SOURCE, REPOSITORY_SOURCE,
AUTO_DEVOPS_SOURCE, WEBIDE_SOURCE, REMOTE_SOURCE, EXTERNAL_PROJECT_SOURCE,
BRIDGE_SOURCE, PARAMETER_SOURCE)
BRIDGE_SOURCE, PARAMETER_SOURCE, COMPLIANCE_SOURCE)
"""
configSource: PipelineConfigSourceEnum
......@@ -18364,6 +18364,7 @@ type PipelineCancelPayload {
enum PipelineConfigSourceEnum {
AUTO_DEVOPS_SOURCE
BRIDGE_SOURCE
COMPLIANCE_SOURCE
EXTERNAL_PROJECT_SOURCE
PARAMETER_SOURCE
REMOTE_SOURCE
......
......@@ -10763,7 +10763,7 @@
},
{
"name": "pipelineConfigurationFullPath",
"description": "Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`.",
"description": "Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/.compliance-gitlab-ci.yml@compliance/hippa`.",
"args": [
],
......@@ -10933,7 +10933,7 @@
},
{
"name": "pipelineConfigurationFullPath",
"description": "Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`.",
"description": "Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/.compliance-gitlab-ci.yml@compliance/hippa`.",
"type": {
"kind": "SCALAR",
"name": "String",
......@@ -53230,7 +53230,7 @@
},
{
"name": "configSource",
"description": "Configuration source of the pipeline (UNKNOWN_SOURCE, REPOSITORY_SOURCE, AUTO_DEVOPS_SOURCE, WEBIDE_SOURCE, REMOTE_SOURCE, EXTERNAL_PROJECT_SOURCE, BRIDGE_SOURCE, PARAMETER_SOURCE)",
"description": "Configuration source of the pipeline (UNKNOWN_SOURCE, REPOSITORY_SOURCE, AUTO_DEVOPS_SOURCE, WEBIDE_SOURCE, REMOTE_SOURCE, EXTERNAL_PROJECT_SOURCE, BRIDGE_SOURCE, PARAMETER_SOURCE, COMPLIANCE_SOURCE)",
"args": [
],
......@@ -54127,6 +54127,12 @@
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "COMPLIANCE_SOURCE",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
......@@ -629,7 +629,7 @@ Represents a ComplianceFramework associated with a Project.
| `description` | String! | Description of the compliance framework. |
| `id` | ID! | Compliance framework ID. |
| `name` | String! | Name of the compliance framework. |
| `pipelineConfigurationFullPath` | String | Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`. |
| `pipelineConfigurationFullPath` | String | Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/.compliance-gitlab-ci.yml@compliance/hippa`. |
### ComposerMetadata
......@@ -2759,7 +2759,7 @@ Information about pagination in a connection..
| `beforeSha` | String | Base SHA of the source branch. |
| `cancelable` | Boolean! | Specifies if a pipeline can be canceled. |
| `committedAt` | Time | Timestamp of the pipeline's commit. |
| `configSource` | PipelineConfigSourceEnum | Configuration source of the pipeline (UNKNOWN_SOURCE, REPOSITORY_SOURCE, AUTO_DEVOPS_SOURCE, WEBIDE_SOURCE, REMOTE_SOURCE, EXTERNAL_PROJECT_SOURCE, BRIDGE_SOURCE, PARAMETER_SOURCE) |
| `configSource` | PipelineConfigSourceEnum | Configuration source of the pipeline (UNKNOWN_SOURCE, REPOSITORY_SOURCE, AUTO_DEVOPS_SOURCE, WEBIDE_SOURCE, REMOTE_SOURCE, EXTERNAL_PROJECT_SOURCE, BRIDGE_SOURCE, PARAMETER_SOURCE, COMPLIANCE_SOURCE) |
| `coverage` | Float | Coverage percentage. |
| `createdAt` | Time! | Timestamp of the pipeline's creation. |
| `detailedStatus` | DetailedStatus! | Detailed status of the pipeline. |
......@@ -5168,6 +5168,7 @@ Rotation length unit of an on-call rotation.
| ----- | ----------- |
| `AUTO_DEVOPS_SOURCE` | |
| `BRIDGE_SOURCE` | |
| `COMPLIANCE_SOURCE` | |
| `EXTERNAL_PROJECT_SOURCE` | |
| `PARAMETER_SOURCE` | |
| `REMOTE_SOURCE` | |
......
......@@ -24,7 +24,7 @@ module Types
argument :pipeline_configuration_full_path,
GraphQL::STRING_TYPE,
required: false,
description: 'Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`.'
description: 'Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/.compliance-gitlab-ci.yml@compliance/hippa`.'
end
end
end
......@@ -25,7 +25,7 @@ module Types
field :pipeline_configuration_full_path, GraphQL::STRING_TYPE,
null: true,
description: 'Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`.'
description: 'Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/.compliance-gitlab-ci.yml@compliance/hippa`.'
end
end
end
......@@ -173,6 +173,7 @@ class License < ApplicationRecord
subepics
threat_monitoring
vulnerability_auto_fix
evaluate_group_level_compliance_pipeline
]
EEU_FEATURES.freeze
......
---
name: ff_evaluate_group_level_compliance_pipeline
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52629
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/300324
milestone: '13.9'
type: development
group: group::compliance
default_enabled: false
# frozen_string_literal: true
module EE
module Gitlab
module Ci
module Pipeline
module Chain
module Config
module Content
extend ::Gitlab::Utils::Override
EE_SOURCES = [::Gitlab::Ci::Pipeline::Chain::Config::Content::Compliance].freeze
private
override :sources
def sources
EE_SOURCES + super
end
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
module Pipeline
module Chain
module Config
class Content
class Compliance < Source
def content
strong_memoize(:content) do
next unless available?
next unless pipeline_configuration_full_path
path_file, path_project = pipeline_configuration_full_path.split('@', 2)
YAML.dump('include' => [{ 'project' => path_project, 'file' => path_file }])
end
end
def source
:compliance_source
end
private
def pipeline_configuration_full_path
strong_memoize(:pipeline_configuration_full_path) do
next unless project
project.compliance_pipeline_configuration_full_path
end
end
def available?
project.feature_available?(:evaluate_group_level_compliance_pipeline) &&
::Feature.enabled?(:ff_evaluate_group_level_compliance_pipeline, project, default_enabled: :yaml)
end
end
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Config::Content do
let(:ci_config_path) { nil }
let(:pipeline) { build(:ci_pipeline, project: project) }
let(:content) { nil }
let(:source) { :push }
let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new(project: project, content: content, source: source) }
subject { described_class.new(pipeline, command) }
let(:content_result) do
<<~EOY
---
include:
- project: compliance/hippa
file: ".compliance-gitlab-ci.yml"
EOY
end
shared_examples 'does not include compliance pipeline configuration content' do
it do
subject.perform!
expect(pipeline.config_source).not_to eq 'compliance_source'
expect(pipeline.pipeline_config.content).not_to eq(content_result)
expect(command.config_content).not_to eq(content_result)
end
end
context 'when project has compliance pipeline configuration defined' do
let(:project) { create(:project, ci_config_path: ci_config_path) }
let(:compliance_group) { create(:group, :private, name: "compliance") }
let(:compliance_project) { create(:project, namespace: compliance_group, name: "hippa") }
let(:framework) { create(:compliance_framework, namespace_id: compliance_group.id, pipeline_configuration_full_path: ".compliance-gitlab-ci.yml@compliance/hippa") }
let!(:framework_project_setting) { create(:compliance_framework_project_setting, project: project, framework_id: framework.id) }
context 'when feature is available' do
before do
stub_feature_flags(ff_evaluate_group_level_compliance_pipeline: true)
stub_licensed_features(evaluate_group_level_compliance_pipeline: true)
end
it 'includes compliance pipeline configuration content' do
subject.perform!
expect(pipeline.config_source).to eq 'compliance_source'
expect(pipeline.pipeline_config.content).to eq(content_result)
expect(command.config_content).to eq(content_result)
end
end
context 'when feature is not available' do
using RSpec::Parameterized::TableSyntax
where(:licensed, :feature_flag) do
true | false
false | true
false | false
end
with_them do
before do
stub_feature_flags(ff_evaluate_group_level_compliance_pipeline: licensed)
stub_licensed_features(evaluate_group_level_compliance_pipeline: feature_flag)
end
it_behaves_like 'does not include compliance pipeline configuration content'
end
end
end
context 'when project does not have compliance label defined' do
let(:project) { create(:project, ci_config_path: ci_config_path) }
context 'when feature is available' do
before do
stub_feature_flags(ff_evaluate_group_level_compliance_pipeline: true)
stub_licensed_features(evaluate_group_level_compliance_pipeline: true)
end
it_behaves_like 'does not include compliance pipeline configuration content'
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
include AfterNextHelpers
subject(:execute) { service.execute(:push) }
let(:project) { create(:project, :repository, name: 'website') }
let(:user) { project.owner }
let(:compliance_group) { create(:group, :private, name: "compliance") }
let(:compliance_project) { create(:project, :repository, namespace: compliance_group, name: "hippa") }
let(:framework) { create(:compliance_framework, namespace_id: compliance_group.id, pipeline_configuration_full_path: ".compliance-gitlab-ci.yml@compliance/hippa") }
let!(:framework_project_setting) { create(:compliance_framework_project_setting, project: project, framework_id: framework.id) }
let!(:ref_sha) { compliance_project.commit('HEAD').sha }
let(:compliance_config) do
<<~EOY
---
compliance_build:
stage: build
script:
- echo 'hello from compliance build'
compliance_test:
stage: test
script:
- echo 'hello from compliance test'
EOY
end
let(:service) { described_class.new(project, user, { ref: 'master' }) }
before do
stub_feature_flags(ff_evaluate_group_level_compliance_pipeline: true)
stub_licensed_features(evaluate_group_level_compliance_pipeline: true)
allow_next(Repository).to receive(:blob_data_at).with(ref_sha, '.compliance-gitlab-ci.yml').and_return(compliance_config)
end
context 'when user has access to compliance project' do
before do
compliance_project.add_maintainer(project.owner)
end
it 'persists pipeline' do
is_expected.to be_persisted
end
it 'sets the correct source' do
expect(execute.config_source).to eq("compliance_source")
end
it 'persists jobs' do
expect { execute }.to change(Ci::Build, :count).from(0).to(2)
end
it do
expect(execute.processables.map(&:name)).to eq(%w(compliance_build compliance_test))
end
end
context 'when user does not have access to compliance project' do
it 'includes access denied error' do
expect(execute.yaml_errors).to eq "Project `compliance/hippa` not found or access denied!"
end
it 'does not persist jobs' do
expect { execute }.not_to change(Ci::Build, :count).from(0)
end
end
end
......@@ -34,16 +34,22 @@ module Gitlab
private
def find_config
SOURCES.each do |source|
sources.each do |source|
config = source.new(@pipeline, @command)
return config if config.exists?
end
nil
end
def sources
SOURCES
end
end
end
end
end
end
end
Gitlab::Ci::Pipeline::Chain::Config::Content.prepend_if_ee('EE::Gitlab::Ci::Pipeline::Chain::Config::Content')
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