summaryrefslogtreecommitdiff
path: root/qa
diff options
context:
space:
mode:
authorDylan Griffith <dyl.griffith@gmail.com>2018-05-18 17:33:57 +0200
committerDylan Griffith <dyl.griffith@gmail.com>2018-06-04 17:20:48 +0200
commit2ae48f1ca910031a77550e1f85492d9786c3069e (patch)
treecc9c70d691e9f0158fb1e33759e5d3eff63ba4bd /qa
parent8d2b4e02e50f4830f27ad91a0afa1676759fc426 (diff)
downloadgitlab-ce-2ae48f1ca910031a77550e1f85492d9786c3069e.tar.gz
Add QA integration test for full Auto DevOps flow
Diffstat (limited to 'qa')
-rw-r--r--qa/qa.rb12
-rw-r--r--qa/qa/factory/resource/kubernetes_cluster.rb55
-rw-r--r--qa/qa/fixtures/auto_devops_rack/Gemfile3
-rw-r--r--qa/qa/fixtures/auto_devops_rack/Gemfile.lock15
-rw-r--r--qa/qa/fixtures/auto_devops_rack/Rakefile7
-rw-r--r--qa/qa/fixtures/auto_devops_rack/config.ru1
-rw-r--r--qa/qa/page/menu/side.rb18
-rw-r--r--qa/qa/page/project/operations/kubernetes/add.rb19
-rw-r--r--qa/qa/page/project/operations/kubernetes/add_existing.rb39
-rw-r--r--qa/qa/page/project/operations/kubernetes/index.rb19
-rw-r--r--qa/qa/page/project/operations/kubernetes/show.rb39
-rw-r--r--qa/qa/page/project/pipeline/show.rb4
-rw-r--r--qa/qa/page/project/settings/ci_cd.rb15
-rw-r--r--qa/qa/scenario/test/integration/kubernetes.rb11
-rw-r--r--qa/qa/service/kubernetes_cluster.rb65
-rw-r--r--qa/qa/service/runner.rb1
-rw-r--r--qa/qa/specs/features/project/auto_devops_spec.rb69
17 files changed, 389 insertions, 3 deletions
diff --git a/qa/qa.rb b/qa/qa.rb
index 40e12c8b336..7f2da05dd63 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -41,6 +41,7 @@ module QA
autoload :SecretVariable, 'qa/factory/resource/secret_variable'
autoload :Runner, 'qa/factory/resource/runner'
autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token'
+ autoload :KubernetesCluster, 'qa/factory/resource/kubernetes_cluster'
end
module Repository
@@ -72,6 +73,7 @@ module QA
module Integration
autoload :LDAP, 'qa/scenario/test/integration/ldap'
+ autoload :Kubernetes, 'qa/scenario/test/integration/kubernetes'
autoload :Mattermost, 'qa/scenario/test/integration/mattermost'
end
@@ -150,6 +152,15 @@ module QA
autoload :Show, 'qa/page/project/issue/show'
autoload :Index, 'qa/page/project/issue/index'
end
+
+ module Operations
+ module Kubernetes
+ autoload :Index, 'qa/page/project/operations/kubernetes/index'
+ autoload :Add, 'qa/page/project/operations/kubernetes/add'
+ autoload :AddExisting, 'qa/page/project/operations/kubernetes/add_existing'
+ autoload :Show, 'qa/page/project/operations/kubernetes/show'
+ end
+ end
end
module Profile
@@ -195,6 +206,7 @@ module QA
#
module Service
autoload :Shellout, 'qa/service/shellout'
+ autoload :KubernetesCluster, 'qa/service/kubernetes_cluster'
autoload :Omnibus, 'qa/service/omnibus'
autoload :Runner, 'qa/service/runner'
end
diff --git a/qa/qa/factory/resource/kubernetes_cluster.rb b/qa/qa/factory/resource/kubernetes_cluster.rb
new file mode 100644
index 00000000000..f32cf985e9d
--- /dev/null
+++ b/qa/qa/factory/resource/kubernetes_cluster.rb
@@ -0,0 +1,55 @@
+require 'securerandom'
+
+module QA
+ module Factory
+ module Resource
+ class KubernetesCluster < Factory::Base
+ attr_writer :project, :cluster,
+ :install_helm_tiller, :install_ingress, :install_prometheus, :install_runner
+
+ product :ingress_ip do
+ Page::Project::Operations::Kubernetes::Show.perform do |page|
+ page.ingress_ip
+ end
+ end
+
+ def fabricate!
+ @project.visit!
+
+ Page::Menu::Side.act { click_operations_kubernetes }
+
+ Page::Project::Operations::Kubernetes::Index.perform do |page|
+ page.add_kubernetes_cluster
+ end
+
+ Page::Project::Operations::Kubernetes::Add.perform do |page|
+ page.add_existing_cluster
+ end
+
+ Page::Project::Operations::Kubernetes::AddExisting.perform do |page|
+ page.set_cluster_name(@cluster.cluster_name)
+ page.set_api_url(@cluster.api_url)
+ page.set_ca_certificate(@cluster.ca_certificate)
+ page.set_token(@cluster.token)
+ page.add_cluster!
+ end
+
+ if @install_helm_tiller
+ Page::Project::Operations::Kubernetes::Show.perform do |page|
+ # Helm must be installed before everything else
+ page.install!(:helm)
+ page.await_installed(:helm)
+
+ page.install!(:ingress) if @install_ingress
+ page.await_installed(:ingress) if @install_ingress
+ page.install!(:prometheus) if @install_prometheus
+ page.await_installed(:prometheus) if @install_prometheus
+ page.install!(:runner) if @install_runner
+ page.await_installed(:runner) if @install_runner
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/fixtures/auto_devops_rack/Gemfile b/qa/qa/fixtures/auto_devops_rack/Gemfile
new file mode 100644
index 00000000000..fc7514242d0
--- /dev/null
+++ b/qa/qa/fixtures/auto_devops_rack/Gemfile
@@ -0,0 +1,3 @@
+source 'https://rubygems.org'
+gem 'rack'
+gem 'rake'
diff --git a/qa/qa/fixtures/auto_devops_rack/Gemfile.lock b/qa/qa/fixtures/auto_devops_rack/Gemfile.lock
new file mode 100644
index 00000000000..09cf72c48ac
--- /dev/null
+++ b/qa/qa/fixtures/auto_devops_rack/Gemfile.lock
@@ -0,0 +1,15 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ rack (2.0.4)
+ rake (12.3.0)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ rack
+ rake
+
+BUNDLED WITH
+ 1.16.1
diff --git a/qa/qa/fixtures/auto_devops_rack/Rakefile b/qa/qa/fixtures/auto_devops_rack/Rakefile
new file mode 100644
index 00000000000..c865c9aaac1
--- /dev/null
+++ b/qa/qa/fixtures/auto_devops_rack/Rakefile
@@ -0,0 +1,7 @@
+require 'rake/testtask'
+
+task default: %w[test]
+
+task :test do
+ puts "ok"
+end
diff --git a/qa/qa/fixtures/auto_devops_rack/config.ru b/qa/qa/fixtures/auto_devops_rack/config.ru
new file mode 100644
index 00000000000..bde8e15488a
--- /dev/null
+++ b/qa/qa/fixtures/auto_devops_rack/config.ru
@@ -0,0 +1 @@
+run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, StringIO.new("Hello World!\n")] }
diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb
index 7e028add2ef..3630b7e8568 100644
--- a/qa/qa/page/menu/side.rb
+++ b/qa/qa/page/menu/side.rb
@@ -7,9 +7,11 @@ module QA
element :settings_link, 'link_to edit_project_path'
element :repository_link, "title: 'Repository'"
element :pipelines_settings_link, "title: 'CI / CD'"
+ element :operations_kubernetes_link, "title: _('Kubernetes')"
element :issues_link, /link_to.*shortcuts-issues/
element :issues_link_text, "Issues"
element :top_level_items, '.sidebar-top-level-items'
+ element :operations_section, "class: 'shortcuts-operations'"
element :activity_link, "title: 'Activity'"
end
@@ -33,6 +35,14 @@ module QA
end
end
+ def click_operations_kubernetes
+ hover_operations do
+ within_submenu do
+ click_link('Kubernetes')
+ end
+ end
+ end
+
def click_ci_cd_pipelines
within_sidebar do
click_link('CI / CD')
@@ -61,6 +71,14 @@ module QA
end
end
+ def hover_operations
+ within_sidebar do
+ find('.shortcuts-operations').hover
+
+ yield
+ end
+ end
+
def within_sidebar
page.within('.sidebar-top-level-items') do
yield
diff --git a/qa/qa/page/project/operations/kubernetes/add.rb b/qa/qa/page/project/operations/kubernetes/add.rb
new file mode 100644
index 00000000000..9b3c482fa6c
--- /dev/null
+++ b/qa/qa/page/project/operations/kubernetes/add.rb
@@ -0,0 +1,19 @@
+module QA
+ module Page
+ module Project
+ module Operations
+ module Kubernetes
+ class Add < Page::Base
+ view 'app/views/projects/clusters/new.html.haml' do
+ element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add an existing Kubernetes cluster')"
+ end
+
+ def add_existing_cluster
+ click_on 'Add an existing Kubernetes cluster'
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/operations/kubernetes/add_existing.rb b/qa/qa/page/project/operations/kubernetes/add_existing.rb
new file mode 100644
index 00000000000..eef82b5f329
--- /dev/null
+++ b/qa/qa/page/project/operations/kubernetes/add_existing.rb
@@ -0,0 +1,39 @@
+module QA
+ module Page
+ module Project
+ module Operations
+ module Kubernetes
+ class AddExisting < Page::Base
+ view 'app/views/projects/clusters/user/_form.html.haml' do
+ element :cluster_name, 'text_field :name'
+ element :api_url, 'text_field :api_url'
+ element :ca_certificate, 'text_area :ca_cert'
+ element :token, 'text_field :token'
+ element :add_cluster_button, "submit s_('ClusterIntegration|Add Kubernetes cluster')"
+ end
+
+ def set_cluster_name(name)
+ fill_in 'cluster_name', with: name
+ end
+
+ def set_api_url(api_url)
+ fill_in 'cluster_platform_kubernetes_attributes_api_url', with: api_url
+ end
+
+ def set_ca_certificate(ca_certificate)
+ fill_in 'cluster_platform_kubernetes_attributes_ca_cert', with: ca_certificate
+ end
+
+ def set_token(token)
+ fill_in 'cluster_platform_kubernetes_attributes_token', with: token
+ end
+
+ def add_cluster!
+ click_on 'Add Kubernetes cluster'
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/operations/kubernetes/index.rb b/qa/qa/page/project/operations/kubernetes/index.rb
new file mode 100644
index 00000000000..7261b5645da
--- /dev/null
+++ b/qa/qa/page/project/operations/kubernetes/index.rb
@@ -0,0 +1,19 @@
+module QA
+ module Page
+ module Project
+ module Operations
+ module Kubernetes
+ class Index < Page::Base
+ view 'app/views/projects/clusters/_empty_state.html.haml' do
+ element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add Kubernetes cluster')"
+ end
+
+ def add_kubernetes_cluster
+ click_on 'Add Kubernetes cluster'
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb
new file mode 100644
index 00000000000..4923304133e
--- /dev/null
+++ b/qa/qa/page/project/operations/kubernetes/show.rb
@@ -0,0 +1,39 @@
+module QA
+ module Page
+ module Project
+ module Operations
+ module Kubernetes
+ class Show < Page::Base
+ view 'app/assets/javascripts/clusters/components/application_row.vue' do
+ element :application_row, 'js-cluster-application-row-${this.id}'
+ element :install_button, "s__('ClusterIntegration|Install')"
+ element :installed_button, "s__('ClusterIntegration|Installed')"
+ end
+
+ view 'app/assets/javascripts/clusters/components/applications.vue' do
+ element :ingress_ip_address, 'id="ingress-ip-address"'
+ end
+
+ def install!(application_name)
+ within(".js-cluster-application-row-#{application_name}") do
+ click_on 'Install'
+ end
+ end
+
+ def await_installed(application_name)
+ within(".js-cluster-application-row-#{application_name}") do
+ page.has_text?('Installed', wait: 300)
+ end
+ end
+
+ def ingress_ip
+ # We need to wait longer since it can take some time before the
+ # ip address is assigned for the ingress controller
+ page.find('#ingress-ip-address', wait: 500).value
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index ec61c47b3bb..de849b3eee8 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -24,10 +24,10 @@ module QA::Page
end
end
- def has_build?(name, status: :success)
+ def has_build?(name, status: :success, wait:)
within('.pipeline-graph') do
within('.ci-job-component', text: name) do
- has_selector?(".ci-status-icon-#{status}")
+ has_selector?(".ci-status-icon-#{status}", wait: wait)
end
end
end
diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb
index 145c3d3ddfa..dfb71e0a9f0 100644
--- a/qa/qa/page/project/settings/ci_cd.rb
+++ b/qa/qa/page/project/settings/ci_cd.rb
@@ -8,6 +8,13 @@ module QA # rubocop:disable Naming/FileName
view 'app/views/projects/settings/ci_cd/show.html.haml' do
element :runners_settings, 'Runners settings'
element :secret_variables, 'Variables'
+ element :auto_devops_section, 'Auto DevOps'
+ end
+
+ view 'app/views/projects/settings/ci_cd/_autodevops_form.html.haml' do
+ element :enable_auto_devops_button, 'Enable Auto DevOps'
+ element :domain_input, 'Domain'
+ element :save_changes_button, "submit 'Save changes'"
end
def expand_runners_settings(&block)
@@ -21,6 +28,14 @@ module QA # rubocop:disable Naming/FileName
Settings::SecretVariables.perform(&block)
end
end
+
+ def enable_auto_devops_with_domain(domain)
+ expand_section('Auto DevOps') do
+ choose 'Enable Auto DevOps'
+ fill_in 'Domain', with: domain
+ click_on 'Save changes'
+ end
+ end
end
end
end
diff --git a/qa/qa/scenario/test/integration/kubernetes.rb b/qa/qa/scenario/test/integration/kubernetes.rb
new file mode 100644
index 00000000000..7479073e979
--- /dev/null
+++ b/qa/qa/scenario/test/integration/kubernetes.rb
@@ -0,0 +1,11 @@
+module QA
+ module Scenario
+ module Test
+ module Integration
+ class Kubernetes < Test::Instance
+ tags :kubernetes
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/service/kubernetes_cluster.rb b/qa/qa/service/kubernetes_cluster.rb
new file mode 100644
index 00000000000..01310900f7d
--- /dev/null
+++ b/qa/qa/service/kubernetes_cluster.rb
@@ -0,0 +1,65 @@
+require 'securerandom'
+require 'mkmf'
+
+module QA
+ module Service
+ class KubernetesCluster
+ include Service::Shellout
+
+ attr_reader :api_url, :ca_certificate, :token
+
+ def cluster_name
+ @cluster_name ||= "qa-cluster-#{SecureRandom.hex(4)}-#{Time.now.utc.strftime("%Y%m%d%H%M%S")}"
+ end
+
+ def create!
+ validate_dependencies
+ login_if_not_already_logged_in
+
+ shell <<~CMD.tr("\n", ' ')
+ gcloud container clusters
+ create #{cluster_name}
+ --enable-legacy-authorization
+ && gcloud container clusters
+ get-credentials #{cluster_name}
+ CMD
+
+ @api_url = `kubectl config view --minify -o jsonpath='{.clusters[].cluster.server}'`
+ @ca_certificate = Base64.decode64(`kubectl get secrets -o jsonpath="{.items[0].data['ca\\.crt']}"`)
+ @token = Base64.decode64(`kubectl get secrets -o jsonpath='{.items[0].data.token}'`)
+ self
+ end
+
+ def remove!
+ shell("gcloud container clusters delete #{cluster_name} --quiet --async")
+ end
+
+ private
+
+ def validate_dependencies
+ find_executable('gcloud') || raise("You must first install `gcloud` executable to run these tests.")
+ find_executable('kubectl') || raise("You must first install `kubectl` executable to run these tests.")
+ end
+
+ def login_if_not_already_logged_in
+ account = `gcloud auth list --filter=status:ACTIVE --format="value(account)"`
+ if account.empty?
+ attempt_login_with_env_vars
+ else
+ puts "gcloud account found. Using: #{account} for creating K8s cluster."
+ end
+ end
+
+ def attempt_login_with_env_vars
+ puts "No gcloud account. Attempting to login from env vars GCLOUD_ACCOUNT_EMAIL and GCLOUD_ACCOUNT_KEY."
+ gcloud_account_key = Tempfile.new('gcloud-account-key')
+ gcloud_account_key.write(ENV.fetch("GCLOUD_ACCOUNT_KEY"))
+ gcloud_account_key.close
+ gcloud_account_email = ENV.fetch("GCLOUD_ACCOUNT_EMAIL")
+ shell("gcloud auth activate-service-account #{gcloud_account_email} --key-file #{gcloud_account_key.path}")
+ ensure
+ gcloud_account_key && gcloud_account_key.unlink
+ end
+ end
+ end
+end
diff --git a/qa/qa/service/runner.rb b/qa/qa/service/runner.rb
index c0352e0467a..9417c707105 100644
--- a/qa/qa/service/runner.rb
+++ b/qa/qa/service/runner.rb
@@ -3,7 +3,6 @@ require 'securerandom'
module QA
module Service
class Runner
- include Scenario::Actable
include Service::Shellout
attr_accessor :token, :address, :tags, :image
diff --git a/qa/qa/specs/features/project/auto_devops_spec.rb b/qa/qa/specs/features/project/auto_devops_spec.rb
new file mode 100644
index 00000000000..360a6f3552d
--- /dev/null
+++ b/qa/qa/specs/features/project/auto_devops_spec.rb
@@ -0,0 +1,69 @@
+module QA
+ feature 'Auto Devops', :kubernetes do
+ let(:executor) { "qa-runner-#{Time.now.to_i}" }
+
+ after do
+ @cluster&.remove!
+ end
+
+ scenario 'user creates a new project and runs auto devops' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+
+ project = Factory::Resource::Project.fabricate! do |p|
+ p.name = 'project-with-autodevops'
+ p.description = 'Project with Auto Devops'
+ end
+
+ # Create Auto Devops compatible repo
+ project.visit!
+ Git::Repository.perform do |repository|
+ repository.uri = Page::Project::Show.act do
+ choose_repository_clone_http
+ repository_location.uri
+ end
+
+ repository.use_default_credentials
+ repository.clone
+ repository.configure_identity('GitLab QA', 'root@gitlab.com')
+
+ repository.checkout_new_branch('master')
+ repository.add_file('config.ru', File.read(File.join(__dir__, "../../../fixtures/auto_devops_rack/config.ru")))
+ repository.add_file('Gemfile', File.read(File.join(__dir__, "../../../fixtures/auto_devops_rack/Gemfile")))
+ repository.add_file('Gemfile.lock', File.read(File.join(__dir__, "../../../fixtures/auto_devops_rack/Gemfile.lock")))
+ repository.add_file('Rakefile', File.read(File.join(__dir__, "../../../fixtures/auto_devops_rack/Rakefile")))
+ repository.commit('Create auto devops repo')
+ repository.push_changes("master:master")
+ end
+
+ # Create and connect K8s cluster
+ @cluster = Service::KubernetesCluster.new.create!
+ kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |c|
+ c.project = project
+ c.cluster = @cluster
+ c.install_helm_tiller = true
+ c.install_ingress = true
+ c.install_prometheus = true
+ c.install_runner = true
+ end
+
+ project.visit!
+ Page::Menu::Side.act { click_ci_cd_settings }
+ Page::Project::Settings::CICD.perform do |p|
+ p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io")
+ end
+
+ project.visit!
+ Page::Menu::Side.act { click_ci_cd_pipelines }
+ Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
+
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ expect(pipeline).to have_build('build', status: :success, wait: 600)
+ expect(pipeline).to have_build('test', status: :success, wait: 600)
+ expect(pipeline).to have_build('sast', status: :success, wait: 600)
+ expect(pipeline).to have_build('production', status: :success, wait: 600)
+ expect(pipeline).to have_build('performance', status: :success, wait: 600)
+ end
+ end
+ end
+end