Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make sure project namespace is used instead of project name #122

Merged
merged 1 commit into from
Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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