Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
0048c521
Commit
0048c521
authored
Apr 30, 2019
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab-ce master
parents
9d8394cc
cc2c513b
Changes
42
Hide whitespace changes
Inline
Side-by-side
Showing
42 changed files
with
842 additions
and
30 deletions
+842
-30
app/controllers/clusters/applications_controller.rb
app/controllers/clusters/applications_controller.rb
+13
-0
app/models/clusters/applications/cert_manager.rb
app/models/clusters/applications/cert_manager.rb
+6
-0
app/models/clusters/applications/helm.rb
app/models/clusters/applications/helm.rb
+7
-0
app/models/clusters/applications/ingress.rb
app/models/clusters/applications/ingress.rb
+7
-0
app/models/clusters/applications/jupyter.rb
app/models/clusters/applications/jupyter.rb
+6
-0
app/models/clusters/applications/knative.rb
app/models/clusters/applications/knative.rb
+6
-0
app/models/clusters/applications/prometheus.rb
app/models/clusters/applications/prometheus.rb
+17
-1
app/models/clusters/applications/runner.rb
app/models/clusters/applications/runner.rb
+7
-0
app/models/clusters/concerns/application_core.rb
app/models/clusters/concerns/application_core.rb
+10
-0
app/models/clusters/concerns/application_status.rb
app/models/clusters/concerns/application_status.rb
+10
-3
app/serializers/cluster_application_entity.rb
app/serializers/cluster_application_entity.rb
+1
-0
app/services/clusters/applications/check_installation_progress_service.rb
...sters/applications/check_installation_progress_service.rb
+3
-3
app/services/clusters/applications/check_uninstall_progress_service.rb
...clusters/applications/check_uninstall_progress_service.rb
+62
-0
app/services/clusters/applications/create_service.rb
app/services/clusters/applications/create_service.rb
+2
-2
app/services/clusters/applications/destroy_service.rb
app/services/clusters/applications/destroy_service.rb
+23
-0
app/services/clusters/applications/uninstall_service.rb
app/services/clusters/applications/uninstall_service.rb
+29
-0
app/services/clusters/applications/update_service.rb
app/services/clusters/applications/update_service.rb
+1
-1
app/workers/all_queues.yml
app/workers/all_queues.yml
+2
-0
app/workers/clusters/applications/uninstall_worker.rb
app/workers/clusters/applications/uninstall_worker.rb
+17
-0
app/workers/clusters/applications/wait_for_uninstall_app_worker.rb
...rs/clusters/applications/wait_for_uninstall_app_worker.rb
+20
-0
config/routes.rb
config/routes.rb
+1
-0
lib/gitlab/kubernetes/helm/api.rb
lib/gitlab/kubernetes/helm/api.rb
+7
-0
locale/gitlab.pot
locale/gitlab.pot
+3
-0
spec/controllers/projects/clusters/applications_controller_spec.rb
...rollers/projects/clusters/applications_controller_spec.rb
+62
-0
spec/factories/clusters/applications/helm.rb
spec/factories/clusters/applications/helm.rb
+14
-5
spec/fixtures/api/schemas/cluster_status.json
spec/fixtures/api/schemas/cluster_status.json
+2
-1
spec/lib/gitlab/kubernetes/helm/api_spec.rb
spec/lib/gitlab/kubernetes/helm/api_spec.rb
+22
-0
spec/models/clusters/applications/cert_manager_spec.rb
spec/models/clusters/applications/cert_manager_spec.rb
+6
-0
spec/models/clusters/applications/helm_spec.rb
spec/models/clusters/applications/helm_spec.rb
+8
-0
spec/models/clusters/applications/ingress_spec.rb
spec/models/clusters/applications/ingress_spec.rb
+6
-0
spec/models/clusters/applications/jupyter_spec.rb
spec/models/clusters/applications/jupyter_spec.rb
+9
-0
spec/models/clusters/applications/knative_spec.rb
spec/models/clusters/applications/knative_spec.rb
+6
-0
spec/models/clusters/applications/prometheus_spec.rb
spec/models/clusters/applications/prometheus_spec.rb
+52
-1
spec/models/clusters/applications/runner_spec.rb
spec/models/clusters/applications/runner_spec.rb
+8
-0
spec/serializers/cluster_application_entity_spec.rb
spec/serializers/cluster_application_entity_spec.rb
+4
-0
spec/services/clusters/applications/check_installation_progress_service_spec.rb
.../applications/check_installation_progress_service_spec.rb
+3
-3
spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
...ers/applications/check_uninstall_progress_service_spec.rb
+145
-0
spec/services/clusters/applications/destroy_service_spec.rb
spec/services/clusters/applications/destroy_service_spec.rb
+63
-0
spec/services/clusters/applications/uninstall_service_spec.rb
.../services/clusters/applications/uninstall_service_spec.rb
+77
-0
spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
...amples/models/cluster_application_core_shared_examples.rb
+8
-0
spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
...ples/models/cluster_application_status_shared_examples.rb
+55
-10
spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb
...usters/applications/wait_for_uninstall_app_worker_spec.rb
+32
-0
No files found.
app/controllers/clusters/applications_controller.rb
View file @
0048c521
...
...
@@ -4,6 +4,7 @@ class Clusters::ApplicationsController < Clusters::BaseController
before_action
:cluster
before_action
:authorize_create_cluster!
,
only:
[
:create
]
before_action
:authorize_update_cluster!
,
only:
[
:update
]
before_action
:authorize_admin_cluster!
,
only:
[
:destroy
]
def
create
request_handler
do
...
...
@@ -21,6 +22,14 @@ class Clusters::ApplicationsController < Clusters::BaseController
end
end
def
destroy
request_handler
do
Clusters
::
Applications
::
DestroyService
.
new
(
@cluster
,
current_user
,
cluster_application_destroy_params
)
.
execute
(
request
)
end
end
private
def
request_handler
...
...
@@ -40,4 +49,8 @@ class Clusters::ApplicationsController < Clusters::BaseController
def
cluster_application_params
params
.
permit
(
:application
,
:hostname
,
:email
)
end
def
cluster_application_destroy_params
params
.
permit
(
:application
)
end
end
app/models/clusters/applications/cert_manager.rb
View file @
0048c521
...
...
@@ -24,6 +24,12 @@ module Clusters
'stable/cert-manager'
end
# We will implement this in future MRs.
# Need to reverse postinstall step
def
allowed_to_uninstall?
false
end
def
install_command
Gitlab
::
Kubernetes
::
Helm
::
InstallCommand
.
new
(
name:
'certmanager'
,
...
...
app/models/clusters/applications/helm.rb
View file @
0048c521
...
...
@@ -29,6 +29,13 @@ module Clusters
self
.
status
=
'installable'
if
cluster
&
.
platform_kubernetes_active?
end
# We will implement this in future MRs.
# Basically we need to check all other applications are not installed
# first.
def
allowed_to_uninstall?
false
end
def
install_command
Gitlab
::
Kubernetes
::
Helm
::
InitCommand
.
new
(
name:
name
,
...
...
app/models/clusters/applications/ingress.rb
View file @
0048c521
...
...
@@ -35,6 +35,13 @@ module Clusters
'stable/nginx-ingress'
end
# We will implement this in future MRs.
# Basically we need to check all dependent applications are not installed
# first.
def
allowed_to_uninstall?
false
end
def
install_command
Gitlab
::
Kubernetes
::
Helm
::
InstallCommand
.
new
(
name:
name
,
...
...
app/models/clusters/applications/jupyter.rb
View file @
0048c521
...
...
@@ -38,6 +38,12 @@ module Clusters
content_values
.
to_yaml
end
# Will be addressed in future MRs
# We need to investigate and document what will be permenantly deleted.
def
allowed_to_uninstall?
false
end
def
install_command
Gitlab
::
Kubernetes
::
Helm
::
InstallCommand
.
new
(
name:
name
,
...
...
app/models/clusters/applications/knative.rb
View file @
0048c521
...
...
@@ -51,6 +51,12 @@ module Clusters
{
"domain"
=>
hostname
}.
to_yaml
end
# Handled in a new issue:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/59369
def
allowed_to_uninstall?
false
end
def
install_command
Gitlab
::
Kubernetes
::
Helm
::
InstallCommand
.
new
(
name:
name
,
...
...
app/models/clusters/applications/prometheus.rb
View file @
0048c521
...
...
@@ -16,10 +16,12 @@ module Clusters
default_value_for
:version
,
VERSION
after_destroy
:disable_prometheus_integration
state_machine
:status
do
after_transition
any
=>
[
:installed
]
do
|
application
|
application
.
cluster
.
projects
.
each
do
|
project
|
project
.
find_or_initialize_service
(
'prometheus'
).
update
(
active:
true
)
project
.
find_or_initialize_service
(
'prometheus'
).
update
!
(
active:
true
)
end
end
end
...
...
@@ -47,6 +49,14 @@ module Clusters
)
end
def
uninstall_command
Gitlab
::
Kubernetes
::
Helm
::
DeleteCommand
.
new
(
name:
name
,
rbac:
cluster
.
platform_kubernetes_rbac?
,
files:
files
)
end
def
upgrade_command
(
values
)
::
Gitlab
::
Kubernetes
::
Helm
::
InstallCommand
.
new
(
name:
name
,
...
...
@@ -82,6 +92,12 @@ module Clusters
private
def
disable_prometheus_integration
cluster
.
projects
.
each
do
|
project
|
project
.
prometheus_service
&
.
update!
(
active:
false
)
end
end
def
kube_client
cluster
&
.
kubeclient
&
.
core_client
end
...
...
app/models/clusters/applications/runner.rb
View file @
0048c521
...
...
@@ -29,6 +29,13 @@ module Clusters
content_values
.
to_yaml
end
# Need to investigate if pipelines run by this runner will stop upon the
# executor pod stopping
# I.e.run a pipeline, and uninstall runner while pipeline is running
def
allowed_to_uninstall?
false
end
def
install_command
Gitlab
::
Kubernetes
::
Helm
::
InstallCommand
.
new
(
name:
name
,
...
...
app/models/clusters/concerns/application_core.rb
View file @
0048c521
...
...
@@ -18,6 +18,16 @@ module Clusters
self
.
status
=
'installable'
if
cluster
&
.
application_helm_available?
end
def
can_uninstall?
allowed_to_uninstall?
end
# All new applications should uninstall by default
# Override if there's dependencies that needs to be uninstalled first
def
allowed_to_uninstall?
true
end
def
self
.
application_name
self
.
to_s
.
demodulize
.
underscore
end
...
...
app/models/clusters/concerns/application_status.rb
View file @
0048c521
...
...
@@ -25,9 +25,11 @@ module Clusters
state
:updating
,
value:
4
state
:updated
,
value:
5
state
:update_errored
,
value:
6
state
:uninstalling
,
value:
7
state
:uninstall_errored
,
value:
8
event
:make_scheduled
do
transition
[
:installable
,
:errored
,
:installed
,
:updated
,
:update_errored
]
=>
:scheduled
transition
[
:installable
,
:errored
,
:installed
,
:updated
,
:update_errored
,
:uninstall_errored
]
=>
:scheduled
end
event
:make_installing
do
...
...
@@ -40,8 +42,9 @@ module Clusters
end
event
:make_errored
do
transition
any
-
[
:updating
]
=>
:errored
transition
any
-
[
:updating
,
:uninstalling
]
=>
:errored
transition
[
:updating
]
=>
:update_errored
transition
[
:uninstalling
]
=>
:uninstall_errored
end
event
:make_updating
do
...
...
@@ -52,6 +55,10 @@ module Clusters
transition
any
=>
:update_errored
end
event
:make_uninstalling
do
transition
[
:scheduled
]
=>
:uninstalling
end
before_transition
any
=>
[
:scheduled
]
do
|
app_status
,
_
|
app_status
.
status_reason
=
nil
end
...
...
@@ -65,7 +72,7 @@ module Clusters
app_status
.
status_reason
=
nil
end
before_transition
any
=>
[
:update_errored
]
do
|
app_status
,
transition
|
before_transition
any
=>
[
:update_errored
,
:uninstall_errored
]
do
|
app_status
,
transition
|
status_reason
=
transition
.
args
.
first
app_status
.
status_reason
=
status_reason
if
status_reason
end
...
...
app/serializers/cluster_application_entity.rb
View file @
0048c521
...
...
@@ -10,4 +10,5 @@ class ClusterApplicationEntity < Grape::Entity
expose
:hostname
,
if:
->
(
e
,
_
)
{
e
.
respond_to?
(
:hostname
)
}
expose
:email
,
if:
->
(
e
,
_
)
{
e
.
respond_to?
(
:email
)
}
expose
:update_available?
,
as: :update_available
,
if:
->
(
e
,
_
)
{
e
.
respond_to?
(
:update_available?
)
}
expose
:can_uninstall?
,
as: :can_uninstall
end
app/services/clusters/applications/check_installation_progress_service.rb
View file @
0048c521
...
...
@@ -37,7 +37,7 @@ module Clusters
end
def
check_timeout
if
time
outed
?
if
time
d_out
?
begin
app
.
make_errored!
(
"Operation timed out. Check pod logs for
#{
pod_name
}
for more details."
)
end
...
...
@@ -51,8 +51,8 @@ module Clusters
install_command
.
pod_name
end
def
time
outed
?
Time
.
now
.
utc
-
app
.
updated_at
.
to_time
.
utc
>
ClusterWaitForAppInstallationWorker
::
TIMEOUT
def
time
d_out
?
Time
.
now
.
utc
-
app
.
updated_at
.
utc
>
ClusterWaitForAppInstallationWorker
::
TIMEOUT
end
def
remove_installation_pod
...
...
app/services/clusters/applications/check_uninstall_progress_service.rb
0 → 100644
View file @
0048c521
# frozen_string_literal: true
module
Clusters
module
Applications
class
CheckUninstallProgressService
<
BaseHelmService
def
execute
return
unless
app
.
uninstalling?
case
installation_phase
when
Gitlab
::
Kubernetes
::
Pod
::
SUCCEEDED
on_success
when
Gitlab
::
Kubernetes
::
Pod
::
FAILED
on_failed
else
check_timeout
end
rescue
Kubeclient
::
HttpError
=>
e
log_error
(
e
)
app
.
make_errored!
(
_
(
'Kubernetes error: %{error_code}'
)
%
{
error_code:
e
.
error_code
})
end
private
def
on_success
app
.
destroy!
rescue
StandardError
=>
e
app
.
make_errored!
(
_
(
'Application uninstalled but failed to destroy: %{error_message}'
)
%
{
error_message:
e
.
message
})
ensure
remove_installation_pod
end
def
on_failed
app
.
make_errored!
(
_
(
'Operation failed. Check pod logs for %{pod_name} for more details.'
)
%
{
pod_name:
pod_name
})
end
def
check_timeout
if
timed_out?
app
.
make_errored!
(
_
(
'Operation timed out. Check pod logs for %{pod_name} for more details.'
)
%
{
pod_name:
pod_name
})
else
WaitForUninstallAppWorker
.
perform_in
(
WaitForUninstallAppWorker
::
INTERVAL
,
app
.
name
,
app
.
id
)
end
end
def
pod_name
app
.
uninstall_command
.
pod_name
end
def
timed_out?
Time
.
now
.
utc
-
app
.
updated_at
.
utc
>
WaitForUninstallAppWorker
::
TIMEOUT
end
def
remove_installation_pod
helm_api
.
delete_pod!
(
pod_name
)
end
def
installation_phase
helm_api
.
status
(
pod_name
)
end
end
end
end
app/services/clusters/applications/create_service.rb
View file @
0048c521
...
...
@@ -10,8 +10,8 @@ module Clusters
end
def
builder
cluster
.
method
(
"application_
#{
application_name
}
"
).
call
||
cluster
.
method
(
"build_application_
#{
application_name
}
"
).
call
cluster
.
public_send
(
:"application_
#{
application_name
}
"
)
||
# rubocop:disable GitlabSecurity/PublicSend
cluster
.
public_send
(
:"build_application_
#{
application_name
}
"
)
# rubocop:disable GitlabSecurity/PublicSend
end
end
end
...
...
app/services/clusters/applications/destroy_service.rb
0 → 100644
View file @
0048c521
# frozen_string_literal: true
module
Clusters
module
Applications
class
DestroyService
<
::
Clusters
::
Applications
::
BaseService
def
execute
(
_request
)
instantiate_application
.
tap
do
|
application
|
break
unless
application
.
can_uninstall?
application
.
make_scheduled!
Clusters
::
Applications
::
UninstallWorker
.
perform_async
(
application
.
name
,
application
.
id
)
end
end
private
def
builder
cluster
.
public_send
(
:"application_
#{
application_name
}
"
)
# rubocop:disable GitlabSecurity/PublicSend
end
end
end
end
app/services/clusters/applications/uninstall_service.rb
0 → 100644
View file @
0048c521
# frozen_string_literal: true
module
Clusters
module
Applications
class
UninstallService
<
BaseHelmService
def
execute
return
unless
app
.
scheduled?
app
.
make_uninstalling!
uninstall
end
private
def
uninstall
helm_api
.
uninstall
(
app
.
uninstall_command
)
Clusters
::
Applications
::
WaitForUninstallAppWorker
.
perform_in
(
Clusters
::
Applications
::
WaitForUninstallAppWorker
::
INTERVAL
,
app
.
name
,
app
.
id
)
rescue
Kubeclient
::
HttpError
=>
e
log_error
(
e
)
app
.
make_errored!
(
"Kubernetes error:
#{
e
.
error_code
}
"
)
rescue
StandardError
=>
e
log_error
(
e
)
app
.
make_errored!
(
'Failed to uninstall.'
)
end
end
end
end
app/services/clusters/applications/update_service.rb
View file @
0048c521
...
...
@@ -10,7 +10,7 @@ module Clusters
end
def
builder
cluster
.
method
(
"application_
#{
application_name
}
"
).
call
cluster
.
public_send
(
:"application_
#{
application_name
}
"
)
# rubocop:disable GitlabSecurity/PublicSend
end
end
end
...
...
app/workers/all_queues.yml
View file @
0048c521
...
...
@@ -32,6 +32,8 @@
-
gcp_cluster:cluster_wait_for_ingress_ip_address
-
gcp_cluster:cluster_configure
-
gcp_cluster:cluster_project_configure
-
gcp_cluster:clusters_applications_wait_for_uninstall_app
-
gcp_cluster:clusters_applications_uninstall
-
github_import_advance_stage
-
github_importer:github_import_import_diff_note
...
...
app/workers/clusters/applications/uninstall_worker.rb
0 → 100644
View file @
0048c521
# frozen_string_literal: true
module
Clusters
module
Applications
class
UninstallWorker
include
ApplicationWorker
include
ClusterQueue
include
ClusterApplications
def
perform
(
app_name
,
app_id
)
find_application
(
app_name
,
app_id
)
do
|
app
|
Clusters
::
Applications
::
UninstallService
.
new
(
app
).
execute
end
end
end
end
end
app/workers/clusters/applications/wait_for_uninstall_app_worker.rb
0 → 100644
View file @
0048c521
# frozen_string_literal: true
module
Clusters
module
Applications
class
WaitForUninstallAppWorker
include
ApplicationWorker
include
ClusterQueue
include
ClusterApplications
INTERVAL
=
10
.
seconds
TIMEOUT
=
20
.
minutes
def
perform
(
app_name
,
app_id
)
find_application
(
app_name
,
app_id
)
do
|
app
|
Clusters
::
Applications
::
CheckUninstallProgressService
.
new
(
app
).
execute
end
end
end
end
end
config/routes.rb
View file @
0048c521
...
...
@@ -115,6 +115,7 @@ Rails.application.routes.draw do
scope
:applications
do
post
'/:application'
,
to:
'clusters/applications#create'
,
as: :install_applications
patch
'/:application'
,
to:
'clusters/applications#update'
,
as: :update_applications
delete
'/:application'
,
to:
'clusters/applications#destroy'
,
as: :uninstall_applications
end
get
:cluster_status
,
format: :json
...
...
lib/gitlab/kubernetes/helm/api.rb
View file @
0048c521
...
...
@@ -22,6 +22,13 @@ module Gitlab
alias_method
:update
,
:install
def
uninstall
(
command
)
namespace
.
ensure_exists!
delete_pod!
(
command
.
pod_name
)
kubeclient
.
create_pod
(
command
.
pod_resource
)
end
##
# Returns Pod phase
#
...
...
locale/gitlab.pot
View file @
0048c521
...
...
@@ -1213,6 +1213,9 @@ msgstr ""
msgid "Application settings saved successfully"
msgstr ""
msgid "Application uninstalled but failed to destroy: %{error_message}"
msgstr ""
msgid "Application was successfully destroyed."
msgstr ""
...
...
spec/controllers/projects/clusters/applications_controller_spec.rb
View file @
0048c521
...
...
@@ -145,4 +145,66 @@ describe Projects::Clusters::ApplicationsController do
it_behaves_like
'a secure endpoint'
end
end
describe
'DELETE destroy'
do
subject
do
delete
:destroy
,
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_prometheus
,
:installed
,
cluster:
cluster
)
}
let
(
:application_name
)
{
application
.
name
}
let
(
:params
)
{
{
application:
application_name
,
id:
cluster
.
id
}
}
let
(
:worker_class
)
{
Clusters
::
Applications
::
UninstallWorker
}
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
(
worker_class
).
to
receive
(
:perform_async
).
with
(
application
.
name
,
application
.
id
).
once
is_expected
.
to
have_http_status
(
:no_content
)
expect
(
cluster
.
application_prometheus
).
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
(
worker_class
).
to
receive
(
:perform_async
)
end
it_behaves_like
'a secure endpoint'
end
end
end
spec/factories/clusters/applications/helm.rb
View file @
0048c521
...
...
@@ -6,6 +6,11 @@ FactoryBot.define do
status
(
-
2
)
end
trait
:errored
do
status
(
-
1
)
status_reason
'something went wrong'
end
trait
:installable
do
status
0
end
...
...
@@ -30,17 +35,21 @@ FactoryBot.define do
status
5
end
trait
:errored
do
status
(
-
1
)
trait
:
update_
errored
do
status
(
6
)
status_reason
'something went wrong'
end
trait
:update_errored
do
status
(
6
)
trait
:uninstalling
do
status
7
end
trait
:uninstall_errored
do
status
(
8
)
status_reason
'something went wrong'
end
trait
:time
outed
do
trait
:time
d_out
do
installing
updated_at
{
ClusterWaitForAppInstallationWorker
::
TIMEOUT
.
ago
}
end
...
...
spec/fixtures/api/schemas/cluster_status.json
View file @
0048c521
...
...
@@ -36,7 +36,8 @@
"external_hostname"
:
{
"type"
:
[
"string"
,
"null"
]
},
"hostname"
:
{
"type"
:
[
"string"
,
"null"
]
},
"email"
:
{
"type"
:
[
"string"
,
"null"
]
},
"update_available"
:
{
"type"
:
[
"boolean"
,
"null"
]
}
"update_available"
:
{
"type"
:
[
"boolean"
,
"null"
]
},
"can_uninstall"
:
{
"type"
:
"boolean"
}
},
"required"
:
[
"name"
,
"status"
]
}
...
...
spec/lib/gitlab/kubernetes/helm/api_spec.rb
View file @
0048c521
...
...
@@ -33,6 +33,28 @@ describe Gitlab::Kubernetes::Helm::Api do
end
end
describe
'#uninstall'
do
before
do
allow
(
client
).
to
receive
(
:create_pod
).
and_return
(
nil
)
allow
(
client
).
to
receive
(
:delete_pod
).
and_return
(
nil
)
allow
(
namespace
).
to
receive
(
:ensure_exists!
).
once
end
it
'ensures the namespace exists before creating the POD'
do
expect
(
namespace
).
to
receive
(
:ensure_exists!
).
once
.
ordered
expect
(
client
).
to
receive
(
:create_pod
).
once
.
ordered
subject
.
uninstall
(
command
)
end
it
'removes an existing pod before installing'
do
expect
(
client
).
to
receive
(
:delete_pod
).
with
(
'install-app-name'
,
'gitlab-managed-apps'
).
once
.
ordered
expect
(
client
).
to
receive
(
:create_pod
).
once
.
ordered
subject
.
uninstall
(
command
)
end
end
describe
'#install'
do
before
do
allow
(
client
).
to
receive
(
:create_pod
).
and_return
(
nil
)
...
...
spec/models/clusters/applications/cert_manager_spec.rb
View file @
0048c521
...
...
@@ -10,6 +10,12 @@ describe Clusters::Applications::CertManager do
include_examples
'cluster application version specs'
,
:clusters_applications_cert_managers
include_examples
'cluster application initial status specs'
describe
'#can_uninstall?'
do
subject
{
cert_manager
.
can_uninstall?
}
it
{
is_expected
.
to
be_falsey
}
end
describe
'#install_command'
do
let
(
:cert_email
)
{
'admin@example.com'
}
...
...
spec/models/clusters/applications/helm_spec.rb
View file @
0048c521
...
...
@@ -18,6 +18,14 @@ describe Clusters::Applications::Helm do
it
{
is_expected
.
to
contain_exactly
(
installed_cluster
,
updated_cluster
)
}
end
describe
'#can_uninstall?'
do
let
(
:helm
)
{
create
(
:clusters_applications_helm
)
}
subject
{
helm
.
can_uninstall?
}
it
{
is_expected
.
to
be_falsey
}
end
describe
'#issue_client_cert'
do
let
(
:application
)
{
create
(
:clusters_applications_helm
)
}
subject
{
application
.
issue_client_cert
}
...
...
spec/models/clusters/applications/ingress_spec.rb
View file @
0048c521
...
...
@@ -18,6 +18,12 @@ describe Clusters::Applications::Ingress do
allow
(
ClusterWaitForIngressIpAddressWorker
).
to
receive
(
:perform_async
)
end
describe
'#can_uninstall?'
do
subject
{
ingress
.
can_uninstall?
}
it
{
is_expected
.
to
be_falsey
}
end
describe
'#make_installed!'
do
before
do
application
.
make_installed!
...
...
spec/models/clusters/applications/jupyter_spec.rb
View file @
0048c521
...
...
@@ -10,6 +10,15 @@ describe Clusters::Applications::Jupyter do
it
{
is_expected
.
to
belong_to
(
:oauth_application
)
}
describe
'#can_uninstall?'
do
let
(
:ingress
)
{
create
(
:clusters_applications_ingress
,
:installed
,
external_hostname:
'localhost.localdomain'
)
}
let
(
:jupyter
)
{
create
(
:clusters_applications_jupyter
,
cluster:
ingress
.
cluster
)
}
subject
{
jupyter
.
can_uninstall?
}
it
{
is_expected
.
to
be_falsey
}
end
describe
'#set_initial_status'
do
before
do
jupyter
.
set_initial_status
...
...
spec/models/clusters/applications/knative_spec.rb
View file @
0048c521
...
...
@@ -39,6 +39,12 @@ describe Clusters::Applications::Knative do
end
end
describe
'#can_uninstall?'
do
subject
{
knative
.
can_uninstall?
}
it
{
is_expected
.
to
be_falsey
}
end
describe
'#schedule_status_update with external_ip'
do
let
(
:application
)
{
create
(
:clusters_applications_knative
,
:installed
)
}
...
...
spec/models/clusters/applications/prometheus_spec.rb
View file @
0048c521
...
...
@@ -11,6 +11,21 @@ describe Clusters::Applications::Prometheus do
include_examples
'cluster application helm specs'
,
:clusters_applications_prometheus
include_examples
'cluster application initial status specs'
describe
'after_destroy'
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:cluster
)
{
create
(
:cluster
,
:with_installed_helm
,
projects:
[
project
])
}
let!
(
:application
)
{
create
(
:clusters_applications_prometheus
,
:installed
,
cluster:
cluster
)
}
let!
(
:prometheus_service
)
{
project
.
create_prometheus_service
(
active:
true
)
}
it
'deactivates prometheus_service after destroy'
do
expect
do
application
.
destroy!
prometheus_service
.
reload
end
.
to
change
(
prometheus_service
,
:active
).
from
(
true
).
to
(
false
)
end
end
describe
'transition to installed'
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:cluster
)
{
create
(
:cluster
,
:with_installed_helm
,
projects:
[
project
])
}
...
...
@@ -23,12 +38,20 @@ describe Clusters::Applications::Prometheus do
end
it
'ensures Prometheus service is activated'
do
expect
(
prometheus_service
).
to
receive
(
:update
).
with
(
active:
true
)
expect
(
prometheus_service
).
to
receive
(
:update
!
).
with
(
active:
true
)
subject
.
make_installed
end
end
describe
'#can_uninstall?'
do
let
(
:prometheus
)
{
create
(
:clusters_applications_prometheus
)
}
subject
{
prometheus
.
can_uninstall?
}
it
{
is_expected
.
to
be_truthy
}
end
describe
'#prometheus_client'
do
context
'cluster is nil'
do
it
'returns nil'
do
...
...
@@ -134,6 +157,34 @@ describe Clusters::Applications::Prometheus do
end
end
describe
'#uninstall_command'
do
let
(
:prometheus
)
{
create
(
:clusters_applications_prometheus
)
}
subject
{
prometheus
.
uninstall_command
}
it
{
is_expected
.
to
be_an_instance_of
(
Gitlab
::
Kubernetes
::
Helm
::
DeleteCommand
)
}
it
'has the application name'
do
expect
(
subject
.
name
).
to
eq
(
'prometheus'
)
end
it
'has files'
do
expect
(
subject
.
files
).
to
eq
(
prometheus
.
files
)
end
it
'is rbac'
do
expect
(
subject
).
to
be_rbac
end
context
'on a non rbac enabled cluster'
do
before
do
prometheus
.
cluster
.
platform_kubernetes
.
abac!
end
it
{
is_expected
.
not_to
be_rbac
}
end
end
describe
'#upgrade_command'
do
let
(
:prometheus
)
{
build
(
:clusters_applications_prometheus
)
}
let
(
:values
)
{
prometheus
.
values
}
...
...
spec/models/clusters/applications/runner_spec.rb
View file @
0048c521
...
...
@@ -13,6 +13,14 @@ describe Clusters::Applications::Runner do
it
{
is_expected
.
to
belong_to
(
:runner
)
}
describe
'#can_uninstall?'
do
let
(
:gitlab_runner
)
{
create
(
:clusters_applications_runner
,
runner:
ci_runner
)
}
subject
{
gitlab_runner
.
can_uninstall?
}
it
{
is_expected
.
to
be_falsey
}
end
describe
'#install_command'
do
let
(
:kubeclient
)
{
double
(
'kubernetes client'
)
}
let
(
:gitlab_runner
)
{
create
(
:clusters_applications_runner
,
runner:
ci_runner
)
}
...
...
spec/serializers/cluster_application_entity_spec.rb
View file @
0048c521
...
...
@@ -21,6 +21,10 @@ describe ClusterApplicationEntity do
expect
(
subject
[
:status_reason
]).
to
be_nil
end
it
'has can_uninstall'
do
expect
(
subject
[
:can_uninstall
]).
to
be_falsey
end
context
'non-helm application'
do
let
(
:application
)
{
build
(
:clusters_applications_runner
,
version:
'0.0.0'
)
}
...
...
spec/services/clusters/applications/check_installation_progress_service_spec.rb
View file @
0048c521
...
...
@@ -18,7 +18,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
end
context
"when phase is
#{
a_phase
}
"
do
context
'when not time
outed
'
do
context
'when not time
d_out
'
do
it
'reschedule a new check'
do
expect
(
ClusterWaitForAppInstallationWorker
).
to
receive
(
:perform_in
).
once
expect
(
service
).
not_to
receive
(
:remove_installation_pod
)
...
...
@@ -113,7 +113,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
end
context
'when timed out'
do
let
(
:application
)
{
create
(
:clusters_applications_helm
,
:time
outed
,
:updating
)
}
let
(
:application
)
{
create
(
:clusters_applications_helm
,
:time
d_out
,
:updating
)
}
before
do
expect
(
service
).
to
receive
(
:installation_phase
).
once
.
and_return
(
phase
)
...
...
@@ -174,7 +174,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
end
context
'when timed out'
do
let
(
:application
)
{
create
(
:clusters_applications_helm
,
:time
outed
)
}
let
(
:application
)
{
create
(
:clusters_applications_helm
,
:time
d_out
)
}
before
do
expect
(
service
).
to
receive
(
:installation_phase
).
once
.
and_return
(
phase
)
...
...
spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
0 → 100644
View file @
0048c521
# frozen_string_literal: true
require
'spec_helper'
describe
Clusters
::
Applications
::
CheckUninstallProgressService
do
RESCHEDULE_PHASES
=
Gitlab
::
Kubernetes
::
Pod
::
PHASES
-
[
Gitlab
::
Kubernetes
::
Pod
::
SUCCEEDED
,
Gitlab
::
Kubernetes
::
Pod
::
FAILED
].
freeze
let
(
:application
)
{
create
(
:clusters_applications_prometheus
,
:uninstalling
)
}
let
(
:service
)
{
described_class
.
new
(
application
)
}
let
(
:phase
)
{
Gitlab
::
Kubernetes
::
Pod
::
UNKNOWN
}
let
(
:errors
)
{
nil
}
let
(
:worker_class
)
{
Clusters
::
Applications
::
WaitForUninstallAppWorker
}
before
do
allow
(
service
).
to
receive
(
:installation_errors
).
and_return
(
errors
)
allow
(
service
).
to
receive
(
:remove_installation_pod
)
end
shared_examples
'a not yet terminated installation'
do
|
a_phase
|
let
(
:phase
)
{
a_phase
}
before
do
expect
(
service
).
to
receive
(
:installation_phase
).
once
.
and_return
(
phase
)
end
context
"when phase is
#{
a_phase
}
"
do
context
'when not timed_out'
do
it
'reschedule a new check'
do
expect
(
worker_class
).
to
receive
(
:perform_in
).
once
expect
(
service
).
not_to
receive
(
:remove_installation_pod
)
expect
do
service
.
execute
application
.
reload
end
.
not_to
change
(
application
,
:status
)
expect
(
application
.
status_reason
).
to
be_nil
end
end
end
end
context
'when application is installing'
do
RESCHEDULE_PHASES
.
each
{
|
phase
|
it_behaves_like
'a not yet terminated installation'
,
phase
}
context
'when installation POD succeeded'
do
let
(
:phase
)
{
Gitlab
::
Kubernetes
::
Pod
::
SUCCEEDED
}
before
do
expect
(
service
).
to
receive
(
:installation_phase
).
once
.
and_return
(
phase
)
end
it
'removes the installation POD'
do
expect
(
service
).
to
receive
(
:remove_installation_pod
).
once
service
.
execute
end
it
'destroys the application'
do
expect
(
worker_class
).
not_to
receive
(
:perform_in
)
service
.
execute
expect
(
application
).
to
be_destroyed
end
context
'an error occurs while destroying'
do
before
do
expect
(
application
).
to
receive
(
:destroy!
).
once
.
and_raise
(
"destroy failed"
)
end
it
'still removes the installation POD'
do
expect
(
service
).
to
receive
(
:remove_installation_pod
).
once
service
.
execute
end
it
'makes the application uninstall_errored'
do
service
.
execute
expect
(
application
).
to
be_uninstall_errored
expect
(
application
.
status_reason
).
to
eq
(
'Application uninstalled but failed to destroy: destroy failed'
)
end
end
end
context
'when installation POD failed'
do
let
(
:phase
)
{
Gitlab
::
Kubernetes
::
Pod
::
FAILED
}
let
(
:errors
)
{
'test installation failed'
}
before
do
expect
(
service
).
to
receive
(
:installation_phase
).
once
.
and_return
(
phase
)
end
it
'make the application errored'
do
service
.
execute
expect
(
application
).
to
be_uninstall_errored
expect
(
application
.
status_reason
).
to
eq
(
'Operation failed. Check pod logs for uninstall-prometheus for more details.'
)
end
end
context
'when timed out'
do
let
(
:application
)
{
create
(
:clusters_applications_prometheus
,
:timed_out
,
:uninstalling
)
}
before
do
expect
(
service
).
to
receive
(
:installation_phase
).
once
.
and_return
(
phase
)
end
it
'make the application errored'
do
expect
(
worker_class
).
not_to
receive
(
:perform_in
)
service
.
execute
expect
(
application
).
to
be_uninstall_errored
expect
(
application
.
status_reason
).
to
eq
(
'Operation timed out. Check pod logs for uninstall-prometheus for more details.'
)
end
end
context
'when installation raises a Kubeclient::HttpError'
do
let
(
:cluster
)
{
create
(
:cluster
,
:provided_by_user
,
:project
)
}
let
(
:logger
)
{
service
.
send
(
:logger
)
}
let
(
:error
)
{
Kubeclient
::
HttpError
.
new
(
401
,
'Unauthorized'
,
nil
)
}
before
do
application
.
update!
(
cluster:
cluster
)
expect
(
service
).
to
receive
(
:installation_phase
).
and_raise
(
error
)
end
include_examples
'logs kubernetes errors'
do
let
(
:error_name
)
{
'Kubeclient::HttpError'
}
let
(
:error_message
)
{
'Unauthorized'
}
let
(
:error_code
)
{
401
}
end
it
'shows the response code from the error'
do
service
.
execute
expect
(
application
).
to
be_uninstall_errored
expect
(
application
.
status_reason
).
to
eq
(
'Kubernetes error: 401'
)
end
end
end
end
spec/services/clusters/applications/destroy_service_spec.rb
0 → 100644
View file @
0048c521
# frozen_string_literal: true
require
'spec_helper'
describe
Clusters
::
Applications
::
DestroyService
,
'#execute'
do
let
(
:cluster
)
{
create
(
:cluster
,
:project
,
:provided_by_gcp
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:params
)
{
{
application:
'prometheus'
}
}
let
(
:service
)
{
described_class
.
new
(
cluster
,
user
,
params
)
}
let
(
:test_request
)
{
double
}
let
(
:worker_class
)
{
Clusters
::
Applications
::
UninstallWorker
}
subject
{
service
.
execute
(
test_request
)
}
before
do
allow
(
worker_class
).
to
receive
(
:perform_async
)
end
context
'application is not installed'
do
it
'raises Clusters::Applications::BaseService::InvalidApplicationError'
do
expect
(
worker_class
).
not_to
receive
(
:perform_async
)
expect
{
subject
}
.
to
raise_exception
{
Clusters
::
Applications
::
BaseService
::
InvalidApplicationError
}
.
and
not_change
{
Clusters
::
Applications
::
Prometheus
.
count
}
.
and
not_change
{
Clusters
::
Applications
::
Prometheus
.
with_status
(
:scheduled
).
count
}
end
end
context
'application is installed'
do
context
'application is schedulable'
do
let!
(
:application
)
do
create
(
:clusters_applications_prometheus
,
:installed
,
cluster:
cluster
)
end
it
'makes application scheduled!'
do
subject
expect
(
application
.
reload
).
to
be_scheduled
end
it
'schedules UninstallWorker'
do
expect
(
worker_class
).
to
receive
(
:perform_async
).
with
(
application
.
name
,
application
.
id
)
subject
end
end
context
'application is not schedulable'
do
let!
(
:application
)
do
create
(
:clusters_applications_prometheus
,
:updating
,
cluster:
cluster
)
end
it
'raises StateMachines::InvalidTransition'
do
expect
(
worker_class
).
not_to
receive
(
:perform_async
)
expect
{
subject
}
.
to
raise_exception
{
StateMachines
::
InvalidTransition
}
.
and
not_change
{
Clusters
::
Applications
::
Prometheus
.
with_status
(
:scheduled
).
count
}
end
end
end
end
spec/services/clusters/applications/uninstall_service_spec.rb
0 → 100644
View file @
0048c521
# frozen_string_literal: true
require
'spec_helper'
describe
Clusters
::
Applications
::
UninstallService
,
'#execute'
do
let
(
:application
)
{
create
(
:clusters_applications_prometheus
,
:scheduled
)
}
let
(
:service
)
{
described_class
.
new
(
application
)
}
let
(
:helm_client
)
{
instance_double
(
Gitlab
::
Kubernetes
::
Helm
::
Api
)
}
let
(
:worker_class
)
{
Clusters
::
Applications
::
WaitForUninstallAppWorker
}
before
do
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
(
:uninstall
).
with
(
kind_of
(
Gitlab
::
Kubernetes
::
Helm
::
DeleteCommand
))
allow
(
worker_class
).
to
receive
(
:perform_in
).
and_return
(
nil
)
end
it
'make the application to be uninstalling'
do
expect
(
application
.
cluster
).
not_to
be_nil
service
.
execute
expect
(
application
).
to
be_uninstalling
end
it
'schedule async installation status check'
do
expect
(
worker_class
).
to
receive
(
:perform_in
).
once
service
.
execute
end
end
context
'when k8s cluster communication fails'
do
let
(
:error
)
{
Kubeclient
::
HttpError
.
new
(
500
,
'system failure'
,
nil
)
}
before
do
expect
(
helm_client
).
to
receive
(
:uninstall
).
with
(
kind_of
(
Gitlab
::
Kubernetes
::
Helm
::
DeleteCommand
)).
and_raise
(
error
)
end
include_examples
'logs kubernetes errors'
do
let
(
:error_name
)
{
'Kubeclient::HttpError'
}
let
(
:error_message
)
{
'system failure'
}
let
(
:error_code
)
{
500
}
end
it
'make the application errored'
do
service
.
execute
expect
(
application
).
to
be_uninstall_errored
expect
(
application
.
status_reason
).
to
match
(
'Kubernetes error: 500'
)
end
end
context
'a non kubernetes error happens'
do
let
(
:application
)
{
create
(
:clusters_applications_prometheus
,
:scheduled
)
}
let
(
:error
)
{
StandardError
.
new
(
'something bad happened'
)
}
before
do
expect
(
helm_client
).
to
receive
(
:uninstall
).
with
(
kind_of
(
Gitlab
::
Kubernetes
::
Helm
::
DeleteCommand
)).
and_raise
(
error
)
end
include_examples
'logs kubernetes errors'
do
let
(
:error_name
)
{
'StandardError'
}
let
(
:error_message
)
{
'something bad happened'
}
let
(
:error_code
)
{
nil
}
end
it
'make the application errored'
do
service
.
execute
expect
(
application
).
to
be_uninstall_errored
expect
(
application
.
status_reason
).
to
eq
(
'Failed to uninstall.'
)
end
end
end
spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
View file @
0048c521
...
...
@@ -2,6 +2,14 @@ shared_examples 'cluster application core specs' do |application_name|
it
{
is_expected
.
to
belong_to
(
:cluster
)
}
it
{
is_expected
.
to
validate_presence_of
(
:cluster
)
}
describe
'#can_uninstall?'
do
it
'calls allowed_to_uninstall?'
do
expect
(
subject
).
to
receive
(
:allowed_to_uninstall?
).
and_return
(
true
)
expect
(
subject
.
can_uninstall?
).
to
be_truthy
end
end
describe
'#name'
do
it
'is .application_name'
do
expect
(
subject
.
name
).
to
eq
(
described_class
.
application_name
)
...
...
spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
View file @
0048c521
...
...
@@ -114,6 +114,17 @@ shared_examples 'cluster application status specs' do |application_name|
expect
(
subject
.
status_reason
).
to
eq
(
reason
)
end
end
context
'application is uninstalling'
do
subject
{
create
(
application_name
,
:uninstalling
)
}
it
'is uninstall_errored'
do
subject
.
make_errored
(
reason
)
expect
(
subject
).
to
be_uninstall_errored
expect
(
subject
.
status_reason
).
to
eq
(
reason
)
end
end
end
describe
'#make_scheduled'
do
...
...
@@ -125,6 +136,16 @@ shared_examples 'cluster application status specs' do |application_name|
expect
(
subject
).
to
be_scheduled
end
describe
'when installed'
do
subject
{
create
(
application_name
,
:installed
)
}
it
'is scheduled'
do
subject
.
make_scheduled
expect
(
subject
).
to
be_scheduled
end
end
describe
'when was errored'
do
subject
{
create
(
application_name
,
:errored
)
}
...
...
@@ -148,6 +169,28 @@ shared_examples 'cluster application status specs' do |application_name|
expect
(
subject
.
status_reason
).
to
be_nil
end
end
describe
'when was uninstall_errored'
do
subject
{
create
(
application_name
,
:uninstall_errored
)
}
it
'clears #status_reason'
do
expect
(
subject
.
status_reason
).
not_to
be_nil
subject
.
make_scheduled!
expect
(
subject
.
status_reason
).
to
be_nil
end
end
end
describe
'#make_uninstalling'
do
subject
{
create
(
application_name
,
:scheduled
)
}
it
'is uninstalling'
do
subject
.
make_uninstalling!
expect
(
subject
).
to
be_uninstalling
end
end
end
...
...
@@ -155,16 +198,18 @@ shared_examples 'cluster application status specs' do |application_name|
using
RSpec
::
Parameterized
::
TableSyntax
where
(
:trait
,
:available
)
do
:not_installable
|
false
:installable
|
false
:scheduled
|
false
:installing
|
false
:installed
|
true
:updating
|
false
:updated
|
true
:errored
|
false
:update_errored
|
false
:timeouted
|
false
:not_installable
|
false
:installable
|
false
:scheduled
|
false
:installing
|
false
:installed
|
true
:updating
|
false
:updated
|
true
:errored
|
false
:update_errored
|
false
:uninstalling
|
false
:uninstall_errored
|
false
:timed_out
|
false
end
with_them
do
...
...
spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb
0 → 100644
View file @
0048c521
# frozen_string_literal: true
require
'spec_helper'
describe
Clusters
::
Applications
::
WaitForUninstallAppWorker
,
'#perform'
do
let
(
:app
)
{
create
(
:clusters_applications_helm
)
}
let
(
:app_name
)
{
app
.
name
}
let
(
:app_id
)
{
app
.
id
}
subject
{
described_class
.
new
.
perform
(
app_name
,
app_id
)
}
context
'app exists'
do
let
(
:service
)
{
instance_double
(
Clusters
::
Applications
::
CheckUninstallProgressService
)
}
it
'calls the check service'
do
expect
(
Clusters
::
Applications
::
CheckUninstallProgressService
).
to
receive
(
:new
).
with
(
app
).
and_return
(
service
)
expect
(
service
).
to
receive
(
:execute
).
once
subject
end
end
context
'app does not exist'
do
let
(
:app_id
)
{
0
}
it
'does not call the check service'
do
expect
(
Clusters
::
Applications
::
CheckUninstallProgressService
).
not_to
receive
(
:new
)
expect
{
subject
}.
to
raise_error
(
ActiveRecord
::
RecordNotFound
)
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment