From 0ced1017250c1dcd17a7a66acb19d5ebc0371a96 Mon Sep 17 00:00:00 2001 From: Jan Fooken Date: Tue, 9 Jul 2024 16:30:31 +0200 Subject: [PATCH 01/11] Add factory for affected packages --- assets/js/lib/test-utils/factories/advisoryErrata.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/assets/js/lib/test-utils/factories/advisoryErrata.js b/assets/js/lib/test-utils/factories/advisoryErrata.js index ce184270fc..d64793f98c 100644 --- a/assets/js/lib/test-utils/factories/advisoryErrata.js +++ b/assets/js/lib/test-utils/factories/advisoryErrata.js @@ -2,6 +2,14 @@ import { faker } from '@faker-js/faker'; import { Factory } from 'fishery'; import { advisoryType } from './relevantPatches'; +const affectedPackageFactory = Factory.define(() => ({ + name: faker.animal.cat().toLowerCase(), + arch_label: faker.helpers.arrayElement(["x86_64", "i586", "aarch64"]), + version: faker.system.semver(), + release: `${faker.number.int({ min: 0, max: 100 })}`, + epoch: `${faker.number.int({ min: 0, max: 50 })}`, +})); + const fixMapFactory = Factory.define(({ transientParams }) => { const { length = 1 } = transientParams; @@ -31,6 +39,8 @@ export const advisoryErrataFactory = Factory.define(({ params }) => ({ { transient: { length: faker.number.int({ min: 1, max: 10 }) } } ), cves: cveFactory.buildList(10), + affected_packages: + params.affected_packages || affectedPackageFactory.buildList(10), errata_details: { id: faker.number.int({ min: 1, max: 65536 }), issue_date: faker.date.recent({ days: 30 }), From e4c58bf64151017543c6a3d949165b7f957a07b7 Mon Sep 17 00:00:00 2001 From: Jan Fooken Date: Tue, 9 Jul 2024 16:32:14 +0200 Subject: [PATCH 02/11] Enhance advisory errata endpoint with affected packages This patch adds the affected packages to the advisory errata endpoint and adds an openAPI spec for the changes. --- .../controllers/fallback_controller.ex | 7 ++++ .../controllers/v1/suse_manager_controller.ex | 10 ++++- .../v1/schema/available_software_updates.ex | 40 ++++++++++++++++++- lib/trento_web/views/v1/suse_manager_view.ex | 6 ++- 4 files changed, 58 insertions(+), 5 deletions(-) diff --git a/lib/trento_web/controllers/fallback_controller.ex b/lib/trento_web/controllers/fallback_controller.ex index 25cfd95abc..f75bd83f07 100644 --- a/lib/trento_web/controllers/fallback_controller.ex +++ b/lib/trento_web/controllers/fallback_controller.ex @@ -138,6 +138,13 @@ defmodule TrentoWeb.FallbackController do |> render(:"422", reason: "Unable to retrieve Bugzilla fixes for this advisory.") end + def call(conn, {:error, :error_getting_affected_packages}) do + conn + |> put_status(:unprocessable_entity) + |> put_view(ErrorView) + |> render(:"422", reason: "Unable to retrieve affected packages for this advisory.") + end + def call(conn, {:error, :connection_test_failed}) do conn |> put_status(:unprocessable_entity) diff --git a/lib/trento_web/controllers/v1/suse_manager_controller.ex b/lib/trento_web/controllers/v1/suse_manager_controller.ex index 69cc4f3da2..e9a268ebaf 100644 --- a/lib/trento_web/controllers/v1/suse_manager_controller.ex +++ b/lib/trento_web/controllers/v1/suse_manager_controller.ex @@ -96,8 +96,14 @@ defmodule TrentoWeb.V1.SUSEManagerController do def errata_details(conn, %{advisory_name: advisory_name}) do with {:ok, errata_details} <- Discovery.get_errata_details(advisory_name), {:ok, cves} <- Discovery.get_cves(advisory_name), - {:ok, fixes} <- Discovery.get_bugzilla_fixes(advisory_name) do - render(conn, %{errata_details: errata_details, cves: cves, fixes: fixes}) + {:ok, fixes} <- Discovery.get_bugzilla_fixes(advisory_name), + {:ok, affected_packages} <- Discovery.get_affected_packages(advisory_name) do + render(conn, %{ + errata_details: errata_details, + cves: cves, + fixes: fixes, + affected_packages: affected_packages + }) end end end diff --git a/lib/trento_web/openapi/v1/schema/available_software_updates.ex b/lib/trento_web/openapi/v1/schema/available_software_updates.ex index e5e2f3ce76..dd9983df0b 100644 --- a/lib/trento_web/openapi/v1/schema/available_software_updates.ex +++ b/lib/trento_web/openapi/v1/schema/available_software_updates.ex @@ -197,6 +197,43 @@ defmodule TrentoWeb.OpenApi.V1.Schema.AvailableSoftwareUpdates do }) end + defmodule AffectedPackages do + @moduledoc false + OpenApiSpex.schema(%{ + title: "AffectedPackages", + description: "Response returned from the get affected packages endpoint", + type: :array, + additionalProperties: false, + items: %Schema{ + title: "AffectedPackage", + description: "Metadata for a package effected from an advisory", + type: :object, + properties: %{ + name: %Schema{ + type: :string, + description: "Package name" + }, + arch_label: %Schema{ + type: :string, + description: "Package architecture" + }, + version: %Schema{ + type: :string, + description: "Package version" + }, + release: %Schema{ + type: :string, + description: "Package release number" + }, + epoch: %Schema{ + type: :string, + description: "Package epoch number" + } + } + } + }) + end + defmodule ErrataDetailsResponse do @moduledoc false OpenApiSpex.schema(%{ @@ -207,7 +244,8 @@ defmodule TrentoWeb.OpenApi.V1.Schema.AvailableSoftwareUpdates do properties: %{ errata_details: ErrataDetails, cves: CVEs, - fixes: AdvisoryFixes + fixes: AdvisoryFixes, + affected_packages: AffectedPackages } }) end diff --git a/lib/trento_web/views/v1/suse_manager_view.ex b/lib/trento_web/views/v1/suse_manager_view.ex index 8c26911e9b..2c1440826d 100644 --- a/lib/trento_web/views/v1/suse_manager_view.ex +++ b/lib/trento_web/views/v1/suse_manager_view.ex @@ -66,7 +66,8 @@ defmodule TrentoWeb.V1.SUSEManagerView do def render("errata_details.json", %{ errata_details: errata_details = %{errataFrom: errataFrom}, cves: cves, - fixes: fixes + fixes: fixes, + affected_packages: affected_packages }), do: %{ errata_details: @@ -74,6 +75,7 @@ defmodule TrentoWeb.V1.SUSEManagerView do |> Map.drop([:errataFrom]) |> Map.put(:errata_from, errataFrom), cves: cves, - fixes: fixes + fixes: fixes, + affected_packages: affected_packages } end From 628b551f36bdaeada395d499728c5ab537cebe1c Mon Sep 17 00:00:00 2001 From: Jan Fooken Date: Tue, 9 Jul 2024 16:34:02 +0200 Subject: [PATCH 03/11] Update advisory errata backend tests for affected packages This patch adds unit test cases and updates the existing cases to include the new data available from the affected packages discovery. --- .../v1/suse_manager_controller_test.exs | 51 ++++++++++++++++++- .../views/v1/suse_manager_view_test.exs | 8 ++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/test/trento_web/controllers/v1/suse_manager_controller_test.exs b/test/trento_web/controllers/v1/suse_manager_controller_test.exs index d5b24a4804..2681b99f61 100644 --- a/test/trento_web/controllers/v1/suse_manager_controller_test.exs +++ b/test/trento_web/controllers/v1/suse_manager_controller_test.exs @@ -184,6 +184,12 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do {:ok, fixes} end) + affected_packages = build_list(10, :affected_package) + + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_affected_packages, 1, fn _ -> + {:ok, affected_packages} + end) + json = conn |> get("/api/v1/software_updates/errata_details/#{advisory_name}") @@ -218,7 +224,8 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do reboot_suggested: ^reboot_suggested, restart_suggested: ^restart_suggested }, - cves: ^cves + cves: ^cves, + affected_packages: ^affected_packages } = result end @@ -240,6 +247,10 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do {:ok, build(:bugzilla_fix)} end) + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_affected_packages, 1, fn _ -> + {:ok, build_list(10, :affected_package)} + end) + advisory_name = Faker.Pokemon.name() conn @@ -266,6 +277,10 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do {:ok, build(:bugzilla_fix)} end) + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_affected_packages, 1, fn _ -> + {:ok, build_list(10, :affected_package)} + end) + advisory_name = Faker.Pokemon.name() conn @@ -292,6 +307,40 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do {:error, :error_getting_fixes} end) + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_affected_packages, 1, fn _ -> + {:ok, build_list(10, :affected_package)} + end) + + advisory_name = Faker.Pokemon.name() + + conn + |> get("/api/v1/software_updates/errata_details/#{advisory_name}") + |> json_response(:unprocessable_entity) + |> assert_schema("UnprocessableEntity", api_spec) + end + + test "should return 422 when advisory affected packages are not found", %{ + conn: conn, + api_spec: api_spec + } do + insert_software_updates_settings() + + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_errata_details, 1, fn _ -> + {:ok, build(:errata_details)} + end) + + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_cves, 1, fn _ -> + {:ok, build_list(10, :cve)} + end) + + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_bugzilla_fixes, 1, fn _ -> + {:ok, build(:bugzilla_fix)} + end) + + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_affected_packages, 1, fn _ -> + {:error, :error_getting_affected_packages} + end) + advisory_name = Faker.Pokemon.name() conn diff --git a/test/trento_web/views/v1/suse_manager_view_test.exs b/test/trento_web/views/v1/suse_manager_view_test.exs index 03c70fe697..101c77d419 100644 --- a/test/trento_web/views/v1/suse_manager_view_test.exs +++ b/test/trento_web/views/v1/suse_manager_view_test.exs @@ -33,16 +33,20 @@ defmodule TrentoWeb.V1.SUSEManagerViewTest do fixes = build(:bugzilla_fix) + affected_packages = build_list(10, :affected_package) + assert %{ errata_details: ^expected_errata_details, cves: ^cves, - fixes: ^fixes + fixes: ^fixes, + affected_packages: ^affected_packages } = render(SUSEManagerView, "errata_details.json", %{ errata_details: Map.put(errata_details_sans_errata_from, :errataFrom, errata_from), cves: cves, - fixes: fixes + fixes: fixes, + affected_packages: affected_packages }) end end From 8b32aa8cc745c44bbdf0a9a94f2cce4878635bff Mon Sep 17 00:00:00 2001 From: Jan Fooken Date: Tue, 9 Jul 2024 16:35:49 +0200 Subject: [PATCH 04/11] Update AdvisoryDetails component to display affected package data This patch updates the AdvisoryDetails component to display the affected packages. --- assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx index 19924828ee..727fcf1894 100644 --- a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx +++ b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx @@ -5,6 +5,9 @@ import PageHeader from '@common/PageHeader'; import ListView from '@common/ListView'; import AdvisoryIcon from '@common/AdvisoryIcon'; +export const formatPackage = ({ name, version, epoch, release, arch_label }) => + `${name}-${version}-${epoch}.${release}-${arch_label}`; + function EmptyData() { return

No data available

; } @@ -12,7 +15,6 @@ function EmptyData() { function AdvisoryDetails({ advisoryName, errata, - packages, affectsPackageMaintanaceStack, }) { const { @@ -25,7 +27,7 @@ function AdvisoryDetails({ reboot_suggested: rebootSuggested, } = errata.errata_details; - const { fixes, cves } = errata; + const { fixes, cves, affected_packages: affectedPackages } = errata; return (
@@ -118,10 +120,10 @@ function AdvisoryDetails({

Affected Packages

- {packages && packages.length ? ( + {affectedPackages && affectedPackages.length ? (
    - {packages.map((pkg) => ( -
  • {pkg}
  • + {affectedPackages.map((pkg) => ( +
  • {formatPackage(pkg)}
  • ))}
) : ( From fc2fc9a0754a0c9fd59b1cc20a9bdadab064860c Mon Sep 17 00:00:00 2001 From: Jan Fooken Date: Tue, 9 Jul 2024 16:37:32 +0200 Subject: [PATCH 05/11] Update AdvisoryDetails frontend test and story to include affected packages This patch updates and adds the frontend test and story to include the affected packages data. --- .../AdvisoryDetails.stories.jsx | 10 +++++++- .../AdvisoryDetails/AdvisoryDetails.test.jsx | 25 ++++++++----------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.stories.jsx b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.stories.jsx index 18ee771edf..9cc05b4883 100644 --- a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.stories.jsx +++ b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.stories.jsx @@ -28,8 +28,16 @@ However, the post didn't come by today, and I am starting to wonder, if my Geeko 4815162342: 'Geekos unexpectedly eating quiches', }, cves: ['CVE-2024-35938'], + affected_packages: [ + { + name: 'libprocps7', + version: '3.3.15', + release: '7.34.1', + epoch: '150000', + arch_label: 'x86_64', + }, + ], }, - packages: undefined, affectsPackageMaintanaceStack: false, }, }; diff --git a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.test.jsx b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.test.jsx index c336562c57..6d7a296fee 100644 --- a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.test.jsx +++ b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.test.jsx @@ -7,11 +7,15 @@ import { faker } from '@faker-js/faker'; import { renderWithRouter as render } from '@lib/test-utils'; import { advisoryErrataFactory, cveFactory } from '@lib/test-utils/factories'; -import AdvisoryDetails from './AdvisoryDetails'; +import AdvisoryDetails, { formatPackage } from './AdvisoryDetails'; describe('AdvisoryDetails', () => { it('displays a message, when the CVE, packages or fixes section is empty', () => { - const errata = advisoryErrataFactory.build({ cves: [], fixes: {} }); + const errata = advisoryErrataFactory.build({ + cves: [], + fixes: {}, + affected_packages: {}, + }); render( @@ -34,22 +38,15 @@ describe('AdvisoryDetails', () => { expect(screen.getByText(errata.errata_details.description)).toBeVisible(); }); - it('displays packages', () => { + it('displays affected packages', () => { const errata = advisoryErrataFactory.build(); const advisoryName = faker.lorem.word(); - const packages = faker.word.words(2).split(' '); - - render( - - ); + render(); - packages.forEach((expectedWord) => { - expect(screen.getByText(expectedWord)).toBeVisible(); + errata.affected_packages.forEach((pkg) => { + const el = screen.getByText(formatPackage(pkg)); + expect(el).toBeVisible(); }); }); From 0b5dc961ab4d0dcd0ed6afddb1fcadf4250a341c Mon Sep 17 00:00:00 2001 From: Jan Fooken Date: Tue, 9 Jul 2024 19:16:08 +0200 Subject: [PATCH 06/11] Fix formatting --- assets/js/lib/test-utils/factories/advisoryErrata.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/lib/test-utils/factories/advisoryErrata.js b/assets/js/lib/test-utils/factories/advisoryErrata.js index d64793f98c..557dd23e66 100644 --- a/assets/js/lib/test-utils/factories/advisoryErrata.js +++ b/assets/js/lib/test-utils/factories/advisoryErrata.js @@ -4,7 +4,7 @@ import { advisoryType } from './relevantPatches'; const affectedPackageFactory = Factory.define(() => ({ name: faker.animal.cat().toLowerCase(), - arch_label: faker.helpers.arrayElement(["x86_64", "i586", "aarch64"]), + arch_label: faker.helpers.arrayElement(['x86_64', 'i586', 'aarch64']), version: faker.system.semver(), release: `${faker.number.int({ min: 0, max: 100 })}`, epoch: `${faker.number.int({ min: 0, max: 50 })}`, From d9ee1a036bc22c06a22e8af897433117a25b9947 Mon Sep 17 00:00:00 2001 From: Jan Fooken Date: Wed, 10 Jul 2024 10:31:34 +0200 Subject: [PATCH 07/11] Fix spelling --- assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx | 6 +++--- assets/js/pages/AdvisoryDetails/AdvisoryDetails.stories.jsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx index 727fcf1894..13ef22d3bd 100644 --- a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx +++ b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx @@ -15,7 +15,7 @@ function EmptyData() { function AdvisoryDetails({ advisoryName, errata, - affectsPackageMaintanaceStack, + affectsPackageMaintenanceStack, }) { const { issue_date: issueDate, @@ -62,8 +62,8 @@ function AdvisoryDetails({ content: rebootSuggested ? 'Yes' : 'No', }, { - title: 'Affects Package Maintanace Stack', - content: affectsPackageMaintanaceStack ? 'Yes' : 'No', + title: 'Affects Package Maintenance Stack', + content: affectsPackageMaintenanceStack ? 'Yes' : 'No', }, ]} /> diff --git a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.stories.jsx b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.stories.jsx index 9cc05b4883..eafd2c6202 100644 --- a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.stories.jsx +++ b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.stories.jsx @@ -38,6 +38,6 @@ However, the post didn't come by today, and I am starting to wonder, if my Geeko }, ], }, - affectsPackageMaintanaceStack: false, + affectsPackageMaintenanceStack: false, }, }; From a182b4eee04b169127fa1b1fd3e8de15b37aaf9f Mon Sep 17 00:00:00 2001 From: Jan Fooken Date: Wed, 10 Jul 2024 10:39:31 +0200 Subject: [PATCH 08/11] Changes suggested by Alessio --- assets/js/lib/test-utils/factories/advisoryErrata.js | 7 +++---- assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx | 2 +- assets/js/pages/AdvisoryDetails/AdvisoryDetails.test.jsx | 8 ++++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/assets/js/lib/test-utils/factories/advisoryErrata.js b/assets/js/lib/test-utils/factories/advisoryErrata.js index 557dd23e66..7337c70ffd 100644 --- a/assets/js/lib/test-utils/factories/advisoryErrata.js +++ b/assets/js/lib/test-utils/factories/advisoryErrata.js @@ -2,8 +2,8 @@ import { faker } from '@faker-js/faker'; import { Factory } from 'fishery'; import { advisoryType } from './relevantPatches'; -const affectedPackageFactory = Factory.define(() => ({ - name: faker.animal.cat().toLowerCase(), +const affectedPackageFactory = Factory.define(({ sequence }) => ({ + name: `${faker.animal.cat().toLowerCase()}${sequence}`, arch_label: faker.helpers.arrayElement(['x86_64', 'i586', 'aarch64']), version: faker.system.semver(), release: `${faker.number.int({ min: 0, max: 100 })}`, @@ -39,8 +39,7 @@ export const advisoryErrataFactory = Factory.define(({ params }) => ({ { transient: { length: faker.number.int({ min: 1, max: 10 }) } } ), cves: cveFactory.buildList(10), - affected_packages: - params.affected_packages || affectedPackageFactory.buildList(10), + affected_packages: affectedPackageFactory.buildList(10), errata_details: { id: faker.number.int({ min: 1, max: 65536 }), issue_date: faker.date.recent({ days: 30 }), diff --git a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx index 13ef22d3bd..b6329f5ab5 100644 --- a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx +++ b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx @@ -5,7 +5,7 @@ import PageHeader from '@common/PageHeader'; import ListView from '@common/ListView'; import AdvisoryIcon from '@common/AdvisoryIcon'; -export const formatPackage = ({ name, version, epoch, release, arch_label }) => +const formatPackage = ({ name, version, epoch, release, arch_label }) => `${name}-${version}-${epoch}.${release}-${arch_label}`; function EmptyData() { diff --git a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.test.jsx b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.test.jsx index 6d7a296fee..a6eafe37a7 100644 --- a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.test.jsx +++ b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.test.jsx @@ -7,14 +7,14 @@ import { faker } from '@faker-js/faker'; import { renderWithRouter as render } from '@lib/test-utils'; import { advisoryErrataFactory, cveFactory } from '@lib/test-utils/factories'; -import AdvisoryDetails, { formatPackage } from './AdvisoryDetails'; +import AdvisoryDetails from './AdvisoryDetails'; describe('AdvisoryDetails', () => { it('displays a message, when the CVE, packages or fixes section is empty', () => { const errata = advisoryErrataFactory.build({ cves: [], fixes: {}, - affected_packages: {}, + affected_packages: [], }); render( @@ -44,8 +44,8 @@ describe('AdvisoryDetails', () => { render(); - errata.affected_packages.forEach((pkg) => { - const el = screen.getByText(formatPackage(pkg)); + errata.affected_packages.forEach(({ name }) => { + const el = screen.getByText(name, { exact: false }); expect(el).toBeVisible(); }); }); From 061d3a58c43c61b06effdd041d73086ef3a56c6c Mon Sep 17 00:00:00 2001 From: Jan Fooken Date: Thu, 11 Jul 2024 10:26:57 +0200 Subject: [PATCH 09/11] Expose affected systems in the errata details This patch adds the affected systems from the discovery result to the errata details response. Therefore, this patch contains an update to the openAPI specification. Furthermore, it adds new and updates existing tests to include the changes. --- .../controllers/fallback_controller.ex | 7 +++ .../controllers/v1/suse_manager_controller.ex | 6 +- .../v1/schema/available_software_updates.ex | 24 +++++++- lib/trento_web/views/v1/suse_manager_view.ex | 6 +- .../v1/suse_manager_controller_test.exs | 59 ++++++++++++++++++- .../views/v1/suse_manager_view_test.exs | 8 ++- 6 files changed, 102 insertions(+), 8 deletions(-) diff --git a/lib/trento_web/controllers/fallback_controller.ex b/lib/trento_web/controllers/fallback_controller.ex index f75bd83f07..5a3eed9717 100644 --- a/lib/trento_web/controllers/fallback_controller.ex +++ b/lib/trento_web/controllers/fallback_controller.ex @@ -145,6 +145,13 @@ defmodule TrentoWeb.FallbackController do |> render(:"422", reason: "Unable to retrieve affected packages for this advisory.") end + def call(conn, {:error, :error_getting_affected_systems}) do + conn + |> put_status(:unprocessable_entity) + |> put_view(ErrorView) + |> render(:"422", reason: "Unable to retrieve affected systems for this advisory.") + end + def call(conn, {:error, :connection_test_failed}) do conn |> put_status(:unprocessable_entity) diff --git a/lib/trento_web/controllers/v1/suse_manager_controller.ex b/lib/trento_web/controllers/v1/suse_manager_controller.ex index e9a268ebaf..68db614285 100644 --- a/lib/trento_web/controllers/v1/suse_manager_controller.ex +++ b/lib/trento_web/controllers/v1/suse_manager_controller.ex @@ -97,12 +97,14 @@ defmodule TrentoWeb.V1.SUSEManagerController do with {:ok, errata_details} <- Discovery.get_errata_details(advisory_name), {:ok, cves} <- Discovery.get_cves(advisory_name), {:ok, fixes} <- Discovery.get_bugzilla_fixes(advisory_name), - {:ok, affected_packages} <- Discovery.get_affected_packages(advisory_name) do + {:ok, affected_packages} <- Discovery.get_affected_packages(advisory_name), + {:ok, affected_systems} <- Discovery.get_affected_systems(advisory_name) do render(conn, %{ errata_details: errata_details, cves: cves, fixes: fixes, - affected_packages: affected_packages + affected_packages: affected_packages, + affected_systems: affected_systems }) end end diff --git a/lib/trento_web/openapi/v1/schema/available_software_updates.ex b/lib/trento_web/openapi/v1/schema/available_software_updates.ex index dd9983df0b..b7cdaef2b7 100644 --- a/lib/trento_web/openapi/v1/schema/available_software_updates.ex +++ b/lib/trento_web/openapi/v1/schema/available_software_updates.ex @@ -234,6 +234,27 @@ defmodule TrentoWeb.OpenApi.V1.Schema.AvailableSoftwareUpdates do }) end + defmodule AffectedSystems do + @moduledoc false + OpenApiSpex.schema(%{ + title: "AffectedSystems", + description: "Response returned from the get affected systems endpoint", + type: :array, + additionalProperties: false, + items: %Schema{ + title: "AffectedSystem", + description: "Metadata for a system effected by an advisory", + type: :object, + properties: %{ + name: %Schema{ + type: :string, + description: "System name" + } + } + } + }) + end + defmodule ErrataDetailsResponse do @moduledoc false OpenApiSpex.schema(%{ @@ -245,7 +266,8 @@ defmodule TrentoWeb.OpenApi.V1.Schema.AvailableSoftwareUpdates do errata_details: ErrataDetails, cves: CVEs, fixes: AdvisoryFixes, - affected_packages: AffectedPackages + affected_packages: AffectedPackages, + affected_systems: AffectedSystems } }) end diff --git a/lib/trento_web/views/v1/suse_manager_view.ex b/lib/trento_web/views/v1/suse_manager_view.ex index 2c1440826d..f8213e4fca 100644 --- a/lib/trento_web/views/v1/suse_manager_view.ex +++ b/lib/trento_web/views/v1/suse_manager_view.ex @@ -67,7 +67,8 @@ defmodule TrentoWeb.V1.SUSEManagerView do errata_details: errata_details = %{errataFrom: errataFrom}, cves: cves, fixes: fixes, - affected_packages: affected_packages + affected_packages: affected_packages, + affected_systems: affected_systems }), do: %{ errata_details: @@ -76,6 +77,7 @@ defmodule TrentoWeb.V1.SUSEManagerView do |> Map.put(:errata_from, errataFrom), cves: cves, fixes: fixes, - affected_packages: affected_packages + affected_packages: affected_packages, + affected_systems: affected_systems } end diff --git a/test/trento_web/controllers/v1/suse_manager_controller_test.exs b/test/trento_web/controllers/v1/suse_manager_controller_test.exs index 2681b99f61..e26eae7ae9 100644 --- a/test/trento_web/controllers/v1/suse_manager_controller_test.exs +++ b/test/trento_web/controllers/v1/suse_manager_controller_test.exs @@ -190,6 +190,12 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do {:ok, affected_packages} end) + affected_systems = build_list(10, :affected_system) + + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_affected_systems, 1, fn _ -> + {:ok, affected_systems} + end) + json = conn |> get("/api/v1/software_updates/errata_details/#{advisory_name}") @@ -225,7 +231,8 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do restart_suggested: ^restart_suggested }, cves: ^cves, - affected_packages: ^affected_packages + affected_packages: ^affected_packages, + affected_systems: ^affected_systems } = result end @@ -251,6 +258,10 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do {:ok, build_list(10, :affected_package)} end) + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_affected_systems, 1, fn _ -> + {:ok, build_list(10, :affected_system)} + end) + advisory_name = Faker.Pokemon.name() conn @@ -281,6 +292,10 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do {:ok, build_list(10, :affected_package)} end) + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_affected_systems, 1, fn _ -> + {:ok, build_list(10, :affected_system)} + end) + advisory_name = Faker.Pokemon.name() conn @@ -311,6 +326,10 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do {:ok, build_list(10, :affected_package)} end) + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_affected_systems, 1, fn _ -> + {:ok, build_list(10, :affected_system)} + end) + advisory_name = Faker.Pokemon.name() conn @@ -341,6 +360,44 @@ defmodule TrentoWeb.V1.SUSEManagerControllerTest do {:error, :error_getting_affected_packages} end) + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_affected_systems, 1, fn _ -> + {:ok, build_list(10, :affected_system)} + end) + + advisory_name = Faker.Pokemon.name() + + conn + |> get("/api/v1/software_updates/errata_details/#{advisory_name}") + |> json_response(:unprocessable_entity) + |> assert_schema("UnprocessableEntity", api_spec) + end + + test "should return 422 when advisory affected systems are not found", %{ + conn: conn, + api_spec: api_spec + } do + insert_software_updates_settings() + + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_errata_details, 1, fn _ -> + {:ok, build(:errata_details)} + end) + + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_cves, 1, fn _ -> + {:ok, build_list(10, :cve)} + end) + + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_bugzilla_fixes, 1, fn _ -> + {:ok, build(:bugzilla_fix)} + end) + + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_affected_packages, 1, fn _ -> + {:ok, build_list(10, :affected_package)} + end) + + expect(Trento.SoftwareUpdates.Discovery.Mock, :get_affected_systems, 1, fn _ -> + {:error, :error_getting_affected_systems} + end) + advisory_name = Faker.Pokemon.name() conn diff --git a/test/trento_web/views/v1/suse_manager_view_test.exs b/test/trento_web/views/v1/suse_manager_view_test.exs index 101c77d419..a459485bad 100644 --- a/test/trento_web/views/v1/suse_manager_view_test.exs +++ b/test/trento_web/views/v1/suse_manager_view_test.exs @@ -35,18 +35,22 @@ defmodule TrentoWeb.V1.SUSEManagerViewTest do affected_packages = build_list(10, :affected_package) + affected_systems = build_list(10, :affected_system) + assert %{ errata_details: ^expected_errata_details, cves: ^cves, fixes: ^fixes, - affected_packages: ^affected_packages + affected_packages: ^affected_packages, + affected_systems: ^affected_systems } = render(SUSEManagerView, "errata_details.json", %{ errata_details: Map.put(errata_details_sans_errata_from, :errataFrom, errata_from), cves: cves, fixes: fixes, - affected_packages: affected_packages + affected_packages: affected_packages, + affected_systems: affected_systems }) end end From 87345e455a7e9d3d6f1445b8c6b937420444479d Mon Sep 17 00:00:00 2001 From: Jan Fooken Date: Thu, 11 Jul 2024 10:43:11 +0200 Subject: [PATCH 10/11] Reword openAPI specification for affected packages This patch rewords the openAPI specification for the affected packages. I changed the words, to clarify the difference between a package version and a release. --- .../openapi/v1/schema/available_software_updates.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/trento_web/openapi/v1/schema/available_software_updates.ex b/lib/trento_web/openapi/v1/schema/available_software_updates.ex index b7cdaef2b7..0fb84c6b87 100644 --- a/lib/trento_web/openapi/v1/schema/available_software_updates.ex +++ b/lib/trento_web/openapi/v1/schema/available_software_updates.ex @@ -206,7 +206,7 @@ defmodule TrentoWeb.OpenApi.V1.Schema.AvailableSoftwareUpdates do additionalProperties: false, items: %Schema{ title: "AffectedPackage", - description: "Metadata for a package effected from an advisory", + description: "Metadata for a package effected by an advisory", type: :object, properties: %{ name: %Schema{ @@ -219,11 +219,11 @@ defmodule TrentoWeb.OpenApi.V1.Schema.AvailableSoftwareUpdates do }, version: %Schema{ type: :string, - description: "Package version" + description: "Package upstream version" }, release: %Schema{ type: :string, - description: "Package release number" + description: "Package RPM release number" }, epoch: %Schema{ type: :string, From ceebf41c462e084ff0b7d552c4c3cce10cb8c585 Mon Sep 17 00:00:00 2001 From: Jan Fooken Date: Thu, 11 Jul 2024 10:45:27 +0200 Subject: [PATCH 11/11] Display affected systems to the user This patch displays the systems affected by a advisory errata to the user. It exposes the new backend API in the advisory errata page and updates the relevant tests. This change required an update to the factories. Furthermore, I added a section for showcasing the "empty data" state in the advisory details page. --- .../test-utils/factories/advisoryErrata.js | 5 ++++ .../pages/AdvisoryDetails/AdvisoryDetails.jsx | 21 +++++++++++++- .../AdvisoryDetails.stories.jsx | 29 +++++++++++++++++++ .../AdvisoryDetails/AdvisoryDetails.test.jsx | 15 +++++++++- 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/assets/js/lib/test-utils/factories/advisoryErrata.js b/assets/js/lib/test-utils/factories/advisoryErrata.js index 7337c70ffd..8a01df2190 100644 --- a/assets/js/lib/test-utils/factories/advisoryErrata.js +++ b/assets/js/lib/test-utils/factories/advisoryErrata.js @@ -10,6 +10,10 @@ const affectedPackageFactory = Factory.define(({ sequence }) => ({ epoch: `${faker.number.int({ min: 0, max: 50 })}`, })); +const affectedSystemFactory = Factory.define(({ sequence }) => ({ + name: `${faker.string.uuid()}-${sequence}`, +})); + const fixMapFactory = Factory.define(({ transientParams }) => { const { length = 1 } = transientParams; @@ -40,6 +44,7 @@ export const advisoryErrataFactory = Factory.define(({ params }) => ({ ), cves: cveFactory.buildList(10), affected_packages: affectedPackageFactory.buildList(10), + affected_systems: affectedSystemFactory.buildList(10), errata_details: { id: faker.number.int({ min: 1, max: 65536 }), issue_date: faker.date.recent({ days: 30 }), diff --git a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx index b6329f5ab5..1640f1a418 100644 --- a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx +++ b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.jsx @@ -27,7 +27,12 @@ function AdvisoryDetails({ reboot_suggested: rebootSuggested, } = errata.errata_details; - const { fixes, cves, affected_packages: affectedPackages } = errata; + const { + fixes, + cves, + affected_packages: affectedPackages, + affected_systems: affectedSystems, + } = errata; return (
@@ -131,6 +136,20 @@ function AdvisoryDetails({ )}
+
+

Affected Systems

+
+ {affectedSystems && affectedSystems.length ? ( +
    + {affectedSystems.map(({ name }) => ( +
  • {name}
  • + ))} +
+ ) : ( + + )} +
+
); } diff --git a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.stories.jsx b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.stories.jsx index eafd2c6202..eca6b4176b 100644 --- a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.stories.jsx +++ b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.stories.jsx @@ -37,6 +37,35 @@ However, the post didn't come by today, and I am starting to wonder, if my Geeko arch_label: 'x86_64', }, ], + affected_systems: [ + { + name: 'vmdrbddev01', + }, + ], + }, + affectsPackageMaintenanceStack: false, + }, +}; + +export const Empty = { + args: { + advisoryName: 'SUSE-15-SP4-2023-3369', + errata: { + errata_details: { + issue_date: Date.now(), + update_date: Date.now(), + synopsis: 'I think my Geekos ate my quiche 🦎🦎', + advisory_status: 'stable', + type: 'security_advisory', + description: `My Geekos really love the cakes I order from the crab bakery. +Yesterday, I left before the post arrived. Normally, the post just delivers my packages the next day. +However, the post didn't come by today, and I am starting to wonder, if my Geekos ate my quiche. AITA? 😟`, + reboot_suggested: true, + }, + fixes: {}, + cves: [], + affected_packages: [], + affected_systems: [], }, affectsPackageMaintenanceStack: false, }, diff --git a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.test.jsx b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.test.jsx index a6eafe37a7..862a7bbbce 100644 --- a/assets/js/pages/AdvisoryDetails/AdvisoryDetails.test.jsx +++ b/assets/js/pages/AdvisoryDetails/AdvisoryDetails.test.jsx @@ -15,13 +15,14 @@ describe('AdvisoryDetails', () => { cves: [], fixes: {}, affected_packages: [], + affected_systems: [], }); render( ); - expect(screen.getAllByText('No data available').length).toBe(3); + expect(screen.getAllByText('No data available').length).toBe(4); }); it('displays relevant errata data', () => { @@ -50,6 +51,18 @@ describe('AdvisoryDetails', () => { }); }); + it('displays affected systems', () => { + const errata = advisoryErrataFactory.build(); + const advisoryName = faker.lorem.word(); + + render(); + + errata.affected_systems.forEach(({ name }) => { + const el = screen.getByText(name); + expect(el).toBeVisible(); + }); + }); + it('displays fixes with a valid link', () => { const errata = advisoryErrataFactory.build(); const advisoryName = faker.lorem.word();