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