Commit 6eed9844 authored by Hordur Freyr Yngvason's avatar Hordur Freyr Yngvason Committed by Adam Hegyi

Use Helm 3 for GitLab-managed apps in new clusters

With this change, any existing cluster will continue to use
Helm 2, while all new clusters will use Helm 3 for GitLab-managed apps.
Note that this only applies to GitLab-managed apps -- Auto DevOps
uses its own Helm version on a per-project basis.

The diff looks big because we split the Helm command classes into two
modules, V2 and V3, to allow them to evolve independently as long as
they both implement the same minimal interface.

The command classes in the V2 module should be exactly equal to their
old counterparts, besides the addition of BaseCommand#helm_version,
which the common Kubernetes pod generator uses to resolve the container
image.

The command classes in the V3 module are ports of the relevant subset of
commands. There are more classes in the V2 module because we carried
along support for in-cluster Tiller. This is currently unused, but may
be used to clean up Tiller if we decide to offer Helm 2 to 3 migration
in the product later on.

See also https://gitlab.com/gitlab-org/gitlab/-/issues/120021
parent 4f0935a6
......@@ -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