chore: Switch to new images (#830)
Co-authored-by: Oleksandr K <okozachenko@vexxhost.com>
Co-authored-by: okozachenko1203 <okozachenko1203@users.noreply.github.com>
diff --git a/images/horizon/Earthfile b/images/horizon/Earthfile
new file mode 100644
index 0000000..6ff9199
--- /dev/null
+++ b/images/horizon/Earthfile
@@ -0,0 +1,18 @@
+VERSION 0.7
+
+image:
+ ARG PROJECT=horizon
+ ARG RELEASE=2023.2
+ ARG REF=3c6029cd94846235e25058b71522c13556f41f58
+ FROM ../openstack-service+image \
+ --PROJECT ${PROJECT} \
+ --RELEASE ${RELEASE} \
+ --PROJECT_REF ${REF} \
+ --PIP_PACKAGES "git+https://github.com/openstack/designate-dashboard.git@stable/${RELEASE} git+https://github.com/openstack/heat-dashboard.git@stable/${RELEASE} git+https://github.com/openstack/ironic-ui.git@stable/${RELEASE} git+https://github.com/openstack/magnum-ui.git@stable/${RELEASE} git+https://github.com/openstack/neutron-vpnaas-dashboard.git@stable/${RELEASE} git+https://github.com/openstack/octavia-dashboard.git@stable/${RELEASE} git+https://github.com/openstack/senlin-dashboard.git@stable/${RELEASE} git+https://github.com/openstack/manila-ui.git@stable/${RELEASE}"
+ DO \
+ ../+APT_INSTALL \
+ --PACKAGES "apache2 gettext libapache2-mod-wsgi-py3"
+ DO ../+APPLY_PATCHES
+ SAVE IMAGE --push \
+ ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
+ ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
diff --git a/images/horizon/patches/0000-fix-ignore-errors-when-flavors-are-deleted.patch b/images/horizon/patches/0000-fix-ignore-errors-when-flavors-are-deleted.patch
new file mode 100644
index 0000000..50d68c9
--- /dev/null
+++ b/images/horizon/patches/0000-fix-ignore-errors-when-flavors-are-deleted.patch
@@ -0,0 +1,107 @@
+From c62527488bfeab588c4abbc8426688e4feef87a4 Mon Sep 17 00:00:00 2001
+From: okozachenko <okozachenko1203@gmail.com>
+Date: Thu, 2 Nov 2023 01:27:20 +1100
+Subject: [PATCH] fix: ignore errors when flavors are deleted
+
+The code used to list flavors when in the admin
+or project side was not consistent and raised
+alerts if viewing in the admin side but not in the
+project side.
+
+This patch moves their behaviour to be consistent
+and refactors the code to use the same code-base.
+
+Closes-Bug: #2042362
+Change-Id: I37cc02102285b1e83ec1343b710a57fb5ac4ba15
+(cherry picked from commit 40759aa9cdb9b2162b3f50df751c500db94943b3)
+---
+ .../dashboards/admin/instances/tests.py | 4 ----
+ .../dashboards/admin/instances/views.py | 17 +++++------------
+ .../dashboards/project/instances/tests.py | 1 +
+ .../dashboards/project/instances/views.py | 11 +++--------
+ 4 files changed, 9 insertions(+), 24 deletions(-)
+
+diff --git a/openstack_dashboard/dashboards/admin/instances/tests.py b/openstack_dashboard/dashboards/admin/instances/tests.py
+index 3630cb79ade..c6cf65e5dab 100644
+--- a/openstack_dashboard/dashboards/admin/instances/tests.py
++++ b/openstack_dashboard/dashboards/admin/instances/tests.py
+@@ -133,10 +133,6 @@ def test_index_flavor_get_exception(self):
+ res = self.client.get(INDEX_URL)
+ instances = res.context['table'].data
+ self.assertTemplateUsed(res, INDEX_TEMPLATE)
+- # Since error messages produced for each instance are identical,
+- # there will be only one error message for all instances
+- # (messages de-duplication).
+- self.assertMessageCount(res, error=1)
+ self.assertCountEqual(instances, servers)
+
+ self.assertEqual(self.mock_image_list_detailed.call_count, 4)
+diff --git a/openstack_dashboard/dashboards/admin/instances/views.py b/openstack_dashboard/dashboards/admin/instances/views.py
+index c35527fe465..efa28dd763e 100644
+--- a/openstack_dashboard/dashboards/admin/instances/views.py
++++ b/openstack_dashboard/dashboards/admin/instances/views.py
+@@ -33,6 +33,8 @@
+ from openstack_dashboard.dashboards.admin.instances \
+ import tables as project_tables
+ from openstack_dashboard.dashboards.admin.instances import tabs
++from openstack_dashboard.dashboards.project.instances \
++ import utils as instance_utils
+ from openstack_dashboard.dashboards.project.instances import views
+ from openstack_dashboard.dashboards.project.instances.workflows \
+ import update_instance
+@@ -215,18 +217,9 @@ def get_data(self):
+ else:
+ inst.image['name'] = _("-")
+
+- flavor_id = inst.flavor["id"]
+- try:
+- if flavor_id in flavor_dict:
+- inst.full_flavor = flavor_dict[flavor_id]
+- else:
+- # If the flavor_id is not in flavor_dict list,
+- # gets it via nova api.
+- inst.full_flavor = api.nova.flavor_get(
+- self.request, flavor_id)
+- except Exception:
+- msg = _('Unable to retrieve instance size information.')
+- exceptions.handle(self.request, msg)
++ inst.full_flavor = instance_utils.resolve_flavor(self.request,
++ inst, flavor_dict)
++
+ tenant = tenant_dict.get(inst.tenant_id, None)
+ inst.tenant_name = getattr(tenant, "name", None)
+ return instances
+diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py
+index 70d32bc4b3c..c44dedd5b5a 100644
+--- a/openstack_dashboard/dashboards/project/instances/tests.py
++++ b/openstack_dashboard/dashboards/project/instances/tests.py
+@@ -316,6 +316,7 @@ def test_index_flavor_list_exception(self):
+ self.mock_is_feature_available.return_value = True
+ self.mock_server_list_paged.return_value = [servers, False, False]
+ self.mock_servers_update_addresses.return_value = None
++ self.mock_flavor_get.side_effect = self.exceptions.nova
+ self.mock_flavor_list.side_effect = self.exceptions.nova
+ self.mock_image_list_detailed.return_value = (self.images.list(),
+ False, False)
+diff --git a/openstack_dashboard/dashboards/project/instances/views.py b/openstack_dashboard/dashboards/project/instances/views.py
+index badf540b830..b848f6fffd9 100644
+--- a/openstack_dashboard/dashboards/project/instances/views.py
++++ b/openstack_dashboard/dashboards/project/instances/views.py
+@@ -171,14 +171,9 @@ def get_data(self):
+ for instance in instances:
+ self._populate_image_info(instance, image_dict, volume_dict)
+
+- flavor_id = instance.flavor["id"]
+- if flavor_id in flavor_dict:
+- instance.full_flavor = flavor_dict[flavor_id]
+- else:
+- # If the flavor_id is not in flavor_dict,
+- # put info in the log file.
+- LOG.info('Unable to retrieve flavor "%s" for instance "%s".',
+- flavor_id, instance.id)
++ instance.full_flavor = instance_utils.resolve_flavor(self.request,
++ instance,
++ flavor_dict)
+
+ return instances
+
diff --git a/images/horizon/patches/0001-fix-disable-resizing-for-admins.patch b/images/horizon/patches/0001-fix-disable-resizing-for-admins.patch
new file mode 100644
index 0000000..aaa5058
--- /dev/null
+++ b/images/horizon/patches/0001-fix-disable-resizing-for-admins.patch
@@ -0,0 +1,112 @@
+From d3ac70fb12dc363a0fbed39bcfd3642e36f4515d Mon Sep 17 00:00:00 2001
+From: Mohammed Naser <mnaser@vexxhost.com>
+Date: Mon, 20 Feb 2023 00:55:14 +0000
+Subject: [PATCH] fix: disable resizing for admins
+
+By default, admins see all clusters and they are allowed to do all
+actions however the resize function will not work so we're displaying
+something for admins that they can't use.
+
+This will hide the resize button for clusters that don't match the
+project ID of the current user.
+
+Change-Id: If09c509abdd21a5a7b9bc374af52a06404fb0ff8
+(cherry picked from commit 345f853567d25f1b163025f0295c742582052748)
+---
+ .../clusters/resize/resize.service.js | 9 ++++---
+ .../clusters/resize/resize.service.spec.js | 27 ++++++++++++++-----
+ 2 files changed, 26 insertions(+), 10 deletions(-)
+
+diff --git a/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.js b/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.js
+index ebc6a961..b86833a0 100644
+--- a/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.js
++++ b/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.js
+@@ -32,6 +32,7 @@
+ resizeService.$inject = [
+ '$rootScope',
+ '$q',
++ 'horizon.app.core.openstack-service-api.userSession',
+ 'horizon.app.core.openstack-service-api.magnum',
+ 'horizon.framework.util.actions.action-result.service',
+ 'horizon.framework.util.i18n.gettext',
+@@ -43,8 +44,8 @@
+ ];
+
+ function resizeService(
+- $rootScope, $q, magnum, actionResult, gettext, $qExtensions, modal, toast, spinnerModal,
+- resourceType
++ $rootScope, $q, userSession, magnum, actionResult, gettext, $qExtensions,
++ modal, toast, spinnerModal, resourceType
+ ) {
+
+ var modalConfig, formModel;
+@@ -87,8 +88,8 @@
+ return deferred.promise;
+ }
+
+- function allowed() {
+- return $qExtensions.booleanAsPromise(true);
++ function allowed(selected) {
++ return userSession.isCurrentProject(selected.project_id);
+ }
+
+ function constructModalConfig(workerNodesList) {
+diff --git a/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.spec.js b/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.spec.js
+index 842df87d..27fa8064 100644
+--- a/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.spec.js
++++ b/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.spec.js
+@@ -19,16 +19,17 @@
+
+ describe('horizon.dashboard.container-infra.clusters.resize.service', function() {
+
+- var service, $scope, $q, deferred, magnum, spinnerModal, modalConfig;
++ var service, $scope, $q, deferred, magnum, spinnerModal, modalConfig, userSession;
+ var selected = {
+- id: 1
++ id: 1,
++ project_id: "f5ed2d21437644adb2669f9ade9c949b"
+ };
+ var modal = {
+ open: function(config) {
+ deferred = $q.defer();
+ deferred.resolve(config);
+ modalConfig = config;
+-
++``
+ return deferred.promise;
+ }
+ };
+@@ -50,6 +51,7 @@
+ 'horizon.dashboard.container-infra.clusters.resize.service');
+ magnum = $injector.get('horizon.app.core.openstack-service-api.magnum');
+ spinnerModal = $injector.get('horizon.framework.widgets.modal-wait-spinner.service');
++ userSession = $injector.get('horizon.app.core.openstack-service-api.userSession');
+
+ spyOn(spinnerModal, 'showModalSpinner').and.callFake(function() {});
+ spyOn(spinnerModal, 'hideModalSpinner').and.callFake(function() {});
+@@ -60,9 +62,22 @@
+ spyOn(modal, 'open').and.callThrough();
+ }));
+
+- it('should check the policy if the user is allowed to update cluster', function() {
+- var allowed = service.allowed();
+- expect(allowed).toBeTruthy();
++ it('should allow user to resize cluster if they are in the same project', async function() {
++ spyOn(userSession, 'get').and.returnValue({project_id: selected.project_id});
++
++ await service.allowed(selected);
++ });
++
++ it('should not allow user to resize cluster if they are in a different project', async function() {
++ spyOn(userSession, 'get').and.returnValue({project_id: 'different_project'});
++
++ try {
++ await service.allowed(selected);
++ } catch (err) {
++ return;
++ }
++
++ throw new Error('User should not be allowed to resize cluster');
+ });
+
+ it('should open the modal, hide the loading spinner and check the form model',
diff --git a/images/horizon/patches/0002-capi-avoid-going-through-heat-for-worker-list.patch b/images/horizon/patches/0002-capi-avoid-going-through-heat-for-worker-list.patch
new file mode 100644
index 0000000..5970ef2
--- /dev/null
+++ b/images/horizon/patches/0002-capi-avoid-going-through-heat-for-worker-list.patch
@@ -0,0 +1,68 @@
+From 6ecbb870f24f5c5c4a5b548166ac292801adda84 Mon Sep 17 00:00:00 2001
+From: Mohammed Naser <mnaser@vexxhost.com>
+Date: Sun, 19 Feb 2023 21:39:46 +0000
+Subject: [PATCH] [capi] Avoid going through Heat for worker list
+
+By default, Magnum UI goes through Heat to get the list of nodes
+which is not correct since it's making an assumption that Heat
+is always in use.
+
+The fix for this would be to make sure that Magnum has a list of
+all the VMs in it's database (or some sort of API call that
+returns them all from the driver) but that's quite a big amount
+of work to implement for now.
+
+So for now, if stack_id doesn't look like a UUID, we assume it
+is deployed using Clsuter API driver for Magnum and look up with
+that alternative method instead.
+
+(cherry picked from commit 6f31cc5cacf23398b76392922ee9863d50aa9e7e)
+(cherry picked from commit d44f16f13a89d7fb00d3d949a392d638ce2d0cc8)
+(cherry picked from commit 72122e350429590e9002058e7e35c4dcc94d2d4f)
+---
+ magnum_ui/api/rest/magnum.py | 18 ++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+
+diff --git a/magnum_ui/api/rest/magnum.py b/magnum_ui/api/rest/magnum.py
+index ba66e0e..bf331bc 100644
+--- a/magnum_ui/api/rest/magnum.py
++++ b/magnum_ui/api/rest/magnum.py
+@@ -17,6 +17,8 @@
+
+ from collections import defaultdict
+
++from oslo_utils import uuidutils
++
+ from django.conf import settings
+ from django.http import HttpResponse
+ from django.http import HttpResponseNotFound
+@@ -228,6 +230,19 @@ class ClusterResize(generic.View):
+
+ url_regex = r'container_infra/clusters/(?P<cluster_id>[^/]+)/resize$'
+
++ def _cluster_api_resize_get(self, request, cluster):
++ search_opts = {"name": "%s-" % cluster["stack_id"]}
++ servers = api.nova.server_list(request, search_opts=search_opts)[0]
++
++ worker_nodes = []
++ for server in servers:
++ control_plane_prefix = "%s-control-plane" % cluster["stack_id"]
++ if not server.name.startswith(control_plane_prefix):
++ worker_nodes.append({"name": server.name, "id": server.id})
++
++ return {"cluster": change_to_id(cluster),
++ "worker_nodes": worker_nodes}
++
+ @rest_utils.ajax()
+ def get(self, request, cluster_id):
+ """Get cluster details for resize"""
+@@ -237,6 +252,9 @@ def get(self, request, cluster_id):
+ print(e)
+ return HttpResponseNotFound()
+
++ if not uuidutils.is_uuid_like(cluster["stack_id"]):
++ return self._cluster_api_resize_get(request, cluster)
++
+ stack = heat.stack_get(request, cluster["stack_id"])
+ search_opts = {"name": "%s-" % stack.stack_name}
+ servers = api.nova.server_list(request, search_opts=search_opts)[0]