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';
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);
// 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(() => {
this.store.notifyInstallFailure(appId);
this.store.updateAppProperty(
appId,
'requestReason',
s__('ClusterIntegration|Request to begin installing failed'),
);
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;
......
<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';
export const KNATIVE = 'knative';
export const RUNNER = 'runner';
export const CERT_MANAGER = 'cert_manager';
export const CROSSPLANE = 'crossplane';
export const PROMETHEUS = 'prometheus';
export const ELASTIC_STACK = 'elastic_stack';
......
......@@ -7,6 +7,7 @@ export default class ClusterService {
helm: this.options.installHelmEndpoint,
ingress: this.options.installIngressEndpoint,
cert_manager: this.options.installCertManagerEndpoint,
crossplane: this.options.installCrossplaneEndpoint,
runner: this.options.installRunnerEndpoint,
prometheus: this.options.installPrometheusEndpoint,
jupyter: this.options.installJupyterEndpoint,
......
......@@ -6,6 +6,7 @@ import {
KNATIVE,
CERT_MANAGER,
ELASTIC_STACK,
CROSSPLANE,
RUNNER,
APPLICATION_INSTALLED_STATUSES,
APPLICATION_STATUS,
......@@ -26,6 +27,7 @@ const applicationInitialState = {
uninstallable: false,
uninstallFailed: false,
uninstallSuccessful: false,
validationError: null,
};
export default class ClusterStore {
......@@ -58,6 +60,11 @@ export default class ClusterStore {
title: s__('ClusterIntegration|Cert-Manager'),
email: null,
},
crossplane: {
...applicationInitialState,
title: s__('ClusterIntegration|Crossplane'),
stack: null,
},
runner: {
...applicationInitialState,
title: s__('ClusterIntegration|GitLab Runner'),
......@@ -203,6 +210,9 @@ export default class ClusterStore {
} else if (appId === CERT_MANAGER) {
this.state.applications.cert_manager.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) {
this.state.applications.jupyter.hostname = this.updateHostnameIfUnset(
this.state.applications.jupyter.hostname,
......
......@@ -47,7 +47,7 @@ class Clusters::ApplicationsController < Clusters::BaseController
end
def cluster_application_params
params.permit(:application, :hostname, :kibana_hostname, :email)
params.permit(:application, :hostname, :kibana_hostname, :email, :stack)
end
def cluster_application_destroy_params
......
......@@ -17,6 +17,7 @@ class Clusters::ClustersController < Clusters::BaseController
end
before_action only: [:show] do
push_frontend_feature_flag(:enable_cluster_application_elastic_stack)
push_frontend_feature_flag(:enable_cluster_application_crossplane)
end
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
Applications::Helm.application_name => Applications::Helm,
Applications::Ingress.application_name => Applications::Ingress,
Applications::CertManager.application_name => Applications::CertManager,
Applications::Crossplane.application_name => Applications::Crossplane,
Applications::Prometheus.application_name => Applications::Prometheus,
Applications::Runner.application_name => Applications::Runner,
Applications::Jupyter.application_name => Applications::Jupyter,
......@@ -47,6 +48,7 @@ module Clusters
has_one_cluster_application :helm
has_one_cluster_application :ingress
has_one_cluster_application :cert_manager
has_one_cluster_application :crossplane
has_one_cluster_application :prometheus
has_one_cluster_application :runner
has_one_cluster_application :jupyter
......
......@@ -10,6 +10,7 @@ class ClusterApplicationEntity < Grape::Entity
expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) }
expose :kibana_hostname, if: -> (e, _) { e.respond_to?(:kibana_hostname) }
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 :can_uninstall?, as: :can_uninstall
end
......@@ -27,6 +27,10 @@ module Clusters
application.email = params[:email]
end
if application.has_attribute?(:stack)
application.stack = params[:stack]
end
if application.respond_to?(:oauth_application)
application.oauth_application = create_oauth_application(application, request)
end
......@@ -64,7 +68,7 @@ module Clusters
end
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
def unknown_application?
......
......@@ -12,6 +12,7 @@
install_helm_path: clusterable.install_applications_cluster_path(@cluster, :helm),
install_ingress_path: clusterable.install_applications_cluster_path(@cluster, :ingress),
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_runner_path: clusterable.install_applications_cluster_path(@cluster, :runner),
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
t.index ["cluster_id"], name: "index_clusters_applications_cert_managers_on_cluster_id", unique: true
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|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
......@@ -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", "users", on_delete: :nullify
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_helm", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_ingress", "clusters", on_delete: :cascade
......
......@@ -73,6 +73,7 @@ module Gitlab
clusters_applications_helm: count(::Clusters::Applications::Helm.available),
clusters_applications_ingress: count(::Clusters::Applications::Ingress.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_runner: count(::Clusters::Applications::Runner.available),
clusters_applications_knative: count(::Clusters::Applications::Knative.available),
......
......@@ -1495,6 +1495,9 @@ msgstr ""
msgid "Amazon EKS integration allows you to provision EKS clusters from GitLab."
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."
msgstr ""
......@@ -3646,6 +3649,12 @@ msgstr ""
msgid "ClusterIntegration|Creating Kubernetes cluster"
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?"
msgstr ""
......@@ -3661,6 +3670,9 @@ msgstr ""
msgid "ClusterIntegration|Enable this setting if using role-based access control (RBAC)."
msgstr ""
msgid "ClusterIntegration|Enabled stack"
msgstr ""
msgid "ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster"
msgstr ""
......@@ -3709,6 +3721,9 @@ msgstr ""
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
msgid "ClusterIntegration|Gitlab Integration"
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
msgstr ""
......@@ -4030,6 +4045,9 @@ msgstr ""
msgid "ClusterIntegration|Select a region to choose a VPC"
msgstr ""
msgid "ClusterIntegration|Select a stack to install Crossplane."
msgstr ""
msgid "ClusterIntegration|Select machine type"
msgstr ""
......@@ -4982,6 +5000,9 @@ msgstr ""
msgid "Cron syntax"
msgstr ""
msgid "Crossplane"
msgstr ""
msgid "Current Branch"
msgstr ""
......@@ -8442,6 +8463,9 @@ msgstr ""
msgid "Golden Tanuki"
msgstr ""
msgid "Google Cloud Platform"
msgstr ""
msgid "Google Code import"
msgstr ""
......@@ -10885,6 +10909,9 @@ msgstr ""
msgid "Metrics|e.g. req/sec"
msgstr ""
msgid "Microsoft Azure"
msgstr ""
msgid "Migrated %{success_count}/%{total_count} files."
msgstr ""
......@@ -14669,6 +14696,9 @@ msgstr ""
msgid "Rollback"
msgstr ""
msgid "Rook"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
......@@ -15243,6 +15273,9 @@ msgstr ""
msgid "Select Page"
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."
msgstr ""
......@@ -19854,6 +19887,9 @@ msgstr ""
msgid "You must provide your current password in order to change it."
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"
msgstr ""
......
......@@ -83,6 +83,11 @@ FactoryBot.define do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
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
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
......
......@@ -37,6 +37,7 @@
"hostname": { "type": ["string", "null"] },
"kibana_hostname": { "type": ["string", "null"] },
"email": { "type": ["string", "null"] },
"stack": { "type": ["string", "null"] },
"update_available": { "type": ["boolean", "null"] },
"can_uninstall": { "type": "boolean" }
},
......
......@@ -286,16 +286,21 @@ describe('Clusters', () => {
});
describe('installApplication', () => {
it.each(APPLICATIONS)('tries to install %s', applicationId => {
jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce();
it.each(APPLICATIONS)('tries to install %s', (applicationId, done) => {
jest.spyOn(cluster.service, 'installApplication').mockResolvedValue();
cluster.store.state.applications[applicationId].status = INSTALLABLE;
cluster.installApplication({ id: applicationId });
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);
// eslint-disable-next-line promise/valid-params
cluster
.installApplication({ id: applicationId })
.then(() => {
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', () => {
......
......@@ -6,6 +6,7 @@ import { APPLICATIONS_MOCK_STATE } from '../services/mock_data';
import eventHub from '~/clusters/event_hub';
import { shallowMount } from '@vue/test-utils';
import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue';
import CrossplaneProviderStack from '~/clusters/components/crossplane_provider_stack.vue';
describe('Applications', () => {
let vm;
......@@ -16,6 +17,7 @@ describe('Applications', () => {
gon.features = gon.features || {};
gon.features.enableClusterApplicationElasticStack = true;
gon.features.enableClusterApplicationCrossplane = true;
});
afterEach(() => {
......@@ -42,6 +44,10 @@ describe('Applications', () => {
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', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull();
});
......@@ -83,6 +89,10 @@ describe('Applications', () => {
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', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull();
});
......@@ -124,6 +134,10 @@ describe('Applications', () => {
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', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull();
});
......@@ -179,6 +193,7 @@ describe('Applications', () => {
},
helm: { title: 'Helm Tiller' },
cert_manager: { title: 'Cert-Manager' },
crossplane: { title: 'Crossplane', stack: '' },
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
jupyter: { title: 'JupyterHub', hostname: '' },
......@@ -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('with ingress installed with ip & elastic stack installable', () => {
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 = {
email: 'test@example.com',
can_uninstall: false,
},
{
name: 'crossplane',
status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
can_uninstall: false,
},
{
name: 'elastic_stack',
status: APPLICATION_STATUS.INSTALLING,
status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
can_uninstall: false,
},
......@@ -104,6 +110,12 @@ const CLUSTERS_MOCK_DATA = {
status_reason: 'Cannot connect',
email: 'test@example.com',
},
{
name: 'crossplane',
status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
stack: 'gcp',
},
{
name: 'elastic_stack',
status: APPLICATION_STATUS.ERROR,
......@@ -116,6 +128,7 @@ const CLUSTERS_MOCK_DATA = {
POST: {
'/gitlab-org/gitlab-shell/clusters/1/applications/helm': {},
'/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/runner': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': {},
......@@ -138,6 +151,7 @@ const DEFAULT_APPLICATION_STATE = {
const APPLICATIONS_MOCK_STATE = {
helm: { title: 'Helm Tiller', status: 'installable' },
ingress: { title: 'Ingress', status: 'installable' },
crossplane: { title: 'Crossplane', status: 'installable', stack: '' },
cert_manager: { title: 'Cert-Manager', status: 'installable' },
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
......
......@@ -71,6 +71,7 @@ describe('Clusters Store', () => {
uninstallable: false,
uninstallSuccessful: false,
uninstallFailed: false,
validationError: null,
},
ingress: {
title: 'Ingress',
......@@ -84,6 +85,7 @@ describe('Clusters Store', () => {
uninstallable: false,
uninstallSuccessful: false,
uninstallFailed: false,
validationError: null,
},
runner: {
title: 'GitLab Runner',
......@@ -100,6 +102,7 @@ describe('Clusters Store', () => {
uninstallable: false,
uninstallSuccessful: false,
uninstallFailed: false,
validationError: null,
},
prometheus: {
title: 'Prometheus',
......@@ -111,6 +114,7 @@ describe('Clusters Store', () => {
uninstallable: false,
uninstallSuccessful: false,
uninstallFailed: false,
validationError: null,
},
jupyter: {
title: 'JupyterHub',
......@@ -123,6 +127,7 @@ describe('Clusters Store', () => {
uninstallable: false,
uninstallSuccessful: false,
uninstallFailed: false,
validationError: null,
},
knative: {
title: 'Knative',
......@@ -140,6 +145,7 @@ describe('Clusters Store', () => {
uninstallFailed: false,
updateSuccessful: false,
updateFailed: false,
validationError: null,
},
cert_manager: {
title: 'Cert-Manager',
......@@ -152,11 +158,12 @@ describe('Clusters Store', () => {
uninstallable: false,
uninstallSuccessful: false,
uninstallFailed: false,
validationError: null,
},
elastic_stack: {
title: 'Elastic Stack',
status: mockResponseData.applications[7].status,
installFailed: false,
status: APPLICATION_STATUS.INSTALLABLE,
installFailed: true,
statusReason: mockResponseData.applications[7].status_reason,
requestReason: null,
kibana_hostname: '',
......@@ -164,6 +171,19 @@ describe('Clusters Store', () => {
uninstallable: false,
uninstallSuccessful: 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: [],
......
......@@ -44,6 +44,7 @@ describe Gitlab::UsageData do
create(:clusters_applications_ingress, :installed, cluster: gcp_cluster)
create(:clusters_applications_cert_manager, :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_knative, :installed, cluster: gcp_cluster)
create(:clusters_applications_elastic_stack, :installed, cluster: gcp_cluster)
......@@ -140,6 +141,7 @@ describe Gitlab::UsageData do
clusters_applications_ingress
clusters_applications_cert_managers
clusters_applications_prometheus
clusters_applications_crossplane
clusters_applications_runner
clusters_applications_knative
clusters_applications_elastic_stack
......@@ -222,6 +224,7 @@ describe Gitlab::UsageData do
expect(count_data[:clusters_applications_helm]).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_crossplane]).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_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
let!(:helm) { create(:clusters_applications_helm, cluster: cluster) }
let!(:ingress) { create(:clusters_applications_ingress, 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!(:runner) { create(:clusters_applications_runner, cluster: cluster) }
let!(:jupyter) { create(:clusters_applications_jupyter, cluster: cluster) }
......@@ -522,7 +523,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
let!(:elastic_stack) { create(:clusters_applications_elastic_stack, cluster: cluster) }
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
......
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