Merge branch '49952-port-upgrade-command-to-ce' into 'master'
Port UpgradeCommand to CE See merge request gitlab-org/gitlab-ce!21949
This commit is contained in:
		
						commit
						607f263c48
					
				| 
						 | 
				
			
			@ -15,6 +15,9 @@ module Clusters
 | 
			
		|||
          state :scheduled, value: 1
 | 
			
		||||
          state :installing, value: 2
 | 
			
		||||
          state :installed, value: 3
 | 
			
		||||
          state :updating, value: 4
 | 
			
		||||
          state :updated, value: 5
 | 
			
		||||
          state :update_errored, value: 6
 | 
			
		||||
 | 
			
		||||
          event :make_scheduled do
 | 
			
		||||
            transition [:installable, :errored] => :scheduled
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +35,18 @@ module Clusters
 | 
			
		|||
            transition any => :errored
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          event :make_updating do
 | 
			
		||||
            transition [:installed, :updated, :update_errored] => :updating
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          event :make_updated do
 | 
			
		||||
            transition [:updating] => :updated
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          event :make_update_errored do
 | 
			
		||||
            transition any => :update_errored
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          before_transition any => [:scheduled] do |app_status, _|
 | 
			
		||||
            app_status.status_reason = nil
 | 
			
		||||
          end
 | 
			
		||||
| 
						 | 
				
			
			@ -40,6 +55,15 @@ module Clusters
 | 
			
		|||
            status_reason = transition.args.first
 | 
			
		||||
            app_status.status_reason = status_reason if status_reason
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          before_transition any => [:updating] do |app_status, _|
 | 
			
		||||
            app_status.status_reason = nil
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          before_transition any => [:update_errored] do |app_status, transition|
 | 
			
		||||
            status_reason = transition.args.first
 | 
			
		||||
            app_status.status_reason = status_reason if status_reason
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,12 @@ module Gitlab
 | 
			
		|||
          kubeclient.create_pod(command.pod_resource)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def update(command)
 | 
			
		||||
          namespace.ensure_exists!
 | 
			
		||||
          update_config_map(command)
 | 
			
		||||
          kubeclient.create_pod(command.pod_resource)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ##
 | 
			
		||||
        # Returns Pod phase
 | 
			
		||||
        #
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +42,12 @@ module Gitlab
 | 
			
		|||
          kubeclient.delete_pod(pod_name, namespace.name)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def get_config_map(config_map_name)
 | 
			
		||||
          namespace.ensure_exists!
 | 
			
		||||
 | 
			
		||||
          kubeclient.get_config_map(config_map_name, namespace.name)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        private
 | 
			
		||||
 | 
			
		||||
        attr_reader :kubeclient, :namespace
 | 
			
		||||
| 
						 | 
				
			
			@ -46,6 +58,12 @@ module Gitlab
 | 
			
		|||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def update_config_map(command)
 | 
			
		||||
          command.config_map_resource.tap do |config_map_resource|
 | 
			
		||||
            kubeclient.update_config_map(config_map_resource)
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def create_service_account(command)
 | 
			
		||||
          command.service_account_resource.tap do |service_account_resource|
 | 
			
		||||
            break unless service_account_resource
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Gitlab
 | 
			
		||||
  module Kubernetes
 | 
			
		||||
    module Helm
 | 
			
		||||
      class UpgradeCommand
 | 
			
		||||
        include BaseCommand
 | 
			
		||||
 | 
			
		||||
        attr_reader :name, :chart, :version, :repository, :files
 | 
			
		||||
 | 
			
		||||
        def initialize(name, chart:, files:, rbac:, version: nil, repository: nil)
 | 
			
		||||
          @name = name
 | 
			
		||||
          @chart = chart
 | 
			
		||||
          @rbac = rbac
 | 
			
		||||
          @version = version
 | 
			
		||||
          @files = files
 | 
			
		||||
          @repository = repository
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def generate_script
 | 
			
		||||
          super + [
 | 
			
		||||
            init_command,
 | 
			
		||||
            repository_command,
 | 
			
		||||
            script_command
 | 
			
		||||
          ].compact.join("\n")
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def rbac?
 | 
			
		||||
          @rbac
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def pod_name
 | 
			
		||||
          "upgrade-#{name}"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        private
 | 
			
		||||
 | 
			
		||||
        def init_command
 | 
			
		||||
          'helm init --client-only >/dev/null'
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def repository_command
 | 
			
		||||
          "helm repo add #{name} #{repository}" if repository
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def script_command
 | 
			
		||||
          upgrade_flags = "#{optional_version_flag}#{optional_tls_flags}" \
 | 
			
		||||
            " --reset-values" \
 | 
			
		||||
            " --install" \
 | 
			
		||||
            " --namespace #{::Gitlab::Kubernetes::Helm::NAMESPACE}" \
 | 
			
		||||
            " -f /data/helm/#{name}/config/values.yaml"
 | 
			
		||||
 | 
			
		||||
          "helm upgrade #{name} #{chart}#{upgrade_flags} >/dev/null\n"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def optional_version_flag
 | 
			
		||||
          " --version #{version}" if version
 | 
			
		||||
        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
 | 
			
		||||
