Commit 8f778ed5 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 3b8b1417 8753c663
......@@ -141,6 +141,9 @@ export default {
isInstalling() {
return this.status === APPLICATION_STATUS.INSTALLING;
},
isExternallyInstalled() {
return this.status === APPLICATION_STATUS.EXTERNALLY_INSTALLED;
},
canInstall() {
return (
this.status === APPLICATION_STATUS.NOT_INSTALLABLE ||
......@@ -193,10 +196,17 @@ export default {
label = __('Installing');
} else if (this.installed) {
label = __('Installed');
} else if (this.isExternallyInstalled) {
label = __('Externally installed');
}
return label;
},
buttonGridCellClass() {
return this.showManageButton || this.status === APPLICATION_STATUS.EXTERNALLY_INSTALLED
? 'section-25'
: 'section-15';
},
showManageButton() {
return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED;
},
......@@ -427,8 +437,7 @@ export default {
</div>
</div>
<div
:class="{ 'section-25': showManageButton, 'section-15': !showManageButton }"
class="table-section table-button-footer section-align-top"
:class="[buttonGridCellClass, 'table-section', 'table-button-footer', 'section-align-top']"
role="gridcell"
>
<div v-if="showManageButton" class="btn-group table-action-buttons">
......
......@@ -26,6 +26,7 @@ export const APPLICATION_STATUS = {
ERROR: 'errored',
PRE_INSTALLED: 'pre_installed',
UNINSTALLED: 'uninstalled',
EXTERNALLY_INSTALLED: 'externally_installed',
};
/*
......
import Vue from 'vue';
import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import IntegrationForm from '../components/integration_form.vue';
import { createStore } from '../stores';
export default () => {
dirtySubmitFactory(document.querySelectorAll('.js-cluster-integrations-form'));
const entryPoint = document.querySelector('#js-cluster-details-form');
if (!entryPoint) {
......
......@@ -15,6 +15,7 @@ const {
UNINSTALL_ERRORED,
PRE_INSTALLED,
UNINSTALLED,
EXTERNALLY_INSTALLED,
} = APPLICATION_STATUS;
const applicationStateMachine = {
......@@ -71,6 +72,9 @@ const applicationStateMachine = {
[UNINSTALLED]: {
target: UNINSTALLED,
},
[EXTERNALLY_INSTALLED]: {
target: EXTERNALLY_INSTALLED,
},
},
},
[NOT_INSTALLABLE]: {
......
# frozen_string_literal: true
class Admin::Clusters::IntegrationsController < Clusters::IntegrationsController
include EnforcesAdminAuthentication
private
def clusterable
@clusterable ||= InstanceClusterablePresenter.fabricate(Clusters::Instance.new, current_user: current_user)
end
end
......@@ -60,6 +60,9 @@ class Clusters::ClustersController < Clusters::BaseController
end
def show
if params[:tab] == 'integrations'
@prometheus_integration = Clusters::IntegrationPresenter.new(@cluster.find_or_build_application(Clusters::Applications::Prometheus))
end
end
def update
......
# frozen_string_literal: true
module Clusters
class IntegrationsController < ::Clusters::BaseController
before_action :cluster
before_action :authorize_admin_cluster!, only: [:create_or_update]
def create_or_update
service_response = Clusters::Integrations::CreateService
.new(container: clusterable, cluster: cluster, current_user: current_user, params: cluster_integration_params)
.execute
if service_response.success?
redirect_to cluster.show_path(params: { tab: 'integrations' }), notice: service_response.message
else
redirect_to cluster.show_path(params: { tab: 'integrations' }), alert: service_response.message
end
end
private
def clusterable
raise NotImplementedError
end
def cluster_integration_params
params.require(:integration).permit(:application_type, :enabled)
end
def cluster
@cluster ||= clusterable.clusters.find(params[:cluster_id]).present(current_user: current_user)
end
end
end
# frozen_string_literal: true
class Groups::Clusters::IntegrationsController < Clusters::IntegrationsController
include ControllerWithCrossProjectAccessCheck
prepend_before_action :group
requires_cross_project_access
private
def clusterable
@clusterable ||= ClusterablePresenter.fabricate(group, current_user: current_user)
end
def group
@group ||= find_routable!(Group, params[:group_id] || params[:id])
end
end
# frozen_string_literal: true
class Projects::Clusters::IntegrationsController < ::Clusters::IntegrationsController
prepend_before_action :project
private
def clusterable
@clusterable ||= ClusterablePresenter.fabricate(project, current_user: current_user)
end
def project
@project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]))
end
end
......@@ -71,6 +71,8 @@ module ClustersHelper
render_if_exists 'clusters/clusters/health'
when 'apps'
render 'applications'
when 'integrations'
render 'integrations'
when 'settings'
render 'advanced_settings_container'
else
......
......@@ -32,7 +32,7 @@ module Clusters
end
state_machine :status do
after_transition any => [:installed] do |application|
after_transition any => [:installed, :externally_installed] do |application|
application.run_after_commit do
Clusters::Applications::ActivateServiceWorker
.perform_async(application.cluster_id, ::PrometheusService.to_param) # rubocop:disable CodeReuse/ServiceClass
......
......@@ -9,6 +9,7 @@ module Clusters
scope :available, -> do
where(
status: [
self.state_machines[:status].states[:externally_installed].value,
self.state_machines[:status].states[:installed].value,
self.state_machines[:status].states[:updated].value
]
......@@ -28,6 +29,7 @@ module Clusters
state :uninstalling, value: 7
state :uninstall_errored, value: 8
state :uninstalled, value: 10
state :externally_installed, value: 11
# Used for applications that are pre-installed by the cluster,
# e.g. Knative in GCP Cloud Run enabled clusters
......@@ -37,7 +39,7 @@ module Clusters
state :pre_installed, value: 9
event :make_externally_installed do
transition any => :installed
transition any => :externally_installed
end
event :make_externally_uninstalled do
......@@ -79,7 +81,7 @@ module Clusters
transition [:scheduled] => :uninstalling
end
before_transition any => [:scheduled, :installed, :uninstalled] do |application, _|
before_transition any => [:scheduled, :installed, :uninstalled, :externally_installed] do |application, _|
application.status_reason = nil
end
......@@ -114,7 +116,7 @@ module Clusters
end
def available?
pre_installed? || installed? || updated?
pre_installed? || installed? || externally_installed? || updated?
end
def update_in_progress?
......
......@@ -437,7 +437,7 @@ module Issuable
end
def subscribed_without_subscriptions?(user, project)
participants(user).include?(user)
participant?(user)
end
def can_assign_epic?(user)
......
......@@ -56,18 +56,34 @@ module Participable
# This method processes attributes of objects in breadth-first order.
#
# Returns an Array of User instances.
def participants(current_user = nil)
all_participants[current_user]
def participants(user = nil)
filtered_participants_hash[user]
end
# Checks if the user is a participant in a discussion.
#
# This method processes attributes of objects in breadth-first order.
#
# Returns a Boolean.
def participant?(user)
can_read_participable?(user) &&
all_participants_hash[user].include?(user)
end
private
def all_participants
@all_participants ||= Hash.new do |hash, user|
def all_participants_hash
@all_participants_hash ||= Hash.new do |hash, user|
hash[user] = raw_participants(user)
end
end
def filtered_participants_hash
@filtered_participants_hash ||= Hash.new do |hash, user|
hash[user] = filter_by_ability(all_participants_hash[user])
end
end
def raw_participants(current_user = nil)
current_user ||= author
ext = Gitlab::ReferenceExtractor.new(project, current_user)
......@@ -98,8 +114,6 @@ module Participable
end
participants.merge(ext.users)
filter_by_ability(participants)
end
def filter_by_ability(participants)
......@@ -110,6 +124,15 @@ module Participable
Ability.users_that_can_read_project(participants.to_a, project)
end
end
def can_read_participable?(participant)
case self
when PersonalSnippet
participant.can?(:read_snippet, self)
else
participant.can?(:read_project, project)
end
end
end
Participable.prepend_if_ee('EE::Participable')
......@@ -49,13 +49,25 @@ module Clusters
end
end
def show_path
def show_path(params: {})
if cluster.project_type?
project_cluster_path(project, cluster)
project_cluster_path(project, cluster, params)
elsif cluster.group_type?
group_cluster_path(group, cluster)
group_cluster_path(group, cluster, params)
elsif cluster.instance_type?
admin_cluster_path(cluster)
admin_cluster_path(cluster, params)
else
raise NotImplementedError
end
end
def integrations_path
if cluster.project_type?
create_or_update_project_cluster_integration_path(project, cluster)
elsif cluster.group_type?
create_or_update_group_cluster_integration_path(group, cluster)
elsif cluster.instance_type?
create_or_update_admin_cluster_integration_path(cluster)
else
raise NotImplementedError
end
......
# frozen_string_literal: true
module Clusters
class IntegrationPresenter < Gitlab::View::Presenter::Delegated
presents :integration
def integration?
integration.new_record? || integration.externally_installed? || integration.uninstalled?
end
def application_type
integration.class.application_name
end
def enabled
integration.externally_installed?
end
end
end
# frozen_string_literal: true
module Clusters
module Integrations
class CreateService < BaseContainerService
InvalidApplicationError = Class.new(StandardError)
attr_accessor :cluster
def initialize(container:, cluster:, current_user: nil, params: {})
@cluster = cluster
super(container: container, current_user: current_user, params: params)
end
def execute
return ServiceResponse.error(message: 'Unauthorized') unless authorized?
application_class = Clusters::Cluster::APPLICATIONS[params[:application_type]]
application = cluster.find_or_build_application(application_class)
if params[:enabled]
application.make_externally_installed!
ServiceResponse.success(message: s_('ClusterIntegration|Integration enabled'), payload: { application: application })
else
application.make_externally_uninstalled!
ServiceResponse.success(message: s_('ClusterIntegration|Integration disabled'), payload: { application: application })
end
end
private
def authorized?
Ability.allowed?(current_user, :admin_cluster, cluster)
end
end
end
end
.settings.expanded.border-0.m-0
%p
= s_('ClusterIntegration|Integrations enable you to integrate your cluster as part of your GitLab workflow.')
= link_to _('Learn more'), help_page_path('user/clusters/integrations.md'), target: '_blank'
.settings-content#advanced-settings-section
- if can?(current_user, :admin_cluster, @cluster) && @prometheus_integration.integration?
.sub-section.form-group
= form_for @prometheus_integration, url: @cluster.integrations_path, as: :integration, method: :post, html: { class: 'js-cluster-integrations-form' } do |form|
= form.hidden_field :application_type
.form-group
.gl-form-checkbox.custom-control.custom-checkbox
= form.check_box :enabled, { class: 'custom-control-input'}, true, false
= form.label :enabled, s_('ClusterIntegration|Enable Prometheus integration'), class: 'custom-control-label'
.gl-form-group
.form-text.text-gl-muted
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path("user/clusters/integrations", anchor: "prometheus-cluster-integration") }
- link_end = '</a>'.html_safe
= html_escape(s_('ClusterIntegration|Before you enable this integration, follow the %{link_start}documented process%{link_end}.')) % { link_start: link_start, link_end: link_end }
= form.submit _('Save changes'), class: 'btn gl-button btn-success'
- tab_name = 'integrations'
- active = params[:tab] == tab_name
%li.nav-item{ role: 'presentation' }
%a#cluster-apps-tab.nav-link{ class: active_when(active), href: clusterable.cluster_path(@cluster.id, params: {tab: tab_name}) }
%span= _('Integrations')
......@@ -59,6 +59,7 @@
= render_if_exists 'clusters/clusters/environments_tab'
= render 'clusters/clusters/health_tab'
= render 'applications_tab'
= render 'integrations_tab'
= render 'advanced_settings_tab'
.tab-content.py-3
......
---
title: Add spent quick action alias
merge_request: 58539
author: Lee Tickett @leetickett
type: added
---
title: Ability to add Prometheus as cluster integration
merge_request: 55244
author:
type: added
---
title: Check access only for requesting user when checking if subscribed
merge_request: 57201
author:
type: performance
......@@ -220,6 +220,12 @@ Rails.application.routes.draw do
post :authorize_aws_role
end
resource :integration, controller: 'clusters/integrations', only: [] do
collection do
post :create_or_update
end
end
member do
Gitlab.ee do
get :metrics, format: :json
......
......@@ -1001,8 +1001,8 @@ Logs produced by pods running **GitLab Managed Apps** can be browsed using
## Install with one click
WARNING:
The one click installation method is scheduled for removal in GitLab 14.0. The removal
of this feature from GitLab does not affect installed applications to avoid breaking
The one-click installation method is deprecated and scheduled for removal in [GitLab 14.0](https://gitlab.com/groups/gitlab-org/-/epics/4280).
This removal does not affect installed applications to avoid breaking
changes. Following GitLab 14.0, users can take ownership of already installed applications
using our documentation.
......
---
stage: Configure
group: Configure
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Cluster integrations **(FREE)**
GitLab provides several ways to integrate applications to your
Kubernetes cluster.
To enable cluster integrations, first add a Kubernetes cluster to a GitLab
[project](../project/clusters/add_remove_clusters.md) or [group](../group/clusters/index.md#group-level-kubernetes-clusters).
## Prometheus cluster integration
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55244) in GitLab 13.11.
You can integrate your Kubernetes cluster with
[Prometheus](https://prometheus.io/) for monitoring key metrics of your
apps directly from the GitLab UI.
Once enabled, you will see metrics from services available in the
[metrics library](../project/integrations/prometheus_library/index.md).
Prerequisites:
To benefit from this integration, you must have Prometheus
installed in your cluster with the following requirements:
1. Prometheus must be installed inside the `gitlab-managed-apps` namespace.
1. The `Service` resource for Prometheus must be named `prometheus-prometheus-server`.
You can use the following commands to install Prometheus to meet the requirements for cluster integrations:
```shell
# Create the require Kubernetes namespace
kubectl create ns gitlab-managed-apps
# Download Helm chart values that is compatible with the requirements above.
# You should substitute the tag that corresponds to the GitLab version in the url
# - https://gitlab.com/gitlab-org/gitlab/-/raw/<tag>/vendor/prometheus/values.yaml
#
wget https://gitlab.com/gitlab-org/gitlab/-/raw/v13.9.0-ee/vendor/prometheus/values.yaml
# Add the Prometheus community helm repo
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# Install Prometheus
helm install prometheus prometheus-community/prometheus -n gitlab-managed-apps --values values.yaml
```
Alternatively, you can use your preferred installation method to install
Prometheus as long as you meet the requirements above.
### Enable Prometheus integration for your cluster
To enable the Prometheus integration for your cluster:
1. Go to the cluster's page:
- For a [project-level cluster](../project/clusters/index.md), navigate to your project's
**Operations > Kubernetes**.
- For a [group-level cluster](../group/clusters/index.md), navigate to your group's
**Kubernetes** page.
1. Select the **Integrations** tab.
1. Check the **Enable Prometheus integration** checkbox.
1. Click **Save changes**.
1. Go to the **Health** tab to see your cluster's metrics.
......@@ -31,6 +31,10 @@ Once enabled, GitLab detects metrics from known services in the
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/28916) in GitLab 10.5.
**Deprecated:** Managed Prometheus on Kubernetes is deprecated, and
scheduled for removal in [GitLab
14.0](https://gitlab.com/groups/gitlab-org/-/epics/4280).
GitLab can seamlessly deploy and manage Prometheus on a
[connected Kubernetes cluster](../clusters/index.md), to help you monitor your apps.
......
......@@ -10,5 +10,12 @@ module EE
Ability.users_that_can_read_group(participants.to_a, self.group)
end
override :can_read_participable?
def can_read_participable?(participant)
return super unless self.is_a?(Epic)
participant.can?(:read_group, group)
end
end
end
......@@ -115,25 +115,6 @@ module EE
issues.preload(project: [:route, { namespace: [:route] }])
end
# override
def subscribed_without_subscriptions?(user, *)
# TODO: this really shouldn't be necessary, because the support
# bot should be a participant (which is what the superclass
# method checks for). However, the support bot gets filtered out
# at the end of Participable#raw_participants as not being able
# to read the project. Overriding *that* behavior is problematic
# because it doesn't use the Policy framework, and instead uses a
# custom-coded Ability.users_that_can_read_project, which is...
# a pain to override in EE. So... here we say, the support bot
# is subscribed by default, until an unsubscribed record appears,
# even though it's not *technically* a participant in this issue.
# Making the support bot subscribed to every issue is not as bad as it
# seems, though, since it isn't permitted to :receive_notifications,
# and doesn't actually show up in the participants list.
user.bot? || super
end
# override
def weight
super if weight_available?
......
......@@ -112,13 +112,13 @@ RSpec.describe Security::FindingsFinder do
subject { finder_result.total_pages }
context 'when the per_page is not provided' do
it { is_expected.to be(2) }
it { is_expected.to be(1) }
end
context 'when the per_page is provided' do
let(:per_page) { 100 }
let(:per_page) { 3 }
it { is_expected.to be(1) }
it { is_expected.to be(3) }
end
end
......@@ -126,13 +126,13 @@ RSpec.describe Security::FindingsFinder do
subject { finder_result.total_count }
context 'when the scope is not provided' do
it { is_expected.to be(35) }
it { is_expected.to be(8) }
end
context 'when the scope is provided as `all`' do
let(:scope) { 'all' }
it { is_expected.to be(36) }
it { is_expected.to be(8) }
end
end
......@@ -140,6 +140,9 @@ RSpec.describe Security::FindingsFinder do
subject { finder_result.next_page }
context 'when the page is not provided' do
# Limit per_page to force pagination on smaller dataset
let(:per_page) { 2 }
it { is_expected.to be(2) }
end
......@@ -159,6 +162,8 @@ RSpec.describe Security::FindingsFinder do
context 'when the page is provided' do
let(:page) { 2 }
# Limit per_page to force pagination on smaller dataset
let(:per_page) { 2 }
it { is_expected.to be(1) }
end
......@@ -172,24 +177,12 @@ RSpec.describe Security::FindingsFinder do
%w[
4ae096451135db224b9e16818baaca8096896522
0bfcfbb70b15a7cecef9a1ea39df15ecfd88949f
117590fc6b3841014366f335f494d1aa36ce7b46
8fac98c156431a8bdb7a69a935cc564c314ab776
95566733fc91301623055363a77124410592af7e
0314c9673160662292cfab1af6dc5c880fb73717
4e44f4045e2a27d147d08895acf8df502f440f96
b5f82291ed084fe134af5a9b90a8078ab802a6cc
98366a28fa80b23a1dafe2b36e239a04909495c4
157f362acf654c60e224400f59a088e1c01b369f
b9c0d1cdc7cb9c180ebb6981abbddc2df0172509
cefacf9f36c487d04f33c59f22e6c402bff5300a
d533c3a12403b6c6033a50b53f9c73f894a40fc6
92c7bdc63a9908bddbc5b66c95e93e99a1927879
dd482eab94e695ae85c1a883c4dbe4c74a7e6b2c
be6f6e4fb5bdfd8819e70d930b32798b38a361e0
f603dd8517800823df02a8f1e5621b56c00710d8
21b17b6ced16fe507dd5b71bca24f0515d04fb7e
f1dde46676cd2a8e48f0837e5dae77087419b09c
fec8863c5c1b4ed58eddf7722a9f1598af3aca70
e325e114daf41074d41d1ebe1869158c4f7594dc
baf3e36cda35331daed7a3e80155533d552844fa
3204893d5894c74aaee86ce5bc28427f9f14e512
98366a28fa80b23a1dafe2b36e239a04909495c4
9a644ee1b89ac29d6175dc1170914f47b0531635
]
end
......@@ -198,23 +191,12 @@ RSpec.describe Security::FindingsFinder do
context 'when the page is provided' do
let(:page) { 2 }
# Limit per_page to force pagination on smaller dataset
let(:per_page) { 2 }
let(:expected_fingerprints) do
%w[
51026f8933c463b316c5bc33adb462e4a6f6cff2
45cb4c0323b0b4a1adcb66fa1d0684d53e15cc27
48f71ab14afcf0f497fb238dc4289294b93873b0
18fe6882cdac0f3eac7784a33c9daf20109010ce
2cae57e97785a8aef9ae4ed947093d6a908bcc52
857969b55ba97d5e1c06ab920b470b009c2f3274
e3b452f63d8979e6f3e4839c6ec14b62917758e4
63dfc168b8c01a446088c9b8cf68a7d4a2a0013b
7b0792ce8db4e2cb74083490e6a87176accea102
30ab265fb9e816976b740beb0557ca79e8653bb6
81a3b7c4885e64f9013ac904bf118a05bcb7732d
ecd3b645971fc2682f5cb23d938037c6f072207f
55c41a63d2c9c3ea243b9f9cd3254d68fbee2b6b
3204893d5894c74aaee86ce5bc28427f9f14e512
157f362acf654c60e224400f59a088e1c01b369f
0bfcfbb70b15a7cecef9a1ea39df15ecfd88949f
baf3e36cda35331daed7a3e80155533d552844fa
]
end
......@@ -222,44 +204,10 @@ RSpec.describe Security::FindingsFinder do
end
context 'when the per_page is provided' do
let(:per_page) { 40 }
let(:per_page) { 1 }
let(:expected_fingerprints) do
%w[
3204893d5894c74aaee86ce5bc28427f9f14e512
157f362acf654c60e224400f59a088e1c01b369f
4ae096451135db224b9e16818baaca8096896522
d533c3a12403b6c6033a50b53f9c73f894a40fc6
b9c0d1cdc7cb9c180ebb6981abbddc2df0172509
98366a28fa80b23a1dafe2b36e239a04909495c4
b5f82291ed084fe134af5a9b90a8078ab802a6cc
4e44f4045e2a27d147d08895acf8df502f440f96
8fac98c156431a8bdb7a69a935cc564c314ab776
95566733fc91301623055363a77124410592af7e
0314c9673160662292cfab1af6dc5c880fb73717
117590fc6b3841014366f335f494d1aa36ce7b46
0bfcfbb70b15a7cecef9a1ea39df15ecfd88949f
92c7bdc63a9908bddbc5b66c95e93e99a1927879
cefacf9f36c487d04f33c59f22e6c402bff5300a
dd482eab94e695ae85c1a883c4dbe4c74a7e6b2c
48f71ab14afcf0f497fb238dc4289294b93873b0
45cb4c0323b0b4a1adcb66fa1d0684d53e15cc27
e3b452f63d8979e6f3e4839c6ec14b62917758e4
857969b55ba97d5e1c06ab920b470b009c2f3274
63dfc168b8c01a446088c9b8cf68a7d4a2a0013b
7b0792ce8db4e2cb74083490e6a87176accea102
2cae57e97785a8aef9ae4ed947093d6a908bcc52
18fe6882cdac0f3eac7784a33c9daf20109010ce
e325e114daf41074d41d1ebe1869158c4f7594dc
51026f8933c463b316c5bc33adb462e4a6f6cff2
fec8863c5c1b4ed58eddf7722a9f1598af3aca70
f1dde46676cd2a8e48f0837e5dae77087419b09c
21b17b6ced16fe507dd5b71bca24f0515d04fb7e
be6f6e4fb5bdfd8819e70d930b32798b38a361e0
f603dd8517800823df02a8f1e5621b56c00710d8
30ab265fb9e816976b740beb0557ca79e8653bb6
81a3b7c4885e64f9013ac904bf118a05bcb7732d
55c41a63d2c9c3ea243b9f9cd3254d68fbee2b6b
ecd3b645971fc2682f5cb23d938037c6f072207f
]
end
......@@ -270,18 +218,10 @@ RSpec.describe Security::FindingsFinder do
let(:severity_levels) { [:medium] }
let(:expected_fingerprints) do
%w[
b5f82291ed084fe134af5a9b90a8078ab802a6cc
4e44f4045e2a27d147d08895acf8df502f440f96
8fac98c156431a8bdb7a69a935cc564c314ab776
95566733fc91301623055363a77124410592af7e
0314c9673160662292cfab1af6dc5c880fb73717
117590fc6b3841014366f335f494d1aa36ce7b46
0bfcfbb70b15a7cecef9a1ea39df15ecfd88949f
d533c3a12403b6c6033a50b53f9c73f894a40fc6
9a644ee1b89ac29d6175dc1170914f47b0531635
b9c0d1cdc7cb9c180ebb6981abbddc2df0172509
98366a28fa80b23a1dafe2b36e239a04909495c4
92c7bdc63a9908bddbc5b66c95e93e99a1927879
cefacf9f36c487d04f33c59f22e6c402bff5300a
baf3e36cda35331daed7a3e80155533d552844fa
]
end
......@@ -292,10 +232,7 @@ RSpec.describe Security::FindingsFinder do
let(:confidence_levels) { [:low] }
let(:expected_fingerprints) do
%w[
30ab265fb9e816976b740beb0557ca79e8653bb6
81a3b7c4885e64f9013ac904bf118a05bcb7732d
55c41a63d2c9c3ea243b9f9cd3254d68fbee2b6b
ecd3b645971fc2682f5cb23d938037c6f072207f
98366a28fa80b23a1dafe2b36e239a04909495c4
]
end
......@@ -321,25 +258,13 @@ RSpec.describe Security::FindingsFinder do
let(:expected_fingerprints) do
%w[
4ae096451135db224b9e16818baaca8096896522
157f362acf654c60e224400f59a088e1c01b369f
baf3e36cda35331daed7a3e80155533d552844fa
0bfcfbb70b15a7cecef9a1ea39df15ecfd88949f
117590fc6b3841014366f335f494d1aa36ce7b46
8fac98c156431a8bdb7a69a935cc564c314ab776
95566733fc91301623055363a77124410592af7e
0314c9673160662292cfab1af6dc5c880fb73717
4e44f4045e2a27d147d08895acf8df502f440f96
b5f82291ed084fe134af5a9b90a8078ab802a6cc
98366a28fa80b23a1dafe2b36e239a04909495c4
b9c0d1cdc7cb9c180ebb6981abbddc2df0172509
cefacf9f36c487d04f33c59f22e6c402bff5300a
d533c3a12403b6c6033a50b53f9c73f894a40fc6
92c7bdc63a9908bddbc5b66c95e93e99a1927879
dd482eab94e695ae85c1a883c4dbe4c74a7e6b2c
be6f6e4fb5bdfd8819e70d930b32798b38a361e0
f603dd8517800823df02a8f1e5621b56c00710d8
db759283b7fb13eae48a3f60db4c7506cdab8f26
21b17b6ced16fe507dd5b71bca24f0515d04fb7e
f1dde46676cd2a8e48f0837e5dae77087419b09c
fec8863c5c1b4ed58eddf7722a9f1598af3aca70
3204893d5894c74aaee86ce5bc28427f9f14e512
9a644ee1b89ac29d6175dc1170914f47b0531635
]
end
......
......@@ -262,7 +262,7 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do
subject { described_class.new(pipeline: pipeline).execute }
it 'returns all vulnerabilities with all scanners available' do
expect(subject.findings.map(&:scanner).map(&:external_id).uniq).to match_array %w[bandit bundler_audit find_sec_bugs flawfinder gemnasium klar zaproxy]
expect(subject.findings.map(&:scanner).map(&:external_id).uniq).to match_array %w[bundler_audit find_sec_bugs gemnasium klar zaproxy]
end
end
......@@ -277,11 +277,11 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do
context 'by all filters' do
context 'with found entity' do
let(:params) { { report_type: %w[sast dast container_scanning dependency_scanning], scanner: %w[bandit bundler_audit find_sec_bugs flawfinder gemnasium klar zaproxy], scope: 'all' } }
let(:params) { { report_type: %w[sast dast container_scanning dependency_scanning], scanner: %w[bundler_audit find_sec_bugs gemnasium klar zaproxy], scope: 'all' } }
it 'filters by all params' do
expect(subject.findings.count).to eq(cs_count + dast_count + ds_count + sast_count)
expect(subject.findings.map(&:scanner).map(&:external_id).uniq).to match_array %w[bandit bundler_audit find_sec_bugs flawfinder gemnasium klar zaproxy]
expect(subject.findings.map(&:scanner).map(&:external_id).uniq).to match_array %w[bundler_audit find_sec_bugs gemnasium klar zaproxy]
expect(subject.findings.map(&:confidence).uniq).to match_array(%w[unknown low medium high])
expect(subject.findings.map(&:severity).uniq).to match_array(%w[unknown low medium high critical info])
end
......@@ -326,7 +326,7 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do
let(:confirmed_fingerprint) do
Digest::SHA1.hexdigest(
'python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108')
'groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY')
end
let(:resolved_fingerprint) do
......
......@@ -11,9 +11,9 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do
let(:created_at) { 2.weeks.ago }
context "when parsing valid reports" do
where(:report_format, :scanner_length) do
:sast | 4
:sast_deprecated | 3
where(:report_format, :report_version, :scanner_length, :finding_length, :identifier_length, :file_path, :line) do
:sast | '14.0.0' | 1 | 5 | 6 | 'groovy/src/main/java/com/gitlab/security_products/tests/App.groovy' | 47
:sast_deprecated | '1.2' | 3 | 33 | 17 | 'python/hardcoded/hardcoded-tmp.py' | 1
end
with_them do
......@@ -25,8 +25,8 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do
end
it "parses all identifiers and findings" do
expect(report.findings.length).to eq(33)
expect(report.identifiers.length).to eq(17)
expect(report.findings.length).to eq(finding_length)
expect(report.identifiers.length).to eq(identifier_length)
expect(report.scanners.length).to eq(scanner_length)
end
......@@ -35,16 +35,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do
expect(location).to be_a(::Gitlab::Ci::Reports::Security::Locations::Sast)
expect(location).to have_attributes(
file_path: 'python/hardcoded/hardcoded-tmp.py',
start_line: 1,
end_line: 1,
class_name: nil,
method_name: nil
file_path: file_path,
end_line: line,
start_line: line
)
end
it "generates expected metadata_version" do
expect(report.findings.first.metadata_version).to eq('1.2')
expect(report.findings.first.metadata_version).to eq(report_version)
end
end
end
......
......@@ -226,7 +226,7 @@ RSpec.describe Ci::Build do
it 'parses blobs and add the results to the report' do
subject
expect(security_reports.get_report('sast', artifact).findings.size).to eq(33)
expect(security_reports.get_report('sast', artifact).findings.size).to eq(5)
end
it 'adds the created date to the report' do
......@@ -245,7 +245,7 @@ RSpec.describe Ci::Build do
it 'parses blobs and adds the results to the reports' do
subject
expect(security_reports.get_report('sast', sast_artifact).findings.size).to eq(33)
expect(security_reports.get_report('sast', sast_artifact).findings.size).to eq(5)
expect(security_reports.get_report('dependency_scanning', ds_artifact).findings.size).to eq(4)
expect(security_reports.get_report('container_scanning', cs_artifact).findings.size).to eq(8)
expect(security_reports.get_report('dast', dast_artifact).findings.size).to eq(20)
......
......@@ -137,7 +137,7 @@ RSpec.describe Ci::Pipeline do
expect(subject.reports.keys).to contain_exactly('sast', 'dependency_scanning', 'container_scanning')
# for each of report categories, we have merged 2 reports with the same data (fixture)
expect(subject.get_report('sast', sast1_artifact).findings.size).to eq(33)
expect(subject.get_report('sast', sast1_artifact).findings.size).to eq(5)
expect(subject.get_report('dependency_scanning', ds1_artifact).findings.size).to eq(4)
expect(subject.get_report('container_scanning', cs1_artifact).findings.size).to eq(8)
end
......@@ -146,7 +146,7 @@ RSpec.describe Ci::Pipeline do
let(:build_sast_1) { create(:ci_build, :retried, name: 'sast_1', pipeline: pipeline, project: project) }
it 'does not take retried builds into account' do
expect(subject.get_report('sast', sast1_artifact).findings.size).to eq(33)
expect(subject.get_report('sast', sast1_artifact).findings.size).to eq(5)
expect(subject.get_report('dependency_scanning', ds1_artifact).findings.size).to eq(4)
expect(subject.get_report('container_scanning', cs1_artifact).findings.size).to eq(8)
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EE::Participable do
context 'participable is an epic' do
let(:model) { Epic }
let(:instance) { model.new }
let(:user1) { build(:user) }
let(:user2) { build(:user) }
let(:user3) { build(:user) }
let(:group) { build(:group, :public) }
before do
allow(model).to receive(:participant_attrs).and_return([:foo, :bar])
end
describe '#participants' do
it 'returns the list of participants' do
expect(instance).to receive(:foo).and_return(user2)
expect(instance).to receive(:bar).and_return(user3)
expect(instance).to receive(:group).and_return(group)
participants = instance.participants(user1)
expect(participants).to contain_exactly(user2, user3)
end
end
describe '#participant?' do
it 'returns whether the user is a participant' do
allow(instance).to receive(:foo).and_return(user2)
allow(instance).to receive(:bar).and_return(user3)
allow(instance).to receive(:group).and_return(group)
expect(instance.participant?(user1)).to be false
expect(instance.participant?(user2)).to be true
expect(instance.participant?(user3)).to be true
end
end
end
end
......@@ -227,7 +227,7 @@ RSpec.describe Ci::JobArtifact do
subject(:findings_count) { security_report.findings.length }
it { is_expected.to be(33) }
it { is_expected.to be(5) }
context 'for different types' do
where(:file_type, :security_report?) do
......
......@@ -64,7 +64,7 @@ RSpec.describe 'Query.project(fullPath).pipeline(iid).securityReportFindings' do
end
it 'returns all the vulnerability findings' do
expect(security_report_findings.length).to eq(53)
expect(security_report_findings.length).to eq(25)
end
it 'returns all the queried fields', :aggregate_failures do
......
......@@ -78,7 +78,7 @@ RSpec.describe 'Query.project(fullPath).pipeline(iid).securityReportSummary' do
it 'shows the vulnerabilitiesCount and scannedResourcesCount' do
expect(security_report_summary.dig('dast', 'vulnerabilitiesCount')).to eq(20)
expect(security_report_summary.dig('dast', 'scannedResourcesCount')).to eq(26)
expect(security_report_summary.dig('sast', 'vulnerabilitiesCount')).to eq(33)
expect(security_report_summary.dig('sast', 'vulnerabilitiesCount')).to eq(5)
end
it 'shows the first 20 scanned resources' do
......
......@@ -197,7 +197,7 @@ RSpec.describe Ci::CompareSecurityReportsService do
it 'reports new vulnerabilities' do
expect(subject[:status]).to eq(:parsed)
expect(subject[:data]['added'].count).to eq(33)
expect(subject[:data]['added'].count).to eq(5)
expect(subject[:data]['fixed'].count).to eq(0)
end
end
......@@ -218,13 +218,13 @@ RSpec.describe Ci::CompareSecurityReportsService do
it 'reports new vulnerability' do
expect(subject[:data]['added'].count).to eq(1)
expect(subject[:data]['added'].first['identifiers']).to include(a_hash_including('name' => 'CWE-120'))
expect(subject[:data]['added'].first['identifiers']).to include(a_hash_including('name' => 'CWE-327'))
end
it 'reports fixed sast vulnerabilities' do
expect(subject[:data]['fixed'].count).to eq(4)
expect(subject[:data]['fixed'].count).to eq(1)
compare_keys = collect_ids(subject[:data]['fixed'])
expected_keys = %w(char fopen strcpy char)
expected_keys = %w(CIPHER_INTEGRITY)
expect(compare_keys - expected_keys).to eq([])
end
end
......
......@@ -129,7 +129,7 @@ RSpec.describe Security::ReportSummaryService, '#execute' do
it 'returns the vulnerability count' do
expect(result).to match(a_hash_including(
dast: a_hash_including(vulnerabilities_count: 20),
sast: a_hash_including(vulnerabilities_count: 33),
sast: a_hash_including(vulnerabilities_count: 5),
container_scanning: a_hash_including(vulnerabilities_count: 8),
dependency_scanning: a_hash_including(vulnerabilities_count: 4)
))
......
......@@ -31,7 +31,7 @@ RSpec.describe Security::StoreReportService, '#execute' do
using RSpec::Parameterized::TableSyntax
where(:case_name, :trait, :scanners, :identifiers, :findings, :finding_identifiers, :finding_pipelines, :remediations, :fingerprints) do
'with SAST report' | :sast | 3 | 17 | 33 | 39 | 33 | 0 | 2
'with SAST report' | :sast | 1 | 6 | 5 | 7 | 5 | 0 | 2
'with exceeding identifiers' | :with_exceeding_identifiers | 1 | 20 | 1 | 20 | 1 | 0 | 0
'with Dependency Scanning report' | :dependency_scanning_remediation | 1 | 3 | 2 | 3 | 2 | 1 | 0
'with Container Scanning report' | :container_scanning | 1 | 8 | 8 | 8 | 8 | 0 | 0
......@@ -113,9 +113,13 @@ RSpec.describe Security::StoreReportService, '#execute' do
end
context 'with existing data from previous pipeline' do
let(:scanner) { build(:vulnerabilities_scanner, project: project, external_id: 'bandit', name: 'Bandit') }
let(:identifier) { build(:vulnerabilities_identifier, project: project, fingerprint: 'e6dd15eda2137be0034977a85b300a94a4f243a3') }
let(:different_identifier) { build(:vulnerabilities_identifier, project: project, fingerprint: 'fa47ee81f079e5c38ea6edb700b44eaeb62f67ee') }
let(:finding_identifier_fingerprint) do
build(:ci_reports_security_identifier, external_id: "CIPHER_INTEGRITY").fingerprint
end
let(:scanner) { build(:vulnerabilities_scanner, project: project, external_id: 'find_sec_bugs', name: 'Find Security Bugs') }
let(:identifier) { build(:vulnerabilities_identifier, project: project, fingerprint: finding_identifier_fingerprint) }
let(:different_identifier) { build(:vulnerabilities_identifier, project: project) }
let!(:new_artifact) { create(:ee_ci_job_artifact, :sast, job: new_build) }
let(:new_build) { create(:ci_build, pipeline: new_pipeline) }
let(:new_pipeline) { create(:ci_pipeline, project: project) }
......@@ -129,6 +133,15 @@ RSpec.describe Security::StoreReportService, '#execute' do
let(:trait) { :sast }
let(:finding_location_fingerprint) do
build(
:ci_reports_security_locations_sast,
file_path: "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
start_line: "29",
end_line: "29"
).fingerprint
end
let!(:finding) do
create(:vulnerabilities_finding,
pipelines: [pipeline],
......@@ -136,8 +149,8 @@ RSpec.describe Security::StoreReportService, '#execute' do
primary_identifier: identifier,
scanner: scanner,
project: project,
uuid: "80571acf-8660-4bc8-811a-1d8dec9ab6f4",
location_fingerprint: 'd869ba3f0b3347eb2749135a437dc07c8ae0f420')
uuid: "e5388f40-18f5-566d-95c6-d64c6f46a00a",
location_fingerprint: finding_location_fingerprint)
end
let!(:vulnerability) { create(:vulnerability, findings: [finding], project: project) }
......@@ -180,30 +193,30 @@ RSpec.describe Security::StoreReportService, '#execute' do
expect(finding.reload.uuid).to eq(desired_uuid)
end
it 'inserts only new scanners and reuse existing ones' do
expect { subject }.to change { Vulnerabilities::Scanner.count }.by(2)
it 'reuses existing scanner' do
expect { subject }.not_to change { Vulnerabilities::Scanner.count }
end
it 'inserts only new identifiers and reuse existing ones' do
expect { subject }.to change { Vulnerabilities::Identifier.count }.by(16)
expect { subject }.to change { Vulnerabilities::Identifier.count }.by(5)
end
it 'inserts only new findings and reuse existing ones' do
expect { subject }.to change { Vulnerabilities::Finding.count }.by(32)
expect { subject }.to change { Vulnerabilities::Finding.count }.by(4)
end
it 'inserts all finding pipelines (join model) for this new pipeline' do
expect { subject }.to change { Vulnerabilities::FindingPipeline.where(pipeline: new_pipeline).count }.by(33)
expect { subject }.to change { Vulnerabilities::FindingPipeline.where(pipeline: new_pipeline).count }.by(5)
end
it 'inserts new vulnerabilities with data from findings from this new pipeline' do
expect { subject }.to change { Vulnerability.count }.by(32)
expect { subject }.to change { Vulnerability.count }.by(4)
end
it 'updates existing findings with new data' do
subject
expect(finding.reload).to have_attributes(severity: 'medium', name: 'Probable insecure usage of temp file/directory.')
expect(finding.reload).to have_attributes(severity: 'medium', name: 'Cipher with no integrity')
end
it 'updates fingerprints to match new values' do
......@@ -234,7 +247,7 @@ RSpec.describe Security::StoreReportService, '#execute' do
it 'updates existing vulnerability with new data' do
subject
expect(vulnerability.reload).to have_attributes(severity: 'medium', title: 'Probable insecure usage of temp file/directory.', title_html: 'Probable insecure usage of temp file/directory.')
expect(vulnerability.reload).to have_attributes(severity: 'medium', title: 'Cipher with no integrity', title_html: 'Cipher with no integrity')
end
context 'when the existing vulnerability is resolved with the latest report' do
......
......@@ -26,14 +26,27 @@ RSpec.describe Security::StoreScanService do
describe '#execute' do
let_it_be(:unique_finding_uuid) { artifact.security_report.findings[0].uuid }
let_it_be(:duplicate_finding_uuid) { artifact.security_report.findings[5].uuid }
let_it_be(:duplicate_finding_uuid) { artifact.security_report.findings[4].uuid }
let(:finding_location_fingerprint) do
build(
:ci_reports_security_locations_sast,
file_path: "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
start_line: "41",
end_line: "41"
).fingerprint
end
let(:finding_identifier_fingerprint) do
build(:ci_reports_security_identifier, external_id: "PREDICTABLE_RANDOM").fingerprint
end
let(:deduplicate) { false }
let(:service_object) { described_class.new(artifact, known_keys, deduplicate) }
let(:finding_key) do
build(:ci_reports_security_finding_key,
location_fingerprint: 'd869ba3f0b3347eb2749135a437dc07c8ae0f420',
identifier_fingerprint: 'e6dd15eda2137be0034977a85b300a94a4f243a3')
location_fingerprint: finding_location_fingerprint,
identifier_fingerprint: finding_identifier_fingerprint)
end
subject(:store_scan) { service_object.execute }
......
......@@ -64,7 +64,7 @@ RSpec.describe Security::VulnerabilityCountingService, '#execute' do
end
it {
is_expected.to match(a_hash_including("sast" => 33,
is_expected.to match(a_hash_including("sast" => 5,
"dast" => 20,
"container_scanning" => 8,
"dependency_scanning" => 4))
......
......@@ -182,7 +182,7 @@ module Gitlab
parse_params do |raw_time_date|
Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute
end
command :spend do |time_spent, time_spent_date|
command :spend, :spent do |time_spent, time_spent_date|
if time_spent
@updates[:spend_time] = {
duration: time_spent,
......
......@@ -28,7 +28,7 @@ module Gitlab
'unassign_reviewer'
when 'request_review', 'reviewer'
'assign_reviewer'
when 'spend'
when 'spend', 'spent'
event_name_for_spend(args)
when 'unassign'
event_name_for_unassign(args)
......
......@@ -6690,6 +6690,9 @@ msgstr ""
msgid "ClusterIntegration|Base domain"
msgstr ""
msgid "ClusterIntegration|Before you enable this integration, follow the %{link_start}documented process%{link_end}."
msgstr ""
msgid "ClusterIntegration|Blocking mode"
msgstr ""
......@@ -6855,6 +6858,9 @@ msgstr ""
msgid "ClusterIntegration|Enable Cloud Run for Anthos"
msgstr ""
msgid "ClusterIntegration|Enable Prometheus integration"
msgstr ""
msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
msgstr ""
......@@ -6993,6 +6999,15 @@ msgstr ""
msgid "ClusterIntegration|Integrate with a cluster certificate"
msgstr ""
msgid "ClusterIntegration|Integration disabled"
msgstr ""
msgid "ClusterIntegration|Integration enabled"
msgstr ""
msgid "ClusterIntegration|Integrations enable you to integrate your cluster as part of your GitLab workflow."
msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
......@@ -12807,6 +12822,9 @@ msgstr ""
msgid "ExternalWikiService|The URL of the external wiki"
msgstr ""
msgid "Externally installed"
msgstr ""
msgid "Facebook"
msgstr ""
......
......@@ -546,20 +546,30 @@ RSpec.describe Admin::ClustersController do
describe 'GET #show' do
let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
def get_show
def get_show(tab: nil)
get :show,
params: {
id: cluster
id: cluster,
tab: tab
}
end
describe 'functionality' do
render_views
it 'responds successfully' do
get_show
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:cluster)).to eq(cluster)
end
it 'renders integration tab view' do
get_show(tab: 'integrations')
expect(response).to render_template('clusters/clusters/_integrations')
expect(response).to have_gitlab_http_status(:ok)
end
end
describe 'security' do
......
......@@ -641,21 +641,31 @@ RSpec.describe Groups::ClustersController do
describe 'GET show' do
let(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) }
def go
def go(tab: nil)
get :show,
params: {
group_id: group,
id: cluster
id: cluster,
tab: tab
}
end
describe 'functionality' do
render_views
it 'renders view' do
go
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:cluster)).to eq(cluster)
end
it 'renders integration tab view', :aggregate_failures do
go(tab: 'integrations')
expect(response).to render_template('clusters/clusters/_integrations')
expect(response).to have_gitlab_http_status(:ok)
end
end
describe 'security' do
......
......@@ -674,22 +674,32 @@ RSpec.describe Projects::ClustersController do
describe 'GET show' do
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
def go
def go(tab: nil)
get :show,
params: {
namespace_id: project.namespace,
project_id: project,
id: cluster
id: cluster,
tab: tab
}
end
describe 'functionality' do
render_views
it "renders view" do
go
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:cluster)).to eq(cluster)
end
it 'renders integration tab view' do
go(tab: 'integrations')
expect(response).to render_template('clusters/clusters/_integrations')
expect(response).to have_gitlab_http_status(:ok)
end
end
describe 'security' do
......
......@@ -69,6 +69,10 @@ FactoryBot.define do
status { 10 }
end
trait :externally_installed do
status { 11 }
end
trait :timed_out do
installing
updated_at { ClusterWaitForAppInstallationWorker::TIMEOUT.ago }
......
......@@ -89,6 +89,12 @@ describe('Application Row', () => {
checkButtonState('Install', false, true);
});
it('has disabled "Externally installed" when APPLICATION_STATUS.EXTERNALLY_INSTALLED', () => {
mountComponent({ status: APPLICATION_STATUS.EXTERNALLY_INSTALLED });
checkButtonState('Externally installed', false, true);
});
it('has disabled "Installed" when application is installed and not uninstallable', () => {
mountComponent({
status: APPLICATION_STATUS.INSTALLED,
......
......@@ -20,6 +20,8 @@ const {
UNINSTALLING,
UNINSTALL_ERRORED,
UNINSTALLED,
PRE_INSTALLED,
EXTERNALLY_INSTALLED,
} = APPLICATION_STATUS;
const NO_EFFECTS = 'no effects';
......@@ -29,19 +31,21 @@ describe('applicationStateMachine', () => {
describe(`current state is ${NO_STATUS}`, () => {
it.each`
expectedState | event | effects
${INSTALLING} | ${SCHEDULED} | ${NO_EFFECTS}
${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS}
${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS}
${INSTALLING} | ${INSTALLING} | ${NO_EFFECTS}
${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS}
${INSTALLABLE} | ${ERROR} | ${{ installFailed: true }}
${UPDATING} | ${UPDATING} | ${NO_EFFECTS}
${INSTALLED} | ${UPDATED} | ${NO_EFFECTS}
${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }}
${UNINSTALLING} | ${UNINSTALLING} | ${NO_EFFECTS}
${INSTALLED} | ${UNINSTALL_ERRORED} | ${{ uninstallFailed: true }}
${UNINSTALLED} | ${UNINSTALLED} | ${NO_EFFECTS}
expectedState | event | effects
${INSTALLING} | ${SCHEDULED} | ${NO_EFFECTS}
${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS}
${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS}
${INSTALLING} | ${INSTALLING} | ${NO_EFFECTS}
${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS}
${INSTALLABLE} | ${ERROR} | ${{ installFailed: true }}
${UPDATING} | ${UPDATING} | ${NO_EFFECTS}
${INSTALLED} | ${UPDATED} | ${NO_EFFECTS}
${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }}
${UNINSTALLING} | ${UNINSTALLING} | ${NO_EFFECTS}
${INSTALLED} | ${UNINSTALL_ERRORED} | ${{ uninstallFailed: true }}
${UNINSTALLED} | ${UNINSTALLED} | ${NO_EFFECTS}
${PRE_INSTALLED} | ${PRE_INSTALLED} | ${NO_EFFECTS}
${EXTERNALLY_INSTALLED} | ${EXTERNALLY_INSTALLED} | ${NO_EFFECTS}
`(`transitions to $expectedState on $event event and applies $effects`, (data) => {
const { expectedState, event, effects } = data;
const currentAppState = {
......
......@@ -115,6 +115,26 @@ RSpec.describe Gitlab::UsageDataCounters::QuickActionActivityUniqueCounter, :cle
end
end
context 'tracking spent' do
let(:quickaction_name) { 'spent' }
context 'adding time' do
let(:args) { '1d' }
it_behaves_like 'a tracked quick action unique event' do
let(:action) { 'i_quickactions_spend_add' }
end
end
context 'removing time' do
let(:args) { '-1d' }
it_behaves_like 'a tracked quick action unique event' do
let(:action) { 'i_quickactions_spend_subtract' }
end
end
end
context 'tracking unassign' do
let(:quickaction_name) { 'unassign' }
......
......@@ -39,6 +39,19 @@ RSpec.describe Clusters::Applications::Prometheus do
end
end
describe 'transition to externally_installed' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, :with_installed_helm) }
let(:application) { create(:clusters_applications_prometheus, :installing, cluster: cluster) }
it 'schedules post installation job' do
expect(Clusters::Applications::ActivateServiceWorker)
.to receive(:perform_async).with(cluster.id, 'prometheus')
application.make_externally_installed!
end
end
describe 'transition to updating' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, projects: [project]) }
......
......@@ -380,7 +380,7 @@ RSpec.describe Issuable do
context 'user is a participant in the issue' do
before do
allow(issue).to receive(:participants).with(user).and_return([user])
allow(issue).to receive(:participant?).with(user).and_return(true)
end
it 'returns false when no subcription exists' do
......
......@@ -39,11 +39,12 @@ RSpec.describe Participable do
expect(participants).to include(user3)
end
it 'caches the raw list of participants' do
it 'caches the list of filtered participants' do
instance = model.new
user1 = build(:user)
expect(instance).to receive(:raw_participants).once
expect(instance).to receive(:all_participants_hash).once.and_return({})
expect(instance).to receive(:filter_by_ability).once
instance.participants(user1)
instance.participants(user1)
......@@ -91,5 +92,71 @@ RSpec.describe Participable do
expect(ext_arg).to be_an_instance_of(Gitlab::ReferenceExtractor)
end
end
context 'participable is a personal snippet' do
let(:model) { PersonalSnippet }
let(:instance) { model.new(author: user1) }
let(:user1) { build(:user) }
let(:user2) { build(:user) }
let(:user3) { build(:user) }
before do
allow(model).to receive(:participant_attrs).and_return([:foo, :bar])
end
it 'returns the list of participants' do
expect(instance).to receive(:foo).and_return(user1)
expect(instance).to receive(:bar).and_return(user2)
participants = instance.participants(user1)
expect(participants).to contain_exactly(user1)
end
end
end
describe '#participant?' do
let(:instance) { model.new }
let(:user1) { build(:user) }
let(:user2) { build(:user) }
let(:user3) { build(:user) }
let(:project) { build(:project, :public) }
before do
allow(model).to receive(:participant_attrs).and_return([:foo, :bar])
end
it 'returns whether the user is a participant' do
allow(instance).to receive(:foo).and_return(user2)
allow(instance).to receive(:bar).and_return(user3)
allow(instance).to receive(:project).and_return(project)
expect(instance.participant?(user1)).to be false
expect(instance.participant?(user2)).to be true
expect(instance.participant?(user3)).to be true
end
it 'caches the list of raw participants' do
expect(instance).to receive(:raw_participants).once.and_return([])
expect(instance).to receive(:project).twice.and_return(project)
instance.participant?(user1)
instance.participant?(user1)
end
context 'participable is a personal snippet' do
let(:model) { PersonalSnippet }
let(:instance) { model.new(author: user1) }
it 'returns whether the user is a participant' do
allow(instance).to receive(:foo).and_return(user1)
allow(instance).to receive(:bar).and_return(user2)
expect(instance.participant?(user1)).to be true
expect(instance.participant?(user2)).to be false
expect(instance.participant?(user3)).to be false
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Admin::Clusters::IntegrationsController, :enable_admin_mode do
include AccessMatchersForController
shared_examples 'a secure endpoint' do
context 'it is allowed for admins only' do
it { expect { subject }.to be_allowed_for(:admin) }
it { expect { subject }.to be_denied_for(:user) }
it { expect { subject }.to be_denied_for(:external) }
end
end
describe 'POST create_or_update' do
let(:cluster) { create(:cluster, :instance, :provided_by_gcp) }
let(:user) { create(:admin) }
it_behaves_like '#create_or_update action' do
let(:path) { create_or_update_admin_cluster_integration_path(cluster) }
let(:redirect_path) { admin_cluster_path(cluster, params: { tab: 'integrations' }) }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Groups::Clusters::IntegrationsController do
include AccessMatchersForController
shared_examples 'a secure endpoint' do
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
expect { subject }.to be_allowed_for(:admin)
end
it 'is denied for admin when admin mode disabled' do
expect { subject }.to be_denied_for(:admin)
end
context 'it is allowed for group maintainers' do
it { expect { subject }.to be_allowed_for(:owner).of(group) }
it { expect { subject }.to be_allowed_for(:maintainer).of(group) }
it { expect { subject }.to be_denied_for(:developer).of(group) }
it { expect { subject }.to be_denied_for(:reporter).of(group) }
it { expect { subject }.to be_denied_for(:guest).of(group) }
it { expect { subject }.to be_denied_for(:user) }
it { expect { subject }.to be_denied_for(:external) }
end
end
describe 'POST create_or_update' do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
let_it_be(:member) { create(:group_member, user: user, group: group) }
let(:cluster) { create(:cluster, :group, :provided_by_gcp, groups: [group]) }
it_behaves_like '#create_or_update action' do
let(:path) { create_or_update_group_cluster_integration_path(group, cluster) }
let(:redirect_path) { group_cluster_path(group, cluster, params: { tab: 'integrations' }) }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Clusters::IntegrationsController do
include AccessMatchersForController
shared_examples 'a secure endpoint' do
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
expect { subject }.to be_allowed_for(:admin)
end
it 'is denied for admin when admin mode disabled' do
expect { subject }.to be_denied_for(:admin)
end
context 'it is allowed for project maintainers' do
it { expect { subject }.to be_allowed_for(:owner).of(project) }
it { expect { subject }.to be_allowed_for(:maintainer).of(project) }
it { expect { subject }.to be_denied_for(:developer).of(project) }
it { expect { subject }.to be_denied_for(:reporter).of(project) }
it { expect { subject }.to be_denied_for(:guest).of(project) }
it { expect { subject }.to be_denied_for(:user) }
it { expect { subject }.to be_denied_for(:external) }
end
end
describe 'POST create_or_update' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
let(:user) { project.owner }
it_behaves_like '#create_or_update action' do
let(:path) { create_or_update_project_cluster_integration_path(project, cluster) }
let(:redirect_path) { project_cluster_path(project, cluster, params: { tab: 'integrations' }) }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Clusters::Integrations::CreateService, '#execute' do
let_it_be(:project) { create(:project) }
let_it_be_with_reload(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
let(:params) do
{ application_type: 'prometheus', enabled: true }
end
let(:service) do
described_class.new(container: project, cluster: cluster, current_user: project.owner, params: params)
end
it 'creates a new Prometheus instance' do
expect(service.execute).to be_success
expect(cluster.application_prometheus).to be_present
expect(cluster.application_prometheus).to be_persisted
expect(cluster.application_prometheus).to be_externally_installed
end
context 'enabled param is false' do
let(:params) do
{ application_type: 'prometheus', enabled: false }
end
it 'creates a new uninstalled Prometheus instance' do
expect(service.execute).to be_success
expect(cluster.application_prometheus).to be_present
expect(cluster.application_prometheus).to be_persisted
expect(cluster.application_prometheus).to be_uninstalled
end
end
context 'unauthorized user' do
let(:service) do
unauthorized_user = create(:user)
described_class.new(container: project, cluster: cluster, current_user: unauthorized_user, params: params)
end
it 'does not create a new Prometheus instance' do
expect(service.execute).to be_error
expect(cluster.application_prometheus).to be_nil
end
end
context 'prometheus record exists' do
before do
create(:clusters_applications_prometheus, cluster: cluster)
end
it 'updates the Prometheus instance' do
expect(service.execute).to be_success
expect(cluster.application_prometheus).to be_present
expect(cluster.application_prometheus).to be_persisted
expect(cluster.application_prometheus).to be_externally_installed
end
context 'enabled param is false' do
let(:params) do
{ application_type: 'prometheus', enabled: false }
end
it 'updates the Prometheus instance as uninstalled' do
expect(service.execute).to be_success
expect(cluster.application_prometheus).to be_present
expect(cluster.application_prometheus).to be_persisted
expect(cluster.application_prometheus).to be_uninstalled
end
end
end
context 'for an un-supported application type' do
let(:params) do
{ application_type: 'something_else', enabled: true }
end
it 'errors' do
expect { service.execute}.to raise_error(ArgumentError)
end
end
end
......@@ -368,24 +368,24 @@ RSpec.describe QuickActions::InterpretService do
spent_at: DateTime.current.to_date
})
end
it 'returns the spend_time message including the formatted duration and verb' do
_, _, message = service.execute('/spend -120m', issuable)
expect(message).to eq('Subtracted 2h spent time.')
end
end
shared_examples 'spend command with negative time' do
it 'populates spend_time: -1800 if content contains /spend -30m' do
it 'populates spend_time: -7200 if content contains -120m' do
_, updates, _ = service.execute(content, issuable)
expect(updates).to eq(spend_time: {
duration: -1800,
duration: -7200,
user_id: developer.id,
spent_at: DateTime.current.to_date
})
end
it 'returns the spend_time message including the formatted duration and verb' do
_, _, message = service.execute(content, issuable)
expect(message).to eq('Subtracted 2h spent time.')
end
end
shared_examples 'spend command with valid date' do
......@@ -1242,8 +1242,18 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { issue }
end
it_behaves_like 'spend command' do
let(:content) { '/spent 1h' }
let(:issuable) { issue }
end
it_behaves_like 'spend command with negative time' do
let(:content) { '/spend -30m' }
let(:content) { '/spend -120m' }
let(:issuable) { issue }
end
it_behaves_like 'spend command with negative time' do
let(:content) { '/spent -120m' }
let(:issuable) { issue }
end
......@@ -1253,26 +1263,52 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { issue }
end
it_behaves_like 'spend command with valid date' do
let(:date) { '2016-02-02' }
let(:content) { "/spent 30m #{date}" }
let(:issuable) { issue }
end
it_behaves_like 'spend command with invalid date' do
let(:content) { '/spend 30m 17-99-99' }
let(:issuable) { issue }
end
it_behaves_like 'spend command with invalid date' do
let(:content) { '/spent 30m 17-99-99' }
let(:issuable) { issue }
end
it_behaves_like 'spend command with future date' do
let(:content) { '/spend 30m 6017-10-10' }
let(:issuable) { issue }
end
it_behaves_like 'spend command with future date' do
let(:content) { '/spent 30m 6017-10-10' }
let(:issuable) { issue }
end
it_behaves_like 'failed command' do
let(:content) { '/spend' }
let(:issuable) { issue }
end
it_behaves_like 'failed command' do
let(:content) { '/spent' }
let(:issuable) { issue }
end
it_behaves_like 'failed command' do
let(:content) { '/spend abc' }
let(:issuable) { issue }
end
it_behaves_like 'failed command' do
let(:content) { '/spent abc' }
let(:issuable) { issue }
end
it_behaves_like 'remove_estimate command' do
let(:content) { '/remove_estimate' }
let(:issuable) { issue }
......@@ -2247,10 +2283,14 @@ RSpec.describe QuickActions::InterpretService do
end
describe 'spend command' do
let(:content) { '/spend -120m' }
it 'includes the formatted duration and proper verb when using /spend' do
_, explanations = service.explain('/spend -120m', issue)
it 'includes the formatted duration and proper verb' do
_, explanations = service.explain(content, issue)
expect(explanations).to eq(['Subtracts 2h spent time.'])
end
it 'includes the formatted duration and proper verb when using /spent' do
_, explanations = service.explain('/spent -120m', issue)
expect(explanations).to eq(['Subtracts 2h spent time.'])
end
......
......@@ -138,7 +138,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
it 'is installed' do
subject.make_externally_installed
expect(subject).to be_installed
expect(subject).to be_externally_installed
end
context 'helm record does not exist' do
......@@ -170,7 +170,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
it 'is installed' do
subject.make_externally_installed
expect(subject).to be_installed
expect(subject).to be_externally_installed
end
end
......@@ -180,7 +180,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
it 'is installed' do
subject.make_externally_installed
expect(subject).to be_installed
expect(subject).to be_externally_installed
end
it 'clears #status_reason' do
......@@ -317,6 +317,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
:uninstall_errored | false
:uninstalled | false
:timed_out | false
:externally_installed | true
end
with_them do
......
# frozen_string_literal: true
RSpec.shared_examples '#create_or_update action' do
let(:params) do
{ integration: { application_type: Clusters::Applications::Prometheus.application_name, enabled: true } }
end
let(:path) { raise NotImplementedError }
let(:redirect_path) { raise NotImplementedError }
describe 'authorization' do
subject do
post path, params: params
end
it_behaves_like 'a secure endpoint'
end
describe 'functionality' do
before do
sign_in(user)
end
it 'redirects on success' do
post path, params: params
expect(response).to have_gitlab_http_status(:redirect)
expect(response).to redirect_to(redirect_path)
expect(flash[:notice]).to be_present
end
it 'redirects on error' do
error = ServiceResponse.error(message: 'failed')
expect_next_instance_of(Clusters::Integrations::CreateService) do |service|
expect(service).to receive(:execute).and_return(error)
end
post path, params: params
expect(response).to have_gitlab_http_status(:redirect)
expect(response).to redirect_to(redirect_path)
expect(flash[:alert]).to eq(error.message)
end
end
end
......@@ -41,7 +41,7 @@ RSpec.shared_examples 'parse cluster applications artifact' do |release_name|
end.to change(application_class, :count)
expect(cluster_application).to be_persisted
expect(cluster_application).to be_installed
expect(cluster_application).to be_externally_installed
end
end
......@@ -53,7 +53,7 @@ RSpec.shared_examples 'parse cluster applications artifact' do |release_name|
it 'marks the application as installed' do
described_class.new(job, user).execute(artifact)
expect(cluster_application).to be_installed
expect(cluster_application).to be_externally_installed
end
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment