Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThong Kuah <tkuah@gitlab.com>2018-09-28 04:29:25 +0300
committerThong Kuah <tkuah@gitlab.com>2018-09-28 04:48:10 +0300
commit86d374399933e2dfc7f1acfb0f6c47dd27ebdf3f (patch)
treef138ecfcc05b29d2e403892285f6aaf253e68901
parentec4eda2686bb05b8625d0ff5e5e2052d07246b67 (diff)
Restore Gcp:: services to master
We need the admin `gitlab` SA to remain as-is, with the `cluster-admin` privillege.
-rw-r--r--app/services/clusters/gcp/finalize_creation_service.rb23
-rw-r--r--app/services/clusters/gcp/kubernetes.rb1
-rw-r--r--app/services/clusters/gcp/kubernetes/create_service_account_service.rb34
-rw-r--r--app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb11
-rw-r--r--app/services/clusters/gcp/services_account_service.rb43
-rw-r--r--spec/services/clusters/gcp/finalize_creation_service_spec.rb270
-rw-r--r--spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb41
-rw-r--r--spec/services/clusters/gcp/services_account_service_spec.rb73
8 files changed, 172 insertions, 324 deletions
diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb
index 5893f27a5cf..3ae0a4a19d0 100644
--- a/app/services/clusters/gcp/finalize_creation_service.rb
+++ b/app/services/clusters/gcp/finalize_creation_service.rb
@@ -9,9 +9,8 @@ module Clusters
@provider = provider
configure_provider
+ create_gitlab_service_account!
configure_kubernetes
- create_gitlab_services_account!
- configure_kubernetes_token
cluster.save!
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
@@ -24,8 +23,8 @@ module Clusters
private
- def create_gitlab_services_account!
- Clusters::Gcp::ServicesAccountService.new(kube_client, cluster).execute
+ def create_gitlab_service_account!
+ Clusters::Gcp::Kubernetes::CreateServiceAccountService.new(kube_client, rbac: create_rbac_cluster?).execute
end
def configure_provider
@@ -40,25 +39,19 @@ module Clusters
ca_cert: Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate),
username: gke_cluster.master_auth.username,
password: gke_cluster.master_auth.password,
- authorization_type: authorization_type
- )
- end
-
- def configure_kubernetes_token
- cluster.platform_kubernetes.token = request_kubernetes_token
+ authorization_type: authorization_type,
+ token: request_kubernetes_token)
end
def request_kubernetes_token
- namespace = rbac_cluster? ? cluster.platform_kubernetes.actual_namespace : Clusters::Gcp::Kubernetes::SERVICE_ACCOUNT_NAMESPACE
-
- Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(kube_client, namespace).execute
+ Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(kube_client).execute
end
def authorization_type
- rbac_cluster? ? 'rbac' : 'abac'
+ create_rbac_cluster? ? 'rbac' : 'abac'
end
- def rbac_cluster?
+ def create_rbac_cluster?
!provider.legacy_abac?
end
diff --git a/app/services/clusters/gcp/kubernetes.rb b/app/services/clusters/gcp/kubernetes.rb
index 483f5a63c48..d014d73b3e8 100644
--- a/app/services/clusters/gcp/kubernetes.rb
+++ b/app/services/clusters/gcp/kubernetes.rb
@@ -8,7 +8,6 @@ module Clusters
SERVICE_ACCOUNT_TOKEN_NAME = 'gitlab-token'
CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
CLUSTER_ROLE_NAME = 'cluster-admin'
- EDIT_ROLE_NAME = 'edit'
end
end
end
diff --git a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb
index be2740d0c4e..d17744591e6 100644
--- a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb
+++ b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb
@@ -4,46 +4,46 @@ module Clusters
module Gcp
module Kubernetes
class CreateServiceAccountService
- attr_reader :kubeclient, :name, :namespace, :rbac
+ attr_reader :kubeclient, :rbac
- def initialize(kubeclient, name:, namespace:, rbac:)
+ def initialize(kubeclient, rbac:)
@kubeclient = kubeclient
- @name = name
- @namespace = namespace
@rbac = rbac
end
def execute
kubeclient.create_service_account(service_account_resource)
kubeclient.create_secret(service_account_token_resource)
- kubeclient.create_role_binding(role_binding_resource) if rbac
+ kubeclient.create_cluster_role_binding(cluster_role_binding_resource) if rbac
end
private
def service_account_resource
- Gitlab::Kubernetes::ServiceAccount.new(name, namespace).generate
+ Gitlab::Kubernetes::ServiceAccount.new(service_account_name, service_account_namespace).generate
end
def service_account_token_resource
Gitlab::Kubernetes::ServiceAccountToken.new(
- service_account_token_name, name, namespace).generate
+ SERVICE_ACCOUNT_TOKEN_NAME, service_account_name, service_account_namespace).generate
end
- def service_account_token_name
- SERVICE_ACCOUNT_TOKEN_NAME
+ def cluster_role_binding_resource
+ subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }]
+
+ Gitlab::Kubernetes::ClusterRoleBinding.new(
+ CLUSTER_ROLE_BINDING_NAME,
+ CLUSTER_ROLE_NAME,
+ subjects
+ ).generate
end
- def edit_role_name
- EDIT_ROLE_NAME
+ def service_account_name
+ SERVICE_ACCOUNT_NAME
end
- def role_binding_resource
- Gitlab::Kubernetes::RoleBinding.new(
- role_name: edit_role_name,
- namespace: namespace,
- service_account_name: name
- ).generate
+ def service_account_namespace
+ SERVICE_ACCOUNT_NAMESPACE
end
end
end
diff --git a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
index 89209ed8bfa..9e09345c8dc 100644
--- a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
+++ b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
@@ -4,11 +4,10 @@ module Clusters
module Gcp
module Kubernetes
class FetchKubernetesTokenService
- attr_reader :kubeclient, :namespace
+ attr_reader :kubeclient
- def initialize(kubeclient, namespace)
+ def initialize(kubeclient)
@kubeclient = kubeclient
- @namespace = namespace
end
def execute
@@ -19,16 +18,12 @@ module Clusters
private
def get_secret
- kubeclient.get_secret(service_account_token_name, namespace).as_json
+ kubeclient.get_secret(SERVICE_ACCOUNT_TOKEN_NAME, SERVICE_ACCOUNT_NAMESPACE).as_json
rescue Kubeclient::HttpError => err
raise err unless err.error_code == 404
nil
end
-
- def service_account_token_name
- SERVICE_ACCOUNT_TOKEN_NAME
- end
end
end
end
diff --git a/app/services/clusters/gcp/services_account_service.rb b/app/services/clusters/gcp/services_account_service.rb
deleted file mode 100644
index 064a00d4c2e..00000000000
--- a/app/services/clusters/gcp/services_account_service.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Gcp
- class ServicesAccountService
- attr_reader :kube_client, :cluster
-
- def initialize(kube_client, cluster)
- @kube_client = kube_client
- @cluster = cluster
- end
-
- def execute
- create_service_account
- create_namespaced_service_account
- end
-
- private
-
- def create_namespaced_service_account
- return unless cluster.platform_kubernetes_rbac?
-
- namespace_name = cluster.platform_kubernetes.actual_namespace
-
- ensure_namespace_exists(namespace_name)
- create_service_account(namespace: namespace_name, rbac: true)
- end
-
- def ensure_namespace_exists(namespace_name)
- Gitlab::Kubernetes::Namespace.new(namespace_name, kube_client).ensure_exists!
- end
-
- def create_service_account(namespace: 'default', rbac: false)
- Clusters::Gcp::Kubernetes::CreateServiceAccountService.new(
- kube_client,
- name: cluster.platform_kubernetes.service_account_name,
- namespace: namespace,
- rbac: rbac
- ).execute
- end
- end
- end
-end
diff --git a/spec/services/clusters/gcp/finalize_creation_service_spec.rb b/spec/services/clusters/gcp/finalize_creation_service_spec.rb
index 75d5e57fd83..0f484222228 100644
--- a/spec/services/clusters/gcp/finalize_creation_service_spec.rb
+++ b/spec/services/clusters/gcp/finalize_creation_service_spec.rb
@@ -1,168 +1,156 @@
-# frozen_string_literal: true
-
require 'spec_helper'
-describe Clusters::Gcp::FinalizeCreationService, '#execute' do
+describe Clusters::Gcp::FinalizeCreationService do
include GoogleApi::CloudPlatformHelpers
include KubernetesHelpers
- let(:cluster) { create(:cluster, :project, :providing_by_gcp) }
- let(:provider) { cluster.provider }
- let(:platform) { cluster.platform }
- let(:gcp_project_id) { provider.gcp_project_id }
- let(:zone) { provider.zone }
- let(:cluster_name) { cluster.name }
- let(:endpoint) { '111.111.111.111' }
- let(:api_url) { 'https://' + endpoint }
- let(:username) { 'sample-username' }
- let(:password) { 'sample-password' }
- let(:secret_name) { 'gitlab-token' }
-
- subject { described_class.new.execute(provider) }
-
- shared_examples 'success' do
- it 'configures provider and kubernetes' do
- subject
-
- expect(provider).to be_created
- end
+ describe '#execute' do
+ let(:cluster) { create(:cluster, :project, :providing_by_gcp) }
+ let(:provider) { cluster.provider }
+ let(:platform) { cluster.platform }
+ let(:gcp_project_id) { provider.gcp_project_id }
+ let(:zone) { provider.zone }
+ let(:cluster_name) { cluster.name }
- it 'properly configures database models' do
- subject
+ subject { described_class.new.execute(provider) }
- cluster.reload
+ shared_examples 'success' do
+ it 'configures provider and kubernetes' do
+ subject
- expect(provider.endpoint).to eq(endpoint)
- expect(platform.api_url).to eq(api_url)
- expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert))
- expect(platform.username).to eq(username)
- expect(platform.password).to eq(password)
- expect(platform.token).to eq(token)
+ expect(provider).to be_created
+ end
end
- end
-
- shared_examples 'error' do
- it 'sets an error to provider object' do
- subject
- expect(provider.reload).to be_errored
- end
- end
+ shared_examples 'error' do
+ it 'sets an error to provider object' do
+ subject
- shared_examples 'kubernetes information not successfully fetched' do
- context 'when failed to fetch gke cluster info' do
- before do
- stub_cloud_platform_get_zone_cluster_error(gcp_project_id, zone, cluster_name)
+ expect(provider.reload).to be_errored
end
-
- it_behaves_like 'error'
end
- context 'when token is empty' do
- let(:token) { '' }
+ context 'when suceeded to fetch gke cluster info' do
+ let(:endpoint) { '111.111.111.111' }
+ let(:api_url) { 'https://' + endpoint }
+ let(:username) { 'sample-username' }
+ let(:password) { 'sample-password' }
+ let(:secret_name) { 'gitlab-token' }
- it_behaves_like 'error'
- end
-
- context 'when failed to fetch kubernetes token' do
before do
- stub_kubeclient_get_secret_error(api_url, secret_name, namespace: namespace)
+ stub_cloud_platform_get_zone_cluster(
+ gcp_project_id, zone, cluster_name,
+ {
+ endpoint: endpoint,
+ username: username,
+ password: password
+ }
+ )
end
- it_behaves_like 'error'
+ context 'service account and token created' do
+ before do
+ stub_kubeclient_discover(api_url)
+ stub_kubeclient_create_service_account(api_url)
+ stub_kubeclient_create_secret(api_url)
+ end
+
+ shared_context 'kubernetes token successfully fetched' do
+ let(:token) { 'sample-token' }
+
+ before do
+ stub_kubeclient_get_secret(
+ api_url,
+ {
+ metadata_name: secret_name,
+ token: Base64.encode64(token)
+ } )
+ end
+ end
+
+ context 'provider legacy_abac is enabled' do
+ include_context 'kubernetes token successfully fetched'
+
+ it_behaves_like 'success'
+
+ it 'properly configures database models' do
+ subject
+
+ cluster.reload
+
+ expect(provider.endpoint).to eq(endpoint)
+ expect(platform.api_url).to eq(api_url)
+ expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert))
+ expect(platform.username).to eq(username)
+ expect(platform.password).to eq(password)
+ expect(platform).to be_abac
+ expect(platform.authorization_type).to eq('abac')
+ expect(platform.token).to eq(token)
+ end
+ end
+
+ context 'provider legacy_abac is disabled' do
+ before do
+ provider.legacy_abac = false
+ end
+
+ include_context 'kubernetes token successfully fetched'
+
+ context 'cluster role binding created' do
+ before do
+ stub_kubeclient_create_cluster_role_binding(api_url)
+ end
+
+ it_behaves_like 'success'
+
+ it 'properly configures database models' do
+ subject
+
+ cluster.reload
+
+ expect(provider.endpoint).to eq(endpoint)
+ expect(platform.api_url).to eq(api_url)
+ expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert))
+ expect(platform.username).to eq(username)
+ expect(platform.password).to eq(password)
+ expect(platform).to be_rbac
+ expect(platform.token).to eq(token)
+ end
+ end
+ end
+
+ context 'when token is empty' do
+ before do
+ stub_kubeclient_get_secret(api_url, token: '', metadata_name: secret_name)
+ end
+
+ it_behaves_like 'error'
+ end
+
+ context 'when failed to fetch kubernetes token' do
+ before do
+ stub_kubeclient_get_secret_error(api_url, secret_name)
+ end
+
+ it_behaves_like 'error'
+ end
+
+ context 'when service account fails to create' do
+ before do
+ stub_kubeclient_create_service_account_error(api_url)
+ end
+
+ it_behaves_like 'error'
+ end
+ end
end
- context 'when service account fails to create' do
+ context 'when failed to fetch gke cluster info' do
before do
- stub_kubeclient_create_service_account_error(api_url)
+ stub_cloud_platform_get_zone_cluster_error(gcp_project_id, zone, cluster_name)
end
it_behaves_like 'error'
end
end
-
- shared_context 'kubernetes information successfully fetched' do
- before do
- stub_cloud_platform_get_zone_cluster(
- gcp_project_id, zone, cluster_name,
- {
- endpoint: endpoint,
- username: username,
- password: password
- }
- )
-
- stub_kubeclient_discover(api_url)
- stub_kubeclient_create_service_account(api_url)
- stub_kubeclient_create_secret(api_url)
-
- stub_kubeclient_get_secret(
- api_url,
- {
- metadata_name: secret_name,
- token: Base64.encode64(token)
- }
- )
- end
- end
-
- context 'With a legacy ABAC cluster' do
- let(:token) { 'sample-token' }
- let(:namespace) { 'default' }
-
- before do
- provider.legacy_abac = true
- end
-
- include_context 'kubernetes information successfully fetched'
-
- it_behaves_like 'success'
-
- it 'uses ABAC authorization type' do
- subject
- cluster.reload
-
- expect(platform).to be_abac
- expect(platform.authorization_type).to eq('abac')
- end
-
- it_behaves_like 'kubernetes information not successfully fetched'
- end
-
- context 'With an RBAC cluster' do
- let(:token) { 'sample-token' }
- let(:namespace) { "#{cluster.project.path}-#{cluster.project.id}" }
-
- include_context 'kubernetes information successfully fetched'
-
- before do
- provider.legacy_abac = false
-
- stub_kubeclient_create_service_account(api_url, namespace: namespace)
- stub_kubeclient_create_secret(api_url, namespace: namespace)
- stub_kubeclient_create_role_binding(api_url, namespace: namespace)
- stub_kubeclient_create_namespace(api_url)
- stub_kubeclient_get_namespace(api_url, namespace: namespace)
-
- options = {
- metadata_name: secret_name,
- token: Base64.encode64(token),
- namespace: namespace
- }
-
- stub_kubeclient_get_secret(api_url, options)
- end
-
- it_behaves_like 'success'
-
- it 'uses RBAC authorization type' do
- subject
- cluster.reload
-
- expect(platform).to be_rbac
- expect(platform.authorization_type).to eq('rbac')
- end
-
- it_behaves_like 'kubernetes information not successfully fetched'
- end
end
diff --git a/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb
index 1b26735df16..065d021db5e 100644
--- a/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb
+++ b/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do
include KubernetesHelpers
- let(:service) { described_class.new(kubeclient, name: name, namespace: namespace, rbac: rbac) }
+ let(:service) { described_class.new(kubeclient, rbac: rbac) }
describe '#execute' do
let(:rbac) { false }
@@ -13,17 +13,6 @@ describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do
let(:username) { 'admin' }
let(:password) { 'xxx' }
- let(:cluster) do
- create(:cluster,
- :project, :provided_by_gcp,
- platform_kubernetes: create(:cluster_platform_kubernetes, :configured)
- )
- end
-
- let(:platform_kubernetes) { cluster.platform_kubernetes }
- let(:namespace) { platform_kubernetes.actual_namespace }
- let(:name) { platform_kubernetes.service_account_name }
-
let(:kubeclient) do
Gitlab::Kubernetes::KubeClient.new(
api_url,
@@ -37,18 +26,18 @@ describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do
context 'when params are correct' do
before do
stub_kubeclient_discover(api_url)
- stub_kubeclient_create_service_account(api_url, namespace: namespace)
- stub_kubeclient_create_secret(api_url, namespace: namespace)
+ stub_kubeclient_create_service_account(api_url)
+ stub_kubeclient_create_secret(api_url)
end
shared_examples 'creates service account and token' do
it 'creates a kubernetes service account' do
subject
- expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts").with(
+ expect(WebMock).to have_requested(:post, api_url + '/api/v1/namespaces/default/serviceaccounts').with(
body: hash_including(
kind: 'ServiceAccount',
- metadata: { name: name, namespace: namespace }
+ metadata: { name: 'gitlab', namespace: 'default' }
)
)
end
@@ -56,12 +45,12 @@ describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do
it 'creates a kubernetes secret of type ServiceAccountToken' do
subject
- expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets").with(
+ expect(WebMock).to have_requested(:post, api_url + '/api/v1/namespaces/default/secrets').with(
body: hash_including(
kind: 'Secret',
metadata: {
name: 'gitlab-token',
- namespace: namespace,
+ namespace: 'default',
annotations: {
'kubernetes.io/service-account.name': 'gitlab'
}
@@ -80,24 +69,24 @@ describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do
let(:rbac) { true }
before do
- stub_kubeclient_create_role_binding(api_url, namespace: namespace)
+ stub_kubeclient_create_cluster_role_binding(api_url)
end
it_behaves_like 'creates service account and token'
- it 'creates a kubernetes role binding with edit access' do
+ it 'creates a kubernetes cluster role binding' do
subject
- expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings").with(
+ expect(WebMock).to have_requested(:post, api_url + '/apis/rbac.authorization.k8s.io/v1/clusterrolebindings').with(
body: hash_including(
- kind: 'RoleBinding',
- metadata: { name: 'gitlab-edit', namespace: namespace },
+ kind: 'ClusterRoleBinding',
+ metadata: { name: 'gitlab-admin' },
roleRef: {
apiGroup: 'rbac.authorization.k8s.io',
- kind: 'Role',
- name: 'edit'
+ kind: 'ClusterRole',
+ name: 'cluster-admin'
},
- subjects: [{ kind: 'ServiceAccount', name: 'gitlab', namespace: namespace }]
+ subjects: [{ kind: 'ServiceAccount', namespace: 'default', name: 'gitlab' }]
)
)
end
diff --git a/spec/services/clusters/gcp/services_account_service_spec.rb b/spec/services/clusters/gcp/services_account_service_spec.rb
deleted file mode 100644
index f6f08eae666..00000000000
--- a/spec/services/clusters/gcp/services_account_service_spec.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Clusters::Gcp::ServicesAccountService, '#execute' do
- include GoogleApi::CloudPlatformHelpers
- include KubernetesHelpers
-
- let(:endpoint) { '111.111.111.111' }
- let(:api_url) { 'https://' + endpoint }
- let(:cluster) { create(:cluster, :project, :providing_by_gcp, platform_kubernetes: create(:cluster_platform_kubernetes)) }
- let(:username) { 'sample-username' }
- let(:password) { 'sample-password' }
-
- let(:kubeclient) do
- Gitlab::Kubernetes::KubeClient.new(
- api_url,
- ['api', 'apis/rbac.authorization.k8s.io'],
- auth_options: { username: username, password: password }
- )
- end
-
- subject { described_class.new(kubeclient, cluster).execute }
-
- context 'With an ABAC cluster' do
- before do
- stub_kubeclient_discover(api_url)
- stub_kubeclient_create_service_account(api_url)
- stub_kubeclient_create_secret(api_url)
- end
-
- it 'creates default service account' do
- subject
-
- expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/default/serviceaccounts").with(
- body: hash_including(
- kind: 'ServiceAccount',
- metadata: { name: 'gitlab', namespace: 'default' }
- )
- )
- end
- end
-
- context 'With an RBAC cluster' do
- let(:namespace) { "#{cluster.project.path}-#{cluster.project.id}" }
-
- before do
- cluster.platform_kubernetes.rbac!
-
- stub_kubeclient_discover(api_url)
- stub_kubeclient_create_service_account(api_url)
- stub_kubeclient_create_secret(api_url)
-
- stub_kubeclient_create_namespace(api_url)
- stub_kubeclient_get_namespace(api_url, namespace: namespace)
-
- stub_kubeclient_create_service_account(api_url, namespace: namespace)
- stub_kubeclient_create_secret(api_url, namespace: namespace)
- stub_kubeclient_create_role_binding(api_url, namespace: namespace)
- end
-
- it 'creates namespaced service account' do
- subject
-
- expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts").with(
- body: hash_including(
- kind: 'ServiceAccount',
- metadata: { name: "gitlab-#{namespace}", namespace: namespace }
- )
- )
- end
- end
-end