Skip to content

Commit

Permalink
Merge ad6f916 into 86af8ca
Browse files Browse the repository at this point in the history
  • Loading branch information
nuwang committed Jan 23, 2020
2 parents 86af8ca + ad6f916 commit 7a472c3
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 36 deletions.
2 changes: 1 addition & 1 deletion cloudman/clusterman/cluster_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def add_node(self, name, size):
"config_appliance": {
"sshUser": "ubuntu",
"runner": "ansible",
"repository": " https://github.com/CloudVE/ansible-docker-boot",
"repository": "https://github.com/CloudVE/ansible-docker-boot",
"inventoryTemplate": "${host}\n\n"
"[all:vars]\n"
"ansible_ssh_port=22\n"
Expand Down
2 changes: 2 additions & 0 deletions cloudman/helmsman/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def list(self):
name=client.releases.parse_chart_name(release.get('CHART')),
namespace=release.get("NAMESPACE"),
chart_version=client.releases.parse_chart_version(release.get('CHART')),
revision=release.get("REVISION"),
app_version=release.get("APP VERSION"),
state=release.get("STATUS"),
updated=release.get("UPDATED"),
Expand Down Expand Up @@ -181,6 +182,7 @@ def __init__(self, service, id, name, namespace, **kwargs):
self.namespace = namespace
self.display_name = self.name.title()
self.chart_version = kwargs.get('chart_version')
self.revision = kwargs.get('revision')
self.app_version = kwargs.get('app_version')
self.state = kwargs.get('state')
self.updated = kwargs.get('updated')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def process_settings(settings):
if chart.get('namespace'):
extra_args["namespace"] = chart.get('namespace')
if chart.get('version'):
extra_args["version"] = chart.get('version')
extra_args["chart_version"] = chart.get('version')
if chart.get('values'):
values = chart.get('values')
with tempfile.NamedTemporaryFile(mode="w", prefix="helmsman") as f:
Expand Down
3 changes: 2 additions & 1 deletion cloudman/helmsman/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ class HMChartSerializer(serializers.Serializer):
name = serializers.CharField()
display_name = serializers.CharField(read_only=True)
chart_version = serializers.CharField(allow_blank=True, required=False)
revision = serializers.IntegerField(allow_null=True, required=False)
app_version = serializers.CharField(read_only=True)
namespace = serializers.CharField()
state = serializers.CharField(read_only=True)
state = serializers.CharField(allow_blank=True, read_only=False)
updated = serializers.CharField(read_only=True)
access_address = serializers.CharField(read_only=True)
values = serializers.DictField()
Expand Down
111 changes: 82 additions & 29 deletions cloudman/helmsman/tests/mock_helm.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ def __init__(self, testcase):
self.patch2.start()
testcase.addCleanup(self.patch2.stop)
testcase.addCleanup(self.patch1.stop)
self.chart_list_field_name = ["NAME", "REVISION", "UPDATED", "STATUS",
"CHART", "APP VERSION", "NAMESPACE"]
self.installed_charts = [
self.chart_list_field_names = ["NAME", "REVISION", "UPDATED", "STATUS",
"CHART", "APP VERSION", "NAMESPACE"]
self.chart_history_field_names = ["REVISION", "UPDATED", "STATUS",
"CHART", "APP VERSION", "DESCRIPTION"]
self.revision_history = [
{
'NAME': 'turbulent-markhor',
'REVISION': 12,
Expand All @@ -35,6 +37,10 @@ def __init__(self, testcase):
}
}
]
self.chart_database = {
'turbulent-markhor': self.revision_history
}

self.repo_list_field_name = ["NAME", "URL"]
self.installed_repos = {
'stable': {
Expand Down Expand Up @@ -82,6 +88,20 @@ def _parse_helm_command(self, command):
'-f', '--values', type=str, help='value files')
parser_upgrade.set_defaults(func=self._helm_upgrade)

# Helm rollback
parser_rollback = subparsers.add_parser('rollback', help='rolls back a release to a previous revision')
parser_rollback.add_argument(
'release', type=str, help='release name')
parser_rollback.add_argument(
'revision', type=int, help='revision number')
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.set_defaults(func=self._helm_history)

# Helm repo commands
parser_repo = subparsers.add_parser('repo', help='repo commands')
subparser_repo = parser_repo.add_subparsers()
Expand Down Expand Up @@ -127,42 +147,79 @@ def _helm_init(self, args):
def _helm_list(self, args):
# pretend to succeed
with StringIO() as output:
writer = csv.DictWriter(output, fieldnames=self.chart_list_field_name,
writer = csv.DictWriter(output, fieldnames=self.chart_list_field_names,
delimiter="\t", extrasaction='ignore')
writer.writeheader()
writer.writerows(self.installed_charts)
for release in self.chart_database.values():
# Write data about the latest revision for each chart
writer.writerow(release[-1])
return output.getvalue()

def _helm_install(self, args):
repo_name, chart_name = args.chart.split('/')
chart = {
'NAME': '%s-%s' % (chart_name, uuid.uuid4().hex[:6]),
release_name = '%s-%s' % (chart_name, uuid.uuid4().hex[:6])
revision = {
'NAME': release_name,
'REVISION': 1,
'UPDATED': 'Fri Apr 19 05:33:37 2019',
'STATUS': 'DEPLOYED',
'CHART': '%s-%s' % (chart_name, args.version or "1.0.0"),
'APP VERSION': '2.0.2',
'NAMESPACE': args.namespace,
'DESCRIPTION': 'Initial Install',
'VALUES': {}
}
if args.values:
with open(args.values, 'r') as f:
values = yaml.safe_load(f)
chart['VALUES'] = values
self.installed_charts.append(chart)
return chart
revision['VALUES'] = values
self.chart_database[release_name] = [revision]
return revision

def _helm_upgrade(self, args):
matches = [chart for chart in self.installed_charts
if chart.get('NAME') == args.release]
if not matches:
revisions = self.chart_database.get(args.release)
if not revisions:
return 'Error: "%s" has no deployed releases' % args.release
release = matches[0]
latest_release = revisions[-1]
new_release = dict(latest_release)
new_release['REVISION'] += 1
new_release['DESCRIPTION'] = 'Upgraded successfully'
if args.values:
with open(args.values, 'r') as f:
values = yaml.safe_load(f)
release['VALUES'] = values
return release
new_release['VALUES'] = values
revisions.append(new_release)
return new_release

def _helm_rollback(self, args):
revisions = self.chart_database.get(args.release)
if not revisions:
return 'Error: "%s" has no deployed releases' % args.release
latest_revision = revisions[-1]
if args.revision:
matches = [r for r in revisions if r['REVISION'] == args.revision]
if not matches:
return 'Error: "%s" has no matching revision %s' % (args.release, args.revision)
rollback_revision = matches[0]
else:
rollback_revision = revisions[-1]
new_revision = dict(rollback_revision)
new_revision['REVISION'] = latest_revision['REVISION'] + 1
new_revision['DESCRIPTION'] = 'Rolled back to %s' % args.revision
revisions.append(new_revision)
return new_revision

def _helm_history(self, args):
revisions = self.chart_database.get(args.release)
if not revisions:
return 'Error: "%s" has no deployed releases' % args.release
# pretend to succeed
with StringIO() as output:
writer = csv.DictWriter(output, fieldnames=self.chart_history_field_names,
delimiter="\t", extrasaction='ignore')
writer.writeheader()
writer.writerows(revisions)
return output.getvalue()

def _helm_repo_update(self, args):
# pretend to succeed
Expand All @@ -186,27 +243,23 @@ def _helm_repo_list(self, args):
return output.getvalue()

def _helm_get_values(self, args):
matches = [chart for chart in self.installed_charts
if chart.get('NAME') == args.release]
if matches:
chart = matches[0]
with StringIO() as output:
yaml.safe_dump(chart.get('VALUES'), output, allow_unicode=True)
return output.getvalue()
else:
revisions = self.chart_database.get(args.release)
if not revisions:
return 'Error: release: "%s" not found' % args.release
latest_release = revisions[-1]
with StringIO() as output:
yaml.safe_dump(latest_release.get('VALUES'), output, allow_unicode=True)
return output.getvalue()

def _helm_get_manifest(self, args):
# pretend to succeed
pass

def _helm_delete(self, args):
matches = [chart for chart in self.installed_charts
if chart.get('NAME') == args.release]
if matches:
self.installed_charts.remove(matches[0])
else:
revisions = self.chart_database.get(args.release)
if not revisions:
return 'Error: release: "%s" not found' % args.release
self.chart_database.pop(args.release, None)

def mock_run_command(self, command, shell=False):
if isinstance(command, list):
Expand Down
5 changes: 5 additions & 0 deletions cloudman/projman/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ def update(self, chart, values):
updated_chart = self._get_helmsman_api().charts.update(chart, values)
return self._to_proj_chart(updated_chart)

def rollback(self, chart, revision=None):
self.check_permissions('charts.change_chart', chart)
updated_chart = self._get_helmsman_api().charts.rollback(chart, revision)
return self._to_proj_chart(updated_chart)

def delete(self, chart_id):
obj = self.get(chart_id)
if obj:
Expand Down
7 changes: 5 additions & 2 deletions cloudman/projman/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def create(self, valid_data):
% project_id)
return project.charts.create(
valid_data.get('repo_name', 'cloudve'), valid_data.get('name'),
valid_data.get('release_name'), valid_data.get('chart_version'),
valid_data.get('release_name'), valid_data.get('version'),
valid_data.get('values'))

def update(self, chart, validated_data):
Expand All @@ -60,7 +60,10 @@ def update(self, chart, validated_data):
if not project:
raise ValidationError("Specified project id: %s does not exist"
% project_id)
return project.charts.update(chart, validated_data.get("values"))
if validated_data.get('state') == "rollback":
return project.charts.rollback(chart)
else:
return project.charts.update(chart, validated_data.get("values"))


class UserSerializer(dj_serializers.UserDetailsSerializer):
Expand Down
38 changes: 37 additions & 1 deletion cloudman/projman/tests/test_project_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class ProjectChartServiceTests(ProjManManServiceTestBase):
CHART_DATA = {
'name': 'galaxy',
'display_name': 'Galaxy',
'chart_version': '3.0.0',
'chart_version': '1.0.0',
'state': "DEPLOYED",
'values': {
'hello': 'world'
Expand Down Expand Up @@ -164,13 +164,31 @@ def _update_project_chart(self, project_id, chart_id):
self.assertEqual(response.status_code, status.HTTP_200_OK)
return response.data['id']

def _rollback_project_chart(self, project_id, chart_id):
url = reverse('projman:chart-detail', args=[project_id, chart_id])
response = self.client.get(url)
chart = response.data
chart['state'] = 'rollback'
return self.client.put(url, chart, format='json')

def _check_project_chart_update(self, project_id, chart_id):
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.assertEqual(response.data['revision'], 2)
self.assertEqual(response.data['values']['hello'], 'anotherworld')
self.assertEqual(response.data['values']['new_value'], 'anothervalue')

def _check_project_chart_rollback(self, project_id, chart_id):
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.assertEqual(response.data['revision'], 3)
# Should have reverted to original value
self.assertEqual(response.data['values']['hello'], 'world')
# Should have lost upgraded value
self.assertNotIn('new_value', response.data['values'])

def _check_project_chart_reuse_values(self, project_id, chart_id):
url = reverse('projman:chart-detail', args=[project_id, chart_id])
response = self.client.get(url)
Expand Down Expand Up @@ -240,3 +258,21 @@ def test_can_view_shared_chart(self):
chart_id_now = self._list_project_chart(project_id)
assert chart_id_now # should be visible
assert chart_id_then == chart_id_now # should be the same chart

def test_chart_rollback(self):
project_id = self._create_project()
self._create_project_chart(project_id)
chart_id = self._list_project_chart(project_id)
self._update_project_chart(project_id, chart_id)
self._rollback_project_chart(project_id, chart_id)
self._check_project_chart_rollback(project_id, chart_id)

def test_chart_rollback_unauthorized(self):
project_id = self._create_project()
self._create_project_chart(project_id)
chart_id = self._list_project_chart(project_id)
self._update_project_chart(project_id, chart_id)
self.client.force_login(
User.objects.get_or_create(username='projadminnoauth', is_staff=False)[0])
response = self._rollback_project_chart(project_id, chart_id)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, response.data)
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ envlist = py36
skipsdist = True

[testenv]
commands = {envpython} -m coverage run --source cloudman --branch cloudman/manage.py test cloudman
commands = {envpython} -m coverage run --source cloudman --branch cloudman/manage.py test cloudman {posargs}
setenv =
CELERY_CONFIG_MODULE=cloudlaunchserver.celeryconfig_test
# Fix for import issue: https://github.com/travis-ci/travis-ci/issues/7940
Expand Down

0 comments on commit 7a472c3

Please sign in to comment.