Add Helm InstallCommand
This commit is contained in:
		
							parent
							
								
									760a154a03
								
							
						
					
					
						commit
						8ec618a6ed
					
				|  | @ -26,6 +26,10 @@ module Clusters | |||
|       def name | ||||
|         self.class.application_name | ||||
|       end | ||||
| 
 | ||||
|       def install_command | ||||
|         Gitlab::Kubernetes::Helm::InstallCommand.new(name, true) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -20,6 +20,10 @@ module Clusters | |||
|       def helm_api | ||||
|         @helm_api ||= Gitlab::Kubernetes::Helm.new(kubeclient) | ||||
|       end | ||||
| 
 | ||||
|       def install_command | ||||
|         @install_command ||= app.install_command | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -48,17 +48,17 @@ module Clusters | |||
|       end | ||||
| 
 | ||||
|       def remove_installation_pod | ||||
|         helm_api.delete_installation_pod!(app) | ||||
|         helm_api.delete_installation_pod!(install_command.pod_name) | ||||
|       rescue | ||||
|         # no-op | ||||
|       end | ||||
| 
 | ||||
|       def installation_phase | ||||
|         helm_api.installation_status(app) | ||||
|         helm_api.installation_status(install_command.pod_name) | ||||
|       end | ||||
| 
 | ||||
|       def installation_errors | ||||
|         helm_api.installation_log(app) | ||||
|         helm_api.installation_log(install_command.pod_name) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ module Clusters | |||
| 
 | ||||
|         begin | ||||
|           app.make_installing! | ||||
|           helm_api.install(app) | ||||
|           helm_api.install(install_command) | ||||
| 
 | ||||
|           ClusterWaitForAppInstallationWorker.perform_in( | ||||
|             ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id) | ||||
|  |  | |||
|  | @ -3,27 +3,27 @@ module Gitlab | |||
|     class Helm | ||||
|       HELM_VERSION = '2.7.0'.freeze | ||||
|       NAMESPACE = 'gitlab-managed-apps'.freeze | ||||
|       COMMAND_SCRIPT = <<-EOS.freeze | ||||
|       INSTALL_DEPS = <<-EOS.freeze | ||||
|         set -eo pipefail | ||||
|         apk add -U ca-certificates openssl >/dev/null | ||||
|         wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null | ||||
|         mv /tmp/linux-amd64/helm /usr/bin/ | ||||
|         helm init ${HELM_INIT_OPTS} >/dev/null | ||||
|         [[ -z "${HELM_COMMAND+x}" ]] || helm ${HELM_COMMAND} >/dev/null | ||||
|       EOS | ||||
| 
 | ||||
|       InstallCommand = Struct.new(:name, :install_helm, :chart) do | ||||
|         def pod_name | ||||
|           "install-#{name}" | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       def initialize(kubeclient) | ||||
|         @kubeclient = kubeclient | ||||
|         @namespace = Namespace.new(NAMESPACE, kubeclient) | ||||
|       end | ||||
| 
 | ||||
|       def init! | ||||
|         install(OpenStruct.new(name: 'helm')) | ||||
|       end | ||||
| 
 | ||||
|       def install(app) | ||||
|       def install(command) | ||||
|         @namespace.ensure_exists! | ||||
|         @kubeclient.create_pod(pod_resource(app)) | ||||
|         @kubeclient.create_pod(pod_resource(command)) | ||||
|       end | ||||
| 
 | ||||
|       ## | ||||
|  | @ -33,31 +33,27 @@ module Gitlab | |||
|       # | ||||
|       # values: "Pending", "Running", "Succeeded", "Failed", "Unknown" | ||||
|       # | ||||
|       def installation_status(app) | ||||
|         @kubeclient.get_pod(pod_name(app), @namespace.name).status.phase | ||||
|       def installation_status(pod_name) | ||||
|         @kubeclient.get_pod(pod_name, @namespace.name).status.phase | ||||
|       end | ||||
| 
 | ||||
|       def installation_log(app) | ||||
|         @kubeclient.get_pod_log(pod_name(app), @namespace.name).body | ||||
|       def installation_log(pod_name) | ||||
|         @kubeclient.get_pod_log(pod_name, @namespace.name).body | ||||
|       end | ||||
| 
 | ||||