| 
						 | 
				
			
			@ -22,11 +22,24 @@ FactoryBot.define do
 | 
			
		|||
      status 3
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    trait :updating do
 | 
			
		||||
      status 4
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    trait :updated do
 | 
			
		||||
      status 5
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    trait :errored do
 | 
			
		||||
      status(-1)
 | 
			
		||||
      status_reason 'something went wrong'
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    trait :update_errored do
 | 
			
		||||
      status(6)
 | 
			
		||||
      status_reason 'something went wrong'
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    trait :timeouted do
 | 
			
		||||
      installing
 | 
			
		||||
      updated_at ClusterWaitForAppInstallationWorker::TIMEOUT.ago
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -150,6 +150,43 @@ describe Gitlab::Kubernetes::Helm::Api do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#update' do
 | 
			
		||||
    let(:rbac) { false }
 | 
			
		||||
 | 
			
		||||
    let(:command) do
 | 
			
		||||
      Gitlab::Kubernetes::Helm::UpgradeCommand.new(
 | 
			
		||||
        application_name,
 | 
			
		||||
        chart: 'chart-name',
 | 
			
		||||
        files: files,
 | 
			
		||||
        rbac: rbac
 | 
			
		||||
      )
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    before do
 | 
			
		||||
      allow(namespace).to receive(:ensure_exists!).once
 | 
			
		||||
 | 
			
		||||
      allow(client).to receive(:update_config_map).and_return(nil)
 | 
			
		||||
      allow(client).to receive(:create_pod).and_return(nil)
 | 
			
		||||
    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.update(command)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'updates the config map on kubeclient when one exists' do
 | 
			
		||||
      resource = Gitlab::Kubernetes::ConfigMap.new(
 | 
			
		||||
        application_name, files
 | 
			
		||||
      ).generate
 | 
			
		||||
 | 
			
		||||
      expect(client).to receive(:update_config_map).with(resource).once
 | 
			
		||||
 | 
			
		||||
      subject.update(command)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#status' do
 | 
			
		||||
    let(:phase) { Gitlab::Kubernetes::Pod::RUNNING }
 | 
			
		||||
    let(:pod) { Kubeclient::Resource.new(status: { phase: phase }) } # partial representation
 | 
			
		||||
| 
						 | 
				
			
			@ -179,4 +216,25 @@ describe Gitlab::Kubernetes::Helm::Api do
 | 
			
		|||
      subject.delete_pod!(command.pod_name)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#get_config_map' do
 | 
			
		||||
    before do
 | 
			
		||||
      allow(namespace).to receive(:ensure_exists!).once
 | 
			
		||||
      allow(client).to receive(:get_config_map).and_return(nil)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'ensures the namespace exists before retrieving the config map' do
 | 
			
		||||
      expect(namespace).to receive(:ensure_exists!).once
 | 
			
		||||
 | 
			
		||||
      subject.get_config_map('example-config-map-name')
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'gets the config map on kubeclient' do
 | 
			
		||||
      expect(client).to receive(:get_config_map)
 | 
			
		||||
        .with('example-config-map-name', namespace.name)
 | 
			
		||||
        .once
 | 
			
		||||
 | 
			
		||||
      subject.get_config_map('example-config-map-name')
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,136 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rails_helper'
 | 
			
		||||
 | 
			
		||||
