Merge branch '56937-edit-knative-domain' into 'master'
Edit Knative domain after it has been deployed Closes #56937 See merge request gitlab-org/gitlab-ce!25386
This commit is contained in:
commit
5cea1e8b80
35 changed files with 959 additions and 301 deletions
|
@ -36,6 +36,7 @@ export default class Clusters {
|
|||
installRunnerPath,
|
||||
installJupyterPath,
|
||||
installKnativePath,
|
||||
updateKnativePath,
|
||||
installPrometheusPath,
|
||||
managePrometheusPath,
|
||||
hasRbac,
|
||||
|
@ -62,6 +63,7 @@ export default class Clusters {
|
|||
installPrometheusEndpoint: installPrometheusPath,
|
||||
installJupyterEndpoint: installJupyterPath,
|
||||
installKnativeEndpoint: installKnativePath,
|
||||
updateKnativeEndpoint: updateKnativePath,
|
||||
});
|
||||
|
||||
this.installApplication = this.installApplication.bind(this);
|
||||
|
@ -128,6 +130,8 @@ export default class Clusters {
|
|||
eventHub.$on('upgradeApplication', data => this.upgradeApplication(data));
|
||||
eventHub.$on('upgradeFailed', appId => this.upgradeFailed(appId));
|
||||
eventHub.$on('dismissUpgradeSuccess', appId => this.dismissUpgradeSuccess(appId));
|
||||
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
|
||||
eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data));
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
|
@ -136,6 +140,8 @@ export default class Clusters {
|
|||
eventHub.$off('upgradeApplication', this.upgradeApplication);
|
||||
eventHub.$off('upgradeFailed', this.upgradeFailed);
|
||||
eventHub.$off('dismissUpgradeSuccess', this.dismissUpgradeSuccess);
|
||||
eventHub.$off('saveKnativeDomain');
|
||||
eventHub.$off('setKnativeHostname');
|
||||
}
|
||||
|
||||
initPolling() {
|
||||
|
@ -271,6 +277,18 @@ export default class Clusters {
|
|||
this.store.updateAppProperty(appId, 'requestStatus', null);
|
||||
}
|
||||
|
||||
saveKnativeDomain(data) {
|
||||
const appId = data.id;
|
||||
this.store.updateAppProperty(appId, 'status', APPLICATION_STATUS.UPDATING);
|
||||
this.service.updateApplication(appId, data.params);
|
||||
}
|
||||
|
||||
setKnativeHostname(data) {
|
||||
const appId = data.id;
|
||||
this.store.updateAppProperty(appId, 'isEditingHostName', true);
|
||||
this.store.updateAppProperty(appId, 'hostname', data.hostname);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.destroyed = true;
|
||||
|
||||
|
|
|
@ -191,14 +191,7 @@ export default {
|
|||
return this.status === APPLICATION_STATUS.UPDATE_ERRORED;
|
||||
},
|
||||
upgradeFailureDescription() {
|
||||
return sprintf(
|
||||
s__(
|
||||
'ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again.',
|
||||
),
|
||||
{
|
||||
title: this.title,
|
||||
},
|
||||
);
|
||||
return s__('ClusterIntegration|Update failed. Please check the logs and try again.');
|
||||
},
|
||||
upgradeSuccessDescription() {
|
||||
return sprintf(s__('ClusterIntegration|%{title} upgraded successfully.'), {
|
||||
|
@ -210,9 +203,9 @@ export default {
|
|||
if (this.upgradeAvailable && !this.upgradeFailed && !this.isUpgrading) {
|
||||
label = s__('ClusterIntegration|Upgrade');
|
||||
} else if (this.isUpgrading) {
|
||||
label = s__('ClusterIntegration|Upgrading');
|
||||
label = s__('ClusterIntegration|Updating');
|
||||
} else if (this.upgradeFailed) {
|
||||
label = s__('ClusterIntegration|Retry upgrade');
|
||||
label = s__('ClusterIntegration|Retry update');
|
||||
}
|
||||
|
||||
return label;
|
||||
|
@ -224,6 +217,14 @@ export default {
|
|||
(this.upgradeRequested && !this.upgradeSuccessful)
|
||||
);
|
||||
},
|
||||
shouldShowUpgradeDetails() {
|
||||
// This method only returns true when;
|
||||
// Upgrade was successful OR Upgrade failed
|
||||
// AND new upgrade is unavailable AND version information is present.
|
||||
return (
|
||||
(this.upgradeSuccessful || this.upgradeFailed) && !this.upgradeAvailable && this.version
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
status() {
|
||||
|
@ -303,7 +304,7 @@ export default {
|
|||
</div>
|
||||
|
||||
<div
|
||||
v-if="(upgradeSuccessful || upgradeFailed) && !upgradeAvailable"
|
||||
v-if="shouldShowUpgradeDetails"
|
||||
class="form-text text-muted label p-0 js-cluster-application-upgrade-details"
|
||||
>
|
||||
{{ versionLabel }}
|
||||
|
|
|
@ -15,11 +15,14 @@ import { s__, sprintf } from '../../locale';
|
|||
import applicationRow from './application_row.vue';
|
||||
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
|
||||
import { CLUSTER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants';
|
||||
import LoadingButton from '~/vue_shared/components/loading_button.vue';
|
||||
import eventHub from '~/clusters/event_hub';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
applicationRow,
|
||||
clipboardButton,
|
||||
LoadingButton,
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
|
@ -173,16 +176,55 @@ export default {
|
|||
jupyterHostname() {
|
||||
return this.applications.jupyter.hostname;
|
||||
},
|
||||
knative() {
|
||||
return this.applications.knative;
|
||||
},
|
||||
knativeInstalled() {
|
||||
return this.applications.knative.status === APPLICATION_STATUS.INSTALLED;
|
||||
return (
|
||||
this.knative.status === APPLICATION_STATUS.INSTALLED ||
|
||||
this.knativeUpgrading ||
|
||||
this.knativeUpgradeFailed ||
|
||||
this.knative.status === APPLICATION_STATUS.UPDATED
|
||||
);
|
||||
},
|
||||
knativeUpgrading() {
|
||||
return (
|
||||
this.knative.status === APPLICATION_STATUS.UPDATING ||
|
||||
this.knative.status === APPLICATION_STATUS.SCHEDULED
|
||||
);
|
||||
},
|
||||
knativeUpgradeFailed() {
|
||||
return this.knative.status === APPLICATION_STATUS.UPDATE_ERRORED;
|
||||
},
|
||||
knativeExternalIp() {
|
||||
return this.applications.knative.externalIp;
|
||||
return this.knative.externalIp;
|
||||
},
|
||||
canUpdateKnativeEndpoint() {
|
||||
return this.knativeExternalIp && !this.knativeUpgradeFailed && !this.knativeUpgrading;
|
||||
},
|
||||
knativeHostname: {
|
||||
get() {
|
||||
return this.knative.hostname;
|
||||
},
|
||||
set(hostname) {
|
||||
eventHub.$emit('setKnativeHostname', {
|
||||
id: 'knative',
|
||||
hostname,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.helmInstallIllustration = helmInstallIllustration;
|
||||
},
|
||||
methods: {
|
||||
saveKnativeDomain() {
|
||||
eventHub.$emit('saveKnativeDomain', {
|
||||
id: 'knative',
|
||||
params: { hostname: this.knative.hostname },
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -471,58 +513,72 @@ export default {
|
|||
}}
|
||||
</p>
|
||||
|
||||
<template v-if="knativeInstalled">
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<template v-if="knativeInstalled || (helmInstalled && rbac)">
|
||||
<div
|
||||
:class="{ 'col-md-6': knativeInstalled, 'col-12': helmInstalled && rbac }"
|
||||
class="form-group col-sm-12 mb-0"
|
||||
>
|
||||
<label for="knative-domainname">
|
||||
<strong>
|
||||
{{ s__('ClusterIntegration|Knative Domain Name:') }}
|
||||
</strong>
|
||||
</label>
|
||||
<input
|
||||
id="knative-domainname"
|
||||
v-model="applications.knative.hostname"
|
||||
v-model="knativeHostname"
|
||||
type="text"
|
||||
class="form-control js-domainname"
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="helmInstalled && rbac">
|
||||
<div class="form-group">
|
||||
<label for="knative-domainname">
|
||||
{{ s__('ClusterIntegration|Knative Domain Name:') }}
|
||||
</label>
|
||||
<input
|
||||
id="knative-domainname"
|
||||
v-model="applications.knative.hostname"
|
||||
type="text"
|
||||
class="form-control js-domainname"
|
||||
class="form-control js-knative-domainname"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="knativeInstalled">
|
||||
<div class="form-group">
|
||||
<div class="form-group col-sm-12 col-md-6 pl-md-0 mb-0 mt-3 mt-md-0">
|
||||
<label for="knative-ip-address">
|
||||
{{ s__('ClusterIntegration|Knative IP Address:') }}
|
||||
<strong>
|
||||
{{ s__('ClusterIntegration|Knative Endpoint:') }}
|
||||
</strong>
|
||||
</label>
|
||||
<div v-if="knativeExternalIp" class="input-group">
|
||||
<input
|
||||
id="knative-ip-address"
|
||||
:value="knativeExternalIp"
|
||||
type="text"
|
||||
class="form-control js-ip-address"
|
||||
class="form-control js-knative-ip-address"
|
||||
readonly
|
||||
/>
|
||||
<span class="input-group-append">
|
||||
<clipboard-button
|
||||
:text="knativeExternalIp"
|
||||
:title="s__('ClusterIntegration|Copy Knative IP Address to clipboard')"
|
||||
class="input-group-text js-clipboard-btn"
|
||||
:title="s__('ClusterIntegration|Copy Knative Endpoint to clipboard')"
|
||||
class="input-group-text js-knative-ip-clipboard-btn"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<input v-else type="text" class="form-control js-ip-address" readonly value="?" />
|
||||
<input
|
||||
v-else
|
||||
type="text"
|
||||
class="form-control js-knative-ip-address"
|
||||
readonly
|
||||
value="?"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p v-if="!knativeExternalIp" class="settings-message js-no-ip-message">
|
||||
<p class="form-text text-muted col-12">
|
||||
{{
|
||||
s__(
|
||||
`ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint.`,
|
||||
)
|
||||
}}
|
||||
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
|
||||
{{ __('More information') }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p
|
||||
v-if="!knativeExternalIp"
|
||||
class="settings-message js-no-knative-ip-message mt-2 mr-3 mb-0 ml-3 "
|
||||
>
|
||||
{{
|
||||
s__(`ClusterIntegration|The IP address is in
|
||||
the process of being assigned. Please check your Kubernetes
|
||||
|
@ -530,18 +586,16 @@ export default {
|
|||
}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{
|
||||
s__(`ClusterIntegration|Point a wildcard DNS to this
|
||||
generated IP address in order to access
|
||||
your application after it has been deployed.`)
|
||||
}}
|
||||
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
|
||||
{{ __('More information') }}
|
||||
</a>
|
||||
</p>
|
||||
<button
|
||||
v-if="canUpdateKnativeEndpoint"
|
||||
class="btn btn-success js-knative-save-domain-button mt-3 ml-3"
|
||||
@click="saveKnativeDomain"
|
||||
>
|
||||
{{ s__('ClusterIntegration|Save changes') }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</application-row>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -12,6 +12,9 @@ export default class ClusterService {
|
|||
jupyter: this.options.installJupyterEndpoint,
|
||||
knative: this.options.installKnativeEndpoint,
|
||||
};
|
||||
this.appUpdateEndpointMap = {
|
||||
knative: this.options.updateKnativeEndpoint,
|
||||
};
|
||||
}
|
||||
|
||||
fetchData() {
|
||||
|
@ -22,6 +25,10 @@ export default class ClusterService {
|
|||
return axios.post(this.appInstallEndpointMap[appId], params);
|
||||
}
|
||||
|
||||
updateApplication(appId, params) {
|
||||
return axios.patch(this.appUpdateEndpointMap[appId], params);
|
||||
}
|
||||
|
||||
static updateCluster(endpoint, data) {
|
||||
return axios.put(endpoint, data);
|
||||
}
|
||||
|
|
|
@ -66,6 +66,7 @@ export default class ClusterStore {
|
|||
requestStatus: null,
|
||||
requestReason: null,
|
||||
hostname: null,
|
||||
isEditingHostName: false,
|
||||
externalIp: null,
|
||||
},
|
||||
},
|
||||
|
@ -129,8 +130,10 @@ export default class ClusterStore {
|
|||
? `jupyter.${this.state.applications.ingress.externalIp}.nip.io`
|
||||
: '');
|
||||
} else if (appId === KNATIVE) {
|
||||
if (!this.state.applications.knative.isEditingHostName) {
|
||||
this.state.applications.knative.hostname =
|
||||
serverAppEntry.hostname || this.state.applications.knative.hostname;
|
||||
}
|
||||
this.state.applications.knative.externalIp =
|
||||
serverAppEntry.external_ip || this.state.applications.knative.externalIp;
|
||||
} else if (appId === RUNNER) {
|
||||
|
|
|
@ -3,26 +3,41 @@
|
|||
class Clusters::ApplicationsController < Clusters::BaseController
|
||||
before_action :cluster
|
||||
before_action :authorize_create_cluster!, only: [:create]
|
||||
before_action :authorize_update_cluster!, only: [:update]
|
||||
|
||||
def create
|
||||
request_handler do
|
||||
Clusters::Applications::CreateService
|
||||
.new(@cluster, current_user, create_cluster_application_params)
|
||||
.new(@cluster, current_user, cluster_application_params)
|
||||
.execute(request)
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
request_handler do
|
||||
Clusters::Applications::UpdateService
|
||||
.new(@cluster, current_user, cluster_application_params)
|
||||
.execute(request)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request_handler
|
||||
yield
|
||||
|
||||
head :no_content
|
||||
rescue Clusters::Applications::CreateService::InvalidApplicationError
|
||||
rescue Clusters::Applications::BaseService::InvalidApplicationError
|
||||
render_404
|
||||
rescue StandardError
|
||||
head :bad_request
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cluster
|
||||
@cluster ||= clusterable.clusters.find(params[:id]) || render_404
|
||||
end
|
||||
|
||||
def create_cluster_application_params
|
||||
def cluster_application_params
|
||||
params.permit(:application, :hostname, :email)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,6 +30,12 @@ module Clusters
|
|||
# Override if you need extra data synchronized
|
||||
# from K8s after installation
|
||||
end
|
||||
|
||||
def update_command
|
||||
install_command.tap do |command|
|
||||
command.version = version
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -44,6 +44,10 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
|
|||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def update_applications_cluster_path(cluster, application)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def cluster_path(cluster, params = {})
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
|
|
@ -14,6 +14,11 @@ class GroupClusterablePresenter < ClusterablePresenter
|
|||
install_applications_group_cluster_path(clusterable, cluster, application)
|
||||
end
|
||||
|
||||
override :update_applications_cluster_path
|
||||
def update_applications_cluster_path(cluster, application)
|
||||
update_applications_group_cluster_path(clusterable, cluster, application)
|
||||
end
|
||||
|
||||
override :cluster_path
|
||||
def cluster_path(cluster, params = {})
|
||||
group_cluster_path(clusterable, cluster, params)
|
||||
|
|
|
@ -14,6 +14,11 @@ class ProjectClusterablePresenter < ClusterablePresenter
|
|||
install_applications_project_cluster_path(clusterable, cluster, application)
|
||||
end
|
||||
|
||||
override :update_applications_cluster_path
|
||||
def update_applications_cluster_path(cluster, application)
|
||||
update_applications_project_cluster_path(clusterable, cluster, application)
|
||||
end
|
||||
|
||||
override :cluster_path
|
||||
def cluster_path(cluster, params = {})
|
||||
project_cluster_path(clusterable, cluster, params)
|
||||
|
|
|
@ -46,6 +46,10 @@ module Clusters
|
|||
@install_command ||= app.install_command
|
||||
end
|
||||
|
||||
def update_command
|
||||
@update_command ||= app.update_command
|
||||
end
|
||||
|
||||
def upgrade_command(new_values = "")
|
||||
app.upgrade_command(new_values)
|
||||
end
|
||||
|
|
76
app/services/clusters/applications/base_service.rb
Normal file
76
app/services/clusters/applications/base_service.rb
Normal file
|
@ -0,0 +1,76 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Clusters
|
||||
module Applications
|
||||
class BaseService
|
||||
InvalidApplicationError = Class.new(StandardError)
|
||||
|
||||
attr_reader :cluster, :current_user, :params
|
||||
|
||||
def initialize(cluster, user, params = {})
|
||||
@cluster = cluster
|
||||
@current_user = user
|
||||
@params = params.dup
|
||||
end
|
||||
|
||||
def execute(request)
|
||||
instantiate_application.tap do |application|
|
||||
if application.has_attribute?(:hostname)
|
||||
application.hostname = params[:hostname]
|
||||
end
|
||||
|
||||
if application.has_attribute?(:email)
|
||||
application.email = params[:email]
|
||||
end
|
||||
|
||||
if application.respond_to?(:oauth_application)
|
||||
application.oauth_application = create_oauth_application(application, request)
|
||||
end
|
||||
|
||||
worker = worker_class(application)
|
||||
|
||||
application.make_scheduled!
|
||||
|
||||
worker.perform_async(application.name, application.id)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def worker_class(application)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def builders
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def project_builders
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def instantiate_application
|
||||
builder.call(@cluster) || raise(InvalidApplicationError, "invalid application: #{application_name}")
|
||||
end
|
||||
|
||||
def builder
|
||||
builders[application_name] || raise(InvalidApplicationError, "invalid application: #{application_name}")
|
||||
end
|
||||
|
||||
def application_name
|
||||
params[:application]
|
||||
end
|
||||
|
||||
def create_oauth_application(application, request)
|
||||
oauth_application_params = {
|
||||
name: params[:application],
|
||||
redirect_uri: application.callback_url,
|
||||
scopes: 'api read_user openid',
|
||||
owner: current_user
|
||||
}
|
||||
|
||||
::Applications::CreateService.new(current_user, oauth_application_params).execute(request)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,47 +2,11 @@
|
|||
|
||||
module Clusters
|
||||
module Applications
|
||||
class CreateService
|
||||
InvalidApplicationError = Class.new(StandardError)
|
||||
|
||||
attr_reader :cluster, :current_user, :params
|
||||
|
||||
def initialize(cluster, user, params = {})
|
||||
@cluster = cluster
|
||||
@current_user = user
|
||||
@params = params.dup
|
||||
end
|
||||
|
||||
def execute(request)
|
||||
create_application.tap do |application|
|
||||
if application.has_attribute?(:hostname)
|
||||
application.hostname = params[:hostname]
|
||||
end
|
||||
|
||||
if application.has_attribute?(:email)
|
||||
application.email = params[:email]
|
||||
end
|
||||
|
||||
if application.respond_to?(:oauth_application)
|
||||
application.oauth_application = create_oauth_application(application, request)
|
||||
end
|
||||
|
||||
worker = application.updateable? ? ClusterUpgradeAppWorker : ClusterInstallAppWorker
|
||||
|
||||
application.make_scheduled!
|
||||
|
||||
worker.perform_async(application.name, application.id)
|
||||
end
|
||||
end
|
||||
|
||||
class CreateService < Clusters::Applications::BaseService
|
||||
private
|
||||
|
||||
def create_application
|
||||
builder.call(@cluster)
|
||||
end
|
||||
|
||||
def builder
|
||||
builders[application_name] || raise(InvalidApplicationError, "invalid application: #{application_name}")
|
||||
def worker_class(application)
|
||||
application.updateable? ? ClusterUpgradeAppWorker : ClusterInstallAppWorker
|
||||
end
|
||||
|
||||
def builders
|
||||
|
@ -65,21 +29,6 @@ module Clusters
|
|||
"knative" => -> (cluster) { cluster.application_knative || cluster.build_application_knative }
|
||||
}
|
||||
end
|
||||
|
||||
def application_name
|
||||
params[:application]
|
||||
end
|
||||
|
||||
def create_oauth_application(application, request)
|
||||
oauth_application_params = {
|
||||
name: params[:application],
|
||||
redirect_uri: application.callback_url,
|
||||
scopes: 'api read_user openid',
|
||||
owner: current_user
|
||||
}
|
||||
|
||||
::Applications::CreateService.new(current_user, oauth_application_params).execute(request)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,6 @@ module Clusters
|
|||
def execute
|
||||
return unless app.scheduled?
|
||||
|
||||
begin
|
||||
app.make_installing!
|
||||
helm_api.install(install_command)
|
||||
|
||||
|
@ -22,4 +21,3 @@ module Clusters
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
24
app/services/clusters/applications/patch_service.rb
Normal file
24
app/services/clusters/applications/patch_service.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Clusters
|
||||
module Applications
|
||||
class PatchService < BaseHelmService
|
||||
def execute
|
||||
return unless app.scheduled?
|
||||
|
||||
app.make_updating!
|
||||
|
||||
helm_api.update(update_command)
|
||||
|
||||
ClusterWaitForAppInstallationWorker.perform_in(
|
||||
ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
|
||||
rescue Kubeclient::HttpError => e
|
||||
log_error(e)
|
||||
app.make_update_errored!("Kubernetes error: #{e.error_code}")
|
||||
rescue StandardError => e
|
||||
log_error(e)
|
||||
app.make_update_errored!("Can't start update process.")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
34
app/services/clusters/applications/update_service.rb
Normal file
34
app/services/clusters/applications/update_service.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Clusters
|
||||
module Applications
|
||||
class UpdateService < Clusters::Applications::BaseService
|
||||
private
|
||||
|
||||
def worker_class(application)
|
||||
ClusterPatchAppWorker
|
||||
end
|
||||
|
||||
def builders
|
||||
{
|
||||
"helm" => -> (cluster) { cluster.application_helm },
|
||||
"ingress" => -> (cluster) { cluster.application_ingress },
|
||||
"cert_manager" => -> (cluster) { cluster.application_cert_manager }
|
||||
}.tap do |hash|
|
||||
hash.merge!(project_builders) if cluster.project_type?
|
||||
end
|
||||
end
|
||||
|
||||
# These applications will need extra configuration to enable them to work
|
||||
# with groups of projects
|
||||
def project_builders
|
||||
{
|
||||
"prometheus" => -> (cluster) { cluster.application_prometheus },
|
||||
"runner" => -> (cluster) { cluster.application_runner },
|
||||
"jupyter" => -> (cluster) { cluster.application_jupyter },
|
||||
"knative" => -> (cluster) { cluster.application_knative }
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,6 +15,7 @@
|
|||
install_runner_path: clusterable.install_applications_cluster_path(@cluster, :runner),
|
||||
install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter),
|
||||
install_knative_path: clusterable.install_applications_cluster_path(@cluster, :knative),
|
||||
update_knative_path: clusterable.update_applications_cluster_path(@cluster, :knative),
|
||||
toggle_status: @cluster.enabled? ? 'true': 'false',
|
||||
has_rbac: @cluster.platform_kubernetes_rbac? ? 'true': 'false',
|
||||
cluster_type: @cluster.cluster_type,
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
- cronjob:prune_web_hook_logs
|
||||
|
||||
- gcp_cluster:cluster_install_app
|
||||
- gcp_cluster:cluster_patch_app
|
||||
- gcp_cluster:cluster_upgrade_app
|
||||
- gcp_cluster:cluster_provision
|
||||
- gcp_cluster:cluster_wait_for_app_installation
|
||||
|
|
13
app/workers/cluster_patch_app_worker.rb
Normal file
13
app/workers/cluster_patch_app_worker.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ClusterPatchAppWorker
|
||||
include ApplicationWorker
|
||||
include ClusterQueue
|
||||
include ClusterApplications
|
||||
|
||||
def perform(app_name, app_id)
|
||||
find_application(app_name, app_id) do |app|
|
||||
Clusters::Applications::PatchService.new(app).execute
|
||||
end
|
||||
end
|
||||
end
|
5
changelogs/unreleased/56937-edit-knative-domain.yml
Normal file
5
changelogs/unreleased/56937-edit-knative-domain.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Edit Knative domain after it has been deployed
|
||||
merge_request: 25386
|
||||
author:
|
||||
type: added
|
|
@ -101,6 +101,7 @@ Rails.application.routes.draw do
|
|||
member do
|
||||
scope :applications do
|
||||
post '/:application', to: 'clusters/applications#create', as: :install_applications
|
||||
patch '/:application', to: 'clusters/applications#update', as: :update_applications
|
||||
end
|
||||
|
||||
get :cluster_status, format: :json
|
||||
|
|
|
@ -7,7 +7,8 @@ module Gitlab
|
|||
include BaseCommand
|
||||
include ClientCommand
|
||||
|
||||
attr_reader :name, :files, :chart, :version, :repository, :preinstall, :postinstall
|
||||
attr_reader :name, :files, :chart, :repository, :preinstall, :postinstall
|
||||
attr_accessor :version
|
||||
|
||||
def initialize(name:, chart:, files:, rbac:, version: nil, repository: nil, preinstall: nil, postinstall: nil)
|
||||
@name = name
|
||||
|
|
|
@ -1698,7 +1698,7 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
|
||||
msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Copy Kubernetes cluster name"
|
||||
|
@ -1821,7 +1821,7 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Knative Domain Name:"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Knative IP Address:"
|
||||
msgid "ClusterIntegration|Knative Endpoint:"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
|
||||
|
@ -1941,7 +1941,7 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Request to begin installing failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Retry upgrade"
|
||||
msgid "ClusterIntegration|Retry update"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Save changes"
|
||||
|
@ -1986,9 +1986,6 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Something went wrong on our end."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2007,12 +2004,21 @@ msgstr ""
|
|||
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Toggle Kubernetes cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Token"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Update failed. Please check the logs and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Updating"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Upgrade"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -9,9 +9,25 @@ describe Groups::Clusters::ApplicationsController do
|
|||
Clusters::Cluster::APPLICATIONS[application]
|
||||
end
|
||||
|
||||
describe 'POST create' do
|
||||
shared_examples 'a secure endpoint' do
|
||||
it { expect { subject }.to be_allowed_for(:admin) }
|
||||
it { expect { subject }.to be_allowed_for(:owner).of(group) }
|
||||
it { expect { subject }.to be_allowed_for(:maintainer).of(group) }
|
||||
it { expect { subject }.to be_denied_for(:developer).of(group) }
|
||||
it { expect { subject }.to be_denied_for(:reporter).of(group) }
|
||||
it { expect { subject }.to be_denied_for(:guest).of(group) }
|
||||
it { expect { subject }.to be_denied_for(:user) }
|
||||
it { expect { subject }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
|
||||
let(:group) { cluster.group }
|
||||
|
||||
describe 'POST create' do
|
||||
subject do
|
||||
post :create, params: params.merge(group_id: group)
|
||||
end
|
||||
|
||||
let(:application) { 'helm' }
|
||||
let(:params) { { application: application, id: cluster.id } }
|
||||
|
||||
|
@ -26,7 +42,7 @@ describe Groups::Clusters::ApplicationsController do
|
|||
it 'schedule an application installation' do
|
||||
expect(ClusterInstallAppWorker).to receive(:perform_async).with(application, anything).once
|
||||
|
||||
expect { go }.to change { current_application.count }
|
||||
expect { subject }.to change { current_application.count }
|
||||
expect(response).to have_http_status(:no_content)
|
||||
expect(cluster.application_helm).to be_scheduled
|
||||
end
|
||||
|
@ -37,7 +53,7 @@ describe Groups::Clusters::ApplicationsController do
|
|||
end
|
||||
|
||||
it 'return 404' do
|
||||
expect { go }.not_to change { current_application.count }
|
||||
expect { subject }.not_to change { current_application.count }
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
@ -46,9 +62,7 @@ describe Groups::Clusters::ApplicationsController do
|
|||
let(:application) { 'unkwnown-app' }
|
||||
|
||||
it 'return 404' do
|
||||
go
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
is_expected.to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -58,9 +72,7 @@ describe Groups::Clusters::ApplicationsController do
|
|||
end
|
||||
|
||||
it 'returns 400' do
|
||||
go
|
||||
|
||||
expect(response).to have_http_status(:bad_request)
|
||||
is_expected.to have_http_status(:bad_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -70,18 +82,66 @@ describe Groups::Clusters::ApplicationsController do
|
|||
allow(ClusterInstallAppWorker).to receive(:perform_async)
|
||||
end
|
||||
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(group) }
|
||||
it { expect { go }.to be_allowed_for(:maintainer).of(group) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(group) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(group) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(group) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
it_behaves_like 'a secure endpoint'
|
||||
end
|
||||
end
|
||||
|
||||
def go
|
||||
post :create, params: params.merge(group_id: group)
|
||||
describe 'PATCH update' do
|
||||
subject do
|
||||
patch :update, params: params.merge(group_id: group)
|
||||
end
|
||||
|
||||
let!(:application) { create(:clusters_applications_cert_managers, :installed, cluster: cluster) }
|
||||
let(:application_name) { application.name }
|
||||
let(:params) { { application: application_name, id: cluster.id, email: "new-email@example.com" } }
|
||||
|
||||
describe 'functionality' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
group.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context "when cluster and app exists" do
|
||||
it "schedules an application update" do
|
||||
expect(ClusterPatchAppWorker).to receive(:perform_async).with(application.name, anything).once
|
||||
|
||||
is_expected.to have_http_status(:no_content)
|
||||
|
||||
expect(cluster.application_cert_manager).to be_scheduled
|
||||
end
|
||||
end
|
||||
|
||||
context 'when cluster do not exists' do
|
||||
before do
|
||||
cluster.destroy!
|
||||
end
|
||||
|
||||
it { is_expected.to have_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'when application is unknown' do
|
||||
let(:application_name) { 'unkwnown-app' }
|
||||
|
||||
it { is_expected.to have_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'when application is already scheduled' do
|
||||
before do
|
||||
application.make_scheduled!
|
||||
end
|
||||
|
||||
it { is_expected.to have_http_status(:bad_request) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
before do
|
||||
allow(ClusterPatchAppWorker).to receive(:perform_async)
|
||||
end
|
||||
|
||||
it_behaves_like 'a secure endpoint'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,7 +9,22 @@ describe Projects::Clusters::ApplicationsController do
|
|||
Clusters::Cluster::APPLICATIONS[application]
|
||||
end
|
||||
|
||||
shared_examples 'a secure endpoint' do
|
||||
it { expect { subject }.to be_allowed_for(:admin) }
|
||||
it { expect { subject }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { subject }.to be_allowed_for(:maintainer).of(project) }
|
||||
it { expect { subject }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { subject }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { subject }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { subject }.to be_denied_for(:user) }
|
||||
it { expect { subject }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
describe 'POST create' do
|
||||
subject do
|
||||
post :create, params: params.merge(namespace_id: project.namespace, project_id: project)
|
||||
end
|
||||
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:project) { cluster.project }
|
||||
let(:application) { 'helm' }
|
||||
|
@ -26,7 +41,7 @@ describe Projects::Clusters::ApplicationsController do
|
|||
it 'schedule an application installation' do
|
||||
expect(ClusterInstallAppWorker).to receive(:perform_async).with(application, anything).once
|
||||
|
||||
expect { go }.to change { current_application.count }
|
||||
expect { subject }.to change { current_application.count }
|
||||
expect(response).to have_http_status(:no_content)
|
||||
expect(cluster.application_helm).to be_scheduled
|
||||
end
|
||||
|
@ -37,7 +52,7 @@ describe Projects::Clusters::ApplicationsController do
|
|||
end
|
||||
|
||||
it 'return 404' do
|
||||
expect { go }.not_to change { current_application.count }
|
||||
expect { subject }.not_to change { current_application.count }
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
@ -46,9 +61,7 @@ describe Projects::Clusters::ApplicationsController do
|
|||
let(:application) { 'unkwnown-app' }
|
||||
|
||||
it 'return 404' do
|
||||
go
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
is_expected.to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -58,9 +71,7 @@ describe Projects::Clusters::ApplicationsController do
|
|||
end
|
||||
|
||||
it 'returns 400' do
|
||||
go
|
||||
|
||||
expect(response).to have_http_status(:bad_request)
|
||||
is_expected.to have_http_status(:bad_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -70,18 +81,68 @@ describe Projects::Clusters::ApplicationsController do
|
|||
allow(ClusterInstallAppWorker).to receive(:perform_async)
|
||||
end
|
||||
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
it_behaves_like 'a secure endpoint'
|
||||
end
|
||||
end
|
||||
|
||||
def go
|
||||
post :create, params: params.merge(namespace_id: project.namespace, project_id: project)
|
||||
describe 'PATCH update' do
|
||||
subject do
|
||||
patch :update, params: params.merge(namespace_id: project.namespace, project_id: project)
|
||||
end
|
||||
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:project) { cluster.project }
|
||||
let!(:application) { create(:clusters_applications_knative, :installed, cluster: cluster) }
|
||||
let(:application_name) { application.name }
|
||||
let(:params) { { application: application_name, id: cluster.id, hostname: "new.example.com" } }
|
||||
|
||||
describe 'functionality' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context "when cluster and app exists" do
|
||||
it "schedules an application update" do
|
||||
expect(ClusterPatchAppWorker).to receive(:perform_async).with(application.name, anything).once
|
||||
|
||||
is_expected.to have_http_status(:no_content)
|
||||
|
||||
expect(cluster.application_knative).to be_scheduled
|
||||
end
|
||||
end
|
||||
|
||||
context 'when cluster do not exists' do
|
||||
before do
|
||||
cluster.destroy!
|
||||
end
|
||||
|
||||
it { is_expected.to have_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'when application is unknown' do
|
||||
let(:application_name) { 'unkwnown-app' }
|
||||
|
||||
it { is_expected.to have_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'when application is already scheduled' do
|
||||
before do
|
||||
application.make_scheduled!
|
||||
end
|
||||
|
||||
it { is_expected.to have_http_status(:bad_request) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
before do
|
||||
allow(ClusterPatchAppWorker).to receive(:perform_async)
|
||||
end
|
||||
|
||||
it_behaves_like 'a secure endpoint'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,6 +31,8 @@ describe 'Clusters Applications', :js do
|
|||
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
|
||||
|
||||
it 'user can install applications' do
|
||||
wait_for_requests
|
||||
|
||||
page.within('.js-cluster-application-row-helm') do
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to be_nil
|
||||
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Install')
|
||||
|
@ -44,6 +46,8 @@ describe 'Clusters Applications', :js do
|
|||
page.within('.js-cluster-application-row-helm') do
|
||||
page.find(:css, '.js-cluster-application-install-button').click
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
it 'they see status transition' do
|
||||
|
@ -52,8 +56,6 @@ describe 'Clusters Applications', :js do
|
|||
expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
|
||||
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
|
||||
|
||||
wait_until_helm_created!
|
||||
|
||||
Clusters::Cluster.last.application_helm.make_installing!
|
||||
|
||||
# FE starts polling and update the buttons to "Installing"
|
||||
|
@ -95,6 +97,60 @@ describe 'Clusters Applications', :js do
|
|||
expect(page).to have_css('.js-cluster-application-install-button:not([disabled])')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user clicks install button' do
|
||||
def domainname_form_value
|
||||
page.find('.js-knative-domainname').value
|
||||
end
|
||||
|
||||
before do
|
||||
allow(ClusterInstallAppWorker).to receive(:perform_async)
|
||||
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
|
||||
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
|
||||
|
||||
page.within('.js-cluster-application-row-knative') do
|
||||
expect(page).to have_css('.js-cluster-application-install-button:not([disabled])')
|
||||
|
||||
page.find('.js-knative-domainname').set("domain.example.org")
|
||||
|
||||
click_button 'Install'
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
|
||||
|
||||
Clusters::Cluster.last.application_knative.make_installing!
|
||||
Clusters::Cluster.last.application_knative.make_installed!
|
||||
Clusters::Cluster.last.application_knative.update_attribute(:external_ip, '127.0.0.1')
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows status transition' do
|
||||
page.within('.js-cluster-application-row-knative') do
|
||||
expect(domainname_form_value).to eq('domain.example.org')
|
||||
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed')
|
||||
end
|
||||
|
||||
expect(page).to have_content('Knative was successfully installed on your Kubernetes cluster')
|
||||
expect(page).to have_css('.js-knative-save-domain-button'), exact_text: 'Save changes'
|
||||
end
|
||||
|
||||
it 'can then update the domain' do
|
||||
page.within('.js-cluster-application-row-knative') do
|
||||
expect(ClusterPatchAppWorker).to receive(:perform_async)
|
||||
|
||||
expect(domainname_form_value).to eq('domain.example.org')
|
||||
|
||||
page.find('.js-knative-domainname').set("new.domain.example.org")
|
||||
|
||||
click_button 'Save changes'
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(domainname_form_value).to eq('new.domain.example.org')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -148,6 +204,8 @@ describe 'Clusters Applications', :js do
|
|||
page.within('.js-cluster-application-row-ingress') do
|
||||
expect(page).to have_css('.js-cluster-application-install-button:not([disabled])')
|
||||
page.find(:css, '.js-cluster-application-install-button').click
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -184,14 +242,4 @@ describe 'Clusters Applications', :js do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def wait_until_helm_created!
|
||||
retries = 0
|
||||
|
||||
while Clusters::Cluster.last.application_helm.nil?
|
||||
raise "Timed out waiting for helm application to be created in DB" if (retries += 1) > 3
|
||||
|
||||
sleep(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -230,7 +230,7 @@ describe('Application Row', () => {
|
|||
expect(upgradeBtn.innerHTML).toContain('Upgrade');
|
||||
});
|
||||
|
||||
it('has enabled "Retry upgrade" when APPLICATION_STATUS.UPDATE_ERRORED', () => {
|
||||
it('has enabled "Retry update" when APPLICATION_STATUS.UPDATE_ERRORED', () => {
|
||||
vm = mountComponent(ApplicationRow, {
|
||||
...DEFAULT_APPLICATION_STATE,
|
||||
status: APPLICATION_STATUS.UPDATE_ERRORED,
|
||||
|
@ -239,10 +239,10 @@ describe('Application Row', () => {
|
|||
|
||||
expect(upgradeBtn).not.toBe(null);
|
||||
expect(vm.upgradeFailed).toBe(true);
|
||||
expect(upgradeBtn.innerHTML).toContain('Retry upgrade');
|
||||
expect(upgradeBtn.innerHTML).toContain('Retry update');
|
||||
});
|
||||
|
||||
it('has disabled "Retry upgrade" when APPLICATION_STATUS.UPDATING', () => {
|
||||
it('has disabled "Updating" when APPLICATION_STATUS.UPDATING', () => {
|
||||
vm = mountComponent(ApplicationRow, {
|
||||
...DEFAULT_APPLICATION_STATE,
|
||||
status: APPLICATION_STATUS.UPDATING,
|
||||
|
@ -251,7 +251,7 @@ describe('Application Row', () => {
|
|||
|
||||
expect(upgradeBtn).not.toBe(null);
|
||||
expect(vm.isUpgrading).toBe(true);
|
||||
expect(upgradeBtn.innerHTML).toContain('Upgrading');
|
||||
expect(upgradeBtn.innerHTML).toContain('Updating');
|
||||
});
|
||||
|
||||
it('clicking upgrade button emits event', () => {
|
||||
|
@ -295,7 +295,7 @@ describe('Application Row', () => {
|
|||
|
||||
expect(failureMessage).not.toBe(null);
|
||||
expect(failureMessage.innerHTML).toContain(
|
||||
'Something went wrong when upgrading GitLab Runner. Please check the logs and try again.',
|
||||
'Update failed. Please check the logs and try again.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import Vue from 'vue';
|
||||
import applications from '~/clusters/components/applications.vue';
|
||||
import { CLUSTER_TYPE } from '~/clusters/constants';
|
||||
import eventHub from '~/clusters/event_hub';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import { APPLICATIONS_MOCK_STATE } from '../services/mock_data';
|
||||
|
||||
describe('Applications', () => {
|
||||
let vm;
|
||||
|
@ -18,16 +20,8 @@ describe('Applications', () => {
|
|||
describe('Project cluster applications', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Applications, {
|
||||
applications: APPLICATIONS_MOCK_STATE,
|
||||
type: CLUSTER_TYPE.PROJECT,
|
||||
applications: {
|
||||
helm: { title: 'Helm Tiller' },
|
||||
ingress: { title: 'Ingress' },
|
||||
cert_manager: { title: 'Cert-Manager' },
|
||||
runner: { title: 'GitLab Runner' },
|
||||
prometheus: { title: 'Prometheus' },
|
||||
jupyter: { title: 'JupyterHub' },
|
||||
knative: { title: 'Knative' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -64,15 +58,7 @@ describe('Applications', () => {
|
|||
beforeEach(() => {
|
||||
vm = mountComponent(Applications, {
|
||||
type: CLUSTER_TYPE.GROUP,
|
||||
applications: {
|
||||
helm: { title: 'Helm Tiller' },
|
||||
ingress: { title: 'Ingress' },
|
||||
cert_manager: { title: 'Cert-Manager' },
|
||||
runner: { title: 'GitLab Runner' },
|
||||
prometheus: { title: 'Prometheus' },
|
||||
jupyter: { title: 'JupyterHub' },
|
||||
knative: { title: 'Knative' },
|
||||
},
|
||||
applications: APPLICATIONS_MOCK_STATE,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -111,17 +97,12 @@ describe('Applications', () => {
|
|||
it('renders ip address with a clipboard button', () => {
|
||||
vm = mountComponent(Applications, {
|
||||
applications: {
|
||||
...APPLICATIONS_MOCK_STATE,
|
||||
ingress: {
|
||||
title: 'Ingress',
|
||||
status: 'installed',
|
||||
externalIp: '0.0.0.0',
|
||||
},
|
||||
helm: { title: 'Helm Tiller' },
|
||||
cert_manager: { title: 'Cert-Manager' },
|
||||
runner: { title: 'GitLab Runner' },
|
||||
prometheus: { title: 'Prometheus' },
|
||||
jupyter: { title: 'JupyterHub', hostname: '' },
|
||||
knative: { title: 'Knative', hostname: '' },
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -137,16 +118,11 @@ describe('Applications', () => {
|
|||
it('renders an input text with a question mark and an alert text', () => {
|
||||
vm = mountComponent(Applications, {
|
||||
applications: {
|
||||
...APPLICATIONS_MOCK_STATE,
|
||||
ingress: {
|
||||
title: 'Ingress',
|
||||
status: 'installed',
|
||||
},
|
||||
helm: { title: 'Helm Tiller' },
|
||||
cert_manager: { title: 'Cert-Manager' },
|
||||
runner: { title: 'GitLab Runner' },
|
||||
prometheus: { title: 'Prometheus' },
|
||||
jupyter: { title: 'JupyterHub', hostname: '' },
|
||||
knative: { title: 'Knative', hostname: '' },
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -160,15 +136,7 @@ describe('Applications', () => {
|
|||
describe('before installing', () => {
|
||||
it('does not render the IP address', () => {
|
||||
vm = mountComponent(Applications, {
|
||||
applications: {
|
||||
helm: { title: 'Helm Tiller' },
|
||||
ingress: { title: 'Ingress' },
|
||||
cert_manager: { title: 'Cert-Manager' },
|
||||
runner: { title: 'GitLab Runner' },
|
||||
prometheus: { title: 'Prometheus' },
|
||||
jupyter: { title: 'JupyterHub', hostname: '' },
|
||||
knative: { title: 'Knative', hostname: '' },
|
||||
},
|
||||
applications: APPLICATIONS_MOCK_STATE,
|
||||
});
|
||||
|
||||
expect(vm.$el.textContent).not.toContain('Ingress IP Address');
|
||||
|
@ -181,17 +149,12 @@ describe('Applications', () => {
|
|||
it('renders email & allows editing', () => {
|
||||
vm = mountComponent(Applications, {
|
||||
applications: {
|
||||
helm: { title: 'Helm Tiller', status: 'installed' },
|
||||
ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
|
||||
...APPLICATIONS_MOCK_STATE,
|
||||
cert_manager: {
|
||||
title: 'Cert-Manager',
|
||||
email: 'before@example.com',
|
||||
status: 'installable',
|
||||
},
|
||||
runner: { title: 'GitLab Runner' },
|
||||
prometheus: { title: 'Prometheus' },
|
||||
jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
|
||||
knative: { title: 'Knative', hostname: '', status: 'installable' },
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -204,17 +167,12 @@ describe('Applications', () => {
|
|||
it('renders email in readonly', () => {
|
||||
vm = mountComponent(Applications, {
|
||||
applications: {
|
||||
helm: { title: 'Helm Tiller', status: 'installed' },
|
||||
ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
|
||||
...APPLICATIONS_MOCK_STATE,
|
||||
cert_manager: {
|
||||
title: 'Cert-Manager',
|
||||
email: 'after@example.com',
|
||||
status: 'installed',
|
||||
},
|
||||
runner: { title: 'GitLab Runner' },
|
||||
prometheus: { title: 'Prometheus' },
|
||||
jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
|
||||
knative: { title: 'Knative', hostname: '', status: 'installable' },
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -229,13 +187,12 @@ describe('Applications', () => {
|
|||
it('renders hostname active input', () => {
|
||||
vm = mountComponent(Applications, {
|
||||
applications: {
|
||||
helm: { title: 'Helm Tiller', status: 'installed' },
|
||||
ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
|
||||
cert_manager: { title: 'Cert-Manager' },
|
||||
runner: { title: 'GitLab Runner' },
|
||||
prometheus: { title: 'Prometheus' },
|
||||
jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
|
||||
knative: { title: 'Knative', hostname: '', status: 'installable' },
|
||||
...APPLICATIONS_MOCK_STATE,
|
||||
ingress: {
|
||||
title: 'Ingress',
|
||||
status: 'installed',
|
||||
externalIp: '1.1.1.1',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -247,13 +204,8 @@ describe('Applications', () => {
|
|||
it('does not render hostname input', () => {
|
||||
vm = mountComponent(Applications, {
|
||||
applications: {
|
||||
helm: { title: 'Helm Tiller', status: 'installed' },
|
||||
...APPLICATIONS_MOCK_STATE,
|
||||
ingress: { title: 'Ingress', status: 'installed' },
|
||||
cert_manager: { title: 'Cert-Manager' },
|
||||
runner: { title: 'GitLab Runner' },
|
||||
prometheus: { title: 'Prometheus' },
|
||||
jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
|
||||
knative: { title: 'Knative', hostname: '', status: 'installable' },
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -265,13 +217,9 @@ describe('Applications', () => {
|
|||
it('renders readonly input', () => {
|
||||
vm = mountComponent(Applications, {
|
||||
applications: {
|
||||
helm: { title: 'Helm Tiller', status: 'installed' },
|
||||
...APPLICATIONS_MOCK_STATE,
|
||||
ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
|
||||
cert_manager: { title: 'Cert-Manager' },
|
||||
runner: { title: 'GitLab Runner' },
|
||||
prometheus: { title: 'Prometheus' },
|
||||
jupyter: { title: 'JupyterHub', status: 'installed', hostname: '' },
|
||||
knative: { title: 'Knative', status: 'installed', hostname: '' },
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -282,15 +230,7 @@ describe('Applications', () => {
|
|||
describe('without ingress installed', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Applications, {
|
||||
applications: {
|
||||
helm: { title: 'Helm Tiller' },
|
||||
ingress: { title: 'Ingress' },
|
||||
cert_manager: { title: 'Cert-Manager' },
|
||||
runner: { title: 'GitLab Runner' },
|
||||
prometheus: { title: 'Prometheus' },
|
||||
jupyter: { title: 'JupyterHub', status: 'not_installable' },
|
||||
knative: { title: 'Knative' },
|
||||
},
|
||||
applications: APPLICATIONS_MOCK_STATE,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -310,4 +250,77 @@ describe('Applications', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Knative application', () => {
|
||||
describe('when installed', () => {
|
||||
describe('with ip address', () => {
|
||||
const props = {
|
||||
applications: {
|
||||
...APPLICATIONS_MOCK_STATE,
|
||||
knative: {
|
||||
title: 'Knative',
|
||||
hostname: 'example.com',
|
||||
status: 'installed',
|
||||
externalIp: '1.1.1.1',
|
||||
},
|
||||
},
|
||||
};
|
||||
it('renders ip address with a clipboard button', () => {
|
||||
vm = mountComponent(Applications, props);
|
||||
|
||||
expect(vm.$el.querySelector('.js-knative-ip-address').value).toEqual('1.1.1.1');
|
||||
|
||||
expect(
|
||||
vm.$el
|
||||
.querySelector('.js-knative-ip-clipboard-btn')
|
||||
.getAttribute('data-clipboard-text'),
|
||||
).toEqual('1.1.1.1');
|
||||
});
|
||||
|
||||
it('renders domain & allows editing', () => {
|
||||
expect(vm.$el.querySelector('.js-knative-domainname').value).toEqual('example.com');
|
||||
expect(vm.$el.querySelector('.js-knative-domainname').getAttribute('readonly')).toBe(
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders an update/save Knative domain button', () => {
|
||||
expect(vm.$el.querySelector('.js-knative-save-domain-button')).not.toBe(null);
|
||||
});
|
||||
|
||||
it('emits event when clicking Save changes button', () => {
|
||||
spyOn(eventHub, '$emit');
|
||||
vm = mountComponent(Applications, props);
|
||||
|
||||
const saveButton = vm.$el.querySelector('.js-knative-save-domain-button');
|
||||
|
||||
saveButton.click();
|
||||
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', {
|
||||
id: 'knative',
|
||||
params: { hostname: 'example.com' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('without ip address', () => {
|
||||
it('renders an input text with a question mark and an alert text', () => {
|
||||
vm = mountComponent(Applications, {
|
||||
applications: {
|
||||
...APPLICATIONS_MOCK_STATE,
|
||||
knative: {
|
||||
title: 'Knative',
|
||||
hostname: 'example.com',
|
||||
status: 'installed',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(vm.$el.querySelector('.js-knative-ip-address').value).toEqual('?');
|
||||
|
||||
expect(vm.$el.querySelector('.js-no-knative-ip-message')).not.toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -115,4 +115,14 @@ const DEFAULT_APPLICATION_STATE = {
|
|||
requestReason: null,
|
||||
};
|
||||
|
||||
export { CLUSTERS_MOCK_DATA, DEFAULT_APPLICATION_STATE };
|
||||
const APPLICATIONS_MOCK_STATE = {
|
||||
helm: { title: 'Helm Tiller', status: 'installable' },
|
||||
ingress: { title: 'Ingress', status: 'installable' },
|
||||
cert_manager: { title: 'Cert-Manager', status: 'installable' },
|
||||
runner: { title: 'GitLab Runner' },
|
||||
prometheus: { title: 'Prometheus' },
|
||||
jupyter: { title: 'JupyterHub', status: 'installable', hostname: '' },
|
||||
knative: { title: 'Knative ', status: 'installable', hostname: '' },
|
||||
};
|
||||
|
||||
export { CLUSTERS_MOCK_DATA, DEFAULT_APPLICATION_STATE, APPLICATIONS_MOCK_STATE };
|
||||
|
|
|
@ -111,6 +111,7 @@ describe('Clusters Store', () => {
|
|||
requestStatus: null,
|
||||
requestReason: null,
|
||||
hostname: null,
|
||||
isEditingHostName: false,
|
||||
externalIp: null,
|
||||
},
|
||||
cert_manager: {
|
||||
|
|
|
@ -66,9 +66,7 @@ describe Clusters::Applications::Knative do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#install_command' do
|
||||
subject { knative.install_command }
|
||||
|
||||
shared_examples 'a command' do
|
||||
it 'should be an instance of Helm::InstallCommand' do
|
||||
expect(subject).to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand)
|
||||
end
|
||||
|
@ -76,7 +74,6 @@ describe Clusters::Applications::Knative do
|
|||
it 'should be initialized with knative arguments' do
|
||||
expect(subject.name).to eq('knative')
|
||||
expect(subject.chart).to eq('knative/knative')
|
||||
expect(subject.version).to eq('0.2.2')
|
||||
expect(subject.files).to eq(knative.files)
|
||||
end
|
||||
|
||||
|
@ -98,6 +95,27 @@ describe Clusters::Applications::Knative do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#install_command' do
|
||||
subject { knative.install_command }
|
||||
|
||||
it 'should be initialized with latest version' do
|
||||
expect(subject.version).to eq('0.2.2')
|
||||
end
|
||||
|
||||
it_behaves_like 'a command'
|
||||
end
|
||||
|
||||
describe '#update_command' do
|
||||
let!(:current_installed_version) { knative.version = '0.1.0' }
|
||||
subject { knative.update_command }
|
||||
|
||||
it 'should be initialized with current version' do
|
||||
expect(subject.version).to eq(current_installed_version)
|
||||
end
|
||||
|
||||
it_behaves_like 'a command'
|
||||
end
|
||||
|
||||
describe '#files' do
|
||||
let(:application) { knative }
|
||||
let(:values) { subject[:'values.yaml'] }
|
||||
|
|
|
@ -69,6 +69,14 @@ describe GroupClusterablePresenter do
|
|||
it { is_expected.to eq(install_applications_group_cluster_path(group, cluster, application)) }
|
||||
end
|
||||
|
||||
describe '#update_applications_cluster_path' do
|
||||
let(:application) { :helm }
|
||||
|
||||
subject { presenter.update_applications_cluster_path(cluster, application) }
|
||||
|
||||
it { is_expected.to eq(update_applications_group_cluster_path(group, cluster, application)) }
|
||||
end
|
||||
|
||||
describe '#cluster_path' do
|
||||
subject { presenter.cluster_path(cluster) }
|
||||
|
||||
|
|
|
@ -69,6 +69,14 @@ describe ProjectClusterablePresenter do
|
|||
it { is_expected.to eq(install_applications_project_cluster_path(project, cluster, application)) }
|
||||
end
|
||||
|
||||
describe '#update_applications_cluster_path' do
|
||||
let(:application) { :helm }
|
||||
|
||||
subject { presenter.update_applications_cluster_path(cluster, application) }
|
||||
|
||||
it { is_expected.to eq(update_applications_project_cluster_path(project, cluster, application)) }
|
||||
end
|
||||
|
||||
describe '#cluster_path' do
|
||||
subject { presenter.cluster_path(cluster) }
|
||||
|
||||
|
|
128
spec/services/clusters/applications/patch_service_spec.rb
Normal file
128
spec/services/clusters/applications/patch_service_spec.rb
Normal file
|
@ -0,0 +1,128 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Clusters::Applications::PatchService do
|
||||
describe '#execute' do
|
||||
let(:application) { create(:clusters_applications_knative, :scheduled) }
|
||||
let!(:update_command) { application.update_command }
|
||||
let(:service) { described_class.new(application) }
|
||||
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) }
|
||||
|
||||
before do
|
||||
allow(service).to receive(:update_command).and_return(update_command)
|
||||
allow(service).to receive(:helm_api).and_return(helm_client)
|
||||
end
|
||||
|
||||
context 'when there are no errors' do
|
||||
before do
|
||||
expect(helm_client).to receive(:update).with(update_command)
|
||||
allow(ClusterWaitForAppInstallationWorker).to receive(:perform_in).and_return(nil)
|
||||
end
|
||||
|
||||
it 'make the application updating' do
|
||||
expect(application.cluster).not_to be_nil
|
||||
service.execute
|
||||
|
||||
expect(application).to be_updating
|
||||
end
|
||||
|
||||
it 'schedule async installation status check' do
|
||||
expect(ClusterWaitForAppInstallationWorker).to receive(:perform_in).once
|
||||
|
||||
service.execute
|
||||
end
|
||||
end
|
||||
|
||||
context 'when kubernetes cluster communication fails' do
|
||||
let(:error) { Kubeclient::HttpError.new(500, 'system failure', nil) }
|
||||
|
||||
before do
|
||||
expect(helm_client).to receive(:update).with(update_command).and_raise(error)
|
||||
end
|
||||
|
||||
it 'make the application errored' do
|
||||
service.execute
|
||||
|
||||
expect(application).to be_update_errored
|
||||
expect(application.status_reason).to match('Kubernetes error: 500')
|
||||
end
|
||||
|
||||
it 'logs errors' do
|
||||
expect(service.send(:logger)).to receive(:error).with(
|
||||
{
|
||||
exception: 'Kubeclient::HttpError',
|
||||
message: 'system failure',
|
||||
service: 'Clusters::Applications::PatchService',
|
||||
app_id: application.id,
|
||||
project_ids: application.cluster.project_ids,
|
||||
group_ids: [],
|
||||
error_code: 500
|
||||
}
|
||||
)
|
||||
|
||||
expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
|
||||
error,
|
||||
extra: {
|
||||
exception: 'Kubeclient::HttpError',
|
||||
message: 'system failure',
|
||||
service: 'Clusters::Applications::PatchService',
|
||||
app_id: application.id,
|
||||
project_ids: application.cluster.project_ids,
|
||||
group_ids: [],
|
||||
error_code: 500
|
||||
}
|
||||
)
|
||||
|
||||
service.execute
|
||||
end
|
||||
end
|
||||
|
||||
context 'a non kubernetes error happens' do
|
||||
let(:application) { create(:clusters_applications_knative, :scheduled) }
|
||||
let(:error) { StandardError.new('something bad happened') }
|
||||
|
||||
before do
|
||||
expect(application).to receive(:make_updating!).once.and_raise(error)
|
||||
end
|
||||
|
||||
it 'make the application errored' do
|
||||
expect(helm_client).not_to receive(:update)
|
||||
|
||||
service.execute
|
||||
|
||||
expect(application).to be_update_errored
|
||||
expect(application.status_reason).to eq("Can't start update process.")
|
||||
end
|
||||
|
||||
it 'logs errors' do
|
||||
expect(service.send(:logger)).to receive(:error).with(
|
||||
{
|
||||
exception: 'StandardError',
|
||||
error_code: nil,
|
||||
message: 'something bad happened',
|
||||
service: 'Clusters::Applications::PatchService',
|
||||
app_id: application.id,
|
||||
project_ids: application.cluster.projects.pluck(:id),
|
||||
group_ids: []
|
||||
}
|
||||
)
|
||||
|
||||
expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
|
||||
error,
|
||||
extra: {
|
||||
exception: 'StandardError',
|
||||
error_code: nil,
|
||||
message: 'something bad happened',
|
||||
service: 'Clusters::Applications::PatchService',
|
||||
app_id: application.id,
|
||||
project_ids: application.cluster.projects.pluck(:id),
|
||||
group_ids: []
|
||||
}
|
||||
)
|
||||
|
||||
service.execute
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
72
spec/services/clusters/applications/update_service_spec.rb
Normal file
72
spec/services/clusters/applications/update_service_spec.rb
Normal file
|
@ -0,0 +1,72 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Clusters::Applications::UpdateService do
|
||||
include TestRequestHelpers
|
||||
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:user) { create(:user) }
|
||||
let(:params) { { application: 'knative', hostname: 'udpate.example.com' } }
|
||||
let(:service) { described_class.new(cluster, user, params) }
|
||||
|
||||
subject { service.execute(test_request) }
|
||||
|
||||
describe '#execute' do
|
||||
before do
|
||||
allow(ClusterPatchAppWorker).to receive(:perform_async)
|
||||
end
|
||||
|
||||
context 'application is not installed' do
|
||||
it 'raises Clusters::Applications::BaseService::InvalidApplicationError' do
|
||||
expect(ClusterPatchAppWorker).not_to receive(:perform_async)
|
||||
|
||||
expect { subject }
|
||||
.to raise_exception { Clusters::Applications::BaseService::InvalidApplicationError }
|
||||
.and not_change { Clusters::Applications::Knative.count }
|
||||
.and not_change { Clusters::Applications::Knative.with_status(:scheduled).count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'application is installed' do
|
||||
context 'application is schedulable' do
|
||||
let!(:application) do
|
||||
create(:clusters_applications_knative, status: 3, cluster: cluster)
|
||||
end
|
||||
|
||||
it 'updates the application data' do
|
||||
expect do
|
||||
subject
|
||||
end.to change { application.reload.hostname }.to(params[:hostname])
|
||||
end
|
||||
|
||||
it 'makes application scheduled!' do
|
||||
subject
|
||||
|
||||
expect(application.reload).to be_scheduled
|
||||
end
|
||||
|
||||
it 'schedules ClusterPatchAppWorker' do
|
||||
expect(ClusterPatchAppWorker).to receive(:perform_async)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'application is not schedulable' do
|
||||
let!(:application) do
|
||||
create(:clusters_applications_knative, status: 4, cluster: cluster)
|
||||
end
|
||||
|
||||
it 'raises StateMachines::InvalidTransition' do
|
||||
expect(ClusterPatchAppWorker).not_to receive(:perform_async)
|
||||
|
||||
expect { subject }
|
||||
.to raise_exception { StateMachines::InvalidTransition }
|
||||
.and not_change { application.reload.hostname }
|
||||
.and not_change { Clusters::Applications::Knative.with_status(:scheduled).count }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue