Commit 0d454802 authored by Mayra Cabrera's avatar Mayra Cabrera Committed by Kamil Trzciński

Extend Cluster Applications to allow installation of Prometheus

parent 79cbfedf
...@@ -30,6 +30,7 @@ export default class Clusters { ...@@ -30,6 +30,7 @@ export default class Clusters {
installHelmPath, installHelmPath,
installIngressPath, installIngressPath,
installRunnerPath, installRunnerPath,
installPrometheusPath,
clusterStatus, clusterStatus,
clusterStatusReason, clusterStatusReason,
helpPath, helpPath,
...@@ -44,6 +45,7 @@ export default class Clusters { ...@@ -44,6 +45,7 @@ export default class Clusters {
installHelmEndpoint: installHelmPath, installHelmEndpoint: installHelmPath,
installIngressEndpoint: installIngressPath, installIngressEndpoint: installIngressPath,
installRunnerEndpoint: installRunnerPath, installRunnerEndpoint: installRunnerPath,
installPrometheusEndpoint: installPrometheusPath,
}); });
this.toggle = this.toggle.bind(this); this.toggle = this.toggle.bind(this);
......
...@@ -67,6 +67,16 @@ export default { ...@@ -67,6 +67,16 @@ export default {
and send the results back to GitLab.`, and send the results back to GitLab.`,
)); ));
}, },
prometheusDescription() {
return sprintf(
_.escape(s__('ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications.')), {
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html", target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|Gitlab Integration'))}
</a>`,
},
false,
);
},
}, },
}; };
</script> </script>
...@@ -105,6 +115,16 @@ export default { ...@@ -105,6 +115,16 @@ export default {
:status-reason="applications.ingress.statusReason" :status-reason="applications.ingress.statusReason"
:request-status="applications.ingress.requestStatus" :request-status="applications.ingress.requestStatus"
:request-reason="applications.ingress.requestReason" :request-reason="applications.ingress.requestReason"
/>
<application-row
id="prometheus"
:title="applications.prometheus.title"
title-link="https://prometheus.io/docs/introduction/overview/"
:description="prometheusDescription"
:status="applications.prometheus.status"
:status-reason="applications.prometheus.statusReason"
:request-status="applications.prometheus.requestStatus"
:request-reason="applications.prometheus.requestReason"
/> />
<!-- NOTE: Don't forget to update `clusters.scss` min-height for this block and uncomment `application_spec` tests --> <!-- NOTE: Don't forget to update `clusters.scss` min-height for this block and uncomment `application_spec` tests -->
<!-- Add GitLab Runner row, all other plumbing is complete --> <!-- Add GitLab Runner row, all other plumbing is complete -->
......
...@@ -7,6 +7,7 @@ export default class ClusterService { ...@@ -7,6 +7,7 @@ export default class ClusterService {
helm: this.options.installHelmEndpoint, helm: this.options.installHelmEndpoint,
ingress: this.options.installIngressEndpoint, ingress: this.options.installIngressEndpoint,
runner: this.options.installRunnerEndpoint, runner: this.options.installRunnerEndpoint,
prometheus: this.options.installPrometheusEndpoint,
}; };
} }
......
...@@ -28,6 +28,13 @@ export default class ClusterStore { ...@@ -28,6 +28,13 @@ export default class ClusterStore {
requestStatus: null, requestStatus: null,
requestReason: null, requestReason: null,
}, },
prometheus: {
title: s__('ClusterIntegration|Prometheus'),
status: null,
statusReason: null,
requestStatus: null,
requestReason: null,
},
}, },
}; };
} }
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
.cluster-applications-table { .cluster-applications-table {
// Wait for the Vue to kick-in and render the applications block // Wait for the Vue to kick-in and render the applications block
min-height: 302px; min-height: 400px;
} }
.clusters-dropdown-menu { .clusters-dropdown-menu {
......
...@@ -3,32 +3,19 @@ module Clusters ...@@ -3,32 +3,19 @@ module Clusters
class Helm < ActiveRecord::Base class Helm < ActiveRecord::Base
self.table_name = 'clusters_applications_helm' self.table_name = 'clusters_applications_helm'
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus include ::Clusters::Concerns::ApplicationStatus
belongs_to :cluster, class_name: 'Clusters::Cluster', foreign_key: :cluster_id
default_value_for :version, Gitlab::Kubernetes::Helm::HELM_VERSION default_value_for :version, Gitlab::Kubernetes::Helm::HELM_VERSION
validates :cluster, presence: true
after_initialize :set_initial_status
def self.application_name
self.to_s.demodulize.underscore
end
def set_initial_status def set_initial_status
return unless not_installable? return unless not_installable?
self.status = 'installable' if cluster&.platform_kubernetes_active? self.status = 'installable' if cluster&.platform_kubernetes_active?
end end
def name
self.class.application_name
end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, true) Gitlab::Kubernetes::Helm::InstallCommand.new(name, install_helm: true)
end end
end end
end end
......
...@@ -3,41 +3,22 @@ module Clusters ...@@ -3,41 +3,22 @@ module Clusters
class Ingress < ActiveRecord::Base class Ingress < ActiveRecord::Base
self.table_name = 'clusters_applications_ingress' self.table_name = 'clusters_applications_ingress'
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus include ::Clusters::Concerns::ApplicationStatus
belongs_to :cluster, class_name: 'Clusters::Cluster', foreign_key: :cluster_id
validates :cluster, presence: true
default_value_for :ingress_type, :nginx default_value_for :ingress_type, :nginx
default_value_for :version, :nginx default_value_for :version, :nginx
after_initialize :set_initial_status
enum ingress_type: { enum ingress_type: {
nginx: 1 nginx: 1
} }
def self.application_name
self.to_s.demodulize.underscore
end
def set_initial_status
return unless not_installable?
self.status = 'installable' if cluster&.application_helm_installed?
end
def name
self.class.application_name
end
def chart def chart
'stable/nginx-ingress' 'stable/nginx-ingress'
end end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, false, chart) Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart)
end end
end end
end end
......
module Clusters
module Applications
class Prometheus < ActiveRecord::Base
VERSION = "2.0.0".freeze
self.table_name = 'clusters_applications_prometheus'
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
default_value_for :version, VERSION
def chart
'stable/prometheus'
end
def chart_values_file
"#{Rails.root}/vendor/#{name}/values.yaml"
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
end
end
end
end
...@@ -6,7 +6,8 @@ module Clusters ...@@ -6,7 +6,8 @@ module Clusters
APPLICATIONS = { APPLICATIONS = {
Applications::Helm.application_name => Applications::Helm, Applications::Helm.application_name => Applications::Helm,
Applications::Ingress.application_name => Applications::Ingress Applications::Ingress.application_name => Applications::Ingress,
Applications::Prometheus.application_name => Applications::Prometheus
}.freeze }.freeze
belongs_to :user belongs_to :user
...@@ -21,6 +22,7 @@ module Clusters ...@@ -21,6 +22,7 @@ module Clusters
has_one :application_helm, class_name: 'Clusters::Applications::Helm' has_one :application_helm, class_name: 'Clusters::Applications::Helm'
has_one :application_ingress, class_name: 'Clusters::Applications::Ingress' has_one :application_ingress, class_name: 'Clusters::Applications::Ingress'
has_one :application_prometheus, class_name: 'Clusters::Applications::Prometheus'
accepts_nested_attributes_for :provider_gcp, update_only: true accepts_nested_attributes_for :provider_gcp, update_only: true
accepts_nested_attributes_for :platform_kubernetes, update_only: true accepts_nested_attributes_for :platform_kubernetes, update_only: true
...@@ -62,7 +64,8 @@ module Clusters ...@@ -62,7 +64,8 @@ module Clusters
def applications def applications
[ [
application_helm || build_application_helm, application_helm || build_application_helm,
application_ingress || build_application_ingress application_ingress || build_application_ingress,
application_prometheus || build_application_prometheus
] ]
end end
......
module Clusters
module Concerns
module ApplicationCore
extend ActiveSupport::Concern
included do
belongs_to :cluster, class_name: 'Clusters::Cluster', foreign_key: :cluster_id
validates :cluster, presence: true
after_initialize :set_initial_status
def set_initial_status
return unless not_installable?
self.status = 'installable' if cluster&.application_helm_installed?
end
def self.application_name
self.to_s.demodulize.underscore
end
def name
self.class.application_name
end
end
end
end
end
...@@ -18,7 +18,7 @@ module Clusters ...@@ -18,7 +18,7 @@ module Clusters
end end
def helm_api def helm_api
@helm_api ||= Gitlab::Kubernetes::Helm.new(kubeclient) @helm_api ||= Gitlab::Kubernetes::Helm::Api.new(kubeclient)
end end
def install_command def install_command
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
.edit-cluster-form.js-edit-cluster-form{ data: { status_path: status_path, .edit-cluster-form.js-edit-cluster-form{ data: { status_path: status_path,
install_helm_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :helm), install_helm_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :helm),
install_ingress_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :ingress), install_ingress_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :ingress),
install_prometheus_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :prometheus),
toggle_status: @cluster.enabled? ? 'true': 'false', toggle_status: @cluster.enabled? ? 'true': 'false',
cluster_status: @cluster.status_name, cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason, cluster_status_reason: @cluster.status_reason,
......
---
title: Add Prometheus to available Cluster applications
merge_request: 15895
author:
type: added
class CreateClustersApplicationsPrometheus < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :clusters_applications_prometheus do |t|
t.references :cluster, null: false, unique: true, foreign_key: { on_delete: :cascade }
t.integer :status, null: false
t.string :version, null: false
t.text :status_reason
t.timestamps_with_timezone null: false
end
end
end
...@@ -568,6 +568,15 @@ ActiveRecord::Schema.define(version: 20171220191323) do ...@@ -568,6 +568,15 @@ ActiveRecord::Schema.define(version: 20171220191323) do
t.text "status_reason" t.text "status_reason"
end end
create_table "clusters_applications_prometheus", force: :cascade do |t|
t.integer "cluster_id", null: false
t.integer "status", null: false
t.string "version", null: false
t.text "status_reason"
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
end
create_table "container_repositories", force: :cascade do |t| create_table "container_repositories", force: :cascade do |t|
t.integer "project_id", null: false t.integer "project_id", null: false
t.string "name", null: false t.string "name", null: false
......
module Gitlab module Gitlab
module Kubernetes module Kubernetes
class Helm module Helm
HELM_VERSION = '2.7.0'.freeze HELM_VERSION = '2.7.0'.freeze
NAMESPACE = 'gitlab-managed-apps'.freeze NAMESPACE = 'gitlab-managed-apps'.freeze
INSTALL_DEPS = <<-EOS.freeze
set -eo pipefail
apk add -U ca-certificates openssl >/dev/null
wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
mv /tmp/linux-amd64/helm /usr/bin/
EOS
InstallCommand = Struct.new(:name, :install_helm, :chart) do
def pod_name
"install-#{name}"
end
end
def initialize(kubeclient)
@kubeclient = kubeclient
@namespace = Gitlab::Kubernetes::Namespace.new(NAMESPACE, kubeclient)
end
def install(command)
@namespace.ensure_exists!
@kubeclient.create_pod(pod_resource(command))
end
##
# Returns Pod phase
#
# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
#
# values: "Pending", "Running", "Succeeded", "Failed", "Unknown"
#
def installation_status(pod_name)
@kubeclient.get_pod(pod_name, @namespace.name).status.phase
end
def installation_log(pod_name)
@kubeclient.get_pod_log(pod_name, @namespace.name).body
end
def delete_installation_pod!(pod_name)
@kubeclient.delete_pod(pod_name, @namespace.name)
end
private
def pod_resource(command)
labels = { 'gitlab.org/action': 'install', 'gitlab.org/application': command.name }
metadata = { name: command.pod_name, namespace: @namespace.name, labels: labels }
container = {
name: 'helm',
image: 'alpine:3.6',
env: generate_pod_env(command),
command: %w(/bin/sh),
args: %w(-c $(COMMAND_SCRIPT))
}
spec = { containers: [container], restartPolicy: 'Never' }
::Kubeclient::Resource.new(metadata: metadata, spec: spec)
end
def generate_pod_env(command)
{
HELM_VERSION: HELM_VERSION,
TILLER_NAMESPACE: @namespace.name,
COMMAND_SCRIPT: generate_script(command)
}.map { |key, value| { name: key, value: value } }
end
def generate_script(command)
[
INSTALL_DEPS,
helm_init_command(command),
helm_install_command(command)
].join("\n")
end
def helm_init_command(command)
if command.install_helm
'helm init >/dev/null'
else
'helm init --client-only >/dev/null'
end
end
def helm_install_command(command)
return if command.chart.nil?
"helm install #{command.chart} --name #{command.name} --namespace #{@namespace.name} >/dev/null"
end
end end
end end
end end
module Gitlab
module Kubernetes
module Helm
class Api
def initialize(kubeclient)
@kubeclient = kubeclient
@namespace = Gitlab::Kubernetes::Namespace.new(Gitlab::Kubernetes::Helm::NAMESPACE, kubeclient)
end
def install(command)
@namespace.ensure_exists!
@kubeclient.create_pod(pod_resource(command))
end
##
# Returns Pod phase
#
# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
#
# values: "Pending", "Running", "Succeeded", "Failed", "Unknown"
#
def installation_status(pod_name)
@kubeclient.get_pod(pod_name, @namespace.name).status.phase
end
def installation_log(pod_name)
@kubeclient.get_pod_log(pod_name, @namespace.name).body
end
def delete_installation_pod!(pod_name)
@kubeclient.delete_pod(pod_name, @namespace.name)
end
private
def pod_resource(command)
Pod.new(command, @namespace.name, @kubeclient).generate
end
end
end
end
end
module Gitlab
module Kubernetes
module Helm
class InstallCommand
attr_reader :name, :install_helm, :chart, :chart_values_file
def initialize(name, install_helm: false, chart: false, chart_values_file: false)
@name = name
@install_helm = install_helm
@chart = chart
@chart_values_file = chart_values_file
end
def pod_name
"install-#{name}"
end
def generate_script(namespace_name)
[
install_dps_command,
init_command,
complete_command(namespace_name)
].join("\n")
end
private
def init_command
if install_helm
'helm init >/dev/null'
else
'helm init --client-only >/dev/null'
end
end
def complete_command(namespace_name)
return unless chart
"helm install #{chart} --name #{name} --namespace #{namespace_name} >/dev/null"
end
def install_dps_command
<<~HEREDOC
set -eo pipefail
apk add -U ca-certificates openssl >/dev/null
wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v#{Gitlab::Kubernetes::Helm::HELM_VERSION}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
mv /tmp/linux-amd64/helm /usr/bin/
HEREDOC
end
end
end
end
end
module Gitlab
module Kubernetes
module Helm
class Pod
def initialize(command, namespace_name, kubeclient)
@command = command
@namespace_name = namespace_name
@kubeclient = kubeclient
end
def generate
spec = { containers: [container_specification], restartPolicy: 'Never' }
if command.chart_values_file
generate_config_map
spec['volumes'] = volumes_specification
end
::Kubeclient::Resource.new(metadata: metadata, spec: spec)
end
private
attr_reader :command, :namespace_name, :kubeclient
def container_specification
container = {
name: 'helm',
image: 'alpine:3.6',
env: generate_pod_env(command),
command: %w(/bin/sh),
args: %w(-c $(COMMAND_SCRIPT))
}
container[:volumeMounts] = volume_mounts_specification if command.chart_values_file
container
end
def labels
{ 'gitlab.org/action': 'install', 'gitlab.org/application': command.name }
end
def metadata
{ name: command.pod_name, namespace: namespace_name, labels: labels }
end
def volume_mounts_specification
[{ name: 'config-volume', mountPath: '/etc/config' }]
end
def volumes_specification
[{ name: 'config-volume', configMap: { name: 'values-config' } }]
end
def generate_pod_env(command)
{
HELM_VERSION: Gitlab::Kubernetes::Helm::HELM_VERSION,
TILLER_NAMESPACE: namespace_name,
COMMAND_SCRIPT: command.generate_script(namespace_name)
}.map { |key, value| { name: key, value: value } }
end
def generate_config_map
resource = ::Kubeclient::Resource.new
resource.metadata = { name: 'values-config', namespace: namespace_name }
resource.data = YAML.load_file(command.chart_values_file)
kubeclient.create_config_map(resource)
end
end
end
end
end
...@@ -52,7 +52,7 @@ describe Projects::Clusters::ApplicationsController do ...@@ -52,7 +52,7 @@ describe Projects::Clusters::ApplicationsController do
context 'when application is already installing' do context 'when application is already installing' do
before do before do
create(:cluster_applications_helm, :installing, cluster: cluster) create(:clusters_applications_helm, :installing, cluster: cluster)
end end
it 'returns 400' do it 'returns 400' do
......
FactoryBot.define do FactoryBot.define do
factory :cluster_applications_helm, class: Clusters::Applications::Helm do factory :clusters_applications_helm, class: Clusters::Applications::Helm do
cluster factory: %i(cluster provided_by_gcp) cluster factory: %i(cluster provided_by_gcp)
trait :not_installable do trait :not_installable do
...@@ -31,5 +31,8 @@ FactoryBot.define do ...@@ -31,5 +31,8 @@ FactoryBot.define do
installing installing
updated_at ClusterWaitForAppInstallationWorker::TIMEOUT.ago updated_at ClusterWaitForAppInstallationWorker::TIMEOUT.ago
end end
factory :clusters_applications_ingress, class: Clusters::Applications::Ingress
factory :clusters_applications_prometheus, class: Clusters::Applications::Prometheus