describe Gitlab::Kubernetes::Helm::UpgradeCommand do
 | 
			
		||||
  let(:application) { build(:clusters_applications_prometheus) }
 | 
			
		||||
  let(:files) { { 'ca.pem': 'some file content' } }
 | 
			
		||||
  let(:namespace) { ::Gitlab::Kubernetes::Helm::NAMESPACE }
 | 
			
		||||
  let(:rbac) { false }
 | 
			
		||||
  let(:upgrade_command) do
 | 
			
		||||
    described_class.new(
 | 
			
		||||
      application.name,
 | 
			
		||||
      chart: application.chart,
 | 
			
		||||
      files: files,
 | 
			
		||||
      rbac: rbac
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  subject { upgrade_command }
 | 
			
		||||
 | 
			
		||||
  it_behaves_like 'helm commands' do
 | 
			
		||||
    let(:commands) do
 | 
			
		||||
      <<~EOS
 | 
			
		||||
         helm init --client-only >/dev/null
 | 
			
		||||
         helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null
 | 
			
		||||
      EOS
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context 'rbac is true' do
 | 
			
		||||
    let(:rbac) { true }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like 'helm commands' do
 | 
			
		||||
      let(:commands) do
 | 
			
		||||
        <<~EOS
 | 
			
		||||
         helm init --client-only >/dev/null
 | 
			
		||||
         helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null
 | 
			
		||||
        EOS
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context 'with an application with a repository' do
 | 
			
		||||
    let(:ci_runner) { create(:ci_runner) }
 | 
			
		||||
    let(:application) { build(:clusters_applications_runner, runner: ci_runner) }
 | 
			
		||||
    let(:upgrade_command) do
 | 
			
		||||
      described_class.new(
 | 
			
		||||
        application.name,
 | 
			
		||||
        chart: application.chart,
 | 
			
		||||
        files: files,
 | 
			
		||||
        rbac: rbac,
 | 
			
		||||
        repository: application.repository
 | 
			
		||||
      )
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it_behaves_like 'helm commands' do
 | 
			
		||||
      let(:commands) do
 | 
			
		||||
        <<~EOS
 | 
			
		||||
           helm init --client-only >/dev/null
 | 
			
		||||
           helm repo add #{application.name} #{application.repository}
 | 
			
		||||
           helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null
 | 
			
		||||
        EOS
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context 'when there is no ca.pem file' do
 | 
			
		||||
    let(:files) { { 'file.txt': 'some content' } }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like 'helm commands' do
 | 
			
		||||
      let(:commands) do
 | 
			
		||||
        <<~EOS
 | 
			
		||||
         helm init --client-only >/dev/null
 | 
			
		||||
         helm upgrade #{application.name} #{application.chart} --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null
 | 
			
		||||
        EOS
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#pod_resource' do
 | 
			
		||||
    subject { upgrade_command.pod_resource }
 | 
			
		||||
 | 
			
		||||
    context 'rbac is enabled' do
 | 
			
		||||
      let(:rbac) { true }
 | 
			
		||||
 | 
			
		||||
      it 'generates a pod that uses the tiller serviceAccountName' do
 | 
			
		||||
        expect(subject.spec.serviceAccountName).to eq('tiller')
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'rbac is not enabled' do
 | 
			
		||||
      let(:rbac) { false }
 | 
			
		||||
 | 
			
		||||
      it 'generates a pod that uses the default serviceAccountName' do
 | 
			
		||||
        expect(subject.spec.serviceAcccountName).to be_nil
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#config_map_resource' do
 | 
			
		||||
    let(:metadata) do
 | 
			
		||||
      {
 | 
			
		||||
        name: "values-content-configuration-#{application.name}",
 | 
			
		||||
        namespace: namespace,
 | 
			
		||||
        labels: { name: "values-content-configuration-#{application.name}" }
 | 
			
		||||
      }
 | 
			
		||||
    end
 | 
			
		||||
    let(:resource) { ::Kubeclient::Resource.new(metadata: metadata, data: files) }
 | 
			
		||||
 | 
			
		||||
    it 'returns a KubeClient resource with config map content for the application' do
 | 
			
		||||
      expect(subject.config_map_resource).to eq(resource)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#rbac?' do
 | 
			
		||||
    subject { upgrade_command.rbac? }
 | 
			
		||||
 | 
			
		||||
    context 'rbac is enabled' do
 | 
			
		||||
      let(:rbac) { true }
 | 
			
		||||
 | 
			
		||||
      it { is_expected.to be_truthy }
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'rbac is not enabled' do
 | 
			
		||||
      let(:rbac) { false }
 | 
			
		||||
 | 
			
		||||
      it { is_expected.to be_falsey }
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#pod_name' do
 | 
			
		||||
    it 'returns the pod name' do
 | 
			
		||||
      expect(subject.pod_name).to eq("upgrade-#{application.name}")
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
		Loading…
	
		Reference in New Issue