|       def delete_installation_pod!(app) | ||||
|         @kubeclient.delete_pod(pod_name(app), @namespace.name) | ||||
|       def delete_installation_pod!(pod_name) | ||||
|         @kubeclient.delete_pod(pod_name, @namespace.name) | ||||
|       end | ||||
| 
 | ||||
|       private | ||||
| 
 | ||||
|       def pod_name(app) | ||||
|         "install-#{app.name}" | ||||
|       end | ||||
| 
 | ||||
|       def pod_resource(app) | ||||
|         labels = { 'gitlab.org/action': 'install', 'gitlab.org/application': app.name } | ||||
|         metadata = { name: pod_name(app), namespace: @namespace.name, labels: labels } | ||||
|       def pod_resource(command) | ||||
|         labels = { 'gitlab.org/action': 'install', 'gitlab.org/application': command.name } | ||||
|         metadata = { name: command.pod_name, namespace: @namespace.name, labels: labels } | ||||
|         container = { | ||||
|           name: 'helm', | ||||
|           image: 'alpine:3.6', | ||||
|           env: generate_pod_env(app), | ||||
|           env: generate_pod_env(command), | ||||
|           command: %w(/bin/sh), | ||||
|           args: %w(-c $(COMMAND_SCRIPT)) | ||||
|         } | ||||
|  | @ -66,23 +62,34 @@ module Gitlab | |||
|         ::Kubeclient::Resource.new(metadata: metadata, spec: spec) | ||||
|       end | ||||
| 
 | ||||
|       def generate_pod_env(app) | ||||
|         env = { | ||||
|       def generate_pod_env(command) | ||||
|         { | ||||
|           HELM_VERSION: HELM_VERSION, | ||||
|           TILLER_NAMESPACE: NAMESPACE, | ||||
|           COMMAND_SCRIPT: COMMAND_SCRIPT | ||||
|         } | ||||
| 
 | ||||
|         if app.name != 'helm' | ||||
|           env[:HELM_INIT_OPTS] = '--client-only' | ||||
|           env[:HELM_COMMAND] = helm_install_comand(app) | ||||
|         end | ||||
| 
 | ||||
|         env.map { |key, value| { name: key, value: value } } | ||||
|           TILLER_NAMESPACE: @namespace.name, | ||||
|           COMMAND_SCRIPT: generate_script(command) | ||||
|         }.map { |key, value| { name: key, value: value } } | ||||
|       end | ||||
| 
 | ||||
|       def helm_install_comand(app) | ||||
|         "install #{app.chart} --name #{app.name} --namespace #{NAMESPACE}" | ||||
|       def generate_script(command) | ||||
|         [ | ||||
|             INSTALL_DEPS, | ||||
|             helm_init_command(command), | ||||
|             helm_install_command(command) | ||||
|         ].join("\n") | ||||
|       end | ||||
| 
 | ||||
|       def helm_init_command(command) | ||||
|         if command.install_helm | ||||
|           'helm init >/dev/null' | ||||
|         else | ||||
|           'helm init --client-only >/dev/null' | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       def helm_install_command(command) | ||||
|         return if command.chart.nil? | ||||
| 
 | ||||
|         "helm install #{command.chart} --name #{command.name} --namespace #{@namespace.name} >/dev/null" | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  |  | |||
|  | @ -0,0 +1,100 @@ | |||
| require 'spec_helper' | ||||
| 
 | ||||
| describe Gitlab::Kubernetes::Helm do | ||||
|   let(:client) { double('kubernetes client') } | ||||
|   let(:helm) { described_class.new(client) } | ||||
|   let(:namespace) { Gitlab::Kubernetes::Namespace.new(described_class::NAMESPACE, client) } | ||||
|   let(:install_helm) { true } | ||||
|   let(:chart) { 'stable/a_chart' } | ||||
|   let(:application_name) { 'app_name' } | ||||
|   let(:command) { Gitlab::Kubernetes::Helm::InstallCommand.new(application_name, install_helm, chart) } | ||||
|   subject { helm } | ||||
| 
 | ||||
|   before do | ||||
|     allow(Gitlab::Kubernetes::Namespace).to receive(:new).with(described_class::NAMESPACE, client).and_return(namespace) | ||||
|   end | ||||
| 
 | ||||
|   describe '#initialize' do | ||||
|     it 'creates a namespace object' do | ||||
|       expect(Gitlab::Kubernetes::Namespace).to receive(:new).with(described_class::NAMESPACE, client) | ||||
| 
 | ||||
