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 https://gitlab.com/gitlab-org/gitlab-ce/issues/52363
parent 280b6f6f
...@@ -127,6 +127,7 @@ def update_params ...@@ -127,6 +127,7 @@ def update_params
params.require(:cluster).permit( params.require(:cluster).permit(
:enabled, :enabled,
:environment_scope, :environment_scope,
:domain,
platform_kubernetes_attributes: [ platform_kubernetes_attributes: [
:namespace :namespace
] ]
...@@ -136,6 +137,7 @@ def update_params ...@@ -136,6 +137,7 @@ def update_params
:enabled, :enabled,
:name, :name,
:environment_scope, :environment_scope,
:domain,
platform_kubernetes_attributes: [ platform_kubernetes_attributes: [
:api_url, :api_url,
:token, :token,
......
...@@ -26,17 +26,6 @@ def auto_devops_warning_message(project) ...@@ -26,17 +26,6 @@ def auto_devops_warning_message(project)
end end
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 private
def missing_auto_devops_domain?(project) def missing_auto_devops_domain?(project)
......
...@@ -49,7 +49,7 @@ class Cluster < ActiveRecord::Base ...@@ -49,7 +49,7 @@ class Cluster < ActiveRecord::Base
validates :name, cluster_name: true validates :name, cluster_name: true
validates :cluster_type, presence: 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 :restrict_modification, on: :update
validate :no_groups, unless: :group_type? validate :no_groups, unless: :group_type?
...@@ -65,6 +65,7 @@ class Cluster < ActiveRecord::Base ...@@ -65,6 +65,7 @@ class Cluster < ActiveRecord::Base
delegate :available?, to: :application_ingress, prefix: true, allow_nil: true delegate :available?, to: :application_ingress, prefix: true, allow_nil: true
delegate :available?, to: :application_prometheus, 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 :available?, to: :application_knative, prefix: true, allow_nil: true
delegate :external_ip, to: :application_ingress, prefix: true, allow_nil: true
enum cluster_type: { enum cluster_type: {
instance_type: 1, instance_type: 1,
...@@ -193,8 +194,24 @@ def allow_user_defined_namespace? ...@@ -193,8 +194,24 @@ def allow_user_defined_namespace?
project_type? project_type?
end 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 private
def instance_domain
Gitlab::CurrentSettings.auto_devops_domain
end
def restrict_modification def restrict_modification
if provider&.on_creation? if provider&.on_creation?
errors.add(:base, "cannot modify during creation") errors.add(:base, "cannot modify during creation")
......
...@@ -98,6 +98,8 @@ def predefined_variables(project:) ...@@ -98,6 +98,8 @@ def predefined_variables(project:)
.append(key: 'KUBE_NAMESPACE', value: actual_namespace) .append(key: 'KUBE_NAMESPACE', value: actual_namespace)
.append(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true) .append(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true)
end end
variables.concat(cluster.predefined_variables)
end end
end end
......
...@@ -24,6 +24,9 @@ def has_domain? ...@@ -24,6 +24,9 @@ def has_domain?
domain.present? || instance_domain.present? domain.present? || instance_domain.present?
end 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 def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|
if has_domain? if has_domain?
......
...@@ -20,12 +20,28 @@ ...@@ -20,12 +20,28 @@
.form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.") .form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.")
- else - else
= text_field_tag :environment_scope, '*', class: 'col-md-6 form-control disabled', placeholder: s_('ClusterIntegration|Environment scope'), disabled: true = 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 } - environment_scope_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: environment_scope_url }
.form-text.text-muted .form-text.text-muted
%code * %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 } = 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) - if can?(current_user, :update_cluster, @cluster)
.form-group .form-group
= field.submit _('Save changes'), class: 'btn btn-success' = field.submit _('Save changes'), class: 'btn btn-success'
...@@ -21,15 +21,10 @@ ...@@ -21,15 +21,10 @@
= s_('CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found.') = 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' = link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank'
.card-footer.js-extra-settings{ class: @project.auto_devops_enabled? || 'hidden' } .card-footer.js-extra-settings{ class: @project.auto_devops_enabled? || 'hidden' }
= form.label :domain do %p.settings-message.text-center
%strong= _('Domain') - kubernetes_cluster_link = 'https://docs.gitlab.com/ee/user/project/clusters/'
= form.text_field :domain, class: 'form-control', placeholder: 'domain.com' - kubernetes_cluster_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: kubernetes_cluster_link }
.form-text.text-muted = 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 }
= 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'
%label.prepend-top-10 %label.prepend-top-10
%strong= s_('CICD|Deployment strategy') %strong= s_('CICD|Deployment strategy')
%p.settings-message.text-center %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 "" ...@@ -138,9 +138,6 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr "" 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" msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
msgstr "" msgstr ""
...@@ -1236,7 +1233,7 @@ msgstr "" ...@@ -1236,7 +1233,7 @@ msgstr ""
msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found." msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
msgstr "" 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 "" msgstr ""
msgid "CICD|instance enabled" msgid "CICD|instance enabled"
...@@ -1509,6 +1506,9 @@ msgstr "" ...@@ -1509,6 +1506,9 @@ msgstr ""
msgid "Closed (moved)" msgid "Closed (moved)"
msgstr "" 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}" 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 "" msgstr ""
...@@ -1539,6 +1539,9 @@ 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}" 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 "" msgstr ""
msgid "ClusterIntegration|Alternatively"
msgstr ""
msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}" msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
msgstr "" msgstr ""
...@@ -1560,6 +1563,12 @@ 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." msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
msgstr "" 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" msgid "ClusterIntegration|CA Certificate"
msgstr "" msgstr ""
...@@ -1884,6 +1893,9 @@ msgstr "" ...@@ -1884,6 +1893,9 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong while installing %{title}" msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr "" 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." 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 "" msgstr ""
......
...@@ -13,9 +13,7 @@ class CICD < Page::Base ...@@ -13,9 +13,7 @@ class CICD < Page::Base
view 'app/views/projects/settings/ci_cd/_autodevops_form.html.haml' do 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 :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 :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 element :save_changes_button, "submit _('Save changes')" # rubocop:disable QA/ElementWithPattern
end end
...@@ -31,10 +29,9 @@ def expand_ci_variables(&block) ...@@ -31,10 +29,9 @@ def expand_ci_variables(&block)
end end
end end
def enable_auto_devops_with_domain(domain) def enable_auto_devops
expand_section(:autodevops_settings) do expand_section(:autodevops_settings) do
check 'Default to Auto DevOps pipeline' check 'Default to Auto DevOps pipeline'
fill_in 'Domain', with: domain
click_on 'Save changes' click_on 'Save changes'
end end
end end
......
...@@ -6,10 +6,14 @@ module QA ...@@ -6,10 +6,14 @@ module QA
module Resource module Resource
class KubernetesCluster < Base class KubernetesCluster < Base
attr_writer :project, :cluster, 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 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 end
def fabricate! def fabricate!
...@@ -52,6 +56,12 @@ def fabricate! ...@@ -52,6 +56,12 @@ def fabricate!
end end
end end
end end
private
def ingress_ip_value
@ingress_ip_value ||= Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip)
end
end end
end end
end end
...@@ -52,13 +52,13 @@ def login ...@@ -52,13 +52,13 @@ def login
end end
kubernetes_cluster.populate(:ingress_ip) kubernetes_cluster.populate(:ingress_ip)
@project.visit! @project.visit!
Page::Project::Menu.act { click_ci_cd_settings } Page::Project::Menu.act { click_ci_cd_settings }
Page::Project::Settings::CICD.perform do |p| Page::Project::Settings::CICD.perform do |p|
p.enable_auto_devops_with_domain( p.enable_auto_devops
"#{kubernetes_cluster.ingress_ip}.nip.io")
end end
kubernetes_cluster.populate(:domain)
end end
after(:all) do after(:all) do
......
...@@ -429,12 +429,14 @@ def go(format: :html) ...@@ -429,12 +429,14 @@ def go(format: :html)
end end
let(:cluster) { create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group]) } let(:cluster) { create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group]) }
let(:domain) { 'test-domain.com' }
let(:params) do let(:params) do
{ {
cluster: { cluster: {
enabled: false, enabled: false,
name: 'my-new-cluster-name' name: 'my-new-cluster-name',
domain: domain
} }
} }
end end
...@@ -447,6 +449,20 @@ def go(format: :html) ...@@ -447,6 +449,20 @@ def go(format: :html)
expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.') expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
expect(cluster.enabled).to be_falsey expect(cluster.enabled).to be_falsey
expect(cluster.name).to eq('my-new-cluster-name') 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 end
context 'when format is json' do context 'when format is json' do
...@@ -456,7 +472,8 @@ def go(format: :html) ...@@ -456,7 +472,8 @@ def go(format: :html)
{ {
cluster: { cluster: {
enabled: false, enabled: false,
name: 'my-new-cluster-name' name: 'my-new-cluster-name',
domain: domain
} }
} }
end 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