Commit f78c8e7d authored by Thong Kuah's avatar Thong Kuah

Merge branch 'Gitlab-14014-Add-Crossplane-As-Managed-App' into 'master'

.Support for crossplane as a managed app

See merge request gitlab-org/gitlab!18797
parents 98b3746e 8b163729
...@@ -8,7 +8,7 @@ import Flash from '../flash'; ...@@ -8,7 +8,7 @@ import Flash from '../flash';
import Poll from '../lib/utils/poll'; import Poll from '../lib/utils/poll';
import initSettingsPanels from '../settings_panels'; import initSettingsPanels from '../settings_panels';
import eventHub from './event_hub'; 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 ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store'; import ClustersStore from './stores/clusters_store';
import Applications from './components/applications.vue'; import Applications from './components/applications.vue';
...@@ -39,6 +39,7 @@ export default class Clusters { ...@@ -39,6 +39,7 @@ export default class Clusters {
installKnativePath, installKnativePath,
updateKnativePath, updateKnativePath,
installElasticStackPath, installElasticStackPath,
installCrossplanePath,
installPrometheusPath, installPrometheusPath,
managePrometheusPath, managePrometheusPath,
clusterEnvironmentsPath, clusterEnvironmentsPath,
...@@ -83,6 +84,7 @@ export default class Clusters { ...@@ -83,6 +84,7 @@ export default class Clusters {
installHelmEndpoint: installHelmPath, installHelmEndpoint: installHelmPath,
installIngressEndpoint: installIngressPath, installIngressEndpoint: installIngressPath,
installCertManagerEndpoint: installCertManagerPath, installCertManagerEndpoint: installCertManagerPath,
installCrossplaneEndpoint: installCrossplanePath,
installRunnerEndpoint: installRunnerPath, installRunnerEndpoint: installRunnerPath,
installPrometheusEndpoint: installPrometheusPath, installPrometheusEndpoint: installPrometheusPath,
installJupyterEndpoint: installJupyterPath, installJupyterEndpoint: installJupyterPath,
...@@ -227,6 +229,7 @@ export default class Clusters { ...@@ -227,6 +229,7 @@ export default class Clusters {
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data)); eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data)); eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data));
eventHub.$on('uninstallApplication', data => this.uninstallApplication(data)); eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
eventHub.$on('setCrossplaneProviderStack', data => this.setCrossplaneProviderStack(data));
// Add event listener to all the banner close buttons // Add event listener to all the banner close buttons
this.addBannerCloseHandler(this.unreachableContainer, 'unreachable'); this.addBannerCloseHandler(this.unreachableContainer, 'unreachable');
this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure'); this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure');
...@@ -238,6 +241,7 @@ export default class Clusters { ...@@ -238,6 +241,7 @@ export default class Clusters {
eventHub.$off('updateApplication', this.updateApplication); eventHub.$off('updateApplication', this.updateApplication);
eventHub.$off('saveKnativeDomain'); eventHub.$off('saveKnativeDomain');
eventHub.$off('setKnativeHostname'); eventHub.$off('setKnativeHostname');
eventHub.$off('setCrossplaneProviderStack');
eventHub.$off('uninstallApplication'); eventHub.$off('uninstallApplication');
} }
...@@ -404,18 +408,33 @@ export default class Clusters { ...@@ -404,18 +408,33 @@ export default class Clusters {
} }
installApplication({ id: appId, params }) { installApplication({ id: appId, params }) {
this.store.updateAppProperty(appId, 'requestReason', null); return Clusters.validateInstallation(appId, params)
this.store.updateAppProperty(appId, 'statusReason', null); .then(() => {
this.store.updateAppProperty(appId, 'requestReason', null);
this.store.updateAppProperty(appId, 'statusReason', null);
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));
}
this.store.installApplication(appId); static validateInstallation(appId, params) {
return new Promise((resolve, reject) => {
if (appId === CROSSPLANE && !params.stack) {
reject(s__('ClusterIntegration|Select a stack to install Crossplane.'));
return;
}
return this.service.installApplication(appId, params).catch(() => { resolve();
this.store.notifyInstallFailure(appId);
this.store.updateAppProperty(
appId,
'requestReason',
s__('ClusterIntegration|Request to begin installing failed'),
);
}); });
} }
...@@ -463,6 +482,12 @@ export default class Clusters { ...@@ -463,6 +482,12 @@ export default class Clusters {
this.store.updateAppProperty(appId, 'hostname', data.hostname); 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() { destroy() {
this.destroyed = true; this.destroyed = true;
......
...@@ -9,6 +9,7 @@ import jeagerLogo from 'images/cluster_app_logos/jeager.png'; ...@@ -9,6 +9,7 @@ import jeagerLogo from 'images/cluster_app_logos/jeager.png';
import jupyterhubLogo from 'images/cluster_app_logos/jupyterhub.png'; import jupyterhubLogo from 'images/cluster_app_logos/jupyterhub.png';
import kubernetesLogo from 'images/cluster_app_logos/kubernetes.png'; import kubernetesLogo from 'images/cluster_app_logos/kubernetes.png';
import certManagerLogo from 'images/cluster_app_logos/cert_manager.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 knativeLogo from 'images/cluster_app_logos/knative.png';
import meltanoLogo from 'images/cluster_app_logos/meltano.png'; import meltanoLogo from 'images/cluster_app_logos/meltano.png';
import prometheusLogo from 'images/cluster_app_logos/prometheus.png'; import prometheusLogo from 'images/cluster_app_logos/prometheus.png';
...@@ -20,6 +21,7 @@ import KnativeDomainEditor from './knative_domain_editor.vue'; ...@@ -20,6 +21,7 @@ import KnativeDomainEditor from './knative_domain_editor.vue';
import { CLUSTER_TYPE, PROVIDER_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 LoadingButton from '~/vue_shared/components/loading_button.vue';
import eventHub from '~/clusters/event_hub'; import eventHub from '~/clusters/event_hub';
import CrossplaneProviderStack from './crossplane_provider_stack.vue';
export default { export default {
components: { components: {
...@@ -28,6 +30,7 @@ export default { ...@@ -28,6 +30,7 @@ export default {
LoadingButton, LoadingButton,
GlLoadingIcon, GlLoadingIcon,
KnativeDomainEditor, KnativeDomainEditor,
CrossplaneProviderStack,
}, },
props: { props: {
type: { type: {
...@@ -89,6 +92,7 @@ export default { ...@@ -89,6 +92,7 @@ export default {
jupyterhubLogo, jupyterhubLogo,
kubernetesLogo, kubernetesLogo,
certManagerLogo, certManagerLogo,
crossplaneLogo,
knativeLogo, knativeLogo,
meltanoLogo, meltanoLogo,
prometheusLogo, prometheusLogo,
...@@ -116,6 +120,12 @@ export default { ...@@ -116,6 +120,12 @@ export default {
certManagerInstalled() { certManagerInstalled() {
return this.applications.cert_manager.status === APPLICATION_STATUS.INSTALLED; 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() { enableClusterApplicationElasticStack() {
return gon.features && gon.features.enableClusterApplicationElasticStack; return gon.features && gon.features.enableClusterApplicationElasticStack;
}, },
...@@ -151,6 +161,24 @@ export default { ...@@ -151,6 +161,24 @@ export default {
false, 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: `<a href="https://docs.gitlab.com/ce/user/project/integrations/crossplane.html"
target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|Gitlab Integration'))}</a>`,
kubectl: `<code>kubectl</code>`,
},
false,
);
},
prometheusDescription() { prometheusDescription() {
return sprintf( return sprintf(
_.escape( _.escape(
...@@ -182,6 +210,9 @@ export default { ...@@ -182,6 +210,9 @@ export default {
knative() { knative() {
return this.applications.knative; return this.applications.knative;
}, },
crossplane() {
return this.applications.crossplane;
},
cloudRun() { cloudRun() {
return this.providerType === PROVIDER_TYPE.GCP && this.preInstalledKnative; return this.providerType === PROVIDER_TYPE.GCP && this.preInstalledKnative;
}, },
...@@ -218,6 +249,12 @@ export default { ...@@ -218,6 +249,12 @@ export default {
hostname, hostname,
}); });
}, },
setCrossplaneProviderStack(stack) {
eventHub.$emit('setCrossplaneProviderStack', {
id: 'crossplane',
stack,
});
},
}, },
}; };
</script> </script>
...@@ -228,7 +265,7 @@ export default { ...@@ -228,7 +265,7 @@ export default {
<p class="append-bottom-0"> <p class="append-bottom-0">
{{ {{
s__(`ClusterIntegration|Choose which applications to install on your Kubernetes cluster. 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.`)
}} }}
<a :href="helpPath">{{ __('More information') }}</a> <a :href="helpPath">{{ __('More information') }}</a>
</p> </p>
...@@ -253,9 +290,9 @@ export default { ...@@ -253,9 +290,9 @@ export default {
<div slot="description"> <div slot="description">
{{ {{
s__(`ClusterIntegration|Helm streamlines installing s__(`ClusterIntegration|Helm streamlines installing
and managing Kubernetes applications. and managing Kubernetes applications.
Tiller runs inside of your Kubernetes Cluster, Tiller runs inside of your Kubernetes Cluster,
and manages releases of your charts.`) and manages releases of your charts.`)
}} }}
</div> </div>
</application-row> </application-row>
...@@ -263,7 +300,7 @@ export default { ...@@ -263,7 +300,7 @@ export default {
<div class="svg-container" v-html="helmInstallIllustration"></div> <div class="svg-container" v-html="helmInstallIllustration"></div>
{{ {{
s__(`ClusterIntegration|You must first install Helm Tiller before s__(`ClusterIntegration|You must first install Helm Tiller before
installing the applications below`) installing the applications below`)
}} }}
</div> </div>
<application-row <application-row
...@@ -286,8 +323,8 @@ export default { ...@@ -286,8 +323,8 @@ export default {
<p> <p>
{{ {{
s__(`ClusterIntegration|Ingress gives you a way to route s__(`ClusterIntegration|Ingress gives you a way to route
requests to services based on the request host or path, requests to services based on the request host or path,
centralizing a number of services into a single entrypoint.`) centralizing a number of services into a single entrypoint.`)
}} }}
</p> </p>
...@@ -319,8 +356,8 @@ export default { ...@@ -319,8 +356,8 @@ export default {
<p class="form-text text-muted"> <p class="form-text text-muted">
{{ {{
s__(`ClusterIntegration|Point a wildcard DNS to this s__(`ClusterIntegration|Point a wildcard DNS to this
generated endpoint in order to access generated endpoint in order to access
your application after it has been deployed.`) your application after it has been deployed.`)
}} }}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer"> <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }} {{ __('More information') }}
...@@ -331,8 +368,8 @@ export default { ...@@ -331,8 +368,8 @@ export default {
<p v-if="!ingressExternalEndpoint" class="settings-message js-no-endpoint-message"> <p v-if="!ingressExternalEndpoint" class="settings-message js-no-endpoint-message">
{{ {{
s__(`ClusterIntegration|The endpoint is in s__(`ClusterIntegration|The endpoint is in
the process of being assigned. Please check your Kubernetes the process of being assigned. Please check your Kubernetes
cluster or Quotas on Google Kubernetes Engine if it takes a long time.`) cluster or Quotas on Google Kubernetes Engine if it takes a long time.`)
}} }}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer"> <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }} {{ __('More information') }}
...@@ -379,7 +416,7 @@ export default { ...@@ -379,7 +416,7 @@ export default {
<p class="form-text text-muted"> <p class="form-text text-muted">
{{ {{
s__(`ClusterIntegration|Issuers represent a certificate authority. s__(`ClusterIntegration|Issuers represent a certificate authority.
You must provide an email address for your Issuer. `) You must provide an email address for your Issuer. `)
}} }}
<a <a
href="http://docs.cert-manager.io/en/latest/reference/issuers.html?highlight=email" href="http://docs.cert-manager.io/en/latest/reference/issuers.html?highlight=email"
...@@ -435,12 +472,40 @@ export default { ...@@ -435,12 +472,40 @@ export default {
<div slot="description"> <div slot="description">
{{ {{
s__(`ClusterIntegration|GitLab Runner connects to the s__(`ClusterIntegration|GitLab Runner connects to the
repository and executes CI/CD jobs, repository and executes CI/CD jobs,
pushing results back and deploying pushing results back and deploying
applications to production.`) applications to production.`)
}} }}
</div> </div>
</application-row> </application-row>
<application-row
v-if="enableClusterApplicationCrossplane"
id="crossplane"
:logo-url="crossplaneLogo"
:title="applications.crossplane.title"
:status="applications.crossplane.status"
:status-reason="applications.crossplane.statusReason"
:request-status="applications.crossplane.requestStatus"
:request-reason="applications.crossplane.requestReason"
:installed="applications.crossplane.installed"
:install-failed="applications.crossplane.installFailed"
:uninstallable="applications.crossplane.uninstallable"
:uninstall-successful="applications.crossplane.uninstallSuccessful"
:uninstall-failed="applications.crossplane.uninstallFailed"
:install-application-request-params="{ stack: applications.crossplane.stack }"
:disabled="!helmInstalled"
title-link="https://crossplane.io"
>
<template>
<div slot="description">
<p v-html="crossplaneDescription"></p>
<div class="form-group">
<CrossplaneProviderStack :crossplane="crossplane" @set="setCrossplaneProviderStack" />
</div>
</div>
</template>
</application-row>
<application-row <application-row
id="jupyter" id="jupyter"
:logo-url="jupyterhubLogo" :logo-url="jupyterhubLogo"
...@@ -462,10 +527,10 @@ export default { ...@@ -462,10 +527,10 @@ export default {
<p> <p>
{{ {{
s__(`ClusterIntegration|JupyterHub, a multi-user Hub, spawns, s__(`ClusterIntegration|JupyterHub, a multi-user Hub, spawns,
manages, and proxies multiple instances of the single-user manages, and proxies multiple instances of the single-user
Jupyter notebook server. JupyterHub can be used to serve Jupyter notebook server. JupyterHub can be used to serve
notebooks to a class of students, a corporate data science group, notebooks to a class of students, a corporate data science group,
or a scientific research group.`) or a scientific research group.`)
}} }}
</p> </p>
...@@ -492,7 +557,7 @@ export default { ...@@ -492,7 +557,7 @@ export default {
<p v-if="ingressInstalled" class="form-text text-muted"> <p v-if="ingressInstalled" class="form-text text-muted">
{{ {{
s__(`ClusterIntegration|Replace this with your own hostname if you want. 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.`)
}} }}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer"> <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }} {{ __('More information') }}
...@@ -538,9 +603,9 @@ export default { ...@@ -538,9 +603,9 @@ export default {
<p> <p>
{{ {{
s__(`ClusterIntegration|Knative extends Kubernetes to provide s__(`ClusterIntegration|Knative extends Kubernetes to provide
a set of middleware components that are essential to build modern, a set of middleware components that are essential to build modern,
source-centric, and container-based applications that can run source-centric, and container-based applications that can run
anywhere: on premises, in the cloud, or even in a third-party data center.`) anywhere: on premises, in the cloud, or even in a third-party data center.`)
}} }}
</p> </p>
...@@ -612,7 +677,7 @@ export default { ...@@ -612,7 +677,7 @@ export default {
<p v-if="ingressInstalled" class="form-text text-muted"> <p v-if="ingressInstalled" class="form-text text-muted">
{{ {{
s__(`ClusterIntegration|Replace this with your own hostname if you want. 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.`)
}} }}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer"> <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }} {{ __('More information') }}
......
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import { s__ } from '../../locale';
export default {
name: 'CrossplaneProviderStack',
components: {
GlDropdown,
GlDropdownItem,
Icon,
},
props: {
stacks: {
type: Array,
required: false,
default: () => [
{
name: s__('Google Cloud Platform'),
code: 'gcp',
},
{
name: s__('Amazon Web Services'),
code: 'aws',
},
{
name: s__('Microsoft Azure'),
code: 'azure',
},
{
name: s__('Rook'),
code: 'rook',
},
],
},
crossplane: {
type: Object,
required: true,
},
},
computed: {
dropdownText() {
const result = this.stacks.reduce((map, obj) => {
// eslint-disable-next-line no-param-reassign
map[obj.code] = obj.name;
return map;
}, {});
const { stack } = this.crossplane;
if (stack !== '') {
return result[stack];
}
return s__('Select Stack');
},
validationError() {
return this.crossplane.validationError;
},
},
methods: {
selectStack(stack) {
this.$emit('set', stack);
},
},
};
</script>
<template>
<div>
<label>
{{ s__('ClusterIntegration|Enabled stack') }}
</label>
<gl-dropdown
:disabled="crossplane.installed"
:text="dropdownText"
toggle-class="dropdown-menu-toggle gl-field-error-outline"
class="w-100"
:class="{ 'gl-show-field-errors': validationError }"
>
<gl-dropdown-item v-for="stack in stacks" :key="stack.code" @click="selectStack(stack)">
<span class="ml-1">{{ stack.name }}</span>
</gl-dropdown-item>
</gl-dropdown>
<span v-if="validationError" class="gl-field-error">{{ validationError }}</span>
<p class="form-text text-muted">
{{ s__(`You must select a stack for configuring your cloud provider. Learn more about`) }}
<a
href="https://crossplane.io/docs/master/stacks-guide.html"
target="_blank"
rel="noopener noreferrer"
>{{ __('Crossplane') }}</a
>
</p>
</div>
</template>
...@@ -50,6 +50,7 @@ export const JUPYTER = 'jupyter'; ...@@ -50,6 +50,7 @@ export const JUPYTER = 'jupyter';
export const KNATIVE = 'knative'; export const KNATIVE = 'knative';
export const RUNNER = 'runner'; export const RUNNER = 'runner';
export const CERT_MANAGER = 'cert_manager'; export const CERT_MANAGER = 'cert_manager';
export const CROSSPLANE = 'crossplane';
export const PROMETHEUS = 'prometheus'; export const PROMETHEUS = 'prometheus';
export const ELASTIC_STACK = 'elastic_stack'; export const ELASTIC_STACK = 'elastic_stack';
......
...@@ -7,6 +7,7 @@ export default class ClusterService { ...@@ -7,6 +7,7 @@ export default class ClusterService {
helm: this.options.installHelmEndpoint, helm: this.options.installHelmEndpoint,
ingress: this.options.installIngressEndpoint, ingress: this.options.installIngressEndpoint,
cert_manager: this.options.installCertManagerEndpoint, cert_manager: this.options.installCertManagerEndpoint,
crossplane: this.options.installCrossplaneEndpoint,
runner: this.options.installRunnerEndpoint, runner: this.options.installRunnerEndpoint,
prometheus: this.options.installPrometheusEndpoint, prometheus: this.options.installPrometheusEndpoint,
jupyter: this.options.installJupyterEndpoint, jupyter: this.options.installJupyterEndpoint,
......
...@@ -6,6 +6,7 @@ import { ...@@ -6,6 +6,7 @@ import {
KNATIVE, KNATIVE,
CERT_MANAGER, CERT_MANAGER,
ELASTIC_STACK, ELASTIC_STACK,
CROSSPLANE,
RUNNER, RUNNER,
APPLICATION_INSTALLED_STATUSES, APPLICATION_INSTALLED_STATUSES,
APPLICATION_STATUS, APPLICATION_STATUS,
...@@ -26,6 +27,7 @@ const applicationInitialState = { ...@@ -26,6 +27,7 @@ const applicationInitialState = {
uninstallable: false, uninstallable: false,
uninstallFailed: false, uninstallFailed: false,
uninstallSuccessful: false, uninstallSuccessful: false,
validationError: null,
}; };
export default class ClusterStore { export default class ClusterStore {
...@@ -58,6 +60,11 @@ export default class ClusterStore { ...@@ -58,6 +60,11 @@ export default class ClusterStore {
title: s__('ClusterIntegration|Cert-Manager'), title: s__('ClusterIntegration|Cert-Manager'),
email: null, email: null,
}, },
crossplane: {
...applicationInitialState,
title: s__('ClusterIntegration|Crossplane'),
stack: null,
},
runner: { runner: {
...applicationInitialState, ...applicationInitialState,
title: s__('ClusterIntegration|GitLab Runner'), title: s__('ClusterIntegration|GitLab Runner'),
...@@ -203,6 +210,9 @@ export default class ClusterStore { ...@@ -203,6 +210,9 @@ export default class ClusterStore {
} else if (appId === CERT_MANAGER) { } else if (appId === CERT_MANAGER) {
this.state.applications.cert_manager.email = this.state.applications.cert_manager.email =
this.state.applications.cert_manager.email || serverAppEntry.email; this.state.applications.cert_manager.email || serverAppEntry.email;
} else if (appId === CROSSPLANE) {
this.state.applications.crossplane.stack =
this.state.applications.crossplane.stack || serverAppEntry.stack;
} else if (appId === JUPYTER) { } else if (appId === JUPYTER) {
this.state.applications.jupyter.hostname = this.updateHostnameIfUnset( this.state.applications.jupyter.hostname = this.updateHostnameIfUnset(
this.state.applications.jupyter.hostname, this.state.applications.jupyter.hostname,
......
...@@ -47,7 +47,7 @@ class Clusters::ApplicationsController < Clusters::BaseController ...@@ -47,7 +47,7 @@ class Clusters::ApplicationsController < Clusters::BaseController
end end
def cluster_application_params def cluster_application_params
params.permit(:application, :hostname, :kibana_hostname, :email) params.permit(:application, :hostname, :kibana_hostname, :email, :stack)
end end
def cluster_application_destroy_params def cluster_application_destroy_params
......
...@@ -17,6 +17,7 @@ class Clusters::ClustersController < Clusters::BaseController ...@@ -17,6 +17,7 @@ class Clusters::ClustersController < Clusters::BaseController
end end
before_action only: [:show] do before_action only: [:show] do
push_frontend_feature_flag(:enable_cluster_application_elastic_stack) push_frontend_feature_flag(:enable_cluster_application_elastic_stack)
push_frontend_feature_flag(:enable_cluster_application_crossplane)
end end
helper_method :token_in_session helper_method :token_in_session
......
# frozen_string_literal: true
module Clusters
module Applications
class Crossplane < ApplicationRecord
VERSION = '0.4.1'
self.table_name = 'clusters_applications_crossplane'
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
include ::Clusters::Concerns::ApplicationVersion
include ::Clusters::Concerns::ApplicationData
default_value_for :version, VERSION
default_value_for :stack do |crossplane|
''
end
validates :stack, presence: true
def chart
'crossplane/crossplane'
end
def repository
'https://charts.crossplane.io/alpha'
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: 'crossplane',
repository: repository,
version: VERSION,
rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files
)
end
def values
crossplane_values.to_yaml
end
private
def crossplane_values
{
"clusterStacks" => {
self.stack => {
"deploy" => true,
"version" => "alpha"
}
}
}
end
end
end
end
...@@ -14,6 +14,7 @@ module Clusters ...@@ -14,6 +14,7 @@ module Clusters
Applications::Helm.application_name => Applications::Helm, Applications::Helm.application_name => Applications::Helm,
Applications::Ingress.application_name => Applications::Ingress, Applications::Ingress.application_name => Applications::Ingress,
Applications::CertManager.application_name => Applications::CertManager, Applications::CertManager.application_name => Applications::CertManager,
Applications::Crossplane.application_name => Applications::Crossplane,
Applications::Prometheus.application_name => Applications::Prometheus, Applications::Prometheus.application_name => Applications::Prometheus,
Applications::Runner.application_name => Applications::Runner, Applications::Runner.application_name => Applications::Runner,
Applications::Jupyter.application_name => Applications::Jupyter, Applications::Jupyter.application_name => Applications::Jupyter,
...@@ -47,6 +48,7 @@ module Clusters ...@@ -47,6 +48,7 @@ module Clusters
has_one_cluster_application :helm has_one_cluster_application :helm
has_one_cluster_application :ingress has_one_cluster_application :ingress
has_one_cluster_application :cert_manager has_one_cluster_application :cert_manager
has_one_cluster_application :crossplane
has_one_cluster_application :prometheus has_one_cluster_application :prometheus
has_one_cluster_application :runner has_one_cluster_application :runner
has_one_cluster_application :jupyter has_one_cluster_application :jupyter
......
...@@ -10,6 +10,7 @@ class ClusterApplicationEntity < Grape::Entity ...@@ -10,6 +10,7 @@ class ClusterApplicationEntity < Grape::Entity
expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) } expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) }
expose :kibana_hostname, if: -> (e, _) { e.respond_to?(:kibana_hostname) } expose :kibana_hostname, if: -> (e, _) { e.respond_to?(:kibana_hostname) }
expose :email, if: -> (e, _) { e.respond_to?(:email) } expose :email, if: -> (e, _) { e.respond_to?(:email) }
expose :stack, if: -> (e, _) { e.respond_to?(:stack) }
expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) } expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) }
expose :can_uninstall?, as: :can_uninstall expose :can_uninstall?, as: :can_uninstall
end end
...@@ -27,6 +27,10 @@ module Clusters ...@@ -27,6 +27,10 @@ module Clusters
application.email = params[:email] application.email = params[:email]
end end
if application.has_attribute?(:stack)
application.stack = params[:stack]
end
if application.respond_to?(:oauth_application) if application.respond_to?(:oauth_application)
application.oauth_application = create_oauth_application(application, request) application.oauth_application = create_oauth_application(application, request)
end end
...@@ -64,7 +68,7 @@ module Clusters ...@@ -64,7 +68,7 @@ module Clusters
end end
def invalid_application? def invalid_application?
unknown_application? || (application_name == Applications::ElasticStack.application_name && !Feature.enabled?(:enable_cluster_application_elastic_stack)) unknown_application? || (application_name == Applications::ElasticStack.application_name && !Feature.enabled?(:enable_cluster_application_elastic_stack)) || (application_name == Applications::Crossplane.application_name && !Feature.enabled?(:enable_cluster_application_crossplane))
end end
def unknown_application? def unknown_application?
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
install_helm_path: clusterable.install_applications_cluster_path(@cluster, :helm), install_helm_path: clusterable.install_applications_cluster_path(@cluster, :helm),
install_ingress_path: clusterable.install_applications_cluster_path(@cluster, :ingress), install_ingress_path: clusterable.install_applications_cluster_path(@cluster, :ingress),
install_cert_manager_path: clusterable.install_applications_cluster_path(@cluster, :cert_manager), install_cert_manager_path: clusterable.install_applications_cluster_path(@cluster, :cert_manager),
install_crossplane_path: clusterable.install_applications_cluster_path(@cluster, :crossplane),
install_prometheus_path: clusterable.install_applications_cluster_path(@cluster, :prometheus), install_prometheus_path: clusterable.install_applications_cluster_path(@cluster, :prometheus),
install_runner_path: clusterable.install_applications_cluster_path(@cluster, :runner), install_runner_path: clusterable.install_applications_cluster_path(@cluster, :runner),
install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter), install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter),
......
---
title: Support for Crossplane as a managed app
merge_request: 18797
author: Mahendra Bagul
type: added
# frozen_string_literal: true
class CreateClustersApplicationsCrossplane < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :clusters_applications_crossplane do |t|
t.timestamps_with_timezone null: false
t.references :cluster, null: false, index: false, foreign_key: { on_delete: :cascade }
t.integer :status, null: false
t.string :version, null: false, limit: 255
t.string :stack, null: false, limit: 255
t.text :status_reason
t.index :cluster_id, unique: true
end
end
end
...@@ -1078,6 +1078,17 @@ ActiveRecord::Schema.define(version: 2019_11_14_173624) do ...@@ -1078,6 +1078,17 @@ ActiveRecord::Schema.define(version: 2019_11_14_173624) do
t.index ["cluster_id"], name: "index_clusters_applications_cert_managers_on_cluster_id", unique: true t.index ["cluster_id"], name: "index_clusters_applications_cert_managers_on_cluster_id", unique: true
end end
create_table "clusters_applications_crossplane", id: :serial, force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.bigint "cluster_id", null: false
t.integer "status", null: false
t.string "version", limit: 255, null: false
t.string "stack", limit: 255, null: false
t.text "status_reason"
t.index ["cluster_id"], name: "index_clusters_applications_crossplane_on_cluster_id", unique: true
end
create_table "clusters_applications_elastic_stacks", force: :cascade do |t| create_table "clusters_applications_elastic_stacks", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "updated_at", null: false
...@@ -4222,6 +4233,7 @@ ActiveRecord::Schema.define(version: 2019_11_14_173624) do ...@@ -4222,6 +4233,7 @@ ActiveRecord::Schema.define(version: 2019_11_14_173624) do
add_foreign_key "clusters", "projects", column: "management_project_id", name: "fk_f05c5e5a42", on_delete: :nullify add_foreign_key "clusters", "projects", column: "management_project_id", name: "fk_f05c5e5a42", on_delete: :nullify
add_foreign_key "clusters", "users", on_delete: :nullify add_foreign_key "clusters", "users", on_delete: :nullify
add_foreign_key "clusters_applications_cert_managers", "clusters", on_delete: :cascade add_foreign_key "clusters_applications_cert_managers", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_crossplane", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_elastic_stacks", "clusters", on_delete: :cascade add_foreign_key "clusters_applications_elastic_stacks", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_helm", "clusters", on_delete: :cascade add_foreign_key "clusters_applications_helm", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_ingress", "clusters", on_delete: :cascade add_foreign_key "clusters_applications_ingress", "clusters", on_delete: :cascade
......
...@@ -73,6 +73,7 @@ module Gitlab ...@@ -73,6 +73,7 @@ module Gitlab
clusters_applications_helm: count(::Clusters::Applications::Helm.available), clusters_applications_helm: count(::Clusters::Applications::Helm.available),
clusters_applications_ingress: count(::Clusters::Applications::Ingress.available), clusters_applications_ingress: count(::Clusters::Applications::Ingress.available),
clusters_applications_cert_managers: count(::Clusters::Applications::CertManager.available), clusters_applications_cert_managers: count(::Clusters::Applications::CertManager.available),
clusters_applications_crossplane: count(::Clusters::Applications::Crossplane.available),
clusters_applications_prometheus: count(::Clusters::Applications::Prometheus.available), clusters_applications_prometheus: count(::Clusters::Applications::Prometheus.available),
clusters_applications_runner: count(::Clusters::Applications::Runner.available), clusters_applications_runner: count(::Clusters::Applications::Runner.available),
clusters_applications_knative: count(::Clusters::Applications::Knative.available), clusters_applications_knative: count(::Clusters::Applications::Knative.available),
......
...@@ -1495,6 +1495,9 @@ msgstr "" ...@@ -1495,6 +1495,9 @@ msgstr ""
msgid "Amazon EKS integration allows you to provision EKS clusters from GitLab." msgid "Amazon EKS integration allows you to provision EKS clusters from GitLab."
msgstr "" msgstr ""
msgid "Amazon Web Services"
msgstr ""
msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service." msgid "Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr "" msgstr ""
...@@ -3646,6 +3649,12 @@ msgstr "" ...@@ -3646,6 +3649,12 @@ msgstr ""
msgid "ClusterIntegration|Creating Kubernetes cluster" msgid "ClusterIntegration|Creating Kubernetes cluster"
msgstr "" msgstr ""
msgid "ClusterIntegration|Crossplane"
msgstr ""
msgid "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."
msgstr ""
msgid "ClusterIntegration|Did you know?" msgid "ClusterIntegration|Did you know?"
msgstr "" msgstr ""
...@@ -3661,6 +3670,9 @@ msgstr "" ...@@ -3661,6 +3670,9 @@ msgstr ""
msgid "ClusterIntegration|Enable this setting if using role-based access control (RBAC)." msgid "ClusterIntegration|Enable this setting if using role-based access control (RBAC)."
msgstr "" msgstr ""
msgid "ClusterIntegration|Enabled stack"
msgstr ""
msgid "ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster" msgid "ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster"
msgstr "" msgstr ""
...@@ -3709,6 +3721,9 @@ msgstr "" ...@@ -3709,6 +3721,9 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster" msgid "ClusterIntegration|GitLab-managed cluster"
msgstr "" msgstr ""
msgid "ClusterIntegration|Gitlab Integration"
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project" msgid "ClusterIntegration|Google Cloud Platform project"
msgstr "" msgstr ""
...@@ -4030,6 +4045,9 @@ msgstr "" ...@@ -4030,6 +4045,9 @@ msgstr ""
msgid "ClusterIntegration|Select a region to choose a VPC" msgid "ClusterIntegration|Select a region to choose a VPC"
msgstr "" msgstr ""
msgid "ClusterIntegration|Select a stack to install Crossplane."
msgstr ""
msgid "ClusterIntegration|Select machine type" msgid "ClusterIntegration|Select machine type"
msgstr "" msgstr ""
...@@ -4982,6 +5000,9 @@ msgstr "" ...@@ -4982,6 +5000,9 @@ msgstr ""
msgid "Cron syntax" msgid "Cron syntax"
msgstr "" msgstr ""
msgid "Crossplane"
msgstr ""
msgid "Current Branch" msgid "Current Branch"
msgstr "" msgstr ""
...@@ -8442,6 +8463,9 @@ msgstr "" ...@@ -8442,6 +8463,9 @@ msgstr ""
msgid "Golden Tanuki" msgid "Golden Tanuki"
msgstr "" msgstr ""
msgid "Google Cloud Platform"
msgstr ""
msgid "Google Code import" msgid "Google Code import"
msgstr "" msgstr ""
...@@ -10885,6 +10909,9 @@ msgstr "" ...@@ -10885,6 +10909,9 @@ msgstr ""
msgid "Metrics|e.g. req/sec" msgid "Metrics|e.g. req/sec"
msgstr "" msgstr ""
msgid "Microsoft Azure"
msgstr ""
msgid "Migrated %{success_count}/%{total_count} files." msgid "Migrated %{success_count}/%{total_count} files."
msgstr "" msgstr ""
...@@ -14669,6 +14696,9 @@ msgstr "" ...@@ -14669,6 +14696,9 @@ msgstr ""
msgid "Rollback" msgid "Rollback"
msgstr "" msgstr ""
msgid "Rook"
msgstr ""
msgid "Run CI/CD pipelines for external repositories" msgid "Run CI/CD pipelines for external repositories"
msgstr "" msgstr ""
...@@ -15243,6 +15273,9 @@ msgstr "" ...@@ -15243,6 +15273,9 @@ msgstr ""
msgid "Select Page" msgid "Select Page"
msgstr "" msgstr ""
msgid "Select Stack"
msgstr ""
msgid "Select a file from the left sidebar to begin editing. Afterwards, you'll be able to commit your changes." msgid "Select a file from the left sidebar to begin editing. Afterwards, you'll be able to commit your changes."
msgstr "" msgstr ""
...@@ -19854,6 +19887,9 @@ msgstr "" ...@@ -19854,6 +19887,9 @@ msgstr ""
msgid "You must provide your current password in order to change it." msgid "You must provide your current password in order to change it."
msgstr "" msgstr ""
msgid "You must select a stack for configuring your cloud provider. Learn more about"
msgstr ""
msgid "You need a different license to enable FileLocks feature" msgid "You need a different license to enable FileLocks feature"
msgstr "" msgstr ""
......
...@@ -83,6 +83,11 @@ FactoryBot.define do ...@@ -83,6 +83,11 @@ FactoryBot.define do
cluster factory: %i(cluster with_installed_helm provided_by_gcp) cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end end
factory :clusters_applications_crossplane, class: Clusters::Applications::Crossplane do
stack { 'gcp' }
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
factory :clusters_applications_prometheus, class: Clusters::Applications::Prometheus do factory :clusters_applications_prometheus, class: Clusters::Applications::Prometheus do
cluster factory: %i(cluster with_installed_helm provided_by_gcp) cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end end
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
"hostname": { "type": ["string", "null"] }, "hostname": { "type": ["string", "null"] },
"kibana_hostname": { "type": ["string", "null"] }, "kibana_hostname": { "type": ["string", "null"] },
"email": { "type": ["string", "null"] }, "email": { "type": ["string", "null"] },
"stack": { "type": ["string", "null"] },
"update_available": { "type": ["boolean", "null"] }, "update_available": { "type": ["boolean", "null"] },
"can_uninstall": { "type": "boolean" } "can_uninstall": { "type": "boolean" }
}, },
......
...@@ -286,16 +286,21 @@ describe('Clusters', () => { ...@@ -286,16 +286,21 @@ describe('Clusters', () => {
}); });
describe('installApplication', () => { describe('installApplication', () => {
it.each(APPLICATIONS)('tries to install %s', applicationId => { it.each(APPLICATIONS)('tries to install %s', (applicationId, done) => {
jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); jest.spyOn(cluster.service, 'installApplication').mockResolvedValue();
cluster.store.state.applications[applicationId].status = INSTALLABLE; cluster.store.state.applications[applicationId].status = INSTALLABLE;
cluster.installApplication({ id: applicationId }); // eslint-disable-next-line promise/valid-params
cluster
expect(cluster.store.state.applications[applicationId].status).toEqual(INSTALLING); .installApplication({ id: applicationId })
expect(cluster.store.state.applications[applicationId].requestReason).toEqual(null); .then(() => {
expect(cluster.service.installApplication).toHaveBeenCalledWith(applicationId, undefined); expect(cluster.store.state.applications[applicationId].status).toEqual(INSTALLING);
expect(cluster.store.state.applications[applicationId].requestReason).toEqual(null);
expect(cluster.service.installApplication).toHaveBeenCalledWith(applicationId, undefined);
done();
})
.catch();
}); });
it('sets error request status when the request fails', () => { it('sets error request status when the request fails', () => {
......
...@@ -6,6 +6,7 @@ import { APPLICATIONS_MOCK_STATE } from '../services/mock_data'; ...@@ -6,6 +6,7 @@ import { APPLICATIONS_MOCK_STATE } from '../services/mock_data';
import eventHub from '~/clusters/event_hub'; import eventHub from '~/clusters/event_hub';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue'; import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue';
import CrossplaneProviderStack from '~/clusters/components/crossplane_provider_stack.vue';
describe('Applications', () => { describe('Applications', () => {
let vm; let vm;
...@@ -16,6 +17,7 @@ describe('Applications', () => { ...@@ -16,6 +17,7 @@ describe('Applications', () => {
gon.features = gon.features || {}; gon.features = gon.features || {};
gon.features.enableClusterApplicationElasticStack = true; gon.features.enableClusterApplicationElasticStack = true;
gon.features.enableClusterApplicationCrossplane = true;
}); });
afterEach(() => { afterEach(() => {
...@@ -42,6 +44,10 @@ describe('Applications', () => { ...@@ -42,6 +44,10 @@ describe('Applications', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull(); expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull();
}); });
it('renders a row for Crossplane', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-crossplane')).not.toBeNull();
});
it('renders a row for Prometheus', () => { it('renders a row for Prometheus', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull(); expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull();
}); });
...@@ -83,6 +89,10 @@ describe('Applications', () => { ...@@ -83,6 +89,10 @@ describe('Applications', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull(); expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull();
}); });
it('renders a row for Crossplane', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-crossplane')).not.toBeNull();
});
it('renders a row for Prometheus', () => { it('renders a row for Prometheus', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull(); expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull();
}); });
...@@ -124,6 +134,10 @@ describe('Applications', () => { ...@@ -124,6 +134,10 @@ describe('Applications', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull(); expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull();
}); });
it('renders a row for Crossplane', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-crossplane')).not.toBeNull();
});
it('renders a row for Prometheus', () => { it('renders a row for Prometheus', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull(); expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull();
}); });
...@@ -179,6 +193,7 @@ describe('Applications', () => { ...@@ -179,6 +193,7 @@ describe('Applications', () => {
}, },
helm: { title: 'Helm Tiller' }, helm: { title: 'Helm Tiller' },
cert_manager: { title: 'Cert-Manager' }, cert_manager: { title: 'Cert-Manager' },
crossplane: { title: 'Crossplane', stack: '' },
runner: { title: 'GitLab Runner' }, runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' }, prometheus: { title: 'Prometheus' },
jupyter: { title: 'JupyterHub', hostname: '' }, jupyter: { title: 'JupyterHub', hostname: '' },
...@@ -390,6 +405,32 @@ describe('Applications', () => { ...@@ -390,6 +405,32 @@ describe('Applications', () => {
}); });
}); });
describe('Crossplane application', () => {
const propsData = {
applications: {
...APPLICATIONS_MOCK_STATE,
crossplane: {
title: 'Crossplane',
stack: {
code: '',
},
},
},
};
let wrapper;
beforeEach(() => {
wrapper = shallowMount(Applications, { propsData });
});
afterEach(() => {
wrapper.destroy();
});
it('renders the correct Component', () => {
const crossplane = wrapper.find(CrossplaneProviderStack);
expect(crossplane.exists()).toBe(true);
});
});
describe('Elastic Stack application', () => { describe('Elastic Stack application', () => {
describe('with ingress installed with ip & elastic stack installable', () => { describe('with ingress installed with ip & elastic stack installable', () => {
it('renders hostname active input', () => { it('renders hostname active input', () => {
......
import { shallowMount } from '@vue/test-utils';
import { GlDropdownItem } from '@gitlab/ui';
import CrossplaneProviderStack from '~/clusters/components/crossplane_provider_stack.vue';
describe('CrossplaneProviderStack component', () => {
let wrapper;
const defaultProps = {
stacks: [
{
name: 'Google Cloud Platform',
code: 'gcp',
},
{
name: 'Amazon Web Services',
code: 'aws',
},
],
};
function createComponent(props = {}) {
const propsData = {
...defaultProps,
...props,
};
wrapper = shallowMount(CrossplaneProviderStack, {
propsData,
});
}
beforeEach(() => {
const crossplane = {
title: 'crossplane',
stack: '',
};
createComponent({ crossplane });
});
const findDropdownElements = () => wrapper.findAll(GlDropdownItem);
const findFirstDropdownElement = () => findDropdownElements().at(0);
afterEach(() => {
wrapper.destroy();
});
it('renders all of the available stacks in the dropdown', () => {
const dropdownElements = findDropdownElements();
expect(dropdownElements.length).toBe(defaultProps.stacks.length);
defaultProps.stacks.forEach((stack, index) =>
expect(dropdownElements.at(index).text()).toEqual(stack.name),
);
});
it('displays the correct label for the first dropdown item if a stack is selected', () => {
const crossplane = {
title: 'crossplane',
stack: 'gcp',
};
createComponent({ crossplane });
expect(wrapper.vm.dropdownText).toBe('Google Cloud Platform');
});
it('emits the "set" event with the selected stack value', () => {
const crossplane = {
title: 'crossplane',
stack: 'gcp',
};
createComponent({ crossplane });
findFirstDropdownElement().vm.$emit('click');
expect(wrapper.emitted().set[0][0].code).toEqual('gcp');
});
it('it renders the correct dropdown text when no stack is selected', () => {
expect(wrapper.vm.dropdownText).toBe('Select Stack');
});
});
...@@ -52,9 +52,15 @@ const CLUSTERS_MOCK_DATA = { ...@@ -52,9 +52,15 @@ const CLUSTERS_MOCK_DATA = {
email: 'test@example.com', email: 'test@example.com',
can_uninstall: false, can_uninstall: false,
}, },
{
name: 'crossplane',
status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
can_uninstall: false,
},
{ {
name: 'elastic_stack', name: 'elastic_stack',
status: APPLICATION_STATUS.INSTALLING, status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect', status_reason: 'Cannot connect',
can_uninstall: false, can_uninstall: false,
}, },
...@@ -104,6 +110,12 @@ const CLUSTERS_MOCK_DATA = { ...@@ -104,6 +110,12 @@ const CLUSTERS_MOCK_DATA = {
status_reason: 'Cannot connect', status_reason: 'Cannot connect',
email: 'test@example.com', email: 'test@example.com',
}, },
{
name: 'crossplane',
status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
stack: 'gcp',
},
{ {
name: 'elastic_stack', name: 'elastic_stack',
status: APPLICATION_STATUS.ERROR, status: APPLICATION_STATUS.ERROR,
...@@ -116,6 +128,7 @@ const CLUSTERS_MOCK_DATA = { ...@@ -116,6 +128,7 @@ const CLUSTERS_MOCK_DATA = {
POST: { POST: {
'/gitlab-org/gitlab-shell/clusters/1/applications/helm': {}, '/gitlab-org/gitlab-shell/clusters/1/applications/helm': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/ingress': {}, '/gitlab-org/gitlab-shell/clusters/1/applications/ingress': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/crossplane': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/cert_manager': {}, '/gitlab-org/gitlab-shell/clusters/1/applications/cert_manager': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/runner': {}, '/gitlab-org/gitlab-shell/clusters/1/applications/runner': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': {}, '/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': {},
...@@ -138,6 +151,7 @@ const DEFAULT_APPLICATION_STATE = { ...@@ -138,6 +151,7 @@ const DEFAULT_APPLICATION_STATE = {
const APPLICATIONS_MOCK_STATE = { const APPLICATIONS_MOCK_STATE = {
helm: { title: 'Helm Tiller', status: 'installable' }, helm: { title: 'Helm Tiller', status: 'installable' },
ingress: { title: 'Ingress', status: 'installable' }, ingress: { title: 'Ingress', status: 'installable' },
crossplane: { title: 'Crossplane', status: 'installable', stack: '' },
cert_manager: { title: 'Cert-Manager', status: 'installable' }, cert_manager: { title: 'Cert-Manager', status: 'installable' },
runner: { title: 'GitLab Runner' }, runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' }, prometheus: { title: 'Prometheus' },
......
...@@ -71,6 +71,7 @@ describe('Clusters Store', () => { ...@@ -71,6 +71,7 @@ describe('Clusters Store', () => {
uninstallable: false, uninstallable: false,
uninstallSuccessful: false, uninstallSuccessful: false,
uninstallFailed: false, uninstallFailed: false,
validationError: null,
}, },
ingress: { ingress: {
title: 'Ingress', title: 'Ingress',
...@@ -84,6 +85,7 @@ describe('Clusters Store', () => { ...@@ -84,6 +85,7 @@ describe('Clusters Store', () => {
uninstallable: false, uninstallable: false,
uninstallSuccessful: false, uninstallSuccessful: false,
uninstallFailed: false, uninstallFailed: false,
validationError: null,
}, },
runner: { runner: {
title: 'GitLab Runner', title: 'GitLab Runner',
...@@ -100,6 +102,7 @@ describe('Clusters Store', () => { ...@@ -100,6 +102,7 @@ describe('Clusters Store', () => {
uninstallable: false, uninstallable: false,
uninstallSuccessful: false, uninstallSuccessful: false,
uninstallFailed: false, uninstallFailed: false,
validationError: null,
}, },
prometheus: { prometheus: {
title: 'Prometheus', title: 'Prometheus',
...@@ -111,6 +114,7 @@ describe('Clusters Store', () => { ...@@ -111,6 +114,7 @@ describe('Clusters Store', () => {
uninstallable: false, uninstallable: false,
uninstallSuccessful: false, uninstallSuccessful: false,
uninstallFailed: false, uninstallFailed: false,
validationError: null,
}, },
jupyter: { jupyter: {
title: 'JupyterHub', title: 'JupyterHub',
...@@ -123,6 +127,7 @@ describe('Clusters Store', () => { ...@@ -123,6 +127,7 @@ describe('Clusters Store', () => {
uninstallable: false, uninstallable: false,
uninstallSuccessful: false, uninstallSuccessful: false,
uninstallFailed: false, uninstallFailed: false,
validationError: null,
}, },
knative: { knative: {
title: 'Knative', title: 'Knative',
...@@ -140,6 +145,7 @@ describe('Clusters Store', () => { ...@@ -140,6 +145,7 @@ describe('Clusters Store', () => {
uninstallFailed: false, uninstallFailed: false,
updateSuccessful: false, updateSuccessful: false,
updateFailed: false, updateFailed: false,
validationError: null,
}, },
cert_manager: { cert_manager: {
title: 'Cert-Manager', title: 'Cert-Manager',
...@@ -152,11 +158,12 @@ describe('Clusters Store', () => { ...@@ -152,11 +158,12 @@ describe('Clusters Store', () => {
uninstallable: false, uninstallable: false,
uninstallSuccessful: false, uninstallSuccessful: false,
uninstallFailed: false, uninstallFailed: false,
validationError: null,
}, },
elastic_stack: { elastic_stack: {
title: 'Elastic Stack', title: 'Elastic Stack',
status: mockResponseData.applications[7].status, status: APPLICATION_STATUS.INSTALLABLE,
installFailed: false, installFailed: true,
statusReason: mockResponseData.applications[7].status_reason, statusReason: mockResponseData.applications[7].status_reason,
requestReason: null, requestReason: null,
kibana_hostname: '', kibana_hostname: '',
...@@ -164,6 +171,19 @@ describe('Clusters Store', () => { ...@@ -164,6 +171,19 @@ describe('Clusters Store', () => {
uninstallable: false, uninstallable: false,
uninstallSuccessful: false, uninstallSuccessful: false,
uninstallFailed: false, uninstallFailed: false,
validationError: null,
},
crossplane: {
title: 'Crossplane',
status: APPLICATION_STATUS.INSTALLABLE,
installFailed: true,
statusReason: mockResponseData.applications[8].status_reason,
requestReason: null,
installed: false,
uninstallable: false,
uninstallSuccessful: false,
uninstallFailed: false,
validationError: null,
}, },
}, },
environments: [], environments: [],
......
...@@ -44,6 +44,7 @@ describe Gitlab::UsageData do ...@@ -44,6 +44,7 @@ describe Gitlab::UsageData do
create(:clusters_applications_ingress, :installed, cluster: gcp_cluster) create(:clusters_applications_ingress, :installed, cluster: gcp_cluster)
create(:clusters_applications_cert_manager, :installed, cluster: gcp_cluster) create(:clusters_applications_cert_manager, :installed, cluster: gcp_cluster)
create(:clusters_applications_prometheus, :installed, cluster: gcp_cluster) create(:clusters_applications_prometheus, :installed, cluster: gcp_cluster)
create(:clusters_applications_crossplane, :installed, cluster: gcp_cluster)
create(:clusters_applications_runner, :installed, cluster: gcp_cluster) create(:clusters_applications_runner, :installed, cluster: gcp_cluster)
create(:clusters_applications_knative, :installed, cluster: gcp_cluster) create(:clusters_applications_knative, :installed, cluster: gcp_cluster)
create(:clusters_applications_elastic_stack, :installed, cluster: gcp_cluster) create(:clusters_applications_elastic_stack, :installed, cluster: gcp_cluster)
...@@ -140,6 +141,7 @@ describe Gitlab::UsageData do ...@@ -140,6 +141,7 @@ describe Gitlab::UsageData do
clusters_applications_ingress clusters_applications_ingress
clusters_applications_cert_managers clusters_applications_cert_managers
clusters_applications_prometheus clusters_applications_prometheus
clusters_applications_crossplane
clusters_applications_runner clusters_applications_runner
clusters_applications_knative clusters_applications_knative
clusters_applications_elastic_stack clusters_applications_elastic_stack
...@@ -222,6 +224,7 @@ describe Gitlab::UsageData do ...@@ -222,6 +224,7 @@ describe Gitlab::UsageData do
expect(count_data[:clusters_applications_helm]).to eq(1) expect(count_data[:clusters_applications_helm]).to eq(1)
expect(count_data[:clusters_applications_ingress]).to eq(1) expect(count_data[:clusters_applications_ingress]).to eq(1)
expect(count_data[:clusters_applications_cert_managers]).to eq(1) expect(count_data[:clusters_applications_cert_managers]).to eq(1)
expect(count_data[:clusters_applications_crossplane]).to eq(1)
expect(count_data[:clusters_applications_prometheus]).to eq(1) expect(count_data[:clusters_applications_prometheus]).to eq(1)
expect(count_data[:clusters_applications_runner]).to eq(1) expect(count_data[:clusters_applications_runner]).to eq(1)
expect(count_data[:clusters_applications_knative]).to eq(1) expect(count_data[:clusters_applications_knative]).to eq(1)
......
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::Applications::Crossplane do
let(:crossplane) { create(:clusters_applications_crossplane) }
include_examples 'cluster application core specs', :clusters_applications_crossplane
include_examples 'cluster application status specs', :clusters_applications_crossplane
include_examples 'cluster application version specs', :clusters_applications_crossplane
include_examples 'cluster application initial status specs'
describe 'validations' do
it { is_expected.to validate_presence_of(:stack) }
end
describe '#can_uninstall?' do
subject { crossplane.can_uninstall? }
it { is_expected.to be_truthy }
end
describe '#install_command' do
let(:stack) { 'gcp' }
subject { crossplane.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
it 'is initialized with crossplane arguments' do
expect(subject.name).to eq('crossplane')
expect(subject.chart).to eq('crossplane/crossplane')
expect(subject.repository).to eq('https://charts.crossplane.io/alpha')
expect(subject.version).to eq('0.4.1')
expect(subject).to be_rbac
end
context 'application failed to install previously' do
let(:crossplane) { create(:clusters_applications_crossplane, :errored, version: '0.0.1') }
it 'is initialized with the locked version' do
expect(subject.version).to eq('0.4.1')
end
end
end
describe '#files' do
let(:application) { crossplane }
let(:values) { subject[:'values.yaml'] }
subject { application.files }
it 'includes crossplane specific keys in the values.yaml file' do
expect(values).to include('clusterStacks')
end
end
end
...@@ -515,6 +515,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do ...@@ -515,6 +515,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
let!(:helm) { create(:clusters_applications_helm, cluster: cluster) } let!(:helm) { create(:clusters_applications_helm, cluster: cluster) }
let!(:ingress) { create(:clusters_applications_ingress, cluster: cluster) } let!(:ingress) { create(:clusters_applications_ingress, cluster: cluster) }
let!(:cert_manager) { create(:clusters_applications_cert_manager, cluster: cluster) } let!(:cert_manager) { create(:clusters_applications_cert_manager, cluster: cluster) }
let!(:crossplane) { create(:clusters_applications_crossplane, cluster: cluster) }
let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) } let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) }
let!(:runner) { create(:clusters_applications_runner, cluster: cluster) } let!(:runner) { create(:clusters_applications_runner, cluster: cluster) }
let!(:jupyter) { create(:clusters_applications_jupyter, cluster: cluster) } let!(:jupyter) { create(:clusters_applications_jupyter, cluster: cluster) }
...@@ -522,7 +523,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do ...@@ -522,7 +523,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
let!(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: cluster) } let!(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: cluster) }
it 'returns a list of created applications' do it 'returns a list of created applications' do
is_expected.to contain_exactly(helm, ingress, cert_manager, prometheus, runner, jupyter, knative, elastic_stack) is_expected.to contain_exactly(helm, ingress, cert_manager, crossplane, prometheus, runner, jupyter, knative, elastic_stack)
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment