Skip to content

Commit

Permalink
Make sure project namespace is used instead of project name
Browse files Browse the repository at this point in the history
  • Loading branch information
nuwang committed Jun 9, 2020
1 parent 173ad5e commit 20e3ffb
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 28 deletions.
8 changes: 4 additions & 4 deletions cloudman/helmsman/tests/data/helmsman_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ install_templates:
template: |
ingress:
enabled: true
path: '/{{context.project}}/jupyterhub'
path: '{{context.project.access_path}}/jupyterhub'
hub:
baseUrl: '/{{context.project}}/jupyterhub'
baseUrl: '{{context.project.access_path}}/jupyterhub'
proxy:
secretToken: '{{random_alphanumeric(65)}}'
galaxy:
Expand All @@ -41,7 +41,7 @@ install_templates:
<url>https://ngkc4.cloudve.org/auth</url>
<client_id>galaxy-auth</client_id>
<client_secret>{{random_alphanumeric(8)}}-{{random_alphanumeric(4)}}-{{random_alphanumeric(4)}}-{{random_alphanumeric(12)}}</client_secret>
<redirect_uri>https://ngkc4.cloudve.org/{{context.project}}/galaxy/authnz/custos/callback</redirect_uri>
<redirect_uri>https://ngkc4.cloudve.org{{context.project.access_path}}/galaxy/authnz/custos/callback</redirect_uri>
<realm>master</realm>
</provider>
</OIDC>
Expand All @@ -65,7 +65,7 @@ install_templates:
enabled: true
hosts:
- ngkc4.cloudve.org
path: /{{context.project}}/galaxy
path: {{context.project.access_path}}/galaxy
tls:
- hosts:
- ngkc4.cloudve.org
Expand Down
28 changes: 22 additions & 6 deletions cloudman/helmsman/tests/mock_helm.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import argparse
import csv
from io import StringIO
import re
import uuid
import yaml

