Commit 770539d4 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch 'ak/add-elastic-cluster-app' into 'master'

Add elastic-stack as gitlab managed app

See merge request gitlab-org/gitlab!18015
parents 9071b7fa ecb16107
......@@ -37,6 +37,7 @@ export default class Clusters {
installJupyterPath,
installKnativePath,
updateKnativePath,
installElasticStackPath,
installPrometheusPath,
managePrometheusPath,
clusterEnvironmentsPath,
......@@ -86,6 +87,7 @@ export default class Clusters {
installJupyterEndpoint: installJupyterPath,
installKnativeEndpoint: installKnativePath,
updateKnativeEndpoint: updateKnativePath,
installElasticStackEndpoint: installElasticStackPath,
clusterEnvironmentsEndpoint: clusterEnvironmentsPath,
});
......
......@@ -12,6 +12,7 @@ import certManagerLogo from 'images/cluster_app_logos/cert_manager.png';
import knativeLogo from 'images/cluster_app_logos/knative.png';
import meltanoLogo from 'images/cluster_app_logos/meltano.png';
import prometheusLogo from 'images/cluster_app_logos/prometheus.png';
import elasticStackLogo from 'images/cluster_app_logos/elastic_stack.png';
import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
......@@ -91,6 +92,7 @@ export default {
knativeLogo,
meltanoLogo,
prometheusLogo,
elasticStackLogo,
}),
computed: {
isProjectCluster() {
......@@ -114,6 +116,9 @@ export default {
certManagerInstalled() {
return this.applications.cert_manager.status === APPLICATION_STATUS.INSTALLED;
},
enableClusterApplicationElasticStack() {
return gon.features && gon.features.enableClusterApplicationElasticStack;
},
ingressDescription() {
return sprintf(
_.escape(
......@@ -168,6 +173,12 @@ export default {
jupyterHostname() {
return this.applications.jupyter.hostname;
},
elasticStackInstalled() {
return this.applications.elastic_stack.status === APPLICATION_STATUS.INSTALLED;
},
elasticStackKibanaHostname() {
return this.applications.elastic_stack.kibana_hostname;
},
knative() {
return this.applications.knative;
},
......@@ -542,6 +553,75 @@ export default {
/>
</div>
</application-row>
<application-row
v-if="enableClusterApplicationElasticStack"
id="elastic_stack"
:logo-url="elasticStackLogo"
:title="applications.elastic_stack.title"
:status="applications.elastic_stack.status"
:status-reason="applications.elastic_stack.statusReason"
:request-status="applications.elastic_stack.requestStatus"
:request-reason="applications.elastic_stack.requestReason"
:version="applications.elastic_stack.version"
:chart-repo="applications.elastic_stack.chartRepo"
:update-available="applications.elastic_stack.updateAvailable"
:installed="applications.elastic_stack.installed"
:install-failed="applications.elastic_stack.installFailed"
:update-successful="applications.elastic_stack.updateSuccessful"
:update-failed="applications.elastic_stack.updateFailed"
:uninstallable="applications.elastic_stack.uninstallable"
:uninstall-successful="applications.elastic_stack.uninstallSuccessful"
:uninstall-failed="applications.elastic_stack.uninstallFailed"
:disabled="!helmInstalled"
:install-application-request-params="{
kibana_hostname: applications.elastic_stack.kibana_hostname,
}"
title-link="https://github.com/helm/charts/tree/master/stable/elastic-stack"
>
<div slot="description">
<p>
{{
s__(
`ClusterIntegration|The elastic stack collects logs from all pods in your cluster`,
)
}}
</p>
<template v-if="ingressExternalEndpoint">
<div class="form-group">
<label for="elastic-stack-kibana-hostname">{{
s__('ClusterIntegration|Kibana Hostname')
}}</label>
<div class="input-group">
<input
v-model="applications.elastic_stack.kibana_hostname"
:readonly="elasticStackInstalled"
type="text"
class="form-control js-hostname"
/>
<span class="input-group-btn">
<clipboard-button
:text="elasticStackKibanaHostname"
:title="s__('ClusterIntegration|Copy Kibana Hostname')"
class="js-clipboard-btn"
/>
</span>
</div>
<p v-if="ingressInstalled" class="form-text text-muted">
{{
s__(`ClusterIntegration|Replace this with your own hostname if you want.
If you do so, point hostname to Ingress IP Address from above.`)
}}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }}
</a>
</p>
</div>
</template>
</div>
</application-row>
</div>
</section>
</template>
......@@ -2,7 +2,16 @@
import { GlModal } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import trackUninstallButtonClickMixin from 'ee_else_ce/clusters/mixins/track_uninstall_button_click';
import { HELM, INGRESS, CERT_MANAGER, PROMETHEUS, RUNNER, KNATIVE, JUPYTER } from '../constants';
import {
HELM,
INGRESS,
CERT_MANAGER,
PROMETHEUS,
RUNNER,
KNATIVE,
JUPYTER,
ELASTIC_STACK,
} from '../constants';
const CUSTOM_APP_WARNING_TEXT = {
[HELM]: sprintf(
......@@ -28,6 +37,7 @@ const CUSTOM_APP_WARNING_TEXT = {
[JUPYTER]: s__(
'ClusterIntegration|All data not committed to GitLab will be deleted and cannot be restored.',
),
[ELASTIC_STACK]: s__('ClusterIntegration|All data will be deleted and cannot be restored.'),
};
export default {
......
......@@ -51,7 +51,17 @@ export const KNATIVE = 'knative';
export const RUNNER = 'runner';
export const CERT_MANAGER = 'cert_manager';
export const PROMETHEUS = 'prometheus';
export const ELASTIC_STACK = 'elastic_stack';
export const APPLICATIONS = [HELM, INGRESS, JUPYTER, KNATIVE, RUNNER, CERT_MANAGER, PROMETHEUS];
export const APPLICATIONS = [
HELM,
INGRESS,
JUPYTER,
KNATIVE,
RUNNER,
CERT_MANAGER,
PROMETHEUS,
ELASTIC_STACK,
];
export const INGRESS_DOMAIN_SUFFIX = '.nip.io';
......@@ -11,6 +11,7 @@ export default class ClusterService {
prometheus: this.options.installPrometheusEndpoint,
jupyter: this.options.installJupyterEndpoint,
knative: this.options.installKnativeEndpoint,
elastic_stack: this.options.installElasticStackEndpoint,
};
this.appUpdateEndpointMap = {
knative: this.options.updateKnativeEndpoint,
......
......@@ -5,6 +5,7 @@ import {
JUPYTER,
KNATIVE,
CERT_MANAGER,
ELASTIC_STACK,
RUNNER,
APPLICATION_INSTALLED_STATUSES,
APPLICATION_STATUS,
......@@ -85,6 +86,11 @@ export default class ClusterStore {
updateSuccessful: false,
updateFailed: false,
},
elastic_stack: {
...applicationInitialState,
title: s__('ClusterIntegration|Elastic Stack'),
kibana_hostname: null,
},
},
environments: [],
fetchingEnvironments: false,
......@@ -198,12 +204,11 @@ export default class ClusterStore {
this.state.applications.cert_manager.email =
this.state.applications.cert_manager.email || serverAppEntry.email;
} else if (appId === JUPYTER) {
this.state.applications.jupyter.hostname =
this.state.applications.jupyter.hostname ||
serverAppEntry.hostname ||
(this.state.applications.ingress.externalIp
? `jupyter.${this.state.applications.ingress.externalIp}.nip.io`
: '');
this.state.applications.jupyter.hostname = this.updateHostnameIfUnset(
this.state.applications.jupyter.hostname,
serverAppEntry.hostname,
'jupyter',
);
} else if (appId === KNATIVE) {
if (!this.state.applications.knative.isEditingHostName) {
this.state.applications.knative.hostname =
......@@ -216,10 +221,26 @@ export default class ClusterStore {
} else if (appId === RUNNER) {
this.state.applications.runner.version = version;
this.state.applications.runner.updateAvailable = updateAvailable;
} else if (appId === ELASTIC_STACK) {
this.state.applications.elastic_stack.kibana_hostname = this.updateHostnameIfUnset(
this.state.applications.elastic_stack.kibana_hostname,
serverAppEntry.kibana_hostname,
'kibana',
);
}
});
}
updateHostnameIfUnset(current, updated, fallback) {
return (
current ||
updated ||
(this.state.applications.ingress.externalIp
? `${fallback}.${this.state.applications.ingress.externalIp}.nip.io`
: '')
);
}
toggleFetchEnvironments(isFetching) {
this.state.fetchingEnvironments = isFetching;
}
......
......@@ -47,7 +47,7 @@ class Clusters::ApplicationsController < Clusters::BaseController
end
def cluster_application_params
params.permit(:application, :hostname, :email)
params.permit(:application, :hostname, :kibana_hostname, :email)
end
def cluster_application_destroy_params
......
......@@ -15,6 +15,9 @@ class Clusters::ClustersController < Clusters::BaseController
before_action only: [:new, :create_gcp] do
push_frontend_feature_flag(:create_eks_clusters)
end
before_action only: [:show] do
push_frontend_feature_flag(:enable_cluster_application_elastic_stack)
end
helper_method :token_in_session
......
# frozen_string_literal: true
module Clusters
module Applications
class ElasticStack < ApplicationRecord
VERSION = '1.8.0'
self.table_name = 'clusters_applications_elastic_stacks'
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
include ::Clusters::Concerns::ApplicationVersion
include ::Clusters::Concerns::ApplicationData
default_value_for :version, VERSION
def set_initial_status
return unless not_installable?
return unless cluster&.application_ingress_available?
ingress = cluster.application_ingress
self.status = status_states[:installable] if ingress.external_ip_or_hostname?
end
def chart
'stable/elastic-stack'
end
def values
content_values.to_yaml
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: 'elastic-stack',
version: VERSION,
rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files
)
end
def uninstall_command
Gitlab::Kubernetes::Helm::DeleteCommand.new(
name: 'elastic-stack',
rbac: cluster.platform_kubernetes_rbac?,
files: files,
postdelete: post_delete_script
)
end
private
def specification
{
"kibana" => {
"ingress" => {
"hosts" => [kibana_hostname],
"tls" => [{
"hosts" => [kibana_hostname],
"secretName" => "kibana-cert"
}]
}
}
}
end
def content_values
YAML.load_file(chart_values_file).deep_merge!(specification)
end
def post_delete_script
[
Gitlab::Kubernetes::KubectlCmd.delete("pvc", "--selector", "release=elastic-stack")
].compact
end
end
end
end
......@@ -40,7 +40,7 @@ module Clusters
end
def allowed_to_uninstall?
external_ip_or_hostname? && application_jupyter_nil_or_installable?
external_ip_or_hostname? && application_jupyter_nil_or_installable? && application_elastic_stack_nil_or_installable?
end
def install_command
......@@ -91,6 +91,10 @@ module Clusters
def application_jupyter_nil_or_installable?
cluster.application_jupyter.nil? || cluster.application_jupyter&.installable?
end
def application_elastic_stack_nil_or_installable?
cluster.application_elastic_stack.nil? || cluster.application_elastic_stack&.installable?
end
end
end
end
......@@ -18,7 +18,8 @@ module Clusters
Applications::Prometheus.application_name => Applications::Prometheus,
Applications::Runner.application_name => Applications::Runner,
Applications::Jupyter.application_name => Applications::Jupyter,
Applications::Knative.application_name => Applications::Knative
Applications::Knative.application_name => Applications::Knative,
Applications::ElasticStack.application_name => Applications::ElasticStack
}.merge(PROJECT_ONLY_APPLICATIONS).freeze
DEFAULT_ENVIRONMENT = '*'
KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'
......@@ -51,6 +52,7 @@ module Clusters
has_one_cluster_application :runner
has_one_cluster_application :jupyter
has_one_cluster_application :knative
has_one_cluster_application :elastic_stack
has_many :kubernetes_namespaces
......
......@@ -8,6 +8,7 @@ class ClusterApplicationEntity < Grape::Entity
expose :external_ip, if: -> (e, _) { e.respond_to?(:external_ip) }
expose :external_hostname, if: -> (e, _) { e.respond_to?(:external_hostname) }
expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) }
expose :kibana_hostname, if: -> (e, _) { e.respond_to?(:kibana_hostname) }
expose :email, if: -> (e, _) { e.respond_to?(:email) }
expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) }
expose :can_uninstall?, as: :can_uninstall
......
......@@ -19,6 +19,10 @@ module Clusters
application.hostname = params[:hostname]
end
if application.has_attribute?(:kibana_hostname)
application.kibana_hostname = params[:kibana_hostname]
end
if application.has_attribute?(:email)
application.email = params[:email]
end
......@@ -60,7 +64,7 @@ module Clusters
end
def invalid_application?
unknown_application? || (!cluster.project_type? && project_only_application?)
unknown_application? || (!cluster.project_type? && project_only_application?) || (application_name == Applications::ElasticStack.application_name && !Feature.enabled?(:enable_cluster_application_elastic_stack))
end
def unknown_application?
......
......@@ -17,6 +17,7 @@
install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter),
install_knative_path: clusterable.install_applications_cluster_path(@cluster, :knative),
update_knative_path: clusterable.update_applications_cluster_path(@cluster, :knative),
install_elastic_stack_path: clusterable.install_applications_cluster_path(@cluster, :elastic_stack),
cluster_environments_path: cluster_environments_path,
toggle_status: @cluster.enabled? ? 'true': 'false',
has_rbac: has_rbac_enabled?(@cluster) ? 'true': 'false',
......
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class CreateClustersApplicationsElasticStack < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :clusters_applications_elastic_stacks do |t|
t.timestamps_with_timezone null: false
t.references :cluster, null: false, index: false, foreign_key: { on_delete: :cascade }
t.integer :status, null: false
t.string :version, null: false, limit: 255
t.string :kibana_hostname, limit: 255
t.text :status_reason
t.index :cluster_id, unique: true
end
end
end
......@@ -1055,6 +1055,17 @@ ActiveRecord::Schema.define(version: 2019_10_17_045817) do
t.index ["cluster_id"], name: "index_clusters_applications_cert_managers_on_cluster_id", unique: true
end
create_table "clusters_applications_elastic_stacks", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.bigint "cluster_id", null: false
t.integer "status", null: false
t.string "version", limit: 255, null: false
t.string "kibana_hostname", limit: 255
t.text "status_reason"
t.index ["cluster_id"], name: "index_clusters_applications_elastic_stacks_on_cluster_id", unique: true
end
create_table "clusters_applications_helm", id: :serial, force: :cascade do |t|
t.integer "cluster_id", null: false
t.datetime "created_at", null: false
......@@ -4109,6 +4120,7 @@ ActiveRecord::Schema.define(version: 2019_10_17_045817) do
add_foreign_key "clusters", "projects", column: "management_project_id", name: "fk_f05c5e5a42", on_delete: :nullify
add_foreign_key "clusters", "users", on_delete: :nullify
add_foreign_key "clusters_applications_cert_managers", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_elastic_stacks", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_helm", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_ingress", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_jupyter", "clusters", on_delete: :cascade
......
......@@ -40,7 +40,7 @@ module Gitlab
private
def repository_update_command
'helm repo update' if repository
'helm repo update'
end
# Uses `helm upgrade --install` which means we can use this for both
......
......@@ -74,6 +74,7 @@ module Gitlab
clusters_applications_prometheus: count(::Clusters::Applications::Prometheus.available),
clusters_applications_runner: count(::Clusters::Applications::Runner.available),
clusters_applications_knative: count(::Clusters::Applications::Knative.available),
clusters_applications_elastic_stack: count(::Clusters::Applications::ElasticStack.available),
in_review_folder: count(::Environment.in_review_folder),
groups: count(Group),
issues: count(Issue),
......
......@@ -3510,6 +3510,9 @@ msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname"
msgstr ""
msgid "ClusterIntegration|Copy Kibana Hostname"
msgstr ""
msgid "ClusterIntegration|Copy Knative Endpoint"
msgstr ""
......@@ -3555,6 +3558,9 @@ msgstr ""
msgid "ClusterIntegration|Did you know?"
msgstr ""
msgid "ClusterIntegration|Elastic Stack"
msgstr ""
msgid "ClusterIntegration|Enable Cloud Run on GKE (beta)"
msgstr ""
......@@ -3675,6 +3681,9 @@ msgstr ""
msgid "ClusterIntegration|Key pair name"
msgstr ""
msgid "ClusterIntegration|Kibana Hostname"
msgstr ""
msgid "ClusterIntegration|Knative"
msgstr ""
......@@ -3978,6 +3987,9 @@ msgstr ""
msgid "ClusterIntegration|The associated private key will be deleted and cannot be restored."
msgstr ""
msgid "ClusterIntegration|The elastic stack collects logs from all pods in your cluster"
msgstr ""
msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
......
......@@ -79,6 +79,10 @@ FactoryBot.define do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
factory :clusters_applications_elastic_stack, class: Clusters::Applications::ElasticStack do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
factory :clusters_applications_prometheus, class: Clusters::Applications::Prometheus do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
......
......@@ -178,6 +178,37 @@ shared_examples "installing applications on a cluster" do
end
end
context 'when user installs Elastic Stack' do
before do
allow(ClusterInstallAppWorker).to receive(:perform_async)
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
create(:clusters_applications_helm, :installed, cluster: cluster)
create(:clusters_applications_ingress, :installed, external_ip: '127.0.0.1', cluster: cluster)
page.within('.js-cluster-application-row-elastic_stack') do
click_button 'Install'
end
end
it 'shows status transition' do
page.within('.js-cluster-application-row-elastic_stack') do
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
Clusters::Cluster.last.application_elastic_stack.make_installing!
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
Clusters::Cluster.last.application_elastic_stack.make_installed!
expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
end
expect(page).to have_content('Elastic Stack was successfully installed on your Kubernetes cluster')
end
end
context 'when user installs Ingress' do
before do
allow(ClusterInstallAppWorker).to receive(:perform_async)
......
......@@ -35,6 +35,7 @@
"external_ip": { "type": ["string", "null"] },
"external_hostname": { "type": ["string", "null"] },
"hostname": { "type": ["string", "null"] },
"kibana_hostname": { "type": ["string", "null"] },
"email": { "type": ["string", "null"] },
"update_available": { "type": ["boolean", "null"] },
"can_uninstall": { "type": "boolean" }
......
......@@ -13,6 +13,9 @@ describe('Applications', () => {
beforeEach(() => {
Applications = Vue.extend(applications);
gon.features = gon.features || {};
gon.features.enableClusterApplicationElasticStack = true;
});
afterEach(() => {
......@@ -54,6 +57,10 @@ describe('Applications', () => {
it('renders a row for Knative', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-knative')).not.toBeNull();
});
it('renders a row for Elastic Stack', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-elastic_stack')).not.toBeNull();
});
});
describe('Group cluster applications', () => {
......@@ -91,6 +98,10 @@ describe('Applications', () => {
it('renders a row for Knative', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-knative')).not.toBeNull();
});
it('renders a row for Elastic Stack', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-elastic_stack')).not.toBeNull();
});
});
describe('Instance cluster applications', () => {
......@@ -128,6 +139,10 @@ describe('Applications', () => {
it('renders a row for Knative', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-knative')).not.toBeNull();
});
it('renders a row for Elastic Stack', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-elastic_stack')).not.toBeNull();
});
});
describe('Ingress application', () => {
......@@ -168,6 +183,7 @@ describe('Applications', () => {
prometheus: { title: 'Prometheus' },
jupyter: { title: 'JupyterHub', hostname: '' },
knative: { title: 'Knative', hostname: '' },
elastic_stack: { title: 'Elastic Stack', kibana_hostname: '' },
},
});
......@@ -260,7 +276,11 @@ describe('Applications', () => {
},
});
expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual(null);
expect(
vm.$el
.querySelector('.js-cluster-application-row-jupyter .js-hostname')
.getAttribute('readonly'),
).toEqual(null);
});
});
......@@ -273,7 +293,9 @@ describe('Applications', () => {
},
});
expect(vm.$el.querySelector('.js-hostname')).toBe(null);
expect(vm.$el.querySelector('.js-cluster-application-row-jupyter .js-hostname')).toBe(
null,
);
});
});
......@@ -287,7 +309,11 @@ describe('Applications', () => {
},
});
expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual('readonly');
expect(
vm.$el
.querySelector('.js-cluster-application-row-jupyter .js-hostname')
.getAttribute('readonly'),
).toEqual('readonly');
});
});
......@@ -299,7 +325,9 @@ describe('Applications', () => {
});
it('does not render input', () => {
expect(vm.$el.querySelector('.js-hostname')).toBe(null);
expect(vm.$el.querySelector('.js-cluster-application-row-jupyter .js-hostname')).toBe(
null,
);
});
it('renders disabled install button', () => {
......@@ -361,4 +389,84 @@ describe('Applications', () => {
});
});
});
describe('Elastic Stack application', () => {
describe('with ingress installed with ip & elastic stack installable', () => {
it('renders hostname active input', () => {
vm = mountComponent(Applications, {
applications: {
...APPLICATIONS_MOCK_STATE,
ingress: {
title: 'Ingress',
status: 'installed',
externalIp: '1.1.1.1',
},
},
});
expect(
vm.$el
.querySelector('.js-cluster-application-row-elastic_stack .js-hostname')
.getAttribute('readonly'),
).toEqual(null);
});
});
describe('with ingress installed without external ip', () => {
it('does not render hostname input', () => {
vm = mountComponent(Applications, {
applications: {
...APPLICATIONS_MOCK_STATE,
ingress: { title: 'Ingress', status: 'installed' },
},
});
expect(vm.$el.querySelector('.js-cluster-application-row-elastic_stack .js-hostname')).toBe(
null,
);
});
});
describe('with ingress & elastic stack installed', () => {
it('renders readonly input', () => {
vm = mountComponent(Applications, {
applications: {
...APPLICATIONS_MOCK_STATE,
ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
elastic_stack: { title: 'Elastic Stack', status: 'installed', kibana_hostname: '' },
},
});
expect(
vm.$el
.querySelector('.js-cluster-application-row-elastic_stack .js-hostname')
.getAttribute('readonly'),
).toEqual('readonly');
});
});
describe('without ingress installed', () => {
beforeEach(() => {
vm = mountComponent(Applications, {
applications: APPLICATIONS_MOCK_STATE,
});
});
it('does not render input', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-elastic_stack .js-hostname')).toBe(
null,
);
});
it('renders disabled install button', () => {
expect(
vm.$el
.querySelector(
'.js-cluster-application-row-elastic_stack .js-cluster-application-install-button',
)
.getAttribute('disabled'),
).toEqual('disabled');
});
});
});
});
......@@ -52,6 +52,12 @@ const CLUSTERS_MOCK_DATA = {
email: 'test@example.com',
can_uninstall: false,
},
{
name: 'elastic_stack',
status: APPLICATION_STATUS.INSTALLING,
status_reason: 'Cannot connect',
can_uninstall: false,
},
],
},
},
......@@ -98,6 +104,11 @@ const CLUSTERS_MOCK_DATA = {
status_reason: 'Cannot connect',
email: 'test@example.com',
},
{
name: 'elastic_stack',
status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
},
],
},
},
......@@ -110,6 +121,7 @@ const CLUSTERS_MOCK_DATA = {
'/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/jupyter': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/knative': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/elastic_stack': {},
},
};
......@@ -131,6 +143,7 @@ const APPLICATIONS_MOCK_STATE = {
prometheus: { title: 'Prometheus' },
jupyter: { title: 'JupyterHub', status: 'installable', hostname: '' },
knative: { title: 'Knative ', status: 'installable', hostname: '' },
elastic_stack: { title: 'Elastic Stack', status: 'installable', kibana_hostname: '' },
};
export { CLUSTERS_MOCK_DATA, DEFAULT_APPLICATION_STATE, APPLICATIONS_MOCK_STATE };
......@@ -153,6 +153,18 @@ describe('Clusters Store', () => {
uninstallSuccessful: false,
uninstallFailed: false,
},
elastic_stack: {
title: 'Elastic Stack',
status: mockResponseData.applications[7].status,
installFailed: false,
statusReason: mockResponseData.applications[7].status_reason,
requestReason: null,
kibana_hostname: '',
installed: false,
uninstallable: false,
uninstallSuccessful: false,
uninstallFailed: false,
},
},
environments: [],
fetchingEnvironments: false,
......@@ -183,5 +195,16 @@ describe('Clusters Store', () => {
`jupyter.${store.state.applications.ingress.externalIp}.nip.io`,
);
});
it('sets default hostname for elastic stack when ingress has a ip address', () => {
const mockResponseData =
CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data;
store.updateStateFromServer(mockResponseData);
expect(store.state.applications.elastic_stack.kibana_hostname).toEqual(
`kibana.${store.state.applications.ingress.externalIp}.nip.io`,
);
});
});
});
......@@ -86,33 +86,6 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
end
end
context 'when there is no repository' do
let(:repository) { nil }
it_behaves_like 'helm commands' do
let(:commands) do
<<~EOS
helm init --upgrade
for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)
#{helm_install_command}
EOS
end
let(:helm_install_command) do
<<~EOS.squish
helm upgrade app-name chart-name
--install
--reset-values
#{tls_flags}
--version 1.2.3
--set rbac.create\\=false,rbac.enabled\\=false
--namespace gitlab-managed-apps
-f /data/helm/app-name/config/values.yaml
EOS
end
end
end
context 'when there is a pre-install script' do
let(:preinstall) { ['/bin/date', '/bin/true'] }
......
......@@ -32,6 +32,7 @@ describe Gitlab::UsageData do
create(:clusters_applications_prometheus, :installed, cluster: gcp_cluster)
create(:clusters_applications_runner, :installed, cluster: gcp_cluster)
create(:clusters_applications_knative, :installed, cluster: gcp_cluster)
create(:clusters_applications_elastic_stack, :installed, cluster: gcp_cluster)
ProjectFeature.first.update_attribute('repository_access_level', 0)
end
......@@ -120,6 +121,7 @@ describe Gitlab::UsageData do
clusters_applications_prometheus
clusters_applications_runner
clusters_applications_knative
clusters_applications_elastic_stack
in_review_folder
groups
issues
......@@ -190,6 +192,7 @@ describe Gitlab::UsageData do
expect(count_data[:clusters_applications_prometheus]).to eq(1)
expect(count_data[:clusters_applications_runner]).to eq(1)
expect(count_data[:clusters_applications_knative]).to eq(1)
expect(count_data[:clusters_applications_elastic_stack]).to eq(1)
end
it 'works when queries time out' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::Applications::ElasticStack do
include_examples 'cluster application core specs', :clusters_applications_elastic_stack
include_examples 'cluster application status specs', :clusters_applications_elastic_stack
include_examples 'cluster application version specs', :clusters_applications_elastic_stack
include_examples 'cluster application helm specs', :clusters_applications_elastic_stack
describe '#can_uninstall?' do
let(:ingress) { create(:clusters_applications_ingress, :installed, external_hostname: 'localhost.localdomain') }
let(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: ingress.cluster) }
subject { elastic_stack.can_uninstall? }
it { is_expected.to be_truthy }
end
describe '#set_initial_status' do
before do
elastic_stack.set_initial_status
end
context 'when ingress is not installed' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
let(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: cluster) }
it { expect(elastic_stack).to be_not_installable }
end
context 'when ingress is installed and external_ip is assigned' do
let(:ingress) { create(:clusters_applications_ingress, :installed, external_ip: '127.0.0.1') }
let(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: ingress.cluster) }
it { expect(elastic_stack).to be_installable }
end
context 'when ingress is installed and external_hostname is assigned' do
let(:ingress) { create(:clusters_applications_ingress, :installed, external_hostname: 'localhost.localdomain') }
let(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: ingress.cluster) }
it { expect(elastic_stack).to be_installable }
end
end
describe '#install_command' do
let!(:ingress) { create(:clusters_applications_ingress, :installed, external_ip: '127.0.0.1') }
let!(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: ingress.cluster) }
subject { elastic_stack.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
it 'is initialized with elastic stack arguments' do
expect(subject.name).to eq('elastic-stack')
expect(subject.chart).to eq('stable/elastic-stack')
expect(subject.version).to eq('1.8.0')
expect(subject).to be_rbac
expect(subject.files).to eq(elastic_stack.files)
end
context 'on a non rbac enabled cluster' do
before do
elastic_stack.cluster.platform_kubernetes.abac!
end
it { is_expected.not_to be_rbac }
end
context 'application failed to install previously' do
let(:elastic_stack) { create(:clusters_applications_elastic_stack, :errored, version: '0.0.1') }
it 'is initialized with the locked version' do
expect(subject.version).to eq('1.8.0')
end
end
end
describe '#uninstall_command' do
let!(:ingress) { create(:clusters_applications_ingress, :installed, external_ip: '127.0.0.1') }
let!(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: ingress.cluster) }
subject { elastic_stack.uninstall_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
it 'is initialized with elastic stack arguments' do
expect(subject.name).to eq('elastic-stack')
expect(subject).to be_rbac
expect(subject.files).to eq(elastic_stack.files)
end
it 'specifies a post delete command to remove custom resource definitions' do
expect(subject.postdelete).to eq([
'kubectl delete pvc --selector release\\=elastic-stack'
])
end
end
describe '#files' do
let!(:ingress) { create(:clusters_applications_ingress, :installed, external_ip: '127.0.0.1') }
let!(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: ingress.cluster) }
let(:values) { subject[:'values.yaml'] }
subject { elastic_stack.files }
it 'includes elastic stack specific keys in the values.yaml file' do
expect(values).to include('ELASTICSEARCH_HOSTS')
end
end
end
......@@ -21,7 +21,7 @@ describe Clusters::Applications::Ingress do
describe '#can_uninstall?' do
subject { ingress.can_uninstall? }
it 'returns true if application_jupyter_nil_or_installable? AND external_ip_or_hostname? are true' do
it 'returns true if external ip is set and no application exists' do
ingress.external_ip = 'IP'
is_expected.to be_truthy
......@@ -33,6 +33,12 @@ describe Clusters::Applications::Ingress do
is_expected.to be_falsey
end
it 'returns false if application_elastic_stack_nil_or_installable? is false' do
create(:clusters_applications_elastic_stack, :installed, cluster: ingress.cluster)
is_expected.to be_falsey
end
it 'returns false if external_ip_or_hostname? is false' do
is_expected.to be_falsey
end
......
......@@ -508,9 +508,10 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
let!(:runner) { create(:clusters_applications_runner, cluster: cluster) }
let!(:jupyter) { create(:clusters_applications_jupyter, cluster: cluster) }
let!(:knative) { create(:clusters_applications_knative, cluster: cluster) }
let!(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: cluster) }
it 'returns a list of created applications' do
is_expected.to contain_exactly(helm, ingress, cert_manager, prometheus, runner, jupyter, knative)
is_expected.to contain_exactly(helm, ingress, cert_manager, prometheus, runner, jupyter, knative, elastic_stack)
end
end
end
......
......@@ -132,6 +132,34 @@ describe Clusters::Applications::CreateService do
expect(subject.hostname).to eq('example.com')
end
end
context 'elastic stack application' do
let(:params) do
{
application: 'elastic_stack',
kibana_hostname: 'example.com'
}
end
before do
create(:clusters_applications_ingress, :installed, external_ip: "127.0.0.0", cluster: cluster)
expect_any_instance_of(Clusters::Applications::ElasticStack)
.to receive(:make_scheduled!)
.and_call_original
end
it 'creates the application' do
expect do
subject
cluster.reload
end.to change(cluster, :application_elastic_stack)
end
it 'sets the kibana_hostname' do
expect(subject.kibana_hostname).to eq('example.com')
end
end
end
context 'invalid application' do
......
......@@ -8,10 +8,6 @@ shared_examples 'cluster application helm specs' do |application_name|
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
it 'has the application name' do
expect(subject.name).to eq(application.name)
end
it 'has files' do
expect(subject.files).to eq(application.files)
end
......
elasticsearch:
enabled: true
cluster:
env:
MINIMUM_MASTER_NODES: "1"
master:
replicas: 2
client:
replicas: 1
data:
replicas: 1
kibana:
enabled: true
env:
ELASTICSEARCH_HOSTS: http://elastic-stack-elasticsearch-client:9200
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: "nginx"
kubernetes.io/tls-acme: "true"
logstash:
enabled: false
filebeat:
enabled: true
config:
output.file.enabled: false
output.elasticsearch:
enabled: true
hosts: ["http://elastic-stack-elasticsearch-client:9200"]
fluentd:
enabled: false
fluent-bit:
enabled: false
nginx-ldapauth-proxy:
enabled: false
elasticsearch-curator:
enabled: false
elasticsearch-exporter:
enabled: false
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