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 ...@@ -30,7 +30,7 @@ module Clusters
end end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new( helm_command_module::InstallCommand.new(
name: 'certmanager', name: 'certmanager',
repository: repository, repository: repository,
version: VERSION, version: VERSION,
...@@ -43,7 +43,7 @@ module Clusters ...@@ -43,7 +43,7 @@ module Clusters
end end
def uninstall_command def uninstall_command
Gitlab::Kubernetes::Helm::DeleteCommand.new( helm_command_module::DeleteCommand.new(
name: 'certmanager', name: 'certmanager',
rbac: cluster.platform_kubernetes_rbac?, rbac: cluster.platform_kubernetes_rbac?,
files: files, files: files,
......
...@@ -29,7 +29,7 @@ module Clusters ...@@ -29,7 +29,7 @@ module Clusters
end end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new( helm_command_module::InstallCommand.new(
name: 'crossplane', name: 'crossplane',
repository: repository, repository: repository,
version: VERSION, version: VERSION,
......
...@@ -26,7 +26,7 @@ module Clusters ...@@ -26,7 +26,7 @@ module Clusters
end end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new( helm_command_module::InstallCommand.new(
name: 'elastic-stack', name: 'elastic-stack',
version: VERSION, version: VERSION,
rbac: cluster.platform_kubernetes_rbac?, rbac: cluster.platform_kubernetes_rbac?,
...@@ -39,7 +39,7 @@ module Clusters ...@@ -39,7 +39,7 @@ module Clusters
end end
def uninstall_command def uninstall_command
Gitlab::Kubernetes::Helm::DeleteCommand.new( helm_command_module::DeleteCommand.new(
name: 'elastic-stack', name: 'elastic-stack',
rbac: cluster.platform_kubernetes_rbac?, rbac: cluster.platform_kubernetes_rbac?,
files: files, files: files,
...@@ -96,7 +96,7 @@ module Clusters ...@@ -96,7 +96,7 @@ module Clusters
def post_install_script 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 end
...@@ -116,7 +116,7 @@ module Clusters ...@@ -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 # 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. # 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', name: 'elastic-stack',
rbac: cluster.platform_kubernetes_rbac?, rbac: cluster.platform_kubernetes_rbac?,
files: files files: files
......
...@@ -30,7 +30,7 @@ module Clusters ...@@ -30,7 +30,7 @@ module Clusters
end end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new( helm_command_module::InstallCommand.new(
name: 'fluentd', name: 'fluentd',
repository: repository, repository: repository,
version: VERSION, version: VERSION,
......
...@@ -4,6 +4,8 @@ require 'openssl' ...@@ -4,6 +4,8 @@ require 'openssl'
module Clusters module Clusters
module Applications 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 class Helm < ApplicationRecord
self.table_name = 'clusters_applications_helm' self.table_name = 'clusters_applications_helm'
...@@ -49,7 +51,7 @@ module Clusters ...@@ -49,7 +51,7 @@ module Clusters
end end
def install_command def install_command
Gitlab::Kubernetes::Helm::InitCommand.new( Gitlab::Kubernetes::Helm::V2::InitCommand.new(
name: name, name: name,
files: files, files: files,
rbac: cluster.platform_kubernetes_rbac? rbac: cluster.platform_kubernetes_rbac?
...@@ -57,7 +59,7 @@ module Clusters ...@@ -57,7 +59,7 @@ module Clusters
end end
def uninstall_command def uninstall_command
Gitlab::Kubernetes::Helm::ResetCommand.new( Gitlab::Kubernetes::Helm::V2::ResetCommand.new(
name: name, name: name,
files: files, files: files,
rbac: cluster.platform_kubernetes_rbac? rbac: cluster.platform_kubernetes_rbac?
...@@ -86,19 +88,19 @@ module Clusters ...@@ -86,19 +88,19 @@ module Clusters
end end
def create_keys_and_certs 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_key = ca_cert.key_string
self.ca_cert = ca_cert.cert_string self.ca_cert = ca_cert.cert_string
end end
def tiller_cert 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 end
def ca_cert_obj def ca_cert_obj
return unless has_ssl? return unless has_ssl?
Gitlab::Kubernetes::Helm::Certificate Gitlab::Kubernetes::Helm::V2::Certificate
.from_strings(ca_key, ca_cert) .from_strings(ca_key, ca_cert)
end end
end end
......
...@@ -62,7 +62,7 @@ module Clusters ...@@ -62,7 +62,7 @@ module Clusters
end end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new( helm_command_module::InstallCommand.new(
name: name, name: name,
repository: repository, repository: repository,
version: VERSION, version: VERSION,
......
...@@ -39,7 +39,7 @@ module Clusters ...@@ -39,7 +39,7 @@ module Clusters
end end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new( helm_command_module::InstallCommand.new(
name: name, name: name,
version: VERSION, version: VERSION,
rbac: cluster.platform_kubernetes_rbac?, rbac: cluster.platform_kubernetes_rbac?,
......
...@@ -70,7 +70,7 @@ module Clusters ...@@ -70,7 +70,7 @@ module Clusters
end end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new( helm_command_module::InstallCommand.new(
name: name, name: name,
version: VERSION, version: VERSION,
rbac: cluster.platform_kubernetes_rbac?, rbac: cluster.platform_kubernetes_rbac?,
...@@ -94,7 +94,7 @@ module Clusters ...@@ -94,7 +94,7 @@ module Clusters
end end
def uninstall_command def uninstall_command
Gitlab::Kubernetes::Helm::DeleteCommand.new( helm_command_module::DeleteCommand.new(
name: name, name: name,
rbac: cluster.platform_kubernetes_rbac?, rbac: cluster.platform_kubernetes_rbac?,
files: files, files: files,
......
...@@ -67,7 +67,7 @@ module Clusters ...@@ -67,7 +67,7 @@ module Clusters
end end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new( helm_command_module::InstallCommand.new(
name: name, name: name,
repository: repository, repository: repository,
version: VERSION, version: VERSION,
...@@ -79,7 +79,7 @@ module Clusters ...@@ -79,7 +79,7 @@ module Clusters
end end
def patch_command(values) def patch_command(values)
::Gitlab::Kubernetes::Helm::PatchCommand.new( helm_command_module::PatchCommand.new(
name: name, name: name,
repository: repository, repository: repository,
version: version, version: version,
...@@ -90,7 +90,7 @@ module Clusters ...@@ -90,7 +90,7 @@ module Clusters
end end
def uninstall_command def uninstall_command
Gitlab::Kubernetes::Helm::DeleteCommand.new( helm_command_module::DeleteCommand.new(
name: name, name: name,
rbac: cluster.platform_kubernetes_rbac?, rbac: cluster.platform_kubernetes_rbac?,
files: files, files: files,
......
...@@ -30,7 +30,7 @@ module Clusters ...@@ -30,7 +30,7 @@ module Clusters
end end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new( helm_command_module::InstallCommand.new(
name: name, name: name,
version: VERSION, version: VERSION,
rbac: cluster.platform_kubernetes_rbac?, rbac: cluster.platform_kubernetes_rbac?,
......
...@@ -79,6 +79,9 @@ module Clusters ...@@ -79,6 +79,9 @@ module Clusters
validates :cluster_type, presence: true validates :cluster_type, presence: true
validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true } validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true }
validates :namespace_per_environment, inclusion: { in: [true, false] } 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 :restrict_modification, on: :update
validate :no_groups, unless: :group_type? validate :no_groups, unless: :group_type?
......
...@@ -12,6 +12,17 @@ module Clusters ...@@ -12,6 +12,17 @@ module Clusters
after_initialize :set_initial_status 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 def set_initial_status
return unless not_installable? return unless not_installable?
......
...@@ -4,7 +4,7 @@ module Clusters ...@@ -4,7 +4,7 @@ module Clusters
module Concerns module Concerns
module ApplicationData module ApplicationData
def uninstall_command def uninstall_command
Gitlab::Kubernetes::Helm::DeleteCommand.new( helm_command_module::DeleteCommand.new(
name: name, name: name,
rbac: cluster.platform_kubernetes_rbac?, rbac: cluster.platform_kubernetes_rbac?,
files: files 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 ( ...@@ -10967,7 +10967,8 @@ CREATE TABLE clusters (
namespace_per_environment boolean DEFAULT true NOT NULL, namespace_per_environment boolean DEFAULT true NOT NULL,
management_project_id integer, management_project_id integer,
cleanup_status smallint DEFAULT 1 NOT NULL, 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 ( 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 ...@@ -27,7 +27,7 @@ module Gitlab
def container_specification def container_specification
{ {
name: 'helm', 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), env: generate_pod_env(command),
command: %w(/bin/sh), command: %w(/bin/sh),
args: %w(-c $(COMMAND_SCRIPT)) args: %w(-c $(COMMAND_SCRIPT))
...@@ -50,11 +50,10 @@ module Gitlab ...@@ -50,11 +50,10 @@ module Gitlab
end end
def generate_pod_env(command) def generate_pod_env(command)
{ command.env.merge(
HELM_VERSION: Gitlab::Kubernetes::Helm::HELM_VERSION, HELM_VERSION: command.class::HELM_VERSION,
TILLER_NAMESPACE: namespace_name,
COMMAND_SCRIPT: command.generate_script COMMAND_SCRIPT: command.generate_script
}.map { |key, value| { name: key, value: value } } ).map { |key, value| { name: key, value: value } }
end end
def volumes_specification 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 ...@@ -5,7 +5,7 @@ FactoryBot.define do
cluster factory: %i(cluster provided_by_gcp) cluster factory: %i(cluster provided_by_gcp)
before(:create) do before(:create) do
allow(Gitlab::Kubernetes::Helm::Certificate).to receive(:generate_root) allow(Gitlab::Kubernetes::Helm::V2::Certificate).to receive(:generate_root)
.and_return( .and_return(
double( double(
key_string: File.read(Rails.root.join('spec/fixtures/clusters/sample_key.key')), key_string: File.read(Rails.root.join('spec/fixtures/clusters/sample_key.key')),
...@@ -15,7 +15,7 @@ FactoryBot.define do ...@@ -15,7 +15,7 @@ FactoryBot.define do
end end
after(:create) do 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 end
trait :not_installable do trait :not_installable do
......
...@@ -13,7 +13,7 @@ RSpec.describe Gitlab::Kubernetes::Helm::API do ...@@ -13,7 +13,7 @@ RSpec.describe Gitlab::Kubernetes::Helm::API do
let(:files) { {} } let(:files) { {} }
let(:command) do let(:command) do
Gitlab::Kubernetes::Helm::InstallCommand.new( Gitlab::Kubernetes::Helm::V2::InstallCommand.new(
name: application_name, name: application_name,
chart: 'chart-name', chart: 'chart-name',
rbac: rbac, rbac: rbac,
...@@ -142,7 +142,7 @@ RSpec.describe Gitlab::Kubernetes::Helm::API do ...@@ -142,7 +142,7 @@ RSpec.describe Gitlab::Kubernetes::Helm::API do
end end
context 'with a service account' do 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 context 'rbac-enabled cluster' do
let(:rbac) { true } let(:rbac) { true }
......
...@@ -4,7 +4,16 @@ require 'spec_helper' ...@@ -4,7 +4,16 @@ require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::Pod do RSpec.describe Gitlab::Kubernetes::Helm::Pod do
describe '#generate' do describe '#generate' do
let(:app) { create(:clusters_applications_prometheus) } using RSpec::Parameterized::TableSyntax
where(:helm_major_version, :expected_helm_version, :expected_command_env) do
2 | '2.16.9' | [:TILLER_NAMESPACE]
3 | '3.2.4' | nil
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(:command) { app.install_command }
let(:namespace) { Gitlab::Kubernetes::Helm::NAMESPACE } let(:namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
let(:service_account_name) { nil } let(:service_account_name) { nil }
...@@ -32,9 +41,8 @@ RSpec.describe Gitlab::Kubernetes::Helm::Pod do ...@@ -32,9 +41,8 @@ RSpec.describe Gitlab::Kubernetes::Helm::Pod do
it 'generates the appropriate specifications for the container' do it 'generates the appropriate specifications for the container' do
container = subject.generate.spec.containers.first container = subject.generate.spec.containers.first
expect(container.name).to eq('helm') 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.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.count).to eq(3) expect(container.env.map(&:name)).to include(:HELM_VERSION, :COMMAND_SCRIPT, *expected_command_env)
expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT])
expect(container.command).to match_array(["/bin/sh"]) expect(container.command).to match_array(["/bin/sh"])
expect(container.args).to match_array(["-c", "$(COMMAND_SCRIPT)"]) expect(container.args).to match_array(["-c", "$(COMMAND_SCRIPT)"])
end end
...@@ -77,4 +85,5 @@ RSpec.describe Gitlab::Kubernetes::Helm::Pod do ...@@ -77,4 +85,5 @@ RSpec.describe Gitlab::Kubernetes::Helm::Pod do
end end
end end
end 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 # frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::Certificate do RSpec.describe Gitlab::Kubernetes::Helm::V2::Certificate do
describe '.generate_root' do describe '.generate_root' do
subject { described_class.generate_root } subject { described_class.generate_root }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' 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) } subject(:delete_command) { described_class.new(name: app_name, rbac: rbac, files: files) }
let(:app_name) { 'app-name' } let(:app_name) { 'app-name' }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' 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) } subject(:init_command) { described_class.new(name: application.name, files: files, rbac: rbac) }
let(:application) { create(:clusters_applications_helm) } let(:application) { create(:clusters_applications_helm) }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::InstallCommand do RSpec.describe Gitlab::Kubernetes::Helm::V2::InstallCommand do
subject(:install_command) do subject(:install_command) do
described_class.new( described_class.new(
name: 'app-name', name: 'app-name',
...@@ -147,37 +147,6 @@ RSpec.describe Gitlab::Kubernetes::Helm::InstallCommand do ...@@ -147,37 +147,6 @@ RSpec.describe Gitlab::Kubernetes::Helm::InstallCommand do
end end
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 context 'when there is no version' do
let(:version) { nil } let(:version) { nil }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' 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(:files) { { 'ca.pem': 'some file content' } }
let(:repository) { 'https://repository.example.com' } let(:repository) { 'https://repository.example.com' }
let(:rbac) { false } let(:rbac) { false }
...@@ -69,33 +69,6 @@ RSpec.describe Gitlab::Kubernetes::Helm::PatchCommand do ...@@ -69,33 +69,6 @@ RSpec.describe Gitlab::Kubernetes::Helm::PatchCommand do
end end
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 context 'when there is no version' do
let(:version) { nil } let(:version) { nil }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' 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) } subject(:reset_command) { described_class.new(name: name, rbac: rbac, files: files) }
let(:rbac) { true } let(:rbac) { true }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Kubernetes::Helm::BaseCommand do RSpec.describe Gitlab::Kubernetes::Helm::V3::BaseCommand do
subject(:base_command) do subject(:base_command) do
test_class.new(rbac) test_class.new(rbac)
end end
...@@ -11,7 +11,7 @@ RSpec.describe Gitlab::Kubernetes::Helm::BaseCommand do ...@@ -11,7 +11,7 @@ RSpec.describe Gitlab::Kubernetes::Helm::BaseCommand do
let(:rbac) { false } let(:rbac) { false }
let(:test_class) do let(:test_class) do
Class.new(Gitlab::Kubernetes::Helm::BaseCommand) do Class.new(described_class) do
def initialize(rbac) def initialize(rbac)
super( super(
name: 'test-class-name', name: 'test-class-name',
...@@ -22,6 +22,12 @@ RSpec.describe Gitlab::Kubernetes::Helm::BaseCommand do ...@@ -22,6 +22,12 @@ RSpec.describe Gitlab::Kubernetes::Helm::BaseCommand do
end end
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 it_behaves_like 'helm command generator' do
let(:commands) { '' } let(:commands) { '' }
end 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 ...@@ -40,7 +40,7 @@ RSpec.describe Clusters::Applications::CertManager do
subject { cert_manager.install_command } 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 it 'is initialized with cert_manager arguments' do
expect(subject.name).to eq('certmanager') expect(subject.name).to eq('certmanager')
...@@ -90,7 +90,7 @@ RSpec.describe Clusters::Applications::CertManager do ...@@ -90,7 +90,7 @@ RSpec.describe Clusters::Applications::CertManager do
describe '#uninstall_command' do describe '#uninstall_command' do
subject { cert_manager.uninstall_command } 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 it 'is initialized with cert_manager arguments' do
expect(subject.name).to eq('certmanager') expect(subject.name).to eq('certmanager')
......
...@@ -25,7 +25,7 @@ RSpec.describe Clusters::Applications::Crossplane do ...@@ -25,7 +25,7 @@ RSpec.describe Clusters::Applications::Crossplane do
subject { crossplane.install_command } 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 it 'is initialized with crossplane arguments' do
expect(subject.name).to eq('crossplane') expect(subject.name).to eq('crossplane')
......
...@@ -15,7 +15,7 @@ RSpec.describe Clusters::Applications::ElasticStack do ...@@ -15,7 +15,7 @@ RSpec.describe Clusters::Applications::ElasticStack do
subject { elastic_stack.install_command } 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 it 'is initialized with elastic stack arguments' do
expect(subject.name).to eq('elastic-stack') expect(subject.name).to eq('elastic-stack')
...@@ -57,7 +57,7 @@ RSpec.describe Clusters::Applications::ElasticStack do ...@@ -57,7 +57,7 @@ RSpec.describe Clusters::Applications::ElasticStack do
it 'includes a preinstall script' do it 'includes a preinstall script' do
expect(subject.preinstall).not_to be_empty expect(subject.preinstall).not_to be_empty
expect(subject.preinstall.first).to include("delete") expect(subject.preinstall.first).to include("helm uninstall")
end end
end end
...@@ -69,7 +69,7 @@ RSpec.describe Clusters::Applications::ElasticStack do ...@@ -69,7 +69,7 @@ RSpec.describe Clusters::Applications::ElasticStack do
it 'includes a preinstall script' do it 'includes a preinstall script' do
expect(subject.preinstall).not_to be_empty expect(subject.preinstall).not_to be_empty
expect(subject.preinstall.first).to include("delete") expect(subject.preinstall.first).to include("helm uninstall")
end end
end end
...@@ -123,7 +123,7 @@ RSpec.describe Clusters::Applications::ElasticStack do ...@@ -123,7 +123,7 @@ RSpec.describe Clusters::Applications::ElasticStack do
subject { elastic_stack.uninstall_command } 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 it 'is initialized with elastic stack arguments' do
expect(subject.name).to eq('elastic-stack') expect(subject.name).to eq('elastic-stack')
......
...@@ -21,7 +21,7 @@ RSpec.describe Clusters::Applications::Fluentd do ...@@ -21,7 +21,7 @@ RSpec.describe Clusters::Applications::Fluentd do
describe '#install_command' do describe '#install_command' do
subject { fluentd.install_command } 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 it 'is initialized with fluentd arguments' do
expect(subject.name).to eq('fluentd') expect(subject.name).to eq('fluentd')
......
...@@ -56,7 +56,7 @@ RSpec.describe Clusters::Applications::Helm do ...@@ -56,7 +56,7 @@ RSpec.describe Clusters::Applications::Helm do
subject { application.issue_client_cert } subject { application.issue_client_cert }
it 'returns a new cert' do 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.cert_string).not_to eq(application.ca_cert)
expect(subject.key_string).not_to eq(application.ca_key) expect(subject.key_string).not_to eq(application.ca_key)
end end
...@@ -67,7 +67,7 @@ RSpec.describe Clusters::Applications::Helm do ...@@ -67,7 +67,7 @@ RSpec.describe Clusters::Applications::Helm do
subject { helm.install_command } 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 it 'is initialized with 1 arguments' do
expect(subject.name).to eq('helm') expect(subject.name).to eq('helm')
...@@ -104,7 +104,7 @@ RSpec.describe Clusters::Applications::Helm do ...@@ -104,7 +104,7 @@ RSpec.describe Clusters::Applications::Helm do
subject { helm.uninstall_command } 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 it 'has name' do
expect(subject.name).to eq('helm') expect(subject.name).to eq('helm')
......
...@@ -131,7 +131,7 @@ RSpec.describe Clusters::Applications::Ingress do ...@@ -131,7 +131,7 @@ RSpec.describe Clusters::Applications::Ingress do
describe '#install_command' do describe '#install_command' do
subject { ingress.install_command } 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 it 'is initialized with ingress arguments' do
expect(subject.name).to eq('ingress') expect(subject.name).to eq('ingress')
......
...@@ -52,7 +52,7 @@ RSpec.describe Clusters::Applications::Jupyter do ...@@ -52,7 +52,7 @@ RSpec.describe Clusters::Applications::Jupyter do
subject { jupyter.install_command } 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 it 'is initialized with 4 arguments' do
expect(subject.name).to eq('jupyter') expect(subject.name).to eq('jupyter')
......
...@@ -119,7 +119,7 @@ RSpec.describe Clusters::Applications::Knative do ...@@ -119,7 +119,7 @@ RSpec.describe Clusters::Applications::Knative do
shared_examples 'a command' do shared_examples 'a command' do
it 'is an instance of Helm::InstallCommand' 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 end
it 'is initialized with knative arguments' do it 'is initialized with knative arguments' do
...@@ -171,7 +171,7 @@ RSpec.describe Clusters::Applications::Knative do ...@@ -171,7 +171,7 @@ RSpec.describe Clusters::Applications::Knative do
describe '#uninstall_command' do describe '#uninstall_command' do
subject { knative.uninstall_command } 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 it "removes knative deployed services before uninstallation" do
2.times do |i| 2.times do |i|
......
...@@ -148,7 +148,7 @@ RSpec.describe Clusters::Applications::Prometheus do ...@@ -148,7 +148,7 @@ RSpec.describe Clusters::Applications::Prometheus do
subject { prometheus.install_command } 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 it 'is initialized with 3 arguments' do
expect(subject.name).to eq('prometheus') expect(subject.name).to eq('prometheus')
...@@ -195,7 +195,7 @@ RSpec.describe Clusters::Applications::Prometheus do ...@@ -195,7 +195,7 @@ RSpec.describe Clusters::Applications::Prometheus do
subject { prometheus.uninstall_command } 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 it 'has the application name' do
expect(subject.name).to eq('prometheus') expect(subject.name).to eq('prometheus')
...@@ -236,7 +236,7 @@ RSpec.describe Clusters::Applications::Prometheus do ...@@ -236,7 +236,7 @@ RSpec.describe Clusters::Applications::Prometheus do
let(:prometheus) { build(:clusters_applications_prometheus) } let(:prometheus) { build(:clusters_applications_prometheus) }
let(:values) { prometheus.values } 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 it 'is initialized with 3 arguments' do
expect(patch_command.name).to eq('prometheus') expect(patch_command.name).to eq('prometheus')
......
...@@ -27,7 +27,7 @@ RSpec.describe Clusters::Applications::Runner do ...@@ -27,7 +27,7 @@ RSpec.describe Clusters::Applications::Runner do
subject { gitlab_runner.install_command } 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 it 'is initialized with 4 arguments' do
expect(subject.name).to eq('runner') expect(subject.name).to eq('runner')
......
...@@ -540,6 +540,27 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do ...@@ -540,6 +540,27 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end end
end 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 end
describe '.ancestor_clusters_for_clusterable' do describe '.ancestor_clusters_for_clusterable' do
......
...@@ -14,7 +14,7 @@ RSpec.describe Clusters::Applications::UninstallService, '#execute' do ...@@ -14,7 +14,7 @@ RSpec.describe Clusters::Applications::UninstallService, '#execute' do
context 'when there are no errors' do context 'when there are no errors' do
before 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) allow(worker_class).to receive(:perform_in).and_return(nil)
end end
...@@ -36,7 +36,7 @@ RSpec.describe Clusters::Applications::UninstallService, '#execute' do ...@@ -36,7 +36,7 @@ RSpec.describe Clusters::Applications::UninstallService, '#execute' do
let(:error) { Kubeclient::HttpError.new(500, 'system failure', nil) } let(:error) { Kubeclient::HttpError.new(500, 'system failure', nil) }
before do 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 end
include_examples 'logs kubernetes errors' do include_examples 'logs kubernetes errors' do
...@@ -58,7 +58,7 @@ RSpec.describe Clusters::Applications::UninstallService, '#execute' do ...@@ -58,7 +58,7 @@ RSpec.describe Clusters::Applications::UninstallService, '#execute' do
let(:error) { StandardError.new('something bad happened') } let(:error) { StandardError.new('something bad happened') }
before do 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 end
include_examples 'logs kubernetes errors' do include_examples 'logs kubernetes errors' do
......
...@@ -15,6 +15,18 @@ RSpec.shared_examples 'helm command generator' do ...@@ -15,6 +15,18 @@ RSpec.shared_examples 'helm command generator' do
end end
RSpec.shared_examples 'helm command' do 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 describe '#rbac?' do
subject { command.rbac? } subject { command.rbac? }
......
...@@ -25,4 +25,21 @@ RSpec.shared_examples 'cluster application core specs' do |application_name| ...@@ -25,4 +25,21 @@ RSpec.shared_examples 'cluster application core specs' do |application_name|
describe '.association_name' do describe '.association_name' do
it { expect(described_class.association_name).to eq(:"application_#{subject.name}") } it { expect(described_class.association_name).to eq(:"application_#{subject.name}") }
end 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 end
...@@ -6,7 +6,7 @@ RSpec.shared_examples 'cluster application helm specs' do |application_name| ...@@ -6,7 +6,7 @@ RSpec.shared_examples 'cluster application helm specs' do |application_name|
describe '#uninstall_command' do describe '#uninstall_command' do
subject { application.uninstall_command } 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 it 'has files' do
expect(subject.files).to eq(application.files) 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