From 072c01881d7d3971df6317415304e0b5747761a1 Mon Sep 17 00:00:00 2001 From: Matt Kasa <mkasa@gitlab.com> Date: Thu, 5 Sep 2019 14:53:51 -0700 Subject: [PATCH 1/3] Fix setting cluster application statuses - Add status_states method to get list of states with their values - Change applications to set status to status_states values --- app/models/clusters/applications/helm.rb | 2 +- app/models/clusters/applications/jupyter.rb | 2 +- app/models/clusters/applications/knative.rb | 2 +- app/models/clusters/concerns/application_core.rb | 2 +- app/models/clusters/concerns/application_status.rb | 6 ++++++ 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/models/clusters/applications/helm.rb b/app/models/clusters/applications/helm.rb index 455cf200fbc2ee..261f6ce8987059 100644 --- a/app/models/clusters/applications/helm.rb +++ b/app/models/clusters/applications/helm.rb @@ -27,7 +27,7 @@ def issue_client_cert def set_initial_status return unless not_installable? - self.status = 'installable' if cluster&.platform_kubernetes_active? + self.status = status_states[:installable] if cluster&.platform_kubernetes_active? end # It can only be uninstalled if there are no other applications installed diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb index ec65482a846fff..ca93bc15be08cd 100644 --- a/app/models/clusters/applications/jupyter.rb +++ b/app/models/clusters/applications/jupyter.rb @@ -23,7 +23,7 @@ def set_initial_status return unless cluster&.application_ingress_available? ingress = cluster.application_ingress - self.status = 'installable' if ingress.external_ip_or_hostname? + self.status = status_states[:installable] if ingress.external_ip_or_hostname? end def chart diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb index a9b9374622dda7..1069ba9cbef7db 100644 --- a/app/models/clusters/applications/knative.rb +++ b/app/models/clusters/applications/knative.rb @@ -21,7 +21,7 @@ def set_initial_status return unless not_installable? return unless verify_cluster? - self.status = 'installable' + self.status = status_states[:installable] end state_machine :status do diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb index d1b57a21a7d79c..e748c0a855d0d7 100644 --- a/app/models/clusters/concerns/application_core.rb +++ b/app/models/clusters/concerns/application_core.rb @@ -15,7 +15,7 @@ module ApplicationCore def set_initial_status return unless not_installable? - self.status = 'installable' if cluster&.application_helm_available? + self.status = status_states[:installable] if cluster&.application_helm_available? end def can_uninstall? diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb index 342d766f7230a8..f62e60871a9426 100644 --- a/app/models/clusters/concerns/application_status.rb +++ b/app/models/clusters/concerns/application_status.rb @@ -90,6 +90,12 @@ module ApplicationStatus end end + def status_states + self.class.state_machines[:status].states.each_with_object({}) do |state, states| + states[state.name] = state.value + end + end + def updateable? installed? || updated? || update_errored? end -- GitLab From 894d7bf9ddc9d1d00035b50bcdaa772620ae3b0d Mon Sep 17 00:00:00 2001 From: Matt Kasa <mkasa@gitlab.com> Date: Thu, 5 Sep 2019 14:57:12 -0700 Subject: [PATCH 2/3] Add cloud_run column for Cloud Run on GKE - Add cloud_run boolean column and index to cluster_providers_gcp table with default set to false - Add cloud_run scope to Clusters::Cluster - Update schema.rb with cloud_run migration Relates to https://gitlab.com/gitlab-org/gitlab-ce/issues/59370 --- app/models/clusters/providers/gcp.rb | 3 +++ ...add_cloud_run_to_clusters_providers_gcp.rb | 19 +++++++++++++++++++ db/schema.rb | 2 ++ 3 files changed, 24 insertions(+) create mode 100644 db/migrate/20190905140605_add_cloud_run_to_clusters_providers_gcp.rb diff --git a/app/models/clusters/providers/gcp.rb b/app/models/clusters/providers/gcp.rb index 390748bf252b83..dbb6cc45aa42bb 100644 --- a/app/models/clusters/providers/gcp.rb +++ b/app/models/clusters/providers/gcp.rb @@ -10,6 +10,9 @@ class Gcp < ApplicationRecord default_value_for :zone, 'us-central1-a' default_value_for :num_nodes, 3 default_value_for :machine_type, 'n1-standard-2' + default_value_for :cloud_run, false + + scope :cloud_run, -> { where(cloud_run: true) } attr_encrypted :access_token, mode: :per_attribute_iv, diff --git a/db/migrate/20190905140605_add_cloud_run_to_clusters_providers_gcp.rb b/db/migrate/20190905140605_add_cloud_run_to_clusters_providers_gcp.rb new file mode 100644 index 00000000000000..ac2e66a51dce72 --- /dev/null +++ b/db/migrate/20190905140605_add_cloud_run_to_clusters_providers_gcp.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class AddCloudRunToClustersProvidersGcp < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default(:cluster_providers_gcp, :cloud_run, :boolean, default: false) + add_concurrent_index(:cluster_providers_gcp, :cloud_run) + end + + def down + remove_column(:cluster_providers_gcp, :cloud_run) + remove_concurrent_index(:cluster_providers_gcp, :cloud_run) + end +end diff --git a/db/schema.rb b/db/schema.rb index b60593132f5fb7..891f2752c7aced 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -965,6 +965,8 @@ t.text "encrypted_access_token" t.string "encrypted_access_token_iv" t.boolean "legacy_abac", default: false, null: false + t.boolean "cloud_run", default: false, null: false + t.index ["cloud_run"], name: "index_cluster_providers_gcp_on_cloud_run" t.index ["cluster_id"], name: "index_cluster_providers_gcp_on_cluster_id", unique: true end -- GitLab From bcf142af81600b01c79ff435f47f0951120eb426 Mon Sep 17 00:00:00 2001 From: Matt Kasa <mkasa@gitlab.com> Date: Thu, 5 Sep 2019 15:00:48 -0700 Subject: [PATCH 3/3] Add Cloud Run on GKE feature to cluster creation - Permits cloud_run parameter in ClustersController#create - Enables httpLoadBalancing, istioConfig, and cloudRunConfig in Gcp ProvisionService if cloud_run is enabled - Add `Enable Cloud Run on GKE` checkbox to cluster create page - Add `Enable Cloud Run on GKE` checkbox to cluster details - Default knative to pre_installed for cloud_run clusters - Make knative not uninstallable for pre_installd clusters - Update project clusters docs with entry about Cloud Run - Update tests and add new test for cluster with cloud_run enabled - Add Cloud Run on GKE strings to translations - Add spec that will fail when google-api-client has been upgraded - Add pre_installed to applications frontend - Pass providerType and preInstalledKnative to frontend - Display `installed via` on frontend for Cloud Run - Add Cloud Run spec for FinalizeCreationService Relates to https://gitlab.com/gitlab-org/gitlab/issues/27502 --- .../javascripts/clusters/clusters_bundle.js | 9 ++ .../clusters/components/application_row.vue | 9 ++ .../clusters/components/applications.vue | 39 +++++++- app/assets/javascripts/clusters/constants.js | 7 ++ .../services/application_state_machine.js | 25 +++++ .../clusters/stores/clusters_store.js | 13 +++ .../clusters/clusters_controller.rb | 1 + app/models/clusters/applications/knative.rb | 4 + app/models/clusters/cluster.rb | 4 + .../clusters/concerns/application_status.rb | 13 ++- app/models/clusters/providers/gcp.rb | 4 + .../clusters/gcp/finalize_creation_service.rb | 8 ++ .../clusters/gcp/provision_service.rb | 7 +- .../clusters/clusters/gcp/_form.html.haml | 7 ++ app/views/clusters/clusters/show.html.haml | 3 + .../27502-enable-cloud-run-on-gke.yml | 5 + config/initializers/google_api_client.rb | 17 ++++ ...add_cloud_run_to_clusters_providers_gcp.rb | 2 - ..._to_clusters_providers_gcp_on_cloud_run.rb | 17 ++++ db/schema.rb | 2 +- doc/user/project/clusters/index.md | 10 ++ lib/google_api/cloud_platform/client.rb | 52 +++++----- locale/gitlab.pot | 12 +++ spec/factories/clusters/clusters.rb | 4 + spec/factories/clusters/providers/gcp.rb | 4 + .../clusters/stores/clusters_store_spec.js | 3 + spec/initializers/google_api_client_spec.rb | 17 ++++ .../google_api/cloud_platform/client_spec.rb | 98 ++++++++++--------- .../clusters/applications/knative_spec.rb | 7 ++ spec/models/clusters/cluster_spec.rb | 22 +++++ spec/models/clusters/providers/gcp_spec.rb | 16 +++ .../gcp/finalize_creation_service_spec.rb | 24 ++++- .../google_api/cloud_platform_helpers.rb | 2 +- ...ster_application_status_shared_examples.rb | 14 +++ 34 files changed, 403 insertions(+), 78 deletions(-) create mode 100644 changelogs/unreleased/27502-enable-cloud-run-on-gke.yml create mode 100644 config/initializers/google_api_client.rb create mode 100644 db/migrate/20190919162036_add_index_to_clusters_providers_gcp_on_cloud_run.rb create mode 100644 spec/initializers/google_api_client_spec.rb diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index d386960f3b6ef7..7ea8901ecbb7ab 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -41,6 +41,8 @@ export default class Clusters { managePrometheusPath, clusterEnvironmentsPath, hasRbac, + providerType, + preInstalledKnative, clusterType, clusterStatus, clusterStatusReason, @@ -50,6 +52,7 @@ export default class Clusters { environmentsHelpPath, clustersHelpPath, deployBoardsHelpPath, + cloudRunHelpPath, clusterId, } = document.querySelector('.js-edit-cluster-form').dataset; @@ -65,10 +68,13 @@ export default class Clusters { environmentsHelpPath, clustersHelpPath, deployBoardsHelpPath, + cloudRunHelpPath, ); this.store.setManagePrometheusPath(managePrometheusPath); this.store.updateStatus(clusterStatus); this.store.updateStatusReason(clusterStatusReason); + this.store.updateProviderType(providerType); + this.store.updatePreInstalledKnative(preInstalledKnative); this.store.updateRbac(hasRbac); this.service = new ClustersService({ endpoint: statusPath, @@ -153,6 +159,9 @@ export default class Clusters { ingressHelpPath: this.state.ingressHelpPath, managePrometheusPath: this.state.managePrometheusPath, ingressDnsHelpPath: this.state.ingressDnsHelpPath, + cloudRunHelpPath: this.state.cloudRunHelpPath, + providerType: this.state.providerType, + preInstalledKnative: this.state.preInstalledKnative, rbac: this.state.rbac, }, }); diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue index 64364092016d6f..c6c8dc6352c4f0 100644 --- a/app/assets/javascripts/clusters/components/application_row.vue +++ b/app/assets/javascripts/clusters/components/application_row.vue @@ -78,6 +78,10 @@ export default { required: false, default: false, }, + installedVia: { + type: String, + required: false, + }, version: { type: String, required: false, @@ -311,6 +315,11 @@ export default { > <span v-else class="js-cluster-application-title">{{ title }}</span> </strong> + <span + v-if="installedVia" + class="js-cluster-application-installed-via" + v-html="installedVia" + ></span> <slot name="description"></slot> <div v-if="hasError" class="cluster-application-error text-danger prepend-top-10"> <p class="js-cluster-application-general-error-message append-bottom-0"> diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index 27959898fb7949..4d3e759d8d4857 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -16,7 +16,7 @@ import { s__, sprintf } from '../../locale'; import applicationRow from './application_row.vue'; import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; import KnativeDomainEditor from './knative_domain_editor.vue'; -import { CLUSTER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants'; +import { CLUSTER_TYPE, PROVIDER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants'; import LoadingButton from '~/vue_shared/components/loading_button.vue'; import eventHub from '~/clusters/event_hub'; @@ -54,11 +54,26 @@ export default { required: false, default: '', }, + cloudRunHelpPath: { + type: String, + required: false, + default: '', + }, managePrometheusPath: { type: String, required: false, default: '', }, + providerType: { + type: String, + required: false, + default: '', + }, + preInstalledKnative: { + type: Boolean, + required: false, + default: false, + }, rbac: { type: Boolean, required: false, @@ -156,6 +171,25 @@ export default { knative() { return this.applications.knative; }, + cloudRun() { + return this.providerType === PROVIDER_TYPE.GCP && this.preInstalledKnative; + }, + installedVia() { + if (this.cloudRun) { + return sprintf( + _.escape(s__(`ClusterIntegration|installed via %{installed_via}`)), + { + installed_via: `<a href="${ + this.cloudRunHelpPath + }" target="_blank" rel="noopener noreferrer">${_.escape( + s__('ClusterIntegration|Cloud Run'), + )}</a>`, + }, + false, + ); + } + return null; + }, }, created() { this.helmInstallIllustration = helmInstallIllustration; @@ -468,6 +502,7 @@ export default { :installed="applications.knative.installed" :install-failed="applications.knative.installFailed" :install-application-request-params="{ hostname: applications.knative.hostname }" + :installed-via="installedVia" :uninstallable="applications.knative.uninstallable" :uninstall-successful="applications.knative.uninstallSuccessful" :uninstall-failed="applications.knative.uninstallFailed" @@ -499,7 +534,7 @@ export default { </p> <knative-domain-editor - v-if="knative.installed || (helmInstalled && rbac)" + v-if="(knative.installed || (helmInstalled && rbac)) && !preInstalledKnative" :knative="knative" :ingress-dns-help-path="ingressDnsHelpPath" @save="saveKnativeDomain" diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js index 8fd752092c982e..c6e4b7951cf90b 100644 --- a/app/assets/javascripts/clusters/constants.js +++ b/app/assets/javascripts/clusters/constants.js @@ -5,6 +5,11 @@ export const CLUSTER_TYPE = { PROJECT: 'project_type', }; +// These need to match the available providers in app/models/clusters/providers/ +export const PROVIDER_TYPE = { + GCP: 'gcp', +}; + // These need to match what is returned from the server export const APPLICATION_STATUS = { NO_STATUS: null, @@ -19,6 +24,7 @@ export const APPLICATION_STATUS = { UNINSTALLING: 'uninstalling', UNINSTALL_ERRORED: 'uninstall_errored', ERROR: 'errored', + PRE_INSTALLED: 'pre_installed', }; /* @@ -29,6 +35,7 @@ export const APPLICATION_INSTALLED_STATUSES = [ APPLICATION_STATUS.INSTALLED, APPLICATION_STATUS.UPDATING, APPLICATION_STATUS.UNINSTALLING, + APPLICATION_STATUS.PRE_INSTALLED, ]; // These are only used client-side diff --git a/app/assets/javascripts/clusters/services/application_state_machine.js b/app/assets/javascripts/clusters/services/application_state_machine.js index 6e632519d8a2b2..6bc4be7b93ae0d 100644 --- a/app/assets/javascripts/clusters/services/application_state_machine.js +++ b/app/assets/javascripts/clusters/services/application_state_machine.js @@ -13,6 +13,7 @@ const { UPDATE_ERRORED, UNINSTALLING, UNINSTALL_ERRORED, + PRE_INSTALLED, } = APPLICATION_STATUS; const applicationStateMachine = { @@ -63,6 +64,9 @@ const applicationStateMachine = { uninstallFailed: true, }, }, + [PRE_INSTALLED]: { + target: PRE_INSTALLED, + }, }, }, [NOT_INSTALLABLE]: { @@ -123,6 +127,27 @@ const applicationStateMachine = { }, }, }, + [PRE_INSTALLED]: { + on: { + [UPDATE_EVENT]: { + target: UPDATING, + effects: { + updateFailed: false, + updateSuccessful: false, + }, + }, + [NOT_INSTALLABLE]: { + target: NOT_INSTALLABLE, + }, + [UNINSTALL_EVENT]: { + target: UNINSTALLING, + effects: { + uninstallFailed: false, + uninstallSuccessful: false, + }, + }, + }, + }, [UPDATING]: { on: { [UPDATED]: { diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js index 5cddb4cc0982bf..6464461ea0c273 100644 --- a/app/assets/javascripts/clusters/stores/clusters_store.js +++ b/app/assets/javascripts/clusters/stores/clusters_store.js @@ -35,7 +35,10 @@ export default class ClusterStore { environmentsHelpPath: null, clustersHelpPath: null, deployBoardsHelpPath: null, + cloudRunHelpPath: null, status: null, + providerType: null, + preInstalledKnative: false, rbac: false, statusReason: null, applications: { @@ -95,6 +98,7 @@ export default class ClusterStore { environmentsHelpPath, clustersHelpPath, deployBoardsHelpPath, + cloudRunHelpPath, ) { this.state.helpPath = helpPath; this.state.ingressHelpPath = ingressHelpPath; @@ -102,6 +106,7 @@ export default class ClusterStore { this.state.environmentsHelpPath = environmentsHelpPath; this.state.clustersHelpPath = clustersHelpPath; this.state.deployBoardsHelpPath = deployBoardsHelpPath; + this.state.cloudRunHelpPath = cloudRunHelpPath; } setManagePrometheusPath(managePrometheusPath) { @@ -112,6 +117,14 @@ export default class ClusterStore { this.state.status = status; } + updateProviderType(providerType) { + this.state.providerType = providerType; + } + + updatePreInstalledKnative(preInstalledKnative) { + this.state.preInstalledKnative = parseBoolean(preInstalledKnative); + } + updateRbac(rbac) { this.state.rbac = parseBoolean(rbac); } diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index 15f1e8284ffd8a..993aba661f3f83 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -170,6 +170,7 @@ def create_gcp_cluster_params :zone, :num_nodes, :machine_type, + :cloud_run, :legacy_abac ]).merge( provider_type: :gcp, diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb index 1069ba9cbef7db..f2a3695d2eb84b 100644 --- a/app/models/clusters/applications/knative.rb +++ b/app/models/clusters/applications/knative.rb @@ -47,6 +47,10 @@ def values { "domain" => hostname }.to_yaml end + def allowed_to_uninstall? + !pre_installed? + end + def install_command Gitlab::Kubernetes::Helm::InstallCommand.new( name: name, diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 6a5b98a46768b0..3459c566ff3a2c 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -194,6 +194,10 @@ def predefined_variables end end + def knative_pre_installed? + provider&.knative_pre_installed? + end + private def instance_domain diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb index f62e60871a9426..b63a596dfeecce 100644 --- a/app/models/clusters/concerns/application_status.rb +++ b/app/models/clusters/concerns/application_status.rb @@ -28,6 +28,13 @@ module ApplicationStatus state :uninstalling, value: 7 state :uninstall_errored, value: 8 + # Used for applications that are pre-installed by the cluster, + # e.g. Knative in GCP Cloud Run enabled clusters + # Because we cannot upgrade or uninstall Knative in these clusters, + # we define only one simple state transition to enter the `pre_installed` state, + # and no exit transitions. + state :pre_installed, value: 9 + event :make_scheduled do transition [:installable, :errored, :installed, :updated, :update_errored, :uninstall_errored] => :scheduled end @@ -41,6 +48,10 @@ module ApplicationStatus transition [:updating] => :updated end + event :make_pre_installed do + transition any => :pre_installed + end + event :make_errored do transition any - [:updating, :uninstalling] => :errored transition [:updating] => :update_errored @@ -101,7 +112,7 @@ def updateable? end def available? - installed? || updated? + pre_installed? || installed? || updated? end def update_in_progress? diff --git a/app/models/clusters/providers/gcp.rb b/app/models/clusters/providers/gcp.rb index dbb6cc45aa42bb..043765f79ac5f2 100644 --- a/app/models/clusters/providers/gcp.rb +++ b/app/models/clusters/providers/gcp.rb @@ -80,6 +80,10 @@ def api_client @api_client ||= GoogleApi::CloudPlatform::Client.new(access_token, nil) end + + def knative_pre_installed? + cloud_run? + end end end end diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb index c5cde8319647f8..0aff1bcc8b90fc 100644 --- a/app/services/clusters/gcp/finalize_creation_service.rb +++ b/app/services/clusters/gcp/finalize_creation_service.rb @@ -11,6 +11,7 @@ def execute(provider) configure_provider create_gitlab_service_account! configure_kubernetes + configure_pre_installed_knative if provider.knative_pre_installed? cluster.save! rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e log_service_error(e.class.name, provider.id, e.message) @@ -48,6 +49,13 @@ def configure_kubernetes token: request_kubernetes_token) end + def configure_pre_installed_knative + knative = cluster.build_application_knative( + hostname: 'example.com' + ) + knative.make_pre_installed! + end + def request_kubernetes_token Clusters::Kubernetes::FetchKubernetesTokenService.new( kube_client, diff --git a/app/services/clusters/gcp/provision_service.rb b/app/services/clusters/gcp/provision_service.rb index 80040511ec2c2c..7dc2d3c32f13b8 100644 --- a/app/services/clusters/gcp/provision_service.rb +++ b/app/services/clusters/gcp/provision_service.rb @@ -3,6 +3,8 @@ module Clusters module Gcp class ProvisionService + CLOUD_RUN_ADDONS = %i[http_load_balancing istio_config cloud_run_config].freeze + attr_reader :provider def execute(provider) @@ -22,13 +24,16 @@ def execute(provider) private def get_operation_id + enable_addons = provider.cloud_run? ? CLOUD_RUN_ADDONS : [] + operation = provider.api_client.projects_zones_clusters_create( provider.gcp_project_id, provider.zone, provider.cluster.name, provider.num_nodes, machine_type: provider.machine_type, - legacy_abac: provider.legacy_abac + legacy_abac: provider.legacy_abac, + enable_addons: enable_addons ) unless operation.status == 'PENDING' || operation.status == 'RUNNING' diff --git a/app/views/clusters/clusters/gcp/_form.html.haml b/app/views/clusters/clusters/gcp/_form.html.haml index 4d3e3359ea0f22..196ad422766928 100644 --- a/app/views/clusters/clusters/gcp/_form.html.haml +++ b/app/views/clusters/clusters/gcp/_form.html.haml @@ -65,6 +65,13 @@ %p.form-text.text-muted = s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end } + .form-group + = provider_gcp_field.check_box :cloud_run, { label: s_('ClusterIntegration|Enable Cloud Run on GKE (beta)'), + label_class: 'label-bold' } + .form-text.text-muted + = s_('ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster.') + = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'cloud-run-on-gke'), target: '_blank' + .form-group = field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'), label_class: 'label-bold' } diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml index 00fdd5e9562aae..859a07d05b15d2 100644 --- a/app/views/clusters/clusters/show.html.haml +++ b/app/views/clusters/clusters/show.html.haml @@ -22,12 +22,15 @@ cluster_type: @cluster.cluster_type, cluster_status: @cluster.status_name, cluster_status_reason: @cluster.status_reason, + provider_type: @cluster.provider_type, + pre_installed_knative: @cluster.knative_pre_installed? ? 'true': 'false', help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'), ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'), ingress_dns_help_path: help_page_path('user/project/clusters/index.md', anchor: 'manually-determining-the-external-endpoint'), environments_help_path: help_page_path('ci/environments', anchor: 'defining-environments'), clusters_help_path: help_page_path('user/project/clusters/index.md', anchor: 'deploying-to-a-kubernetes-cluster'), deploy_boards_help_path: help_page_path('user/project/deploy_boards.html', anchor: 'enabling-deploy-boards'), + cloud_run_help_path: help_page_path('user/project/clusters/index.md', anchor: 'cloud-run-on-gke'), manage_prometheus_path: manage_prometheus_path, cluster_id: @cluster.id } } diff --git a/changelogs/unreleased/27502-enable-cloud-run-on-gke.yml b/changelogs/unreleased/27502-enable-cloud-run-on-gke.yml new file mode 100644 index 00000000000000..77b365f17dbff7 --- /dev/null +++ b/changelogs/unreleased/27502-enable-cloud-run-on-gke.yml @@ -0,0 +1,5 @@ +--- +title: Enable Cloud Run on GKE cluster creation +merge_request: 16566 +author: +type: added diff --git a/config/initializers/google_api_client.rb b/config/initializers/google_api_client.rb new file mode 100644 index 00000000000000..611726a20c7d16 --- /dev/null +++ b/config/initializers/google_api_client.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true +# +# google-api-client >= 0.26.0 supports enabling CloudRun and Istio during +# cluster creation, but fog-google currently hard deps on '~> 0.23.0', which +# prevents us from upgrading. We are injecting these options as hashes below +# as a workaround until this is resolved. +# +# This can be removed once fog-google and google-api-client can be upgraded. +# See https://gitlab.com/gitlab-org/gitlab-ce/issues/66630 for more details. +# + +require 'google/apis/container_v1beta1' + +Google::Apis::ContainerV1beta1::AddonsConfig::Representation.tap do |representation| + representation.hash :cloud_run_config, as: 'cloudRunConfig' + representation.hash :istio_config, as: 'istioConfig' +end diff --git a/db/migrate/20190905140605_add_cloud_run_to_clusters_providers_gcp.rb b/db/migrate/20190905140605_add_cloud_run_to_clusters_providers_gcp.rb index ac2e66a51dce72..e7ffd7cd4d366a 100644 --- a/db/migrate/20190905140605_add_cloud_run_to_clusters_providers_gcp.rb +++ b/db/migrate/20190905140605_add_cloud_run_to_clusters_providers_gcp.rb @@ -9,11 +9,9 @@ class AddCloudRunToClustersProvidersGcp < ActiveRecord::Migration[5.2] def up add_column_with_default(:cluster_providers_gcp, :cloud_run, :boolean, default: false) - add_concurrent_index(:cluster_providers_gcp, :cloud_run) end def down remove_column(:cluster_providers_gcp, :cloud_run) - remove_concurrent_index(:cluster_providers_gcp, :cloud_run) end end diff --git a/db/migrate/20190919162036_add_index_to_clusters_providers_gcp_on_cloud_run.rb b/db/migrate/20190919162036_add_index_to_clusters_providers_gcp_on_cloud_run.rb new file mode 100644 index 00000000000000..8e0bde97cc1909 --- /dev/null +++ b/db/migrate/20190919162036_add_index_to_clusters_providers_gcp_on_cloud_run.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddIndexToClustersProvidersGcpOnCloudRun < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index(:cluster_providers_gcp, :cloud_run) + end + + def down + remove_concurrent_index(:cluster_providers_gcp, :cloud_run) + end +end diff --git a/db/schema.rb b/db/schema.rb index 891f2752c7aced..392db66f5b66dc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_09_18_104222) do +ActiveRecord::Schema.define(version: 2019_09_19_162036) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 97fa973d3e3583..49878978154eb7 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -154,6 +154,7 @@ new Kubernetes cluster to your project: - **Number of nodes** - Enter the number of nodes you wish the cluster to have. - **Machine type** - The [machine type](https://cloud.google.com/compute/docs/machine-types) of the Virtual Machine instance that the cluster will be based on. + - **Enable Cloud Run on GKE (beta)** - Check this if you want to use Cloud Run on GKE for this cluster. See the [Cloud Run on GKE section](#cloud-run-on-gke) for more information. - **GitLab-managed cluster** - Leave this checked if you want GitLab to manage namespaces and service accounts for this cluster. See the [Managed clusters section](#gitlab-managed-clusters) for more information. 1. Finally, click the **Create Kubernetes cluster** button. @@ -339,6 +340,15 @@ functionalities needed to successfully build and deploy a containerized application. Bear in mind that the same credentials are used for all the applications running on the cluster. +### Cloud Run on GKE + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/merge_requests/16566) in GitLab 12.4. + +You can choose to use Cloud Run on GKE in place of installing Knative and Istio +separately after the cluster has been created. This means that Cloud Run +(Knative), Istio, and HTTP Load Balancing will be enabled on the cluster at +create time and cannot be [installed or uninstalled](../../clusters/applications.md) separately. + ### GitLab-managed clusters > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/22011) in GitLab 11.5. diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb index 9f01a3f97cee3a..eaf94708282b63 100644 --- a/lib/google_api/cloud_platform/client.rb +++ b/lib/google_api/cloud_platform/client.rb @@ -2,6 +2,7 @@ require 'google/apis/compute_v1' require 'google/apis/container_v1' +require 'google/apis/container_v1beta1' require 'google/apis/cloudbilling_v1' require 'google/apis/cloudresourcemanager_v1' @@ -53,30 +54,13 @@ def projects_zones_clusters_get(project_id, zone, cluster_id) service.get_zone_cluster(project_id, zone, cluster_id, options: user_agent_header) end - def projects_zones_clusters_create(project_id, zone, cluster_name, cluster_size, machine_type:, legacy_abac:) - service = Google::Apis::ContainerV1::ContainerService.new + def projects_zones_clusters_create(project_id, zone, cluster_name, cluster_size, machine_type:, legacy_abac:, enable_addons: []) + service = Google::Apis::ContainerV1beta1::ContainerService.new service.authorization = access_token - request_body = Google::Apis::ContainerV1::CreateClusterRequest.new( - { - "cluster": { - "name": cluster_name, - "initial_node_count": cluster_size, - "node_config": { - "machine_type": machine_type - }, - "master_auth": { - "username": CLUSTER_MASTER_AUTH_USERNAME, - "client_certificate_config": { - issue_client_certificate: true - } - }, - "legacy_abac": { - "enabled": legacy_abac - } - } - } - ) + cluster_options = make_cluster_options(cluster_name, cluster_size, machine_type, legacy_abac, enable_addons) + + request_body = Google::Apis::ContainerV1beta1::CreateClusterRequest.new(cluster_options) service.create_cluster(project_id, zone, request_body, options: user_agent_header) end @@ -95,6 +79,30 @@ def parse_operation_id(self_link) private + def make_cluster_options(cluster_name, cluster_size, machine_type, legacy_abac, enable_addons) + { + cluster: { + name: cluster_name, + initial_node_count: cluster_size, + node_config: { + machine_type: machine_type + }, + master_auth: { + username: CLUSTER_MASTER_AUTH_USERNAME, + client_certificate_config: { + issue_client_certificate: true + } + }, + legacy_abac: { + enabled: legacy_abac + }, + addons_config: enable_addons.each_with_object({}) do |addon, hash| + hash[addon] = { disabled: false } + end + } + } + end + def token_life_time(expires_at) DateTime.strptime(expires_at, '%s').to_time.utc - Time.now.utc end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9be6cbca00c86e..a9dabed430bfed 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3325,6 +3325,9 @@ msgstr "" msgid "ClusterIntegration|Choose which of your environments will use this cluster." msgstr "" +msgid "ClusterIntegration|Cloud Run" +msgstr "" + msgid "ClusterIntegration|Cluster health" msgstr "" @@ -3364,6 +3367,9 @@ msgstr "" msgid "ClusterIntegration|Did you know?" msgstr "" +msgid "ClusterIntegration|Enable Cloud Run on GKE (beta)" +msgstr "" + msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster." msgstr "" @@ -3724,6 +3730,9 @@ msgstr "" msgid "ClusterIntegration|Update failed. Please check the logs and try again." msgstr "" +msgid "ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster." +msgstr "" + msgid "ClusterIntegration|Validating project billing status" msgstr "" @@ -3760,6 +3769,9 @@ msgstr "" msgid "ClusterIntegration|help page" msgstr "" +msgid "ClusterIntegration|installed via %{installed_via}" +msgstr "" + msgid "ClusterIntegration|meets the requirements" msgstr "" diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb index d294e6d055e72f..29aea5e403e72d 100644 --- a/spec/factories/clusters/clusters.rb +++ b/spec/factories/clusters/clusters.rb @@ -58,6 +58,10 @@ platform_kubernetes factory: [:cluster_platform_kubernetes, :configured, :rbac_disabled] end + trait :cloud_run_enabled do + provider_gcp factory: [:cluster_provider_gcp, :created, :cloud_run_enabled] + end + trait :disabled do enabled false end diff --git a/spec/factories/clusters/providers/gcp.rb b/spec/factories/clusters/providers/gcp.rb index 22462651b6af10..7fdcdebad346db 100644 --- a/spec/factories/clusters/providers/gcp.rb +++ b/spec/factories/clusters/providers/gcp.rb @@ -34,5 +34,9 @@ trait :abac_enabled do legacy_abac true end + + trait :cloud_run_enabled do + cloud_run true + end end end diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js index ee3b7d8aa90a30..5ee06eb44c94ad 100644 --- a/spec/frontend/clusters/stores/clusters_store_spec.js +++ b/spec/frontend/clusters/stores/clusters_store_spec.js @@ -54,8 +54,11 @@ describe('Clusters Store', () => { environmentsHelpPath: null, clustersHelpPath: null, deployBoardsHelpPath: null, + cloudRunHelpPath: null, status: mockResponseData.status, statusReason: mockResponseData.status_reason, + providerType: null, + preInstalledKnative: false, rbac: false, applications: { helm: { diff --git a/spec/initializers/google_api_client_spec.rb b/spec/initializers/google_api_client_spec.rb new file mode 100644 index 00000000000000..44a1bc0836cdce --- /dev/null +++ b/spec/initializers/google_api_client_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe './config/initializers/google_api_client.rb' do + subject { Google::Apis::ContainerV1beta1 } + + it 'is needed' do |example| + is_expected.not_to be_const_defined(:CloudRunConfig), + <<-MSG.strip_heredoc + The google-api-client gem has been upgraded! + Remove: + #{example.example_group.description} + #{example.file_path} + MSG + end +end diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb index c24998d32f876e..2253feb376dfb4 100644 --- a/spec/lib/google_api/cloud_platform/client_spec.rb +++ b/spec/lib/google_api/cloud_platform/client_spec.rb @@ -68,7 +68,7 @@ describe '#projects_zones_clusters_create' do subject do client.projects_zones_clusters_create( - project_id, zone, cluster_name, cluster_size, machine_type: machine_type, legacy_abac: legacy_abac) + project_id, zone, cluster_name, cluster_size, machine_type: machine_type, legacy_abac: legacy_abac, enable_addons: enable_addons) end let(:project_id) { 'project-123' } @@ -77,39 +77,51 @@ let(:cluster_size) { 1 } let(:machine_type) { 'n1-standard-2' } let(:legacy_abac) { true } - let(:create_cluster_request_body) { double('Google::Apis::ContainerV1::CreateClusterRequest') } + let(:enable_addons) { [] } + + let(:addons_config) do + enable_addons.each_with_object({}) do |addon, hash| + hash[addon] = { disabled: false } + end + end + + let(:cluster_options) do + { + cluster: { + name: cluster_name, + initial_node_count: cluster_size, + node_config: { + machine_type: machine_type + }, + master_auth: { + username: 'admin', + client_certificate_config: { + issue_client_certificate: true + } + }, + legacy_abac: { + enabled: legacy_abac + }, + addons_config: addons_config + } + } + end + + let(:create_cluster_request_body) { double('Google::Apis::ContainerV1beta1::CreateClusterRequest') } let(:operation) { double } before do - allow_any_instance_of(Google::Apis::ContainerV1::ContainerService) + allow_any_instance_of(Google::Apis::ContainerV1beta1::ContainerService) .to receive(:create_cluster).with(any_args) .and_return(operation) end it 'sets corresponded parameters' do - expect_any_instance_of(Google::Apis::ContainerV1::ContainerService) + expect_any_instance_of(Google::Apis::ContainerV1beta1::ContainerService) .to receive(:create_cluster).with(project_id, zone, create_cluster_request_body, options: user_agent_options) - expect(Google::Apis::ContainerV1::CreateClusterRequest) - .to receive(:new).with( - { - "cluster": { - "name": cluster_name, - "initial_node_count": cluster_size, - "node_config": { - "machine_type": machine_type - }, - "master_auth": { - "username": "admin", - "client_certificate_config": { - issue_client_certificate: true - } - }, - "legacy_abac": { - "enabled": true - } - } - } ).and_return(create_cluster_request_body) + expect(Google::Apis::ContainerV1beta1::CreateClusterRequest) + .to receive(:new).with(cluster_options).and_return(create_cluster_request_body) expect(subject).to eq operation end @@ -118,29 +130,25 @@ let(:legacy_abac) { false } it 'sets corresponded parameters' do - expect_any_instance_of(Google::Apis::ContainerV1::ContainerService) + expect_any_instance_of(Google::Apis::ContainerV1beta1::ContainerService) + .to receive(:create_cluster).with(project_id, zone, create_cluster_request_body, options: user_agent_options) + + expect(Google::Apis::ContainerV1beta1::CreateClusterRequest) + .to receive(:new).with(cluster_options).and_return(create_cluster_request_body) + + expect(subject).to eq operation + end + end + + context 'create with enable_addons for cloud_run' do + let(:enable_addons) { [:http_load_balancing, :istio_config, :cloud_run_config] } + + it 'sets corresponded parameters' do + expect_any_instance_of(Google::Apis::ContainerV1beta1::ContainerService) .to receive(:create_cluster).with(project_id, zone, create_cluster_request_body, options: user_agent_options) - expect(Google::Apis::ContainerV1::CreateClusterRequest) - .to receive(:new).with( - { - "cluster": { - "name": cluster_name, - "initial_node_count": cluster_size, - "node_config": { - "machine_type": machine_type - }, - "master_auth": { - "username": "admin", - "client_certificate_config": { - issue_client_certificate: true - } - }, - "legacy_abac": { - "enabled": false - } - } - } ).and_return(create_cluster_request_body) + expect(Google::Apis::ContainerV1beta1::CreateClusterRequest) + .to receive(:new).with(cluster_options).and_return(create_cluster_request_body) expect(subject).to eq operation end diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index 3825994b733f33..16247026e34247 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -16,6 +16,13 @@ allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async) end + describe 'when cloud run is enabled' do + let(:cluster) { create(:cluster, :provided_by_gcp, :cloud_run_enabled) } + let(:knative_cloud_run) { create(:clusters_applications_knative, cluster: cluster) } + + it { expect(knative_cloud_run).to be_not_installable } + end + describe 'when rbac is not enabled' do let(:cluster) { create(:cluster, :provided_by_gcp, :rbac_disabled) } let(:knative_no_rbac) { create(:clusters_applications_knative, cluster: cluster) } diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 9afbe6328cafb3..c09814ebc95ca0 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -747,4 +747,26 @@ end end end + + describe '#knative_pre_installed?' do + subject { cluster.knative_pre_installed? } + + context 'with a GCP provider without cloud_run' do + let(:cluster) { create(:cluster, :provided_by_gcp) } + + it { is_expected.to be_falsey } + end + + context 'with a GCP provider with cloud_run' do + let(:cluster) { create(:cluster, :provided_by_gcp, :cloud_run_enabled) } + + it { is_expected.to be_truthy } + end + + context 'with a user provider' do + let(:cluster) { create(:cluster, :provided_by_user) } + + it { is_expected.to be_falsey } + end + end end diff --git a/spec/models/clusters/providers/gcp_spec.rb b/spec/models/clusters/providers/gcp_spec.rb index 785db4febe083d..7ac1bbfafd8593 100644 --- a/spec/models/clusters/providers/gcp_spec.rb +++ b/spec/models/clusters/providers/gcp_spec.rb @@ -166,6 +166,22 @@ end end + describe '#knative_pre_installed?' do + subject { gcp.knative_pre_installed? } + + context 'when cluster is cloud_run' do + let(:gcp) { create(:cluster_provider_gcp) } + + it { is_expected.to be_falsey } + end + + context 'when cluster is not cloud_run' do + let(:gcp) { create(:cluster_provider_gcp, :cloud_run_enabled) } + + it { is_expected.to be_truthy } + end + end + describe '#api_client' do subject { gcp.api_client } diff --git a/spec/services/clusters/gcp/finalize_creation_service_spec.rb b/spec/services/clusters/gcp/finalize_creation_service_spec.rb index 5f91acb8e844f8..43dbea959a2ef1 100644 --- a/spec/services/clusters/gcp/finalize_creation_service_spec.rb +++ b/spec/services/clusters/gcp/finalize_creation_service_spec.rb @@ -107,6 +107,9 @@ namespace: 'default' } ) + + stub_kubeclient_get_cluster_role_binding_error(api_url, 'gitlab-admin') + stub_kubeclient_create_cluster_role_binding(api_url) end end @@ -133,9 +136,6 @@ context 'With an RBAC cluster' do before do provider.legacy_abac = false - - stub_kubeclient_get_cluster_role_binding_error(api_url, 'gitlab-admin') - stub_kubeclient_create_cluster_role_binding(api_url) end include_context 'kubernetes information successfully fetched' @@ -152,4 +152,22 @@ it_behaves_like 'kubernetes information not successfully fetched' end + + context 'With a Cloud Run cluster' do + before do + provider.cloud_run = true + end + + include_context 'kubernetes information successfully fetched' + + it_behaves_like 'success' + + it 'has knative pre-installed' do + subject + cluster.reload + + expect(cluster.application_knative).to be_present + expect(cluster.application_knative).to be_pre_installed + end + end end diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/google_api/cloud_platform_helpers.rb index a1328ef0d13a38..38ffca8c5ae53f 100644 --- a/spec/support/google_api/cloud_platform_helpers.rb +++ b/spec/support/google_api/cloud_platform_helpers.rb @@ -65,7 +65,7 @@ def cloud_platform_get_zone_cluster_url(project_id, zone, cluster_id) end def cloud_platform_create_cluster_url(project_id, zone) - "https://container.googleapis.com/v1/projects/#{project_id}/zones/#{zone}/clusters" + "https://container.googleapis.com/v1beta1/projects/#{project_id}/zones/#{zone}/clusters" end def cloud_platform_get_zone_operation_url(project_id, zone, operation_id) diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb index 5341aacb445b20..6f06d323a82b4a 100644 --- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb @@ -11,6 +11,20 @@ end end + describe '#status_states' do + let(:cluster) { create(:cluster, :provided_by_gcp) } + + subject { described_class.new(cluster: cluster) } + + it 'returns a hash of state values' do + expect(subject.status_states).to include(:installed) + end + + it 'returns an integer for installed state value' do + expect(subject.status_states[:installed]).to eq(3) + end + end + describe '.available' do subject { described_class.available } -- GitLab