Commit 5b915f5d authored by Thong Kuah's avatar Thong Kuah Committed by Igor Drozdov

Add (BYO) Prometheus as cluster integration

parent c36cf6d0
...@@ -141,6 +141,9 @@ export default { ...@@ -141,6 +141,9 @@ export default {
isInstalling() { isInstalling() {
return this.status === APPLICATION_STATUS.INSTALLING; return this.status === APPLICATION_STATUS.INSTALLING;
}, },
isExternallyInstalled() {
return this.status === APPLICATION_STATUS.EXTERNALLY_INSTALLED;
},
canInstall() { canInstall() {
return ( return (
this.status === APPLICATION_STATUS.NOT_INSTALLABLE || this.status === APPLICATION_STATUS.NOT_INSTALLABLE ||
...@@ -193,10 +196,17 @@ export default { ...@@ -193,10 +196,17 @@ export default {
label = __('Installing'); label = __('Installing');
} else if (this.installed) { } else if (this.installed) {
label = __('Installed'); label = __('Installed');
} else if (this.isExternallyInstalled) {
label = __('Externally installed');
} }
return label; return label;
}, },
buttonGridCellClass() {
return this.showManageButton || this.status === APPLICATION_STATUS.EXTERNALLY_INSTALLED
? 'section-25'
: 'section-15';
},
showManageButton() { showManageButton() {
return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED; return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED;
}, },
...@@ -427,8 +437,7 @@ export default { ...@@ -427,8 +437,7 @@ export default {
</div> </div>
</div> </div>
<div <div
:class="{ 'section-25': showManageButton, 'section-15': !showManageButton }" :class="[buttonGridCellClass, 'table-section', 'table-button-footer', 'section-align-top']"
class="table-section table-button-footer section-align-top"
role="gridcell" role="gridcell"
> >
<div v-if="showManageButton" class="btn-group table-action-buttons"> <div v-if="showManageButton" class="btn-group table-action-buttons">
......
...@@ -26,6 +26,7 @@ export const APPLICATION_STATUS = { ...@@ -26,6 +26,7 @@ export const APPLICATION_STATUS = {
ERROR: 'errored', ERROR: 'errored',
PRE_INSTALLED: 'pre_installed', PRE_INSTALLED: 'pre_installed',
UNINSTALLED: 'uninstalled', UNINSTALLED: 'uninstalled',
EXTERNALLY_INSTALLED: 'externally_installed',
}; };
/* /*
......
import Vue from 'vue'; import Vue from 'vue';
import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import IntegrationForm from '../components/integration_form.vue'; import IntegrationForm from '../components/integration_form.vue';
import { createStore } from '../stores'; import { createStore } from '../stores';
export default () => { export default () => {
dirtySubmitFactory(document.querySelectorAll('.js-cluster-integrations-form'));
const entryPoint = document.querySelector('#js-cluster-details-form'); const entryPoint = document.querySelector('#js-cluster-details-form');
if (!entryPoint) { if (!entryPoint) {
......
...@@ -15,6 +15,7 @@ const { ...@@ -15,6 +15,7 @@ const {
UNINSTALL_ERRORED, UNINSTALL_ERRORED,
PRE_INSTALLED, PRE_INSTALLED,
UNINSTALLED, UNINSTALLED,
EXTERNALLY_INSTALLED,
} = APPLICATION_STATUS; } = APPLICATION_STATUS;
const applicationStateMachine = { const applicationStateMachine = {
...@@ -71,6 +72,9 @@ const applicationStateMachine = { ...@@ -71,6 +72,9 @@ const applicationStateMachine = {
[UNINSTALLED]: { [UNINSTALLED]: {
target: UNINSTALLED, target: UNINSTALLED,
}, },
[EXTERNALLY_INSTALLED]: {
target: EXTERNALLY_INSTALLED,
},
}, },
}, },
[NOT_INSTALLABLE]: { [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 ...@@ -60,6 +60,9 @@ class Clusters::ClustersController < Clusters::BaseController
end end
def show def show
if params[:tab] == 'integrations'
@prometheus_integration = Clusters::IntegrationPresenter.new(@cluster.find_or_build_application(Clusters::Applications::Prometheus))
end
end end
def update 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 ...@@ -71,6 +71,8 @@ module ClustersHelper
render_if_exists 'clusters/clusters/health' render_if_exists 'clusters/clusters/health'
when 'apps' when 'apps'
render 'applications' render 'applications'
when 'integrations'
render 'integrations'
when 'settings' when 'settings'
render 'advanced_settings_container' render 'advanced_settings_container'
else else
......
...@@ -32,7 +32,7 @@ module Clusters ...@@ -32,7 +32,7 @@ module Clusters
end end
state_machine :status do state_machine :status do
after_transition any => [:installed] do |application| after_transition any => [:installed, :externally_installed] do |application|
application.run_after_commit do application.run_after_commit do
Clusters::Applications::ActivateServiceWorker Clusters::Applications::ActivateServiceWorker
.perform_async(application.cluster_id, ::PrometheusService.to_param) # rubocop:disable CodeReuse/ServiceClass .perform_async(application.cluster_id, ::PrometheusService.to_param) # rubocop:disable CodeReuse/ServiceClass
......
...@@ -9,6 +9,7 @@ module Clusters ...@@ -9,6 +9,7 @@ module Clusters
scope :available, -> do scope :available, -> do
where( where(
status: [ status: [
self.state_machines[:status].states[:externally_installed].value,
self.state_machines[:status].states[:installed].value, self.state_machines[:status].states[:installed].value,
self.state_machines[:status].states[:updated].value self.state_machines[:status].states[:updated].value
] ]
...@@ -28,6 +29,7 @@ module Clusters ...@@ -28,6 +29,7 @@ module Clusters
state :uninstalling, value: 7 state :uninstalling, value: 7
state :uninstall_errored, value: 8 state :uninstall_errored, value: 8
state :uninstalled, value: 10 state :uninstalled, value: 10
state :externally_installed, value: 11
# Used for applications that are pre-installed by the cluster, # Used for applications that are pre-installed by the cluster,
# e.g. Knative in GCP Cloud Run enabled clusters # e.g. Knative in GCP Cloud Run enabled clusters
...@@ -37,7 +39,7 @@ module Clusters ...@@ -37,7 +39,7 @@ module Clusters
state :pre_installed, value: 9 state :pre_installed, value: 9
event :make_externally_installed do event :make_externally_installed do
transition any => :installed transition any => :externally_installed
end end
event :make_externally_uninstalled do event :make_externally_uninstalled do
...@@ -79,7 +81,7 @@ module Clusters ...@@ -79,7 +81,7 @@ module Clusters
transition [:scheduled] => :uninstalling transition [:scheduled] => :uninstalling
end end
before_transition any => [:scheduled, :installed, :uninstalled] do |application, _| before_transition any => [:scheduled, :installed, :uninstalled, :externally_installed] do |application, _|
application.status_reason = nil application.status_reason = nil
end end
...@@ -114,7 +116,7 @@ module Clusters ...@@ -114,7 +116,7 @@ module Clusters
end end
def available? def available?
pre_installed? || installed? || updated? pre_installed? || installed? || externally_installed? || updated?
end end
def update_in_progress? def update_in_progress?
......
...@@ -49,13 +49,25 @@ module Clusters ...@@ -49,13 +49,25 @@ module Clusters
end end
end end
def show_path def show_path(params: {})
if cluster.project_type? if cluster.project_type?
project_cluster_path(project, cluster) project_cluster_path(project, cluster, params)
elsif cluster.group_type? elsif cluster.group_type?
group_cluster_path(group, cluster) group_cluster_path(group, cluster, params)
elsif cluster.instance_type? 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 else
raise NotImplementedError raise NotImplementedError
end 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 @@ ...@@ -59,6 +59,7 @@
= render_if_exists 'clusters/clusters/environments_tab' = render_if_exists 'clusters/clusters/environments_tab'
= render 'clusters/clusters/health_tab' = render 'clusters/clusters/health_tab'
= render 'applications_tab' = render 'applications_tab'
= render 'integrations_tab'
= render 'advanced_settings_tab' = render 'advanced_settings_tab'
.tab-content.py-3 .tab-content.py-3
......
---
title: Ability to add Prometheus as cluster integration
merge_request: 55244
author:
type: added
...@@ -220,6 +220,12 @@ Rails.application.routes.draw do ...@@ -220,6 +220,12 @@ Rails.application.routes.draw do
post :authorize_aws_role post :authorize_aws_role
end end
resource :integration, controller: 'clusters/integrations', only: [] do
collection do
post :create_or_update
end
end
member do member do
Gitlab.ee do Gitlab.ee do
get :metrics, format: :json get :metrics, format: :json
......
...@@ -1001,8 +1001,8 @@ Logs produced by pods running **GitLab Managed Apps** can be browsed using ...@@ -1001,8 +1001,8 @@ Logs produced by pods running **GitLab Managed Apps** can be browsed using
## Install with one click ## Install with one click
WARNING: WARNING:
The one click installation method is scheduled for removal in GitLab 14.0. The removal The one-click installation method is deprecated and scheduled for removal in [GitLab 14.0](https://gitlab.com/groups/gitlab-org/-/epics/4280).
of this feature from GitLab does not affect installed applications to avoid breaking This removal does not affect installed applications to avoid breaking
changes. Following GitLab 14.0, users can take ownership of already installed applications changes. Following GitLab 14.0, users can take ownership of already installed applications
using our documentation. 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 ...@@ -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. > [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 GitLab can seamlessly deploy and manage Prometheus on a
[connected Kubernetes cluster](../clusters/index.md), to help you monitor your apps. [connected Kubernetes cluster](../clusters/index.md), to help you monitor your apps.
......
...@@ -6678,6 +6678,9 @@ msgstr "" ...@@ -6678,6 +6678,9 @@ msgstr ""
msgid "ClusterIntegration|Base domain" msgid "ClusterIntegration|Base domain"
msgstr "" msgstr ""
msgid "ClusterIntegration|Before you enable this integration, follow the %{link_start}documented process%{link_end}."
msgstr ""
msgid "ClusterIntegration|Blocking mode" msgid "ClusterIntegration|Blocking mode"
msgstr "" msgstr ""
...@@ -6843,6 +6846,9 @@ msgstr "" ...@@ -6843,6 +6846,9 @@ msgstr ""
msgid "ClusterIntegration|Enable Cloud Run for Anthos" msgid "ClusterIntegration|Enable Cloud Run for Anthos"
msgstr "" msgstr ""
msgid "ClusterIntegration|Enable Prometheus integration"
msgstr ""
msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster." msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
msgstr "" msgstr ""
...@@ -6981,6 +6987,15 @@ msgstr "" ...@@ -6981,6 +6987,15 @@ msgstr ""
msgid "ClusterIntegration|Integrate with a cluster certificate" msgid "ClusterIntegration|Integrate with a cluster certificate"
msgstr "" 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" msgid "ClusterIntegration|Issuer Email"
msgstr "" msgstr ""
...@@ -12777,6 +12792,9 @@ msgstr "" ...@@ -12777,6 +12792,9 @@ msgstr ""
msgid "ExternalWikiService|The URL of the external wiki" msgid "ExternalWikiService|The URL of the external wiki"
msgstr "" msgstr ""
msgid "Externally installed"
msgstr ""
msgid "Facebook" msgid "Facebook"
msgstr "" msgstr ""
......
...@@ -546,20 +546,30 @@ RSpec.describe Admin::ClustersController do ...@@ -546,20 +546,30 @@ RSpec.describe Admin::ClustersController do
describe 'GET #show' do describe 'GET #show' do
let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
def get_show def get_show(tab: nil)
get :show, get :show,
params: { params: {
id: cluster id: cluster,
tab: tab
} }
end end
describe 'functionality' do describe 'functionality' do
render_views
it 'responds successfully' do it 'responds successfully' do
get_show get_show
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:cluster)).to eq(cluster) expect(assigns(:cluster)).to eq(cluster)
end 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 end
describe 'security' do describe 'security' do
......
...@@ -641,21 +641,31 @@ RSpec.describe Groups::ClustersController do ...@@ -641,21 +641,31 @@ RSpec.describe Groups::ClustersController do
describe 'GET show' do describe 'GET show' do
let(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) } let(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) }
def go def go(tab: nil)
get :show, get :show,
params: { params: {
group_id: group, group_id: group,
id: cluster id: cluster,
tab: tab
} }
end end
describe 'functionality' do describe 'functionality' do
render_views
it 'renders view' do it 'renders view' do
go go
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:cluster)).to eq(cluster) expect(assigns(:cluster)).to eq(cluster)
end 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 end
describe 'security' do describe 'security' do
......
...@@ -674,22 +674,32 @@ RSpec.describe Projects::ClustersController do ...@@ -674,22 +674,32 @@ RSpec.describe Projects::ClustersController do
describe 'GET show' do describe 'GET show' do
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
def go def go(tab: nil)
get :show, get :show,
params: { params: {
namespace_id: project.namespace, namespace_id: project.namespace,
project_id: project, project_id: project,
id: cluster id: cluster,
tab: tab
} }
end end
describe 'functionality' do describe 'functionality' do
render_views
it "renders view" do it "renders view" do
go go
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:cluster)).to eq(cluster) expect(assigns(:cluster)).to eq(cluster)
end 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 end
describe 'security' do describe 'security' do
......
...@@ -69,6 +69,10 @@ FactoryBot.define do ...@@ -69,6 +69,10 @@ FactoryBot.define do
status { 10 } status { 10 }
end end
trait :externally_installed do
status { 11 }
end
trait :timed_out do trait :timed_out do
installing installing
updated_at { ClusterWaitForAppInstallationWorker::TIMEOUT.ago } updated_at { ClusterWaitForAppInstallationWorker::TIMEOUT.ago }
......
...@@ -89,6 +89,12 @@ describe('Application Row', () => { ...@@ -89,6 +89,12 @@ describe('Application Row', () => {
checkButtonState('Install', false, true); 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', () => { it('has disabled "Installed" when application is installed and not uninstallable', () => {
mountComponent({ mountComponent({
status: APPLICATION_STATUS.INSTALLED, status: APPLICATION_STATUS.INSTALLED,
......
...@@ -20,6 +20,8 @@ const { ...@@ -20,6 +20,8 @@ const {
UNINSTALLING, UNINSTALLING,
UNINSTALL_ERRORED, UNINSTALL_ERRORED,
UNINSTALLED, UNINSTALLED,
PRE_INSTALLED,
EXTERNALLY_INSTALLED,
} = APPLICATION_STATUS; } = APPLICATION_STATUS;
const NO_EFFECTS = 'no effects'; const NO_EFFECTS = 'no effects';
...@@ -29,19 +31,21 @@ describe('applicationStateMachine', () => { ...@@ -29,19 +31,21 @@ describe('applicationStateMachine', () => {
describe(`current state is ${NO_STATUS}`, () => { describe(`current state is ${NO_STATUS}`, () => {
it.each` it.each`
expectedState | event | effects expectedState | event | effects
${INSTALLING} | ${SCHEDULED} | ${NO_EFFECTS} ${INSTALLING} | ${SCHEDULED} | ${NO_EFFECTS}
${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS} ${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS}
${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS} ${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS}
${INSTALLING} | ${INSTALLING} | ${NO_EFFECTS} ${INSTALLING} | ${INSTALLING} | ${NO_EFFECTS}
${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS} ${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS}
${INSTALLABLE} | ${ERROR} | ${{ installFailed: true }} ${INSTALLABLE} | ${ERROR} | ${{ installFailed: true }}
${UPDATING} | ${UPDATING} | ${NO_EFFECTS} ${UPDATING} | ${UPDATING} | ${NO_EFFECTS}
${INSTALLED} | ${UPDATED} | ${NO_EFFECTS} ${INSTALLED} | ${UPDATED} | ${NO_EFFECTS}
${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }} ${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }}
${UNINSTALLING} | ${UNINSTALLING} | ${NO_EFFECTS} ${UNINSTALLING} | ${UNINSTALLING} | ${NO_EFFECTS}
${INSTALLED} | ${UNINSTALL_ERRORED} | ${{ uninstallFailed: true }} ${INSTALLED} | ${UNINSTALL_ERRORED} | ${{ uninstallFailed: true }}
${UNINSTALLED} | ${UNINSTALLED} | ${NO_EFFECTS} ${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) => { `(`transitions to $expectedState on $event event and applies $effects`, (data) => {
const { expectedState, event, effects } = data; const { expectedState, event, effects } = data;
const currentAppState = { const currentAppState = {
......
...@@ -39,6 +39,19 @@ RSpec.describe Clusters::Applications::Prometheus do ...@@ -39,6 +39,19 @@ RSpec.describe Clusters::Applications::Prometheus do
end end
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 describe 'transition to updating' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:cluster) { create(:cluster, projects: [project]) } let(:cluster) { create(:cluster, projects: [project]) }
......
# 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
...@@ -138,7 +138,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name| ...@@ -138,7 +138,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
it 'is installed' do it 'is installed' do
subject.make_externally_installed subject.make_externally_installed
expect(subject).to be_installed expect(subject).to be_externally_installed
end end
context 'helm record does not exist' do context 'helm record does not exist' do
...@@ -170,7 +170,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name| ...@@ -170,7 +170,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
it 'is installed' do it 'is installed' do
subject.make_externally_installed subject.make_externally_installed
expect(subject).to be_installed expect(subject).to be_externally_installed
end end
end end
...@@ -180,7 +180,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name| ...@@ -180,7 +180,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
it 'is installed' do it 'is installed' do
subject.make_externally_installed subject.make_externally_installed
expect(subject).to be_installed expect(subject).to be_externally_installed
end end
it 'clears #status_reason' do it 'clears #status_reason' do
...@@ -317,6 +317,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name| ...@@ -317,6 +317,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
:uninstall_errored | false :uninstall_errored | false
:uninstalled | false :uninstalled | false
:timed_out | false :timed_out | false
:externally_installed | true
end end
with_them do 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| ...@@ -41,7 +41,7 @@ RSpec.shared_examples 'parse cluster applications artifact' do |release_name|
end.to change(application_class, :count) end.to change(application_class, :count)
expect(cluster_application).to be_persisted expect(cluster_application).to be_persisted
expect(cluster_application).to be_installed expect(cluster_application).to be_externally_installed
end end
end end
...@@ -53,7 +53,7 @@ RSpec.shared_examples 'parse cluster applications artifact' do |release_name| ...@@ -53,7 +53,7 @@ RSpec.shared_examples 'parse cluster applications artifact' do |release_name|
it 'marks the application as installed' do it 'marks the application as installed' do
described_class.new(job, user).execute(artifact) described_class.new(job, user).execute(artifact)
expect(cluster_application).to be_installed expect(cluster_application).to be_externally_installed
end end
end 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