diff --git a/app/assets/images/cluster_app_logos/crossplane.png b/app/assets/images/cluster_app_logos/crossplane.png
new file mode 100644
index 00000000000..32d8175108c
Binary files /dev/null and b/app/assets/images/cluster_app_logos/crossplane.png differ
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index d471dcb1b06..75909dd9d20 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -8,7 +8,7 @@ import Flash from '../flash';
import Poll from '../lib/utils/poll';
import initSettingsPanels from '../settings_panels';
import eventHub from './event_hub';
-import { APPLICATION_STATUS, INGRESS, INGRESS_DOMAIN_SUFFIX } from './constants';
+import { APPLICATION_STATUS, INGRESS, INGRESS_DOMAIN_SUFFIX, CROSSPLANE } from './constants';
import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store';
import Applications from './components/applications.vue';
@@ -39,6 +39,7 @@ export default class Clusters {
installKnativePath,
updateKnativePath,
installElasticStackPath,
+ installCrossplanePath,
installPrometheusPath,
managePrometheusPath,
clusterEnvironmentsPath,
@@ -83,6 +84,7 @@ export default class Clusters {
installHelmEndpoint: installHelmPath,
installIngressEndpoint: installIngressPath,
installCertManagerEndpoint: installCertManagerPath,
+ installCrossplaneEndpoint: installCrossplanePath,
installRunnerEndpoint: installRunnerPath,
installPrometheusEndpoint: installPrometheusPath,
installJupyterEndpoint: installJupyterPath,
@@ -227,6 +229,7 @@ export default class Clusters {
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data));
eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
+ eventHub.$on('setCrossplaneProviderStack', data => this.setCrossplaneProviderStack(data));
// Add event listener to all the banner close buttons
this.addBannerCloseHandler(this.unreachableContainer, 'unreachable');
this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure');
@@ -238,6 +241,7 @@ export default class Clusters {
eventHub.$off('updateApplication', this.updateApplication);
eventHub.$off('saveKnativeDomain');
eventHub.$off('setKnativeHostname');
+ eventHub.$off('setCrossplaneProviderStack');
eventHub.$off('uninstallApplication');
}
@@ -404,18 +408,33 @@ export default class Clusters {
}
installApplication({ id: appId, params }) {
- this.store.updateAppProperty(appId, 'requestReason', null);
- this.store.updateAppProperty(appId, 'statusReason', null);
+ return Clusters.validateInstallation(appId, params)
+ .then(() => {
+ this.store.updateAppProperty(appId, 'requestReason', null);
+ this.store.updateAppProperty(appId, 'statusReason', null);
+ this.store.installApplication(appId);
- this.store.installApplication(appId);
+ // eslint-disable-next-line promise/no-nesting
+ this.service.installApplication(appId, params).catch(() => {
+ this.store.notifyInstallFailure(appId);
+ this.store.updateAppProperty(
+ appId,
+ 'requestReason',
+ s__('ClusterIntegration|Request to begin installing failed'),
+ );
+ });
+ })
+ .catch(error => this.store.updateAppProperty(appId, 'validationError', error));
+ }
- return this.service.installApplication(appId, params).catch(() => {
- this.store.notifyInstallFailure(appId);
- this.store.updateAppProperty(
- appId,
- 'requestReason',
- s__('ClusterIntegration|Request to begin installing failed'),
- );
+ static validateInstallation(appId, params) {
+ return new Promise((resolve, reject) => {
+ if (appId === CROSSPLANE && !params.stack) {
+ reject(s__('ClusterIntegration|Select a stack to install Crossplane.'));
+ return;
+ }
+
+ resolve();
});
}
@@ -463,6 +482,12 @@ export default class Clusters {
this.store.updateAppProperty(appId, 'hostname', data.hostname);
}
+ setCrossplaneProviderStack(data) {
+ const appId = data.id;
+ this.store.updateAppProperty(appId, 'stack', data.stack.code);
+ this.store.updateAppProperty(appId, 'validationError', null);
+ }
+
destroy() {
this.destroyed = true;
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 44d77277cc5..a951a6bfeea 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -9,6 +9,7 @@ import jeagerLogo from 'images/cluster_app_logos/jeager.png';
import jupyterhubLogo from 'images/cluster_app_logos/jupyterhub.png';
import kubernetesLogo from 'images/cluster_app_logos/kubernetes.png';
import certManagerLogo from 'images/cluster_app_logos/cert_manager.png';
+import crossplaneLogo from 'images/cluster_app_logos/crossplane.png';
import knativeLogo from 'images/cluster_app_logos/knative.png';
import meltanoLogo from 'images/cluster_app_logos/meltano.png';
import prometheusLogo from 'images/cluster_app_logos/prometheus.png';
@@ -20,6 +21,7 @@ import KnativeDomainEditor from './knative_domain_editor.vue';
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';
+import CrossplaneProviderStack from './crossplane_provider_stack.vue';
export default {
components: {
@@ -28,6 +30,7 @@ export default {
LoadingButton,
GlLoadingIcon,
KnativeDomainEditor,
+ CrossplaneProviderStack,
},
props: {
type: {
@@ -89,6 +92,7 @@ export default {
jupyterhubLogo,
kubernetesLogo,
certManagerLogo,
+ crossplaneLogo,
knativeLogo,
meltanoLogo,
prometheusLogo,
@@ -116,6 +120,12 @@ export default {
certManagerInstalled() {
return this.applications.cert_manager.status === APPLICATION_STATUS.INSTALLED;
},
+ crossplaneInstalled() {
+ return this.applications.crossplane.status === APPLICATION_STATUS.INSTALLED;
+ },
+ enableClusterApplicationCrossplane() {
+ return gon.features && gon.features.enableClusterApplicationCrossplane;
+ },
enableClusterApplicationElasticStack() {
return gon.features && gon.features.enableClusterApplicationElasticStack;
},
@@ -151,6 +161,24 @@ export default {
false,
);
},
+ crossplaneDescription() {
+ return sprintf(
+ _.escape(
+ s__(
+ `ClusterIntegration|Crossplane enables declarative provisioning of managed services from your cloud of choice using %{kubectl} or %{gitlabIntegrationLink}.
+Crossplane runs inside your Kubernetes cluster and supports secure connectivity and secrets management between app containers and the cloud services they depend on.`,
+ ),
+ ),
+ {
+ gitlabIntegrationLink: `
+ ${_.escape(s__('ClusterIntegration|Gitlab Integration'))}`,
+ kubectl: `kubectl`,
+ },
+ false,
+ );
+ },
+
prometheusDescription() {
return sprintf(
_.escape(
@@ -182,6 +210,9 @@ export default {
knative() {
return this.applications.knative;
},
+ crossplane() {
+ return this.applications.crossplane;
+ },
cloudRun() {
return this.providerType === PROVIDER_TYPE.GCP && this.preInstalledKnative;
},
@@ -218,6 +249,12 @@ export default {
hostname,
});
},
+ setCrossplaneProviderStack(stack) {
+ eventHub.$emit('setCrossplaneProviderStack', {
+ id: 'crossplane',
+ stack,
+ });
+ },
},
};
@@ -228,7 +265,7 @@ export default {
{{ s__(`ClusterIntegration|Choose which applications to install on your Kubernetes cluster. - Helm Tiller is required to install any of the following applications.`) + Helm Tiller is required to install any of the following applications.`) }} {{ __('More information') }}
@@ -253,9 +290,9 @@ export default {{{ s__(`ClusterIntegration|Point a wildcard DNS to this - generated endpoint in order to access - your application after it has been deployed.`) + generated endpoint in order to access + your application after it has been deployed.`) }} {{ __('More information') }} @@ -331,8 +368,8 @@ export default {
{{
s__(`ClusterIntegration|Replace this with your own hostname if you want.
- If you do so, point hostname to Ingress IP Address from above.`)
+ If you do so, point hostname to Ingress IP Address from above.`)
}}
{{ __('More information') }}
@@ -538,9 +603,9 @@ export default {
{{
s__(`ClusterIntegration|Knative extends Kubernetes to provide
- a set of middleware components that are essential to build modern,
- source-centric, and container-based applications that can run
- anywhere: on premises, in the cloud, or even in a third-party data center.`)
+ a set of middleware components that are essential to build modern,
+ source-centric, and container-based applications that can run
+ anywhere: on premises, in the cloud, or even in a third-party data center.`)
}}
{{
s__(`ClusterIntegration|Replace this with your own hostname if you want.
- If you do so, point hostname to Ingress IP Address from above.`)
+ If you do so, point hostname to Ingress IP Address from above.`)
}}
{{ __('More information') }}
diff --git a/app/assets/javascripts/clusters/components/crossplane_provider_stack.vue b/app/assets/javascripts/clusters/components/crossplane_provider_stack.vue
new file mode 100644
index 00000000000..966918ae636
--- /dev/null
+++ b/app/assets/javascripts/clusters/components/crossplane_provider_stack.vue
@@ -0,0 +1,93 @@
+
+
+
+
+ {{ s__(`You must select a stack for configuring your cloud provider. Learn more about`) }}
+ {{ __('Crossplane') }}
+