Commit e2cdb457 authored by Adam Hegyi's avatar Adam Hegyi

Merge branch 'add-helm3-support-for-cluster-apps' into 'master'

Use Helm 3 for GitLab-managed apps in new clusters [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!46267
parents 739f0a51 6eed9844
......@@ -30,7 +30,7 @@ module Clusters
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
helm_command_module::InstallCommand.new(
name: 'certmanager',
repository: repository,
version: VERSION,
......@@ -43,7 +43,7 @@ module Clusters
end
def uninstall_command
Gitlab::Kubernetes::Helm::DeleteCommand.new(
helm_command_module::DeleteCommand.new(
name: 'certmanager',
rbac: cluster.platform_kubernetes_rbac?,
files: files,
......
......@@ -29,7 +29,7 @@ module Clusters
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
helm_command_module::InstallCommand.new(
name: 'crossplane',
repository: repository,
version: VERSION,
......
......@@ -26,7 +26,7 @@ module Clusters
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
helm_command_module::InstallCommand.new(
name: 'elastic-stack',
version: VERSION,
rbac: cluster.platform_kubernetes_rbac?,
......@@ -39,7 +39,7 @@ module Clusters
end
def uninstall_command
Gitlab::Kubernetes::Helm::DeleteCommand.new(
helm_command_module::DeleteCommand.new(
name: 'elastic-stack',
rbac: cluster.platform_kubernetes_rbac?,
files: files,
......@@ -96,7 +96,7 @@ module Clusters
def post_install_script
[
"timeout -t60 sh /data/helm/elastic-stack/config/wait-for-elasticsearch.sh http://elastic-stack-elasticsearch-master:9200"
"timeout 60 sh /data/helm/elastic-stack/config/wait-for-elasticsearch.sh http://elastic-stack-elasticsearch-master:9200"
]
end
......@@ -116,7 +116,7 @@ module Clusters
# Chart version 3.0.0 moves to our own chart at https://gitlab.com/gitlab-org/charts/elastic-stack
# and is not compatible with pre-existing resources. We first remove them.
[
Gitlab::Kubernetes::Helm::DeleteCommand.new(
helm_command_module::DeleteCommand.new(
name: 'elastic-stack',
rbac: cluster.platform_kubernetes_rbac?,
files: files
......
......@@ -30,7 +30,7 @@ module Clusters
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
helm_command_module::InstallCommand.new(
name: 'fluentd',
repository: repository,
version: VERSION,
......
......@@ -4,6 +4,8 @@ require 'openssl'
module Clusters
module Applications
# DEPRECATED: This model represents the Helm 2 Tiller server, and is no longer being actively used.
# It is being kept around for a potential cleanup of the unused Tiller server.
class Helm < ApplicationRecord
self.table_name = 'clusters_applications_helm'
......@@ -49,7 +51,7 @@ module Clusters
end
def install_command
Gitlab::Kubernetes::Helm::InitCommand.new(
Gitlab::Kubernetes::Helm::V2::InitCommand.new(
name: name,
files: files,
rbac: cluster.platform_kubernetes_rbac?
......@@ -57,7 +59,7 @@ module Clusters
end
def uninstall_command
Gitlab::Kubernetes::Helm::ResetCommand.new(
Gitlab::Kubernetes::Helm::V2::ResetCommand.new(
name: name,
files: files,
rbac: cluster.platform_kubernetes_rbac?
......@@ -86,19 +88,19 @@ module Clusters
end
def create_keys_and_certs
ca_cert = Gitlab::Kubernetes::Helm::Certificate.generate_root
ca_cert = Gitlab::Kubernetes::Helm::V2::Certificate.generate_root
self.ca_key = ca_cert.key_string
self.ca_cert = ca_cert.cert_string
end
def tiller_cert
@tiller_cert ||= ca_cert_obj.issue(expires_in: Gitlab::Kubernetes::Helm::Certificate::INFINITE_EXPIRY)
@tiller_cert ||= ca_cert_obj.issue(expires_in: Gitlab::Kubernetes::Helm::V2::Certificate::INFINITE_EXPIRY)
end
def ca_cert_obj
return unless has_ssl?
Gitlab::Kubernetes::Helm::Certificate
Gitlab::Kubernetes::Helm::V2::Certificate
.from_strings(ca_key, ca_cert)
end
end
......
......@@ -62,7 +62,7 @@ module Clusters
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
helm_command_module::InstallCommand.new(
name: name,
repository: repository,
version: VERSION,
......
......@@ -39,7 +39,7 @@ module Clusters
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
helm_command_module::InstallCommand.new(
name: name,
version: VERSION,
rbac: cluster.platform_kubernetes_rbac?,
......
......@@ -70,7 +70,7 @@ module Clusters
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
helm_command_module::InstallCommand.new(
name: name,
version: VERSION,
rbac: cluster.platform_kubernetes_rbac?,
......@@ -94,7 +94,7 @@ module Clusters
end
def uninstall_command
Gitlab::Kubernetes::Helm::DeleteCommand.new(
helm_command_module::DeleteCommand.new(
name: name,
rbac: cluster.platform_kubernetes_rbac?,
files: files,
......
......@@ -67,7 +67,7 @@ module Clusters
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
helm_command_module::InstallCommand.new(
name: name,
repository: repository,
version: VERSION,
......@@ -79,7 +79,7 @@ module Clusters
end
def patch_command(values)
::Gitlab::Kubernetes::Helm::PatchCommand.new(
helm_command_module::PatchCommand.new(
name: name,
repository: repository,
version: version,
......@@ -90,7 +90,7 @@ module Clusters
end
def uninstall_command
Gitlab::Kubernetes::Helm::DeleteCommand.new(
helm_command_module::DeleteCommand.new(
name: name,
rbac: cluster.platform_kubernetes_rbac?,
files: files,
......
......@@ -30,7 +30,7 @@ module Clusters
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
helm_command_module::InstallCommand.new(
name: name,
version: VERSION,
rbac: cluster.platform_kubernetes_rbac?,
......
......@@ -79,6 +79,9 @@ module Clusters
validates :cluster_type, presence: true
validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true }
validates :namespace_per_environment, inclusion: { in: [true, false] }
validates :helm_major_version, inclusion: { in: [2, 3] }
default_value_for :helm_major_version, 3
validate :restrict_modification, on: :update
validate :no_groups, unless: :group_type?
......
......@@ -12,6 +12,17 @@ module Clusters
after_initialize :set_initial_status
def helm_command_module
case cluster.helm_major_version
when 3
Gitlab::Kubernetes::Helm::V3
when 2
Gitlab::Kubernetes::Helm::V2
else
raise "Invalid Helm major version"
end
end
def set_initial_status
return unless not_installable?
......
......@@ -4,7 +4,7 @@ module Clusters
module Concerns
module ApplicationData
def uninstall_command
Gitlab::Kubernetes::Helm::DeleteCommand.new(
helm_command_module::DeleteCommand.new(
name: name,
rbac: cluster.platform_kubernetes_rbac?,
files: files
......
---
title: Use Helm 3 by default for GitLab-managed apps in new clusters
merge_request: 46267
author:
type: changed
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddHelmMajorVersionToClusters < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column(:clusters, :helm_major_version, :integer, default: 2, null: false)
end
end
5520cca016af07fb2e009c0e3254362f106a9cc808cbb61e280221be82be1b25
\ No newline at end of file
......@@ -10967,7 +10967,8 @@ CREATE TABLE clusters (
namespace_per_environment boolean DEFAULT true NOT NULL,
management_project_id integer,
cleanup_status smallint DEFAULT 1 NOT NULL,
cleanup_status_reason text
cleanup_status_reason text,
helm_major_version integer DEFAULT 2 NOT NULL
);
CREATE TABLE clusters_applications_cert_managers (
......
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
class BaseCommand
attr_reader :name, :files
def initialize(rbac:, name:, files:)
@rbac = rbac
@name = name
@files = files
end
def rbac?
@rbac
end
def pod_resource
pod_service_account_name = rbac? ? service_account_name : nil
Gitlab::Kubernetes::Helm::Pod.new(self, namespace, service_account_name: pod_service_account_name).generate
end
def generate_script
<<~HEREDOC
set -xeo pipefail
HEREDOC
end
def pod_name
"install-#{name}"
end
def config_map_resource
Gitlab::Kubernetes::ConfigMap.new(name, files).generate
end
def service_account_resource
return unless rbac?
Gitlab::Kubernetes::ServiceAccount.new(service_account_name, namespace).generate
end
def cluster_role_binding_resource
return unless rbac?
subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: namespace }]
Gitlab::Kubernetes::ClusterRoleBinding.new(
cluster_role_binding_name,
cluster_role_name,
subjects
).generate
end
def file_names
files.keys
end
private
def files_dir
"/data/helm/#{name}/config"
end
def namespace
Gitlab::Kubernetes::Helm::NAMESPACE
end
def service_account_name
Gitlab::Kubernetes::Helm::SERVICE_ACCOUNT
end
def cluster_role_binding_name
Gitlab::Kubernetes::Helm::CLUSTER_ROLE_BINDING
end
def cluster_role_name
Gitlab::Kubernetes::Helm::CLUSTER_ROLE
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
class Certificate
INFINITE_EXPIRY = 1000.years
SHORT_EXPIRY = 30.minutes
attr_reader :key, :cert
def key_string
@key.to_s
end
def cert_string
@cert.to_pem
end
def self.from_strings(key_string, cert_string)
key = OpenSSL::PKey::RSA.new(key_string)
cert = OpenSSL::X509::Certificate.new(cert_string)
new(key, cert)
end
def self.generate_root
_issue(signed_by: nil, expires_in: INFINITE_EXPIRY, certificate_authority: true)
end
def issue(expires_in: SHORT_EXPIRY)
self.class._issue(signed_by: self, expires_in: expires_in, certificate_authority: false)
end
private
def self._issue(signed_by:, expires_in:, certificate_authority:)
key = OpenSSL::PKey::RSA.new(4096)
public_key = key.public_key
subject = OpenSSL::X509::Name.parse("/C=US")
cert = OpenSSL::X509::Certificate.new
cert.subject = subject
cert.issuer = signed_by&.cert&.subject || subject
cert.not_before = Time.now
cert.not_after = expires_in.from_now
cert.public_key = public_key
cert.serial = 0x0
cert.version = 2
if certificate_authority
extension_factory = OpenSSL::X509::ExtensionFactory.new
extension_factory.subject_certificate = cert
extension_factory.issuer_certificate = cert
cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
cert.add_extension(extension_factory.create_extension('keyUsage', 'cRLSign,keyCertSign', true))
end
cert.sign(signed_by&.key || key, OpenSSL::Digest::SHA256.new)
new(key, cert)
end
def initialize(key, cert)
@key = key
@cert = cert
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
module ClientCommand
def init_command
<<~SHELL.chomp
export HELM_HOST="localhost:44134"
tiller -listen ${HELM_HOST} -alsologtostderr &
helm init --client-only
SHELL
end
def repository_command
['helm', 'repo', 'add', name, repository].shelljoin if repository
end
private
def repository_update_command
'helm repo update'
end
def optional_tls_flags
return [] unless files.key?(:'ca.pem')
[
'--tls',
'--tls-ca-cert', "#{files_dir}/ca.pem",
'--tls-cert', "#{files_dir}/cert.pem",
'--tls-key', "#{files_dir}/key.pem"
]
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
class DeleteCommand < BaseCommand
include ClientCommand
attr_reader :predelete, :postdelete
def initialize(predelete: nil, postdelete: nil, **args)
super(**args)
@predelete = predelete
@postdelete = postdelete
end
def generate_script
super + [
init_command,
predelete,
delete_command,
postdelete
].compact.join("\n")
end
def pod_name
"uninstall-#{name}"
end
def delete_command
['helm', 'delete', '--purge', name].shelljoin
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
class InitCommand < BaseCommand
def generate_script
super + [
init_helm_command
].join("\n")
end
private
def init_helm_command
command = %w[helm init] + init_command_flags
command.shelljoin
end
def init_command_flags
tls_flags + optional_service_account_flag
end
def tls_flags
[
'--tiller-tls',
'--tiller-tls-verify',
'--tls-ca-cert', "#{files_dir}/ca.pem",
'--tiller-tls-cert', "#{files_dir}/cert.pem",
'--tiller-tls-key', "#{files_dir}/key.pem"
]
end
def optional_service_account_flag
return [] unless rbac?
['--service-account', service_account_name]
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
class InstallCommand < BaseCommand
include ClientCommand
attr_reader :chart, :repository, :preinstall, :postinstall
attr_accessor :version
def initialize(chart:, version: nil, repository: nil, preinstall: nil, postinstall: nil, **args)
super(**args)
@chart = chart
@version = version
@repository = repository
@preinstall = preinstall
@postinstall = postinstall
end
def generate_script
super + [
init_command,
repository_command,
repository_update_command,
preinstall,
install_command,
postinstall
].compact.join("\n")
end
private
# Uses `helm upgrade --install` which means we can use this for both
# installation and uprade of applications
def install_command
command = ['helm', 'upgrade', name, chart] +
install_flag +
rollback_support_flag +
reset_values_flag +
optional_version_flag +
rbac_create_flag +
namespace_flag +
value_flag
command.shelljoin
end
def install_flag
['--install']
end
def reset_values_flag
['--reset-values']
end
def value_flag
['-f', "/data/helm/#{name}/config/values.yaml"]
end
def namespace_flag
['--namespace', Gitlab::Kubernetes::Helm::NAMESPACE]
end
def rbac_create_flag
if rbac?
%w[--set rbac.create=true,rbac.enabled=true]
else
%w[--set rbac.create=false,rbac.enabled=false]
end
end
def optional_version_flag
return [] unless version
['--version', version]
end
def rollback_support_flag
['--atomic', '--cleanup-on-fail']
end
end
end
end
end
# frozen_string_literal: true
# PatchCommand is for updating values in installed charts without overwriting
# existing values.
module Gitlab
module Kubernetes
module Helm
class PatchCommand < BaseCommand
include ClientCommand
attr_reader :chart, :repository
attr_accessor :version
def initialize(chart:, version:, repository: nil, **args)
super(**args)
# version is mandatory to prevent chart mismatches
# we do not want our values interpreted in the context of the wrong version
raise ArgumentError, 'version is required' if version.blank?
@chart = chart
@version = version
@repository = repository
end
def generate_script
super + [
init_command,
repository_command,
repository_update_command,
upgrade_command
].compact.join("\n")
end
private
def upgrade_command
command = ['helm', 'upgrade', name, chart] +
reuse_values_flag +
version_flag +
namespace_flag +
value_flag
command.shelljoin
end
def reuse_values_flag
['--reuse-values']
end
def value_flag
['-f', "/data/helm/#{name}/config/values.yaml"]
end
def namespace_flag
['--namespace', Gitlab::Kubernetes::Helm::NAMESPACE]
end
def version_flag
['--version', version]
end
end
end
end
end
......@@ -27,7 +27,7 @@ module Gitlab
def container_specification
{
name: 'helm',
image: "registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/#{Gitlab::Kubernetes::Helm::HELM_VERSION}-kube-#{Gitlab::Kubernetes::Helm::KUBECTL_VERSION}",
image: "registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/#{command.class::HELM_VERSION}-kube-#{Gitlab::Kubernetes::Helm::KUBECTL_VERSION}-alpine-3.12",
env: generate_pod_env(command),
command: %w(/bin/sh),
args: %w(-c $(COMMAND_SCRIPT))
......@@ -50,11 +50,10 @@ module Gitlab
end
def generate_pod_env(command)
{
HELM_VERSION: Gitlab::Kubernetes::Helm::HELM_VERSION,
TILLER_NAMESPACE: namespace_name,
command.env.merge(
HELM_VERSION: command.class::HELM_VERSION,
COMMAND_SCRIPT: command.generate_script
}.map { |key, value| { name: key, value: value } }
).map { |key, value| { name: key, value: value } }
end
def volumes_specification
......
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
class ResetCommand < BaseCommand
include ClientCommand
def generate_script
super + [
reset_helm_command,
delete_tiller_replicaset,
delete_tiller_clusterrolebinding
].join("\n")
end
def pod_name
"uninstall-#{name}"
end
private
# This method can be delete once we upgrade Helm to > 12.13.0
# https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/27096#note_159695900
#
# Tracking this method to be removed here:
# https://gitlab.com/gitlab-org/gitlab-foss/issues/52791#note_199374155
def delete_tiller_replicaset
delete_args = %w[replicaset -n gitlab-managed-apps -l name=tiller]
Gitlab::Kubernetes::KubectlCmd.delete(*delete_args)
end
def delete_tiller_clusterrolebinding
delete_args = %w[clusterrolebinding tiller-admin]
Gitlab::Kubernetes::KubectlCmd.delete(*delete_args)
end
def reset_helm_command
command = %w[helm reset] + optional_tls_flags
command.shelljoin
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
module V2
class BaseCommand
attr_reader :name, :files
HELM_VERSION = '2.16.9'
def initialize(rbac:, name:, files:)
@rbac = rbac
@name = name
@files = files
end
def env
{ TILLER_NAMESPACE: namespace }
end
def rbac?
@rbac
end
def pod_resource
pod_service_account_name = rbac? ? service_account_name : nil
Gitlab::Kubernetes::Helm::Pod.new(self, namespace, service_account_name: pod_service_account_name).generate
end
def generate_script
<<~HEREDOC
set -xeo pipefail
HEREDOC
end
def pod_name
"install-#{name}"
end
def config_map_resource
Gitlab::Kubernetes::ConfigMap.new(name, files).generate
end
def service_account_resource
return unless rbac?
Gitlab::Kubernetes::ServiceAccount.new(service_account_name, namespace).generate
end
def cluster_role_binding_resource
return unless rbac?
subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: namespace }]
Gitlab::Kubernetes::ClusterRoleBinding.new(
cluster_role_binding_name,
cluster_role_name,
subjects
).generate
end
def file_names
files.keys
end
private
def files_dir
"/data/helm/#{name}/config"
end
def namespace
Gitlab::Kubernetes::Helm::NAMESPACE
end
def service_account_name
Gitlab::Kubernetes::Helm::SERVICE_ACCOUNT
end
def cluster_role_binding_name
Gitlab::Kubernetes::Helm::CLUSTER_ROLE_BINDING
end
def cluster_role_name
Gitlab::Kubernetes::Helm::CLUSTER_ROLE
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
module V2
class Certificate
INFINITE_EXPIRY = 1000.years
SHORT_EXPIRY = 30.minutes
attr_reader :key, :cert
def key_string
@key.to_s
end
def cert_string
@cert.to_pem
end
def self.from_strings(key_string, cert_string)
key = OpenSSL::PKey::RSA.new(key_string)
cert = OpenSSL::X509::Certificate.new(cert_string)
new(key, cert)
end
def self.generate_root
_issue(signed_by: nil, expires_in: INFINITE_EXPIRY, certificate_authority: true)
end
def issue(expires_in: SHORT_EXPIRY)
self.class._issue(signed_by: self, expires_in: expires_in, certificate_authority: false)
end
private
def self._issue(signed_by:, expires_in:, certificate_authority:)
key = OpenSSL::PKey::RSA.new(4096)
public_key = key.public_key
subject = OpenSSL::X509::Name.parse("/C=US")
cert = OpenSSL::X509::Certificate.new
cert.subject = subject
cert.issuer = signed_by&.cert&.subject || subject
cert.not_before = Time.now.utc
cert.not_after = expires_in.from_now.utc
cert.public_key = public_key
cert.serial = 0x0
cert.version = 2
if certificate_authority
extension_factory = OpenSSL::X509::ExtensionFactory.new
extension_factory.subject_certificate = cert
extension_factory.issuer_certificate = cert
cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
cert.add_extension(extension_factory.create_extension('keyUsage', 'cRLSign,keyCertSign', true))
end
cert.sign(signed_by&.key || key, OpenSSL::Digest::SHA256.new)
new(key, cert)
end
def initialize(key, cert)
@key = key
@cert = cert
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
module V2
module ClientCommand
def init_command
<<~SHELL.chomp
export HELM_HOST="localhost:44134"
tiller -listen ${HELM_HOST} -alsologtostderr &
helm init --client-only
SHELL
end
def repository_command
['helm', 'repo', 'add', name, repository].shelljoin if repository
end
private
def repository_update_command
'helm repo update'
end
def optional_tls_flags
return [] unless files.key?(:'ca.pem')
[
'--tls',
'--tls-ca-cert', "#{files_dir}/ca.pem",
'--tls-cert', "#{files_dir}/cert.pem",
'--tls-key', "#{files_dir}/key.pem"
]
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
module V2
class DeleteCommand < BaseCommand
include ClientCommand
attr_reader :predelete, :postdelete
def initialize(predelete: nil, postdelete: nil, **args)
super(**args)
@predelete = predelete
@postdelete = postdelete
end
def generate_script
super + [
init_command,
predelete,
delete_command,
postdelete
].compact.join("\n")
end
def pod_name
"uninstall-#{name}"
end
def delete_command
['helm', 'delete', '--purge', name].shelljoin
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
module V2
class InitCommand < BaseCommand
def generate_script
super + [
init_helm_command
].join("\n")
end
private
def init_helm_command
command = %w[helm init] + init_command_flags
command.shelljoin
end
def init_command_flags
tls_flags + optional_service_account_flag
end
def tls_flags
[
'--tiller-tls',
'--tiller-tls-verify',
'--tls-ca-cert', "#{files_dir}/ca.pem",
'--tiller-tls-cert', "#{files_dir}/cert.pem",
'--tiller-tls-key', "#{files_dir}/key.pem"
]
end
def optional_service_account_flag
return [] unless rbac?
['--service-account', service_account_name]
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
module V2
class InstallCommand < BaseCommand
include ClientCommand
attr_reader :chart, :repository, :preinstall, :postinstall
attr_accessor :version
def initialize(chart:, version: nil, repository: nil, preinstall: nil, postinstall: nil, **args)
super(**args)
@chart = chart
@version = version
@repository = repository
@preinstall = preinstall
@postinstall = postinstall
end
def generate_script
super + [
init_command,
repository_command,
repository_update_command,
preinstall,
install_command,
postinstall
].compact.join("\n")
end
private
# Uses `helm upgrade --install` which means we can use this for both
# installation and uprade of applications
def install_command
command = ['helm', 'upgrade', name, chart] +
install_flag +
rollback_support_flag +
reset_values_flag +
optional_version_flag +
rbac_create_flag +
namespace_flag +
value_flag
command.shelljoin
end
def install_flag
['--install']
end
def reset_values_flag
['--reset-values']
end
def value_flag
['-f', "/data/helm/#{name}/config/values.yaml"]
end
def namespace_flag
['--namespace', Gitlab::Kubernetes::Helm::NAMESPACE]
end
def rbac_create_flag
if rbac?
%w[--set rbac.create=true,rbac.enabled=true]
else
%w[--set rbac.create=false,rbac.enabled=false]
end
end
def optional_version_flag
return [] unless version
['--version', version]
end
def rollback_support_flag
['--atomic', '--cleanup-on-fail']
end
end
end
end
end
end
# frozen_string_literal: true
# PatchCommand is for updating values in installed charts without overwriting
# existing values.
module Gitlab
module Kubernetes
module Helm
module V2
class PatchCommand < BaseCommand
include ClientCommand
attr_reader :chart, :repository
attr_accessor :version
def initialize(chart:, version:, repository: nil, **args)
super(**args)
# version is mandatory to prevent chart mismatches
# we do not want our values interpreted in the context of the wrong version
raise ArgumentError, 'version is required' if version.blank?
@chart = chart
@version = version
@repository = repository
end
def generate_script
super + [
init_command,
repository_command,
repository_update_command,
upgrade_command
].compact.join("\n")
end
private
def upgrade_command
command = ['helm', 'upgrade', name, chart] +
reuse_values_flag +
version_flag +
namespace_flag +
value_flag
command.shelljoin
end
def reuse_values_flag
['--reuse-values']
end
def value_flag
['-f', "/data/helm/#{name}/config/values.yaml"]
end
def namespace_flag
['--namespace', Gitlab::Kubernetes::Helm::NAMESPACE]
end
def version_flag
['--version', version]
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
module V2
class ResetCommand < BaseCommand
include ClientCommand
def generate_script
super + [
reset_helm_command,
delete_tiller_replicaset,
delete_tiller_clusterrolebinding
].join("\n")
end
def pod_name
"uninstall-#{name}"
end
private
# This method can be delete once we upgrade Helm to > 12.13.0
# https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/27096#note_159695900
#
# Tracking this method to be removed here:
# https://gitlab.com/gitlab-org/gitlab-foss/issues/52791#note_199374155
def delete_tiller_replicaset
delete_args = %w[replicaset -n gitlab-managed-apps -l name=tiller]
Gitlab::Kubernetes::KubectlCmd.delete(*delete_args)
end
def delete_tiller_clusterrolebinding
delete_args = %w[clusterrolebinding tiller-admin]
Gitlab::Kubernetes::KubectlCmd.delete(*delete_args)
end
def reset_helm_command
command = %w[helm reset] + optional_tls_flags
command.shelljoin
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
module V3
class BaseCommand
attr_reader :name, :files
HELM_VERSION = '3.2.4'
def initialize(rbac:, name:, files:)
@rbac = rbac
@name = name
@files = files
end
def env
{}
end
def rbac?
@rbac
end
def pod_resource
pod_service_account_name = rbac? ? service_account_name : nil
Gitlab::Kubernetes::Helm::Pod.new(self, namespace, service_account_name: pod_service_account_name).generate
end
def generate_script
<<~HEREDOC
set -xeo pipefail
HEREDOC
end
def pod_name
"install-#{name}"
end
def config_map_resource
Gitlab::Kubernetes::ConfigMap.new(name, files).generate
end
def service_account_resource
return unless rbac?
Gitlab::Kubernetes::ServiceAccount.new(service_account_name, namespace).generate
end
def cluster_role_binding_resource
return unless rbac?
subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: namespace }]
Gitlab::Kubernetes::ClusterRoleBinding.new(
cluster_role_binding_name,
cluster_role_name,
subjects
).generate
end
def file_names
files.keys
end
def repository_command
['helm', 'repo', 'add', name, repository].shelljoin if repository
end
private
def repository_update_command
'helm repo update'
end
def namespace_flag
['--namespace', Gitlab::Kubernetes::Helm::NAMESPACE]
end
def namespace
Gitlab::Kubernetes::Helm::NAMESPACE
end
def service_account_name
Gitlab::Kubernetes::Helm::SERVICE_ACCOUNT
end
def cluster_role_binding_name
Gitlab::Kubernetes::Helm::CLUSTER_ROLE_BINDING
end
def cluster_role_name
Gitlab::Kubernetes::Helm::CLUSTER_ROLE
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
module V3
class DeleteCommand < BaseCommand
attr_reader :predelete, :postdelete
def initialize(predelete: nil, postdelete: nil, **args)
super(**args)
@predelete = predelete
@postdelete = postdelete
end
def generate_script
super + [
predelete,
delete_command,
postdelete
].compact.join("\n")
end
def pod_name
"uninstall-#{name}"
end
def delete_command
['helm', 'uninstall', name, *namespace_flag].shelljoin
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
module V3
class InstallCommand < BaseCommand
attr_reader :chart, :repository, :preinstall, :postinstall
attr_accessor :version
def initialize(chart:, version: nil, repository: nil, preinstall: nil, postinstall: nil, **args)
super(**args)
@chart = chart
@version = version
@repository = repository
@preinstall = preinstall
@postinstall = postinstall
end
def generate_script
super + [
repository_command,
repository_update_command,
preinstall,
install_command,
postinstall
].compact.join("\n")
end
private
# Uses `helm upgrade --install` which means we can use this for both
# installation and uprade of applications
def install_command
command = ['helm', 'upgrade', name, chart] +
install_flag +
rollback_support_flag +
reset_values_flag +
optional_version_flag +
rbac_create_flag +
namespace_flag +
value_flag
command.shelljoin
end
def install_flag
['--install']
end
def reset_values_flag
['--reset-values']
end
def value_flag
['-f', "/data/helm/#{name}/config/values.yaml"]
end
def rbac_create_flag
if rbac?
%w[--set rbac.create=true,rbac.enabled=true]
else
%w[--set rbac.create=false,rbac.enabled=false]
end
end
def optional_version_flag
return [] unless version
['--version', version]
end
def rollback_support_flag
['--atomic', '--cleanup-on-fail']
end
end
end
end
end
end
# frozen_string_literal: true
# PatchCommand is for updating values in installed charts without overwriting
# existing values.
module Gitlab
module Kubernetes
module Helm
module V3
class PatchCommand < BaseCommand
attr_reader :chart, :repository
attr_accessor :version
def initialize(chart:, version:, repository: nil, **args)
super(**args)
# version is mandatory to prevent chart mismatches
# we do not want our values interpreted in the context of the wrong version
raise ArgumentError, 'version is required' if version.blank?
@chart = chart
@version = version
@repository = repository
end
def generate_script
super + [
repository_command,
repository_update_command,
upgrade_command
].compact.join("\n")
end
private
def upgrade_command
command = ['helm', 'upgrade', name, chart] +
reuse_values_flag +
version_flag +
namespace_flag +
value_flag
command.shelljoin
end
def reuse_values_flag
['--reuse-values']
end
def value_flag
['-f', "/data/helm/#{name}/config/values.yaml"]
end
def version_flag
['--version', version]
end
end
end
end
end
end
......@@ -5,7 +5,7 @@ FactoryBot.define do
cluster factory: %i(cluster provided_by_gcp)
before(:create) do
allow(Gitlab::Kubernetes::Helm::Certificate).to receive(:generate_root)
allow(Gitlab::Kubernetes::Helm::V2::Certificate).to receive(:generate_root)
.and_return(
double(
key_string: File.read(Rails.root.join('spec/fixtures/clusters/sample_key.key')),
......@@ -15,7 +15,7 @@ FactoryBot.define do
end
after(:create) do
allow(Gitlab::Kubernetes::Helm::Certificate).to receive(:generate_root).and_call_original
allow(Gitlab::Kubernetes::Helm::V2::Certificate).to receive(:generate_root).and_call_original
end
trait :not_installable do
......
......@@ -13,7 +13,7 @@ RSpec.describe Gitlab::Kubernetes::Helm::API do
let(:files) { {} }
let(:command) do
Gitlab::Kubernetes::Helm::InstallCommand.new(
Gitlab::Kubernetes::Helm::V2::InstallCommand.new(
name: application_name,
chart: 'chart-name',
rbac: rbac,
......@@ -142,7 +142,7 @@ RSpec.describe Gitlab::Kubernetes::Helm::API do
end
context 'with a service account' do
let(:command) { Gitlab::Kubernetes::Helm::InitCommand.new(name: application_name, files: files, rbac: rbac) }
let(:command) { Gitlab::Kubernetes::Helm::V2::InitCommand.new(name: application_name, files: files, rbac: rbac) }
context 'rbac-enabled cluster' do
let(:rbac) { true }
......
......@@ -4,75 +4,84 @@ require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::Pod do
describe '#generate' do
let(:app) { create(:clusters_applications_prometheus) }
let(:command) { app.install_command }
let(:namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
let(:service_account_name) { nil }
using RSpec::Parameterized::TableSyntax
subject { described_class.new(command, namespace, service_account_name: service_account_name) }
where(:helm_major_version, :expected_helm_version, :expected_command_env) do
2 | '2.16.9' | [:TILLER_NAMESPACE]
3 | '3.2.4' | nil
end
context 'with a command' do
it 'generates a Kubeclient::Resource' do
expect(subject.generate).to be_a_kind_of(Kubeclient::Resource)
end
with_them do
let(:cluster) { create(:cluster, helm_major_version: helm_major_version) }
let(:app) { create(:clusters_applications_prometheus, cluster: cluster) }
let(:command) { app.install_command }
let(:namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
let(:service_account_name) { nil }
it 'generates the appropriate metadata' do
metadata = subject.generate.metadata
expect(metadata.name).to eq("install-#{app.name}")
expect(metadata.namespace).to eq('gitlab-managed-apps')
expect(metadata.labels['gitlab.org/action']).to eq('install')
expect(metadata.labels['gitlab.org/application']).to eq(app.name)
end
subject { described_class.new(command, namespace, service_account_name: service_account_name) }
it 'generates a container spec' do
spec = subject.generate.spec
expect(spec.containers.count).to eq(1)
end
context 'with a command' do
it 'generates a Kubeclient::Resource' do
expect(subject.generate).to be_a_kind_of(Kubeclient::Resource)
end
it 'generates the appropriate specifications for the container' do
container = subject.generate.spec.containers.first
expect(container.name).to eq('helm')
expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.16.9-kube-1.13.12')
expect(container.env.count).to eq(3)
expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT])
expect(container.command).to match_array(["/bin/sh"])
expect(container.args).to match_array(["-c", "$(COMMAND_SCRIPT)"])
end
it 'generates the appropriate metadata' do
metadata = subject.generate.metadata
expect(metadata.name).to eq("install-#{app.name}")
expect(metadata.namespace).to eq('gitlab-managed-apps')
expect(metadata.labels['gitlab.org/action']).to eq('install')
expect(metadata.labels['gitlab.org/application']).to eq(app.name)
end
it 'includes a never restart policy' do
spec = subject.generate.spec
expect(spec.restartPolicy).to eq('Never')
end
it 'generates a container spec' do
spec = subject.generate.spec
expect(spec.containers.count).to eq(1)
end
it 'includes volumes for the container' do
container = subject.generate.spec.containers.first
expect(container.volumeMounts.first['name']).to eq('configuration-volume')
expect(container.volumeMounts.first['mountPath']).to eq("/data/helm/#{app.name}/config")
end
it 'generates the appropriate specifications for the container' do
container = subject.generate.spec.containers.first
expect(container.name).to eq('helm')
expect(container.image).to eq("registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/#{expected_helm_version}-kube-1.13.12-alpine-3.12")
expect(container.env.map(&:name)).to include(:HELM_VERSION, :COMMAND_SCRIPT, *expected_command_env)
expect(container.command).to match_array(["/bin/sh"])
expect(container.args).to match_array(["-c", "$(COMMAND_SCRIPT)"])
end
it 'includes a volume inside the specification' do
spec = subject.generate.spec
expect(spec.volumes.first['name']).to eq('configuration-volume')
end
it 'includes a never restart policy' do
spec = subject.generate.spec
expect(spec.restartPolicy).to eq('Never')
end
it 'mounts configMap specification in the volume' do
volume = subject.generate.spec.volumes.first
expect(volume.configMap['name']).to eq("values-content-configuration-#{app.name}")
expect(volume.configMap['items'].first['key']).to eq(:'values.yaml')
expect(volume.configMap['items'].first['path']).to eq(:'values.yaml')
end
it 'includes volumes for the container' do
container = subject.generate.spec.containers.first
expect(container.volumeMounts.first['name']).to eq('configuration-volume')
expect(container.volumeMounts.first['mountPath']).to eq("/data/helm/#{app.name}/config")
end
it 'has no serviceAccountName' do
spec = subject.generate.spec
expect(spec.serviceAccountName).to be_nil
end
it 'includes a volume inside the specification' do
spec = subject.generate.spec
expect(spec.volumes.first['name']).to eq('configuration-volume')
end
context 'with a service_account_name' do
let(:service_account_name) { 'sa' }
it 'mounts configMap specification in the volume' do
volume = subject.generate.spec.volumes.first
expect(volume.configMap['name']).to eq("values-content-configuration-#{app.name}")
expect(volume.configMap['items'].first['key']).to eq(:'values.yaml')
expect(volume.configMap['items'].first['path']).to eq(:'values.yaml')
end
it 'uses the serviceAccountName provided' do
it 'has no serviceAccountName' do
spec = subject.generate.spec
expect(spec.serviceAccountName).to eq(service_account_name)
expect(spec.serviceAccountName).to be_nil
end
context 'with a service_account_name' do
let(:service_account_name) { 'sa' }
it 'uses the serviceAccountName provided' do
spec = subject.generate.spec
expect(spec.serviceAccountName).to eq(service_account_name)
end
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::V2::BaseCommand do
subject(:base_command) do
test_class.new(rbac)
end
let(:application) { create(:clusters_applications_helm) }
let(:rbac) { false }
let(:test_class) do
Class.new(described_class) do
def initialize(rbac)
super(
name: 'test-class-name',
rbac: rbac,
files: { some: 'value' }
)
end
end
end
describe 'HELM_VERSION' do
subject { described_class::HELM_VERSION }
it { is_expected.to match /^2\.\d+\.\d+$/ }
end
describe '#env' do
subject { base_command.env }
it { is_expected.to include(TILLER_NAMESPACE: 'gitlab-managed-apps') }
end
it_behaves_like 'helm command generator' do
let(:commands) { '' }
end
describe '#pod_name' do
subject { base_command.pod_name }
it { is_expected.to eq('install-test-class-name') }
end
it_behaves_like 'helm command' do
let(:command) { base_command }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::Certificate do
RSpec.describe Gitlab::Kubernetes::Helm::V2::Certificate do
describe '.generate_root' do
subject { described_class.generate_root }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::DeleteCommand do
RSpec.describe Gitlab::Kubernetes::Helm::V2::DeleteCommand do
subject(:delete_command) { described_class.new(name: app_name, rbac: rbac, files: files) }
let(:app_name) { 'app-name' }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::InitCommand do
RSpec.describe Gitlab::Kubernetes::Helm::V2::InitCommand do
subject(:init_command) { described_class.new(name: application.name, files: files, rbac: rbac) }
let(:application) { create(:clusters_applications_helm) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::InstallCommand do
RSpec.describe Gitlab::Kubernetes::Helm::V2::InstallCommand do
subject(:install_command) do
described_class.new(
name: 'app-name',
......@@ -147,37 +147,6 @@ RSpec.describe Gitlab::Kubernetes::Helm::InstallCommand do
end
end
context 'when there is no ca.pem file' do
let(:files) { { 'file.txt': 'some content' } }
it_behaves_like 'helm command generator' do
let(:commands) do
<<~EOS
export HELM_HOST="localhost:44134"
tiller -listen ${HELM_HOST} -alsologtostderr &
helm init --client-only
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
EOS
end
let(:helm_install_command) do
<<~EOS.squish
helm upgrade app-name chart-name
--install
--atomic
--cleanup-on-fail
--reset-values
--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 no version' do
let(:version) { nil }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::PatchCommand do
RSpec.describe Gitlab::Kubernetes::Helm::V2::PatchCommand do
let(:files) { { 'ca.pem': 'some file content' } }
let(:repository) { 'https://repository.example.com' }
let(:rbac) { false }
......@@ -69,33 +69,6 @@ RSpec.describe Gitlab::Kubernetes::Helm::PatchCommand do
end
end
context 'when there is no ca.pem file' do
let(:files) { { 'file.txt': 'some content' } }
it_behaves_like 'helm command generator' do
let(:commands) do
<<~EOS
export HELM_HOST="localhost:44134"
tiller -listen ${HELM_HOST} -alsologtostderr &
helm init --client-only
helm repo add app-name https://repository.example.com
helm repo update
#{helm_upgrade_command}
EOS
end
let(:helm_upgrade_command) do
<<~EOS.squish
helm upgrade app-name chart-name
--reuse-values
--version 1.2.3
--namespace gitlab-managed-apps
-f /data/helm/app-name/config/values.yaml
EOS
end
end
end
context 'when there is no version' do
let(:version) { nil }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::ResetCommand do
RSpec.describe Gitlab::Kubernetes::Helm::V2::ResetCommand do
subject(:reset_command) { described_class.new(name: name, rbac: rbac, files: files) }
let(:rbac) { true }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::BaseCommand do
RSpec.describe Gitlab::Kubernetes::Helm::V3::BaseCommand do
subject(:base_command) do
test_class.new(rbac)
end
......@@ -11,7 +11,7 @@ RSpec.describe Gitlab::Kubernetes::Helm::BaseCommand do
let(:rbac) { false }
let(:test_class) do
Class.new(Gitlab::Kubernetes::Helm::BaseCommand) do
Class.new(described_class) do
def initialize(rbac)
super(
name: 'test-class-name',
......@@ -22,6 +22,12 @@ RSpec.describe Gitlab::Kubernetes::Helm::BaseCommand do
end
end
describe 'HELM_VERSION' do
subject { described_class::HELM_VERSION }
it { is_expected.to match /^3\.\d+\.\d+$/ }
end
it_behaves_like 'helm command generator' do
let(:commands) { '' }
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::V3::DeleteCommand do
subject(:delete_command) { described_class.new(name: app_name, rbac: rbac, files: files) }
let(:app_name) { 'app-name' }
let(:rbac) { true }
let(:files) { {} }
it_behaves_like 'helm command generator' do
let(:commands) do
<<~EOS
helm uninstall app-name --namespace gitlab-managed-apps
EOS
end
end
describe '#pod_name' do
subject { delete_command.pod_name }
it { is_expected.to eq('uninstall-app-name') }
end
it_behaves_like 'helm command' do
let(:command) { delete_command }
end
describe '#delete_command' do
it 'deletes the release' do
expect(subject.delete_command).to eq('helm uninstall app-name --namespace gitlab-managed-apps')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::V3::InstallCommand do
subject(:install_command) do
described_class.new(
name: 'app-name',
chart: 'chart-name',
rbac: rbac,
files: files,
version: version,
repository: repository,
preinstall: preinstall,
postinstall: postinstall
)
end
let(:files) { { 'ca.pem': 'some file content' } }
let(:repository) { 'https://repository.example.com' }
let(:rbac) { false }
let(:version) { '1.2.3' }
let(:preinstall) { nil }
let(:postinstall) { nil }
it_behaves_like 'helm command generator' do
let(:commands) do
<<~EOS
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_comand}
EOS
end
let(:helm_install_comand) do
<<~EOS.squish
helm upgrade app-name chart-name
--install
--atomic
--cleanup-on-fail
--reset-values
--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
context 'when rbac is true' do
let(:rbac) { true }
it_behaves_like 'helm command generator' do
let(:commands) do
<<~EOS
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
EOS
end
let(:helm_install_command) do
<<~EOS.squish
helm upgrade app-name chart-name
--install
--atomic
--cleanup-on-fail
--reset-values
--version 1.2.3
--set rbac.create\\=true,rbac.enabled\\=true
--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'] }
it_behaves_like 'helm command generator' do
let(:commands) do
<<~EOS
helm repo add app-name https://repository.example.com
helm repo update
/bin/date
/bin/true
#{helm_install_command}
EOS
end
let(:helm_install_command) do
<<~EOS.squish
helm upgrade app-name chart-name
--install
--atomic
--cleanup-on-fail
--reset-values
--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 post-install script' do
let(:postinstall) { ['/bin/date', "/bin/false\n"] }
it_behaves_like 'helm command generator' do
let(:commands) do
<<~EOS
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
/bin/date
/bin/false
EOS
end
let(:helm_install_command) do
<<~EOS.squish
helm upgrade app-name chart-name
--install
--atomic
--cleanup-on-fail
--reset-values
--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 no version' do
let(:version) { nil }
it_behaves_like 'helm command generator' do
let(:commands) do
<<~EOS
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
EOS
end
let(:helm_install_command) do
<<~EOS.squish
helm upgrade app-name chart-name
--install
--atomic
--cleanup-on-fail
--reset-values
--set rbac.create\\=false,rbac.enabled\\=false
--namespace gitlab-managed-apps
-f /data/helm/app-name/config/values.yaml
EOS
end
end
end
it_behaves_like 'helm command' do
let(:command) { install_command }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::V3::PatchCommand do
let(:files) { { 'ca.pem': 'some file content' } }
let(:repository) { 'https://repository.example.com' }
let(:rbac) { false }
let(:version) { '1.2.3' }
subject(:patch_command) do
described_class.new(
name: 'app-name',
chart: 'chart-name',
rbac: rbac,
files: files,
version: version,
repository: repository
)
end
it_behaves_like 'helm command generator' do
let(:commands) do
<<~EOS
helm repo add app-name https://repository.example.com
helm repo update
#{helm_upgrade_comand}
EOS
end
let(:helm_upgrade_comand) do
<<~EOS.squish
helm upgrade app-name chart-name
--reuse-values
--version 1.2.3
--namespace gitlab-managed-apps
-f /data/helm/app-name/config/values.yaml
EOS
end
end
context 'when rbac is true' do
let(:rbac) { true }
it_behaves_like 'helm command generator' do
let(:commands) do
<<~EOS
helm repo add app-name https://repository.example.com
helm repo update
#{helm_upgrade_command}
EOS
end
let(:helm_upgrade_command) do
<<~EOS.squish
helm upgrade app-name chart-name
--reuse-values
--version 1.2.3
--namespace gitlab-managed-apps
-f /data/helm/app-name/config/values.yaml
EOS
end
end
end
context 'when there is no version' do
let(:version) { nil }
it { expect { patch_command }.to raise_error(ArgumentError, 'version is required') }
end
describe '#pod_name' do
subject { patch_command.pod_name }
it { is_expected.to eq 'install-app-name' }
end
it_behaves_like 'helm command' do
let(:command) { patch_command }
end
end
......@@ -40,7 +40,7 @@ RSpec.describe Clusters::Applications::CertManager do
subject { cert_manager.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with cert_manager arguments' do
expect(subject.name).to eq('certmanager')
......@@ -90,7 +90,7 @@ RSpec.describe Clusters::Applications::CertManager do
describe '#uninstall_command' do
subject { cert_manager.uninstall_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::DeleteCommand) }
it 'is initialized with cert_manager arguments' do
expect(subject.name).to eq('certmanager')
......
......@@ -25,7 +25,7 @@ RSpec.describe Clusters::Applications::Crossplane do
subject { crossplane.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with crossplane arguments' do
expect(subject.name).to eq('crossplane')
......
......@@ -15,7 +15,7 @@ RSpec.describe Clusters::Applications::ElasticStack do
subject { elastic_stack.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with elastic stack arguments' do
expect(subject.name).to eq('elastic-stack')
......@@ -57,7 +57,7 @@ RSpec.describe Clusters::Applications::ElasticStack do
it 'includes a preinstall script' do
expect(subject.preinstall).not_to be_empty
expect(subject.preinstall.first).to include("delete")
expect(subject.preinstall.first).to include("helm uninstall")
end
end
......@@ -69,7 +69,7 @@ RSpec.describe Clusters::Applications::ElasticStack do
it 'includes a preinstall script' do
expect(subject.preinstall).not_to be_empty
expect(subject.preinstall.first).to include("delete")
expect(subject.preinstall.first).to include("helm uninstall")
end
end
......@@ -123,7 +123,7 @@ RSpec.describe Clusters::Applications::ElasticStack do
subject { elastic_stack.uninstall_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::DeleteCommand) }
it 'is initialized with elastic stack arguments' do
expect(subject.name).to eq('elastic-stack')
......
......@@ -21,7 +21,7 @@ RSpec.describe Clusters::Applications::Fluentd do
describe '#install_command' do
subject { fluentd.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with fluentd arguments' do
expect(subject.name).to eq('fluentd')
......
......@@ -56,7 +56,7 @@ RSpec.describe Clusters::Applications::Helm do
subject { application.issue_client_cert }
it 'returns a new cert' do
is_expected.to be_kind_of(Gitlab::Kubernetes::Helm::Certificate)
is_expected.to be_kind_of(Gitlab::Kubernetes::Helm::V2::Certificate)
expect(subject.cert_string).not_to eq(application.ca_cert)
expect(subject.key_string).not_to eq(application.ca_key)
end
......@@ -67,7 +67,7 @@ RSpec.describe Clusters::Applications::Helm do
subject { helm.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InitCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V2::InitCommand) }
it 'is initialized with 1 arguments' do
expect(subject.name).to eq('helm')
......@@ -104,7 +104,7 @@ RSpec.describe Clusters::Applications::Helm do
subject { helm.uninstall_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::ResetCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V2::ResetCommand) }
it 'has name' do
expect(subject.name).to eq('helm')
......
......@@ -131,7 +131,7 @@ RSpec.describe Clusters::Applications::Ingress do
describe '#install_command' do
subject { ingress.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with ingress arguments' do
expect(subject.name).to eq('ingress')
......
......@@ -52,7 +52,7 @@ RSpec.describe Clusters::Applications::Jupyter do
subject { jupyter.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with 4 arguments' do
expect(subject.name).to eq('jupyter')
......
......@@ -119,7 +119,7 @@ RSpec.describe Clusters::Applications::Knative do
shared_examples 'a command' do
it 'is an instance of Helm::InstallCommand' do
expect(subject).to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand)
expect(subject).to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand)
end
it 'is initialized with knative arguments' do
......@@ -171,7 +171,7 @@ RSpec.describe Clusters::Applications::Knative do
describe '#uninstall_command' do
subject { knative.uninstall_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::DeleteCommand) }
it "removes knative deployed services before uninstallation" do
2.times do |i|
......
......@@ -148,7 +148,7 @@ RSpec.describe Clusters::Applications::Prometheus do
subject { prometheus.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with 3 arguments' do
expect(subject.name).to eq('prometheus')
......@@ -195,7 +195,7 @@ RSpec.describe Clusters::Applications::Prometheus do
subject { prometheus.uninstall_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::DeleteCommand) }
it 'has the application name' do
expect(subject.name).to eq('prometheus')
......@@ -236,7 +236,7 @@ RSpec.describe Clusters::Applications::Prometheus do
let(:prometheus) { build(:clusters_applications_prometheus) }
let(:values) { prometheus.values }
it { is_expected.to be_an_instance_of(::Gitlab::Kubernetes::Helm::PatchCommand) }
it { is_expected.to be_an_instance_of(::Gitlab::Kubernetes::Helm::V3::PatchCommand) }
it 'is initialized with 3 arguments' do
expect(patch_command.name).to eq('prometheus')
......
......@@ -27,7 +27,7 @@ RSpec.describe Clusters::Applications::Runner do
subject { gitlab_runner.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) }
it 'is initialized with 4 arguments' do
expect(subject.name).to eq('runner')
......
......@@ -540,6 +540,27 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
end
describe 'helm_major_version can only be 2 or 3' do
using RSpec::Parameterized::TableSyntax
where(:helm_major_version, :expect_valid) do
2 | true
3 | true
4 | false
-1 | false
end
with_them do
let(:cluster) { build(:cluster, helm_major_version: helm_major_version) }
it { is_expected.to eq(expect_valid) }
end
end
end
it 'has default helm_major_version 3' do
expect(create(:cluster).helm_major_version).to eq(3)
end
describe '.ancestor_clusters_for_clusterable' do
......
......@@ -14,7 +14,7 @@ RSpec.describe Clusters::Applications::UninstallService, '#execute' do
context 'when there are no errors' do
before do
expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::DeleteCommand))
expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::V3::DeleteCommand))
allow(worker_class).to receive(:perform_in).and_return(nil)
end
......@@ -36,7 +36,7 @@ RSpec.describe Clusters::Applications::UninstallService, '#execute' do
let(:error) { Kubeclient::HttpError.new(500, 'system failure', nil) }
before do
expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::DeleteCommand)).and_raise(error)
expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::V3::DeleteCommand)).and_raise(error)
end
include_examples 'logs kubernetes errors' do
......@@ -58,7 +58,7 @@ RSpec.describe Clusters::Applications::UninstallService, '#execute' do
let(:error) { StandardError.new('something bad happened') }
before do
expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::DeleteCommand)).and_raise(error)
expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::V3::DeleteCommand)).and_raise(error)
end
include_examples 'logs kubernetes errors' do
......
......@@ -15,6 +15,18 @@ RSpec.shared_examples 'helm command generator' do
end
RSpec.shared_examples 'helm command' do
describe 'HELM_VERSION' do
subject { command.class::HELM_VERSION }
it { is_expected.to match(/\d+\.\d+\.\d+/) }
end
describe '#env' do
subject { command.env }
it { is_expected.to be_a Hash }
end
describe '#rbac?' do
subject { command.rbac? }
......
......@@ -25,4 +25,21 @@ RSpec.shared_examples 'cluster application core specs' do |application_name|
describe '.association_name' do
it { expect(described_class.association_name).to eq(:"application_#{subject.name}") }
end
describe '#helm_command_module' do
using RSpec::Parameterized::TableSyntax
where(:helm_major_version, :expected_helm_command_module) do
2 | Gitlab::Kubernetes::Helm::V2
3 | Gitlab::Kubernetes::Helm::V3
end
with_them do
subject { described_class.new(cluster: cluster).helm_command_module }
let(:cluster) { build(:cluster, helm_major_version: helm_major_version)}
it { is_expected.to eq(expected_helm_command_module) }
end
end
end
......@@ -6,7 +6,7 @@ RSpec.shared_examples 'cluster application helm specs' do |application_name|
describe '#uninstall_command' do
subject { application.uninstall_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::DeleteCommand) }
it 'has files' do
expect(subject.files).to eq(application.files)
......
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