Commit 07a5db4d authored by Ben Bodenmiller's avatar Ben Bodenmiller Committed by Ben Bodenmiller

Jenkins for all

parent 0800baa1
......@@ -164,6 +164,7 @@ class Project < ApplicationRecord
has_one :bamboo_service
has_one :teamcity_service
has_one :pushover_service
has_one :jenkins_service
has_one :jira_service
has_one :redmine_service
has_one :youtrack_service
......
......@@ -17,6 +17,10 @@ class Service < ApplicationRecord
pivotaltracker prometheus pushover redmine slack slack_slash_commands teamcity unify_circuit webex_teams youtrack
].freeze
PROJECT_SPECIFIC_SERVICE_NAMES = %w[
jenkins
].freeze
# Fake services to help with local development.
DEV_SERVICE_NAMES = %w[
mock_ci mock_deployment mock_monitoring
......@@ -212,7 +216,7 @@ class Service < ApplicationRecord
end
def self.project_specific_services_names
[]
PROJECT_SPECIFIC_SERVICE_NAMES
end
def self.available_services_types(include_project_specific: true, include_dev: true)
......
---
title: Move Jenkins to Core
merge_request: 37797
author: Ben Bodenmiller (@bbodenmiller)
type: changed
......@@ -23,23 +23,12 @@ alternative authentication methods to your users.
### Remove Service Integration entries from the database
The `JenkinsService` and `GithubService` classes are only available in the Enterprise Edition codebase,
The `GithubService` class is only available in the Enterprise Edition codebase,
so if you downgrade to the Community Edition, the following error displays:
```plaintext
Completed 500 Internal Server Error in 497ms (ActiveRecord: 32.2ms)
ActionView::Template::Error (The single-table inheritance mechanism failed to locate the subclass: 'JenkinsService'. This
error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this
column if you didn't intend it to be used for storing the inheritance class or overwrite Service.inheritance_column to
use another column for that information.)
```
or
```plaintext
Completed 500 Internal Server Error in 497ms (ActiveRecord: 32.2ms)
ActionView::Template::Error (The single-table inheritance mechanism failed to locate the subclass: 'GithubService'. This
error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this
column if you didn't intend it to be used for storing the inheritance class or overwrite Service.inheritance_column to
......@@ -48,22 +37,23 @@ use another column for that information.)
All services are created automatically for every project you have, so in order
to avoid getting this error, you need to remove all instances of the
`JenkinsService` and `GithubService` from your database:
`GithubService` from your database:
**Omnibus Installation**
```shell
sudo gitlab-rails runner "Service.where(type: ['JenkinsService', 'GithubService']).delete_all"
sudo gitlab-rails runner "Service.where(type: ['GithubService']).delete_all"
```
**Source Installation**
```shell
bundle exec rails runner "Service.where(type: ['JenkinsService', 'GithubService']).delete_all" production
bundle exec rails runner "Service.where(type: ['GithubService']).delete_all" production
```
NOTE: **Note:**
If you are running `GitLab =< v13.0` you need to also remove `JenkinsDeprecatedService` records.
If you are running `GitLab =< v13.0` you need to also remove `JenkinsDeprecatedService` records
and if you are running `GitLab =< v13.6` you need to also remove `JenkinsService` records.
### Variables environment scopes
......
......@@ -43,7 +43,7 @@ GitLab also provides features to improve the security of your own application. F
GitLab can be integrated with the following external service for continuous integration:
- [Jenkins](jenkins.md) CI. **(STARTER)**
- [Jenkins](jenkins.md) CI.
## Feature enhancements
......
......@@ -4,7 +4,7 @@ group: unassigned
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Jenkins CI service **(STARTER)**
# Jenkins CI service
NOTE: **Note:**
This documentation focuses only on how to **configure** a Jenkins *integration* with
......@@ -73,11 +73,11 @@ Create a personal access token to authorize Jenkins' access to GitLab.
1. Click **Access Tokens** in the sidebar.
1. Create a personal access token with the **API** scope checkbox checked. For more details, see
[Personal access tokens](../user/profile/personal_access_tokens.md).
1. Record the personal access token's value, because it's required in [Configure the Jenkins server](#configure-the-jenkins-server).
1. Record the personal access token's value, because it's required in [Configure the Jenkins server](#configure-the-jenkins-server) section.
## Configure the Jenkins server
Install and configure the Jenkins plugins. Both plugins must be installed and configured to
Install and configure the Jenkins plugin. The plugin must be installed and configured to
authorize the connection to GitLab.
1. On the Jenkins server, go to **Manage Jenkins > Manage Plugins**.
......@@ -137,6 +137,8 @@ Set up the Jenkins project you intend to run your build on.
Configure the GitLab integration with Jenkins.
### Option 1: Jenkins integration (recommended)
1. Create a new GitLab project or choose an existing one.
1. Go to **Settings > Integrations**, then select **Jenkins CI**.
1. Turn on the **Active** toggle.
......@@ -154,6 +156,14 @@ Configure the GitLab integration with Jenkins.
authentication.
1. Click **Test settings and save changes**. GitLab tests the connection to Jenkins.
### Option 2: Webhook
1. In the configuration of your Jenkins job, in the GitLab configuration section, click **Advanced**.
1. Click the **Generate** button under the **Secret Token** field.
1. Copy the resulting token, and save the job configuration.
1. In GitLab, create a webhook for your project, enter the trigger URL (e.g. `https://JENKINS_URL/project/YOUR_JOB`) and paste the token in the **Secret Token** field.
1. After you add the webhook, click the **Test** button, and it should succeed.
## Troubleshooting
### Error in merge requests - "Could not connect to the CI server"
......
......@@ -38,7 +38,6 @@ module EE
has_one :push_rule, ->(project) { project&.feature_available?(:push_rules) ? all : none }
has_one :index_status
has_one :jenkins_service
has_one :github_service
has_one :gitlab_slack_application_service
......@@ -581,7 +580,6 @@ module EE
def disabled_services
strong_memoize(:disabled_services) do
super.tap do |services|
services.push('jenkins') unless feature_available?(:jenkins_integration)
services.push('github') unless feature_available?(:github_project_service_integration)
::Gitlab::CurrentSettings.slack_app_enabled ? services.push('slack_slash_commands') : services.push('gitlab_slack_application')
end
......
......@@ -10,7 +10,6 @@ module EE
EE_PROJECT_SPECIFIC_SERVICE_NAMES = %w[
github
jenkins
].freeze
class_methods do
......
......@@ -26,7 +26,6 @@ class License < ApplicationRecord
issuable_default_templates
issue_weights
iterations
jenkins_integration
ldap_group_sync
member_lock
merge_request_approvers
......
......@@ -31,32 +31,6 @@ module EE
type: ::API::Services::Boolean,
desc: 'Append instance name instead of branch to status check name'
}
],
'jenkins' => [
{
required: true,
name: :jenkins_url,
type: String,
desc: 'Jenkins root URL like https://jenkins.example.com'
},
{
required: true,
name: :project_name,
type: String,
desc: 'The URL-friendly project name. Example: my_project_name'
},
{
required: false,
name: :username,
type: String,
desc: 'A user with access to the Jenkins server, if applicable'
},
{
required: false,
name: :password,
type: String,
desc: 'The password of the user'
}
]
)
end
......@@ -65,7 +39,6 @@ module EE
def service_classes
[
::GithubService,
::JenkinsService,
*super
]
end
......
......@@ -29,7 +29,7 @@ RSpec.describe Projects::Settings::IntegrationsController do
context 'Sets correct services list' do
let(:active_services) { assigns(:services).map(&:type) }
let(:disabled_services) { %w(JenkinsService) }
let(:disabled_services) { %w[GithubService] }
it 'enables SlackSlashCommandsService and disables GitlabSlackApplication' do
get :show, params: { namespace_id: project.namespace, project_id: project }
......@@ -60,26 +60,21 @@ RSpec.describe Projects::Settings::IntegrationsController do
let(:namespace) { create(:group, :private) }
let(:project) { create(:project, :private, namespace: namespace) }
context 'when checking of namespace plan is enabled' do
before do
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:should_check_namespace_plan?) { true }
end
before do
create(:license, plan: ::License::PREMIUM_PLAN)
end
context 'and namespace does not have a plan' do
it_behaves_like 'endpoint with some disabled services'
context 'when checking if namespace plan is enabled' do
before do
stub_application_setting(check_namespace_plan: true)
end
context 'and namespace has a plan' do
let(:namespace) { create(:group, :private) }
let!(:gitlab_subscription) { create(:gitlab_subscription, :bronze, namespace: namespace) }
it_behaves_like 'endpoint without disabled services'
end
it_behaves_like 'endpoint with some disabled services'
end
context 'when checking of namespace plan is not enabled' do
context 'when checking if namespace plan is not enabled' do
before do
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:should_check_namespace_plan?) { false }
stub_application_setting(check_namespace_plan: false)
end
it_behaves_like 'endpoint without disabled services'
......
......@@ -37,7 +37,6 @@ RSpec.describe Gitlab::UsageData do
create(:prometheus_alert, project: projects[0])
create(:prometheus_alert, project: projects[1])
create(:service, project: projects[1], type: 'JenkinsService', active: true)
create(:jira_service, project: projects[0], issues_enabled: true, project_key: 'GL')
create(:operations_feature_flag, project: projects[0])
......@@ -104,7 +103,6 @@ RSpec.describe Gitlab::UsageData do
operations_dashboard_default_dashboard
operations_dashboard_users_with_projects_added
pod_logs_usages_total
projects_jenkins_active
projects_jira_issuelist_active
projects_mirrored_with_pipelines_enabled
projects_reporting_ci_cd_back_to_github
......@@ -122,7 +120,6 @@ RSpec.describe Gitlab::UsageData do
network_policy_drops
))
expect(count_data[:projects_jenkins_active]).to eq(1)
expect(count_data[:projects_with_prometheus_alerts]).to eq(2)
expect(count_data[:feature_flags]).to eq(1)
expect(count_data[:status_page_projects]).to eq(1)
......
......@@ -1312,7 +1312,6 @@ RSpec.describe Project do
subject { project.disabled_services }
where(:license_feature, :disabled_services) do
:jenkins_integration | %w(jenkins)
:github_project_service_integration | %w(github)
end
......
......@@ -3,15 +3,8 @@
require 'spec_helper'
RSpec.describe Service do
let(:ee_project_services) do
%w[
github
jenkins
]
end
describe '.available_services_names' do
it { expect(described_class.available_services_names).to include(*ee_project_services) }
it { expect(described_class.available_services_names).to include('github') }
end
describe '.project_specific_services_names' do
......@@ -22,13 +15,13 @@ RSpec.describe Service do
context 'when not on gitlab.com' do
let(:com) { false }
it { expect(described_class.project_specific_services_names).to match_array(ee_project_services) }
it { expect(described_class.project_specific_services_names).to include('github') }
end
context 'when on gitlab.com' do
let(:com) { true }
it { expect(described_class.project_specific_services_names).to match_array(ee_project_services + ['gitlab_slack_application']) }
it { expect(described_class.project_specific_services_names).to include('github', 'gitlab_slack_application') }
end
end
end
......@@ -459,6 +459,32 @@ module API
desc: 'Colorize messages'
}
],
'jenkins' => [
{
required: true,
name: :jenkins_url,
type: String,
desc: 'Jenkins root URL like https://jenkins.example.com'
},
{
required: true,
name: :project_name,
type: String,
desc: 'The URL-friendly project name. Example: my_project_name'
},
{
required: false,
name: :username,
type: String,
desc: 'A user with access to the Jenkins server, if applicable'
},
{
required: false,
name: :password,
type: String,
desc: 'The password of the user'
}
],
'jira' => [
{
required: true,
......@@ -767,6 +793,7 @@ module API
::HangoutsChatService,
::HipchatService,
::IrkerService,
::JenkinsService,
::JiraService,
::MattermostSlashCommandsService,
::SlackSlashCommandsService,
......
......@@ -307,14 +307,12 @@ module QA
module Services
autoload :Jira, 'qa/page/project/settings/services/jira'
autoload :Jenkins, 'qa/page/project/settings/services/jenkins'
autoload :Prometheus, 'qa/page/project/settings/services/prometheus'
end
autoload :Operations, 'qa/page/project/settings/operations'
autoload :Incidents, 'qa/page/project/settings/incidents'
autoload :Integrations, 'qa/page/project/settings/integrations'
module Services
autoload :Prometheus, 'qa/page/project/settings/services/prometheus'
end
end
module SubMenus
......
......@@ -142,10 +142,6 @@ module QA
autoload :Repository, 'qa/ee/page/project/settings/repository'
autoload :PushRules, 'qa/ee/page/project/settings/push_rules'
autoload :LicenseCompliance, 'qa/ee/page/project/settings/license_compliance'
module Services
autoload :Jenkins, 'qa/ee/page/project/settings/services/jenkins'
end
end
module Operations
......
# frozen_string_literal: true
module QA
module EE
module Page
module Project
module Settings
module Services
class Jenkins < QA::Page::Base
view 'app/assets/javascripts/integrations/edit/components/dynamic_field.vue' do
element :jenkins_url_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
element :project_name_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
element :username_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
element :password_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/integrations/edit/components/integration_form.vue' do
element :save_changes_button
end
def setup_service_with(jenkins_url:, project_name:)
set_jenkins_url(jenkins_url)
set_project_name(project_name)
set_username('admin')
set_password('password')
click_save_changes_button
end
private
def set_jenkins_url(jenkins_url)
fill_element(:jenkins_url_field, jenkins_url)
end
def set_project_name(project_name)
fill_element(:project_name_field, project_name)
end
def set_username(username)
fill_element(:username_field, username)
end
def set_password(password)
fill_element(:password_field, password)
end
def click_save_changes_button
click_element :save_changes_button
end
end
end
end
end
end
end
end
# frozen_string_literal: true
module QA
module Page
module Project
module Settings
module Services
class Jenkins < QA::Page::Base
view 'app/assets/javascripts/integrations/edit/components/dynamic_field.vue' do
element :jenkins_url_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
element :project_name_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
element :username_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
element :password_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/integrations/edit/components/integration_form.vue' do
element :save_changes_button
end
def setup_service_with(jenkins_url:, project_name:)
set_jenkins_url(jenkins_url)
set_project_name(project_name)
set_username('admin')
set_password('password')
click_save_changes_button
end
private
def set_jenkins_url(jenkins_url)
fill_element(:jenkins_url_field, jenkins_url)
end
def set_project_name(project_name)
fill_element(:project_name_field, project_name)
end
def set_username(username)
fill_element(:username_field, username)
end
def set_password(password)
fill_element(:password_field, password)
end
def click_save_changes_button
click_element :save_changes_button
end
end
end
end
end
end
end
......@@ -131,7 +131,7 @@ module QA
Page::Project::Menu.perform(&:go_to_integrations_settings)
Page::Project::Settings::Integrations.perform(&:click_jenkins_ci_link)
QA::EE::Page::Project::Settings::Services::Jenkins.perform do |jenkins|
QA::Page::Project::Settings::Services::Jenkins.perform do |jenkins|
jenkins.setup_service_with(jenkins_url: patch_host_name(Vendor::Jenkins::Page::Base.host, 'jenkins-server'),
project_name: project_name)
end
......
......@@ -19,15 +19,16 @@ FactoryBot.define do
create(:jira_import_state, :finished, project: projects[1], label: jira_label, imported_issues_count: 3)
create(:jira_import_state, :scheduled, project: projects[1], label: jira_label)
create(:prometheus_service, project: projects[1])
create(:service, project: projects[1], type: 'JenkinsService', active: true)
create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true)
create(:service, project: projects[1], type: 'SlackService', active: true)
create(:service, project: projects[2], type: 'SlackService', active: true)
create(:service, project: projects[2], type: 'MattermostService', active: false)
create(:service, group: group, project: nil, type: 'MattermostService', active: true)
create(:service, :template, type: 'MattermostService', active: true)
matermost_instance = create(:service, :instance, type: 'MattermostService', active: true)
create(:service, project: projects[1], type: 'MattermostService', active: true, inherit_from_id: matermost_instance.id)
create(:service, group: group, project: nil, type: 'SlackService', active: true, inherit_from_id: matermost_instance.id)
mattermost_instance = create(:service, :instance, type: 'MattermostService', active: true)
create(:service, project: projects[1], type: 'MattermostService', active: true, inherit_from_id: mattermost_instance.id)
create(:service, group: group, project: nil, type: 'SlackService', active: true, inherit_from_id: mattermost_instance.id)
create(:service, project: projects[2], type: 'CustomIssueTrackerService', active: true)
create(:project_error_tracking_setting, project: projects[0])
create(:project_error_tracking_setting, project: projects[1], enabled: false)
......
......@@ -456,6 +456,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
expect(count_data[:projects]).to eq(4)
expect(count_data[:projects_asana_active]).to eq(0)
expect(count_data[:projects_prometheus_active]).to eq(1)
expect(count_data[:projects_jenkins_active]).to eq(1)
expect(count_data[:projects_jira_active]).to eq(4)
expect(count_data[:projects_jira_server_active]).to eq(2)
expect(count_data[:projects_jira_cloud_active]).to eq(2)
......
......@@ -147,22 +147,6 @@ RSpec.describe JenkinsService do
end
end
shared_examples 'project with disabled Jenkins service' do
it 'does not invoke the Jenkins API' do
jenkins_service.execute(push_sample_data)
expect(a_request(:any, jenkins_hook_url)).not_to have_been_made
end
end
shared_examples 'project with enabled Jenkins service' do
it 'invokes the Jenkins API' do
jenkins_service.execute(push_sample_data)
expect(a_request(:post, jenkins_hook_url)).to have_been_made.once
end
end
describe '#execute' do
let(:user) { create(:user, username: 'username') }
let(:namespace) { create(:group, :private) }
......@@ -174,57 +158,28 @@ RSpec.describe JenkinsService do
stub_request(:post, jenkins_hook_url)
end
context 'without a license key' do
before do
License.destroy_all # rubocop: disable Cop/DestroyAll
end
it 'invokes the Jenkins API' do
jenkins_service.execute(push_sample_data)
it_behaves_like 'project with disabled Jenkins service'
expect(a_request(:post, jenkins_hook_url)).to have_been_made.once
end
context 'with a license key' do
context 'when namespace plan check is not enabled' do
before do
stub_application_setting_on_object(project, should_check_namespace_plan: false)
end
it_behaves_like 'project with enabled Jenkins service'
end
context 'when namespace plan check is enabled' do
before do
stub_application_setting_on_object(project, should_check_namespace_plan: true)
end
context 'when namespace does not have a plan' do
let(:namespace) { create(:group, :private) }
it_behaves_like 'project with disabled Jenkins service'
end
context 'when namespace has a plan' do
let(:namespace) { create(:group, :private) }
let!(:gitlab_subscription) { create(:gitlab_subscription, :bronze, namespace: namespace) }
it 'adds default web hook headers to the request' do
jenkins_service.execute(push_sample_data)
it 'adds default web hook headers to the request' do
jenkins_service.execute(push_sample_data)
expect(
a_request(:post, jenkins_hook_url)
.with(headers: { 'X-Gitlab-Event' => 'Push Hook', 'Authorization' => jenkins_authorization })
).to have_been_made.once
end
expect(
a_request(:post, jenkins_hook_url)
.with(headers: { 'X-Gitlab-Event' => 'Push Hook', 'Authorization' => jenkins_authorization })
).to have_been_made.once
end
it 'request url contains properly serialized username and password' do
jenkins_service.execute(push_sample_data)
it 'request url contains properly serialized username and password' do
jenkins_service.execute(push_sample_data)
expect(
a_request(:post, 'http://jenkins.example.com/project/my_project')
.with(headers: { 'Authorization' => jenkins_authorization })
).to have_been_made.once
end
end
end
expect(
a_request(:post, 'http://jenkins.example.com/project/my_project')
.with(headers: { 'Authorization' => jenkins_authorization })
).to have_been_made.once
end
end
......
......@@ -916,5 +916,11 @@ RSpec.describe Service do
described_class.available_services_names(include_dev: false)
end
it { expect(described_class.available_services_names).to include('jenkins') }
end
describe '.project_specific_services_names' do
it { expect(described_class.project_specific_services_names).to include('jenkins') }
end
end
......@@ -85,6 +85,7 @@ module UsageDataHelpers
projects
projects_imported_from_github
projects_asana_active
projects_jenkins_active
projects_jira_active
projects_jira_server_active
projects_jira_cloud_active
......
......@@ -34,8 +34,7 @@ Service.available_services_names.each do |service|
let(:licensed_features) do
{
'github' => :github_project_service_integration,
'jenkins' => :jenkins_integration
'github' => :github_project_service_integration
}
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