diff options
author | Enrique Alcantara <ealcantara@gitlab.com> | 2019-04-23 15:08:09 -0400 |
---|---|---|
committer | Enrique Alcantara <ealcantara@gitlab.com> | 2019-04-30 10:52:33 -0400 |
commit | 4dfb9290e23da1248404af1651b670f01ff6956b (patch) | |
tree | 53800b144d85532efa5a0c055a8e6c359b94ea2b | |
parent | b0043179da70847c20f90a6e80661764b273cc05 (diff) | |
download | gitlab-ce-4dfb9290e23da1248404af1651b670f01ff6956b.tar.gz |
Handle uninstall request in clusters bundle
- Handle uninstallApplication event
- Transition application state machine to uninstalling
when uninstallApplication event is captured.
- Send DELETE request to application route to start the
uninstalling process.
7 files changed, 97 insertions, 50 deletions
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index 8461e01de7b..561b6bdd9f1 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -132,6 +132,7 @@ export default class Clusters { eventHub.$on('dismissUpgradeSuccess', appId => this.dismissUpgradeSuccess(appId)); eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data)); eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data)); + eventHub.$on('uninstallApplication', data => this.uninstallApplication(data)); } removeListeners() { @@ -141,6 +142,7 @@ export default class Clusters { eventHub.$off('dismissUpgradeSuccess', this.dismissUpgradeSuccess); eventHub.$off('saveKnativeDomain'); eventHub.$off('setKnativeHostname'); + eventHub.$off('uninstallApplication'); } initPolling() { @@ -249,14 +251,13 @@ export default class Clusters { } } - installApplication(data) { - const appId = data.id; + installApplication({ id: appId, params }) { this.store.updateAppProperty(appId, 'requestReason', null); this.store.updateAppProperty(appId, 'statusReason', null); this.store.installApplication(appId); - return this.service.installApplication(appId, data.params).catch(() => { + return this.service.installApplication(appId, params).catch(() => { this.store.notifyInstallFailure(appId); this.store.updateAppProperty( appId, @@ -266,6 +267,22 @@ export default class Clusters { }); } + uninstallApplication({ id: appId }) { + this.store.updateAppProperty(appId, 'requestReason', null); + this.store.updateAppProperty(appId, 'statusReason', null); + + this.store.uninstallApplication(appId); + + return this.service.uninstallApplication(appId).catch(() => { + this.store.notifyUninstallFailure(appId); + this.store.updateAppProperty( + appId, + 'requestReason', + s__('ClusterIntegration|Request to begin uninstalling failed'), + ); + }); + } + upgradeApplication(data) { const appId = data.id; diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js index 74fe6417fbc..8fd752092c9 100644 --- a/app/assets/javascripts/clusters/constants.js +++ b/app/assets/javascripts/clusters/constants.js @@ -28,17 +28,23 @@ export const APPLICATION_STATUS = { export const APPLICATION_INSTALLED_STATUSES = [ APPLICATION_STATUS.INSTALLED, APPLICATION_STATUS.UPDATING, + APPLICATION_STATUS.UNINSTALLING, ]; // These are only used client-side export const UPDATE_EVENT = 'update'; export const INSTALL_EVENT = 'install'; +export const UNINSTALL_EVENT = 'uninstall'; +export const HELM = 'helm'; export const INGRESS = 'ingress'; export const JUPYTER = 'jupyter'; export const KNATIVE = 'knative'; export const RUNNER = 'runner'; export const CERT_MANAGER = 'cert_manager'; export const PROMETHEUS = 'prometheus'; + +export const APPLICATIONS = [HELM, INGRESS, JUPYTER, KNATIVE, RUNNER, CERT_MANAGER, PROMETHEUS]; + export const INGRESS_DOMAIN_SUFFIX = '.nip.io'; diff --git a/app/assets/javascripts/clusters/services/clusters_service.js b/app/assets/javascripts/clusters/services/clusters_service.js index dea33ac44c5..01f3732de7e 100644 --- a/app/assets/javascripts/clusters/services/clusters_service.js +++ b/app/assets/javascripts/clusters/services/clusters_service.js @@ -29,6 +29,10 @@ export default class ClusterService { return axios.patch(this.appUpdateEndpointMap[appId], params); } + uninstallApplication(appId, params) { + return axios.delete(this.appInstallEndpointMap[appId], params); + } + static updateCluster(endpoint, data) { return axios.put(endpoint, data); } diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js index d3054f7d731..1b4d7e8372c 100644 --- a/app/assets/javascripts/clusters/stores/clusters_store.js +++ b/app/assets/javascripts/clusters/stores/clusters_store.js @@ -10,6 +10,7 @@ import { APPLICATION_STATUS, INSTALL_EVENT, UPDATE_EVENT, + UNINSTALL_EVENT, } from '../constants'; import transitionApplicationState from '../services/application_state_machine'; @@ -119,6 +120,14 @@ export default class ClusterStore { this.handleApplicationEvent(appId, APPLICATION_STATUS.UPDATE_ERRORED); } + uninstallApplication(appId) { + this.handleApplicationEvent(appId, UNINSTALL_EVENT); + } + + notifyUninstallFailure(appId) { + this.handleApplicationEvent(appId, APPLICATION_STATUS.UNINSTALL_ERRORED); + } + handleApplicationEvent(appId, event) { const currentAppState = this.state.applications[appId]; @@ -144,6 +153,7 @@ export default class ClusterStore { status_reason: statusReason, version, update_available: upgradeAvailable, + can_uninstall: uninstallable, } = serverAppEntry; const currentApplicationState = this.state.applications[appId] || {}; const nextApplicationState = transitionApplicationState(currentApplicationState, status); @@ -153,6 +163,7 @@ export default class ClusterStore { ...nextApplicationState, statusReason, installed: isApplicationInstalled(nextApplicationState.status), + uninstallable, }; if (appId === INGRESS) { diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js index a61103397eb..73897107f67 100644 --- a/spec/frontend/clusters/clusters_bundle_spec.js +++ b/spec/frontend/clusters/clusters_bundle_spec.js @@ -1,12 +1,12 @@ import Clusters from '~/clusters/clusters_bundle'; -import { APPLICATION_STATUS, INGRESS_DOMAIN_SUFFIX } from '~/clusters/constants'; +import { APPLICATION_STATUS, INGRESS_DOMAIN_SUFFIX, APPLICATIONS } from '~/clusters/constants'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import { loadHTMLFixture } from 'helpers/fixtures'; import { setTestTimeout } from 'helpers/timeout'; import $ from 'jquery'; -const { INSTALLING, INSTALLABLE, INSTALLED } = APPLICATION_STATUS; +const { INSTALLING, INSTALLABLE, INSTALLED, UNINSTALLING } = APPLICATION_STATUS; describe('Clusters', () => { setTestTimeout(1000); @@ -212,73 +212,61 @@ describe('Clusters', () => { }); describe('installApplication', () => { - it('tries to install helm', () => { + it.each(APPLICATIONS)('tries to install %s', applicationId => { jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); - cluster.store.state.applications.helm.status = INSTALLABLE; + cluster.store.state.applications[applicationId].status = INSTALLABLE; - cluster.installApplication({ id: 'helm' }); + cluster.installApplication({ id: applicationId }); - expect(cluster.store.state.applications.helm.status).toEqual(INSTALLING); - expect(cluster.store.state.applications.helm.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('helm', 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); }); - it('tries to install ingress', () => { - jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); - - cluster.store.state.applications.ingress.status = INSTALLABLE; - - cluster.installApplication({ id: 'ingress' }); - - expect(cluster.store.state.applications.ingress.status).toEqual(INSTALLING); - expect(cluster.store.state.applications.ingress.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress', undefined); - }); + it('sets error request status when the request fails', () => { + jest + .spyOn(cluster.service, 'installApplication') + .mockRejectedValueOnce(new Error('STUBBED ERROR')); - it('tries to install runner', () => { - jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); + cluster.store.state.applications.helm.status = INSTALLABLE; - cluster.store.state.applications.runner.status = INSTALLABLE; + const promise = cluster.installApplication({ id: 'helm' }); - cluster.installApplication({ id: 'runner' }); + return promise.then(() => { + expect(cluster.store.state.applications.helm.status).toEqual(INSTALLABLE); + expect(cluster.store.state.applications.helm.installFailed).toBe(true); - expect(cluster.store.state.applications.runner.status).toEqual(INSTALLING); - expect(cluster.store.state.applications.runner.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('runner', undefined); + expect(cluster.store.state.applications.helm.requestReason).toBeDefined(); + }); }); + }); - it('tries to install jupyter', () => { - jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); + describe('uninstallApplication', () => { + it.each(APPLICATIONS)('tries to uninstall %s', applicationId => { + jest.spyOn(cluster.service, 'uninstallApplication').mockResolvedValueOnce(); - cluster.installApplication({ - id: 'jupyter', - params: { hostname: cluster.store.state.applications.jupyter.hostname }, - }); + cluster.store.state.applications[applicationId].status = INSTALLED; - cluster.store.state.applications.jupyter.status = INSTALLABLE; - expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('jupyter', { - hostname: cluster.store.state.applications.jupyter.hostname, - }); + cluster.uninstallApplication({ id: applicationId }); + + expect(cluster.store.state.applications[applicationId].status).toEqual(UNINSTALLING); + expect(cluster.store.state.applications[applicationId].requestReason).toEqual(null); + expect(cluster.service.uninstallApplication).toHaveBeenCalledWith(applicationId); }); - it('sets error request status when the request fails', () => { + it('sets error request status when the uninstall request fails', () => { jest - .spyOn(cluster.service, 'installApplication') + .spyOn(cluster.service, 'uninstallApplication') .mockRejectedValueOnce(new Error('STUBBED ERROR')); - cluster.store.state.applications.helm.status = INSTALLABLE; + cluster.store.state.applications.helm.status = INSTALLED; - const promise = cluster.installApplication({ id: 'helm' }); - - expect(cluster.store.state.applications.helm.status).toEqual(INSTALLING); - expect(cluster.store.state.applications.helm.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalled(); + const promise = cluster.uninstallApplication({ id: 'helm' }); return promise.then(() => { - expect(cluster.store.state.applications.helm.status).toEqual(INSTALLABLE); - expect(cluster.store.state.applications.helm.installFailed).toBe(true); + expect(cluster.store.state.applications.helm.status).toEqual(INSTALLED); + expect(cluster.store.state.applications.helm.uninstallFailed).toBe(true); expect(cluster.store.state.applications.helm.requestReason).toBeDefined(); }); diff --git a/spec/frontend/clusters/services/mock_data.js b/spec/frontend/clusters/services/mock_data.js index 1e896af1c7d..41ad398e924 100644 --- a/spec/frontend/clusters/services/mock_data.js +++ b/spec/frontend/clusters/services/mock_data.js @@ -11,6 +11,7 @@ const CLUSTERS_MOCK_DATA = { name: 'helm', status: APPLICATION_STATUS.INSTALLABLE, status_reason: null, + can_uninstall: false, }, { name: 'ingress', @@ -18,32 +19,38 @@ const CLUSTERS_MOCK_DATA = { status_reason: 'Cannot connect', external_ip: null, external_hostname: null, + can_uninstall: false, }, { name: 'runner', status: APPLICATION_STATUS.INSTALLING, status_reason: null, + can_uninstall: false, }, { name: 'prometheus', status: APPLICATION_STATUS.ERROR, status_reason: 'Cannot connect', + can_uninstall: false, }, { name: 'jupyter', status: APPLICATION_STATUS.INSTALLING, status_reason: 'Cannot connect', + can_uninstall: false, }, { name: 'knative', status: APPLICATION_STATUS.INSTALLING, status_reason: 'Cannot connect', + can_uninstall: false, }, { name: 'cert_manager', status: APPLICATION_STATUS.ERROR, status_reason: 'Cannot connect', email: 'test@example.com', + can_uninstall: false, }, ], }, diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js index a20e0439555..aa926bb36d7 100644 --- a/spec/frontend/clusters/stores/clusters_store_spec.js +++ b/spec/frontend/clusters/stores/clusters_store_spec.js @@ -63,6 +63,8 @@ describe('Clusters Store', () => { installed: false, installFailed: false, uninstallable: false, + uninstallSuccessful: false, + uninstallFailed: false, }, ingress: { title: 'Ingress', @@ -74,6 +76,8 @@ describe('Clusters Store', () => { installed: false, installFailed: true, uninstallable: false, + uninstallSuccessful: false, + uninstallFailed: false, }, runner: { title: 'GitLab Runner', @@ -89,6 +93,8 @@ describe('Clusters Store', () => { updateFailed: false, updateSuccessful: false, uninstallable: false, + uninstallSuccessful: false, + uninstallFailed: false, }, prometheus: { title: 'Prometheus', @@ -98,6 +104,8 @@ describe('Clusters Store', () => { installed: false, installFailed: true, uninstallable: false, + uninstallSuccessful: false, + uninstallFailed: false, }, jupyter: { title: 'JupyterHub', @@ -108,6 +116,8 @@ describe('Clusters Store', () => { installed: false, installFailed: false, uninstallable: false, + uninstallSuccessful: false, + uninstallFailed: false, }, knative: { title: 'Knative', @@ -121,6 +131,8 @@ describe('Clusters Store', () => { installed: false, installFailed: false, uninstallable: false, + uninstallSuccessful: false, + uninstallFailed: false, }, cert_manager: { title: 'Cert-Manager', @@ -131,6 +143,8 @@ describe('Clusters Store', () => { email: mockResponseData.applications[6].email, installed: false, uninstallable: false, + uninstallSuccessful: false, + uninstallFailed: false, }, }, }); |