diff options
Diffstat (limited to 'app')
39 files changed, 370 insertions, 255 deletions
diff --git a/app/assets/javascripts/behaviors/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js index 35874140bf9..b2571fb840c 100644 --- a/app/assets/javascripts/behaviors/preview_markdown.js +++ b/app/assets/javascripts/behaviors/preview_markdown.js @@ -36,6 +36,10 @@ MarkdownPreview.prototype.showPreview = function($form) { mdText = $form.find('textarea.markdown-area').val(); + if (mdText === undefined) { + return; + } + if (mdText.trim().length === 0) { preview.text(this.emptyMessage); this.hideReferencedUsers($form); diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js index 5202620057c..56a6cab6c73 100644 --- a/app/assets/javascripts/boards/services/board_service.js +++ b/app/assets/javascripts/boards/services/board_service.js @@ -1,4 +1,9 @@ /* eslint-disable class-methods-use-this */ +/** + * This file is intended to be deleted. + * The existing functions will removed one by one in favor of using the board store directly. + * see https://gitlab.com/gitlab-org/gitlab-ce/issues/61621 + */ import boardsStore from '~/boards/stores/boards_store'; diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 45543ef2cc8..2feb545199b 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -1,5 +1,12 @@ <script> -import { GlButton, GlDropdown, GlDropdownItem, GlModal, GlModalDirective } from '@gitlab/ui'; +import { + GlButton, + GlDropdown, + GlDropdownItem, + GlModal, + GlModalDirective, + GlTooltipDirective, +} from '@gitlab/ui'; import _ from 'underscore'; import { mapActions, mapState } from 'vuex'; import { s__ } from '~/locale'; @@ -30,7 +37,8 @@ export default { GlModal, }, directives: { - GlModalDirective, + GlModal: GlModalDirective, + GlTooltip: GlTooltipDirective, }, props: { externalDashboardUrl: { @@ -328,7 +336,7 @@ export default { <div class="d-flex"> <div v-if="addingMetricsAvailable"> <gl-button - v-gl-modal-directive="$options.addMetric.modalId" + v-gl-modal="$options.addMetric.modalId" class="js-add-metric-button text-success border-success" >{{ $options.addMetric.title }}</gl-button > @@ -395,14 +403,35 @@ export default { :project-path="projectPath" group-id="monitor-area-chart" > - <alert-widget - v-if="alertWidgetAvailable && graphData" - :alerts-endpoint="alertsEndpoint" - :relevant-queries="graphData.queries" - :alerts-to-manage="getGraphAlerts(graphData.queries)" - :modal-id="`alert-modal-${index}-${graphIndex}`" - @setAlerts="setAlerts" - /> + <div class="d-flex align-items-center"> + <alert-widget + v-if="alertWidgetAvailable && graphData" + :modal-id="`alert-modal-${index}-${graphIndex}`" + :alerts-endpoint="alertsEndpoint" + :relevant-queries="graphData.queries" + :alerts-to-manage="getGraphAlerts(graphData.queries)" + @setAlerts="setAlerts" + /> + <gl-dropdown + v-if="alertWidgetAvailable" + v-gl-tooltip + class="mx-2" + toggle-class="btn btn-transparent border-0" + :right="true" + :no-caret="true" + :title="__('More actions')" + > + <template slot="button-content"> + <icon name="ellipsis_v" class="text-secondary" /> + </template> + <gl-dropdown-item + v-if="alertWidgetAvailable" + v-gl-modal="`alert-modal-${index}-${graphIndex}`" + > + {{ __('Alerts') }} + </gl-dropdown-item> + </gl-dropdown> + </div> </monitor-area-chart> </template> </graph-group> diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue index f1f02964a29..295c0851f12 100644 --- a/app/assets/javascripts/monitoring/components/panel_type.vue +++ b/app/assets/javascripts/monitoring/components/panel_type.vue @@ -1,6 +1,7 @@ <script> import { mapState } from 'vuex'; import _ from 'underscore'; +import { GlDropdown, GlDropdownItem, GlModal, GlModalDirective } from '@gitlab/ui'; import MonitorAreaChart from './charts/area.vue'; import MonitorSingleStatChart from './charts/single_stat.vue'; import MonitorEmptyChart from './charts/empty_chart.vue'; @@ -10,6 +11,12 @@ export default { MonitorAreaChart, MonitorSingleStatChart, MonitorEmptyChart, + GlDropdown, + GlDropdownItem, + GlModal, + }, + directives: { + GlModal: GlModalDirective, }, props: { graphData: { @@ -64,14 +71,32 @@ export default { :container-width="dashboardWidth" group-id="monitor-area-chart" > - <alert-widget - v-if="alertWidgetAvailable" - :alerts-endpoint="alertsEndpoint" - :relevant-queries="graphData.queries" - :alerts-to-manage="getGraphAlerts(graphData.queries)" - :modal-id="`alert-modal-${index}`" - @setAlerts="setAlerts" - /> + <div class="d-flex align-items-center"> + <alert-widget + v-if="alertWidgetAvailable && graphData" + :modal-id="`alert-modal-${index}`" + :alerts-endpoint="alertsEndpoint" + :relevant-queries="graphData.queries" + :alerts-to-manage="getGraphAlerts(graphData.queries)" + @setAlerts="setAlerts" + /> + <gl-dropdown + v-if="alertWidgetAvailable" + v-gl-tooltip + class="mx-2" + toggle-class="btn btn-transparent border-0" + :right="true" + :no-caret="true" + :title="__('More actions')" + > + <template slot="button-content"> + <icon name="ellipsis_v" class="text-secondary" /> + </template> + <gl-dropdown-item v-if="alertWidgetAvailable" v-gl-modal="`alert-modal-${index}`"> + {{ __('Alerts') }} + </gl-dropdown-item> + </gl-dropdown> + </div> </monitor-area-chart> <monitor-empty-chart v-else :graph-title="graphData.title" /> </template> diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue index c0ea42ad1a2..13aa8844172 100644 --- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue +++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue @@ -112,12 +112,6 @@ export default { :header="metric.header" :keys="metric.keys" /> - <div id="peek-view-gc" class="view"> - <span v-if="currentRequest.details" class="bold"> - <span title="Invoke Time">{{ currentRequest.details.gc.gc_time }}</span - >ms / <span title="Invoke Count">{{ currentRequest.details.gc.invokes }}</span> gc - </span> - </div> <div v-if="currentRequest.details && currentRequest.details.tracing" id="peek-view-trace" diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue index 244d332f38f..4b2d816c6a0 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -1,9 +1,11 @@ <script> import { GlButton, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui'; -import { s__, sprintf } from '~/locale'; +import axios from '~/lib/utils/axios_utils'; +import flash from '~/flash'; +import { s__, __, sprintf } from '~/locale'; import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; +import Icon from '~/vue_shared/components/icon.vue'; import eventHub from '../event_hub'; -import Icon from '../../vue_shared/components/icon.vue'; export default { directives: { @@ -44,7 +46,24 @@ export default { this.isLoading = true; - eventHub.$emit('postAction', action.path); + /** + * Ideally, the component would not make an api call directly. + * However, in order to use the eventhub and know when to + * toggle back the `isLoading` property we'd need an ID + * to track the request with a wacther - since this component + * is rendered at least 20 times in the same page, moving the + * api call directly here is the most performant solution + */ + axios + .post(`${action.path}.json`) + .then(() => { + this.isLoading = false; + eventHub.$emit('updateTable'); + }) + .catch(() => { + this.isLoading = false; + flash(__('An error occurred while making the request.')); + }); }, isActionDisabled(action) { diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js index a6243366375..126a9a47a2b 100644 --- a/app/assets/javascripts/pipelines/mixins/pipelines.js +++ b/app/assets/javascripts/pipelines/mixins/pipelines.js @@ -60,12 +60,14 @@ export default { eventHub.$on('postAction', this.postAction); eventHub.$on('retryPipeline', this.postAction); eventHub.$on('clickedDropdown', this.updateTable); + eventHub.$on('updateTable', this.updateTable); eventHub.$on('refreshPipelinesTable', this.fetchPipelines); }, beforeDestroy() { eventHub.$off('postAction', this.postAction); eventHub.$off('retryPipeline', this.postAction); eventHub.$off('clickedDropdown', this.updateTable); + eventHub.$off('updateTable', this.updateTable); eventHub.$off('refreshPipelinesTable', this.fetchPipelines); }, destroyed() { diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue index ae9b013d980..f7c508c4e23 100644 --- a/app/assets/javascripts/vue_shared/components/commit.vue +++ b/app/assets/javascripts/vue_shared/components/commit.vue @@ -152,37 +152,35 @@ export default { :href="mergeRequestRef.path" :title="mergeRequestRef.title" class="ref-name" + >{{ mergeRequestRef.iid }}</gl-link > - {{ mergeRequestRef.iid }} - </gl-link> <gl-link v-else v-gl-tooltip :href="commitRef.ref_url" :title="commitRef.name" class="ref-name" + >{{ commitRef.name }}</gl-link > - {{ commitRef.name }} - </gl-link> </template> <icon name="commit" class="commit-icon js-commit-icon" /> - <gl-link :href="commitUrl" class="commit-sha mr-0"> {{ shortSha }} </gl-link> + <gl-link :href="commitUrl" class="commit-sha mr-0">{{ shortSha }}</gl-link> - <div class="commit-title flex-truncate-parent"> - <tooltip-on-truncate v-if="title" class="flex-truncate-child" :title="title"> + <div class="commit-title"> + <span v-if="title" class="flex-truncate-parent"> <user-avatar-link v-if="hasAuthor" :link-href="author.path" :img-src="author.avatar_url" :img-alt="userImageAltDescription" :tooltip-text="author.username" - class="avatar-image-container" + class="avatar-image-container text-decoration-none" /> - <gl-link :href="commitUrl" class="commit-row-message cgray"> - {{ title }} - </gl-link> - </tooltip-on-truncate> + <tooltip-on-truncate :title="title" class="flex-truncate-child"> + <gl-link :href="commitUrl" class="commit-row-message cgray">{{ title }}</gl-link> + </tooltip-on-truncate> + </span> <span v-else>{{ __("Can't find HEAD commit for this branch") }}</span> </div> </div> diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue index fc6a45b957e..6a4a834337a 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue @@ -27,7 +27,6 @@ export default { return { width: 0, height: 0, - isLoaded: false, }; }, computed: { @@ -63,8 +62,6 @@ export default { this.height = contentImg.naturalHeight; this.$nextTick(() => { - this.isLoaded = true; - this.$emit('imgLoaded', { width: this.width, height: this.height, diff --git a/app/assets/stylesheets/framework/responsive_tables.scss b/app/assets/stylesheets/framework/responsive_tables.scss index 6bd44ee19bd..fd6f80e26cb 100644 --- a/app/assets/stylesheets/framework/responsive_tables.scss +++ b/app/assets/stylesheets/framework/responsive_tables.scss @@ -155,7 +155,7 @@ text-overflow: ellipsis; @include media-breakpoint-up(md) { - flex: 0 0 90%; + flex: 0 0 85%; } .avatar { diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 21b3949e361..3489ea78b77 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -194,10 +194,9 @@ module IssuableCollections end def collection_type - @collection_type ||= case finder_type.name - when 'IssuesFinder' + @collection_type ||= if finder_type <= IssuesFinder 'Issue' - when 'MergeRequestsFinder' + elsif finder_type <= MergeRequestsFinder 'MergeRequest' end end diff --git a/app/finders/clusters/knative_services_finder.rb b/app/finders/clusters/knative_services_finder.rb index 7d3b53ef663..71cebe4495e 100644 --- a/app/finders/clusters/knative_services_finder.rb +++ b/app/finders/clusters/knative_services_finder.rb @@ -13,11 +13,11 @@ module Clusters self.reactive_cache_key = ->(finder) { finder.model_name } self.reactive_cache_worker_finder = ->(_id, *cache_args) { from_cache(*cache_args) } - attr_reader :cluster, :project + attr_reader :cluster, :environment - def initialize(cluster, project) + def initialize(cluster, environment) @cluster = cluster - @project = project + @environment = environment end def with_reactive_cache_memoized(*cache_args, &block) @@ -30,11 +30,11 @@ module Clusters clear_reactive_cache!(*cache_args) end - def self.from_cache(cluster_id, project_id) + def self.from_cache(cluster_id, environment_id) cluster = Clusters::Cluster.find(cluster_id) - project = ::Project.find(project_id) + environment = Environment.find(environment_id) - new(cluster, project) + new(cluster, environment) end def calculate_reactive_cache(*) @@ -56,7 +56,7 @@ module Clusters end def cache_args - [cluster.id, project.id] + [cluster.id, environment.id] end def service_pod_details(service) @@ -84,7 +84,7 @@ module Clusters private def search_namespace - @search_namespace ||= cluster.kubernetes_namespace_for(project) + @search_namespace ||= cluster.kubernetes_namespace_for(environment) end def knative_client diff --git a/app/finders/clusters/kubernetes_namespace_finder.rb b/app/finders/clusters/kubernetes_namespace_finder.rb new file mode 100644 index 00000000000..e947796c1e7 --- /dev/null +++ b/app/finders/clusters/kubernetes_namespace_finder.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Clusters + class KubernetesNamespaceFinder + attr_reader :cluster, :project, :environment_slug + + def initialize(cluster, project:, environment_slug:, allow_blank_token: false) + @cluster = cluster + @project = project + @environment_slug = environment_slug + @allow_blank_token = allow_blank_token + end + + def execute + find_namespace(with_environment: cluster.namespace_per_environment?) + end + + private + + attr_reader :allow_blank_token + + def find_namespace(with_environment:) + relation = with_environment ? namespaces.with_environment_slug(environment_slug) : namespaces + + relation.find_by_project_id(project.id) + end + + def namespaces + if allow_blank_token + cluster.kubernetes_namespaces + else + cluster.kubernetes_namespaces.has_service_account_token + end + end + end +end diff --git a/app/finders/projects/serverless/functions_finder.rb b/app/finders/projects/serverless/functions_finder.rb index ebe50806ca1..e8c50ef1a88 100644 --- a/app/finders/projects/serverless/functions_finder.rb +++ b/app/finders/projects/serverless/functions_finder.rb @@ -3,10 +3,11 @@ module Projects module Serverless class FunctionsFinder + include Gitlab::Utils::StrongMemoize + attr_reader :project def initialize(project) - @clusters = project.clusters @project = project end @@ -16,9 +17,8 @@ module Projects # Possible return values: Clusters::KnativeServicesFinder::KNATIVE_STATE def knative_installed - states = @clusters.map do |cluster| - cluster.application_knative - cluster.knative_services_finder(project).knative_detected.tap do |state| + states = services_finders.map do |finder| + finder.knative_detected.tap do |state| return state if state == ::Clusters::KnativeServicesFinder::KNATIVE_STATES['checking'] # rubocop:disable Cop/AvoidReturnFromBlocks end end @@ -31,66 +31,70 @@ module Projects end def invocation_metrics(environment_scope, name) - return unless prometheus_adapter&.can_query? + environment = finders_for_scope(environment_scope).first&.environment - cluster = @clusters.find do |c| - environment_scope == c.environment_scope + if environment.present? && environment.prometheus_adapter&.can_query? + func = ::Serverless::Function.new(project, name, environment.deployment_namespace) + environment.prometheus_adapter.query(:knative_invocation, func) end - - func = ::Serverless::Function.new(project, name, cluster.kubernetes_namespace_for(project)) - prometheus_adapter.query(:knative_invocation, func) end def has_prometheus?(environment_scope) - @clusters.any? do |cluster| - environment_scope == cluster.environment_scope && cluster.application_prometheus_available? + finders_for_scope(environment_scope).any? do |finder| + finder.cluster.application_prometheus_available? end end private def knative_service(environment_scope, name) - @clusters.map do |cluster| - next if environment_scope != cluster.environment_scope - - services = cluster - .knative_services_finder(project) + finders_for_scope(environment_scope).map do |finder| + services = finder .services .select { |svc| svc["metadata"]["name"] == name } - add_metadata(cluster, services).first unless services.nil? + add_metadata(finder, services).first unless services.nil? end end def knative_services - @clusters.map do |cluster| - services = cluster - .knative_services_finder(project) - .services + services_finders.map do |finder| + services = finder.services - add_metadata(cluster, services) unless services.nil? + add_metadata(finder, services) unless services.nil? end end - def add_metadata(cluster, services) + def add_metadata(finder, services) + add_pod_count = services.one? + services.each do |s| - s["environment_scope"] = cluster.environment_scope - s["cluster_id"] = cluster.id + s["environment_scope"] = finder.cluster.environment_scope + s["cluster_id"] = finder.cluster.id - if services.length == 1 - s["podcount"] = cluster - .knative_services_finder(project) + if add_pod_count + s["podcount"] = finder .service_pod_details(s["metadata"]["name"]) .length end end end - # rubocop: disable CodeReuse/ServiceClass - def prometheus_adapter - @prometheus_adapter ||= ::Prometheus::AdapterService.new(project).prometheus_adapter + def services_finders + strong_memoize(:services_finders) do + available_environments.map(&:knative_services_finder).compact + end + end + + def available_environments + @project.environments.available.preload_cluster + end + + def finders_for_scope(environment_scope) + services_finders.select do |finder| + environment_scope == finder.cluster.environment_scope + end end - # rubocop: enable CodeReuse/ServiceClass end end end diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb index 5eb535cab58..08e52f32bb3 100644 --- a/app/models/clusters/applications/prometheus.rb +++ b/app/models/clusters/applications/prometheus.rb @@ -83,7 +83,7 @@ module Clusters # ensures headers containing auth data are appended to original k8s client options options = kube_client.rest_client.options.merge(headers: kube_client.headers) - RestClient::Resource.new(proxy_url, options) + Gitlab::PrometheusClient.new(proxy_url, options) rescue Kubeclient::HttpError # If users have mistakenly set parameters or removed the depended clusters, # `proxy_url` could raise an exception because gitlab can not communicate with the cluster. diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 8bb44b0ce40..97d39491b73 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -53,6 +53,7 @@ module Clusters validates :name, cluster_name: true validates :cluster_type, presence: true validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true } + validates :namespace_per_environment, inclusion: { in: [true, false] } validate :restrict_modification, on: :update validate :no_groups, unless: :group_type? @@ -100,16 +101,6 @@ module Clusters scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) } - scope :with_knative_installed, -> { joins(:application_knative).merge(Clusters::Applications::Knative.available) } - - scope :preload_knative, -> { - preload( - :kubernetes_namespaces, - :platform_kubernetes, - :application_knative - ) - } - def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc) return [] if clusterable.is_a?(Instance) @@ -177,36 +168,15 @@ module Clusters platform_kubernetes.kubeclient if kubernetes? end - ## - # This is subtly different to #find_or_initialize_kubernetes_namespace_for_project - # below because it will ignore any namespaces that have not got a service account - # token. This provides a guarantee that any namespace selected here can be used - # for cluster operations - a namespace needs to have a service account configured - # before it it can be used. - # - # This is used for selecting a namespace to use when querying a cluster, or - # generating variables to pass to CI. - def kubernetes_namespace_for(project) - find_or_initialize_kubernetes_namespace_for_project( - project, scope: kubernetes_namespaces.has_service_account_token - ).namespace - end - - ## - # This is subtly different to #kubernetes_namespace_for because it will include - # namespaces that have yet to receive a service account token. This allows - # the namespace configuration process to be repeatable - if a namespace has - # already been created without a token we don't need to create another - # record entirely, just set the token on the pre-existing namespace. - # - # This is used for configuring cluster namespaces. - def find_or_initialize_kubernetes_namespace_for_project(project, scope: kubernetes_namespaces) - attributes = { project: project } - attributes[:cluster_project] = cluster_project if project_type? + def kubernetes_namespace_for(environment) + project = environment.project + persisted_namespace = Clusters::KubernetesNamespaceFinder.new( + self, + project: project, + environment_slug: environment.slug + ).execute - scope.find_or_initialize_by(attributes).tap do |namespace| - namespace.set_defaults - end + persisted_namespace&.namespace || Gitlab::Kubernetes::DefaultNamespace.new(self, project: project).from_environment_slug(environment.slug) end def allow_user_defined_namespace? @@ -225,10 +195,6 @@ module Clusters end end - def knative_services_finder(project) - @knative_services_finder ||= KnativeServicesFinder.new(self, project) - end - private def instance_domain diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb index b0c4900546e..69a2b99fcb6 100644 --- a/app/models/clusters/kubernetes_namespace.rb +++ b/app/models/clusters/kubernetes_namespace.rb @@ -9,12 +9,12 @@ module Clusters belongs_to :cluster_project, class_name: 'Clusters::Project' belongs_to :cluster, class_name: 'Clusters::Cluster' belongs_to :project, class_name: '::Project' + belongs_to :environment, optional: true has_one :platform_kubernetes, through: :cluster - before_validation :set_defaults - validates :namespace, presence: true validates :namespace, uniqueness: { scope: :cluster_id } + validates :environment_id, uniqueness: { scope: [:cluster_id, :project_id] }, allow_nil: true validates :service_account_name, presence: true @@ -27,6 +27,7 @@ module Clusters algorithm: 'aes-256-cbc' scope :has_service_account_token, -> { where.not(encrypted_service_account_token: nil) } + scope :with_environment_slug, -> (slug) { joins(:environment).where(environments: { slug: slug }) } def token_name "#{namespace}-token" @@ -42,34 +43,8 @@ module Clusters end end - def set_defaults - self.namespace ||= default_platform_kubernetes_namespace - self.namespace ||= default_project_namespace - self.service_account_name ||= default_service_account_name - end - private - def default_service_account_name - return unless namespace - - "#{namespace}-service-account" - end - - def default_platform_kubernetes_namespace - platform_kubernetes&.namespace.presence - end - - def default_project_namespace - Gitlab::NamespaceSanitizer.sanitize(project_slug) if project_slug - end - - def project_slug - return unless project - - "#{project.path}-#{project.id}".downcase - end - def kubeconfig to_kubeconfig( url: api_url, diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 9296c28776b..37614fbe3ca 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -51,11 +51,6 @@ module Clusters delegate :provided_by_user?, to: :cluster, allow_nil: true delegate :allow_user_defined_namespace?, to: :cluster, allow_nil: true - # This is just to maintain compatibility with KubernetesService, which - # will be removed in https://gitlab.com/gitlab-org/gitlab-ce/issues/39217. - # It can be removed once KubernetesService is gone. - delegate :kubernetes_namespace_for, to: :cluster, allow_nil: true - alias_method :active?, :enabled? enum_with_nil authorization_type: { @@ -66,7 +61,7 @@ module Clusters default_value_for :authorization_type, :rbac - def predefined_variables(project:) + def predefined_variables(project:, environment_name:) Gitlab::Ci::Variables::Collection.new.tap do |variables| variables.append(key: 'KUBE_URL', value: api_url) @@ -77,15 +72,14 @@ module Clusters end if !cluster.managed? - project_namespace = namespace.presence || "#{project.path}-#{project.id}".downcase + namespace = Gitlab::Kubernetes::DefaultNamespace.new(cluster, project: project).from_environment_name(environment_name) variables - .append(key: 'KUBE_URL', value: api_url) .append(key: 'KUBE_TOKEN', value: token, public: false, masked: true) - .append(key: 'KUBE_NAMESPACE', value: project_namespace) - .append(key: 'KUBECONFIG', value: kubeconfig(project_namespace), public: false, file: true) + .append(key: 'KUBE_NAMESPACE', value: namespace) + .append(key: 'KUBECONFIG', value: kubeconfig(namespace), public: false, file: true) - elsif kubernetes_namespace = cluster.kubernetes_namespaces.has_service_account_token.find_by(project: project) + elsif kubernetes_namespace = find_persisted_namespace(project, environment_name: environment_name) variables.concat(kubernetes_namespace.predefined_variables) end @@ -111,6 +105,22 @@ module Clusters private + ## + # Environment slug can be predicted given an environment + # name, so even if the environment isn't persisted yet we + # still know what to look for. + def environment_slug(name) + Gitlab::Slug::Environment.new(name).generate + end + + def find_persisted_namespace(project, environment_name:) + Clusters::KubernetesNamespaceFinder.new( + cluster, + project: project, + environment_slug: environment_slug(environment_name) + ).execute + end + def kubeconfig(namespace) to_kubeconfig( url: api_url, diff --git a/app/models/concerns/prometheus_adapter.rb b/app/models/concerns/prometheus_adapter.rb index c2542dbe743..9ac4722c6b1 100644 --- a/app/models/concerns/prometheus_adapter.rb +++ b/app/models/concerns/prometheus_adapter.rb @@ -14,10 +14,6 @@ module PrometheusAdapter raise NotImplementedError end - def prometheus_client_wrapper - Gitlab::PrometheusClient.new(prometheus_client) - end - def can_query? prometheus_client.present? end @@ -35,7 +31,7 @@ module PrometheusAdapter def calculate_reactive_cache(query_class_name, *args) return unless prometheus_client - data = Object.const_get(query_class_name, false).new(prometheus_client_wrapper).query(*args) + data = Object.const_get(query_class_name, false).new(prometheus_client).query(*args) { success: true, data: data, diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index db501b4b506..0bd90bd28e3 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -2,12 +2,14 @@ class DeployKey < Key include IgnorableColumn + include FromUnion has_many :deploy_keys_projects, inverse_of: :deploy_key, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, through: :deploy_keys_projects scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) } scope :are_public, -> { where(public: true) } + scope :with_projects, -> { includes(deploy_keys_projects: { project: [:route, :namespace] }) } ignore_column :can_push @@ -22,7 +24,7 @@ class DeployKey < Key end def almost_orphaned? - self.deploy_keys_projects.length == 1 + self.deploy_keys_projects.count == 1 end def destroyed_when_orphaned? @@ -46,6 +48,6 @@ class DeployKey < Key end def projects_with_write_access - Project.preload(:route).where(id: deploy_keys_projects.with_write_access.select(:project_id)) + Project.with_route.where(id: deploy_keys_projects.with_write_access.select(:project_id)) end end diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb index 15906ed8e06..40c66d5bc4c 100644 --- a/app/models/deploy_keys_project.rb +++ b/app/models/deploy_keys_project.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true class DeployKeysProject < ApplicationRecord - belongs_to :project + belongs_to :project, inverse_of: :deploy_keys_projects belongs_to :deploy_key, inverse_of: :deploy_keys_projects - scope :without_project_deleted, -> { joins(:project).where(projects: { pending_delete: false }) } scope :in_project, ->(project) { where(project: project) } scope :with_write_access, -> { where(can_push: true) } diff --git a/app/models/environment.rb b/app/models/environment.rb index 513427ac2c5..1b53c4b45f9 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -48,6 +48,7 @@ class Environment < ApplicationRecord end scope :in_review_folder, -> { where(environment_type: "review") } scope :for_name, -> (name) { where(name: name) } + scope :preload_cluster, -> { preload(last_deployment: :cluster) } ## # Search environments which have names like the given query. @@ -170,7 +171,7 @@ class Environment < ApplicationRecord def deployment_namespace strong_memoize(:kubernetes_namespace) do - deployment_platform&.kubernetes_namespace_for(project) + deployment_platform.cluster.kubernetes_namespace_for(self) if deployment_platform end end @@ -233,6 +234,12 @@ class Environment < ApplicationRecord end end + def knative_services_finder + if last_deployment&.cluster + Clusters::KnativeServicesFinder.new(last_deployment.cluster, self) + end + end + private def generate_slug diff --git a/app/models/list.rb b/app/models/list.rb index d28a9bda82d..ccadd39bda2 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -3,10 +3,11 @@ class List < ApplicationRecord belongs_to :board belongs_to :label + include Importable enum list_type: { backlog: 0, label: 1, closed: 2, assignee: 3, milestone: 4 } - validates :board, :list_type, presence: true + validates :board, :list_type, presence: true, unless: :importing? validates :label, :position, presence: true, if: :label? validates :label_id, uniqueness: { scope: :board_id }, if: :label? validates :position, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, if: :movable? diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index f45bd0e03de..2c9dbf2585c 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -196,6 +196,12 @@ class MergeRequestDiff < ApplicationRecord real_size.presence || raw_diffs.size end + def lines_count + strong_memoize(:lines_count) do + diffs.diff_files.sum(&:line_count) + end + end + def raw_diffs(options = {}) if options[:ignore_whitespace_change] @diffs_no_whitespace ||= compare.diffs(options) diff --git a/app/models/project.rb b/app/models/project.rb index 8f234fba04f..960795b73cb 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -214,7 +214,7 @@ class Project < ApplicationRecord as: :source, class_name: 'ProjectMember', dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent has_many :members_and_requesters, as: :source, class_name: 'ProjectMember' - has_many :deploy_keys_projects + has_many :deploy_keys_projects, inverse_of: :project has_many :deploy_keys, through: :deploy_keys_projects has_many :users_star_projects has_many :starrers, through: :users_star_projects, source: :user @@ -1487,6 +1487,9 @@ class Project < ApplicationRecord end def pipeline_for(ref, sha = nil, id = nil) + sha ||= commit(ref).try(:sha) + return unless sha + if id.present? pipelines_for(ref, sha).find_by(id: id) else @@ -1494,11 +1497,7 @@ class Project < ApplicationRecord end end - def pipelines_for(ref, sha = nil) - sha ||= commit(ref).try(:sha) - - return unless sha - + def pipelines_for(ref, sha) ci_pipelines.order(id: :desc).where(sha: sha, ref: ref) end @@ -1856,8 +1855,12 @@ class Project < ApplicationRecord end end - def deployment_variables(environment: nil) - deployment_platform(environment: environment)&.predefined_variables(project: self) || [] + def deployment_variables(environment:) + platform = deployment_platform(environment: environment) + + return [] unless platform.present? + + platform.predefined_variables(project: self, environment_name: environment) end def auto_devops_variables diff --git a/app/models/project_services/mock_deployment_service.rb b/app/models/project_services/mock_deployment_service.rb index 1103cb11e73..6f2b0f7747f 100644 --- a/app/models/project_services/mock_deployment_service.rb +++ b/app/models/project_services/mock_deployment_service.rb @@ -24,7 +24,7 @@ class MockDeploymentService < Service %w() end - def predefined_variables(project:) + def predefined_variables(project:, environment_name:) [] end diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb index c68a9d923c8..6eff2ea2e3a 100644 --- a/app/models/project_services/prometheus_service.rb +++ b/app/models/project_services/prometheus_service.rb @@ -63,15 +63,16 @@ class PrometheusService < MonitoringService # Check we can connect to the Prometheus API def test(*args) - Gitlab::PrometheusClient.new(prometheus_client).ping - + prometheus_client.ping { success: true, result: 'Checked API endpoint' } rescue Gitlab::PrometheusClient::Error => err { success: false, result: err } end def prometheus_client - RestClient::Resource.new(api_url, max_redirects: 0) if should_return_client? + return unless should_return_client? + + Gitlab::PrometheusClient.new(api_url) end def prometheus_available? @@ -84,7 +85,7 @@ class PrometheusService < MonitoringService private def should_return_client? - api_url && manual_configuration? && active? && valid? + api_url.present? && manual_configuration? && active? && valid? end def synchronize_service_state diff --git a/app/models/user.rb b/app/models/user.rb index b439d1c0c16..4630552e02e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -933,7 +933,7 @@ class User < ApplicationRecord end def project_deploy_keys - DeployKey.unscoped.in_projects(authorized_projects.pluck(:id)).distinct(:id) + DeployKey.in_projects(authorized_projects.select(:id)).distinct(:id) end def highest_role @@ -941,11 +941,10 @@ class User < ApplicationRecord end def accessible_deploy_keys - @accessible_deploy_keys ||= begin - key_ids = project_deploy_keys.pluck(:id) - key_ids.push(*DeployKey.are_public.pluck(:id)) - DeployKey.where(id: key_ids) - end + DeployKey.from_union([ + DeployKey.where(id: project_deploy_keys.select(:deploy_key_id)), + DeployKey.are_public + ]) end def created_by diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb index 2cf3278d240..f85c1a237a6 100644 --- a/app/presenters/blob_presenter.rb +++ b/app/presenters/blob_presenter.rb @@ -3,12 +3,13 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated presents :blob - def highlight(plain: nil) + def highlight(since: nil, to: nil, plain: nil) load_all_blob_data Gitlab::Highlight.highlight( blob.path, - blob.data, + limited_blob_data(since: since, to: to), + since: since, language: blob.language_from_gitattributes, plain: plain ) @@ -23,4 +24,18 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated def load_all_blob_data blob.load_all_data! if blob.respond_to?(:load_all_data!) end + + def limited_blob_data(since: nil, to: nil) + return blob.data if since.blank? || to.blank? + + limited_blob_lines(since, to).join + end + + def limited_blob_lines(since, to) + all_lines[since - 1..to - 1] + end + + def all_lines + @all_lines ||= blob.data.lines + end end diff --git a/app/presenters/blobs/unfold_presenter.rb b/app/presenters/blobs/unfold_presenter.rb index 21a1e1309e0..f4672d22fc9 100644 --- a/app/presenters/blobs/unfold_presenter.rb +++ b/app/presenters/blobs/unfold_presenter.rb @@ -21,20 +21,19 @@ module Blobs load_all_blob_data @subject = blob - @all_lines = blob.data.lines super(params) if full? - self.attributes = { since: 1, to: @all_lines.size, bottom: false, unfold: false, offset: 0, indent: 0 } + self.attributes = { since: 1, to: all_lines.size, bottom: false, unfold: false, offset: 0, indent: 0 } end end # Returns an array of Gitlab::Diff::Line with match line added def diff_lines - diff_lines = lines.map.with_index do |line, index| - full_line = limited_blob_lines[index].delete("\n") + diff_lines = limited_blob_lines(since, to).map.with_index do |line, index| + full_line = line.delete("\n") - Gitlab::Diff::Line.new(full_line, nil, nil, nil, nil, rich_text: line) + Gitlab::Diff::Line.new(full_line, nil, nil, nil, nil, rich_text: lines[index]) end add_match_line(diff_lines) @@ -43,7 +42,7 @@ module Blobs end def lines - @lines ||= limit(highlight.lines).map(&:html_safe) + @lines ||= highlight(since: since, to: to).lines.map(&:html_safe) end def match_line_text @@ -59,7 +58,7 @@ module Blobs def add_match_line(diff_lines) return unless unfold? - if bottom? && to < @all_lines.size + if bottom? && to < all_lines.size old_pos = to - offset new_pos = to elsif since != 1 @@ -73,15 +72,5 @@ module Blobs bottom? ? diff_lines.push(match_line) : diff_lines.unshift(match_line) end - - def limited_blob_lines - @limited_blob_lines ||= limit(@all_lines) - end - - def limit(lines) - return lines if full? - - lines[since - 1..to - 1] - end end end diff --git a/app/presenters/projects/settings/deploy_keys_presenter.rb b/app/presenters/projects/settings/deploy_keys_presenter.rb index 85518c9a3a4..6f8c4e1f902 100644 --- a/app/presenters/projects/settings/deploy_keys_presenter.rb +++ b/app/presenters/projects/settings/deploy_keys_presenter.rb @@ -12,48 +12,38 @@ module Projects @key ||= DeployKey.new.tap { |dk| dk.deploy_keys_projects.build } end - # rubocop: disable CodeReuse/ActiveRecord def enabled_keys - @enabled_keys ||= project.deploy_keys.includes(:projects) - end - # rubocop: enable CodeReuse/ActiveRecord - - def any_keys_enabled? - enabled_keys.any? + project.deploy_keys end def available_keys - @available_keys ||= current_user.accessible_deploy_keys - enabled_keys + current_user + .accessible_deploy_keys + .id_not_in(enabled_keys.select(:id)) + .with_projects end - # rubocop: disable CodeReuse/ActiveRecord def available_project_keys - @available_project_keys ||= current_user.project_deploy_keys.includes(:projects) - enabled_keys - end - # rubocop: enable CodeReuse/ActiveRecord - - def key_available?(deploy_key) - available_keys.include?(deploy_key) + current_user + .project_deploy_keys + .id_not_in(enabled_keys.select(:id)) + .with_projects end - # rubocop: disable CodeReuse/ActiveRecord def available_public_keys - return @available_public_keys if defined?(@available_public_keys) - - @available_public_keys ||= DeployKey.are_public.includes(:projects) - enabled_keys - - # Public keys that are already used by another accessible project are already - # in @available_project_keys. - @available_public_keys -= available_project_keys + DeployKey + .are_public + .id_not_in(enabled_keys.select(:id)) + .id_not_in(available_project_keys.select(:id)) + .with_projects end - # rubocop: enable CodeReuse/ActiveRecord def as_json serializer = DeployKeySerializer.new # rubocop: disable CodeReuse/Serializer opts = { user: current_user } { - enabled_keys: serializer.represent(enabled_keys, opts), + enabled_keys: serializer.represent(enabled_keys.with_projects, opts), available_project_keys: serializer.represent(available_project_keys, opts), public_keys: serializer.represent(available_public_keys, opts) } diff --git a/app/serializers/deploy_key_entity.rb b/app/serializers/deploy_key_entity.rb index 54bf030aba1..e47d6454780 100644 --- a/app/serializers/deploy_key_entity.rb +++ b/app/serializers/deploy_key_entity.rb @@ -10,9 +10,10 @@ class DeployKeyEntity < Grape::Entity expose :created_at expose :updated_at expose :deploy_keys_projects, using: DeployKeysProjectEntity do |deploy_key| - deploy_key.deploy_keys_projects - .without_project_deleted - .select { |deploy_key_project| Ability.allowed?(options[:user], :read_project, deploy_key_project.project) } + deploy_key.deploy_keys_projects.select do |deploy_key_project| + !deploy_key_project.project&.pending_delete? && + Ability.allowed?(options[:user], :read_project, deploy_key_project.project) + end end expose :can_edit diff --git a/app/services/clusters/build_kubernetes_namespace_service.rb b/app/services/clusters/build_kubernetes_namespace_service.rb new file mode 100644 index 00000000000..2574f77bbf9 --- /dev/null +++ b/app/services/clusters/build_kubernetes_namespace_service.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Clusters + class BuildKubernetesNamespaceService + attr_reader :cluster, :environment + + def initialize(cluster, environment:) + @cluster = cluster + @environment = environment + end + + def execute + cluster.kubernetes_namespaces.build(attributes) + end + + private + + def attributes + attributes = { + project: environment.project, + namespace: namespace, + service_account_name: "#{namespace}-service-account" + } + + attributes[:cluster_project] = cluster.cluster_project if cluster.project_type? + attributes[:environment] = environment if cluster.namespace_per_environment? + + attributes + end + + def namespace + Gitlab::Kubernetes::DefaultNamespace.new(cluster, project: environment.project).from_environment_slug(environment.slug) + end + end +end diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb index 5fb5e15c32d..e5a5b73321a 100644 --- a/app/services/clusters/create_service.rb +++ b/app/services/clusters/create_service.rb @@ -11,7 +11,8 @@ module Clusters def execute(access_token: nil) raise ArgumentError, 'Unknown clusterable provided' unless clusterable - cluster_params = params.merge(user: current_user).merge(clusterable_params) + cluster_params = params.merge(global_params).merge(clusterable_params) + cluster_params[:provider_gcp_attributes].try do |provider| provider[:access_token] = access_token end @@ -35,6 +36,10 @@ module Clusters @clusterable ||= params.delete(:clusterable) end + def global_params + { user: current_user, namespace_per_environment: Feature.enabled?(:kubernetes_namespace_per_environment, default_enabled: true) } + end + def clusterable_params case clusterable when ::Project diff --git a/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb b/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb index 806f320381d..c45dac7b273 100644 --- a/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb +++ b/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb @@ -11,7 +11,6 @@ module Clusters end def execute - configure_kubernetes_namespace create_project_service_account configure_kubernetes_token @@ -22,10 +21,6 @@ module Clusters attr_reader :cluster, :kubernetes_namespace, :platform - def configure_kubernetes_namespace - kubernetes_namespace.set_defaults - end - def create_project_service_account Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService.namespace_creator( platform.kubeclient, diff --git a/app/services/prometheus/proxy_service.rb b/app/services/prometheus/proxy_service.rb index c5d2b84878b..a62eb76b8ce 100644 --- a/app/services/prometheus/proxy_service.rb +++ b/app/services/prometheus/proxy_service.rb @@ -98,7 +98,7 @@ module Prometheus end def prometheus_client_wrapper - prometheus_adapter&.prometheus_client_wrapper + prometheus_adapter&.prometheus_client end def can_query? diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index cbe713b7468..ff0c5b241b2 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -32,6 +32,8 @@ = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets qa-snippets-link' do = _('Snippets') + = render_if_exists 'layouts/nav/sidebar/analytics_link' + - if any_dashboard_nav_link?([:groups, :milestones, :activity, :snippets]) %li.header-more.dropdown.d-xl-none{ class: ('d-lg-none' unless has_extra_nav_icons?) } %a{ href: "#", data: { toggle: "dropdown" } } @@ -53,6 +55,9 @@ = nav_link(controller: 'dashboard/snippets') do = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets' do = _('Snippets') + + = render_if_exists 'layouts/nav/sidebar/analytics_more_link' + %li.dropdown.d-lg-none = render_if_exists 'dashboard/operations/nav_link_list' - if can?(current_user, :read_instance_statistics) diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml index 1056977886a..e42772c2dd9 100644 --- a/app/views/projects/_export.html.haml +++ b/app/views/projects/_export.html.haml @@ -15,6 +15,7 @@ %li= _('Project configuration, including services') %li= _('Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities') %li= _('LFS objects') + %li= _('Issue Boards') %p= _('The following items will NOT be exported:') %ul %li= _('Job traces and artifacts') diff --git a/app/workers/chaos/kill_worker.rb b/app/workers/chaos/kill_worker.rb index bbad53c9b86..80f04db1be4 100644 --- a/app/workers/chaos/kill_worker.rb +++ b/app/workers/chaos/kill_worker.rb @@ -5,6 +5,8 @@ module Chaos include ApplicationWorker include ChaosQueue + sidekiq_options retry: false + def perform Gitlab::Chaos.kill end |