Extend Cluster Applications to install GitLab Runner to Kubernetes cluster

This commit is contained in:
Mayra Cabrera 2018-03-01 23:46:02 +00:00 committed by Kamil Trzciński
parent 947a7f8587
commit c607008ee5
35 changed files with 768 additions and 421 deletions

View file

@ -99,12 +99,6 @@
</p>
`;
},
gitlabRunnerDescription() {
return _.escape(s__(
`ClusterIntegration|GitLab Runner is the open source project that is used to run your jobs
and send the results back to GitLab.`,
));
},
prometheusDescription() {
return sprintf(
_.escape(s__(
@ -256,6 +250,22 @@
>
</div>
</application-row>
<application-row
id="runner"
:title="applications.runner.title"
title-link="https://docs.gitlab.com/runner/"
:status="applications.runner.status"
:status-reason="applications.runner.statusReason"
:request-status="applications.runner.requestStatus"
:request-reason="applications.runner.requestReason"
>
<div slot="description">
{{ s__(`ClusterIntegration|GitLab Runner connects to this
project's repository and executes CI/CD jobs,
pushing results back and deploying,
applications to production.`) }}
</div>
</application-row>
<!--
NOTE: Don't forget to update `clusters.scss`
min-height for this block and uncomment `application_spec` tests

View file

@ -15,7 +15,7 @@ module Clusters
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, install_helm: true)
Gitlab::Kubernetes::Helm::InitCommand.new(name)
end
end
end

View file

@ -5,6 +5,7 @@ module Clusters
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
include ::Clusters::Concerns::ApplicationData
include AfterCommitQueue
default_value_for :ingress_type, :nginx
@ -29,12 +30,12 @@ module Clusters
'stable/nginx-ingress'
end
def chart_values_file
"#{Rails.root}/vendor/#{name}/values.yaml"
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
Gitlab::Kubernetes::Helm::InstallCommand.new(
name,
chart: chart,
values: values
)
end
def schedule_status_update

View file

@ -7,6 +7,7 @@ module Clusters
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
include ::Clusters::Concerns::ApplicationData
default_value_for :version, VERSION
@ -30,12 +31,12 @@ module Clusters
80
end
def chart_values_file
"#{Rails.root}/vendor/#{name}/values.yaml"
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
Gitlab::Kubernetes::Helm::InstallCommand.new(
name,
chart: chart,
values: values
)
end
def proxy_client

View file

@ -0,0 +1,68 @@
module Clusters
module Applications
class Runner < ActiveRecord::Base
VERSION = '0.1.13'.freeze
self.table_name = 'clusters_applications_runners'
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
include ::Clusters::Concerns::ApplicationData
belongs_to :runner, class_name: 'Ci::Runner', foreign_key: :runner_id
delegate :project, to: :cluster
default_value_for :version, VERSION
def chart
"#{name}/gitlab-runner"
end
def repository
'https://charts.gitlab.io'
end
def values
content_values.to_yaml
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name,
chart: chart,
values: values,
repository: repository
)
end
private
def ensure_runner
runner || create_and_assign_runner
end
def create_and_assign_runner
transaction do
project.runners.create!(name: 'kubernetes-cluster', tag_list: %w(kubernetes cluster)).tap do |runner|
update!(runner_id: runner.id)
end
end
end
def gitlab_url
Gitlab::Routing.url_helpers.root_url(only_path: false)
end
def specification
{
"gitlabUrl" => gitlab_url,
"runnerToken" => ensure_runner.token
}
end
def content_values
specification.merge(YAML.load_file(chart_values_file))
end
end
end
end

View file

@ -7,7 +7,8 @@ module Clusters
APPLICATIONS = {
Applications::Helm.application_name => Applications::Helm,
Applications::Ingress.application_name => Applications::Ingress,
Applications::Prometheus.application_name => Applications::Prometheus
Applications::Prometheus.application_name => Applications::Prometheus,
Applications::Runner.application_name => Applications::Runner
}.freeze
belongs_to :user
@ -23,6 +24,7 @@ module Clusters
has_one :application_helm, class_name: 'Clusters::Applications::Helm'
has_one :application_ingress, class_name: 'Clusters::Applications::Ingress'
has_one :application_prometheus, class_name: 'Clusters::Applications::Prometheus'
has_one :application_runner, class_name: 'Clusters::Applications::Runner'
accepts_nested_attributes_for :provider_gcp, update_only: true
accepts_nested_attributes_for :platform_kubernetes, update_only: true
@ -68,7 +70,8 @@ module Clusters
[
application_helm || build_application_helm,
application_ingress || build_application_ingress,
application_prometheus || build_application_prometheus
application_prometheus || build_application_prometheus,
application_runner || build_application_runner
]
end

View file

@ -0,0 +1,23 @@
module Clusters
module Concerns
module ApplicationData
extend ActiveSupport::Concern
included do
def repository
nil
end
def values
File.read(chart_values_file)
end
private
def chart_values_file
"#{Rails.root}/vendor/#{name}/values.yaml"
end
end
end
end
end

View file

@ -10,6 +10,7 @@
install_helm_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :helm),
install_ingress_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :ingress),
install_prometheus_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :prometheus),
install_runner_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :runner),
toggle_status: @cluster.enabled? ? 'true': 'false',
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,

View file

@ -0,0 +1,5 @@
---
title: Allow installation of GitLab Runner with a single click
merge_request: 17134
author:
type: added

View file

@ -0,0 +1,32 @@
class CreateClustersApplicationsRunners < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
create_table :clusters_applications_runners do |t|
t.references :cluster, null: false, foreign_key: { on_delete: :cascade }
t.references :runner, references: :ci_runners
t.index :runner_id
t.index :cluster_id, unique: true
t.integer :status, null: false
t.timestamps_with_timezone null: false
t.string :version, null: false
t.text :status_reason
end
add_concurrent_foreign_key :clusters_applications_runners, :ci_runners,
column: :runner_id,
on_delete: :nullify
end
def down
if foreign_keys_for(:clusters_applications_runners, :runner_id).any?
remove_foreign_key :clusters_applications_runners, column: :runner_id
end
drop_table :clusters_applications_runners
end
end

View file

@ -582,6 +582,19 @@ ActiveRecord::Schema.define(version: 20180301084653) do
t.datetime_with_timezone "updated_at", null: false
end
create_table "clusters_applications_runners", force: :cascade do |t|
t.integer "cluster_id", null: false
t.integer "runner_id"
t.integer "status", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.string "version", null: false
t.text "status_reason"
end
add_index "clusters_applications_runners", ["cluster_id"], name: "index_clusters_applications_runners_on_cluster_id", unique: true, using: :btree
add_index "clusters_applications_runners", ["runner_id"], name: "index_clusters_applications_runners_on_runner_id", using: :btree
create_table "container_repositories", force: :cascade do |t|
t.integer "project_id", null: false
t.string "name", null: false
@ -1988,6 +2001,8 @@ ActiveRecord::Schema.define(version: 20180301084653) do
add_foreign_key "cluster_providers_gcp", "clusters", on_delete: :cascade
add_foreign_key "clusters", "users", on_delete: :nullify
add_foreign_key "clusters_applications_helm", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_runners", "ci_runners", column: "runner_id", name: "fk_02de2ded36", on_delete: :nullify
add_foreign_key "clusters_applications_runners", "clusters", on_delete: :cascade
add_foreign_key "container_repositories", "projects"
add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade
add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade

View file

@ -120,6 +120,7 @@ added directly to your configured cluster. Those applications are needed for
| [Helm Tiller](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It will be automatically installed as a dependency when you try to install a different app. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. |
| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. |
| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications |
| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. |
## Getting the external IP address

View file

@ -0,0 +1,37 @@
module Gitlab
module Kubernetes
class ConfigMap
def initialize(name, values)
@name = name
@values = values
end
def generate
resource = ::Kubeclient::Resource.new
resource.metadata = metadata
resource.data = { values: values }
resource
end
private
attr_reader :name, :values
def metadata
{
name: config_map_name,
namespace: namespace,
labels: { name: config_map_name }
}
end
def config_map_name
"values-content-configuration-#{name}"
end
def namespace
Gitlab::Kubernetes::Helm::NAMESPACE
end
end
end
end

View file

@ -9,7 +9,8 @@ module Gitlab
def install(command)
@namespace.ensure_exists!
@kubeclient.create_pod(pod_resource(command))
create_config_map(command) if command.config_map?
@kubeclient.create_pod(command.pod_resource)
end
##
@ -33,8 +34,10 @@ module Gitlab
private
def pod_resource(command)
Gitlab::Kubernetes::Helm::Pod.new(command, @namespace.name, @kubeclient).generate
def create_config_map(command)
command.config_map_resource.tap do |config_map_resource|
@kubeclient.create_config_map(config_map_resource)
end
end
end
end

View file

@ -0,0 +1,40 @@
module Gitlab
module Kubernetes
module Helm
class BaseCommand
attr_reader :name
def initialize(name)
@name = name
end
def pod_resource
Gitlab::Kubernetes::Helm::Pod.new(self, namespace).generate
end
def generate_script
<<~HEREDOC
set -eo pipefail
apk add -U ca-certificates openssl >/dev/null
wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v#{Gitlab::Kubernetes::Helm::HELM_VERSION}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
mv /tmp/linux-amd64/helm /usr/bin/
HEREDOC
end
def config_map?
false
end
def pod_name
"install-#{name}"
end
private
def namespace
Gitlab::Kubernetes::Helm::NAMESPACE
end
end
end
end
end

View file

@ -0,0 +1,19 @@
module Gitlab
module Kubernetes
module Helm
class InitCommand < BaseCommand
def generate_script
super + [
init_helm_command
].join("\n")
end
private
def init_helm_command
"helm init >/dev/null"
end
end
end
end
end

View file

@ -1,54 +1,45 @@
module Gitlab
module Kubernetes
module Helm
class InstallCommand
attr_reader :name, :install_helm, :chart, :chart_values_file
class InstallCommand < BaseCommand
attr_reader :name, :chart, :repository, :values
def initialize(name, install_helm: false, chart: false, chart_values_file: false)
def initialize(name, chart:, values:, repository: nil)
@name = name
@install_helm = install_helm
@chart = chart
@chart_values_file = chart_values_file
@values = values
@repository = repository
end
def pod_name
"install-#{name}"
end
def generate_script(namespace_name)
[
install_dps_command,
def generate_script
super + [
init_command,
complete_command(namespace_name)
].join("\n")
repository_command,
script_command
].compact.join("\n")
end
def config_map?
true
end
def config_map_resource
Gitlab::Kubernetes::ConfigMap.new(name, values).generate
end
private
def init_command
if install_helm
'helm init >/dev/null'
else
'helm init --client-only >/dev/null'
end
'helm init --client-only >/dev/null'
end
def complete_command(namespace_name)
return unless chart
if chart_values_file
"helm install #{chart} --name #{name} --namespace #{namespace_name} -f /data/helm/#{name}/config/values.yaml >/dev/null"
else
"helm install #{chart} --name #{name} --namespace #{namespace_name} >/dev/null"
end
def repository_command
"helm repo add #{name} #{repository}" if repository
end
def install_dps_command
def script_command
<<~HEREDOC
set -eo pipefail
apk add -U ca-certificates openssl >/dev/null
wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v#{Gitlab::Kubernetes::Helm::HELM_VERSION}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
mv /tmp/linux-amd64/helm /usr/bin/
helm install #{chart} --name #{name} --namespace #{Gitlab::Kubernetes::Helm::NAMESPACE} -f /data/helm/#{name}/config/values.yaml >/dev/null
HEREDOC
end
end

View file

@ -2,18 +2,17 @@ module Gitlab
module Kubernetes
module Helm
class Pod
def initialize(command, namespace_name, kubeclient)
def initialize(command, namespace_name)
@command = command
@namespace_name = namespace_name
@kubeclient = kubeclient
end
def generate
spec = { containers: [container_specification], restartPolicy: 'Never' }
if command.chart_values_file
create_config_map
if command.config_map?
spec[:volumes] = volumes_specification
spec[:containers][0][:volumeMounts] = volume_mounts_specification
end
::Kubeclient::Resource.new(metadata: metadata, spec: spec)
@ -21,18 +20,16 @@ module Gitlab
private
attr_reader :command, :namespace_name, :kubeclient
attr_reader :command, :namespace_name, :kubeclient, :config_map
def container_specification
container = {
{
name: 'helm',
image: 'alpine:3.6',
env: generate_pod_env(command),
command: %w(/bin/sh),
args: %w(-c $(COMMAND_SCRIPT))
}
container[:volumeMounts] = volume_mounts_specification if command.chart_values_file
container
end
def labels
@ -50,13 +47,12 @@ module Gitlab
}
end
def volume_mounts_specification
[
{
name: 'configuration-volume',
mountPath: "/data/helm/#{command.name}/config"
}
]
def generate_pod_env(command)
{
HELM_VERSION: Gitlab::Kubernetes::Helm::HELM_VERSION,
TILLER_NAMESPACE: namespace_name,
COMMAND_SCRIPT: command.generate_script
}.map { |key, value| { name: key, value: value } }
end
def volumes_specification
@ -71,23 +67,13 @@ module Gitlab
]
end
def generate_pod_env(command)
{
HELM_VERSION: Gitlab::Kubernetes::Helm::HELM_VERSION,
TILLER_NAMESPACE: namespace_name,
COMMAND_SCRIPT: command.generate_script(namespace_name)
}.map { |key, value| { name: key, value: value } }
end
def create_config_map
resource = ::Kubeclient::Resource.new
resource.metadata = {
name: "values-content-configuration-#{command.name}",
namespace: namespace_name,
labels: { name: "values-content-configuration-#{command.name}" }
}
resource.data = { values: File.read(command.chart_values_file) }
kubeclient.create_config_map(resource)
def volume_mounts_specification
[
{
name: 'configuration-volume',
mountPath: "/data/helm/#{command.name}/config"
}
]
end
end
end

View file

@ -34,5 +34,6 @@ FactoryBot.define do
factory :clusters_applications_ingress, class: Clusters::Applications::Ingress
factory :clusters_applications_prometheus, class: Clusters::Applications::Prometheus
factory :clusters_applications_runner, class: Clusters::Applications::Runner
end
end

View file

@ -38,11 +38,9 @@ describe('Applications', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).toBeDefined();
});
/* * /
it('renders a row for GitLab Runner', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-runner')).toBeDefined();
});
/* */
});
describe('Ingress application', () => {

View file

@ -0,0 +1,25 @@
require 'spec_helper'
describe Gitlab::Kubernetes::ConfigMap do
let(:kubeclient) { double('kubernetes client') }
let(:application) { create(:clusters_applications_prometheus) }
let(:config_map) { described_class.new(application.name, application.values) }
let(:namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
let(:metadata) do
{
name: "values-content-configuration-#{application.name}",
namespace: namespace,
labels: { name: "values-content-configuration-#{application.name}" }
}
end
describe '#generate' do
let(:resource) { ::Kubeclient::Resource.new(metadata: metadata, data: { values: application.values }) }
subject { config_map.generate }
it 'should build a Kubeclient Resource' do
is_expected.to eq(resource)
end
end
end

View file

@ -5,14 +5,21 @@ describe Gitlab::Kubernetes::Helm::Api do
let(:helm) { described_class.new(client) }
let(:gitlab_namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
let(:namespace) { Gitlab::Kubernetes::Namespace.new(gitlab_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: install_helm, chart: chart) }
let(:application) { create(:clusters_applications_prometheus) }
let(:command) do
Gitlab::Kubernetes::Helm::InstallCommand.new(
application.name,
chart: application.chart,
values: application.values
)
end
subject { helm }
before do
allow(Gitlab::Kubernetes::Namespace).to receive(:new).with(gitlab_namespace, client).and_return(namespace)
allow(client).to receive(:create_config_map)
end
describe '#initialize' do
@ -26,6 +33,7 @@ describe Gitlab::Kubernetes::Helm::Api do
describe '#install' do
before do
allow(client).to receive(:create_pod).and_return(nil)
allow(client).to receive(:create_config_map).and_return(nil)
allow(namespace).to receive(:ensure_exists!).once
end
@ -35,6 +43,16 @@ describe Gitlab::Kubernetes::Helm::Api do
subject.install(command)
end
context 'with a ConfigMap' do
let(:resource) { Gitlab::Kubernetes::ConfigMap.new(application.name, application.values).generate }
it 'creates a ConfigMap on kubeclient' do
expect(client).to receive(:create_config_map).with(resource).once
subject.install(command)
end
end
end
describe '#installation_status' do

View file

@ -0,0 +1,44 @@
require 'spec_helper'
describe Gitlab::Kubernetes::Helm::BaseCommand do
let(:application) { create(:clusters_applications_helm) }
let(:base_command) { described_class.new(application.name) }
describe '#generate_script' do
let(:helm_version) { Gitlab::Kubernetes::Helm::HELM_VERSION }
let(:command) do
<<~HEREDOC
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/
HEREDOC
end
subject { base_command.generate_script }
it 'should return a command that prepares the environment for helm-cli' do
expect(subject).to eq(command)
end
end
describe '#pod_resource' do
subject { base_command.pod_resource }
it 'should returns a kubeclient resoure with pod content for application' do
is_expected.to be_an_instance_of ::Kubeclient::Resource
end
end
describe '#config_map?' do
subject { base_command.config_map? }
it { is_expected.to be_falsy }
end
describe '#pod_name' do
subject { base_command.pod_name }
it { is_expected.to eq('install-helm') }
end
end

View file

@ -0,0 +1,24 @@
require 'spec_helper'
describe Gitlab::Kubernetes::Helm::InitCommand do
let(:application) { create(:clusters_applications_helm) }
let(:init_command) { described_class.new(application.name) }
describe '#generate_script' do
let(:command) do
<<~MSG.chomp
set -eo pipefail
apk add -U ca-certificates openssl >/dev/null
wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
mv /tmp/linux-amd64/helm /usr/bin/
helm init >/dev/null
MSG
end
subject { init_command.generate_script }
it 'should return the appropriate command' do
is_expected.to eq(command)
end
end
end

View file

@ -1,117 +1,56 @@
require 'rails_helper'
describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:prometheus) { create(:clusters_applications_prometheus) }
let(:application) { create(:clusters_applications_prometheus) }
let(:namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
describe "#initialize" do
context "With all the params" do
subject { described_class.new(prometheus.name, install_helm: true, chart: prometheus.chart, chart_values_file: prometheus.chart_values_file) }
it 'should assign all parameters' do
expect(subject.name).to eq(prometheus.name)
expect(subject.install_helm).to be_truthy
expect(subject.chart).to eq(prometheus.chart)
expect(subject.chart_values_file).to eq("#{Rails.root}/vendor/prometheus/values.yaml")
end
end
context 'when install_helm is not set' do
subject { described_class.new(prometheus.name, chart: prometheus.chart, chart_values_file: true) }
it 'should set install_helm as false' do
expect(subject.install_helm).to be_falsy
end
end
context 'when chart is not set' do
subject { described_class.new(prometheus.name, install_helm: true) }
it 'should set chart as nil' do
expect(subject.chart).to be_falsy
end
end
context 'when chart_values_file is not set' do
subject { described_class.new(prometheus.name, install_helm: true, chart: prometheus.chart) }
it 'should set chart_values_file as nil' do
expect(subject.chart_values_file).to be_falsy
end
end
let(:install_command) do
described_class.new(
application.name,
chart: application.chart,
values: application.values
)
end
describe "#generate_script" do
let(:install_command) { described_class.new(prometheus.name, install_helm: install_helm) }
let(:client) { double('kubernetes client') }
let(:namespace) { Gitlab::Kubernetes::Namespace.new(Gitlab::Kubernetes::Helm::NAMESPACE, client) }
subject { install_command.send(:generate_script, namespace.name) }
describe '#generate_script' do
let(:command) do
<<~MSG
set -eo pipefail
apk add -U ca-certificates openssl >/dev/null
wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
mv /tmp/linux-amd64/helm /usr/bin/
helm init --client-only >/dev/null
helm install #{application.chart} --name #{application.name} --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null
MSG
end
subject { install_command.generate_script }
it 'should return appropriate command' do
is_expected.to eq(command)
end
context 'with an application with a repository' do
let(:ci_runner) { create(:ci_runner) }
let(:application) { create(:clusters_applications_runner, runner: ci_runner) }
let(:install_command) do
described_class.new(
application.name,
chart: application.chart,
values: application.values,
repository: application.repository
)
end
context 'when install helm is true' do
let(:install_helm) { true }
let(:command) do
<<~MSG
set -eo pipefail
apk add -U ca-certificates openssl >/dev/null
wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
mv /tmp/linux-amd64/helm /usr/bin/
helm init >/dev/null
MSG
end
it 'should return appropriate command' do
is_expected.to eq(command)
end
end
context 'when install helm is false' do
let(:install_helm) { false }
let(:command) do
<<~MSG
set -eo pipefail
apk add -U ca-certificates openssl >/dev/null
wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
mv /tmp/linux-amd64/helm /usr/bin/
helm init --client-only >/dev/null
MSG
end
it 'should return appropriate command' do
is_expected.to eq(command)
end
end
context 'when chart is present' do
let(:install_command) { described_class.new(prometheus.name, chart: prometheus.chart) }
let(:command) do
<<~MSG.chomp
set -eo pipefail
apk add -U ca-certificates openssl >/dev/null
wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
mv /tmp/linux-amd64/helm /usr/bin/
helm init --client-only >/dev/null
helm install #{prometheus.chart} --name #{prometheus.name} --namespace #{namespace.name} >/dev/null
MSG
end
it 'should return appropriate command' do
is_expected.to eq(command)
end
end
context 'when chart values file is present' do
let(:install_command) { described_class.new(prometheus.name, chart: prometheus.chart, chart_values_file: prometheus.chart_values_file) }
let(:command) do
<<~MSG.chomp
set -eo pipefail
apk add -U ca-certificates openssl >/dev/null
wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
mv /tmp/linux-amd64/helm /usr/bin/
helm init --client-only >/dev/null
helm install #{prometheus.chart} --name #{prometheus.name} --namespace #{namespace.name} -f /data/helm/#{prometheus.name}/config/values.yaml >/dev/null
helm repo add #{application.name} #{application.repository}
helm install #{application.chart} --name #{application.name} --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null
MSG
end
@ -121,10 +60,27 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
end
end
describe "#pod_name" do
let(:install_command) { described_class.new(prometheus.name, install_helm: true, chart: prometheus.chart, chart_values_file: true) }
subject { install_command.send(:pod_name) }
describe '#config_map?' do
subject { install_command.config_map? }
it { is_expected.to eq('install-prometheus') }
it { is_expected.to be_truthy }
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: { values: application.values }) }
subject { install_command.config_map_resource }
it 'returns a KubeClient resource with config map content for the application' do
is_expected.to eq(resource)
end
end
end

View file

@ -5,13 +5,9 @@ describe Gitlab::Kubernetes::Helm::Pod do
let(:cluster) { create(:cluster) }
let(:app) { create(:clusters_applications_prometheus, cluster: cluster) }
let(:command) { app.install_command }
let(:client) { double('kubernetes client') }
let(:namespace) { Gitlab::Kubernetes::Namespace.new(Gitlab::Kubernetes::Helm::NAMESPACE, client) }
subject { described_class.new(command, namespace.name, client) }
let(:namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
before do
allow(client).to receive(:create_config_map).and_return(nil)
end
subject { described_class.new(command, namespace) }
shared_examples 'helm pod' do
it 'should generate a Kubeclient::Resource' do
@ -47,7 +43,7 @@ describe Gitlab::Kubernetes::Helm::Pod do
end
end
context 'with a configuration file' do
context 'with a install command' do
it_behaves_like 'helm pod'
it 'should include volumes for the container' do
@ -62,14 +58,14 @@ describe Gitlab::Kubernetes::Helm::Pod do
end
it 'should mount configMap specification in the volume' do
spec = subject.generate.spec
expect(spec.volumes.first.configMap['name']).to eq("values-content-configuration-#{app.name}")
expect(spec.volumes.first.configMap['items'].first['key']).to eq('values')
expect(spec.volumes.first.configMap['items'].first['path']).to eq('values.yaml')
volume = subject.generate.spec.volumes.first
expect(volume.configMap['name']).to eq("values-content-configuration-#{app.name}")
expect(volume.configMap['items'].first['key']).to eq('values')
expect(volume.configMap['items'].first['path']).to eq('values.yaml')
end
end
context 'without a configuration file' do
context 'with a init command' do
let(:app) { create(:clusters_applications_helm, cluster: cluster) }
it_behaves_like 'helm pod'

View file

@ -1,102 +1,17 @@
require 'rails_helper'
describe Clusters::Applications::Helm do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to validate_presence_of(:cluster) }
describe '#name' do
it 'is .application_name' do
expect(subject.name).to eq(described_class.application_name)
end
it 'is recorded in Clusters::Cluster::APPLICATIONS' do
expect(Clusters::Cluster::APPLICATIONS[subject.name]).to eq(described_class)
end
end
describe '#version' do
it 'defaults to Gitlab::Kubernetes::Helm::HELM_VERSION' do
expect(subject.version).to eq(Gitlab::Kubernetes::Helm::HELM_VERSION)
end
end
describe '#status' do
let(:cluster) { create(:cluster) }
subject { described_class.new(cluster: cluster) }
it 'defaults to :not_installable' do
expect(subject.status_name).to be(:not_installable)
end
context 'when platform kubernetes is defined' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
it 'defaults to :installable' do
expect(subject.status_name).to be(:installable)
end
end
end
include_examples 'cluster application core specs', :clusters_applications_helm
describe '#install_command' do
it 'has all the needed information' do
expect(subject.install_command).to have_attributes(name: subject.name, install_helm: true)
end
end
let(:helm) { create(:clusters_applications_helm) }
describe 'status state machine' do
describe '#make_installing' do
subject { create(:clusters_applications_helm, :scheduled) }
subject { helm.install_command }
it 'is installing' do
subject.make_installing!
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InitCommand) }
expect(subject).to be_installing
end
end
describe '#make_installed' do
subject { create(:clusters_applications_helm, :installing) }
it 'is installed' do
subject.make_installed
expect(subject).to be_installed
end
end
describe '#make_errored' do
subject { create(:clusters_applications_helm, :installing) }
let(:reason) { 'some errors' }
it 'is errored' do
subject.make_errored(reason)
expect(subject).to be_errored
expect(subject.status_reason).to eq(reason)
end
end
describe '#make_scheduled' do
subject { create(:clusters_applications_helm, :installable) }
it 'is scheduled' do
subject.make_scheduled
expect(subject).to be_scheduled
end
describe 'when was errored' do
subject { create(:clusters_applications_helm, :errored) }
it 'clears #status_reason' do
expect(subject.status_reason).not_to be_nil
subject.make_scheduled!
expect(subject.status_reason).to be_nil
end
end
it 'should be initialized with 1 arguments' do
expect(subject.name).to eq('helm')
end
end
end

View file

@ -1,16 +1,16 @@
require 'rails_helper'
describe Clusters::Applications::Ingress do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to validate_presence_of(:cluster) }
let(:ingress) { create(:clusters_applications_ingress) }
include_examples 'cluster application core specs', :clusters_applications_ingress
include_examples 'cluster application status specs', :cluster_application_ingress
before do
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
end
include_examples 'cluster application specs', described_class
describe '#make_installed!' do
before do
application.make_installed!
@ -52,4 +52,27 @@ describe Clusters::Applications::Ingress do
end
end
end
describe '#install_command' do
subject { ingress.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
it 'should be initialized with ingress arguments' do
expect(subject.name).to eq('ingress')
expect(subject.chart).to eq('stable/nginx-ingress')
expect(subject.values).to eq(ingress.values)
end
end
describe '#values' do
subject { ingress.values }
it 'should include ingress valid keys' do
is_expected.to include('image')
is_expected.to include('repository')
is_expected.to include('stats')
is_expected.to include('podAnnotations')
end
end
end

View file

@ -1,10 +1,8 @@
require 'rails_helper'
describe Clusters::Applications::Prometheus do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to validate_presence_of(:cluster) }
include_examples 'cluster application specs', described_class
include_examples 'cluster application core specs', :clusters_applications_prometheus
include_examples 'cluster application status specs', :cluster_application_prometheus
describe 'transition to installed' do
let(:project) { create(:project) }
@ -24,14 +22,6 @@ describe Clusters::Applications::Prometheus do
end
end
describe "#chart_values_file" do
subject { create(:clusters_applications_prometheus).chart_values_file }
it 'should return chart values file path' do
expect(subject).to eq("#{Rails.root}/vendor/prometheus/values.yaml")
end
end
describe '#proxy_client' do
context 'cluster is nil' do
it 'returns nil' do
@ -85,4 +75,33 @@ describe Clusters::Applications::Prometheus do
end
end
end
describe '#install_command' do
let(:kubeclient) { double('kubernetes client') }
let(:prometheus) { create(:clusters_applications_prometheus) }
subject { prometheus.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
it 'should be initialized with 3 arguments' do
expect(subject.name).to eq('prometheus')
expect(subject.chart).to eq('stable/prometheus')
expect(subject.values).to eq(prometheus.values)
end
end
describe '#values' do
let(:prometheus) { create(:clusters_applications_prometheus) }
subject { prometheus.values }
it 'should include prometheus valid values' do
is_expected.to include('alertmanager')
is_expected.to include('kubeStateMetrics')
is_expected.to include('nodeExporter')
is_expected.to include('pushgateway')
is_expected.to include('serverFiles')
end
end
end

View file

@ -0,0 +1,69 @@
require 'rails_helper'
describe Clusters::Applications::Runner do
let(:ci_runner) { create(:ci_runner) }
include_examples 'cluster application core specs', :clusters_applications_runner
include_examples 'cluster application status specs', :cluster_application_runner
it { is_expected.to belong_to(:runner) }
describe '#install_command' do
let(:kubeclient) { double('kubernetes client') }
let(:gitlab_runner) { create(:clusters_applications_runner, runner: ci_runner) }
subject { gitlab_runner.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
it 'should be initialized with 4 arguments' do
expect(subject.name).to eq('runner')
expect(subject.chart).to eq('runner/gitlab-runner')
expect(subject.repository).to eq('https://charts.gitlab.io')
expect(subject.values).to eq(gitlab_runner.values)
end
end
describe '#values' do
let(:gitlab_runner) { create(:clusters_applications_runner, runner: ci_runner) }
subject { gitlab_runner.values }
it 'should include runner valid values' do
is_expected.to include('concurrent')
is_expected.to include('checkInterval')
is_expected.to include('rbac')
is_expected.to include('runners')
is_expected.to include('resources')
is_expected.to include("runnerToken: #{ci_runner.token}")
is_expected.to include("gitlabUrl: #{Gitlab::Routing.url_helpers.root_url}")
end
context 'without a runner' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster) }
let(:gitlab_runner) { create(:clusters_applications_runner, cluster: cluster) }
before do
cluster.projects << project
end
it 'creates a runner' do
expect do
subject
end.to change { Ci::Runner.count }.by(1)
end
it 'uses the new runner token' do
expect(subject).to include("runnerToken: #{gitlab_runner.reload.runner.token}")
end
it 'assigns the new runner to runner' do
subject
gitlab_runner.reload
expect(gitlab_runner.runner).not_to be_nil
end
end
end
end

View file

@ -8,6 +8,7 @@ describe Clusters::Cluster do
it { is_expected.to have_one(:application_helm) }
it { is_expected.to have_one(:application_ingress) }
it { is_expected.to have_one(:application_prometheus) }
it { is_expected.to have_one(:application_runner) }
it { is_expected.to delegate_method(:status).to(:provider) }
it { is_expected.to delegate_method(:status_reason).to(:provider) }
it { is_expected.to delegate_method(:status_name).to(:provider) }
@ -196,9 +197,10 @@ describe Clusters::Cluster do
let!(:helm) { create(:clusters_applications_helm, cluster: cluster) }
let!(:ingress) { create(:clusters_applications_ingress, cluster: cluster) }
let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) }
let!(:runner) { create(:clusters_applications_runner, cluster: cluster) }
it 'returns a list of created applications' do
is_expected.to contain_exactly(helm, ingress, prometheus)
is_expected.to contain_exactly(helm, ingress, prometheus, runner)
end
end
end

View file

@ -1,105 +0,0 @@
shared_examples 'cluster application specs' do
let(:factory_name) { described_class.to_s.downcase.gsub("::", "_") }
describe '#name' do
it 'is .application_name' do
expect(subject.name).to eq(described_class.application_name)
end
it 'is recorded in Clusters::Cluster::APPLICATIONS' do
expect(Clusters::Cluster::APPLICATIONS[subject.name]).to eq(described_class)
end
end
describe '#status' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
subject { described_class.new(cluster: cluster) }
it 'defaults to :not_installable' do
expect(subject.status_name).to be(:not_installable)
end
context 'when application helm is scheduled' do
before do
create(factory_name, :scheduled, cluster: cluster)
end
it 'defaults to :not_installable' do
expect(subject.status_name).to be(:not_installable)
end
end
context 'when application helm is installed' do
before do
create(:clusters_applications_helm, :installed, cluster: cluster)
end
it 'defaults to :installable' do
expect(subject.status_name).to be(:installable)
end
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: false)
end
end
describe 'status state machine' do
describe '#make_installing' do
subject { create(factory_name, :scheduled) }
it 'is installing' do
subject.make_installing!
expect(subject).to be_installing
end
end
describe '#make_installed' do
subject { create(factory_name, :installing) }
it 'is installed' do
subject.make_installed
expect(subject).to be_installed
end
end
describe '#make_errored' do
subject { create(factory_name, :installing) }
let(:reason) { 'some errors' }
it 'is errored' do
subject.make_errored(reason)
expect(subject).to be_errored
expect(subject.status_reason).to eq(reason)
end
end
describe '#make_scheduled' do
subject { create(factory_name, :installable) }
it 'is scheduled' do
subject.make_scheduled
expect(subject).to be_scheduled
end
describe 'when was errored' do
subject { create(factory_name, :errored) }
it 'clears #status_reason' do
expect(subject.status_reason).not_to be_nil
subject.make_scheduled!
expect(subject.status_reason).to be_nil
end
end
end
end
end

View file

@ -0,0 +1,70 @@
shared_examples 'cluster application core specs' do |application_name|
it { is_expected.to belong_to(:cluster) }
it { is_expected.to validate_presence_of(:cluster) }
describe '#name' do
it 'is .application_name' do
expect(subject.name).to eq(described_class.application_name)
end
it 'is recorded in Clusters::Cluster::APPLICATIONS' do
expect(Clusters::Cluster::APPLICATIONS[subject.name]).to eq(described_class)
end
end
describe 'status state machine' do
describe '#make_installing' do
subject { create(application_name, :scheduled) }
it 'is installing' do
subject.make_installing!
expect(subject).to be_installing
end
end
describe '#make_installed' do
subject { create(application_name, :installing) }
it 'is installed' do
subject.make_installed
expect(subject).to be_installed
end
end
describe '#make_errored' do
subject { create(application_name, :installing) }
let(:reason) { 'some errors' }
it 'is errored' do
subject.make_errored(reason)
expect(subject).to be_errored
expect(subject.status_reason).to eq(reason)
end
end
describe '#make_scheduled' do
subject { create(application_name, :installable) }
it 'is scheduled' do
subject.make_scheduled
expect(subject).to be_scheduled
end
describe 'when was errored' do
subject { create(application_name, :errored) }
it 'clears #status_reason' do
expect(subject.status_reason).not_to be_nil
subject.make_scheduled!
expect(subject.status_reason).to be_nil
end
end
end
end
end

View file

@ -0,0 +1,31 @@
shared_examples 'cluster application status specs' do |application_name|
describe '#status' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
subject { described_class.new(cluster: cluster) }
it 'sets a default status' do
expect(subject.status_name).to be(:not_installable)
end
context 'when application helm is scheduled' do
before do
create(:clusters_applications_helm, :scheduled, cluster: cluster)
end
it 'defaults to :not_installable' do
expect(subject.status_name).to be(:not_installable)
end
end
context 'when application is scheduled' do
before do
create(:clusters_applications_helm, :installed, cluster: cluster)
end
it 'sets a default status' do
expect(subject.status_name).to be(:installable)
end
end
end
end

25
vendor/runner/values.yaml vendored Normal file
View file

@ -0,0 +1,25 @@
## Configure the maximum number of concurrent jobs
## - Documentation: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
## - Default value: 10
## - Currently don't support auto-scaling.
concurrent: 4
## Defines in seconds how often to check GitLab for a new builds
## - Documentation: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
## - Default value: 3
checkInterval: 3
## For RBAC support
rbac:
create: false
clusterWideAccess: false
## Configuration for the Pods that that the runner launches for each new job
##
runners:
image: ubuntu:16.04
privileged: false
builds: {}
services: {}
helpers: {}
resources: {}