Add Web Terminal for CI builds

parent 8a63ad11
......@@ -155,7 +155,7 @@ class Projects::JobsController < Projects::ApplicationController
end
def authorize_use_build_terminal!
return access_denied! unless can?(current_user, :use_build_terminal, build)
return access_denied! unless can?(current_user, :create_build_terminal, build)
end
def verify_api_request!
......
......@@ -31,9 +31,13 @@ module Ci
has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
has_one :metadata, class_name: 'Ci::BuildMetadata'
has_one :runner_session, class_name: 'Ci::BuildRunnerSession'
has_one :runner_session, class_name: 'Ci::BuildRunnerSession', validate: true, inverse_of: :build
accepts_nested_attributes_for :runner_session
delegate :timeout, to: :metadata, prefix: true, allow_nil: true
delegate :url, :certificate, :authorization, to: :runner_session, prefix: true, allow_nil: true
delegate :url, to: :runner_session, prefix: true, allow_nil: true
delegate :terminal_specification, to: :runner_session, allow_nil: true
delegate :gitlab_deploy_token, to: :project
##
......@@ -598,25 +602,6 @@ module Ci
running? && runner_session_url.present?
end
def terminal_specification
return {} unless runner_session_url.present?
headers = {}
if runner_session_authorization.present?
headers['Authorization'] = runner_session_authorization
end
# TODO: change url to "#{metadata_runner_session_url}/exec" once the runner
# has all the terminal server changes
{
subprotocols: ['terminal.gitlab.com'].freeze,
url: "#{runner_session_url}?build=#{id}",
headers: headers,
ca_pem: runner_session_certificate.presence
}
end
private
def update_artifacts_size
......
......@@ -6,9 +6,20 @@ module Ci
self.table_name = 'ci_builds_runner_session'
belongs_to :build, class_name: 'Ci::Build'
belongs_to :build, class_name: 'Ci::Build', inverse_of: :runner_session
validates :build, presence: true
validates :url, url: { protocols: %w(ws) }
validates :url, url: { protocols: %w(https) }
def terminal_specification
return {} unless url.present?
{
subprotocols: ['terminal.gitlab.com'].freeze,
url: "#{url}/exec".sub("https://", "wss://"),
headers: { Authorization: authorization.presence }.compact,
ca_pem: certificate.presence
}
end
end
end
......@@ -34,6 +34,6 @@ module Ci
enable :update_commit_status
end
rule { can?(:update_build) & terminal }.enable :use_build_terminal
rule { can?(:update_build) & terminal }.enable :create_build_terminal
end
end
......@@ -43,10 +43,7 @@ module Ci
# with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
begin
build.runner_id = runner.id
if params.dig(:session, :url).present?
build.build_runner_session(params[:session])
end
build.runner_session_attributes = params[:session] if params[:session].present?
build.run!
register_success(build)
......
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } }
.sidebar-container
.blocks-container
.block
%strong.inline.prepend-top-8
= @build.name
- if can?(current_user, :use_build_terminal, @build)
- if can?(current_user, :create_build_terminal, @build)
.block
= link_to terminal_project_job_path(@project, @build), class: 'terminal-button pull-right btn visible-md-block visible-lg-block', title: 'Terminal' do
Terminal
......
......@@ -571,7 +571,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'when job exists' do
context 'and it has a terminal' do
let!(:job) { create(:ci_build, :running, :with_runner_session_url, pipeline: pipeline) }
let!(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline) }
it 'has a job' do
get_terminal(id: job.id)
......@@ -611,7 +611,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
describe 'GET #terminal_websocket_authorize' do
let!(:job) { create(:ci_build, :running, :with_runner_session_url, pipeline: pipeline) }
let!(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline) }
before do
project.add_developer(user)
......
......@@ -249,9 +249,9 @@ FactoryBot.define do
failure_reason 2
end
trait :with_runner_session_url do
after(:create) do |build|
build.create_runner_session(url: 'ws://localhost')
trait :with_runner_session do
after(:build) do |build|
build.build_runner_session(url: 'ws://localhost')
end
end
end
......
require 'spec_helper'
describe Ci::BuildRunnerSession, model: true do
let!(:build) { create(:ci_build, :with_runner_session) }
subject { build.runner_session }
it { is_expected.to belong_to(:build) }
it { is_expected.to validate_presence_of(:build) }
it { is_expected.to validate_presence_of(:url).with_message('must be a valid URL') }
describe '#terminal_specification' do
let(:terminal_specification) { subject.terminal_specification }
it 'returns empty hash if no url' do
subject.url = ''
expect(terminal_specification).to be_empty
end
context 'when url is present' do
it 'returns ca_pem nil if empty certificate' do
subject.certificate = ''
expect(terminal_specification[:ca_pem]).to be_nil
end
it 'adds Authorization header if authorization is present' do
subject.authorization = 'whatever'
expect(terminal_specification[:headers]).to include(Authorization: 'whatever')
end
end
end
end
......@@ -20,6 +20,7 @@ describe Ci::Build do
it { is_expected.to have_many(:deployments) }
it { is_expected.to have_many(:sourced_pipelines) }
it { is_expected.to have_many(:trace_sections)}
it { is_expected.to have_one(:runner_session)}
it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) }
......@@ -47,7 +48,7 @@ describe Ci::Build do
context 'when transitioning to any state from running' do
it 'removes runner_session' do
%w(success drop cancel).each do |event|
build = FactoryBot.create(:ci_build, :running, :with_runner_session_url, pipeline: pipeline)
build = FactoryBot.create(:ci_build, :running, :with_runner_session, pipeline: pipeline)
build.fire_events!(event)
......@@ -2674,28 +2675,4 @@ describe Ci::Build do
end
end
end
describe '#terminal_specification' do
subject { build.terminal_specification }
it 'returns empty hash if no runner_session_url' do
expect(subject).to be_empty
end
context 'when runner_session_url is present' do
set(:build) { create(:ci_build, :with_runner_session_url, project: project, user: user) }
it 'returns ca_pem nil if empty runner_session_certificate' do
build.runner_session.certificate = ''
expect(subject[:ca_pem]).to be_nil
end
it 'adds Authorization header if runner_session_authorization is present' do
build.runner_session.authorization = 'whatever'
expect(subject[:headers]).to include('Authorization' => 'whatever')
end
end
end
end
......@@ -548,8 +548,21 @@ module Ci
end
end
def execute(runner)
described_class.new(runner).execute.build
context 'when runner_session params are' do
it 'present sets runner session configuration in the build' do
runner_session_params = { session: { 'url' => 'https://example.com' } }
expect(execute(specific_runner, runner_session_params).runner_session.attributes)
.to include(runner_session_params[:session])
end
it 'not present it does not configure the runner session' do
expect(execute(specific_runner).runner_session).to be_nil
end
end
def execute(runner, params = {})
described_class.new(runner).execute(params).build
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