Expand All @@ -13,6 +14,8 @@ class MockHelm(object):
to simulate helm commands.
"""

DNS_SUBDOMAIN_REGEX = re.compile('^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{,63}(?<!-)$')

def __init__(self):
self.revision_history = [
{
Expand Down Expand Up @@ -60,6 +63,13 @@ def __init__(self):
self.repo_search_field_names = ["NAME", "CHART VERSION", "APP VERSION", "DESCRIPTION"]
self.parser = self._create_parser()

def validate_namespace(self, value):
value = str(value)
if not self.DNS_SUBDOMAIN_REGEX.match(value):
raise argparse.ArgumentTypeError(
"Namespace: \"%s\" must be a valid subdomain name which is RFC1123 compliant." % value)
return value

def _create_parser(self):
parser = argparse.ArgumentParser(prog='helm')

Expand All @@ -69,7 +79,8 @@ def _create_parser(self):
parser_list = subparsers.add_parser('list', help='list releases')
parser_list.add_argument('--all-namespaces', action='store_true',
help='list releases from all namespaces')
parser_list.add_argument('--namespace', type=str, help='namespace')
parser_list.add_argument('--namespace', type=self.validate_namespace,
help='namespace')
parser_list.set_defaults(func=self._helm_list)

# Helm install
Expand All @@ -80,7 +91,8 @@ def _create_parser(self):
'chart', type=str, help='chart name')
parser_inst.add_argument(
'--generate-name', action='store_true', help='generate random name')
parser_inst.add_argument('--namespace', type=str, help='namespace')
parser_inst.add_argument('--namespace', type=self.validate_namespace,
help='namespace')
parser_inst.add_argument('--version', type=str, help='version')
parser_inst.add_argument(
'-f', '--values', type=str, help='value files', nargs="*", action="append")
Expand Down Expand Up @@ -108,14 +120,16 @@ def _create_parser(self):
parser_rollback.add_argument(
'revision', type=int, help='revision number')
parser_rollback.add_argument(
'--namespace', type=str, help='namespace of release')
'--namespace', type=self.validate_namespace,
help='namespace of release')
parser_rollback.set_defaults(func=self._helm_rollback)

# Helm history
parser_history = subparsers.add_parser('history', help='prints historical revisions for a given release')
parser_history.add_argument(
'release', type=str, help='release name')
parser_history.add_argument('--namespace', type=str, help='namespace')
parser_history.add_argument('--namespace', type=self.validate_namespace,
help='namespace')
parser_history.set_defaults(func=self._helm_history)

# Helm repo commands
Expand All @@ -142,7 +156,8 @@ def _create_parser(self):
p_get_values.add_argument(
'--all', action='store_true', help='dump all values')
p_get_values.add_argument(
'--namespace', type=str, help='namespace of release')
'--namespace', type=self.validate_namespace,
help='namespace of release')
p_get_values.set_defaults(func=self._helm_get_values)


Expand All @@ -153,7 +168,8 @@ def _create_parser(self):
# Helm delete
parser_delete = subparsers.add_parser('delete', help='delete a release')
parser_delete.add_argument('release', type=str, help='release name')
parser_delete.add_argument('--namespace', type=str, help='namespace')
parser_delete.add_argument('--namespace', type=self.validate_namespace,
help='namespace')
parser_delete.set_defaults(func=self._helm_delete)

# Helm search
Expand Down
4 changes: 2 additions & 2 deletions cloudman/helmsman/tests/test_helmsman_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,9 @@ class InstallTemplateServiceTests(HelmsManServiceTestBase):
'context': {'project': 'test'},
'template': """ingress:
enabled: true
path: '/{{context.project}}/galaxy'
path: '{{context.project.access_path}}/galaxy'
hub:
baseUrl: '/{{context.project}}/galaxy'
baseUrl: '{{context.project.access_path}}/galaxy'
proxy:
secretToken: '{{random_alphanumeric(65)}}'"""
}
Expand Down
12 changes: 8 additions & 4 deletions cloudman/projman/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def _init_default_project_charts(self, project):
if chart_template:
project.charts.create(chart_template.name)
else:
client.charts.create("cloudve", "projman", project.name)
client.charts.create("cloudve", "projman", project.namespace)

def create(self, name):
self.check_permissions('projman.add_project')
Expand Down Expand Up @@ -178,22 +178,26 @@ def find(self, name):

def list(self):
return [self._to_proj_chart(chart) for chart
in self._get_helmsman_api().charts.list(self.project.name)
in self._get_helmsman_api().charts.list(self.project.namespace)
if self.has_permissions('projman.view_chart', chart)]

def get(self, chart_id):
chart = self._get_helmsman_api().charts.get(chart_id)
self.check_permissions('projman.view_chart', chart)
return (self._to_proj_chart(chart)
if chart and chart.namespace == self.project.name else None)
if chart and chart.namespace == self.project.namespace else None)

def create(self, template_name, release_name=None,
values=None, context=None):
self.check_permissions('projman.add_chart')
template = self._get_helmsman_api().templates.get(template_name)
if not context:
context = {}
context.update({'project': self.project.name})
context.update({'project': {
'name': self.project.name,
'namespace': self.project.namespace,
'access_path': f"/{self.project.namespace}",
}})
return self._to_proj_chart(
template.install(self.project.name, release_name,
values, context=context))
Expand Down
8 changes: 4 additions & 4 deletions cloudman/projman/tests/data/helmsman_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ install_templates:
template: |
ingress:
enabled: true
path: '/{{context.project}}/jupyterhub'
path: '{{context.project.access_path}}/jupyterhub'
hub:
baseUrl: '/{{context.project}}/jupyterhub'
baseUrl: '{{context.project.access_path}}/jupyterhub'
proxy:
secretToken: '{{random_alphanumeric(65)}}'
galaxy:
Expand All @@ -30,7 +30,7 @@ install_templates:
<url>https://ngkc4.cloudve.org/auth</url>
<client_id>galaxy-auth</client_id>
<client_secret>{{random_alphanumeric(8)}}-{{random_alphanumeric(4)}}-{{random_alphanumeric(4)}}-{{random_alphanumeric(12)}}</client_secret>
<redirect_uri>https://ngkc4.cloudve.org/{{context.project}}/galaxy/authnz/custos/callback</redirect_uri>
<redirect_uri>https://ngkc4.cloudve.org{{context.project.access_path}}/galaxy/authnz/custos/callback</redirect_uri>
<realm>master</realm>
</provider>
</OIDC>
Expand All @@ -54,7 +54,7 @@ install_templates:
enabled: true
hosts:
- ngkc4.cloudve.org
path: /{{context.project}}/galaxy
path: {{context.project.access_path}}/galaxy
tls:
- hosts:
- ngkc4.cloudve.org
Expand Down
37 changes: 29 additions & 8 deletions cloudman/projman/tests/test_project_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,25 @@ class ProjectServiceTests(ProjManManServiceTestBase):
'name': 'GVL'
}

def _create_project(self):
def _create_project(self, project_data=None):
# create the object
url = reverse('projman:projects-list')
return self.client.post(url, self.PROJECT_DATA, format='json')
return self.client.post(url, project_data or self.PROJECT_DATA, format='json')

def _list_project(self):
def _list_project(self, project_data=None):
# list existing objects
url = reverse('projman:projects-list')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
self.assertDictContainsSubset(self.PROJECT_DATA, response.data['results'][0])
self.assertDictContainsSubset(project_data or self.PROJECT_DATA, response.data['results'][0])
return response.data['results'][0]['id']

def _check_project_exists(self, project_id):
def _check_project_exists(self, project_id, project_data=None):
# check it exists
url = reverse('projman:projects-detail', args=[project_id])
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertDictContainsSubset(self.PROJECT_DATA, response.data)
self.assertDictContainsSubset(project_data or self.PROJECT_DATA, response.data)
return response.data['id']

def _delete_project(self, project_id):
Expand Down Expand Up @@ -139,6 +139,27 @@ def test_namespace_tied_to_project(self):
obj = client.namespaces.get(namespace)
self.assertIsNone(obj, "Deleting the project should delete namespace")

def test_project_name_special_chars(self):
"""
When creating a project with special chars, the corresponding namespace
should be RFC1123 compliant.
"""
PROJECT_DATA = {
"name": "A non RFC1123 compli'ant name"
}
response = self._create_project(project_data=PROJECT_DATA)
# Namespace should be slugified version of project name
project_id = response.data['id']
namespace = response.data['namespace']
self.assertEquals(namespace, "a-non-rfc1123-compliant-name")
admin = User.objects.filter(is_superuser=True).first()
client = HelmsManAPI(HMServiceContext(user=admin))
obj = client.namespaces.get(namespace)
assert obj
self._delete_project(project_id)
obj = client.namespaces.get(namespace)
self.assertIsNone(obj, "Deleting the project should delete namespace")


class ProjectChartServiceTests(ProjManManServiceTestBase):

Expand Down Expand Up @@ -196,11 +217,11 @@ def _list_project_chart(self, project_id):

return response.data['results'][1]['id']

def _check_project_chart_exists(self, project_id, chart_id):
def _check_project_chart_exists(self, project_id, chart_id, project_data=None):
url = reverse('projman:chart-detail', args=[project_id, chart_id])
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertDictContainsSubset(self.PROJECT_DATA, response.data['project'])
self.assertDictContainsSubset(project_data or self.PROJECT_DATA, response.data['project'])
# Flatten dicts because assertDictContainsSubset doesn't handle nested dicts
response_chart = hm_helpers.flatten_dict(response.data)
expected_chart = hm_helpers.flatten_dict(self.CHART_DATA)
Expand Down

0 comments on commit 20e3ffb

Please sign in to comment.