Commit de2bb111 authored by Jeremy Jackson's avatar Jeremy Jackson Committed by Olena Horal-Koretska

Add a checkbox for adding SAST to a new project

parent e8989fc6
......@@ -192,19 +192,28 @@
}
}
.initialize-with-readme-setting {
.form-check {
margin-bottom: 10px;
.nested-settings {
padding-left: 20px;
}
.option-title {
font-weight: $gl-font-weight-normal;
display: inline-block;
color: $gl-text-color;
}
.input-btn-group {
display: flex;
.option-description {
color: $project-option-descr-color;
}
.input-large {
flex: 1;
}
.btn {
margin-left: 10px;
}
}
.content-list > .settings-flex-row {
display: flex;
align-items: center;
.float-right {
margin-left: auto;
}
}
......
......@@ -73,6 +73,13 @@ class ProjectsController < Projects::ApplicationController
@project = ::Projects::CreateService.new(current_user, project_params(attributes: project_params_create_attributes)).execute
if @project.saved?
experiment(:new_project_sast_enabled, user: current_user).track(:created,
property: active_new_project_tab,
checked: Gitlab::Utils.to_boolean(project_params[:initialize_with_sast]),
project: @project,
namespace: @project.namespace
)
redirect_to(
project_path(@project, custom_import_params),
notice: _("Project '%{project_name}' was successfully created.") % { project_name: @project.name }
......@@ -436,6 +443,7 @@ class ProjectsController < Projects::ApplicationController
:template_name,
:template_project_id,
:merge_method,
:initialize_with_sast,
:initialize_with_readme,
:autoclose_referenced_issues,
:suggestion_commit_message,
......
# frozen_string_literal: true
class NewProjectSastEnabledExperiment < ApplicationExperiment # rubocop:disable Gitlab/NamespacedClass
def publish(_result = nil)
super
publish_to_database
end
def candidate_behavior
end
def free_indicator_behavior
end
end
......@@ -8,6 +8,7 @@ module Projects
@current_user = user
@params = params.dup
@skip_wiki = @params.delete(:skip_wiki)
@initialize_with_sast = Gitlab::Utils.to_boolean(@params.delete(:initialize_with_sast))
@initialize_with_readme = Gitlab::Utils.to_boolean(@params.delete(:initialize_with_readme))
@import_data = @params.delete(:import_data)
@relations_block = @params.delete(:relations_block)
......@@ -118,6 +119,7 @@ module Projects
Projects::PostCreationWorker.perform_async(@project.id)
create_readme if @initialize_with_readme
create_sast_commit if @initialize_with_sast
end
# Add an authorization for the current user authorizations inline
......@@ -160,6 +162,10 @@ module Projects
Files::CreateService.new(@project, current_user, commit_attrs).execute
end
def create_sast_commit
::Security::CiConfiguration::SastCreateService.new(@project, current_user, {}, commit_on_default: true).execute
end
def readme_content
@readme_template.presence || experiment(:new_project_readme_content, namespace: @project.namespace).run_with(@project)
end
......
......@@ -25,7 +25,7 @@ module Security
rescue Gitlab::Git::PreReceiveError => e
ServiceResponse.error(message: e.message)
rescue StandardError
project.repository.rm_branch(current_user, branch_name) if project.repository.branch_exists?(branch_name)
remove_branch_on_exception
raise
end
......@@ -50,6 +50,10 @@ module Security
Gitlab::Routing.url_helpers.project_new_merge_request_url(project, merge_request: merge_request_params)
end
def remove_branch_on_exception
project.repository.rm_branch(current_user, branch_name) if project.repository.branch_exists?(branch_name)
end
def track_event(attributes_for_commit)
action = attributes_for_commit[:actions].first
......
......@@ -5,15 +5,28 @@ module Security
class SastCreateService < ::Security::CiConfiguration::BaseCreateService
attr_reader :params
def initialize(project, current_user, params)
def initialize(project, current_user, params, commit_on_default: false)
super(project, current_user)
@params = params
@commit_on_default = commit_on_default
@branch_name = project.default_branch if @commit_on_default
end
private
def remove_branch_on_exception
super unless @commit_on_default
end
def action
Security::CiConfiguration::SastBuildAction.new(project.auto_devops_enabled?, params, existing_gitlab_ci_content).generate
existing_content = begin
existing_gitlab_ci_content # this can fail on the very first commit
rescue StandardError
nil
end
Security::CiConfiguration::SastBuildAction.new(project.auto_devops_enabled?, params, existing_content).generate
end
def next_branch
......
......@@ -53,15 +53,36 @@
= render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false
- if !hide_init_with_readme
.form-group.row.initialize-with-readme-setting
%div{ :class => "col-sm-12" }
.form-check
= check_box_tag 'project[initialize_with_readme]', '1', true, class: 'form-check-input', data: { qa_selector: "initialize_with_readme_checkbox", track_label: "#{track_label}", track_action: "activate_form_input", track_property: "init_with_readme", track_value: "" }
= label_tag 'project[initialize_with_readme]', class: 'form-check-label' do
.option-title
%strong= s_('ProjectsNew|Initialize repository with a README')
.option-description
= s_('ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository.')
= f.label :project_configuration, class: 'label-bold' do
= s_('ProjectsNew|Project Configuration')
.form-group
.form-check.gl-mb-3
= check_box_tag 'project[initialize_with_readme]', '1', true, class: 'form-check-input', data: { qa_selector: 'initialize_with_readme_checkbox', track_label: track_label, track_action: 'activate_form_input', track_property: 'init_with_readme' }
= label_tag 'project[initialize_with_readme]', s_('ProjectsNew|Initialize repository with a README'), class: 'form-check-label'
.form-text.text-muted
= s_('ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository.')
- experiment(:new_project_sast_enabled, user: current_user) do |e|
- e.try do
.form-group
.form-check.gl-mb-3
= check_box_tag 'project[initialize_with_sast]', '1', true, class: 'form-check-input', data: { track_experiment: e.name, track_label: track_label, track_action: 'activate_form_input', track_property: 'init_with_sast' }
= label_tag 'project[initialize_with_sast]', class: 'form-check-label' do
= s_('ProjectsNew|Enable Static Application Security Testing (SAST)')
.form-text.text-muted
= s_('ProjectsNew|Analyze your source code for known security vulnerabilities.')
= link_to _('Learn more.'), help_page_path('user/application_security/sast/index'), target: '_blank', rel: 'noopener noreferrer', data: { track_action: 'followed', track_experiment: e.name }
- e.try(:free_indicator) do
.form-group
.form-check.gl-mb-3
= check_box_tag 'project[initialize_with_sast]', '1', true, class: 'form-check-input', data: { track_experiment: e.name, track_label: track_label, track_action: 'activate_form_input', track_property: 'init_with_sast' }
= label_tag 'project[initialize_with_sast]', class: 'form-check-label' do
= s_('ProjectsNew|Enable Static Application Security Testing (SAST)')
%span.badge.badge-info.badge-pill.gl-badge.sm= _('Free')
.form-text.text-muted
= s_('ProjectsNew|Analyze your source code for known security vulnerabilities.')
= link_to _('Learn more.'), help_page_path('user/application_security/sast/index'), target: '_blank', rel: 'noopener noreferrer', data: { track_action: 'followed', track_experiment: e.name }
= f.submit _('Create project'), class: "btn gl-button btn-confirm", data: { track_label: "#{track_label}", track_action: "click_button", track_property: "create_project", track_value: "" }
= link_to _('Cancel'), dashboard_projects_path, class: 'btn gl-button btn-default btn-cancel', data: { track_label: "#{track_label}", track_action: "click_button", track_property: "cancel", track_value: "" }
---
name: new_project_sast_enabled
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70548
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340929
milestone: '14.4'
type: experiment
group: group::adoption
default_enabled: false
......@@ -14854,6 +14854,9 @@ msgstr ""
msgid "Framework successfully deleted"
msgstr ""
msgid "Free"
msgstr ""
msgid "Free Trial of GitLab.com Ultimate"
msgstr ""
......@@ -27040,6 +27043,9 @@ msgstr ""
msgid "ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository."
msgstr ""
msgid "ProjectsNew|Analyze your source code for known security vulnerabilities."
msgstr ""
msgid "ProjectsNew|Connect your external repository to GitLab CI/CD."
msgstr ""
......@@ -27067,6 +27073,9 @@ msgstr ""
msgid "ProjectsNew|Description format"
msgstr ""
msgid "ProjectsNew|Enable Static Application Security Testing (SAST)"
msgstr ""
msgid "ProjectsNew|Import"
msgstr ""
......@@ -27082,6 +27091,9 @@ msgstr ""
msgid "ProjectsNew|No import options available"
msgstr ""
msgid "ProjectsNew|Project Configuration"
msgstr ""
msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
msgstr ""
......
......@@ -420,42 +420,66 @@ RSpec.describe ProjectsController do
end
describe 'POST create' do
let!(:params) do
{
path: 'foo',
description: 'bar',
import_url: project.http_url_to_repo,
namespace_id: user.namespace.id
}
end
subject { post :create, params: { project: params } }
before do
sign_in(user)
end
context 'when import by url is disabled' do
before do
stub_application_setting(import_sources: [])
context 'on import' do
let(:params) do
{
path: 'foo',
description: 'bar',
namespace_id: user.namespace.id,
import_url: project.http_url_to_repo
}
end
it 'does not create project and reports an error' do
expect { subject }.not_to change { Project.count }
context 'when import by url is disabled' do
before do
stub_application_setting(import_sources: [])
end
expect(response).to have_gitlab_http_status(:not_found)
it 'does not create project and reports an error' do
expect { subject }.not_to change { Project.count }
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when import by url is enabled' do
before do
stub_application_setting(import_sources: ['git'])
end
it 'creates project' do
expect { subject }.to change { Project.count }
expect(response).to have_gitlab_http_status(:redirect)
end
end
end
context 'when import by url is enabled' do
before do
stub_application_setting(import_sources: ['git'])
context 'with new_project_sast_enabled', :experiment do
let(:params) do
{
path: 'foo',
description: 'bar',
namespace_id: user.namespace.id,
initialize_with_sast: '1'
}
end
it 'creates project' do
expect { subject }.to change { Project.count }
it 'tracks an event on project creation' do
expect(experiment(:new_project_sast_enabled)).to track(:created,
property: 'blank',
checked: true,
project: an_instance_of(Project),
namespace: user.namespace
).on_next_instance.with_context(user: user)
expect(response).to have_gitlab_http_status(:redirect)
post :create, params: { project: params }
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe NewProjectSastEnabledExperiment do
it "defines the expected behaviors and variants" do
expect(subject.behaviors.keys).to match_array(%w[control candidate free_indicator])
end
it "publishes to the database" do
expect(subject).to receive(:publish_to_database)
subject.publish
end
end
......@@ -33,6 +33,29 @@ RSpec.describe 'User creates a project', :js do
expect(page).to have_content(project.url_to_repo)
end
it 'creates a new project that is not blank' do
stub_experiments(new_project_sast_enabled: 'candidate')
visit(new_project_path)
find('[data-qa-panel-name="blank_project"]').click # rubocop:disable QA/SelectorUsage
fill_in(:project_name, with: 'With initial commits')
expect(page).to have_checked_field 'Initialize repository with a README'
expect(page).to have_checked_field 'Enable Static Application Security Testing (SAST)'
page.within('#content-body') do
click_button('Create project')
end
project = Project.last
expect(current_path).to eq(project_path(project))
expect(page).to have_content('With initial commits')
expect(page).to have_content('Configure SAST in `.gitlab-ci.yml`, creating this file if it does not already exist')
expect(page).to have_content('README.md Initial commit')
end
context 'in a subgroup they do not own' do
let(:parent) { create(:group) }
let!(:subgroup) { create(:group, parent: parent) }
......
......@@ -622,6 +622,22 @@ RSpec.describe Projects::CreateService, '#execute' do
end
end
context 'when SAST initialization is requested' do
let(:project) { create_project(user, opts) }
before do
opts[:initialize_with_sast] = '1'
allow(Gitlab::CurrentSettings).to receive(:default_branch_name).and_return('main')
end
it 'creates a commit for SAST', :aggregate_failures do
expect(project.repository.commit_count).to be(1)
expect(project.repository.commit.message).to eq(
'Configure SAST in `.gitlab-ci.yml`, creating this file if it does not already exist'
)
end
end
describe 'create integration for the project' do
subject(:project) { create_project(user, opts) }
......
......@@ -23,4 +23,27 @@ RSpec.describe Security::CiConfiguration::SastCreateService, :snowplow do
end
include_examples 'services security ci configuration create service'
context "when committing to the default branch", :aggregate_failures do
subject(:result) { described_class.new(project, user, params, commit_on_default: true).execute }
let(:params) { {} }
before do
project.add_developer(user)
end
it "doesn't try to remove that branch on raised exceptions" do
expect(Files::MultiService).to receive(:new).and_raise(StandardError, '_exception_')
expect(project.repository).not_to receive(:rm_branch)
expect { result }.to raise_error(StandardError, '_exception_')
end
it "commits directly to the default branch" do
expect(result.status).to eq(:success)
expect(result.payload[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
expect(result.payload[:branch]).to eq('master')
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