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
b9a3438b
Commit
b9a3438b
authored
May 17, 2019
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab-ce master
parents
5d24b055
74fa088b
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
69 additions
and
169 deletions
+69
-169
app/models/ci/build.rb
app/models/ci/build.rb
+0
-2
app/services/clusters/refresh_service.rb
app/services/clusters/refresh_service.rb
+1
-5
app/services/projects/create_service.rb
app/services/projects/create_service.rb
+0
-6
app/services/projects/transfer_service.rb
app/services/projects/transfer_service.rb
+0
-5
app/workers/cluster_configure_worker.rb
app/workers/cluster_configure_worker.rb
+1
-1
changelogs/unreleased/60379-remove-ci-preparing-state-feature-flag.yml
...released/60379-remove-ci-preparing-state-feature-flag.yml
+5
-0
lib/gitlab/ci/templates/dotNET-Core.yml
lib/gitlab/ci/templates/dotNET-Core.yml
+50
-41
spec/models/ci/build_spec.rb
spec/models/ci/build_spec.rb
+6
-14
spec/services/clusters/refresh_service_spec.rb
spec/services/clusters/refresh_service_spec.rb
+4
-22
spec/services/projects/create_service_spec.rb
spec/services/projects/create_service_spec.rb
+0
-27
spec/services/projects/transfer_service_spec.rb
spec/services/projects/transfer_service_spec.rb
+0
-27
spec/workers/cluster_configure_worker_spec.rb
spec/workers/cluster_configure_worker_spec.rb
+2
-19
No files found.
app/models/ci/build.rb
View file @
b9a3438b
...
@@ -378,8 +378,6 @@ module Ci
...
@@ -378,8 +378,6 @@ module Ci
end
end
def
any_unmet_prerequisites?
def
any_unmet_prerequisites?
return
false
unless
Feature
.
enabled?
(
:ci_preparing_state
,
default_enabled:
true
)
prerequisites
.
present?
prerequisites
.
present?
end
end
...
...
app/services/clusters/refresh_service.rb
View file @
b9a3438b
...
@@ -21,11 +21,7 @@ module Clusters
...
@@ -21,11 +21,7 @@ module Clusters
private_class_method
:projects_with_missing_kubernetes_namespaces_for_cluster
private_class_method
:projects_with_missing_kubernetes_namespaces_for_cluster
def
self
.
clusters_with_missing_kubernetes_namespaces_for_project
(
project
)
def
self
.
clusters_with_missing_kubernetes_namespaces_for_project
(
project
)
if
Feature
.
enabled?
(
:ci_preparing_state
,
default_enabled:
true
)
project
.
clusters
.
managed
.
missing_kubernetes_namespace
(
project
.
kubernetes_namespaces
)
project
.
clusters
.
managed
.
missing_kubernetes_namespace
(
project
.
kubernetes_namespaces
)
else
project
.
all_clusters
.
managed
.
missing_kubernetes_namespace
(
project
.
kubernetes_namespaces
)
end
end
end
private_class_method
:clusters_with_missing_kubernetes_namespaces_for_project
private_class_method
:clusters_with_missing_kubernetes_namespaces_for_project
...
...
app/services/projects/create_service.rb
View file @
b9a3438b
...
@@ -100,8 +100,6 @@ module Projects
...
@@ -100,8 +100,6 @@ module Projects
current_user
.
invalidate_personal_projects_count
current_user
.
invalidate_personal_projects_count
create_readme
if
@initialize_with_readme
create_readme
if
@initialize_with_readme
configure_group_clusters_for_project
end
end
# Refresh the current user's authorizations inline (so they can access the
# Refresh the current user's authorizations inline (so they can access the
...
@@ -127,10 +125,6 @@ module Projects
...
@@ -127,10 +125,6 @@ module Projects
Files
::
CreateService
.
new
(
@project
,
current_user
,
commit_attrs
).
execute
Files
::
CreateService
.
new
(
@project
,
current_user
,
commit_attrs
).
execute
end
end
def
configure_group_clusters_for_project
ClusterProjectConfigureWorker
.
perform_async
(
@project
.
id
)
end
def
skip_wiki?
def
skip_wiki?
!
@project
.
feature_available?
(
:wiki
,
current_user
)
||
@skip_wiki
!
@project
.
feature_available?
(
:wiki
,
current_user
)
||
@skip_wiki
end
end
...
...
app/services/projects/transfer_service.rb
View file @
b9a3438b
...
@@ -54,7 +54,6 @@ module Projects
...
@@ -54,7 +54,6 @@ module Projects
end
end
attempt_transfer_transaction
attempt_transfer_transaction
configure_group_clusters_for_project
end
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: enable CodeReuse/ActiveRecord
...
@@ -164,10 +163,6 @@ module Projects
...
@@ -164,10 +163,6 @@ module Projects
@new_namespace
.
full_path
@new_namespace
.
full_path
)
)
end
end
def
configure_group_clusters_for_project
ClusterProjectConfigureWorker
.
perform_async
(
project
.
id
)
end
end
end
end
end
...
...
app/workers/cluster_configure_worker.rb
View file @
b9a3438b
...
@@ -6,7 +6,7 @@ class ClusterConfigureWorker
...
@@ -6,7 +6,7 @@ class ClusterConfigureWorker
def
perform
(
cluster_id
)
def
perform
(
cluster_id
)
Clusters
::
Cluster
.
managed
.
find_by_id
(
cluster_id
).
try
do
|
cluster
|
Clusters
::
Cluster
.
managed
.
find_by_id
(
cluster_id
).
try
do
|
cluster
|
if
cluster
.
project_type?
||
Feature
.
disabled?
(
:ci_preparing_state
,
default_enabled:
true
)
if
cluster
.
project_type?
Clusters
::
RefreshService
.
create_or_update_namespaces_for_cluster
(
cluster
)
Clusters
::
RefreshService
.
create_or_update_namespaces_for_cluster
(
cluster
)
end
end
end
end
...
...
changelogs/unreleased/60379-remove-ci-preparing-state-feature-flag.yml
0 → 100644
View file @
b9a3438b
---
title
:
Remove ability for group clusters to be automatically configured on creation
merge_request
:
27245
author
:
type
:
removed
lib/gitlab/ci/templates/dotNET-Core.yml
View file @
b9a3438b
...
@@ -3,10 +3,11 @@
...
@@ -3,10 +3,11 @@
# ### Specify the Docker image
# ### Specify the Docker image
#
#
# Instead of installing .NET Core SDK manually, a docker image is used
# Instead of installing .NET Core SDK manually, a docker image is used
# with already pre-installed .NET Core SDK.
# with already pre-installed .NET Core SDK.
# The 'latest' tag targets the latest available version of .NET Core SDK image.
#
# If preferred, you can explicitly specify version of .NET Core e.g. using '2.2-sdk' tag.
# The 'latest' tag targets the latest available version of .NET Core SDK image.
# If preferred, you can explicitly specify version of .NET Core (e.g. using '2.2-sdk' tag).
#
#
# See other available tags for .NET Core: https://hub.docker.com/r/microsoft/dotnet
# See other available tags for .NET Core: https://hub.docker.com/r/microsoft/dotnet
# Learn more about Docker tags: https://docs.docker.com/glossary/?term=tag
# Learn more about Docker tags: https://docs.docker.com/glossary/?term=tag
...
@@ -17,16 +18,16 @@ image: microsoft/dotnet:latest
...
@@ -17,16 +18,16 @@ image: microsoft/dotnet:latest
#
#
variables
:
variables
:
# 1) Name of directory where restore and build objects are stored.
# 1) Name of directory where restore and build objects are stored.
OBJECTS_DIRECTORY
:
'
obj'
OBJECTS_DIRECTORY
:
'
obj'
# 2) Name of directory used for keeping restored dependencies.
# 2) Name of directory used for keeping restored dependencies.
NUGET_PACKAGES_DIRECTORY
:
'
.nuget'
NUGET_PACKAGES_DIRECTORY
:
'
.nuget'
# 3) A relative path to the source code from project repository root.
# 3) A relative path to the source code from project repository root.
# NOTE: Please edit this path so it matches the structure of your project!
# NOTE: Please edit this path so it matches the structure of your project!
SOURCE_CODE_PATH
:
'
*/*/'
SOURCE_CODE_PATH
:
'
*/*/'
# ### Define stage list
# ### Define stage list
#
#
# In this example there are only two stages.
# In this example there are only two stages.
# Initially, the project will be built and then tested.
# Initially, the project will be built and then tested.
stages
:
stages
:
-
build
-
build
...
@@ -34,47 +35,55 @@ stages:
...
@@ -34,47 +35,55 @@ stages:
# ### Define global cache rule
# ### Define global cache rule
#
#
# Before building the project, all dependencies (e.g. third-party NuGet packages)
# Before building the project, all dependencies (e.g. third-party NuGet packages)
# must be restored. Jobs on GitLab.com's Shared Runners are executed on autoscaled machines.
# must be restored. Jobs on GitLab.com's Shared Runners are executed on autoscaled machines.
# Each machine is used only once (for security reasons) and after that it is removed.
#
# What that means is that before every job a dependency restore must be performed
# Each machine is used only once (for security reasons) and after that is removed.
# This means that, before every job, a dependency restore must be performed
# because restored dependencies are removed along with machines. Fortunately,
# because restored dependencies are removed along with machines. Fortunately,
# GitLab provides cache mechanism with the aim of keeping restored dependencies
# GitLab provides cache mechanism with the aim of keeping restored dependencies
# for other jobs. This example shows how to configure cache to pass over restored
# for other jobs.
#
# This example shows how to configure cache to pass over restored
# dependencies for re-use.
# dependencies for re-use.
#
#
# With global cache rule, cached dependencies will be downloaded before every job
# With global cache rule, cached dependencies will be downloaded before every job
# and then unpacked to the paths as specified below.
# and then unpacked to the paths as specified below.
cache
:
cache
:
# Per-stage and per-branch caching.
# Per-stage and per-branch caching.
key
:
"
$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
key
:
"
$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
paths
:
paths
:
# Specify three paths that should be cached:
# Specify three paths that should be cached:
#
#
# 1) Main JSON file holding information about package dependency tree, packages versions,
# 1) Main JSON file holding information about package dependency tree, packages versions,
# frameworks etc. It also holds information where to the dependencies were restored.
# frameworks etc. It also holds information where to the dependencies were restored.
-
'
$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/project.assets.json'
-
'
$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/project.assets.json'
# 2) Other NuGet and MSBuild related files. Also needed.
# 2) Other NuGet and MSBuild related files. Also needed.
-
'
$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/*.csproj.nuget.*'
-
'
$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/*.csproj.nuget.*'
# 3) Path to the directory where restored dependencies are kept.
# 3) Path to the directory where restored dependencies are kept.
-
'
$NUGET_PACKAGES_DIRECTORY'
-
'
$NUGET_PACKAGES_DIRECTORY'
# 'pull-push' policy means that latest cache will be downloaded (if exists)
#
# before executing the job, and a newer version will be uploaded afterwards.
# 'pull-push' policy means that latest cache will be downloaded (if it exists)
# Such setting saves time when there are no changes in referenced third-party
# before executing the job, and a newer version will be uploaded afterwards.
# packages. For example if you run a pipeline with changes in your code,
# Such a setting saves time when there are no changes in referenced third-party
# but with no changes within third-party packages which your project is using,
# packages.
# then project restore will happen in next to no time as all required dependencies
#
# will already be there — unzipped from cache. 'pull-push' policy is a default
# For example, if you run a pipeline with changes in your code,
# cache policy, you do not have to specify it explicitly.
# but with no changes within third-party packages which your project is using,
policy
:
pull-push
# then project restore will happen quickly as all required dependencies
# will already be there — unzipped from cache.
# 'pull-push' policy is the default cache policy, you do not have to specify it explicitly.
policy
:
pull-push
# ### Restore project dependencies
# ### Restore project dependencies
#
#
# NuGet packages by default are restored to '.nuget/packages' directory
# NuGet packages by default are restored to '.nuget/packages' directory
# in the user's home directory. That directory is out of scope of GitLab caching.
# in the user's home directory. That directory is out of scope of GitLab caching.
# To get around this a custom path can be specified using '--packages <PATH>' option
#
# for 'dotnet restore' command. In this example a temporary directory is created
# To get around this, a custom path can be specified using the '--packages <PATH>' option
# in the root of project repository, so it's content can be cached.
# for 'dotnet restore' command. In this example, a temporary directory is created
# in the root of project repository, so its content can be cached.
#
#
# Learn more about GitLab cache: https://docs.gitlab.com/ee/ci/caching/index.html
# Learn more about GitLab cache: https://docs.gitlab.com/ee/ci/caching/index.html
before_script
:
before_script
:
...
@@ -82,26 +91,26 @@ before_script:
...
@@ -82,26 +91,26 @@ before_script:
build
:
build
:
stage
:
build
stage
:
build
# ### Build all projects discovered from solution file.
# ### Build all projects discovered from solution file.
#
#
# Note: this will fail if you have any projects in your solution that are not
# Note: this will fail if you have any projects in your solution that are not
# .NET Core
based projects e.g. WCF service, which is based on .NET Framework,
# .NET Core
-based projects (e.g. WCF service), which is based on .NET Framework,
# not .NET Core. In
such scenario you will need to build every .NET Core based
# not .NET Core. In
this scenario, you will need to build every .NET Core-based
# project by explicitly specifying a relative path to the directory
# project by explicitly specifying a relative path to the directory
# where it is located
e.g. 'dotnet build ./src/ConsoleApp'.
# where it is located
(e.g. 'dotnet build ./src/ConsoleApp').
# Only one project path can be passed as a parameter to 'dotnet build' command.
# Only one project path can be passed as a parameter to 'dotnet build' command.
script
:
script
:
-
'
dotnet
build
--no-restore'
-
'
dotnet
build
--no-restore'
tests
:
tests
:
stage
:
test
stage
:
test
# ### Run the tests
# ### Run the tests
#
#
# You can either run tests for all test projects that are defined in your solution
# You can either run tests for all test projects that are defined in your solution
# with 'dotnet test' or run tests only for specific project by specifying
# with 'dotnet test' or run tests only for specific project by specifying
# a relative path to the directory where it is located
e.g. 'dotnet test ./test/UnitTests'.
# a relative path to the directory where it is located
(e.g. 'dotnet test ./test/UnitTests').
#
#
# You may want to define separate testing jobs for different types of testing
# You may want to define separate testing jobs for different types of testing
#
e.g. integration tests, unit tests etc
.
#
(e.g. integration tests, unit tests etc)
.
script
:
script
:
-
'
dotnet
test
--no-restore'
-
'
dotnet
test
--no-restore'
spec/models/ci/build_spec.rb
View file @
b9a3438b
...
@@ -2925,26 +2925,18 @@ describe Ci::Build do
...
@@ -2925,26 +2925,18 @@ describe Ci::Build do
subject
{
build
.
any_unmet_prerequisites?
}
subject
{
build
.
any_unmet_prerequisites?
}
before
do
allow
(
build
).
to
receive
(
:prerequisites
).
and_return
(
prerequisites
)
end
context
'build has prerequisites'
do
context
'build has prerequisites'
do
before
do
let
(
:prerequisites
)
{
[
double
]
}
allow
(
build
).
to
receive
(
:prerequisites
).
and_return
([
double
])
end
it
{
is_expected
.
to
be_truthy
}
it
{
is_expected
.
to
be_truthy
}
context
'and the ci_preparing_state feature is disabled'
do
before
do
stub_feature_flags
(
ci_preparing_state:
false
)
end
it
{
is_expected
.
to
be_falsey
}
end
end
end
context
'build does not have prerequisites'
do
context
'build does not have prerequisites'
do
before
do
let
(
:prerequisites
)
{
[]
}
allow
(
build
).
to
receive
(
:prerequisites
).
and_return
([])
end
it
{
is_expected
.
to
be_falsey
}
it
{
is_expected
.
to
be_falsey
}
end
end
...
...
spec/services/clusters/refresh_service_spec.rb
View file @
b9a3438b
...
@@ -93,32 +93,14 @@ describe Clusters::RefreshService do
...
@@ -93,32 +93,14 @@ describe Clusters::RefreshService do
let
(
:group
)
{
cluster
.
group
}
let
(
:group
)
{
cluster
.
group
}
let
(
:project
)
{
create
(
:project
,
group:
group
)
}
let
(
:project
)
{
create
(
:project
,
group:
group
)
}
context
'when ci_preparing_state feature flag is enabled'
do
include_examples
'does not create a kubernetes namespace'
include_examples
'does not create a kubernetes namespace'
context
'when project already has kubernetes namespace'
do
before
do
create
(
:cluster_kubernetes_namespace
,
project:
project
,
cluster:
cluster
)
end
include_examples
'does not create a kubernetes namespace'
end
end
context
'when
ci_preparing_state feature flag is disabled
'
do
context
'when
project already has kubernetes namespace
'
do
before
do
before
do
stub_feature_flags
(
ci_preparing_state:
false
)
create
(
:cluster_kubernetes_namespace
,
project:
project
,
cluster:
cluster
)
end
end
include_examples
'creates a kubernetes namespace'
include_examples
'does not create a kubernetes namespace'
context
'when project already has kubernetes namespace'
do
before
do
create
(
:cluster_kubernetes_namespace
,
project:
project
,
cluster:
cluster
)
end
include_examples
'does not create a kubernetes namespace'
end
end
end
end
end
...
...
spec/services/projects/create_service_spec.rb
View file @
b9a3438b
...
@@ -268,33 +268,6 @@ describe Projects::CreateService, '#execute' do
...
@@ -268,33 +268,6 @@ describe Projects::CreateService, '#execute' do
end
end
end
end
context
'when group has kubernetes cluster'
do
let
(
:group_cluster
)
{
create
(
:cluster
,
:group
,
:provided_by_gcp
)
}
let
(
:group
)
{
group_cluster
.
group
}
let
(
:token
)
{
'aaaa'
}
let
(
:service_account_creator
)
{
double
(
Clusters
::
Gcp
::
Kubernetes
::
CreateOrUpdateServiceAccountService
,
execute:
true
)
}
let
(
:secrets_fetcher
)
{
double
(
Clusters
::
Gcp
::
Kubernetes
::
FetchKubernetesTokenService
,
execute:
token
)
}
before
do
group
.
add_owner
(
user
)
stub_feature_flags
(
ci_preparing_state:
false
)
expect
(
Clusters
::
Gcp
::
Kubernetes
::
CreateOrUpdateServiceAccountService
).
to
receive
(
:namespace_creator
).
and_return
(
service_account_creator
)
expect
(
Clusters
::
Gcp
::
Kubernetes
::
FetchKubernetesTokenService
).
to
receive
(
:new
).
and_return
(
secrets_fetcher
)
end
it
'creates kubernetes namespace for the project'
do
project
=
create_project
(
user
,
opts
.
merge!
(
namespace_id:
group
.
id
))
expect
(
project
).
to
be_valid
kubernetes_namespace
=
group_cluster
.
kubernetes_namespaces
.
first
expect
(
kubernetes_namespace
).
to
be_present
expect
(
kubernetes_namespace
.
project
).
to
eq
(
project
)
end
end
context
'when there is an active service template'
do
context
'when there is an active service template'
do
before
do
before
do
create
(
:service
,
project:
nil
,
template:
true
,
active:
true
)
create
(
:service
,
project:
nil
,
template:
true
,
active:
true
)
...
...
spec/services/projects/transfer_service_spec.rb
View file @
b9a3438b
...
@@ -73,33 +73,6 @@ describe Projects::TransferService do
...
@@ -73,33 +73,6 @@ describe Projects::TransferService do
shard_name:
project
.
repository_storage
shard_name:
project
.
repository_storage
)
)
end
end
context
'new group has a kubernetes cluster'
do
let
(
:group_cluster
)
{
create
(
:cluster
,
:group
,
:provided_by_gcp
)
}
let
(
:group
)
{
group_cluster
.
group
}
let
(
:token
)
{
'aaaa'
}
let
(
:service_account_creator
)
{
double
(
Clusters
::
Gcp
::
Kubernetes
::
CreateOrUpdateServiceAccountService
,
execute:
true
)
}
let
(
:secrets_fetcher
)
{
double
(
Clusters
::
Gcp
::
Kubernetes
::
FetchKubernetesTokenService
,
execute:
token
)
}
subject
{
transfer_project
(
project
,
user
,
group
)
}
before
do
stub_feature_flags
(
ci_preparing_state:
false
)
expect
(
Clusters
::
Gcp
::
Kubernetes
::
CreateOrUpdateServiceAccountService
).
to
receive
(
:namespace_creator
).
and_return
(
service_account_creator
)
expect
(
Clusters
::
Gcp
::
Kubernetes
::
FetchKubernetesTokenService
).
to
receive
(
:new
).
and_return
(
secrets_fetcher
)
end
it
'creates kubernetes namespace for the project'
do
subject
expect
(
project
.
kubernetes_namespaces
.
count
).
to
eq
(
1
)
kubernetes_namespace
=
group_cluster
.
kubernetes_namespaces
.
first
expect
(
kubernetes_namespace
).
to
be_present
expect
(
kubernetes_namespace
.
project
).
to
eq
(
project
)
end
end
end
end
context
'when transfer fails'
do
context
'when transfer fails'
do
...
...
spec/workers/cluster_configure_worker_spec.rb
View file @
b9a3438b
...
@@ -4,11 +4,6 @@ require 'spec_helper'
...
@@ -4,11 +4,6 @@ require 'spec_helper'
describe
ClusterConfigureWorker
,
'#perform'
do
describe
ClusterConfigureWorker
,
'#perform'
do
let
(
:worker
)
{
described_class
.
new
}
let
(
:worker
)
{
described_class
.
new
}
let
(
:ci_preparing_state_enabled
)
{
false
}
before
do
stub_feature_flags
(
ci_preparing_state:
ci_preparing_state_enabled
)
end
shared_examples
'configured cluster'
do
shared_examples
'configured cluster'
do
it
'creates a namespace'
do
it
'creates a namespace'
do
...
@@ -33,26 +28,14 @@ describe ClusterConfigureWorker, '#perform' do
...
@@ -33,26 +28,14 @@ describe ClusterConfigureWorker, '#perform' do
context
'when group has a project'
do
context
'when group has a project'
do
let!
(
:project
)
{
create
(
:project
,
group:
group
)
}
let!
(
:project
)
{
create
(
:project
,
group:
group
)
}
it_behaves_like
'configured cluster'
it_behaves_like
'unconfigured cluster'
context
'ci_preparing_state feature is enabled'
do
let
(
:ci_preparing_state_enabled
)
{
true
}
it_behaves_like
'unconfigured cluster'
end
end
end
context
'when group has project in a sub-group'
do
context
'when group has project in a sub-group'
do
let!
(
:subgroup
)
{
create
(
:group
,
parent:
group
)
}
let!
(
:subgroup
)
{
create
(
:group
,
parent:
group
)
}
let!
(
:project
)
{
create
(
:project
,
group:
subgroup
)
}
let!
(
:project
)
{
create
(
:project
,
group:
subgroup
)
}
it_behaves_like
'configured cluster'
it_behaves_like
'unconfigured cluster'
context
'ci_preparing_state feature is enabled'
do
let
(
:ci_preparing_state_enabled
)
{
true
}
it_behaves_like
'unconfigured cluster'
end
end
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