Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-25 18:07:58 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-25 18:07:58 +0300
commit0d8e625e4cd499162e6113dca4988b28f9faa9b6 (patch)
tree8744ab9fdba76e924f15272bba473521e39b8760 /app
parentb5249f2d99206a72459bc5e2bf2aeb2f06ee36f3 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/alert_management/components/alert_management_list.vue45
-rw-r--r--app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.graphql1
-rw-r--r--app/assets/javascripts/alert_management/graphql/queries/get_alerts.query.graphql4
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss23
-rw-r--r--app/assets/stylesheets/pages/alert_management/list.scss13
-rw-r--r--app/controllers/ide_controller.rb6
-rw-r--r--app/controllers/projects/jobs_controller.rb40
-rw-r--r--app/controllers/projects/web_ide_terminals_controller.rb98
-rw-r--r--app/models/ci/build.rb1
-rw-r--r--app/models/ci/build_runner_session.rb15
-rw-r--r--app/models/ci/pipeline_enums.rb2
-rw-r--r--app/models/project.rb6
-rw-r--r--app/models/project_import_state.rb6
-rw-r--r--app/models/user.rb1
-rw-r--r--app/models/web_ide_terminal.rb51
-rw-r--r--app/policies/ci/build_policy.rb22
-rw-r--r--app/policies/project_policy.rb8
-rw-r--r--app/serializers/web_ide_terminal_entity.rb12
-rw-r--r--app/serializers/web_ide_terminal_serializer.rb11
-rw-r--r--app/services/ci/create_web_ide_terminal_service.rb123
-rw-r--r--app/services/ci/web_ide_config_service.rb59
-rw-r--r--app/services/clusters/parse_cluster_applications_artifact_service.rb27
-rw-r--r--app/services/groups/import_export/export_service.rb23
-rw-r--r--app/services/groups/import_export/import_service.rb5
-rw-r--r--app/services/projects/after_import_service.rb8
-rw-r--r--app/services/projects/import_export/export_service.rb14
-rw-r--r--app/workers/repository_import_worker.rb7
-rw-r--r--app/workers/stuck_import_jobs_worker.rb6
28 files changed, 585 insertions, 52 deletions
diff --git a/app/assets/javascripts/alert_management/components/alert_management_list.vue b/app/assets/javascripts/alert_management/components/alert_management_list.vue
index d42e7d760b7..a1a71e592f6 100644
--- a/app/assets/javascripts/alert_management/components/alert_management_list.vue
+++ b/app/assets/javascripts/alert_management/components/alert_management_list.vue
@@ -21,11 +21,12 @@ import getAlerts from '../graphql/queries/get_alerts.query.graphql';
import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql';
import { ALERTS_STATUS, ALERTS_STATUS_TABS, ALERTS_SEVERITY_LABELS } from '../constants';
import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql';
-import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
+import { capitalizeFirstCharacter, convertToSnakeCase } from '~/lib/utils/text_utility';
const tdClass = 'table-col d-flex d-md-table-cell align-items-center';
const bodyTrClass =
'gl-border-1 gl-border-t-solid gl-border-gray-100 hover-bg-blue-50 hover-gl-cursor-pointer hover-gl-border-b-solid hover-gl-border-blue-200';
+const findDefaultSortColumn = () => document.querySelector('.js-started-at');
export default {
i18n: {
@@ -41,34 +42,41 @@ export default {
key: 'severity',
label: s__('AlertManagement|Severity'),
tdClass: `${tdClass} rounded-top text-capitalize`,
+ sortable: true,
},
{
- key: 'startedAt',
+ key: 'startTime',
label: s__('AlertManagement|Start time'),
+ thClass: 'js-started-at',
tdClass,
+ sortable: true,
},
{
- key: 'endedAt',
+ key: 'endTime',
label: s__('AlertManagement|End time'),
tdClass,
+ sortable: true,
},
{
key: 'title',
label: s__('AlertManagement|Alert'),
- thClass: 'w-30p',
+ thClass: 'w-30p alert-title',
tdClass,
+ sortable: false,
},
{
- key: 'eventCount',
+ key: 'eventsCount',
label: s__('AlertManagement|Events'),
- thClass: 'text-right gl-pr-9',
+ thClass: 'text-right gl-pr-9 w-3rem',
tdClass: `${tdClass} text-md-right`,
+ sortable: true,
},
{
key: 'status',
thClass: 'w-15p',
label: s__('AlertManagement|Status'),
tdClass: `${tdClass} rounded-bottom`,
+ sortable: true,
},
],
statuses: {
@@ -122,6 +130,7 @@ export default {
return {
projectPath: this.projectPath,
statuses: this.statusFilter,
+ sort: this.sort,
};
},
update(data) {
@@ -148,6 +157,7 @@ export default {
errored: false,
isAlertDismissed: false,
isErrorAlertDismissed: false,
+ sort: 'START_TIME_ASC',
statusFilter: this.$options.statusTabs[4].filters,
};
},
@@ -170,10 +180,22 @@ export default {
return !this.loading && this.hasAlerts ? bodyTrClass : '';
},
},
+ mounted() {
+ findDefaultSortColumn().ariaSort = 'ascending';
+ },
methods: {
filterAlertsByStatus(tabIndex) {
this.statusFilter = this.$options.statusTabs[tabIndex].filters;
},
+ fetchSortedData({ sortBy, sortDesc }) {
+ const sortDirection = sortDesc ? 'DESC' : 'ASC';
+ const sortColumn = convertToSnakeCase(sortBy).toUpperCase();
+
+ if (sortBy !== 'startTime') {
+ findDefaultSortColumn().ariaSort = 'none';
+ }
+ this.sort = `${sortColumn}_${sortDirection}`;
+ },
capitalizeFirstCharacter,
updateAlertStatus(status, iid) {
this.$apollo
@@ -235,7 +257,10 @@ export default {
:busy="loading"
stacked="md"
:tbody-tr-class="tbodyTrClass"
+ :no-local-sorting="true"
+ sort-icon-left
@row-clicked="navigateToAlertDetails"
+ @sort-changed="fetchSortedData"
>
<template #cell(severity)="{ item }">
<div
@@ -252,13 +277,17 @@ export default {
</div>
</template>
- <template #cell(startedAt)="{ item }">
+ <template #cell(startTime)="{ item }">
<time-ago v-if="item.startedAt" :time="item.startedAt" />
</template>
- <template #cell(endedAt)="{ item }">
+ <template #cell(endTime)="{ item }">
<time-ago v-if="item.endedAt" :time="item.endedAt" />
</template>
+ <!-- TODO: Remove after: https://gitlab.com/gitlab-org/gitlab/-/issues/218467 -->
+ <template #cell(eventsCount)="{ item }">
+ {{ item.eventCount }}
+ </template>
<template #cell(title)="{ item }">
<div class="gl-max-w-full text-truncate">{{ item.title }}</div>
diff --git a/app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.graphql b/app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.graphql
index 009ae0b2930..09151f233f5 100644
--- a/app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.graphql
+++ b/app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.graphql
@@ -4,6 +4,7 @@ mutation ($projectPath: ID!, $status: AlertManagementStatus!, $iid: String!) {
alert {
iid,
status,
+ endedAt
}
}
}
diff --git a/app/assets/javascripts/alert_management/graphql/queries/get_alerts.query.graphql b/app/assets/javascripts/alert_management/graphql/queries/get_alerts.query.graphql
index 294467d6bd1..42e5282e174 100644
--- a/app/assets/javascripts/alert_management/graphql/queries/get_alerts.query.graphql
+++ b/app/assets/javascripts/alert_management/graphql/queries/get_alerts.query.graphql
@@ -1,8 +1,8 @@
#import "../fragments/list_item.fragment.graphql"
-query getAlerts($projectPath: ID!, $statuses: [AlertManagementStatus!]) {
+query getAlerts($projectPath: ID!, $statuses: [AlertManagementStatus!], $sort: AlertManagementAlertSort ) {
project(fullPath: $projectPath) {
- alertManagementAlerts(statuses: $statuses) {
+ alertManagementAlerts(statuses: $statuses, sort: $sort) {
nodes {
...AlertListItem
}
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index ef75dabbda4..c6ff7de402e 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -55,3 +55,26 @@ $tooltip-padding-y: 0.5rem;
$tooltip-padding-x: 0.75rem;
$tooltip-arrow-height: 0.5rem;
$tooltip-arrow-width: 1rem;
+$b-table-sort-icon-bg-ascending: url('data:image/svg+xml, <svg \
+ xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="4 0 8 16"> \
+ <path style="fill: #666;" fill-rule="evenodd" d="M11.707085,11.7071 \
+ L7.999975,15.4142 L4.292875,11.7071 C3.902375,11.3166 3.902375, \
+ 10.6834 4.292875,10.2929 C4.683375,9.90237 \
+ 5.316575,9.90237 5.707075,10.2929 L6.999975, \
+ 11.5858 L6.999975,2 C6.999975,1.44771 \
+ 7.447695,1 7.999975,1 C8.552255,1 8.999975,1.44771 \
+ 8.999975,2 L8.999975,11.5858 L10.292865,10.2929 C10.683395 \
+ ,9.90237 11.316555,9.90237 11.707085,10.2929 \
+ C12.097605,10.6834 12.097605,11.3166 11.707085,11.7071 Z"/> \
+ </svg>') !default;
+$b-table-sort-icon-bg-descending: url('data:image/svg+xml,<svg \
+ xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="4 0 8 16"> \
+ <path style="fill: #666;" fill-rule="evenodd" d="M4.29289,4.2971 L8,0.59 \
+ L11.7071,4.2971 C12.0976,4.6876 \
+ 12.0976,5.3208 11.7071,5.7113 C11.3166,6.10183 10.6834, \
+ 6.10183 10.2929,5.7113 L9,4.4184 L9,14.0042 C9,14.55649 \
+ 8.55228,15.0042 8,15.0042 C7.44772,15.0042 7,14.55649 \
+ 7,14.0042 L7,4.4184 L5.70711,5.7113 C5.31658,6.10183 4.68342,6.10183 4.29289,5.7113 \
+ C3.90237,5.3208 3.90237,4.6876 4.29289,4.2971 Z"/> \
+ </svg> ') !default;
+$b-table-sort-icon-bg-not-sorted: '';
diff --git a/app/assets/stylesheets/pages/alert_management/list.scss b/app/assets/stylesheets/pages/alert_management/list.scss
index c5930a087c9..2eee093dfce 100644
--- a/app/assets/stylesheets/pages/alert_management/list.scss
+++ b/app/assets/stylesheets/pages/alert_management/list.scss
@@ -28,8 +28,19 @@
td,
th {
- @include gl-p-5;
+ // TODO: There is no gl-pl-9 utlity for this padding, to be done and then removed.
+ padding-left: 1.25rem;
+ @include gl-py-5;
+ @include gl-outline-none;
border: 0; // Remove cell border styling so that we can set border styling per row
+
+ &.event-count {
+ @include gl-pr-9;
+ }
+
+ &.alert-title {
+ @include gl-pointer-events-none;
+ }
}
th {
diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb
index 8a838db04f9..2bf7bdd1ae0 100644
--- a/app/controllers/ide_controller.rb
+++ b/app/controllers/ide_controller.rb
@@ -6,9 +6,11 @@ class IdeController < ApplicationController
include ClientsidePreviewCSP
include StaticObjectExternalStorageCSP
+ before_action do
+ push_frontend_feature_flag(:build_service_proxy)
+ end
+
def index
Gitlab::UsageDataCounters::WebIdeCounter.increment_views_count
end
end
-
-IdeController.prepend_if_ee('EE::IdeController')
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index e0457925b34..e1f6cbe3dca 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -14,6 +14,8 @@ class Projects::JobsController < Projects::ApplicationController
before_action only: [:show] do
push_frontend_feature_flag(:job_log_json, project, default_enabled: true)
end
+ before_action :authorize_create_proxy_build!, only: :proxy_websocket_authorize
+ before_action :verify_proxy_request!, only: :proxy_websocket_authorize
layout 'project'
@@ -151,6 +153,10 @@ class Projects::JobsController < Projects::ApplicationController
render json: Gitlab::Workhorse.channel_websocket(@build.terminal_specification)
end
+ def proxy_websocket_authorize
+ render json: proxy_websocket_service(build_service_specification)
+ end
+
private
def authorize_update_build!
@@ -165,10 +171,19 @@ class Projects::JobsController < Projects::ApplicationController
return access_denied! unless can?(current_user, :create_build_terminal, build)
end
+ def authorize_create_proxy_build!
+ return access_denied! unless can?(current_user, :create_build_service_proxy, build)
+ end
+
def verify_api_request!
Gitlab::Workhorse.verify_api_request!(request.headers)
end
+ def verify_proxy_request!
+ verify_api_request!
+ set_workhorse_internal_api_content_type
+ end
+
def raw_send_params
{ type: 'text/plain; charset=utf-8', disposition: 'inline' }
end
@@ -202,6 +217,27 @@ class Projects::JobsController < Projects::ApplicationController
'attachment'
end
-end
-Projects::JobsController.prepend_if_ee('EE::Projects::JobsController')
+ def build_service_specification
+ build.service_specification(service: params['service'],
+ port: params['port'],
+ path: params['path'],
+ subprotocols: proxy_subprotocol)
+ end
+
+ def proxy_subprotocol
+ # This will allow to reuse the same subprotocol set
+ # in the original websocket connection
+ request.headers['HTTP_SEC_WEBSOCKET_PROTOCOL'].presence || ::Ci::BuildRunnerSession::TERMINAL_SUBPROTOCOL
+ end
+
+ # This method provides the information to Workhorse
+ # about the service we want to proxy to.
+ # For security reasons, in case this operation is started by JS,
+ # it's important to use only sourced GitLab JS code
+ def proxy_websocket_service(service)
+ service[:url] = ::Gitlab::UrlHelpers.as_wss(service[:url])
+
+ ::Gitlab::Workhorse.channel_websocket(service)
+ end
+end
diff --git a/app/controllers/projects/web_ide_terminals_controller.rb b/app/controllers/projects/web_ide_terminals_controller.rb
new file mode 100644
index 00000000000..08ea5c4bca8
--- /dev/null
+++ b/app/controllers/projects/web_ide_terminals_controller.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+class Projects::WebIdeTerminalsController < Projects::ApplicationController
+ before_action :authenticate_user!
+
+ before_action :build, except: [:check_config, :create]
+ before_action :authorize_create_web_ide_terminal!
+ before_action :authorize_read_web_ide_terminal!, except: [:check_config, :create]
+ before_action :authorize_update_web_ide_terminal!, only: [:cancel, :retry]
+
+ def check_config
+ return respond_422 unless branch_sha
+
+ result = ::Ci::WebIdeConfigService.new(project, current_user, sha: branch_sha).execute
+
+ if result[:status] == :success
+ head :ok
+ else
+ respond_422
+ end
+ end
+
+ def show
+ render_terminal(build)
+ end
+
+ def create
+ result = ::Ci::CreateWebIdeTerminalService.new(project,
+ current_user,
+ ref: params[:branch])
+ .execute
+
+ if result[:status] == :error
+ render status: :bad_request, json: result[:message]
+ else
+ pipeline = result[:pipeline]
+ current_build = pipeline.builds.last
+
+ if current_build
+ Gitlab::UsageDataCounters::WebIdeCounter.increment_terminals_count
+
+ render_terminal(current_build)
+ else
+ render status: :bad_request, json: pipeline.errors.full_messages
+ end
+ end
+ end
+
+ def cancel
+ return respond_422 unless build.cancelable?
+
+ build.cancel
+
+ head :ok
+ end
+
+ def retry
+ return respond_422 unless build.retryable?
+
+ new_build = Ci::Build.retry(build, current_user)
+
+ render_terminal(new_build)
+ end
+
+ private
+
+ def authorize_create_web_ide_terminal!
+ return access_denied! unless can?(current_user, :create_web_ide_terminal, project)
+ end
+
+ def authorize_read_web_ide_terminal!
+ authorize_build_ability!(:read_web_ide_terminal)
+ end
+
+ def authorize_update_web_ide_terminal!
+ authorize_build_ability!(:update_web_ide_terminal)
+ end
+
+ def authorize_build_ability!(ability)
+ return access_denied! unless can?(current_user, ability, build)
+ end
+
+ def build
+ @build ||= project.builds.find(params[:id])
+ end
+
+ def branch_sha
+ return unless params[:branch].present?
+
+ project.commit(params[:branch])&.id
+ end
+
+ def render_terminal(current_build)
+ render json: WebIdeTerminalSerializer
+ .new(project: project, current_user: current_user)
+ .represent(current_build)
+ end
+end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 2b67bc543b5..c4930d831df 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -55,6 +55,7 @@ module Ci
delegate :url, to: :runner_session, prefix: true, allow_nil: true
delegate :terminal_specification, to: :runner_session, allow_nil: true
+ delegate :service_specification, to: :runner_session, allow_nil: true
delegate :gitlab_deploy_token, to: :project
delegate :trigger_short_token, to: :trigger_request, allow_nil: true
diff --git a/app/models/ci/build_runner_session.rb b/app/models/ci/build_runner_session.rb
index b46bbe69c7c..bc7f17f046c 100644
--- a/app/models/ci/build_runner_session.rb
+++ b/app/models/ci/build_runner_session.rb
@@ -7,6 +7,8 @@ module Ci
extend Gitlab::Ci::Model
TERMINAL_SUBPROTOCOL = 'terminal.gitlab.com'
+ DEFAULT_SERVICE_NAME = 'build'.freeze
+ DEFAULT_PORT_NAME = 'default_port'.freeze
self.table_name = 'ci_builds_runner_session'
@@ -23,6 +25,17 @@ module Ci
channel_specification(wss_url, TERMINAL_SUBPROTOCOL)
end
+ def service_specification(service: nil, path: nil, port: nil, subprotocols: nil)
+ return {} unless url.present?
+
+ port = port.presence || DEFAULT_PORT_NAME
+ service = service.presence || DEFAULT_SERVICE_NAME
+ url = "#{self.url}/proxy/#{service}/#{port}/#{path}"
+ subprotocols = subprotocols.presence || ::Ci::BuildRunnerSession::TERMINAL_SUBPROTOCOL
+
+ channel_specification(url, subprotocols)
+ end
+
private
def channel_specification(url, subprotocol)
@@ -37,5 +50,3 @@ module Ci
end
end
end
-
-Ci::BuildRunnerSession.prepend_if_ee('EE::Ci::BuildRunnerSession')
diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb
index 7e203cb67c4..0b971276a4b 100644
--- a/app/models/ci/pipeline_enums.rb
+++ b/app/models/ci/pipeline_enums.rb
@@ -27,6 +27,7 @@ module Ci
# https://gitlab.com/gitlab-org/gitlab/issues/195991
pipeline: 7,
chat: 8,
+ webide: 9,
merge_request_event: 10,
external_pull_request_event: 11,
parent_pipeline: 12
@@ -40,6 +41,7 @@ module Ci
unknown_source: nil,
repository_source: 1,
auto_devops_source: 2,
+ webide_source: 3,
remote_source: 4,
external_project_source: 5,
bridge_source: 6
diff --git a/app/models/project.rb b/app/models/project.rb
index 314f24f3f6d..795c2c5f1bd 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -328,6 +328,8 @@ class Project < ApplicationRecord
has_many :repository_storage_moves, class_name: 'ProjectRepositoryStorageMove'
+ has_many :webide_pipelines, -> { webide_source }, class_name: 'Ci::Pipeline', inverse_of: :project
+
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature, update_only: true
accepts_nested_attributes_for :project_setting, update_only: true
@@ -733,6 +735,10 @@ class Project < ApplicationRecord
end
end
+ def active_webide_pipelines(user:)
+ webide_pipelines.running_or_pending.for_user(user)
+ end
+
def autoclose_referenced_issues
return true if super.nil?
diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb
index dfe660ac780..4bd3ffbea2f 100644
--- a/app/models/project_import_state.rb
+++ b/app/models/project_import_state.rb
@@ -84,7 +84,11 @@ class ProjectImportState < ApplicationRecord
update_column(:last_error, sanitized_message)
rescue ActiveRecord::ActiveRecordError => e
- Gitlab::AppLogger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}")
+ Gitlab::Import::Logger.error(
+ message: 'Error setting import status to failed',
+ error: e.message,
+ original_error: sanitized_message
+ )
ensure
@errors = original_errors
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 021b1e60646..3af53d06922 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -69,7 +69,6 @@ class User < ApplicationRecord
MINIMUM_INACTIVE_DAYS = 180
- ignore_column :bot_type, remove_with: '13.1', remove_after: '2020-05-22'
ignore_column :ghost, remove_with: '13.2', remove_after: '2020-06-22'
# Override Devise::Models::Trackable#update_tracked_fields!
diff --git a/app/models/web_ide_terminal.rb b/app/models/web_ide_terminal.rb
new file mode 100644
index 00000000000..ef70df2405f
--- /dev/null
+++ b/app/models/web_ide_terminal.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+class WebIdeTerminal
+ include ::Gitlab::Routing
+
+ attr_reader :build, :project
+
+ delegate :id, :status, to: :build
+
+ def initialize(build)
+ @build = build
+ @project = build.project
+ end
+
+ def show_path
+ web_ide_terminal_route_generator(:show)
+ end
+
+ def retry_path
+ web_ide_terminal_route_generator(:retry)
+ end
+
+ def cancel_path
+ web_ide_terminal_route_generator(:cancel)
+ end
+
+ def terminal_path
+ terminal_project_job_path(project, build, format: :ws)
+ end
+
+ def proxy_websocket_path
+ proxy_project_job_path(project, build, format: :ws)
+ end
+
+ def services
+ build.services.map(&:alias).compact + Array(build.image&.alias)
+ end
+
+ private
+
+ def web_ide_terminal_route_generator(action, options = {})
+ options.reverse_merge!(action: action,
+ controller: 'projects/web_ide_terminals',
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: build.id,
+ only_path: true)
+
+ url_for(options)
+ end
+end
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index 12892a69257..0879a740f8a 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -36,6 +36,10 @@ module Ci
@subject.has_terminal?
end
+ condition(:is_web_ide_terminal, scope: :subject) do
+ @subject.pipeline.webide?
+ end
+
rule { protected_ref | archived }.policy do
prevent :update_build
prevent :update_commit_status
@@ -50,6 +54,24 @@ module Ci
end
rule { can?(:update_build) & terminal }.enable :create_build_terminal
+
+ rule { is_web_ide_terminal & can?(:create_web_ide_terminal) & (admin | owner_of_job) }.policy do
+ enable :read_web_ide_terminal
+ enable :update_web_ide_terminal
+ end
+
+ rule { is_web_ide_terminal & ~can?(:update_web_ide_terminal) }.policy do
+ prevent :create_build_terminal
+ end
+
+ rule { can?(:update_web_ide_terminal) & terminal }.policy do
+ enable :create_build_terminal
+ enable :create_build_service_proxy
+ end
+
+ rule { ~can?(:build_service_proxy_enabled) }.policy do
+ prevent :create_build_service_proxy
+ end
end
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 44de17121de..93f18a350ad 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -147,6 +147,10 @@ class ProjectPolicy < BasePolicy
@user && @user.confirmed?
end
+ condition(:build_service_proxy_enabled) do
+ ::Feature.enabled?(:build_service_proxy, @subject)
+ end
+
features = %w[
merge_requests
issues
@@ -559,6 +563,10 @@ class ProjectPolicy < BasePolicy
enable :read_project
end
+ rule { can?(:create_pipeline) & can?(:maintainer_access) }.enable :create_web_ide_terminal
+
+ rule { build_service_proxy_enabled }.enable :build_service_proxy_enabled
+
private
def team_member?
diff --git a/app/serializers/web_ide_terminal_entity.rb b/app/serializers/web_ide_terminal_entity.rb
new file mode 100644
index 00000000000..e2e90e824e7
--- /dev/null
+++ b/app/serializers/web_ide_terminal_entity.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class WebIdeTerminalEntity < Grape::Entity
+ expose :id
+ expose :status
+ expose :show_path
+ expose :cancel_path
+ expose :retry_path
+ expose :terminal_path
+ expose :services
+ expose :proxy_websocket_path, if: ->(_) { Feature.enabled?(:build_service_proxy) }
+end
diff --git a/app/serializers/web_ide_terminal_serializer.rb b/app/serializers/web_ide_terminal_serializer.rb
new file mode 100644
index 00000000000..5a9c4b99e0a
--- /dev/null
+++ b/app/serializers/web_ide_terminal_serializer.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class WebIdeTerminalSerializer < BaseSerializer
+ entity WebIdeTerminalEntity
+
+ def represent(resource, opts = {})
+ resource = WebIdeTerminal.new(resource) if resource.is_a?(Ci::Build)
+
+ super
+ end
+end
diff --git a/app/services/ci/create_web_ide_terminal_service.rb b/app/services/ci/create_web_ide_terminal_service.rb
new file mode 100644
index 00000000000..29d40756ab4
--- /dev/null
+++ b/app/services/ci/create_web_ide_terminal_service.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+module Ci
+ class CreateWebIdeTerminalService < ::BaseService
+ include ::Gitlab::Utils::StrongMemoize
+
+ TerminalCreationError = Class.new(StandardError)
+
+ TERMINAL_NAME = 'terminal'.freeze
+
+ attr_reader :terminal
+
+ def execute
+ check_access!
+ validate_params!
+ load_terminal_config!
+
+ pipeline = create_pipeline!
+ success(pipeline: pipeline)
+ rescue TerminalCreationError => e
+ error(e.message)
+ rescue ActiveRecord::RecordInvalid => e
+ error("Failed to persist the pipeline: #{e.message}")
+ end
+
+ private
+
+ def create_pipeline!
+ build_pipeline.tap do |pipeline|
+ pipeline.stages << terminal_stage_seed(pipeline).to_resource
+ pipeline.save!
+
+ Ci::ProcessPipelineService
+ .new(pipeline)
+ .execute(nil, initial_process: true)
+
+ pipeline_created_counter.increment(source: :webide)
+ end
+ end
+
+ def build_pipeline
+ Ci::Pipeline.new(
+ project: project,
+ user: current_user,
+ source: :webide,
+ config_source: :webide_source,
+ ref: ref,
+ sha: sha,
+ tag: false,
+ before_sha: Gitlab::Git::BLANK_SHA
+ )
+ end
+
+ def terminal_stage_seed(pipeline)
+ attributes = {
+ name: TERMINAL_NAME,
+ index: 0,
+ builds: [terminal_build_seed]
+ }
+
+ Gitlab::Ci::Pipeline::Seed::Stage.new(pipeline, attributes, [])
+ end
+
+ def terminal_build_seed
+ terminal.merge(
+ name: TERMINAL_NAME,
+ stage: TERMINAL_NAME,
+ user: current_user,
+ scheduling_type: :stage)
+ end
+
+ def load_terminal_config!
+ result = ::Ci::WebIdeConfigService.new(project, current_user, sha: sha).execute
+ raise TerminalCreationError, result[:message] if result[:status] != :success
+
+ @terminal = result[:terminal]
+ raise TerminalCreationError, 'Terminal is not configured' unless terminal
+ end
+
+ def validate_params!
+ unless sha
+ raise TerminalCreationError, 'Ref does not exist'
+ end
+
+ unless branch_exists?
+ raise TerminalCreationError, 'Ref needs to be a branch'
+ end
+ end
+
+ def check_access!
+ unless can?(current_user, :create_web_ide_terminal, project)
+ raise TerminalCreationError, 'Insufficient permissions to create a terminal'
+ end
+
+ if terminal_active?
+ raise TerminalCreationError, 'There is already a terminal running'
+ end
+ end
+
+ def pipeline_created_counter
+ @pipeline_created_counter ||= Gitlab::Metrics
+ .counter(:pipelines_created_total, "Counter of pipelines created")
+ end
+
+ def terminal_active?
+ project.active_webide_pipelines(user: current_user).exists?
+ end
+
+ def ref
+ strong_memoize(:ref) do
+ Gitlab::Git.ref_name(params[:ref])
+ end
+ end
+
+ def branch_exists?
+ project.repository.branch_exists?(ref)
+ end
+
+ def sha
+ project.commit(params[:ref]).try(:id)
+ end
+ end
+end
diff --git a/app/services/ci/web_ide_config_service.rb b/app/services/ci/web_ide_config_service.rb
new file mode 100644
index 00000000000..ade9132f419
--- /dev/null
+++ b/app/services/ci/web_ide_config_service.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Ci
+ class WebIdeConfigService < ::BaseService
+ include ::Gitlab::Utils::StrongMemoize
+
+ ValidationError = Class.new(StandardError)
+
+ WEBIDE_CONFIG_FILE = '.gitlab/.gitlab-webide.yml'.freeze
+
+ attr_reader :config, :config_content
+
+ def execute
+ check_access!
+ load_config_content!
+ load_config!
+
+ success(terminal: config.terminal_value)
+ rescue ValidationError => e
+ error(e.message)
+ end
+
+ private
+
+ def check_access!
+ unless can?(current_user, :download_code, project)
+ raise ValidationError, 'Insufficient permissions to read configuration'
+ end
+ end
+
+ def load_config_content!
+ @config_content = webide_yaml_from_repo
+
+ unless config_content
+ raise ValidationError, "Failed to load Web IDE config file '#{WEBIDE_CONFIG_FILE}' for #{params[:sha]}"
+ end
+ end
+
+ def load_config!
+ @config = Gitlab::WebIde::Config.new(config_content)
+
+ unless @config.valid?
+ raise ValidationError, @config.errors.first
+ end
+ rescue Gitlab::WebIde::Config::ConfigError => e
+ raise ValidationError, e.message
+ end
+
+ def webide_yaml_from_repo
+ gitlab_webide_yml_for(params[:sha])
+ rescue GRPC::NotFound, GRPC::Internal
+ nil
+ end
+
+ def gitlab_webide_yml_for(sha)
+ project.repository.blob_data_at(sha, WEBIDE_CONFIG_FILE)
+ end
+ end
+end
diff --git a/app/services/clusters/parse_cluster_applications_artifact_service.rb b/app/services/clusters/parse_cluster_applications_artifact_service.rb
index b8e1c80cfe7..35fba5f47c7 100644
--- a/app/services/clusters/parse_cluster_applications_artifact_service.rb
+++ b/app/services/clusters/parse_cluster_applications_artifact_service.rb
@@ -18,13 +18,9 @@ module Clusters
raise ArgumentError, 'Artifact is not cluster_applications file type' unless artifact&.cluster_applications?
- unless artifact.file.size < MAX_ACCEPTABLE_ARTIFACT_SIZE
- return error(too_big_error_message, :bad_request)
- end
-
- unless cluster
- return error(s_('ClusterIntegration|No deployment cluster found for this job'))
- end
+ return error(too_big_error_message, :bad_request) unless artifact.file.size < MAX_ACCEPTABLE_ARTIFACT_SIZE
+ return error(no_deployment_message, :bad_request) unless job.deployment
+ return error(no_deployment_cluster_message, :bad_request) unless cluster
parse!(artifact)
@@ -61,7 +57,8 @@ module Clusters
Clusters::Cluster.transaction do
RELEASE_NAMES.each do |release_name|
- application = find_or_build_application(release_name)
+ application_class = Clusters::Cluster::APPLICATIONS[release_name]
+ application = cluster.find_or_build_application(application_class)
release = release_by_name[release_name]
@@ -80,16 +77,18 @@ module Clusters
end
end
- def find_or_build_application(application_name)
- application_class = Clusters::Cluster::APPLICATIONS[application_name]
-
- cluster.find_or_build_application(application_class)
- end
-
def too_big_error_message
human_size = ActiveSupport::NumberHelper.number_to_human_size(MAX_ACCEPTABLE_ARTIFACT_SIZE)
s_('ClusterIntegration|Cluster_applications artifact too big. Maximum allowable size: %{human_size}') % { human_size: human_size }
end
+
+ def no_deployment_message
+ s_('ClusterIntegration|No deployment found for this job')
+ end
+
+ def no_deployment_cluster_message
+ s_('ClusterIntegration|No deployment cluster found for this job')
+ end
end
end
diff --git a/app/services/groups/import_export/export_service.rb b/app/services/groups/import_export/export_service.rb
index 0f2e3bb65f9..39a6889fc84 100644
--- a/app/services/groups/import_export/export_service.rb
+++ b/app/services/groups/import_export/export_service.rb
@@ -4,10 +4,11 @@ module Groups
module ImportExport
class ExportService
def initialize(group:, user:, params: {})
- @group = group
+ @group = group
@current_user = user
- @params = params
- @shared = @params[:shared] || Gitlab::ImportExport::Shared.new(@group)
+ @params = params
+ @shared = @params[:shared] || Gitlab::ImportExport::Shared.new(@group)
+ @logger = Gitlab::Export::Logger.build
end
def async_execute
@@ -91,21 +92,21 @@ module Groups
end
def notify_success
- @shared.logger.info(
- group_id: @group.id,
- group_name: @group.name,
- message: 'Group Import/Export: Export succeeded'
+ @logger.info(
+ message: 'Group Export succeeded',
+ group_id: @group.id,
+ group_name: @group.name
)
notification_service.group_was_exported(@group, @current_user)
end
def notify_error
- @shared.logger.error(
- group_id: @group.id,
+ @logger.error(
+ message: 'Group Export failed',
+ group_id: @group.id,
group_name: @group.name,
- error: @shared.errors.join(', '),
- message: 'Group Import/Export: Export failed'
+ errors: @shared.errors.join(', ')
)
notification_service.group_was_not_exported(@group, @current_user, @shared.errors)
diff --git a/app/services/groups/import_export/import_service.rb b/app/services/groups/import_export/import_service.rb
index e1be40f0241..dcd78210801 100644
--- a/app/services/groups/import_export/import_service.rb
+++ b/app/services/groups/import_export/import_service.rb
@@ -9,6 +9,7 @@ module Groups
@group = group
@current_user = user
@shared = Gitlab::ImportExport::Shared.new(@group)
+ @logger = Gitlab::Import::Logger.build
end
def async_execute
@@ -81,7 +82,7 @@ module Groups
end
def notify_success
- @shared.logger.info(
+ @logger.info(
group_id: @group.id,
group_name: @group.name,
message: 'Group Import/Export: Import succeeded'
@@ -89,7 +90,7 @@ module Groups
end
def notify_error
- @shared.logger.error(
+ @logger.error(
group_id: @group.id,
group_name: @group.name,
message: "Group Import/Export: Errors occurred, see '#{Gitlab::ErrorTracking::Logger.file_name}' for details"
diff --git a/app/services/projects/after_import_service.rb b/app/services/projects/after_import_service.rb
index b09a8e0bece..fad2290a47b 100644
--- a/app/services/projects/after_import_service.rb
+++ b/app/services/projects/after_import_service.rb
@@ -22,8 +22,12 @@ module Projects
# causing GC to run every time.
service.increment!
rescue Projects::HousekeepingService::LeaseTaken => e
- Gitlab::AppLogger.info(
- "Could not perform housekeeping for project #{@project.full_path} (#{@project.id}): #{e}")
+ Gitlab::Import::Logger.info(
+ message: 'Project housekeeping failed',
+ project_full_path: @project.full_path,
+ project_id: @project.id,
+ error: e.message
+ )
end
private
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index 0c515479cfa..031b99753c3 100644
--- a/app/services/projects/import_export/export_service.rb
+++ b/app/services/projects/import_export/export_service.rb
@@ -9,6 +9,7 @@ module Projects
super
@shared = project.import_export_shared
+ @logger = Gitlab::Export::Logger.build
end
def execute(after_export_strategy = nil)
@@ -115,11 +116,20 @@ module Projects
end
def notify_success
- Gitlab::AppLogger.info("Import/Export - Project #{project.name} with ID: #{project.id} successfully exported")
+ @logger.info(
+ message: 'Project successfully exported',
+ project_name: project.name,
+ project_id: project.id
+ )
end
def notify_error
- Gitlab::AppLogger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{shared.errors.join(', ')}")
+ @logger.error(
+ message: 'Project export error',
+ export_errors: shared.errors.join(', '),
+ project_name: project.name,
+ project_id: project.id
+ )
notification_service.project_not_exported(project, current_user, shared.errors)
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 9f17ef467e3..30570a2227e 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -43,7 +43,12 @@ class RepositoryImportWorker # rubocop:disable Scalability/IdempotentWorker
def start_import
return true if start(project.import_state)
- Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while importing.") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::Import::Logger.info(
+ message: 'Project was in inconsistent state while importing',
+ project_full_path: project.full_path,
+ project_import_status: project.import_status
+ )
+
false
end
diff --git a/app/workers/stuck_import_jobs_worker.rb b/app/workers/stuck_import_jobs_worker.rb
index 6a48b78b22c..8e8bf537bd5 100644
--- a/app/workers/stuck_import_jobs_worker.rb
+++ b/app/workers/stuck_import_jobs_worker.rb
@@ -45,7 +45,11 @@ class StuckImportJobsWorker # rubocop:disable Scalability/IdempotentWorker
completed_import_states = enqueued_import_states_with_jid.where(id: completed_import_state_ids)
completed_import_state_jids = completed_import_states.map { |import_state| import_state.jid }.join(', ')
- Rails.logger.info("Marked stuck import jobs as failed. JIDs: #{completed_import_state_jids}") # rubocop:disable Gitlab/RailsLogger
+
+ Gitlab::Import::Logger.info(
+ message: 'Marked stuck import jobs as failed',
+ job_ids: completed_import_state_jids
+ )
completed_import_states.each do |import_state|
import_state.mark_as_failed(error_message)