|       subject | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#install' do | ||||
|     before do | ||||
|       allow(client).to receive(:create_pod).and_return(nil) | ||||
|       allow(namespace).to receive(:ensure_exists!).once | ||||
|     end | ||||
| 
 | ||||
|     it 'ensures the namespace exists before creating the POD' do | ||||
|       expect(namespace).to receive(:ensure_exists!).once.ordered | ||||
|       expect(client).to receive(:create_pod).once.ordered | ||||
| 
 | ||||
|       subject.install(command) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#installation_status' do | ||||
|     let(:phase) { Gitlab::Kubernetes::Pod::RUNNING } | ||||
|     let(:pod) { Kubeclient::Resource.new(status: { phase: phase }) } # partial representation | ||||
| 
 | ||||
|     it 'fetches POD phase from kubernetes cluster' do | ||||
|       expect(client).to receive(:get_pod).with(command.pod_name, described_class::NAMESPACE).once.and_return(pod) | ||||
| 
 | ||||
|       expect(subject.installation_status(command.pod_name)).to eq(phase) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#installation_log' do | ||||
|     let(:log) { 'some output' } | ||||
|     let(:response) { RestClient::Response.new(log) } | ||||
| 
 | ||||
|     it 'fetches POD phase from kubernetes cluster' do | ||||
|       expect(client).to receive(:get_pod_log).with(command.pod_name, described_class::NAMESPACE).once.and_return(response) | ||||
| 
 | ||||
|       expect(subject.installation_log(command.pod_name)).to eq(log) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#delete_installation_pod!' do | ||||
|     it 'deletes the POD from kubernetes cluster' do | ||||
|       expect(client).to receive(:delete_pod).with(command.pod_name, described_class::NAMESPACE).once | ||||
| 
 | ||||
|       subject.delete_installation_pod!(command.pod_name) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#helm_init_command' do | ||||
|     subject { helm.send(:helm_init_command, command) } | ||||
| 
 | ||||
|     context 'when command.install_helm is true' do | ||||
|       let(:install_helm) { true } | ||||
| 
 | ||||
|       it { is_expected.to eq('helm init >/dev/null') } | ||||
|     end | ||||
| 
 | ||||
|     context 'when command.install_helm is false' do | ||||
|       let(:install_helm) { false } | ||||
| 
 | ||||
|       it { is_expected.to eq('helm init --client-only >/dev/null') } | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#helm_install_command' do | ||||
|     subject { helm.send(:helm_install_command, command) } | ||||
| 
 | ||||
|     context 'when command.chart is nil' do | ||||
|       let(:chart) { nil } | ||||
| 
 | ||||
|       it { is_expected.to be_nil } | ||||
|     end | ||||
| 
 | ||||
|     context 'when command.chart is set' do | ||||
|       let(:chart) { 'stable/a_chart' } | ||||
| 
 | ||||
|       it { is_expected.to eq("helm install #{chart} --name #{application_name} --namespace #{namespace.name} >/dev/null")} | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -38,6 +38,12 @@ describe Clusters::Applications::Helm do | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#install_command' do | ||||
|     it 'has all the needed information' do | ||||
|       expect(subject.install_command).to have_attributes(name: subject.name, install_helm: true, chart: nil) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe 'status state machine' do | ||||
|     describe '#make_installing' do | ||||
|       subject { create(:cluster_applications_helm, :scheduled) } | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ describe Clusters::Applications::InstallService do | |||
| 
 | ||||
|     context 'when there are no errors' do | ||||
|       before do | ||||
|         expect(helm_client).to receive(:install).with(application) | ||||
|         expect(helm_client).to receive(:install).with(application.install_command) | ||||
|         allow(ClusterWaitForAppInstallationWorker).to receive(:perform_in).and_return(nil) | ||||
|       end | ||||
| 
 | ||||
|  | @ -33,7 +33,7 @@ describe Clusters::Applications::InstallService do | |||
|     context 'when k8s cluster communication fails' do | ||||
|       before do | ||||
|         error = KubeException.new(500, 'system failure', nil) | ||||
|         expect(helm_client).to receive(:install).with(application).and_raise(error) | ||||
|         expect(helm_client).to receive(:install).with(application.install_command).and_raise(error) | ||||
|       end | ||||
| 
 | ||||
|       it 'make the application errored' do | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue