Commit 8ff73614 authored by Mayra Cabrera's avatar Mayra Cabrera

Moves domain setting to Cluster setting

Changes domain field to be on the Cluster page show, removing it from
Auto DevOps setting. Also injects the new environment variable
KUBE_INGRESS_BASE_DOMAIN into kubernetes#predefined_variables.

Migration to move the information from ProjectAutoDevops#domain
to Clusters::Cluster#domain. As well as necessary modifications to qa
selectors

Closes #52363
parent 280b6f6f
......@@ -127,6 +127,7 @@ class Clusters::ClustersController < Clusters::BaseController
params.require(:cluster).permit(
:enabled,
:environment_scope,
:domain,
platform_kubernetes_attributes: [
:namespace
]
......@@ -136,6 +137,7 @@ class Clusters::ClustersController < Clusters::BaseController
:enabled,
:name,
:environment_scope,
:domain,
platform_kubernetes_attributes: [
:api_url,
:token,
......
......@@ -26,17 +26,6 @@ module AutoDevopsHelper
end
end
# rubocop: disable CodeReuse/ActiveRecord
def cluster_ingress_ip(project)
project
.cluster_ingresses
.where("external_ip is not null")
.limit(1)
.pluck(:external_ip)
.first
end
# rubocop: enable CodeReuse/ActiveRecord
private
def missing_auto_devops_domain?(project)
......
......@@ -49,7 +49,7 @@ module Clusters
validates :name, cluster_name: true
validates :cluster_type, presence: true
validates :domain, allow_nil: true, hostname: { allow_numeric_hostname: true, require_valid_tld: true }
validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true, require_valid_tld: true }
validate :restrict_modification, on: :update
validate :no_groups, unless: :group_type?
......@@ -65,6 +65,7 @@ module Clusters
delegate :available?, to: :application_ingress, prefix: true, allow_nil: true
delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true
delegate :available?, to: :application_knative, prefix: true, allow_nil: true
delegate :external_ip, to: :application_ingress, prefix: true, allow_nil: true
enum cluster_type: {
instance_type: 1,
......@@ -193,8 +194,24 @@ module Clusters
project_type?
end
def has_domain?
domain.present? || instance_domain.present?
end
def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless has_domain?
variables.append(key: 'KUBE_INGRESS_BASE_DOMAIN', value: domain.presence || instance_domain)
end
end
private
def instance_domain
Gitlab::CurrentSettings.auto_devops_domain
end
def restrict_modification
if provider&.on_creation?
errors.add(:base, "cannot modify during creation")
......
......@@ -98,6 +98,8 @@ module Clusters
.append(key: 'KUBE_NAMESPACE', value: actual_namespace)
.append(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true)
end
variables.concat(cluster.predefined_variables)
end
end
......
......@@ -24,6 +24,9 @@ class ProjectAutoDevops < ActiveRecord::Base
domain.present? || instance_domain.present?
end
# From 11.8, AUTO_DEVOPS_DOMAIN has been replaced by KUBE_INGRESS_BASE_DOMAIN.
# See Clusters::Cluster#predefined_variables and https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580
# for more info. Support for AUTO_DEVOPS_DOMAIN support will be dropped on 12.0.
def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
if has_domain?
......
......@@ -20,12 +20,28 @@
.form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.")
- else
= text_field_tag :environment_scope, '*', class: 'col-md-6 form-control disabled', placeholder: s_('ClusterIntegration|Environment scope'), disabled: true
- environment_scope_url = 'https://docs.gitlab.com/ee/user/project/clusters/#setting-the-environment-scope-premium'
- environment_scope_url = 'https://docs.gitlab.com/ee/user/project/clusters/#base-domain'
- environment_scope_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: environment_scope_url }
.form-text.text-muted
%code *
= s_("ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}").html_safe % { environment_scope_start: environment_scope_start, environment_scope_end: '</a>'.html_safe }
.form-group
%h5= s_('ClusterIntegration|Base domain')
= field.text_field :domain, class: 'col-md-6 form-control js-select-on-focus'
.form-text.text-muted
- if @cluster.application_ingress_external_ip.present?
- auto_devops_url = 'https://docs.gitlab.com/ee/topics/autodevops/'
- auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url }
= s_('ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured to the Ingress IP Address below.').html_safe % { auto_devops_start: auto_devops_start, auto_devops_end: '</a>'.html_safe }
= s_('ClusterIntegration|Alternatively')
%code #{@cluster.application_ingress_external_ip}.nip.io
- custom_domain_url = 'https://docs.gitlab.com/ee/user/project/clusters/#pointing-your-dns-at-the-cluster-ip'
- custom_domain_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: custom_domain_url }
= s_('ClusterIntegration| can be used instead of a custom domain. %{custom_domain_start}More information%{custom_domain_end}').html_safe % { custom_domain_start: custom_domain_start, custom_domain_end: '</a>'.html_safe }
- else
= s_('ClusterIntegration|Before setting a domain, you must first install Ingress on your cluster below.')
- if can?(current_user, :update_cluster, @cluster)
.form-group
= field.submit _('Save changes'), class: 'btn btn-success'
......@@ -21,15 +21,10 @@
= s_('CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found.')
= link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank'
.card-footer.js-extra-settings{ class: @project.auto_devops_enabled? || 'hidden' }
= form.label :domain do
%strong= _('Domain')
= form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
.form-text.text-muted
= s_('CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.')
- if cluster_ingress_ip = cluster_ingress_ip(@project)
= s_('%{nip_domain} can be used as an alternative to a custom domain.').html_safe % { nip_domain: "<code>#{cluster_ingress_ip}.nip.io</code>".html_safe }
= link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-devops-base-domain'), target: '_blank'
%p.settings-message.text-center
- kubernetes_cluster_link = 'https://docs.gitlab.com/ee/user/project/clusters/'
- kubernetes_cluster_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: kubernetes_cluster_link }
= s_('CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly.').html_safe % { kubernetes_cluster_start: kubernetes_cluster_start, kubernetes_cluster_end: '</a>'.html_safe }
%label.prepend-top-10
%strong= s_('CICD|Deployment strategy')
%p.settings-message.text-center
......
---
title: Moves domain setting to cluster page
merge_request: 24580
author:
type: added
# frozen_string_literal: true
class MigrateAutoDevOpsDomainToClusterDomain < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
domains_info = connection.exec_query(project_auto_devops_query).rows
domains_info.each_slice(1_000) do |batch|
update_clusters_query = build_clusters_query(Hash[*batch.flatten])
connection.exec_query(update_clusters_query)
end
end
def down
# no-op
end
private
def project_auto_devops_table
@project_auto_devops_table ||= ProjectAutoDevops.arel_table
end
def cluster_projects_table
@cluster_projects_table ||= Clusters::Project.arel_table
end
# Fetches ProjectAutoDevops records with:
# - A domain set
# - With a Clusters::Project related to Project
#
# Returns an array of arrays like:
# => [
# [177, "104.198.38.135.nip.io"],
# [178, "35.232.213.111.nip.io"],
# ...
# ]
# Where the first element is the Cluster ID and
# the second element is the domain.
def project_auto_devops_query
project_auto_devops_table.join(cluster_projects_table, Arel::Nodes::OuterJoin)
.on(project_auto_devops_table[:project_id].eq(cluster_projects_table[:project_id]))
.where(project_auto_devops_table[:domain].not_eq(nil).and(project_auto_devops_table[:domain].not_eq('')))
.project(cluster_projects_table[:cluster_id], project_auto_devops_table[:domain])
.to_sql
end
# Returns an SQL UPDATE query using a CASE statement
# to update multiple cluster rows with different values.
#
# Example:
# UPDATE clusters
# SET domain = (CASE
# WHEN id = 177 then '104.198.38.135.nip.io'
# WHEN id = 178 then '35.232.213.111.nip.io'
# WHEN id = 179 then '35.232.168.149.nip.io'
# WHEN id = 180 then '35.224.116.88.nip.io'
# END)
# WHERE id IN (177,178,179,180);
def build_clusters_query(cluster_domains_info)
<<~HEREDOC
UPDATE clusters
SET domain = (CASE
#{cluster_when_statements(cluster_domains_info)}
END)
WHERE id IN (#{cluster_domains_info.keys.join(",")});
HEREDOC
end
def cluster_when_statements(cluster_domains_info)
cluster_domains_info.map do |cluster_id, domain|
"WHEN id = #{cluster_id} then '#{domain}'"
end.join("\n")
end
end
......@@ -138,9 +138,6 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
msgid "%{nip_domain} can be used as an alternative to a custom domain."
msgstr ""
msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
msgstr ""
......@@ -1236,7 +1233,7 @@ msgstr ""
msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
msgstr ""
msgid "CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages."
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
msgid "CICD|instance enabled"
......@@ -1509,6 +1506,9 @@ msgstr ""
msgid "Closed (moved)"
msgstr ""
msgid "ClusterIntegration| can be used instead of a custom domain. %{custom_domain_start}More information%{custom_domain_end}"
msgstr ""
msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
msgstr ""
......@@ -1539,6 +1539,9 @@ msgstr ""
msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
msgstr ""
msgid "ClusterIntegration|Alternatively"
msgstr ""
msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
msgstr ""
......@@ -1560,6 +1563,12 @@ msgstr ""
msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
msgstr ""
msgid "ClusterIntegration|Base domain"
msgstr ""
msgid "ClusterIntegration|Before setting a domain, you must first install Ingress on your cluster below."
msgstr ""
msgid "ClusterIntegration|CA Certificate"
msgstr ""
......@@ -1884,6 +1893,9 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured to the Ingress IP Address below."
msgstr ""
msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
......
......@@ -13,9 +13,7 @@ module QA # rubocop:disable Naming/FileName
view 'app/views/projects/settings/ci_cd/_autodevops_form.html.haml' do
element :enable_auto_devops_field, 'check_box :enabled' # rubocop:disable QA/ElementWithPattern
element :domain_field, 'text_field :domain' # rubocop:disable QA/ElementWithPattern
element :enable_auto_devops_button, "%strong= s_('CICD|Default to Auto DevOps pipeline')" # rubocop:disable QA/ElementWithPattern
element :domain_input, "%strong= _('Domain')" # rubocop:disable QA/ElementWithPattern
element :save_changes_button, "submit _('Save changes')" # rubocop:disable QA/ElementWithPattern
end
......@@ -31,10 +29,9 @@ module QA # rubocop:disable Naming/FileName
end
end
def enable_auto_devops_with_domain(domain)
def enable_auto_devops
expand_section(:autodevops_settings) do
check 'Default to Auto DevOps pipeline'
fill_in 'Domain', with: domain
click_on 'Save changes'
end
end
......
......@@ -6,10 +6,14 @@ module QA
module Resource
class KubernetesCluster < Base
attr_writer :project, :cluster,
:install_helm_tiller, :install_ingress, :install_prometheus, :install_runner
:install_helm_tiller, :install_ingress, :install_prometheus, :install_runner, :domain
attribute :ingress_ip do
Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip)
ingress_ip_value
end
attribute :domain do
"#{ingress_ip_value}.nip.io"
end
def fabricate!
......@@ -52,6 +56,12 @@ module QA
end
end
end
private
def ingress_ip_value
@ingress_ip_value ||= Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip)
end
end
end
end
......@@ -52,13 +52,13 @@ module QA
end
kubernetes_cluster.populate(:ingress_ip)
@project.visit!
Page::Project::Menu.act { click_ci_cd_settings }
Page::Project::Settings::CICD.perform do |p|
p.enable_auto_devops_with_domain(
"#{kubernetes_cluster.ingress_ip}.nip.io")
p.enable_auto_devops
end
kubernetes_cluster.populate(:domain)
end
after(:all) do
......
......@@ -429,12 +429,14 @@ describe Groups::ClustersController do
end
let(:cluster) { create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group]) }
let(:domain) { 'test-domain.com' }
let(:params) do
{
cluster: {
enabled: false,
name: 'my-new-cluster-name'
name: 'my-new-cluster-name',
domain: domain
}
}
end
......@@ -447,6 +449,20 @@ describe Groups::ClustersController do
expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
expect(cluster.enabled).to be_falsey
expect(cluster.name).to eq('my-new-cluster-name')
expect(cluster.domain).to eq('test-domain.com')
end
context 'when domain is invalid' do
let(:domain) { 'not-a-valid-domain' }
it 'should not update cluster attributes' do
go
cluster.reload
expect(response).to render_template(:show)
expect(cluster.name).not_to eq('my-new-cluster-name')
expect(cluster.domain).not_to eq('test-domain.com')
end
end
context 'when format is json' do
......@@ -456,7 +472,8 @@ describe Groups::ClustersController do
{
cluster: {
enabled: false,
name: 'my-new-cluster-name'
name: 'my-new-cluster-name',
domain: domain
}
}
end
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Clusterable > Show page' do
let(:current_user) { create(:user) }
before do
sign_in(current_user)
end
shared_examples 'editing domain' do
before do
clusterable.add_maintainer(current_user)
end
it 'allow the user to set domain' do
visit cluster_path
within '#cluster-integration' do
fill_in('cluster_domain', with: 'test.com')
click_on 'Save changes'
end
expect(page.status_code).to eq(200)
expect(page).to have_content('Kubernetes cluster was successfully updated.')
end
context 'when there is a cluster with ingress and external ip' do
before do
cluster.create_application_ingress!(external_ip: '192.168.1.100')
visit cluster_path
end
it 'shows help text with the domain as an alternative to custom domain' do
within '#cluster-integration' do
expect(page).to have_content('Alternatively 192.168.1.100.nip.io can be used instead of a custom domain')
end
end
end
context 'when there is no ingress' do
it 'alternative to custom domain is not shown' do
visit cluster_path
within '#cluster-integration' do
expect(page).to have_content('Before setting a domain, you must first install Ingress on your cluster below.')
end
end
end
end
context 'when clusterable is a project' do
it_behaves_like 'editing domain' do
let(:clusterable) { create(:project) }
let(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) }
let(:cluster_path) { project_cluster_path(clusterable, cluster) }
end
end
context 'when clusterable is a group' do
it_behaves_like 'editing domain' do
let(:clusterable) { create(:group) }
let(:cluster) { create(:cluster, :provided_by_gcp, :group, groups: [clusterable]) }
let(:cluster_path) { group_cluster_path(clusterable, cluster) }
end
end
end
......@@ -98,14 +98,12 @@ describe "Projects > Settings > Pipelines settings" do
expect(page).not_to have_content('instance enabled')
expect(find_field('project_auto_devops_attributes_enabled')).not_to be_checked
check 'Default to Auto DevOps pipeline'
fill_in('project_auto_devops_attributes_domain', with: 'test.com')
click_on 'Save changes'
end
expect(page.status_code).to eq(200)
expect(project.auto_devops).to be_present
expect(project.auto_devops).to be_enabled
expect(project.auto_devops.domain).to eq('test.com')
page.within '#autodevops-settings' do
expect(find_field('project_auto_devops_attributes_enabled')).to be_checked
......@@ -113,29 +111,6 @@ describe "Projects > Settings > Pipelines settings" do
end
end
end
context 'when there is a cluster with ingress and external_ip' do
before do
cluster = create(:cluster, projects: [project])
cluster.create_application_ingress!(external_ip: '192.168.1.100')
end
it 'shows the help text with the nip.io domain as an alternative to custom domain' do
visit project_settings_ci_cd_path(project)
expect(page).to have_content('192.168.1.100.nip.io can be used as an alternative to a custom domain')
end
end
context 'when there is no ingress' do
before do
create(:cluster, projects: [project])
end
it 'alternative to custom domain is not shown' do
visit project_settings_ci_cd_path(project)
expect(page).not_to have_content('can be used as an alternative to a custom domain')
end
end
end
describe 'runners registration token' do
......
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190129165720_migrate_auto_dev_ops_domain_to_cluster_domain.rb')
describe MigrateAutoDevOpsDomainToClusterDomain, :migration do
include MigrationHelpers::ClusterHelpers
let(:migration) { described_class.new }
let(:project_auto_devops_table) { table(:project_auto_devops) }
let(:clusters_table) { table(:clusters) }
let(:cluster_projects_table) { table(:cluster_projects) }
# Following lets are needed by MigrationHelpers::ClusterHelpers
let(:cluster_kubernetes_namespaces_table) { table(:clusters_kubernetes_namespaces) }
let(:projects_table) { table(:projects) }
let(:namespaces_table) { table(:namespaces) }
let(:provider_gcp_table) { table(:cluster_providers_gcp) }
let(:platform_kubernetes_table) { table(:cluster_platforms_kubernetes) }
before do
setup_cluster_projects_with_domain(quantity: 20, domain: domain)
end
context 'with ProjectAutoDevOps with no domain' do
let(:domain) { nil }
it 'should not update cluster project' do
migrate!
expect(clusters_without_domain.count).to eq(clusters_table.count)
end
end
context 'with ProjectAutoDevOps with domain' do
let(:domain) { 'example-domain.com' }
it 'should update all cluster projects' do
migrate!
expect(clusters_with_domain.count).to eq(clusters_table.count)
end
end
context 'when only some ProjectAutoDevOps have domain set' do
let(:domain) { 'example-domain.com' }
before do
setup_cluster_projects_with_domain(quantity: 20, domain: nil)
end
it 'should only update specific cluster projects' do
migrate!
project_auto_devops_with_domain.each do |project_auto_devops|
cluster_project = Clusters::Project.find_by(project_id: project_auto_devops.project_id)
cluster = Clusters::Cluster.find(cluster_project.cluster_id)
expect(cluster.domain).to be_present
end
project_auto_devops_without_domain.each do |project_auto_devops|
cluster_project = Clusters::Project.find_by(project_id: project_auto_devops.project_id)
cluster = Clusters::Cluster.find(cluster_project.cluster_id)
expect(cluster.domain).not_to be_present
end
end
end
def setup_cluster_projects_with_domain(quantity:, domain:)
create_cluster_project_list(quantity)
cluster_projects = cluster_projects_table.last(quantity)
cluster_projects.each do |cluster_project|
project_auto_devops_table.create(
project_id: cluster_project.project_id,
enabled: true,
domain: domain
)
end
end
def project_auto_devops_with_domain
project_auto_devops_table.where.not("domain IS NULL OR domain = ''")
end
def project_auto_devops_without_domain
project_auto_devops_table.where("domain IS NULL OR domain = ''")
end
def clusters_with_domain
clusters_table.where.not("domain IS NULL OR domain = ''")
end
def clusters_without_domain
clusters_table.where("domain IS NULL OR domain = ''")
end
end
......@@ -30,6 +30,7 @@ describe Clusters::Cluster do
it { is_expected.to delegate_method(:available?).to(:application_ingress).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_prometheus).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_knative).with_prefix }
it { is_expected.to delegate_method(:external_ip).to(:application_ingress).with_prefix }
it { is_expected.to respond_to :project }
......@@ -514,4 +515,62 @@ describe Clusters::Cluster do
it { is_expected.to be_falsey }
end
end
describe '#has_domain?' do
subject { cluster.has_domain? }
context 'with domain set at instance level' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
before do
stub_application_setting(auto_devops_domain: 'global_domain.com')
end
it { is_expected.to be_truthy }
end
context 'with domain set in cluster' do
let(:cluster) { create(:cluster, :provided_by_gcp, :with_domain) }
it { is_expected.to be_truthy }
end
context 'when domain is not set at instance level nor in cluster' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
it { is_expected.to be_falsy }
end
end
describe '#predefined_variables' do
subject { cluster.predefined_variables }
context 'with an instance domain' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
before do
stub_application_setting(auto_devops_domain: 'global_domain.com')
end
it 'should include KUBE_INGRESS_BASE_DOMAIN' do
expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'global_domain.com')
end
end
context 'with a cluster domain' do
let(:cluster) { create(:cluster, :provided_by_gcp, domain: 'example.com') }
it 'should include KUBE_INGRESS_BASE_DOMAIN' do
expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'example.com')
end
end
context 'with no domain' do
let(:cluster) { create(:cluster) }
it 'should return an empty array' do
expect(subject.to_hash).to be_empty
end
end
end
end
......@@ -297,6 +297,19 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
end
end
end
context 'with a domain' do
let!(:cluster) do
create(:cluster, :provided_by_gcp, :with_domain,
platform_kubernetes: kubernetes)