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>2022-03-16 21:08:16 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-16 21:08:16 +0300
commit204df35415f2b0ed86c83b31b1d276f52e07e577 (patch)
tree1db4c0f302c145a5b6cd02afe7d49ea72267f612 /app
parentdb19df23733c768c564534a09de2e6718097ec95 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/images/aws-cloud-formation.pngbin2545 -> 0 bytes
-rw-r--r--app/assets/javascripts/clusters/components/new_cluster.vue6
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_empty_state.vue4
-rw-r--r--app/assets/javascripts/pages/admin/clusters/connect/index.js (renamed from app/assets/javascripts/pages/admin/clusters/new/index.js)0
-rw-r--r--app/assets/javascripts/pages/groups/clusters/connect/index.js (renamed from app/assets/javascripts/pages/groups/clusters/new/index.js)0
-rw-r--r--app/assets/javascripts/pages/projects/clusters/connect/index.js (renamed from app/assets/javascripts/pages/projects/clusters/new/index.js)0
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js20
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue96
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/clusters/clusters_controller.rb6
-rw-r--r--app/controllers/groups/runners_controller.rb6
-rw-r--r--app/controllers/projects/releases_controller.rb2
-rw-r--r--app/graphql/mutations/ci/runner/delete.rb9
-rw-r--r--app/helpers/clusters_helper.rb15
-rw-r--r--app/models/ci/build.rb8
-rw-r--r--app/models/integration.rb2
-rw-r--r--app/models/integrations/harbor.rb104
-rw-r--r--app/models/project.rb1
-rw-r--r--app/policies/ci/runner_policy.rb6
-rw-r--r--app/presenters/clusterable_presenter.rb4
-rw-r--r--app/presenters/instance_clusterable_presenter.rb5
-rw-r--r--app/presenters/project_clusterable_presenter.rb4
-rw-r--r--app/views/clusters/clusters/_multiple_clusters_message.html.haml4
-rw-r--r--app/views/clusters/clusters/_sidebar.html.haml4
-rw-r--r--app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml6
-rw-r--r--app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml8
-rw-r--r--app/views/clusters/clusters/connect.html.haml11
-rw-r--r--app/views/clusters/clusters/gcp/_form.html.haml6
-rw-r--r--app/views/clusters/clusters/new.html.haml30
-rw-r--r--app/views/clusters/clusters/user/_form.html.haml8
-rw-r--r--app/views/projects/issues/_service_desk_empty_state.html.haml4
31 files changed, 266 insertions, 119 deletions
diff --git a/app/assets/images/aws-cloud-formation.png b/app/assets/images/aws-cloud-formation.png
deleted file mode 100644
index 1d078309d86..00000000000
--- a/app/assets/images/aws-cloud-formation.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/javascripts/clusters/components/new_cluster.vue b/app/assets/javascripts/clusters/components/new_cluster.vue
index 2e74ad073c5..8f3e2916270 100644
--- a/app/assets/javascripts/clusters/components/new_cluster.vue
+++ b/app/assets/javascripts/clusters/components/new_cluster.vue
@@ -5,9 +5,9 @@ import { s__ } from '~/locale';
export default {
i18n: {
- title: s__('ClusterIntegration|Enter the details for your Kubernetes cluster'),
+ title: s__('ClusterIntegration|Enter your Kubernetes cluster certificate details'),
information: s__(
- 'ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{linkStart}documentation%{linkEnd} on Kubernetes',
+ 'ClusterIntegration|Enter details about your cluster. %{linkStart}How do I use a certificate to connect to my cluster?%{linkEnd}',
),
},
components: {
@@ -21,7 +21,7 @@ export default {
</script>
<template>
- <div>
+ <div class="gl-pt-4">
<h4>{{ $options.i18n.title }}</h4>
<p>
<gl-sprintf :message="$options.i18n.information">
diff --git a/app/assets/javascripts/clusters_list/components/clusters_empty_state.vue b/app/assets/javascripts/clusters_list/components/clusters_empty_state.vue
index ce601de57bd..76bec05cfc7 100644
--- a/app/assets/javascripts/clusters_list/components/clusters_empty_state.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters_empty_state.vue
@@ -13,7 +13,7 @@ export default {
GlSprintf,
GlAlert,
},
- inject: ['emptyStateHelpText', 'clustersEmptyStateImage', 'newClusterPath'],
+ inject: ['emptyStateHelpText', 'clustersEmptyStateImage', 'addClusterPath'],
props: {
isChildComponent: {
default: false,
@@ -57,7 +57,7 @@ export default {
category="primary"
variant="confirm"
:disabled="!canAddCluster"
- :href="newClusterPath"
+ :href="addClusterPath"
>
{{ $options.i18n.buttonText }}
</gl-button>
diff --git a/app/assets/javascripts/pages/admin/clusters/new/index.js b/app/assets/javascripts/pages/admin/clusters/connect/index.js
index de9ded87ef3..de9ded87ef3 100644
--- a/app/assets/javascripts/pages/admin/clusters/new/index.js
+++ b/app/assets/javascripts/pages/admin/clusters/connect/index.js
diff --git a/app/assets/javascripts/pages/groups/clusters/new/index.js b/app/assets/javascripts/pages/groups/clusters/connect/index.js
index de9ded87ef3..de9ded87ef3 100644
--- a/app/assets/javascripts/pages/groups/clusters/new/index.js
+++ b/app/assets/javascripts/pages/groups/clusters/connect/index.js
diff --git a/app/assets/javascripts/pages/projects/clusters/new/index.js b/app/assets/javascripts/pages/projects/clusters/connect/index.js
index de9ded87ef3..de9ded87ef3 100644
--- a/app/assets/javascripts/pages/projects/clusters/new/index.js
+++ b/app/assets/javascripts/pages/projects/clusters/connect/index.js
diff --git a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js
index e10ad8bafaa..88c975b97b9 100644
--- a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js
+++ b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js
@@ -14,7 +14,11 @@ export const EASY_BUTTONS = [
templateName:
'easybutton-amazon-linux-2-docker-manual-scaling-with-schedule-ondemandonly.cf.yml',
description: s__(
- 'Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot. Default choice for Linux Docker executor.',
+ 'Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot.',
+ ),
+ moreDetails1: s__('Runners|No spot. This is the default choice for Linux Docker executor.'),
+ moreDetails2: s__(
+ 'Runners|A capacity of 1 enables warm HA through Auto Scaling group re-spawn. A capacity of 2 enables hot HA because the service is available even when a node is lost. A capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
),
},
{
@@ -26,12 +30,20 @@ export const EASY_BUTTONS = [
),
{ percentage: '100%' },
),
+ moreDetails1: sprintf(s__('Runners|%{percentage} spot.'), { percentage: '100%' }),
+ moreDetails2: s__(
+ 'Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
+ ),
},
{
stackName: 'win2019-shell-non-spot',
templateName: 'easybutton-windows2019-shell-manual-scaling-with-scheduling-ondemandonly.cf.yml',
description: s__(
- 'Runners|Windows 2019 Shell with manual scaling and optional scheduling. Non-spot. Default choice for Windows Shell executor.',
+ 'Runners|Windows 2019 Shell with manual scaling and optional scheduling. Non-spot.',
+ ),
+ moreDetails1: s__('Runners|No spot. Default choice for Windows Shell executor.'),
+ moreDetails2: s__(
+ 'Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
),
},
{
@@ -43,5 +55,9 @@ export const EASY_BUTTONS = [
),
{ percentage: '100%' },
),
+ moreDetails1: sprintf(s__('Runners|%{percentage} spot.'), { percentage: '100%' }),
+ moreDetails2: s__(
+ 'Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
+ ),
},
];
diff --git a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue
index 811176821e2..eee65d90285 100644
--- a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue
@@ -1,8 +1,15 @@
<script>
-import { GlModal, GlSprintf, GlLink } from '@gitlab/ui';
-import awsCloudFormationImageUrl from 'images/aws-cloud-formation.png';
+import {
+ GlModal,
+ GlSprintf,
+ GlLink,
+ GlFormRadioGroup,
+ GlFormRadio,
+ GlAccordion,
+ GlAccordionItem,
+} from '@gitlab/ui';
import Tracking from '~/tracking';
-import { getBaseURL, objectToQuery } from '~/lib/utils/url_utility';
+import { getBaseURL, objectToQuery, visitUrl } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import { README_URL, CF_BASE_URL, TEMPLATES_BASE_URL, EASY_BUTTONS } from './constants';
@@ -11,6 +18,10 @@ export default {
GlModal,
GlSprintf,
GlLink,
+ GlFormRadioGroup,
+ GlFormRadio,
+ GlAccordion,
+ GlAccordionItem,
},
mixins: [Tracking.mixin()],
props: {
@@ -18,13 +29,16 @@ export default {
type: String,
required: true,
},
- imgSrc: {
- type: String,
- required: false,
- default: awsCloudFormationImageUrl,
- },
+ },
+ data() {
+ return {
+ selected: this.$options.easyButtons[0],
+ };
},
methods: {
+ borderBottom(idx) {
+ return idx < this.$options.easyButtons.length - 1;
+ },
easyButtonUrl(easyButton) {
const params = {
templateURL: TEMPLATES_BASE_URL + easyButton.templateName,
@@ -38,18 +52,26 @@ export default {
label: stackName,
});
},
+ handleModalPrimary() {
+ this.trackCiRunnerTemplatesClick(this.selected.stackName);
+ visitUrl(this.easyButtonUrl(this.selected), true);
+ },
},
i18n: {
title: s__('Runners|Deploy GitLab Runner in AWS'),
instructions: s__(
- 'Runners|For each solution, you will choose a capacity. 1 enables warm HA through Auto Scaling group re-spawn. 2 enables hot HA because the service is available even when a node is lost. 3 or more enables hot HA and manual scaling of runner fleet.',
- ),
- dont_see_what_you_are_looking_for: s__(
- "Rnners|Don't see what you are looking for? See the full list of options, including a fully customizable option, %{linkStart}here%{linkEnd}.",
+ 'Runners|Select your preferred option here. In the next step, you can choose the capacity for your runner in the AWS CloudFormation console.',
),
- note: s__(
- 'Runners|If you do not select an AWS VPC, the runner will deploy to the Default VPC in the AWS Region you select. Please consult with your AWS administrator to understand if there are any security risks to deploying into the Default VPC in any given region in your AWS account.',
+ chooseRunner: s__('Runners|Choose your preferred GitLab Runner'),
+ dontSeeWhatYouAreLookingFor: s__(
+ "Runners|Don't see what you are looking for? See the full list of options, including a fully customizable option %{linkStart}here%{linkEnd}.",
),
+ moreDetails: __('More Details'),
+ lessDetails: __('Less Details'),
+ },
+ deployButton: {
+ text: s__('Runners|Deploy GitLab Runner in AWS'),
+ attributes: [{ variant: 'confirm' }],
},
closeButton: {
text: __('Cancel'),
@@ -63,37 +85,41 @@ export default {
<gl-modal
:modal-id="modalId"
:title="$options.i18n.title"
+ :action-primary="$options.deployButton"
:action-secondary="$options.closeButton"
size="sm"
+ @primary="handleModalPrimary"
>
<p>{{ $options.i18n.instructions }}</p>
- <ul class="gl-list-style-none gl-p-0 gl-mb-0">
- <li v-for="easyButton in $options.easyButtons" :key="easyButton.templateName">
- <gl-link
- :href="easyButtonUrl(easyButton)"
- target="_blank"
- class="gl-display-flex gl-font-weight-bold"
- @click="trackCiRunnerTemplatesClick(easyButton.stackName)"
- >
- <img
- :title="easyButton.stackName"
- :alt="easyButton.stackName"
- :src="imgSrc"
- width="46"
- height="46"
- class="gl-mt-2 gl-mr-5 gl-mb-6"
- />
+ <gl-form-radio-group v-model="selected" :label="$options.i18n.chooseRunner" label-sr-only>
+ <gl-form-radio
+ v-for="(easyButton, idx) in $options.easyButtons"
+ :key="easyButton.templateName"
+ :value="easyButton"
+ class="gl-py-5 gl-pl-8"
+ :class="{ 'gl-border-b': borderBottom(idx) }"
+ >
+ <div class="gl-mt-n1 gl-pl-4 gl-pb-2 gl-font-weight-bold">
{{ easyButton.description }}
- </gl-link>
- </li>
- </ul>
+ <gl-accordion :header-level="3" class="gl-pt-3">
+ <gl-accordion-item
+ :title="$options.i18n.moreDetails"
+ :title-visible="$options.i18n.lessDetails"
+ class="gl-font-weight-normal"
+ >
+ <p class="gl-pt-2">{{ easyButton.moreDetails1 }}</p>
+ <p class="gl-m-0">{{ easyButton.moreDetails2 }}</p>
+ </gl-accordion-item>
+ </gl-accordion>
+ </div>
+ </gl-form-radio>
+ </gl-form-radio-group>
<p>
- <gl-sprintf :message="$options.i18n.dont_see_what_you_are_looking_for">
+ <gl-sprintf :message="$options.i18n.dontSeeWhatYouAreLookingFor">
<template #link="{ content }">
<gl-link :href="$options.readmeUrl" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
- <p class="gl-font-sm gl-mb-0">{{ $options.i18n.note }}</p>
</gl-modal>
</template>
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 89b130d1d47..1d17e8aa085 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -246,19 +246,19 @@ class ApplicationController < ActionController::Base
end
def git_not_found!
- render "errors/git_not_found.html", layout: "errors", status: :not_found
+ render template: "errors/git_not_found", formats: :html, layout: "errors", status: :not_found
end
def render_403
respond_to do |format|
- format.html { render "errors/access_denied", layout: "errors", status: :forbidden }
+ format.html { render template: "errors/access_denied", formats: :html, layout: "errors", status: :forbidden }
format.any { head :forbidden }
end
end
def render_404
respond_to do |format|
- format.html { render "errors/not_found", layout: "errors", status: :not_found }
+ format.html { render template: "errors/not_found", formats: :html, layout: "errors", status: :not_found }
# Prevent the Rails CSRF protector from thinking a missing .js file is a JavaScript file
format.js { render json: '', status: :not_found, content_type: 'application/json' }
format.any { head :not_found }
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 4fa98130b3f..d9179129983 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -9,9 +9,9 @@ class Clusters::ClustersController < Clusters::BaseController
before_action :generate_gcp_authorize_url, only: [:new]
before_action :validate_gcp_token, only: [:new]
before_action :gcp_cluster, only: [:new]
- before_action :user_cluster, only: [:new]
+ before_action :user_cluster, only: [:new, :connect]
before_action :authorize_read_cluster!, only: [:show, :index]
- before_action :authorize_create_cluster!, only: [:new, :authorize_aws_role]
+ before_action :authorize_create_cluster!, only: [:new, :connect, :authorize_aws_role]
before_action :authorize_update_cluster!, only: [:update]
before_action :update_applications_status, only: [:cluster_status]
before_action :ensure_feature_enabled!, except: :index
@@ -152,7 +152,7 @@ class Clusters::ClustersController < Clusters::BaseController
validate_gcp_token
gcp_cluster
- render :new, locals: { active_tab: 'add' }
+ render :connect
end
end
diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb
index 2e26216c72c..dabef978ee1 100644
--- a/app/controllers/groups/runners_controller.rb
+++ b/app/controllers/groups/runners_controller.rb
@@ -32,12 +32,12 @@ class Groups::RunnersController < Groups::ApplicationController
end
def destroy
- if @runner.belongs_to_more_than_one_project?
- redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found, alert: _('Runner was not deleted because it is assigned to multiple projects.')
- else
+ if can?(current_user, :delete_runner, @runner)
Ci::Runners::UnregisterRunnerService.new(@runner, current_user).execute
redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found
+ else
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found, alert: _('Runner cannot be deleted, please contact your administrator.')
end
end
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index 62e607ae1af..1a2baf96020 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -72,7 +72,7 @@ class Projects::ReleasesController < Projects::ApplicationController
def fetch_latest_tag
allowed_values = ['released_at']
- params.reject! { |key, value| key.to_sym == :order_by && allowed_values.any?(value) }
+ params.reject! { |key, value| key.to_sym == :order_by && !allowed_values.any?(value) }
@latest_tag = releases(order_by: params[:order_by]).first&.tag
end
diff --git a/app/graphql/mutations/ci/runner/delete.rb b/app/graphql/mutations/ci/runner/delete.rb
index d26f3772578..1713ec0bf6d 100644
--- a/app/graphql/mutations/ci/runner/delete.rb
+++ b/app/graphql/mutations/ci/runner/delete.rb
@@ -17,20 +17,11 @@ module Mutations
def resolve(id:, **runner_attrs)
runner = authorized_find!(id)
- error = authenticate_delete_runner!(runner)
- return { errors: [error] } if error
-
::Ci::Runners::UnregisterRunnerService.new(runner, current_user).execute
{ errors: runner.errors.full_messages }
end
- def authenticate_delete_runner!(runner)
- return if current_user.can_admin_all_resources?
-
- "Runner #{runner.to_global_id} associated with more than one project" if runner.runner_projects.count > 1
- end
-
def find_object(id)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 62d93d75b11..5d0dbcb1217 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -1,17 +1,6 @@
# frozen_string_literal: true
module ClustersHelper
- def create_new_cluster_label(provider: nil)
- case provider
- when 'aws'
- s_('ClusterIntegration|Create new cluster on EKS')
- when 'gcp'
- s_('ClusterIntegration|Create new cluster on GKE')
- else
- s_('ClusterIntegration|Create new cluster')
- end
- end
-
def display_cluster_agents?(clusterable)
clusterable.is_a?(Project)
end
@@ -27,8 +16,8 @@ module ClustersHelper
},
clusters_empty_state_image: image_path('illustrations/empty-state/empty-state-clusters.svg'),
empty_state_help_text: clusterable.empty_state_help_text,
- new_cluster_path: clusterable.new_path(tab: 'create'),
- add_cluster_path: clusterable.new_path(tab: 'add'),
+ new_cluster_path: clusterable.new_path,
+ add_cluster_path: clusterable.connect_path,
can_add_cluster: clusterable.can_add_cluster?.to_s,
can_admin_cluster: clusterable.can_admin_cluster?.to_s,
display_cluster_agents: display_cluster_agents?(clusterable).to_s,
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index ae43053ff16..68ec196a9ee 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -72,6 +72,7 @@ module Ci
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 :harbor_integration, to: :project
delegate :trigger_short_token, to: :trigger_request, allow_nil: true
##
@@ -583,6 +584,7 @@ module Ci
.append(key: 'CI_REGISTRY_PASSWORD', value: token.to_s, public: false, masked: true)
.append(key: 'CI_REPOSITORY_URL', value: repo_url.to_s, public: false)
.concat(deploy_token_variables)
+ .concat(harbor_variables)
end
end
@@ -619,6 +621,12 @@ module Ci
end
end
+ def harbor_variables
+ return [] unless harbor_integration.try(:activated?)
+
+ Gitlab::Ci::Variables::Collection.new(harbor_integration.ci_variables)
+ end
+
def features
{
trace_sections: true,
diff --git a/app/models/integration.rb b/app/models/integration.rb
index 0445894644e..274c16507b7 100644
--- a/app/models/integration.rb
+++ b/app/models/integration.rb
@@ -20,7 +20,7 @@ class Integration < ApplicationRecord
INTEGRATION_NAMES = %w[
asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord
- drone_ci emails_on_push ewm external_wiki flowdock hangouts_chat irker jira
+ drone_ci emails_on_push ewm external_wiki flowdock hangouts_chat harbor irker jira
mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email
pivotaltracker prometheus pushover redmine slack slack_slash_commands teamcity unify_circuit webex_teams youtrack zentao
].freeze
diff --git a/app/models/integrations/harbor.rb b/app/models/integrations/harbor.rb
new file mode 100644
index 00000000000..4c76e418886
--- /dev/null
+++ b/app/models/integrations/harbor.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+module Integrations
+ class Harbor < Integration
+ prop_accessor :url, :project_name, :username, :password
+
+ validates :url, public_url: true, presence: true, if: :activated?
+ validates :project_name, presence: true, if: :activated?
+ validates :username, presence: true, if: :activated?
+ validates :password, format: { with: ::Ci::Maskable::REGEX }, if: :activated?
+
+ before_validation :reset_username_and_password
+
+ def title
+ 'Harbor'
+ end
+
+ def description
+ s_("HarborIntegration|Use Harbor as this project's container registry.")
+ end
+
+ def help
+ s_("HarborIntegration|After the Harbor integration is activated, global variables ‘$HARBOR_USERNAME’, ‘$HARBOR_PASSWORD’, ‘$HARBOR_URL’ and ‘$HARBOR_PROJECT’ will be created for CI/CD use.")
+ end
+
+ class << self
+ def to_param
+ name.demodulize.downcase
+ end
+
+ def supported_events
+ []
+ end
+
+ def supported_event_actions
+ []
+ end
+ end
+
+ def test(*_args)
+ client.ping
+ end
+
+ def fields
+ [
+ {
+ type: 'text',
+ name: 'url',
+ title: s_('HarborIntegration|Harbor URL'),
+ placeholder: 'https://demo.goharbor.io',
+ help: s_('HarborIntegration|Base URL of the Harbor instance.'),
+ required: true
+ },
+ {
+ type: 'text',
+ name: 'project_name',
+ title: s_('HarborIntegration|Harbor project name'),
+ help: s_('HarborIntegration|The name of the project in Harbor.')
+ },
+ {
+ type: 'text',
+ name: 'username',
+ title: s_('HarborIntegration|Harbor username'),
+ required: true
+ },
+ {
+ type: 'text',
+ name: 'password',
+ title: s_('HarborIntegration|Harbor password'),
+ non_empty_password_title: s_('HarborIntegration|Enter Harbor password'),
+ non_empty_password_help: s_('HarborIntegration|Password for your Harbor username.'),
+ required: true
+ }
+ ]
+ end
+
+ def ci_variables
+ return [] unless activated?
+
+ [
+ { key: 'HARBOR_URL', value: url },
+ { key: 'HARBOR_PROJECT', value: project_name },
+ { key: 'HARBOR_USERNAME', value: username },
+ { key: 'HARBOR_PASSWORD', value: password, public: false, masked: true }
+ ]
+ end
+
+ private
+
+ def client
+ @client ||= ::Gitlab::Harbor::Client.new(self)
+ end
+
+ def reset_username_and_password
+ if url_changed? && !password_touched?
+ self.password = nil
+ end
+
+ if url_changed? && !username_touched?
+ self.username = nil
+ end
+ end
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 3778db48ff2..a6e106892fd 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -196,6 +196,7 @@ class Project < ApplicationRecord
has_one :external_wiki_integration, class_name: 'Integrations::ExternalWiki'
has_one :flowdock_integration, class_name: 'Integrations::Flowdock'
has_one :hangouts_chat_integration, class_name: 'Integrations::HangoutsChat'
+ has_one :harbor_integration, class_name: 'Integrations::Harbor'
has_one :irker_integration, class_name: 'Integrations::Irker'
has_one :jenkins_integration, class_name: 'Integrations::Jenkins'
has_one :jira_integration, class_name: 'Integrations::Jira'
diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb
index bdbe7021276..6dfe9cc496b 100644
--- a/app/policies/ci/runner_policy.rb
+++ b/app/policies/ci/runner_policy.rb
@@ -9,6 +9,10 @@ module Ci
@user.owns_runner?(@subject)
end
+ condition(:belongs_to_multiple_projects) do
+ @subject.belongs_to_more_than_one_project?
+ end
+
rule { anonymous }.prevent_all
rule { admin }.policy do
@@ -22,6 +26,8 @@ module Ci
enable :delete_runner
end
+ rule { ~admin & belongs_to_multiple_projects }.prevent :delete_runner
+
rule { ~admin & locked }.prevent :assign_runner
end
end
diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb
index cc466e0ff81..82152ce42ae 100644
--- a/app/presenters/clusterable_presenter.rb
+++ b/app/presenters/clusterable_presenter.rb
@@ -32,6 +32,10 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
new_polymorphic_path([clusterable, :cluster], options)
end
+ def connect_path
+ polymorphic_path([clusterable, :clusters], action: :connect)
+ end
+
def authorize_aws_role_path
polymorphic_path([clusterable, :clusters], action: :authorize_aws_role)
end
diff --git a/app/presenters/instance_clusterable_presenter.rb b/app/presenters/instance_clusterable_presenter.rb
index f2550eb17e3..9e4a3b403ea 100644
--- a/app/presenters/instance_clusterable_presenter.rb
+++ b/app/presenters/instance_clusterable_presenter.rb
@@ -38,6 +38,11 @@ class InstanceClusterablePresenter < ClusterablePresenter
admin_cluster_path(cluster, params)
end
+ override :connect_path
+ def connect_path
+ connect_admin_clusters_path
+ end
+
override :create_user_clusters_path
def create_user_clusters_path
create_user_admin_clusters_path
diff --git a/app/presenters/project_clusterable_presenter.rb b/app/presenters/project_clusterable_presenter.rb
index 6c4d1143c0f..624fa1e0cb0 100644
--- a/app/presenters/project_clusterable_presenter.rb
+++ b/app/presenters/project_clusterable_presenter.rb
@@ -22,12 +22,12 @@ class ProjectClusterablePresenter < ClusterablePresenter
override :sidebar_text
def sidebar_text
- s_('ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way.')
+ s_('ClusterIntegration|Use GitLab to deploy to your cluster, run jobs, use review apps, and more.')
end
override :learn_more_link
def learn_more_link
- ApplicationController.helpers.link_to(s_('ClusterIntegration|Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
+ ApplicationController.helpers.link_to(s_('ClusterIntegration|Learn more about Kubernetes.'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
end
def metrics_dashboard_path(cluster)
diff --git a/app/views/clusters/clusters/_multiple_clusters_message.html.haml b/app/views/clusters/clusters/_multiple_clusters_message.html.haml
index ed95744c11d..04c1f9b6e7a 100644
--- a/app/views/clusters/clusters/_multiple_clusters_message.html.haml
+++ b/app/views/clusters/clusters/_multiple_clusters_message.html.haml
@@ -2,5 +2,5 @@
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe
- help_link_end = '</a>'.html_safe
-%p
- = s_('ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}.').html_safe % { help_link_start: help_link_start % { url: autodevops_help_url }, help_link_end: help_link_end }
+%p.gl-font-weight-bold
+ = s_('ClusterIntegration|Using AutoDevOps with multiple clusters? %{help_link_start}Read this first.%{help_link_end}').html_safe % { help_link_start: help_link_start % { url: autodevops_help_url }, help_link_end: help_link_end }
diff --git a/app/views/clusters/clusters/_sidebar.html.haml b/app/views/clusters/clusters/_sidebar.html.haml
index 31add011bfa..bda774ee780 100644
--- a/app/views/clusters/clusters/_sidebar.html.haml
+++ b/app/views/clusters/clusters/_sidebar.html.haml
@@ -1,5 +1,5 @@
-%h4.gl-mt-0
- = s_('ClusterIntegration|Add a Kubernetes cluster integration')
+%h3
+ = s_('ClusterIntegration|Connect a Kubernetes cluster')
%p
= clusterable.sidebar_text
%p
diff --git a/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml b/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml
index c10983a5405..826dc749dad 100644
--- a/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml
+++ b/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml
@@ -3,10 +3,10 @@
- logo_path = local_assigns.fetch(:logo_path)
- label = local_assigns.fetch(:label)
- last = local_assigns.fetch(:last, false)
-- classes = ["btn btn-light btn-outline flex-fill d-inline-flex flex-column justify-content-center align-items-center w-50 js-create-#{provider}-cluster-button"]
-- conditional_classes = [('mr-3' unless last), ('active' if is_current_provider)]
+- classes = ["btn btn-confirm gl-button btn-confirm-secondary gl-flex-direction-column gl-w-half js-create-#{provider}-cluster-button"]
+- conditional_classes = [('gl-mr-5' unless last), ('active' if is_current_provider)]
= link_to clusterable.new_path(provider: provider), class: classes + conditional_classes do
- .svg-content.p-2= image_tag logo_path, alt: label, class: 'gl-w-64 gl-h-64'
+ .svg-content.gl-p-3= image_tag logo_path, alt: label, class: 'gl-w-64 gl-h-64'
%span
= label
diff --git a/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml b/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml
index aee355bbf71..321fb854e0d 100644
--- a/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml
+++ b/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml
@@ -1,10 +1,10 @@
- gke_label = s_('ClusterIntegration|Google GKE')
- eks_label = s_('ClusterIntegration|Amazon EKS')
-- create_cluster_label = s_('ClusterIntegration|Create cluster on')
-.d-flex.flex-column.p-3
- %h4.mb-3
+- create_cluster_label = s_('ClusterIntegration|Where do you want to create a cluster?')
+.gl-p-5
+ %h4.gl-mb-5
= create_cluster_label
- .d-flex
+ .gl-display-flex
= render partial: 'clusters/clusters/cloud_providers/cloud_provider_button',
locals: { provider: 'aws', label: eks_label, logo_path: 'illustrations/logos/amazon_eks.svg' }
= render partial: 'clusters/clusters/cloud_providers/cloud_provider_button',
diff --git a/app/views/clusters/clusters/connect.html.haml b/app/views/clusters/clusters/connect.html.haml
new file mode 100644
index 00000000000..1043f78bd3c
--- /dev/null
+++ b/app/views/clusters/clusters/connect.html.haml
@@ -0,0 +1,11 @@
+- @content_class = 'limit-container-width' unless fluid_layout
+- add_to_breadcrumbs _('Kubernetes Clusters'), clusterable.index_path
+- breadcrumb_title _('Connect a cluster')
+- page_title _('Connect a Kubernetes Cluster')
+
+.row.gl-mt-3
+ .col-md-3
+ = render 'sidebar'
+ .col-md-9
+ #js-cluster-new{ data: js_cluster_new }
+ = render 'clusters/clusters/user/form'
diff --git a/app/views/clusters/clusters/gcp/_form.html.haml b/app/views/clusters/clusters/gcp/_form.html.haml
index c8105fd1152..58b8e8b1003 100644
--- a/app/views/clusters/clusters/gcp/_form.html.haml
+++ b/app/views/clusters/clusters/gcp/_form.html.haml
@@ -67,20 +67,20 @@
label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster.')
- = link_to _('More information'), help_page_path('user/project/clusters/add_gke_clusters.md', anchor: 'cloud-run-for-anthos'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('user/project/clusters/add_gke_clusters.md', anchor: 'cloud-run-for-anthos'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'),
label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Allow GitLab to manage namespaces and service accounts for this cluster.')
- = link_to _('More information'), help_page_path('user/project/clusters/gitlab_managed_clusters.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('user/project/clusters/gitlab_managed_clusters.md'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= field.check_box :namespace_per_environment, { label: s_('ClusterIntegration|Namespace per environment'), label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Deploy each environment to its own namespace. Otherwise, environments within a project share a project-wide namespace. Note that anyone who can trigger a deployment of a namespace can read its secrets. If modified, existing environments will use their current namespaces until the cluster cache is cleared.')
- = link_to _('More information'), help_page_path('user/project/clusters/deploy_to_cluster.md', anchor: 'custom-namespace'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('user/project/clusters/deploy_to_cluster.md', anchor: 'custom-namespace'), target: '_blank', rel: 'noopener noreferrer'
.form-group.js-gke-cluster-creation-submit-container
= field.submit s_('ClusterIntegration|Create Kubernetes cluster'),
diff --git a/app/views/clusters/clusters/new.html.haml b/app/views/clusters/clusters/new.html.haml
index 7af7a812338..a184f412565 100644
--- a/app/views/clusters/clusters/new.html.haml
+++ b/app/views/clusters/clusters/new.html.haml
@@ -1,9 +1,8 @@
-- breadcrumb_title _('Kubernetes')
-- page_title _('Kubernetes Cluster')
+- @content_class = 'limit-container-width' unless fluid_layout
+- add_to_breadcrumbs _('Kubernetes Clusters'), clusterable.index_path
+- breadcrumb_title _('Create a cluster')
+- page_title _('Create a Kubernetes cluster')
- provider = params[:provider]
-- active_tab = params[:tab] || local_assigns.fetch(:active_tab, 'create')
-- is_active_tab_create = active_tab === 'create'
-- is_active_tab_add = active_tab === 'add'
= render_gcp_signup_offer
@@ -11,21 +10,8 @@
.col-md-3
= render 'sidebar'
.col-md-9
- = gl_tabs_nav({ class: 'nav-justified' }) do
- = gl_tab_link_to clusterable.new_path(tab: 'create'), { item_active: is_active_tab_create } do
- %span= create_new_cluster_label(provider: params[:provider])
- = gl_tab_link_to s_('ClusterIntegration|Connect existing cluster'), clusterable.new_path(tab: 'add'), { item_active: is_active_tab_add, qa_selector: 'add_existing_cluster_tab' }
+ = render 'clusters/clusters/cloud_providers/cloud_provider_selector'
- .tab-content
- - if is_active_tab_create
- .tab-pane.active{ role: 'tabpanel' }
- = render 'clusters/clusters/cloud_providers/cloud_provider_selector'
-
- - if ['aws', 'gcp'].include?(provider)
- .p-3.border-top
- = render "clusters/clusters/#{provider}/new"
-
- - if is_active_tab_add
- .tab-pane.active.gl-p-5{ role: 'tabpanel' }
- #js-cluster-new{ data: js_cluster_new }
- = render 'clusters/clusters/user/form'
+ - if ['aws', 'gcp'].include?(provider)
+ .gl-p-5.gl-border-1.gl-border-t-solid.gl-border-gray-100
+ = render "clusters/clusters/#{provider}/new"
diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml
index 29af79cee5f..2dd15ebd266 100644
--- a/app/views/clusters/clusters/user/_form.html.haml
+++ b/app/views/clusters/clusters/user/_form.html.haml
@@ -1,6 +1,6 @@
-- more_info_link = link_to _('More information'), help_page_path('user/project/clusters/add_remove_clusters.md',
+- more_info_link = link_to _('Learn more.'), help_page_path('user/project/clusters/add_remove_clusters.md',
anchor: 'add-existing-cluster'), target: '_blank', rel: 'noopener noreferrer'
-- rbac_help_link = link_to _('More information'), help_page_path('user/project/clusters/add_remove_clusters.md',
+- rbac_help_link = link_to _('Learn more.'), help_page_path('user/project/clusters/add_remove_clusters.md',
anchor: 'access-controls'), target: '_blank', rel: 'noopener noreferrer'
- api_url_help_text = s_('ClusterIntegration|The URL used to access the Kubernetes API.')
@@ -47,13 +47,13 @@
label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Allow GitLab to manage namespaces and service accounts for this cluster.')
- = link_to _('More information'), help_page_path('user/project/clusters/gitlab_managed_clusters.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('user/project/clusters/gitlab_managed_clusters.md'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= field.check_box :namespace_per_environment, { label: s_('ClusterIntegration|Namespace per environment'), label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Deploy each environment to its own namespace. Otherwise, environments within a project share a project-wide namespace. Note that anyone who can trigger a deployment of a namespace can read its secrets. If modified, existing environments will use their current namespaces until the cluster cache is cleared.')
- = link_to _('More information'), help_page_path('user/project/clusters/deploy_to_cluster.md', anchor: 'custom-namespace'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('user/project/clusters/deploy_to_cluster.md', anchor: 'custom-namespace'), target: '_blank', rel: 'noopener noreferrer'
= field.fields_for :platform_kubernetes, @user_cluster.platform_kubernetes do |platform_kubernetes_field|
- if @user_cluster.allow_user_defined_namespace?
diff --git a/app/views/projects/issues/_service_desk_empty_state.html.haml b/app/views/projects/issues/_service_desk_empty_state.html.haml
index 3e0b80700fe..efc319ed8df 100644
--- a/app/views/projects/issues/_service_desk_empty_state.html.haml
+++ b/app/views/projects/issues/_service_desk_empty_state.html.haml
@@ -6,7 +6,7 @@
- if Gitlab::ServiceDesk.supported?
.empty-state
.svg-content
- = render 'shared/empty_states/icons/service_desk_empty_state.svg'
+ = render partial: 'shared/empty_states/icons/service_desk_empty_state', formats: :svg
.text-content
%h4= title_text
@@ -25,7 +25,7 @@
- else
.empty-state
.svg-content
- = render 'shared/empty_states/icons/service_desk_setup.svg'
+ = render partial: 'shared/empty_states/icons/service_desk_setup', formats: :svg
.text-content
- if can_edit_project_settings
%h4= s_('ServiceDesk|Service Desk is not supported')