From ee17fa24967ad789e4a805eb910f55ed86b5b85a Mon Sep 17 00:00:00 2001 From: TenSt Date: Fri, 14 Aug 2026 12:36:13 +0200 Subject: [PATCH 01/47] LWLP-35: create db schema for clearinghouse ui --- .github/workflows/content-sources-actions.yml | 13 + db/migrations.latest | 2 +- ..._create_lightwell_vulnerabilities.down.sql | 6 + ...00_create_lightwell_vulnerabilities.up.sql | 53 ++ db/seeds/lightwell_vulnerabilities.sql | 110 ++++ docs/lightwell_vulnerabilities_dev.md | 61 +++ mk/includes.mk | 1 + mk/sqlc.mk | 9 + pkg/lightwell/db/queries/vulnerabilities.sql | 170 ++++++ pkg/lightwell/db/schema.sql | 35 ++ pkg/lightwell/db/sqlc.yaml | 64 +++ pkg/lightwell/db/store/db.go | 32 ++ pkg/lightwell/db/store/models.go | 44 ++ pkg/lightwell/db/store/querier.go | 18 + pkg/lightwell/db/store/store_test.go | 497 ++++++++++++++++++ pkg/lightwell/db/store/vulnerabilities.sql.go | 352 +++++++++++++ 16 files changed, 1466 insertions(+), 1 deletion(-) create mode 100644 db/migrations/20260814120000_create_lightwell_vulnerabilities.down.sql create mode 100644 db/migrations/20260814120000_create_lightwell_vulnerabilities.up.sql create mode 100644 db/seeds/lightwell_vulnerabilities.sql create mode 100644 docs/lightwell_vulnerabilities_dev.md create mode 100644 mk/sqlc.mk create mode 100644 pkg/lightwell/db/queries/vulnerabilities.sql create mode 100644 pkg/lightwell/db/schema.sql create mode 100644 pkg/lightwell/db/sqlc.yaml create mode 100644 pkg/lightwell/db/store/db.go create mode 100644 pkg/lightwell/db/store/models.go create mode 100644 pkg/lightwell/db/store/querier.go create mode 100644 pkg/lightwell/db/store/store_test.go create mode 100644 pkg/lightwell/db/store/vulnerabilities.sql.go diff --git a/.github/workflows/content-sources-actions.yml b/.github/workflows/content-sources-actions.yml index 782c04d03..fa3893a53 100644 --- a/.github/workflows/content-sources-actions.yml +++ b/.github/workflows/content-sources-actions.yml @@ -51,6 +51,19 @@ jobs: - run: make deployment-generate - run: git diff --exit-code deployments/deployment.yaml + sqlcdiff: + name: sqlc diff + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version-file: go.mod + - run: | + make sqlc-generate-lightwell + - run: | + git diff --exit-code pkg/lightwell/db/store/ + golangci: name: Lint runs-on: ubuntu-latest diff --git a/db/migrations.latest b/db/migrations.latest index 0c7101893..d81f8babb 100644 --- a/db/migrations.latest +++ b/db/migrations.latest @@ -1 +1 @@ -20260811120000 +20260814120000 diff --git a/db/migrations/20260814120000_create_lightwell_vulnerabilities.down.sql b/db/migrations/20260814120000_create_lightwell_vulnerabilities.down.sql new file mode 100644 index 000000000..8ec6c7427 --- /dev/null +++ b/db/migrations/20260814120000_create_lightwell_vulnerabilities.down.sql @@ -0,0 +1,6 @@ +BEGIN; + +DROP TABLE IF EXISTS lightwell_vulnerability_customers; +DROP TABLE IF EXISTS lightwell_vulnerabilities; + +COMMIT; diff --git a/db/migrations/20260814120000_create_lightwell_vulnerabilities.up.sql b/db/migrations/20260814120000_create_lightwell_vulnerabilities.up.sql new file mode 100644 index 000000000..8b9b42ad0 --- /dev/null +++ b/db/migrations/20260814120000_create_lightwell_vulnerabilities.up.sql @@ -0,0 +1,53 @@ +BEGIN; + +CREATE TABLE lightwell_vulnerabilities ( + uuid UUID PRIMARY KEY, + vulnerability_id TEXT NOT NULL UNIQUE, + purl TEXT, + component_name TEXT NOT NULL, + component_version TEXT NOT NULL, + title TEXT, + cwe TEXT, + description TEXT, + severity TEXT NOT NULL, + cvss DOUBLE PRECISION, + cvss_vector TEXT, + exploit_tested BOOLEAN NOT NULL DEFAULT false, + reproducer_included BOOLEAN NOT NULL DEFAULT false, + customer_priority TEXT, + stage TEXT NOT NULL, + language TEXT, + complexity TEXT NOT NULL, + submitted_date DATE NOT NULL, + last_updated TIMESTAMPTZ NOT NULL, + embargo BOOLEAN NOT NULL DEFAULT false, + duplicate BOOLEAN NOT NULL DEFAULT false, + ltwwlsupt_ticket_id TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE lightwell_vulnerability_customers ( + customer_id TEXT NOT NULL, + vulnerability_uuid UUID NOT NULL REFERENCES lightwell_vulnerabilities (uuid) ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (customer_id, vulnerability_uuid) +); + +CREATE INDEX idx_lvc_customer_id ON lightwell_vulnerability_customers (customer_id); + +CREATE INDEX idx_lv_severity ON lightwell_vulnerabilities (severity); +CREATE INDEX idx_lv_stage ON lightwell_vulnerabilities (stage); +CREATE INDEX idx_lv_complexity ON lightwell_vulnerabilities (complexity); +CREATE INDEX idx_lv_ltwwlsupt_ticket_id ON lightwell_vulnerabilities (ltwwlsupt_ticket_id); + +CREATE INDEX idx_lv_embargo_true ON lightwell_vulnerabilities (uuid) WHERE embargo = true; +CREATE INDEX idx_lv_duplicate_true ON lightwell_vulnerabilities (uuid) WHERE duplicate = true; + +CREATE INDEX idx_lv_blocked ON lightwell_vulnerabilities (submitted_date, stage); + +CREATE INDEX idx_lv_vulnerability_id_trgm ON lightwell_vulnerabilities USING gin (vulnerability_id gin_trgm_ops); +CREATE INDEX idx_lv_component_name_trgm ON lightwell_vulnerabilities USING gin (component_name gin_trgm_ops); +CREATE INDEX idx_lv_title_trgm ON lightwell_vulnerabilities USING gin (title gin_trgm_ops); + +COMMIT; diff --git a/db/seeds/lightwell_vulnerabilities.sql b/db/seeds/lightwell_vulnerabilities.sql new file mode 100644 index 000000000..e61fd0ac3 --- /dev/null +++ b/db/seeds/lightwell_vulnerabilities.sql @@ -0,0 +1,110 @@ +-- Dev seed for lightwell vulnerabilities (from lightwell-vulnerabilities-2026-08-14.json) +BEGIN; + +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000001', 'LWL-2026-4401', 'pkg:maven/org.apache.logging.log4j/log4j-core@2.17.1', 'log4j-core', '2.17.1', 'JNDI injection via crafted log message', 'CWE-917', 'Remote code execution through JNDI lookup in log messages', 'Critical', 9.8, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Submitted', 'java', 'Extensive', '2026-08-12', '2026-08-13 08:17:00+00', true, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000002', 'LWL-2026-4402', 'pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.14.0', 'jackson-databind', '2.14.0', 'Deserialization gadget chain in TemplatesImpl', 'CWE-502', 'Unsafe deserialization allows arbitrary code execution', 'Important', 8.1, 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Submitted', 'java', 'Complex', '2026-08-13', '2026-08-14 07:00:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000003', 'LWL-2026-4403', 'pkg:maven/org.springframework/spring-web@5.3.20', 'spring-web', '5.3.20', 'HTTP request smuggling via malformed headers', 'CWE-444', 'Request smuggling through inconsistent header parsing', 'Important', 7.5, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N', true, false, 'Priority 2', 'Submitted', 'java', 'Complex', '2026-08-11', '2026-08-12 09:34:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000004', 'LWL-2026-4404', 'pkg:pypi/requests@2.28.0', 'requests', '2.28.0', 'SSRF via redirect handling', 'CWE-918', 'Server-side request forgery through unvalidated redirects', 'Moderate', 5.3, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N', false, false, 'Priority 3', 'Submitted', 'python', 'Standard', '2026-08-13', '2026-08-14 07:00:00+00', false, false, 'batch-3'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000005', 'LWL-2026-4405', 'pkg:npm/express@4.18.2', 'express', '4.18.2', 'Path traversal in static file serving', 'CWE-22', 'Directory traversal allows reading arbitrary files', 'Important', 7.5, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N', true, true, 'Priority 1', 'Submitted', 'javascript', 'Complex', '2026-08-10', '2026-08-11 10:51:00+00', true, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000006', 'LWL-2026-4406', 'pkg:maven/commons-io/commons-io@2.11.0', 'Apache Commons IO', '2.11.0', 'Infinite loop via crafted ZIP entry', 'CWE-835', 'Denial of service through malicious archive processing', 'Moderate', 5.5, 'CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H', true, true, 'Priority 2', 'Submitted', 'java', 'Standard', '2026-08-12', '2026-08-13 08:17:00+00', false, false, 'batch-3'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000007', 'LWL-2026-4407', 'pkg:nuget/Newtonsoft.Json@13.0.1', 'Newtonsoft.Json', '13.0.1', 'Stack overflow during deep JSON parsing', 'CWE-674', 'DoS via deeply nested JSON structures', 'Moderate', 5.3, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L', false, false, 'Priority 3', 'Submitted', 'csharp', 'Standard', '2026-08-13', '2026-08-14 07:00:00+00', false, false, 'batch-3'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000008', 'LWL-2026-4408', 'pkg:maven/org.apache.httpcomponents/httpclient@4.5.13', 'Apache HttpClient', '4.5.13', 'Certificate validation bypass', 'CWE-295', 'TLS certificate not properly validated under certain conditions', 'Important', 7.4, 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N', true, false, 'Priority 2', 'Submitted', 'java', 'Complex', '2026-08-09', '2026-08-10 11:08:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000009', 'LWL-2026-4301', 'pkg:maven/org.apache.struts/struts2-core@2.5.30', 'struts2-core', '2.5.30', 'OGNL injection in tag attributes', 'CWE-917', 'Remote code execution via OGNL expression injection', 'Critical', 9.8, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Classified', 'java', 'Extensive', '2026-08-06', '2026-08-09 12:25:00+00', true, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000000a', 'LWL-2026-4302', 'pkg:pypi/django@4.1.0', 'Django', '4.1.0', 'SQL injection in QuerySet.extra()', 'CWE-89', 'SQL injection through unsanitized extra() parameters', 'Important', 8.6, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N', true, true, 'Priority 1', 'Classified', 'python', 'Complex', '2026-08-07', '2026-08-10 11:08:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000000b', 'LWL-2026-4303', 'pkg:npm/lodash@4.17.21', 'lodash', '4.17.21', 'Prototype pollution in merge functions', 'CWE-1321', 'Object prototype pollution through recursive merge', 'Important', 7.3, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L', false, false, 'Priority 2', 'Classified', 'javascript', 'Standard', '2026-08-08', '2026-08-11 10:51:00+00', false, false, 'batch-3'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000000c', 'LWL-2026-4304', 'pkg:maven/org.cryptolib/cryptolib-core@1.70', 'CryptoLib Core', '1.70', 'Timing side-channel in ECDSA', 'CWE-208', 'Key recovery via timing analysis of ECDSA operations', 'Moderate', 5.9, 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N', true, true, 'Priority 2', 'Classified', 'java', 'Extensive', '2026-08-05', '2026-08-08 13:42:00+00', true, false, 'batch-3'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000000d', 'LWL-2026-4305', 'pkg:pypi/flask@2.2.0', 'Flask', '2.2.0', 'Open redirect in URL handling', 'CWE-601', 'Unvalidated redirect allows phishing attacks', 'Moderate', 4.7, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:L/A:N', false, false, 'Priority 4', 'Classified', 'python', 'Standard', '2026-08-08', '2026-08-10 11:08:00+00', false, false, 'batch-3'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000000e', 'LWL-2026-4306', 'pkg:npm/axios@1.3.0', 'axios', '1.3.0', 'SSRF via request interceptor bypass', 'CWE-918', 'Server-side request forgery through interceptor chain manipulation', 'Important', 7.2, 'CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N', true, false, 'Priority 2', 'Classified', 'javascript', 'Complex', '2026-08-04', '2026-08-07 14:59:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000000f', 'LWL-2026-4307', 'pkg:nuget/System.Text.Json@7.0.0', 'System.Text.Json', '7.0.0', 'Type confusion in polymorphic deserialization', 'CWE-843', 'Arbitrary type instantiation via crafted JSON payload', 'Important', 8.1, 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Classified', 'csharp', 'Extensive', '2026-08-03', '2026-08-06 07:16:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000010', 'LWL-2026-4201', 'pkg:maven/org.apache.tomcat/tomcat-catalina@9.0.65', 'Tomcat Catalina', '9.0.65', 'Request smuggling via chunked encoding', 'CWE-444', 'HTTP request smuggling through malformed chunked transfer encoding', 'Important', 8.1, 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Fix in Progress', 'java', 'Extensive', '2026-07-30', '2026-08-11 10:51:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000011', 'LWL-2026-4202', 'pkg:pypi/numpy@1.24.0', 'NumPy', '1.24.0', 'Buffer overflow in array reshape', 'CWE-120', 'Heap buffer overflow through crafted array dimensions', 'Important', 7.8, 'CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H', true, true, 'Priority 2', 'Fix in Progress', 'python', 'Complex', '2026-08-02', '2026-08-12 09:34:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000012', 'LWL-2026-4203', 'pkg:npm/jsonwebtoken@9.0.0', 'jsonwebtoken', '9.0.0', 'Algorithm confusion in token verification', 'CWE-327', 'JWT signature bypass through algorithm switching attack', 'Critical', 9.1, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N', true, true, 'Priority 1', 'Fix in Progress', 'javascript', 'Complex', '2026-07-31', '2026-08-13 08:17:00+00', true, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000013', 'LWL-2026-4204', 'pkg:maven/com.google.guava/guava@31.1-jre', 'Guava', '31.1-jre', 'Temporary file information disclosure', 'CWE-377', 'Predictable temp file names allow information leakage', 'Moderate', 5.5, 'CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N', false, false, 'Priority 3', 'Fix in Progress', 'java', 'Standard', '2026-08-04', '2026-08-10 11:08:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000014', 'LWL-2026-4205', 'pkg:pypi/pillow@9.4.0', 'Pillow', '9.4.0', 'Heap overflow in TIFF decoder', 'CWE-122', 'Remote code execution via crafted TIFF image', 'Important', 8.8, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Fix in Progress', 'python', 'Extensive', '2026-07-27', '2026-08-09 12:25:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000015', 'LWL-2026-4206', 'pkg:nuget/Microsoft.Data.SqlClient@5.1.0', 'Microsoft.Data.SqlClient', '5.1.0', 'Connection string injection', 'CWE-99', 'SQL Server connection hijacking through crafted connection strings', 'Important', 7.5, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N', true, false, 'Priority 2', 'Fix in Progress', 'csharp', 'Complex', '2026-08-01', '2026-08-08 13:42:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000016', 'LWL-2026-4207', 'pkg:maven/org.yaml/snakeyaml@1.33', 'SnakeYAML', '1.33', 'Arbitrary code execution via YAML deserialization', 'CWE-502', 'RCE through unsafe YAML constructor usage', 'Critical', 9.8, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Fix in Progress', 'java', 'Extensive', '2026-07-25', '2026-08-12 09:34:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000017', 'LWL-2026-4208', 'pkg:npm/minimatch@3.0.4', 'minimatch', '3.0.4', 'ReDoS in brace expansion', 'CWE-1333', 'Regular expression denial of service through crafted glob patterns', 'Moderate', 5.3, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L', false, false, 'Priority 4', 'Fix in Progress', 'javascript', 'Standard', '2026-08-03', '2026-08-07 14:59:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000018', 'LWL-2026-4209', 'pkg:pypi/cryptography@39.0.0', 'cryptography', '39.0.0', 'NULL pointer dereference in X.509 parsing', 'CWE-476', 'DoS through malformed X.509 certificate', 'Moderate', 5.9, 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H', true, true, 'Priority 3', 'Fix in Progress', 'python', 'Standard', '2026-08-05', '2026-08-11 10:51:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000019', 'LWL-2026-4101', 'pkg:maven/org.apache.commons/commons-text@1.9', 'Apache Commons Text', '1.9', 'Text4Shell - StringSubstitutor interpolation', 'CWE-94', 'Code injection through uncontrolled string interpolation', 'Critical', 9.8, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Validation', 'java', 'Extensive', '2026-07-20', '2026-08-12 09:34:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000001a', 'LWL-2026-4102', 'pkg:pypi/urllib3@1.26.15', 'urllib3', '1.26.15', 'CRLF injection in request headers', 'CWE-93', 'HTTP header injection through crafted URL parameters', 'Moderate', 6.1, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N', false, false, 'Priority 3', 'Validation', 'python', 'Standard', '2026-07-25', '2026-08-10 11:08:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000001b', 'LWL-2026-4103', 'pkg:npm/semver@7.3.8', 'semver', '7.3.8', 'ReDoS in version range parsing', 'CWE-1333', 'Denial of service through crafted semver range strings', 'Moderate', 5.3, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L', false, false, 'Priority 4', 'Validation', 'javascript', 'Standard', '2026-07-27', '2026-08-09 12:25:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000001c', 'LWL-2026-4104', 'pkg:maven/io.netty/netty-codec-http@4.1.86.Final', 'Netty', '4.1.86.Final', 'HTTP/2 CONTINUATION frame flooding', 'CWE-400', 'Memory exhaustion via excessive CONTINUATION frames', 'Important', 7.5, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H', true, true, 'Priority 2', 'Validation', 'java', 'Complex', '2026-07-23', '2026-08-11 10:51:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000001d', 'LWL-2026-4105', 'pkg:nuget/System.Security.Cryptography.Xml@7.0.0', 'System.Security.Cryptography.Xml', '7.0.0', 'XML signature wrapping attack', 'CWE-347', 'Signature verification bypass in XML digital signatures', 'Important', 7.5, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N', true, false, 'Priority 2', 'Validation', 'csharp', 'Extensive', '2026-07-17', '2026-08-08 13:42:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000001e', 'LWL-2026-4106', 'pkg:pypi/paramiko@3.1.0', 'Paramiko', '3.1.0', 'Authentication bypass in SFTP subsystem', 'CWE-287', 'SSH authentication bypass under race condition', 'Critical', 9.1, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N', true, true, 'Priority 1', 'Validation', 'python', 'Extensive', '2026-07-15', '2026-08-13 08:17:00+00', true, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000001f', 'LWL-2026-4107', 'pkg:maven/com.h2database/h2@2.1.214', 'H2 Database', '2.1.214', 'Remote code execution via JDBC URL', 'CWE-94', 'RCE through crafted JDBC connection URL parameters', 'Critical', 9.8, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Validation', 'java', 'Extensive', '2026-07-13', '2026-08-10 11:08:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000020', 'LWL-2026-4108', 'pkg:npm/ua-parser-js@0.7.33', 'ua-parser-js', '0.7.33', 'ReDoS in User-Agent parsing', 'CWE-1333', 'Regular expression denial of service in UA string parsing', 'Moderate', 5.3, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L', false, false, 'Priority 4', 'Validation', 'javascript', 'Standard', '2026-07-26', '2026-08-07 14:59:00+00', false, true, 'batch-3'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000021', 'LWL-2026-4001', 'pkg:maven/org.apache.shiro/shiro-core@1.10.0', 'Apache Shiro', '1.10.0', 'Authentication bypass via path traversal', 'CWE-287', 'Auth bypass using path normalization inconsistencies', 'Critical', 9.8, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Lightwell Network', 'java', 'Extensive', '2026-07-10', '2026-08-12 09:34:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000022', 'LWL-2026-4002', 'pkg:pypi/pyyaml@6.0', 'PyYAML', '6.0', 'Arbitrary code execution via load()', 'CWE-502', 'RCE through unsafe YAML deserialization', 'Important', 8.1, 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Lightwell Network', 'python', 'Complex', '2026-07-15', '2026-08-09 12:25:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000023', 'LWL-2026-4003', 'pkg:npm/node-forge@1.3.1', 'node-forge', '1.3.1', 'RSA PKCS#1 v1.5 signature forgery', 'CWE-347', 'Signature verification bypass in RSA implementation', 'Important', 7.5, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N', true, false, 'Priority 2', 'Lightwell Network', 'javascript', 'Complex', '2026-07-17', '2026-08-07 14:59:00+00', false, false, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000024', 'LWL-2026-4004', 'pkg:maven/org.hibernate/hibernate-core@5.6.14.Final', 'Hibernate', '5.6.14.Final', 'SQL injection in HQL named parameters', 'CWE-89', 'SQL injection through crafted HQL parameter names', 'Important', 8.6, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N', true, true, 'Priority 1', 'Lightwell Network', 'java', 'Complex', '2026-07-12', '2026-08-11 10:51:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000025', 'LWL-2026-4005', 'pkg:nuget/Azure.Identity@1.8.0', 'Azure.Identity', '1.8.0', 'Token cache poisoning', 'CWE-345', 'Authentication token manipulation through shared cache', 'Moderate', 6.5, 'CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N', false, false, 'Priority 3', 'Lightwell Network', 'csharp', 'Standard', '2026-07-20', '2026-08-06 07:16:00+00', false, false, 'batch-3'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000026', 'LWL-2026-4006', 'pkg:pypi/jinja2@3.1.2', 'Jinja2', '3.1.2', 'Sandbox escape via attr filter', 'CWE-693', 'Template sandbox bypass through attribute access', 'Important', 8.1, 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Lightwell Network', 'python', 'Complex', '2026-07-14', '2026-08-10 11:08:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000027', 'LWL-2026-4007', 'pkg:maven/net.minidev/json-smart@2.4.8', 'json-smart', '2.4.8', 'Stack overflow in JSON parser', 'CWE-674', 'DoS through deeply nested JSON structures', 'Moderate', 5.3, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L', false, false, 'Priority 4', 'Lightwell Network', 'java', 'Standard', '2026-07-19', '2026-08-05 08:33:00+00', false, true, 'batch-2'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000028', 'LWL-2026-3901', 'pkg:maven/org.eclipse.jetty/jetty-server@11.0.13', 'Jetty', '11.0.13', 'HTTP response splitting', 'CWE-113', 'Response header injection through crafted request parameters', 'Important', 7.5, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N', true, true, 'Priority 2', 'Lightwell Network', 'java', 'Complex', '2026-07-07', '2026-08-04 09:50:00+00', true, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000029', 'LWL-2026-3902', 'pkg:pypi/setuptools@67.0.0', 'setuptools', '67.0.0', 'Command injection in package install', 'CWE-78', 'OS command injection through crafted package metadata', 'Important', 8.8, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Lightwell Network', 'python', 'Extensive', '2026-07-05', '2026-08-02 11:24:00+00', false, false, 'batch-3'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000002a', 'LWL-2026-3903', 'pkg:npm/tar@6.1.13', 'tar', '6.1.13', 'Arbitrary file overwrite via symlink', 'CWE-59', 'Path traversal through symbolic link following during extraction', 'Important', 7.5, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N', true, false, 'Priority 2', 'Lightwell Network', 'javascript', 'Complex', '2026-07-09', '2026-08-06 07:16:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000002b', 'LWL-2026-3904', 'pkg:maven/org.apache.poi/poi@5.2.3', 'Apache POI', '5.2.3', 'XXE in OOXML document processing', 'CWE-611', 'XML external entity injection via crafted Office documents', 'Moderate', 6.5, 'CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N', false, false, 'Priority 3', 'Lightwell Network', 'java', 'Standard', '2026-07-10', '2026-08-03 10:07:00+00', false, false, 'batch-3'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000002c', 'LWL-2026-3905', 'pkg:nuget/SecureNet.Cryptography@2.1.1', 'SecureNet.Cryptography', '2.1.1', 'LDAP injection in certificate lookup', 'CWE-90', 'LDAP injection through certificate subject DN', 'Moderate', 6.1, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N', false, false, 'Priority 3', 'Lightwell Network', 'csharp', 'Standard', '2026-07-11', '2026-08-05 08:33:00+00', false, false, 'batch-3'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000002d', 'LWL-2026-3906', 'pkg:pypi/lxml@4.9.2', 'lxml', '4.9.2', 'Use-after-free in HTML parser', 'CWE-416', 'Memory corruption through crafted HTML document', 'Important', 8.1, 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Lightwell Network', 'python', 'Extensive', '2026-07-03', '2026-07-31 13:58:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000002e', 'LWL-2026-3801', 'pkg:maven/org.postgresql/postgresql@42.5.1', 'PostgreSQL JDBC', '42.5.1', 'SQL injection via connection property', 'CWE-89', 'SQL injection through JDBC connection properties', 'Critical', 9.8, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Lightwell Network', 'java', 'Extensive', '2026-06-30', '2026-07-30 14:15:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-00000000002f', 'LWL-2026-3802', 'pkg:pypi/certifi@2022.12.7', 'certifi', '2022.12.7', 'Inclusion of revoked root certificate', 'CWE-295', 'Trust of revoked CA certificate in bundle', 'Moderate', 5.3, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N', false, false, 'Priority 3', 'Lightwell Network', 'python', 'Standard', '2026-07-05', '2026-07-27 09:06:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000030', 'LWL-2026-3803', 'pkg:npm/glob-parent@5.1.2', 'glob-parent', '5.1.2', 'ReDoS in brace expansion', 'CWE-1333', 'Regular expression denial of service', 'Moderate', 5.3, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L', false, false, 'Priority 4', 'Lightwell Network', 'javascript', 'Standard', '2026-07-07', '2026-07-29 07:32:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000031', 'LWL-2026-3804', 'pkg:maven/com.squareup.okhttp3/okhttp@4.10.0', 'OkHttp', '4.10.0', 'Header injection in HTTP/2', 'CWE-113', 'Response splitting through HTTP/2 pseudo-headers', 'Important', 7.5, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N', true, true, 'Priority 2', 'Lightwell Network', 'java', 'Complex', '2026-07-02', '2026-08-01 12:41:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000032', 'LWL-2026-3805', 'pkg:nuget/Microsoft.AspNetCore.App@7.0.0', 'ASP.NET Core', '7.0.0', 'CORS policy bypass', 'CWE-942', 'Cross-origin resource sharing policy circumvention', 'Moderate', 6.1, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N', false, false, 'Priority 3', 'Lightwell Network', 'csharp', 'Standard', '2026-07-08', '2026-07-28 08:49:00+00', false, false, 'batch-3'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000033', 'LWL-2026-3806', 'pkg:pypi/werkzeug@2.2.3', 'Werkzeug', '2.2.3', 'Path traversal in SharedDataMiddleware', 'CWE-22', 'Arbitrary file read through path traversal', 'Important', 7.5, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N', true, false, 'Priority 2', 'Lightwell Network', 'python', 'Complex', '2026-07-04', '2026-07-26 10:23:00+00', false, false, 'batch-1'); +INSERT INTO lightwell_vulnerabilities (uuid, vulnerability_id, purl, component_name, component_version, title, cwe, description, severity, cvss, cvss_vector, exploit_tested, reproducer_included, customer_priority, stage, language, complexity, submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id) VALUES ('00000000-0000-4000-8000-000000000034', 'LWL-2026-3807', 'pkg:maven/org.codehaus.plexus/plexus-utils@3.4.2', 'plexus-utils', '3.4.2', 'Command injection in Commandline class', 'CWE-78', 'OS command injection through argument expansion', 'Important', 8.1, 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H', true, true, 'Priority 1', 'Lightwell Network', 'java', 'Complex', '2026-07-01', '2026-07-25 11:40:00+00', false, false, 'batch-1'); + +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000001'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000002'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000003'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000004'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000005'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000006'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000007'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000008'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000009'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-00000000000a'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-00000000000b'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-00000000000c'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-00000000000d'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-00000000000e'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-00000000000f'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000010'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000011'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000012'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000013'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000014'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000015'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000016'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000017'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000018'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000019'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-00000000001a'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-00000000001b'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-00000000001c'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-00000000001d'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-00000000001e'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-00000000001f'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000020'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000021'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000022'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000023'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000024'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000025'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000026'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000027'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000028'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000029'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-00000000002a'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-00000000002b'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-00000000002c'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-00000000002d'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-00000000002e'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-00000000002f'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000030'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000031'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000032'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-1', '00000000-0000-4000-8000-000000000033'); +INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ('demo-customer-2', '00000000-0000-4000-8000-000000000034'); + +COMMIT; diff --git a/docs/lightwell_vulnerabilities_dev.md b/docs/lightwell_vulnerabilities_dev.md new file mode 100644 index 000000000..6600348f9 --- /dev/null +++ b/docs/lightwell_vulnerabilities_dev.md @@ -0,0 +1,61 @@ +# Lightwell vulnerabilities — local development + +Lightwell vulnerabilities live in the same backend as the rest of content-sources: database migrations, compose, and tests follow the normal app workflow. This doc covers the lightwell-specific seed data, sqlc store, and schema reference. HTTP handlers are not implemented yet. + +## Local setup + +Use the standard dev environment (see [README.md](../README.md)): copy `configs/config.yaml.example` to `configs/config.yaml`, then: + +```bash +make compose-up +``` + +That starts dependencies and runs all migrations, including `20260814120000_create_lightwell_vulnerabilities`. + +If you hit migration issues on an old local DB (e.g. from superseded lightwell migrations during development), reset and re-migrate: + +```bash +make test-db-migrations +``` + +## Apply dev seed data + +The seed script loads 52 mock vulnerabilities from `lightwell-vulnerabilities-2026-08-14.json` into two demo customers (`demo-customer-1`, `demo-customer-2`): + +```bash +psql "sslmode=disable dbname=content user=content host=localhost port=5433 password=content" -f db/seeds/lightwell_vulnerabilities.sql +``` + +Or through the compose postgres container: + +```bash +docker compose exec -T postgres-content psql "sslmode=disable dbname=content user=content host=localhost port=5432 password=content" -f - < db/seeds/lightwell_vulnerabilities.sql +``` + +Adjust connection parameters to match your `configs/config.yaml` if they differ from the compose defaults. + +## Run tests + +Integration tests use the configured database and roll back per test: + +```bash +CONFIG_PATH="$(pwd)/configs/" go test ./pkg/lightwell/db/store/... +``` + +Or via make (runs all `pkg/` tests): + +```bash +make test-unit +``` + +## Regenerate sqlc store + +After changing `pkg/lightwell/db/queries/*.sql` or the migration schema: + +```bash +make sqlc-generate-lightwell +``` + +Generated code is written to `pkg/lightwell/db/store/`. + +sqlc uses `pkg/lightwell/db/schema.sql` (final table snapshot) rather than parsing rename migrations directly. Update that file when the lightwell schema changes. diff --git a/mk/includes.mk b/mk/includes.mk index 0f165124c..98f884c2c 100644 --- a/mk/includes.mk +++ b/mk/includes.mk @@ -33,6 +33,7 @@ include mk/docker.mk include mk/mockery.mk include mk/meta-db.mk include mk/db.mk +include mk/sqlc.mk include mk/meta-kafka.mk include mk/kafka.mk include mk/meta-docker.mk diff --git a/mk/sqlc.mk b/mk/sqlc.mk new file mode 100644 index 000000000..2fd391c84 --- /dev/null +++ b/mk/sqlc.mk @@ -0,0 +1,9 @@ +## +# sqlc code generation +## + +SQLC ?= go run github.com/sqlc-dev/sqlc/cmd/sqlc@v1.29.0 + +.PHONY: sqlc-generate-lightwell +sqlc-generate-lightwell: ## Generate sqlc store for lightwell vulnerabilities + cd "$(PROJECT_DIR)/pkg/lightwell/db" && $(SQLC) generate diff --git a/pkg/lightwell/db/queries/vulnerabilities.sql b/pkg/lightwell/db/queries/vulnerabilities.sql new file mode 100644 index 000000000..91c3b7c36 --- /dev/null +++ b/pkg/lightwell/db/queries/vulnerabilities.sql @@ -0,0 +1,170 @@ +-- name: ListCustomerIds :many +SELECT DISTINCT customer_id +FROM lightwell_vulnerability_customers +ORDER BY customer_id; + +-- name: ListVulnerabilities :many +SELECT + v.uuid, + v.vulnerability_id, + v.purl, + v.component_name, + v.component_version, + v.title, + v.cwe, + v.description, + v.severity, + v.cvss, + v.cvss_vector, + v.exploit_tested, + v.reproducer_included, + v.customer_priority, + v.stage, + v.language, + v.complexity, + v.submitted_date, + v.last_updated, + v.embargo, + v.duplicate, + v.ltwwlsupt_ticket_id, + v.created_at, + v.updated_at +FROM lightwell_vulnerabilities v +INNER JOIN lightwell_vulnerability_customers vc ON vc.vulnerability_uuid = v.uuid +WHERE vc.customer_id = sqlc.arg(customer_id) + AND ( + sqlc.narg(severities)::text[] IS NULL + OR cardinality(sqlc.narg(severities)::text[]) = 0 + OR v.severity = ANY (sqlc.narg(severities)::text[]) + ) + AND ( + sqlc.narg(stages)::text[] IS NULL + OR cardinality(sqlc.narg(stages)::text[]) = 0 + OR v.stage = ANY (sqlc.narg(stages)::text[]) + ) + AND ( + sqlc.narg(complexities)::text[] IS NULL + OR cardinality(sqlc.narg(complexities)::text[]) = 0 + OR v.complexity = ANY (sqlc.narg(complexities)::text[]) + ) + AND ( + sqlc.narg(ltwwlsupt_ticket_ids)::text[] IS NULL + OR cardinality(sqlc.narg(ltwwlsupt_ticket_ids)::text[]) = 0 + OR v.ltwwlsupt_ticket_id = ANY (sqlc.narg(ltwwlsupt_ticket_ids)::text[]) + ) + AND ( + sqlc.narg(flag)::text IS NULL + OR ( + sqlc.narg(flag)::text = 'embargo' + AND v.embargo = true + ) + OR ( + sqlc.narg(flag)::text = 'duplicate' + AND v.duplicate = true + ) + ) + AND ( + sqlc.narg(search)::text IS NULL + OR v.vulnerability_id ILIKE '%' || sqlc.narg(search) || '%' + OR v.component_name ILIKE '%' || sqlc.narg(search) || '%' + OR v.title ILIKE '%' || sqlc.narg(search) || '%' + ) +ORDER BY v.last_updated DESC, v.vulnerability_id ASC +LIMIT sqlc.arg(page_limit) OFFSET sqlc.arg(page_offset); + +-- name: CountAggregates :one +SELECT + COUNT(*)::bigint AS total_count, + COUNT(*) FILTER (WHERE v.severity = 'Critical')::bigint AS critical_count, + COUNT(*) FILTER (WHERE v.embargo = true)::bigint AS embargo_count, + COUNT(*) FILTER ( + WHERE v.stage <> 'Lightwell Network' + AND (CURRENT_DATE - v.submitted_date) > 30 + )::bigint AS blocked_count +FROM lightwell_vulnerabilities v +INNER JOIN lightwell_vulnerability_customers vc ON vc.vulnerability_uuid = v.uuid +WHERE vc.customer_id = sqlc.arg(customer_id) + AND ( + sqlc.narg(severities)::text[] IS NULL + OR cardinality(sqlc.narg(severities)::text[]) = 0 + OR v.severity = ANY (sqlc.narg(severities)::text[]) + ) + AND ( + sqlc.narg(stages)::text[] IS NULL + OR cardinality(sqlc.narg(stages)::text[]) = 0 + OR v.stage = ANY (sqlc.narg(stages)::text[]) + ) + AND ( + sqlc.narg(complexities)::text[] IS NULL + OR cardinality(sqlc.narg(complexities)::text[]) = 0 + OR v.complexity = ANY (sqlc.narg(complexities)::text[]) + ) + AND ( + sqlc.narg(ltwwlsupt_ticket_ids)::text[] IS NULL + OR cardinality(sqlc.narg(ltwwlsupt_ticket_ids)::text[]) = 0 + OR v.ltwwlsupt_ticket_id = ANY (sqlc.narg(ltwwlsupt_ticket_ids)::text[]) + ) + AND ( + sqlc.narg(flag)::text IS NULL + OR ( + sqlc.narg(flag)::text = 'embargo' + AND v.embargo = true + ) + OR ( + sqlc.narg(flag)::text = 'duplicate' + AND v.duplicate = true + ) + ) + AND ( + sqlc.narg(search)::text IS NULL + OR v.vulnerability_id ILIKE '%' || sqlc.narg(search) || '%' + OR v.component_name ILIKE '%' || sqlc.narg(search) || '%' + OR v.title ILIKE '%' || sqlc.narg(search) || '%' + ); + +-- name: CountByStage :many +SELECT + v.stage, + COUNT(*)::bigint AS count +FROM lightwell_vulnerabilities v +INNER JOIN lightwell_vulnerability_customers vc ON vc.vulnerability_uuid = v.uuid +WHERE vc.customer_id = sqlc.arg(customer_id) + AND ( + sqlc.narg(severities)::text[] IS NULL + OR cardinality(sqlc.narg(severities)::text[]) = 0 + OR v.severity = ANY (sqlc.narg(severities)::text[]) + ) + AND ( + sqlc.narg(stages)::text[] IS NULL + OR cardinality(sqlc.narg(stages)::text[]) = 0 + OR v.stage = ANY (sqlc.narg(stages)::text[]) + ) + AND ( + sqlc.narg(complexities)::text[] IS NULL + OR cardinality(sqlc.narg(complexities)::text[]) = 0 + OR v.complexity = ANY (sqlc.narg(complexities)::text[]) + ) + AND ( + sqlc.narg(ltwwlsupt_ticket_ids)::text[] IS NULL + OR cardinality(sqlc.narg(ltwwlsupt_ticket_ids)::text[]) = 0 + OR v.ltwwlsupt_ticket_id = ANY (sqlc.narg(ltwwlsupt_ticket_ids)::text[]) + ) + AND ( + sqlc.narg(flag)::text IS NULL + OR ( + sqlc.narg(flag)::text = 'embargo' + AND v.embargo = true + ) + OR ( + sqlc.narg(flag)::text = 'duplicate' + AND v.duplicate = true + ) + ) + AND ( + sqlc.narg(search)::text IS NULL + OR v.vulnerability_id ILIKE '%' || sqlc.narg(search) || '%' + OR v.component_name ILIKE '%' || sqlc.narg(search) || '%' + OR v.title ILIKE '%' || sqlc.narg(search) || '%' + ) +GROUP BY v.stage +ORDER BY v.stage; diff --git a/pkg/lightwell/db/schema.sql b/pkg/lightwell/db/schema.sql new file mode 100644 index 000000000..01a67fd86 --- /dev/null +++ b/pkg/lightwell/db/schema.sql @@ -0,0 +1,35 @@ +-- sqlc schema snapshot: current lightwell vulnerabilities tables (see db/migrations) + +CREATE TABLE lightwell_vulnerabilities ( + uuid UUID PRIMARY KEY, + vulnerability_id TEXT NOT NULL UNIQUE, + purl TEXT, + component_name TEXT NOT NULL, + component_version TEXT NOT NULL, + title TEXT, + cwe TEXT, + description TEXT, + severity TEXT NOT NULL, + cvss DOUBLE PRECISION, + cvss_vector TEXT, + exploit_tested BOOLEAN NOT NULL DEFAULT false, + reproducer_included BOOLEAN NOT NULL DEFAULT false, + customer_priority TEXT, + stage TEXT NOT NULL, + language TEXT, + complexity TEXT NOT NULL, + submitted_date DATE NOT NULL, + last_updated TIMESTAMPTZ NOT NULL, + embargo BOOLEAN NOT NULL DEFAULT false, + duplicate BOOLEAN NOT NULL DEFAULT false, + ltwwlsupt_ticket_id TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE lightwell_vulnerability_customers ( + customer_id TEXT NOT NULL, + vulnerability_uuid UUID NOT NULL REFERENCES lightwell_vulnerabilities (uuid) ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (customer_id, vulnerability_uuid) +); diff --git a/pkg/lightwell/db/sqlc.yaml b/pkg/lightwell/db/sqlc.yaml new file mode 100644 index 000000000..9f04d3824 --- /dev/null +++ b/pkg/lightwell/db/sqlc.yaml @@ -0,0 +1,64 @@ +version: "2" +overrides: + go: + overrides: + - db_type: "uuid" + engine: "postgresql" + go_type: + import: "github.com/google/uuid" + type: "UUID" + - db_type: "text" + engine: "postgresql" + nullable: true + go_type: + type: "string" + pointer: true + - db_type: "pg_catalog.float8" + engine: "postgresql" + nullable: true + go_type: + type: "float64" + pointer: true + - db_type: "date" + engine: "postgresql" + go_type: + import: "time" + type: "Time" + # pgx/v5 + sqlc: both pg_catalog.timestamptz and timestamptz entries are required; + # otherwise sqlc emits pgtype.Timestamptz. + - db_type: "pg_catalog.timestamptz" + engine: "postgresql" + go_type: + import: "time" + type: "Time" + - db_type: "pg_catalog.timestamptz" + engine: "postgresql" + nullable: true + go_type: + import: "time" + type: "Time" + pointer: true + - db_type: "timestamptz" + engine: "postgresql" + go_type: + import: "time" + type: "Time" + - db_type: "timestamptz" + engine: "postgresql" + nullable: true + go_type: + import: "time" + type: "Time" + pointer: true +sql: + - engine: "postgresql" + queries: "queries" + schema: "schema.sql" + gen: + go: + package: "store" + out: "store" + sql_package: "pgx/v5" + emit_json_tags: true + emit_empty_slices: true + emit_interface: true diff --git a/pkg/lightwell/db/store/db.go b/pkg/lightwell/db/store/db.go new file mode 100644 index 000000000..f4b9515f8 --- /dev/null +++ b/pkg/lightwell/db/store/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 + +package store + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/pkg/lightwell/db/store/models.go b/pkg/lightwell/db/store/models.go new file mode 100644 index 000000000..df7274033 --- /dev/null +++ b/pkg/lightwell/db/store/models.go @@ -0,0 +1,44 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 + +package store + +import ( + "time" + + "github.com/google/uuid" +) + +type LightwellVulnerability struct { + Uuid uuid.UUID `json:"uuid"` + VulnerabilityID string `json:"vulnerability_id"` + Purl *string `json:"purl"` + ComponentName string `json:"component_name"` + ComponentVersion string `json:"component_version"` + Title *string `json:"title"` + Cwe *string `json:"cwe"` + Description *string `json:"description"` + Severity string `json:"severity"` + Cvss *float64 `json:"cvss"` + CvssVector *string `json:"cvss_vector"` + ExploitTested bool `json:"exploit_tested"` + ReproducerIncluded bool `json:"reproducer_included"` + CustomerPriority *string `json:"customer_priority"` + Stage string `json:"stage"` + Language *string `json:"language"` + Complexity string `json:"complexity"` + SubmittedDate time.Time `json:"submitted_date"` + LastUpdated time.Time `json:"last_updated"` + Embargo bool `json:"embargo"` + Duplicate bool `json:"duplicate"` + LtwwlsuptTicketID string `json:"ltwwlsupt_ticket_id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type LightwellVulnerabilityCustomer struct { + CustomerID string `json:"customer_id"` + VulnerabilityUuid uuid.UUID `json:"vulnerability_uuid"` + CreatedAt time.Time `json:"created_at"` +} diff --git a/pkg/lightwell/db/store/querier.go b/pkg/lightwell/db/store/querier.go new file mode 100644 index 000000000..1eded932b --- /dev/null +++ b/pkg/lightwell/db/store/querier.go @@ -0,0 +1,18 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 + +package store + +import ( + "context" +) + +type Querier interface { + CountAggregates(ctx context.Context, arg CountAggregatesParams) (CountAggregatesRow, error) + CountByStage(ctx context.Context, arg CountByStageParams) ([]CountByStageRow, error) + ListCustomerIds(ctx context.Context) ([]string, error) + ListVulnerabilities(ctx context.Context, arg ListVulnerabilitiesParams) ([]LightwellVulnerability, error) +} + +var _ Querier = (*Queries)(nil) diff --git a/pkg/lightwell/db/store/store_test.go b/pkg/lightwell/db/store/store_test.go new file mode 100644 index 000000000..77a897ea7 --- /dev/null +++ b/pkg/lightwell/db/store/store_test.go @@ -0,0 +1,497 @@ +package store_test + +import ( + "context" + "fmt" + "os" + "testing" + "time" + + "github.com/content-services/content-sources-backend/pkg/config" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" + "github.com/google/uuid" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var testPool *pgxpool.Pool + +func TestMain(m *testing.M) { + config.ConfigureLogging() + dbConfig := config.Get().Database + connStr := fmt.Sprintf( + "postgres://%s:%s@%s:%d/%s?sslmode=disable", + dbConfig.User, + dbConfig.Password, + dbConfig.Host, + dbConfig.Port, + dbConfig.Name, + ) + + pool, err := pgxpool.New(context.Background(), connStr) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to connect to database: %v\n", err) + os.Exit(1) + } + testPool = pool + + exitCode := m.Run() + pool.Close() + os.Exit(exitCode) +} + +type testVulnSpec struct { + vulnID string + severity string + stage string + language string + complexity string + ltwwlsuptTicketID string + daysAgo int + embargo bool + duplicate bool + component string + title string + customerIDs []string +} + +func beginTestTx(t *testing.T) (context.Context, pgx.Tx, *store.Queries) { + ctx := context.Background() + tx, err := testPool.Begin(ctx) + require.NoError(t, err) + return ctx, tx, store.New(tx) +} + +func rollbackTestTx(t *testing.T, tx pgx.Tx) { + require.NoError(t, tx.Rollback(context.Background())) +} + +func insertTestVulnerabilities(t *testing.T, ctx context.Context, tx pgx.Tx, specs []testVulnSpec) map[string]uuid.UUID { + ids := make(map[string]uuid.UUID, len(specs)) + for _, spec := range specs { + id := uuid.New() + ids[spec.vulnID] = id + _, err := tx.Exec(ctx, ` + INSERT INTO lightwell_vulnerabilities ( + uuid, vulnerability_id, component_name, component_version, title, severity, stage, language, complexity, + submitted_date, last_updated, embargo, duplicate, ltwwlsupt_ticket_id + ) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, CURRENT_DATE - $10::int, NOW(), $11, $12, $13 + )`, + id, + spec.vulnID, + spec.component, + "1.0.0", + spec.title, + spec.severity, + spec.stage, + spec.language, + spec.complexity, + spec.daysAgo, + spec.embargo, + spec.duplicate, + spec.ltwwlsuptTicketID, + ) + require.NoError(t, err) + + for _, customerID := range spec.customerIDs { + _, err = tx.Exec(ctx, + `INSERT INTO lightwell_vulnerability_customers (customer_id, vulnerability_uuid) VALUES ($1, $2)`, + customerID, id, + ) + require.NoError(t, err) + } + } + return ids +} + +func filterParams(customerID string) store.CountAggregatesParams { + return store.CountAggregatesParams{ + CustomerID: customerID, + } +} + +func listParams(customerID string) store.ListVulnerabilitiesParams { + return store.ListVulnerabilitiesParams{ + CustomerID: customerID, + PageLimit: 100, + } +} + +func stageParams(customerID string) store.CountByStageParams { + return store.CountByStageParams{ + CustomerID: customerID, + } +} + +func textFlag(value string) *string { + return &value +} + +func textSearch(value string) *string { + return &value +} + +func TestStore_CustomerScopingAndFilters(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + customerA := fmt.Sprintf("lw-scope-a-%d", time.Now().UnixNano()) + customerB := fmt.Sprintf("lw-scope-b-%d", time.Now().UnixNano()) + + specs := []testVulnSpec{ + { + vulnID: "LWL-TEST-CRIT-STANDARD", + severity: "Critical", + stage: "Submitted", + language: "java", + complexity: "Standard", + ltwwlsuptTicketID: "ticket-alpha", + daysAgo: 8, + embargo: true, + component: "log4j-core", + title: "JNDI injection test", + customerIDs: []string{customerA}, + }, + { + vulnID: "LWL-TEST-IMP-COMPLEX", + severity: "Important", + stage: "Submitted", + language: "java", + complexity: "Complex", + ltwwlsuptTicketID: "ticket-alpha", + daysAgo: 10, + component: "spring-web", + title: "HTTP smuggling test", + customerIDs: []string{customerA}, + }, + { + vulnID: "LWL-TEST-MOD-COMPLEX", + severity: "Moderate", + stage: "Classified", + language: "python", + complexity: "Complex", + ltwwlsuptTicketID: "ticket-beta", + daysAgo: 16, + component: "requests", + title: "SSRF redirect test", + customerIDs: []string{customerA}, + }, + { + vulnID: "LWL-TEST-CRIT-EXTENSIVE", + severity: "Critical", + stage: "Fix in Progress", + language: "java", + complexity: "Extensive", + ltwwlsuptTicketID: "ticket-beta", + daysAgo: 31, + component: "jackson-databind", + title: "Deserialization gadget test", + customerIDs: []string{customerA}, + }, + { + vulnID: "LWL-TEST-NETWORK", + severity: "Important", + stage: "Lightwell Network", + language: "javascript", + complexity: "Extensive", + ltwwlsuptTicketID: "ticket-alpha", + daysAgo: 50, + component: "express", + title: "Path traversal test", + customerIDs: []string{customerA}, + }, + { + vulnID: "LWL-TEST-DUP", + severity: "Moderate", + stage: "Validation", + language: "javascript", + complexity: "Standard", + ltwwlsuptTicketID: "ticket-beta", + daysAgo: 20, + duplicate: true, + component: "lodash", + title: "Prototype pollution test", + customerIDs: []string{customerA}, + }, + { + vulnID: "LWL-TEST-OTHER-CUSTOMER", + severity: "Critical", + stage: "Submitted", + language: "java", + complexity: "Standard", + ltwwlsuptTicketID: "ticket-other", + daysAgo: 9, + component: "other-package", + title: "Other customer only", + customerIDs: []string{customerB}, + }, + } + insertTestVulnerabilities(t, ctx, tx, specs) + + allForA, err := q.ListVulnerabilities(ctx, listParams(customerA)) + require.NoError(t, err) + assert.Len(t, allForA, 6) + assert.False(t, allForA[0].LastUpdated.IsZero()) + + allForB, err := q.ListVulnerabilities(ctx, listParams(customerB)) + require.NoError(t, err) + assert.Len(t, allForB, 1) + assert.Equal(t, "LWL-TEST-OTHER-CUSTOMER", allForB[0].VulnerabilityID) + + criticalParams := listParams(customerA) + criticalParams.Severities = []string{"Critical"} + critical, err := q.ListVulnerabilities(ctx, criticalParams) + require.NoError(t, err) + assert.Len(t, critical, 2) + + stageFilter := listParams(customerA) + stageFilter.Stages = []string{"Classified"} + classified, err := q.ListVulnerabilities(ctx, stageFilter) + require.NoError(t, err) + assert.Len(t, classified, 1) + assert.Equal(t, "LWL-TEST-MOD-COMPLEX", classified[0].VulnerabilityID) + + complexityParams := listParams(customerA) + complexityParams.Complexities = []string{"Standard"} + standard, err := q.ListVulnerabilities(ctx, complexityParams) + require.NoError(t, err) + assert.Len(t, standard, 2) + + ticketParams := listParams(customerA) + ticketParams.LtwwlsuptTicketIds = []string{"ticket-beta"} + beta, err := q.ListVulnerabilities(ctx, ticketParams) + require.NoError(t, err) + assert.Len(t, beta, 3) + + embargoParams := listParams(customerA) + embargoParams.Flag = textFlag("embargo") + embargoed, err := q.ListVulnerabilities(ctx, embargoParams) + require.NoError(t, err) + assert.Len(t, embargoed, 1) + assert.Equal(t, "LWL-TEST-CRIT-STANDARD", embargoed[0].VulnerabilityID) + + dupParams := listParams(customerA) + dupParams.Flag = textFlag("duplicate") + duplicates, err := q.ListVulnerabilities(ctx, dupParams) + require.NoError(t, err) + assert.Len(t, duplicates, 1) + assert.Equal(t, "LWL-TEST-DUP", duplicates[0].VulnerabilityID) + + searchComponent := listParams(customerA) + searchComponent.Search = textSearch("log4j") + byComponent, err := q.ListVulnerabilities(ctx, searchComponent) + require.NoError(t, err) + assert.Len(t, byComponent, 1) + assert.Equal(t, "log4j-core", byComponent[0].ComponentName) + + searchTitle := listParams(customerA) + searchTitle.Search = textSearch("gadget") + byTitle, err := q.ListVulnerabilities(ctx, searchTitle) + require.NoError(t, err) + assert.Len(t, byTitle, 1) + assert.Equal(t, "LWL-TEST-CRIT-EXTENSIVE", byTitle[0].VulnerabilityID) + + searchID := listParams(customerA) + searchID.Search = textSearch("LWL-TEST-MOD") + byID, err := q.ListVulnerabilities(ctx, searchID) + require.NoError(t, err) + assert.Len(t, byID, 1) + assert.Equal(t, "LWL-TEST-MOD-COMPLEX", byID[0].VulnerabilityID) +} + +func TestStore_CountAggregates(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + customerID := fmt.Sprintf("lw-agg-%d", time.Now().UnixNano()) + specs := []testVulnSpec{ + { + vulnID: "LWL-AGG-CRIT-STANDARD", + severity: "Critical", + stage: "Submitted", + language: "java", + complexity: "Standard", + ltwwlsuptTicketID: "ticket-1", + daysAgo: 8, + embargo: true, + customerIDs: []string{customerID}, + }, + { + vulnID: "LWL-AGG-CRIT-EXTENSIVE", + severity: "Critical", + stage: "Submitted", + language: "java", + complexity: "Extensive", + ltwwlsuptTicketID: "ticket-1", + daysAgo: 31, + customerIDs: []string{customerID}, + }, + { + vulnID: "LWL-AGG-MOD-COMPLEX", + severity: "Moderate", + stage: "Submitted", + language: "python", + complexity: "Complex", + ltwwlsuptTicketID: "ticket-1", + daysAgo: 16, + customerIDs: []string{customerID}, + }, + { + vulnID: "LWL-AGG-NOT-BLOCKED-STANDARD", + severity: "Moderate", + stage: "Submitted", + language: "python", + complexity: "Standard", + ltwwlsuptTicketID: "ticket-1", + daysAgo: 5, + customerIDs: []string{customerID}, + }, + { + vulnID: "LWL-AGG-NETWORK", + severity: "Important", + stage: "Lightwell Network", + language: "javascript", + complexity: "Extensive", + ltwwlsuptTicketID: "ticket-1", + daysAgo: 60, + customerIDs: []string{customerID}, + }, + } + insertTestVulnerabilities(t, ctx, tx, specs) + + agg, err := q.CountAggregates(ctx, filterParams(customerID)) + require.NoError(t, err) + assert.Equal(t, int64(5), agg.TotalCount) + assert.Equal(t, int64(2), agg.CriticalCount) + assert.Equal(t, int64(1), agg.EmbargoCount) + assert.Equal(t, int64(1), agg.BlockedCount) +} + +func TestStore_CountByStage(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + customerID := fmt.Sprintf("lw-stage-%d", time.Now().UnixNano()) + specs := []testVulnSpec{ + { + vulnID: "LWL-STAGE-1", + severity: "Critical", + stage: "Submitted", + language: "java", + complexity: "Standard", + ltwwlsuptTicketID: "ticket-1", + daysAgo: 3, + customerIDs: []string{customerID}, + }, + { + vulnID: "LWL-STAGE-2", + severity: "Important", + stage: "Submitted", + language: "java", + complexity: "Complex", + ltwwlsuptTicketID: "ticket-1", + daysAgo: 4, + customerIDs: []string{customerID}, + }, + { + vulnID: "LWL-STAGE-3", + severity: "Moderate", + stage: "Classified", + language: "python", + complexity: "Standard", + ltwwlsuptTicketID: "ticket-1", + daysAgo: 5, + customerIDs: []string{customerID}, + }, + } + insertTestVulnerabilities(t, ctx, tx, specs) + + rows, err := q.CountByStage(ctx, stageParams(customerID)) + require.NoError(t, err) + assert.Len(t, rows, 2) + + counts := map[string]int64{} + for _, row := range rows { + counts[row.Stage] = row.Count + } + assert.Equal(t, int64(2), counts["Submitted"]) + assert.Equal(t, int64(1), counts["Classified"]) +} + +func TestStore_EmptyResultsForUnknownFilters(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + customerID := fmt.Sprintf("lw-empty-%d", time.Now().UnixNano()) + insertTestVulnerabilities(t, ctx, tx, []testVulnSpec{ + { + vulnID: "LWL-EMPTY-1", + severity: "Moderate", + stage: "Submitted", + language: "java", + complexity: "Standard", + ltwwlsuptTicketID: "ticket-1", + daysAgo: 2, + customerIDs: []string{customerID}, + }, + }) + + params := filterParams(customerID) + params.Severities = []string{"NonexistentSeverity"} + agg, err := q.CountAggregates(ctx, params) + require.NoError(t, err) + assert.Equal(t, int64(0), agg.TotalCount) + assert.Equal(t, int64(0), agg.CriticalCount) + assert.Equal(t, int64(0), agg.EmbargoCount) + assert.Equal(t, int64(0), agg.BlockedCount) + + list := listParams(customerID) + list.Severities = []string{"NonexistentSeverity"} + items, err := q.ListVulnerabilities(ctx, list) + require.NoError(t, err) + assert.Empty(t, items) + + stageParamsUnknown := stageParams(customerID) + stageParamsUnknown.Severities = []string{"NonexistentSeverity"} + stageRowsUnknown, err := q.CountByStage(ctx, stageParamsUnknown) + require.NoError(t, err) + assert.Empty(t, stageRowsUnknown) +} + +func TestStore_ListCustomerIds(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + customerA := fmt.Sprintf("lw-list-a-%d", time.Now().UnixNano()) + customerB := fmt.Sprintf("lw-list-b-%d", time.Now().UnixNano()) + insertTestVulnerabilities(t, ctx, tx, []testVulnSpec{ + { + vulnID: "LWL-LIST-1", + severity: "Moderate", + stage: "Submitted", + language: "java", + complexity: "Standard", + ltwwlsuptTicketID: "ticket-1", + daysAgo: 1, + customerIDs: []string{customerA}, + }, + { + vulnID: "LWL-LIST-2", + severity: "Moderate", + stage: "Submitted", + language: "python", + complexity: "Standard", + ltwwlsuptTicketID: "ticket-1", + daysAgo: 1, + customerIDs: []string{customerB}, + }, + }) + + ids, err := q.ListCustomerIds(ctx) + require.NoError(t, err) + assert.Contains(t, ids, customerA) + assert.Contains(t, ids, customerB) +} diff --git a/pkg/lightwell/db/store/vulnerabilities.sql.go b/pkg/lightwell/db/store/vulnerabilities.sql.go new file mode 100644 index 000000000..5653b3c5d --- /dev/null +++ b/pkg/lightwell/db/store/vulnerabilities.sql.go @@ -0,0 +1,352 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: vulnerabilities.sql + +package store + +import ( + "context" +) + +const countAggregates = `-- name: CountAggregates :one +SELECT + COUNT(*)::bigint AS total_count, + COUNT(*) FILTER (WHERE v.severity = 'Critical')::bigint AS critical_count, + COUNT(*) FILTER (WHERE v.embargo = true)::bigint AS embargo_count, + COUNT(*) FILTER ( + WHERE v.stage <> 'Lightwell Network' + AND (CURRENT_DATE - v.submitted_date) > 30 + )::bigint AS blocked_count +FROM lightwell_vulnerabilities v +INNER JOIN lightwell_vulnerability_customers vc ON vc.vulnerability_uuid = v.uuid +WHERE vc.customer_id = $1 + AND ( + $2::text[] IS NULL + OR cardinality($2::text[]) = 0 + OR v.severity = ANY ($2::text[]) + ) + AND ( + $3::text[] IS NULL + OR cardinality($3::text[]) = 0 + OR v.stage = ANY ($3::text[]) + ) + AND ( + $4::text[] IS NULL + OR cardinality($4::text[]) = 0 + OR v.complexity = ANY ($4::text[]) + ) + AND ( + $5::text[] IS NULL + OR cardinality($5::text[]) = 0 + OR v.ltwwlsupt_ticket_id = ANY ($5::text[]) + ) + AND ( + $6::text IS NULL + OR ( + $6::text = 'embargo' + AND v.embargo = true + ) + OR ( + $6::text = 'duplicate' + AND v.duplicate = true + ) + ) + AND ( + $7::text IS NULL + OR v.vulnerability_id ILIKE '%' || $7 || '%' + OR v.component_name ILIKE '%' || $7 || '%' + OR v.title ILIKE '%' || $7 || '%' + ) +` + +type CountAggregatesParams struct { + CustomerID string `json:"customer_id"` + Severities []string `json:"severities"` + Stages []string `json:"stages"` + Complexities []string `json:"complexities"` + LtwwlsuptTicketIds []string `json:"ltwwlsupt_ticket_ids"` + Flag *string `json:"flag"` + Search *string `json:"search"` +} + +type CountAggregatesRow struct { + TotalCount int64 `json:"total_count"` + CriticalCount int64 `json:"critical_count"` + EmbargoCount int64 `json:"embargo_count"` + BlockedCount int64 `json:"blocked_count"` +} + +func (q *Queries) CountAggregates(ctx context.Context, arg CountAggregatesParams) (CountAggregatesRow, error) { + row := q.db.QueryRow(ctx, countAggregates, + arg.CustomerID, + arg.Severities, + arg.Stages, + arg.Complexities, + arg.LtwwlsuptTicketIds, + arg.Flag, + arg.Search, + ) + var i CountAggregatesRow + err := row.Scan( + &i.TotalCount, + &i.CriticalCount, + &i.EmbargoCount, + &i.BlockedCount, + ) + return i, err +} + +const countByStage = `-- name: CountByStage :many +SELECT + v.stage, + COUNT(*)::bigint AS count +FROM lightwell_vulnerabilities v +INNER JOIN lightwell_vulnerability_customers vc ON vc.vulnerability_uuid = v.uuid +WHERE vc.customer_id = $1 + AND ( + $2::text[] IS NULL + OR cardinality($2::text[]) = 0 + OR v.severity = ANY ($2::text[]) + ) + AND ( + $3::text[] IS NULL + OR cardinality($3::text[]) = 0 + OR v.stage = ANY ($3::text[]) + ) + AND ( + $4::text[] IS NULL + OR cardinality($4::text[]) = 0 + OR v.complexity = ANY ($4::text[]) + ) + AND ( + $5::text[] IS NULL + OR cardinality($5::text[]) = 0 + OR v.ltwwlsupt_ticket_id = ANY ($5::text[]) + ) + AND ( + $6::text IS NULL + OR ( + $6::text = 'embargo' + AND v.embargo = true + ) + OR ( + $6::text = 'duplicate' + AND v.duplicate = true + ) + ) + AND ( + $7::text IS NULL + OR v.vulnerability_id ILIKE '%' || $7 || '%' + OR v.component_name ILIKE '%' || $7 || '%' + OR v.title ILIKE '%' || $7 || '%' + ) +GROUP BY v.stage +ORDER BY v.stage +` + +type CountByStageParams struct { + CustomerID string `json:"customer_id"` + Severities []string `json:"severities"` + Stages []string `json:"stages"` + Complexities []string `json:"complexities"` + LtwwlsuptTicketIds []string `json:"ltwwlsupt_ticket_ids"` + Flag *string `json:"flag"` + Search *string `json:"search"` +} + +type CountByStageRow struct { + Stage string `json:"stage"` + Count int64 `json:"count"` +} + +func (q *Queries) CountByStage(ctx context.Context, arg CountByStageParams) ([]CountByStageRow, error) { + rows, err := q.db.Query(ctx, countByStage, + arg.CustomerID, + arg.Severities, + arg.Stages, + arg.Complexities, + arg.LtwwlsuptTicketIds, + arg.Flag, + arg.Search, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []CountByStageRow{} + for rows.Next() { + var i CountByStageRow + if err := rows.Scan(&i.Stage, &i.Count); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listCustomerIds = `-- name: ListCustomerIds :many +SELECT DISTINCT customer_id +FROM lightwell_vulnerability_customers +ORDER BY customer_id +` + +func (q *Queries) ListCustomerIds(ctx context.Context) ([]string, error) { + rows, err := q.db.Query(ctx, listCustomerIds) + if err != nil { + return nil, err + } + defer rows.Close() + items := []string{} + for rows.Next() { + var customer_id string + if err := rows.Scan(&customer_id); err != nil { + return nil, err + } + items = append(items, customer_id) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listVulnerabilities = `-- name: ListVulnerabilities :many +SELECT + v.uuid, + v.vulnerability_id, + v.purl, + v.component_name, + v.component_version, + v.title, + v.cwe, + v.description, + v.severity, + v.cvss, + v.cvss_vector, + v.exploit_tested, + v.reproducer_included, + v.customer_priority, + v.stage, + v.language, + v.complexity, + v.submitted_date, + v.last_updated, + v.embargo, + v.duplicate, + v.ltwwlsupt_ticket_id, + v.created_at, + v.updated_at +FROM lightwell_vulnerabilities v +INNER JOIN lightwell_vulnerability_customers vc ON vc.vulnerability_uuid = v.uuid +WHERE vc.customer_id = $1 + AND ( + $2::text[] IS NULL + OR cardinality($2::text[]) = 0 + OR v.severity = ANY ($2::text[]) + ) + AND ( + $3::text[] IS NULL + OR cardinality($3::text[]) = 0 + OR v.stage = ANY ($3::text[]) + ) + AND ( + $4::text[] IS NULL + OR cardinality($4::text[]) = 0 + OR v.complexity = ANY ($4::text[]) + ) + AND ( + $5::text[] IS NULL + OR cardinality($5::text[]) = 0 + OR v.ltwwlsupt_ticket_id = ANY ($5::text[]) + ) + AND ( + $6::text IS NULL + OR ( + $6::text = 'embargo' + AND v.embargo = true + ) + OR ( + $6::text = 'duplicate' + AND v.duplicate = true + ) + ) + AND ( + $7::text IS NULL + OR v.vulnerability_id ILIKE '%' || $7 || '%' + OR v.component_name ILIKE '%' || $7 || '%' + OR v.title ILIKE '%' || $7 || '%' + ) +ORDER BY v.last_updated DESC, v.vulnerability_id ASC +LIMIT $9 OFFSET $8 +` + +type ListVulnerabilitiesParams struct { + CustomerID string `json:"customer_id"` + Severities []string `json:"severities"` + Stages []string `json:"stages"` + Complexities []string `json:"complexities"` + LtwwlsuptTicketIds []string `json:"ltwwlsupt_ticket_ids"` + Flag *string `json:"flag"` + Search *string `json:"search"` + PageOffset int32 `json:"page_offset"` + PageLimit int32 `json:"page_limit"` +} + +func (q *Queries) ListVulnerabilities(ctx context.Context, arg ListVulnerabilitiesParams) ([]LightwellVulnerability, error) { + rows, err := q.db.Query(ctx, listVulnerabilities, + arg.CustomerID, + arg.Severities, + arg.Stages, + arg.Complexities, + arg.LtwwlsuptTicketIds, + arg.Flag, + arg.Search, + arg.PageOffset, + arg.PageLimit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []LightwellVulnerability{} + for rows.Next() { + var i LightwellVulnerability + if err := rows.Scan( + &i.Uuid, + &i.VulnerabilityID, + &i.Purl, + &i.ComponentName, + &i.ComponentVersion, + &i.Title, + &i.Cwe, + &i.Description, + &i.Severity, + &i.Cvss, + &i.CvssVector, + &i.ExploitTested, + &i.ReproducerIncluded, + &i.CustomerPriority, + &i.Stage, + &i.Language, + &i.Complexity, + &i.SubmittedDate, + &i.LastUpdated, + &i.Embargo, + &i.Duplicate, + &i.LtwwlsuptTicketID, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} From f0048ec46dbf11a54fdfb815e2ba0cebf48f0c2e Mon Sep 17 00:00:00 2001 From: etsien Date: Tue, 18 Aug 2026 17:34:16 -0400 Subject: [PATCH 02/47] build out phase 1: advisories, packages, repositories, package-versions and counts --- api/docs.go | 402 +++++++++++ api/openapi.json | 458 +++++++++++++ db/migrations.latest | 2 +- ...lightwell_advisory_severity_order.down.sql | 7 + ...d_lightwell_advisory_severity_order.up.sql | 20 + pkg/api/lightwell_advisories.go | 29 + pkg/api/lightwell_packages.go | 65 ++ pkg/api/repositories.go | 3 + pkg/handler/api.go | 20 +- pkg/handler/lightwell_advisories.go | 150 ++++ pkg/handler/lightwell_advisories_test.go | 237 +++++++ pkg/handler/lightwell_packages.go | 638 ++++++++++++++++++ pkg/handler/lightwell_packages_test.go | 392 +++++++++++ pkg/handler/repositories.go | 40 ++ pkg/lightwell/db/queries/advisories.sql | 60 ++ pkg/lightwell/db/schema.sql | 32 +- pkg/lightwell/db/store/models.go | 21 + pkg/lightwell/db/store/querier.go | 6 + 18 files changed, 2579 insertions(+), 3 deletions(-) create mode 100644 db/migrations/20260818120000_add_lightwell_advisory_severity_order.down.sql create mode 100644 db/migrations/20260818120000_add_lightwell_advisory_severity_order.up.sql create mode 100644 pkg/api/lightwell_advisories.go create mode 100644 pkg/api/lightwell_packages.go create mode 100644 pkg/handler/lightwell_advisories.go create mode 100644 pkg/handler/lightwell_advisories_test.go create mode 100644 pkg/handler/lightwell_packages.go create mode 100644 pkg/handler/lightwell_packages_test.go create mode 100644 pkg/lightwell/db/queries/advisories.sql diff --git a/api/docs.go b/api/docs.go index 1589607e4..9a4b72a03 100644 --- a/api/docs.go +++ b/api/docs.go @@ -282,6 +282,234 @@ const docTemplate = `{ } } }, + "/lightwell/advisories": { + "get": { + "description": "List security advisories for Lightwell remediated packages with optional filtering.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Advisories", + "operationId": "listLightwellAdvisories", + "parameters": [ + { + "type": "string", + "description": "Filter by repository UUID", + "name": "repository_uuid", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "package_name", + "in": "query" + }, + { + "type": "string", + "description": "Minimum severity level (low, moderate, important, critical)", + "name": "severity_min", + "in": "query" + }, + { + "type": "string", + "description": "Filter by CVE ID (exact match)", + "name": "cve_id", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellAdvisoryCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, + "/lightwell/package_versions": { + "get": { + "description": "List individual package versions aggregated across all Lightwell repositories, with optional CVE-based filtering.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Package Versions (cross-repo)", + "operationId": "listLightwellPackageVersions", + "parameters": [ + { + "type": "string", + "description": "Filter by content type (maven, python, npm)", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by security level (validated, remediated)", + "name": "security_level", + "in": "query" + }, + { + "type": "string", + "description": "Filter by repository name", + "name": "repository", + "in": "query" + }, + { + "type": "string", + "description": "Show only packages that resolve this CVE", + "name": "resolves_cve_id", + "in": "query" + }, + { + "type": "string", + "description": "Show only packages vulnerable to this CVE", + "name": "vulnerable_to_cve_id", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellPackageVersionCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, + "/lightwell/packages": { + "get": { + "description": "List packages aggregated across all Lightwell repositories, with optional filtering by content type, name, and security level.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Packages (cross-repo)", + "operationId": "listLightwellPackages", + "parameters": [ + { + "type": "string", + "description": "Filter by content type (maven, python, npm)", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by security level (validated, remediated)", + "name": "security_level", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellPackageCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, "/module_streams/search": { "post": { "description": "List modules and their streams for repositories", @@ -4893,6 +5121,150 @@ const docTemplate = `{ } } }, + "api.LightwellAdvisoryCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellAdvisoryResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellAdvisoryResponse": { + "type": "object", + "properties": { + "advisory_id": { + "type": "string" + }, + "details": { + "type": "string" + }, + "fixed_versions": { + "type": "array", + "items": { + "type": "string" + } + }, + "package_name": { + "type": "string" + }, + "reference_urls": { + "type": "array", + "items": { + "type": "string" + } + }, + "repository": { + "type": "string" + }, + "severity": { + "type": "string" + } + } + }, + "api.LightwellPackageCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellPackageResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellPackageResponse": { + "type": "object", + "properties": { + "content_type": { + "type": "string" + }, + "group": { + "type": "string" + }, + "latest_releases": { + "type": "array", + "items": { + "$ref": "#/definitions/api.ReleaseInfo" + } + }, + "name": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "versions": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "api.LightwellPackageVersionCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellPackageVersionResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellPackageVersionResponse": { + "type": "object", + "properties": { + "content_type": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "group": { + "type": "string" + }, + "name": { + "type": "string" + }, + "release": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, "api.Links": { "type": "object", "properties": { @@ -5660,6 +6032,11 @@ const docTemplate = `{ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "type": "integer", + "readOnly": true + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "type": "boolean", @@ -5670,6 +6047,11 @@ const docTemplate = `{ "type": "string", "readOnly": true }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "type": "integer", + "readOnly": true + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "type": "string", @@ -5696,6 +6078,11 @@ const docTemplate = `{ "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "type": "integer", + "readOnly": true + }, "warnings": { "description": "Warnings to alert user of mismatched fields if there is an existing repo with the same URL", "type": "array", @@ -5990,6 +6377,11 @@ const docTemplate = `{ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "type": "integer", + "readOnly": true + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "type": "boolean", @@ -6000,6 +6392,11 @@ const docTemplate = `{ "type": "string", "readOnly": true }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "type": "integer", + "readOnly": true + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "type": "string", @@ -6025,6 +6422,11 @@ const docTemplate = `{ "version_count": { "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" + }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "type": "integer", + "readOnly": true } } }, diff --git a/api/openapi.json b/api/openapi.json index 72edbafd7..bec7701f6 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -313,6 +313,150 @@ }, "type": "object" }, + "api.LightwellAdvisoryCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellAdvisoryResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellAdvisoryResponse": { + "properties": { + "advisory_id": { + "type": "string" + }, + "details": { + "type": "string" + }, + "fixed_versions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "package_name": { + "type": "string" + }, + "reference_urls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repository": { + "type": "string" + }, + "severity": { + "type": "string" + } + }, + "type": "object" + }, + "api.LightwellPackageCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellPackageResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellPackageResponse": { + "properties": { + "content_type": { + "type": "string" + }, + "group": { + "type": "string" + }, + "latest_releases": { + "items": { + "$ref": "#/components/schemas/api.ReleaseInfo" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "versions": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "api.LightwellPackageVersionCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellPackageVersionResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellPackageVersionResponse": { + "properties": { + "content_type": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "group": { + "type": "string" + }, + "name": { + "type": "string" + }, + "release": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, "api.Links": { "properties": { "first": { @@ -1079,6 +1223,11 @@ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "readOnly": true, + "type": "integer" + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "readOnly": true, @@ -1089,6 +1238,11 @@ "readOnly": true, "type": "string" }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "readOnly": true, + "type": "integer" + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "readOnly": true, @@ -1115,6 +1269,11 @@ "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "readOnly": true, + "type": "integer" + }, "warnings": { "description": "Warnings to alert user of mismatched fields if there is an existing repo with the same URL", "items": { @@ -1409,6 +1568,11 @@ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "readOnly": true, + "type": "integer" + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "readOnly": true, @@ -1419,6 +1583,11 @@ "readOnly": true, "type": "string" }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "readOnly": true, + "type": "integer" + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "readOnly": true, @@ -1444,6 +1613,11 @@ "version_count": { "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" + }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "readOnly": true, + "type": "integer" } }, "type": "object" @@ -2961,6 +3135,290 @@ ] } }, + "/lightwell/advisories": { + "get": { + "description": "List security advisories for Lightwell remediated packages with optional filtering.", + "operationId": "listLightwellAdvisories", + "parameters": [ + { + "description": "Filter by repository UUID", + "in": "query", + "name": "repository_uuid", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "package_name", + "schema": { + "type": "string" + } + }, + { + "description": "Minimum severity level (low, moderate, important, critical)", + "in": "query", + "name": "severity_min", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by CVE ID (exact match)", + "in": "query", + "name": "cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellAdvisoryCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Advisories", + "tags": [ + "lightwell" + ] + } + }, + "/lightwell/package_versions": { + "get": { + "description": "List individual package versions aggregated across all Lightwell repositories, with optional CVE-based filtering.", + "operationId": "listLightwellPackageVersions", + "parameters": [ + { + "description": "Filter by content type (maven, python, npm)", + "in": "query", + "name": "type", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "name", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by security level (validated, remediated)", + "in": "query", + "name": "security_level", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by repository name", + "in": "query", + "name": "repository", + "schema": { + "type": "string" + } + }, + { + "description": "Show only packages that resolve this CVE", + "in": "query", + "name": "resolves_cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Show only packages vulnerable to this CVE", + "in": "query", + "name": "vulnerable_to_cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellPackageVersionCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Package Versions (cross-repo)", + "tags": [ + "lightwell" + ] + } + }, + "/lightwell/packages": { + "get": { + "description": "List packages aggregated across all Lightwell repositories, with optional filtering by content type, name, and security level.", + "operationId": "listLightwellPackages", + "parameters": [ + { + "description": "Filter by content type (maven, python, npm)", + "in": "query", + "name": "type", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "name", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by security level (validated, remediated)", + "in": "query", + "name": "security_level", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellPackageCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Packages (cross-repo)", + "tags": [ + "lightwell" + ] + } + }, "/module_streams/search": { "post": { "description": "List modules and their streams for repositories", diff --git a/db/migrations.latest b/db/migrations.latest index d81f8babb..a9aea7d61 100644 --- a/db/migrations.latest +++ b/db/migrations.latest @@ -1 +1 @@ -20260814120000 +20260818120000 diff --git a/db/migrations/20260818120000_add_lightwell_advisory_severity_order.down.sql b/db/migrations/20260818120000_add_lightwell_advisory_severity_order.down.sql new file mode 100644 index 000000000..53b3f3983 --- /dev/null +++ b/db/migrations/20260818120000_add_lightwell_advisory_severity_order.down.sql @@ -0,0 +1,7 @@ +BEGIN; + +DROP INDEX IF EXISTS idx_lightwell_advisories_package_name; +DROP INDEX IF EXISTS idx_lightwell_advisories_severity_order; +ALTER TABLE lightwell_advisories DROP COLUMN IF EXISTS severity_order; + +COMMIT; diff --git a/db/migrations/20260818120000_add_lightwell_advisory_severity_order.up.sql b/db/migrations/20260818120000_add_lightwell_advisory_severity_order.up.sql new file mode 100644 index 000000000..544d380e0 --- /dev/null +++ b/db/migrations/20260818120000_add_lightwell_advisory_severity_order.up.sql @@ -0,0 +1,20 @@ +BEGIN; + +ALTER TABLE lightwell_advisories + ADD COLUMN IF NOT EXISTS severity_order SMALLINT NOT NULL DEFAULT 0; + +UPDATE lightwell_advisories SET severity_order = CASE + WHEN severity = 'critical' THEN 4 + WHEN severity = 'important' THEN 3 + WHEN severity = 'moderate' THEN 2 + WHEN severity = 'low' THEN 1 + ELSE 0 +END; + +CREATE INDEX IF NOT EXISTS idx_lightwell_advisories_severity_order + ON lightwell_advisories (severity_order); + +CREATE INDEX IF NOT EXISTS idx_lightwell_advisories_package_name + ON lightwell_advisories (package_name); + +COMMIT; diff --git a/pkg/api/lightwell_advisories.go b/pkg/api/lightwell_advisories.go new file mode 100644 index 000000000..cf8fe846e --- /dev/null +++ b/pkg/api/lightwell_advisories.go @@ -0,0 +1,29 @@ +package api + +type LightwellAdvisoryResponse struct { + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + Details string `json:"details"` + ReferenceURLs []string `json:"reference_urls"` + PackageName string `json:"package_name"` + FixedVersions []string `json:"fixed_versions"` + Repository string `json:"repository"` +} + +type LightwellAdvisoryCollectionResponse struct { + Data []LightwellAdvisoryResponse `json:"data"` + Meta ResponseMetadata `json:"meta"` + Links Links `json:"links"` +} + +func (r *LightwellAdvisoryCollectionResponse) SetMetadata(meta ResponseMetadata, links Links) { + r.Meta = meta + r.Links = links +} + +type LightwellAdvisoryFilterData struct { + RepositoryUUID string `query:"repository_uuid"` + PackageName string `query:"package_name"` + SeverityMin string `query:"severity_min"` + CveID string `query:"cve_id"` +} diff --git a/pkg/api/lightwell_packages.go b/pkg/api/lightwell_packages.go new file mode 100644 index 000000000..cc13d4c81 --- /dev/null +++ b/pkg/api/lightwell_packages.go @@ -0,0 +1,65 @@ +package api + +// LightwellPackageResponse represents a package found across Lightwell repositories. +type LightwellPackageResponse struct { + Name string `json:"name"` + Group string `json:"group,omitempty"` + ContentType string `json:"content_type"` + Repository string `json:"repository"` + RepositoryUUID string `json:"repository_uuid"` + Versions []string `json:"versions"` + LatestReleases []ReleaseInfo `json:"latest_releases"` +} + +// LightwellPackageCollectionResponse is a paginated collection of cross-repo packages. +type LightwellPackageCollectionResponse struct { + Data []LightwellPackageResponse `json:"data"` + Meta ResponseMetadata `json:"meta"` + Links Links `json:"links"` +} + +func (r *LightwellPackageCollectionResponse) SetMetadata(meta ResponseMetadata, links Links) { + r.Meta = meta + r.Links = links +} + +// LightwellPackageVersionResponse represents a single package version across Lightwell repositories. +type LightwellPackageVersionResponse struct { + Name string `json:"name"` + Group string `json:"group,omitempty"` + Version string `json:"version"` + ContentType string `json:"content_type"` + Repository string `json:"repository"` + RepositoryUUID string `json:"repository_uuid"` + Release string `json:"release,omitempty"` + CreatedAt string `json:"created_at,omitempty"` +} + +// LightwellPackageVersionCollectionResponse is a paginated collection of cross-repo package versions. +type LightwellPackageVersionCollectionResponse struct { + Data []LightwellPackageVersionResponse `json:"data"` + Meta ResponseMetadata `json:"meta"` + Links Links `json:"links"` +} + +func (r *LightwellPackageVersionCollectionResponse) SetMetadata(meta ResponseMetadata, links Links) { + r.Meta = meta + r.Links = links +} + +// LightwellPackageFilterData holds query-parameter filters for the cross-repo packages endpoint. +type LightwellPackageFilterData struct { + ContentType string `query:"type"` + Name string `query:"name"` + SecurityLevel string `query:"security_level"` +} + +// LightwellPackageVersionFilterData holds query-parameter filters for the cross-repo package_versions endpoint. +type LightwellPackageVersionFilterData struct { + ContentType string `query:"type"` + Name string `query:"name"` + SecurityLevel string `query:"security_level"` + Repository string `query:"repository"` + ResolvesCveID string `query:"resolves_cve_id"` + VulnerableToCveID string `query:"vulnerable_to_cve_id"` +} diff --git a/pkg/api/repositories.go b/pkg/api/repositories.go index 90e25f87c..059ad93a8 100644 --- a/pkg/api/repositories.go +++ b/pkg/api/repositories.go @@ -45,6 +45,9 @@ type RepositoryResponse struct { SecurityLevel string `json:"security_level,omitempty" readonly:"true"` // Security level of the repository (e.g. validated, remediated) PublishedDistURL string `json:"published_distribution_url,omitempty" readonly:"true"` // Published distribution URL from Pulp PublishedDistBasePath string `json:"-"` // Published dist base path from Pulp + PackagesCount *int `json:"packages_count,omitempty" readonly:"true"` // Lightwell: total distinct packages + VersionsCount *int `json:"versions_count,omitempty" readonly:"true"` // Lightwell: total distinct versions + RemediationsCount *int `json:"remediations_count,omitempty" readonly:"true"` // Lightwell: total security advisories } // RepositoryRequest holds data received from request to create repository diff --git a/pkg/handler/api.go b/pkg/handler/api.go index ade9322d9..ba8c05d6e 100644 --- a/pkg/handler/api.go +++ b/pkg/handler/api.go @@ -18,8 +18,10 @@ import ( "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" "github.com/content-services/content-sources-backend/pkg/db" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/content-services/content-sources-backend/pkg/tasks/client" "github.com/content-services/content-sources-backend/pkg/tasks/queue" + "github.com/jackc/pgx/v5/pgxpool" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" ) @@ -73,12 +75,21 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { } ch := cache.Initialize() + pgxPool, err := pgxpool.New(ctx, db.GetUrl()) + if err != nil { + log.Warn().Err(err).Msg("failed to create pgx pool for lightwell store; advisory endpoints disabled") + } + var lightwellQuerier store.Querier + if pgxPool != nil { + lightwellQuerier = store.New(pgxPool) + } + for i := 0; i < len(paths); i++ { group := engine.Group(paths[i]) group.GET("/openapi.json", openapi) daoReg := dao.GetDaoRegistry(db.DB) - RegisterRepositoryRoutes(group, daoReg, &taskClient, &fsClient) + RegisterRepositoryRoutes(group, daoReg, &taskClient, &fsClient, lightwellQuerier) RegisterRepositoryParameterRoutes(group, daoReg, &fsClient) RegisterRpmRoutes(group, daoReg) RegisterPopularRepositoriesRoutes(group, daoReg) @@ -98,6 +109,10 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { RegisterUserPreferencesRoutes(group, daoReg) RegisterCoverageReportRoutes(group) + if lightwellQuerier != nil { + RegisterLightwellAdvisoryRoutes(group, lightwellQuerier) + } + // Register package and build routes if tang client is available pulpClient := pulp_client.GetPulpClientWithDomain("") if config.Tang == nil { @@ -108,6 +123,9 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { } if config.Tang != nil { RegisterPackageRoutes(group, daoReg, *config.Tang, pulpClient) + if lightwellQuerier != nil { + RegisterLightwellPackageRoutes(group, lightwellQuerier, daoReg, *config.Tang, pulpClient) + } } } diff --git a/pkg/handler/lightwell_advisories.go b/pkg/handler/lightwell_advisories.go new file mode 100644 index 000000000..99ed6d14a --- /dev/null +++ b/pkg/handler/lightwell_advisories.go @@ -0,0 +1,150 @@ +package handler + +import ( + "net/http" + + "github.com/content-services/content-sources-backend/pkg/api" + ce "github.com/content-services/content-sources-backend/pkg/errors" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" + "github.com/content-services/content-sources-backend/pkg/rbac" + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" + "github.com/labstack/echo/v4" +) + +type LightwellAdvisoryHandler struct { + Store store.Querier +} + +func RegisterLightwellAdvisoryRoutes(engine *echo.Group, querier store.Querier) { + h := LightwellAdvisoryHandler{Store: querier} + addRepoRoute(engine, http.MethodGet, "/lightwell/advisories", h.list, rbac.RbacVerbRead) +} + +// listLightwellAdvisories godoc +// @Summary List Lightwell Advisories +// @ID listLightwellAdvisories +// @Description List security advisories for Lightwell remediated packages with optional filtering. +// @Tags lightwell +// @Accept json +// @Produce json +// @Param repository_uuid query string false "Filter by repository UUID" +// @Param package_name query string false "Filter by package name (substring match)" +// @Param severity_min query string false "Minimum severity level (low, moderate, important, critical)" +// @Param cve_id query string false "Filter by CVE ID (exact match)" +// @Param limit query int false "Limit of results to return" +// @Param offset query int false "Offset into results" +// @Success 200 {object} api.LightwellAdvisoryCollectionResponse +// @Failure 400 {object} ce.ErrorResponse +// @Failure 500 {object} ce.ErrorResponse +// @Router /lightwell/advisories [get] +func (h *LightwellAdvisoryHandler) list(c echo.Context) error { + page := ParsePagination(c) + filters := parseLightwellAdvisoryFilters(c) + + severityMin, err := parseSeverityMin(filters.SeverityMin) + if err != nil { + return ce.NewErrorResponse(http.StatusBadRequest, "Invalid severity_min", err.Error()) + } + + var repoUUID pgtype.UUID + if filters.RepositoryUUID != "" { + parsed, err := uuid.Parse(filters.RepositoryUUID) + if err != nil { + return ce.NewErrorResponse(http.StatusBadRequest, "Invalid repository_uuid", err.Error()) + } + repoUUID = pgtype.UUID{Bytes: parsed, Valid: true} + } + + var packageName *string + if filters.PackageName != "" { + packageName = &filters.PackageName + } + + var cveID *string + if filters.CveID != "" { + cveID = &filters.CveID + } + + rows, err := h.Store.ListAdvisories(c.Request().Context(), store.ListAdvisoriesParams{ + RepositoryConfigUuid: repoUUID, + PackageName: packageName, + SeverityMin: severityMin, + CveID: cveID, + PageOffset: int32(page.Offset), //nolint:gosec // bounded by ParsePagination + PageLimit: int32(page.Limit), //nolint:gosec // bounded by MaxLimit (200) + }) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing advisories", err.Error()) + } + + var totalCount int64 + if len(rows) > 0 { + totalCount = rows[0].TotalCount + } + + resp := mapAdvisoryRowsToResponse(rows) + collResp := setCollectionResponseMetadata(&resp, c, totalCount) + return c.JSON(http.StatusOK, collResp) +} + +func mapAdvisoryRowsToResponse(rows []store.ListAdvisoriesRow) api.LightwellAdvisoryCollectionResponse { + data := make([]api.LightwellAdvisoryResponse, 0, len(rows)) + for _, row := range rows { + refURLs := row.ReferenceUrls + if refURLs == nil { + refURLs = []string{} + } + fixedVersions := row.FixedVersions + if fixedVersions == nil { + fixedVersions = []string{} + } + data = append(data, api.LightwellAdvisoryResponse{ + AdvisoryID: row.AdvisoryID, + Severity: row.Severity, + Details: row.Details, + ReferenceURLs: refURLs, + PackageName: row.PackageName, + FixedVersions: fixedVersions, + Repository: row.RepoName, + }) + } + return api.LightwellAdvisoryCollectionResponse{Data: data} +} + +func parseLightwellAdvisoryFilters(c echo.Context) api.LightwellAdvisoryFilterData { + var filters api.LightwellAdvisoryFilterData + _ = echo.QueryParamsBinder(c). + String("repository_uuid", &filters.RepositoryUUID). + String("package_name", &filters.PackageName). + String("severity_min", &filters.SeverityMin). + String("cve_id", &filters.CveID). + BindError() + return filters +} + +var severityMap = map[string]int16{ + "low": 1, + "moderate": 2, + "important": 3, + "critical": 4, +} + +func parseSeverityMin(s string) (pgtype.Int2, error) { + if s == "" { + return pgtype.Int2{}, nil + } + val, ok := severityMap[s] + if !ok { + return pgtype.Int2{}, &invalidSeverityError{severity: s} + } + return pgtype.Int2{Int16: val, Valid: true}, nil +} + +type invalidSeverityError struct { + severity string +} + +func (e *invalidSeverityError) Error() string { + return "invalid severity: " + e.severity + " (must be one of: low, moderate, important, critical)" +} diff --git a/pkg/handler/lightwell_advisories_test.go b/pkg/handler/lightwell_advisories_test.go new file mode 100644 index 000000000..e0775cd54 --- /dev/null +++ b/pkg/handler/lightwell_advisories_test.go @@ -0,0 +1,237 @@ +package handler + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/config" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" + "github.com/content-services/content-sources-backend/pkg/middleware" + test_handler "github.com/content-services/content-sources-backend/pkg/test/handler" + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" + "github.com/labstack/echo/v4" + echo_middleware "github.com/labstack/echo/v4/middleware" + "github.com/redhatinsights/platform-go-middlewares/v2/identity" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type LightwellAdvisorySuite struct { + suite.Suite + echo *echo.Echo + mockQuerier *MockQuerier +} + +func TestLightwellAdvisorySuite(t *testing.T) { + suite.Run(t, new(LightwellAdvisorySuite)) +} + +func (s *LightwellAdvisorySuite) SetupTest() { + s.echo = echo.New() + s.echo.Use(echo_middleware.RequestIDWithConfig(echo_middleware.RequestIDConfig{ + TargetHeader: "x-rh-insights-request-id", + })) + s.echo.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) + s.mockQuerier = &MockQuerier{} +} + +func (s *LightwellAdvisorySuite) TearDownTest() { + require.NoError(s.T(), s.echo.Shutdown(context.Background())) +} + +func (s *LightwellAdvisorySuite) serveRouter(req *http.Request) (int, []byte, error) { + router := echo.New() + router.HTTPErrorHandler = config.CustomHTTPErrorHandler + router.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) + pathPrefix := router.Group(api.FullRootPath()) + RegisterLightwellAdvisoryRoutes(pathPrefix, s.mockQuerier) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + + response := rr.Result() + defer response.Body.Close() + + body, err := io.ReadAll(response.Body) + return response.StatusCode, body, err +} + +func (s *LightwellAdvisorySuite) TestListAdvisories() { + t := s.T() + + repoUUID := uuid.New() + rows := []store.ListAdvisoriesRow{ + { + Uuid: uuid.New(), + AdvisoryID: "CVE-2024-1234", + Severity: "critical", + SeverityOrder: 4, + Details: "Remote code execution vulnerability", + ReferenceUrls: []string{"https://access.redhat.com/security/cve/CVE-2024-1234"}, + PackageName: "spring-core", + FixedVersions: []string{"5.3.18.rhlw-00003"}, + RepoName: "lightwell/java/remediated", + RepositoryConfigurationUuid: repoUUID, + CreatedAt: time.Now(), + TotalCount: 1, + }, + } + + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { + return arg.PageLimit == int32(DefaultLimit) && arg.PageOffset == 0 + })).Return(rows, nil) + + path := fmt.Sprintf("%s/lightwell/advisories", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "CVE-2024-1234", resp.Data[0].AdvisoryID) + assert.Equal(t, "critical", resp.Data[0].Severity) + assert.Equal(t, "spring-core", resp.Data[0].PackageName) + assert.Equal(t, []string{"5.3.18.rhlw-00003"}, resp.Data[0].FixedVersions) + assert.Equal(t, "lightwell/java/remediated", resp.Data[0].Repository) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesWithFilters() { + t := s.T() + + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { + return arg.PackageName != nil && *arg.PackageName == "spring" && + arg.SeverityMin == pgtype.Int2{Int16: 3, Valid: true} && + arg.PageLimit == 10 && arg.PageOffset == 5 + })).Return([]store.ListAdvisoriesRow{}, nil) + + path := fmt.Sprintf("%s/lightwell/advisories?package_name=spring&severity_min=important&limit=10&offset=5", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(0), resp.Meta.Count) + assert.Empty(t, resp.Data) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesInvalidSeverity() { + t := s.T() + + path := fmt.Sprintf("%s/lightwell/advisories?severity_min=bogus", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, code) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesInvalidRepoUUID() { + t := s.T() + + path := fmt.Sprintf("%s/lightwell/advisories?repository_uuid=not-a-uuid", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, code) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesEmptyResult() { + t := s.T() + + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.Anything).Return([]store.ListAdvisoriesRow{}, nil) + + path := fmt.Sprintf("%s/lightwell/advisories", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(0), resp.Meta.Count) + assert.NotNil(t, resp.Data) + assert.Empty(t, resp.Data) +} + +// MockQuerier implements store.Querier for testing +type MockQuerier struct { + mock.Mock +} + +func (m *MockQuerier) ListAdvisories(ctx context.Context, arg store.ListAdvisoriesParams) ([]store.ListAdvisoriesRow, error) { + args := m.Called(ctx, arg) + val, _ := args.Get(0).([]store.ListAdvisoriesRow) + return val, args.Error(1) +} + +func (m *MockQuerier) CountAdvisoriesByRepo(ctx context.Context, repositoryConfigUuid uuid.UUID) (int64, error) { + args := m.Called(ctx, repositoryConfigUuid) + val, _ := args.Get(0).(int64) + return val, args.Error(1) +} + +func (m *MockQuerier) ListAdvisoriesByPackage(ctx context.Context, packageName string) ([]store.ListAdvisoriesByPackageRow, error) { + args := m.Called(ctx, packageName) + val, _ := args.Get(0).([]store.ListAdvisoriesByPackageRow) + return val, args.Error(1) +} + +func (m *MockQuerier) ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]store.ListAdvisoriesByCveIDRow, error) { + args := m.Called(ctx, cveID) + val, _ := args.Get(0).([]store.ListAdvisoriesByCveIDRow) + return val, args.Error(1) +} + +func (m *MockQuerier) CountAggregates(ctx context.Context, arg store.CountAggregatesParams) (store.CountAggregatesRow, error) { + args := m.Called(ctx, arg) + val, _ := args.Get(0).(store.CountAggregatesRow) + return val, args.Error(1) +} + +func (m *MockQuerier) CountByStage(ctx context.Context, arg store.CountByStageParams) ([]store.CountByStageRow, error) { + args := m.Called(ctx, arg) + val, _ := args.Get(0).([]store.CountByStageRow) + return val, args.Error(1) +} + +func (m *MockQuerier) ListCustomerIds(ctx context.Context) ([]string, error) { + args := m.Called(ctx) + val, _ := args.Get(0).([]string) + return val, args.Error(1) +} + +func (m *MockQuerier) ListVulnerabilities(ctx context.Context, arg store.ListVulnerabilitiesParams) ([]store.LightwellVulnerability, error) { + args := m.Called(ctx, arg) + val, _ := args.Get(0).([]store.LightwellVulnerability) + return val, args.Error(1) +} diff --git a/pkg/handler/lightwell_packages.go b/pkg/handler/lightwell_packages.go new file mode 100644 index 000000000..901137d69 --- /dev/null +++ b/pkg/handler/lightwell_packages.go @@ -0,0 +1,638 @@ +package handler + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + "sync" + + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" + "github.com/content-services/content-sources-backend/pkg/config" + "github.com/content-services/content-sources-backend/pkg/dao" + ce "github.com/content-services/content-sources-backend/pkg/errors" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" + "github.com/content-services/content-sources-backend/pkg/rbac" + "github.com/content-services/tang/pkg/tangy" + "github.com/labstack/echo/v4" + "github.com/rs/zerolog/log" +) + +type LightwellPackagesHandler struct { + Store store.Querier + DaoRegistry dao.DaoRegistry + TangClient tangy.Tangy + PulpClient pulp_client.PulpClient +} + +func RegisterLightwellPackageRoutes(engine *echo.Group, querier store.Querier, daoReg *dao.DaoRegistry, tangClient tangy.Tangy, pulpClient pulp_client.PulpClient) { + h := LightwellPackagesHandler{ + Store: querier, + DaoRegistry: *daoReg, + TangClient: tangClient, + PulpClient: pulpClient, + } + addRepoRoute(engine, http.MethodGet, "/lightwell/packages", h.listPackages, rbac.RbacVerbRead) + addRepoRoute(engine, http.MethodGet, "/lightwell/package_versions", h.listPackageVersions, rbac.RbacVerbRead) +} + +// listLightwellPackages godoc +// @Summary List Lightwell Packages (cross-repo) +// @ID listLightwellPackages +// @Description List packages aggregated across all Lightwell repositories, with optional filtering by content type, name, and security level. +// @Tags lightwell +// @Accept json +// @Produce json +// @Param type query string false "Filter by content type (maven, python, npm)" +// @Param name query string false "Filter by package name (substring match)" +// @Param security_level query string false "Filter by security level (validated, remediated)" +// @Param limit query int false "Limit of results to return" +// @Param offset query int false "Offset into results" +// @Success 200 {object} api.LightwellPackageCollectionResponse +// @Failure 400 {object} ce.ErrorResponse +// @Failure 500 {object} ce.ErrorResponse +// @Router /lightwell/packages [get] +func (h *LightwellPackagesHandler) listPackages(c echo.Context) error { + page := ParsePagination(c) + filters := parseLightwellPackageFilters(c) + + if err := validateContentType(filters.ContentType); err != nil { + return ce.NewErrorResponse(http.StatusBadRequest, "Invalid type filter", err.Error()) + } + + repos, err := h.fetchLightwellRepos(c, filters.ContentType, filters.SecurityLevel) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing Lightwell repositories", err.Error()) + } + + items, err := h.aggregatePackages(c.Request().Context(), repos, filters.Name) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error retrieving packages", err.Error()) + } + + totalCount := int64(len(items)) + paged := paginatePackages(items, page.Offset, page.Limit) + resp := api.LightwellPackageCollectionResponse{Data: paged} + collResp := setCollectionResponseMetadata(&resp, c, totalCount) + return c.JSON(http.StatusOK, collResp) +} + +// listLightwellPackageVersions godoc +// @Summary List Lightwell Package Versions (cross-repo) +// @ID listLightwellPackageVersions +// @Description List individual package versions aggregated across all Lightwell repositories, with optional CVE-based filtering. +// @Tags lightwell +// @Accept json +// @Produce json +// @Param type query string false "Filter by content type (maven, python, npm)" +// @Param name query string false "Filter by package name (substring match)" +// @Param security_level query string false "Filter by security level (validated, remediated)" +// @Param repository query string false "Filter by repository name" +// @Param resolves_cve_id query string false "Show only packages that resolve this CVE" +// @Param vulnerable_to_cve_id query string false "Show only packages vulnerable to this CVE" +// @Param limit query int false "Limit of results to return" +// @Param offset query int false "Offset into results" +// @Success 200 {object} api.LightwellPackageVersionCollectionResponse +// @Failure 400 {object} ce.ErrorResponse +// @Failure 500 {object} ce.ErrorResponse +// @Router /lightwell/package_versions [get] +func (h *LightwellPackagesHandler) listPackageVersions(c echo.Context) error { + page := ParsePagination(c) + filters := parseLightwellPackageVersionFilters(c) + + if err := validateContentType(filters.ContentType); err != nil { + return ce.NewErrorResponse(http.StatusBadRequest, "Invalid type filter", err.Error()) + } + + repos, err := h.fetchLightwellRepos(c, filters.ContentType, filters.SecurityLevel) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing Lightwell repositories", err.Error()) + } + if filters.Repository != "" { + repos = filterReposByName(repos, filters.Repository) + } + + items, err := h.aggregatePackageVersions(c.Request().Context(), repos, filters.Name) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error retrieving package versions", err.Error()) + } + + if filters.ResolvesCveID != "" { + items, err = h.filterVersionsByResolvingCve(c.Request().Context(), items, filters.ResolvesCveID) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error filtering by CVE", err.Error()) + } + } + if filters.VulnerableToCveID != "" { + items, err = h.filterVersionsByVulnerableCve(c.Request().Context(), items, filters.VulnerableToCveID) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error filtering by CVE", err.Error()) + } + } + + totalCount := int64(len(items)) + paged := paginateVersions(items, page.Offset, page.Limit) + resp := api.LightwellPackageVersionCollectionResponse{Data: paged} + collResp := setCollectionResponseMetadata(&resp, c, totalCount) + return c.JSON(http.StatusOK, collResp) +} + +// fetchLightwellRepos returns Lightwell repos for the caller's org, optionally +// filtered by content type and security level. +func (h *LightwellPackagesHandler) fetchLightwellRepos(c echo.Context, contentType, securityLevel string) ([]api.RepositoryResponse, error) { + _, orgID := getAccountIdOrgId(c) + ctx := c.Request().Context() + + filter := api.FilterData{Origin: config.OriginLightwell} + if contentType != "" { + filter.ContentType = contentType + } + + repos, _, err := h.DaoRegistry.RepositoryConfig.List(ctx, orgID, api.PaginationData{Limit: MaxLimit}, filter) + if err != nil { + return nil, err + } + + if securityLevel == "" { + return repos.Data, nil + } + filtered := make([]api.RepositoryResponse, 0, len(repos.Data)) + for _, r := range repos.Data { + if strings.EqualFold(r.SecurityLevel, securityLevel) { + filtered = append(filtered, r) + } + } + return filtered, nil +} + +type repoPackageResult struct { + repo api.RepositoryResponse + pkgs []api.LightwellPackageResponse + err error +} + +// aggregatePackages queries Tang for each repo in parallel and merges results. +func (h *LightwellPackagesHandler) aggregatePackages(ctx context.Context, repos []api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageResponse, error) { + results := make([]repoPackageResult, len(repos)) + var wg sync.WaitGroup + + for i, repo := range repos { + wg.Add(1) + go func(idx int, r api.RepositoryResponse) { + defer wg.Done() + pkgs, err := h.fetchPackagesFromRepo(ctx, r, nameSearch) + results[idx] = repoPackageResult{repo: r, pkgs: pkgs, err: err} + }(i, repo) + } + wg.Wait() + + var combined []api.LightwellPackageResponse + var errs []error + for _, res := range results { + if res.err != nil { + errs = append(errs, fmt.Errorf("repo %s: %w", res.repo.Name, res.err)) + continue + } + combined = append(combined, res.pkgs...) + } + + if len(errs) > 0 && len(combined) == 0 { + return nil, errors.Join(errs...) + } + if len(errs) > 0 { + log.Warn().Errs("errors", errs).Msg("partial failure fetching cross-repo packages") + } + + return combined, nil +} + +func (h *LightwellPackagesHandler) fetchPackagesFromRepo(ctx context.Context, repo api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageResponse, error) { + if repo.PublishedDistBasePath == "" { + return nil, nil + } + + repositoryHref, err := h.resolveRepositoryHref(ctx, repo) + if err != nil { + return nil, err + } + + // Fetch all packages from this repo (no server-side pagination — small datasets) + pageOpts := tangy.PageOptions{Offset: 0, Limit: MaxLimit} + + switch repo.ContentType { + case config.ContentTypeMaven: + tangResp, err := h.TangClient.MavenPackageList(ctx, repositoryHref, tangy.MavenPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return mapMavenToLightwellPackages(tangResp, repo), nil + + case config.ContentTypePython: + tangResp, err := h.TangClient.PythonPackageList(ctx, repositoryHref, tangy.PythonPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return mapPythonToLightwellPackages(tangResp, repo), nil + + case config.ContentTypeNpm: + tangResp, err := h.TangClient.NpmPackageList(ctx, repositoryHref, tangy.NpmPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return mapNpmToLightwellPackages(tangResp, repo), nil + + default: + return nil, nil + } +} + +type repoVersionResult struct { + repo api.RepositoryResponse + versions []api.LightwellPackageVersionResponse + err error +} + +// aggregatePackageVersions queries Tang for each repo in parallel and expands +// every package into individual version items. +func (h *LightwellPackagesHandler) aggregatePackageVersions(ctx context.Context, repos []api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageVersionResponse, error) { + results := make([]repoVersionResult, len(repos)) + var wg sync.WaitGroup + + for i, repo := range repos { + wg.Add(1) + go func(idx int, r api.RepositoryResponse) { + defer wg.Done() + versions, err := h.fetchVersionsFromRepo(ctx, r, nameSearch) + results[idx] = repoVersionResult{repo: r, versions: versions, err: err} + }(i, repo) + } + wg.Wait() + + var combined []api.LightwellPackageVersionResponse + var errs []error + for _, res := range results { + if res.err != nil { + errs = append(errs, fmt.Errorf("repo %s: %w", res.repo.Name, res.err)) + continue + } + combined = append(combined, res.versions...) + } + + if len(errs) > 0 && len(combined) == 0 { + return nil, errors.Join(errs...) + } + if len(errs) > 0 { + log.Warn().Errs("errors", errs).Msg("partial failure fetching cross-repo versions") + } + + return combined, nil +} + +func (h *LightwellPackagesHandler) fetchVersionsFromRepo(ctx context.Context, repo api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageVersionResponse, error) { + if repo.PublishedDistBasePath == "" { + return nil, nil + } + + repositoryHref, err := h.resolveRepositoryHref(ctx, repo) + if err != nil { + return nil, err + } + + pageOpts := tangy.PageOptions{Offset: 0, Limit: MaxLimit} + + switch repo.ContentType { + case config.ContentTypeMaven: + tangResp, err := h.TangClient.MavenPackageList(ctx, repositoryHref, tangy.MavenPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return expandMavenVersions(tangResp, repo), nil + + case config.ContentTypePython: + tangResp, err := h.TangClient.PythonPackageList(ctx, repositoryHref, tangy.PythonPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return expandPythonVersions(tangResp, repo), nil + + case config.ContentTypeNpm: + tangResp, err := h.TangClient.NpmPackageList(ctx, repositoryHref, tangy.NpmPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return expandNpmVersions(tangResp, repo), nil + + default: + return nil, nil + } +} + +func (h *LightwellPackagesHandler) resolveRepositoryHref(ctx context.Context, repo api.RepositoryResponse) (string, error) { + domainName, err := h.DaoRegistry.Domain.FetchOrCreateDomain(ctx, repo.OrgID) + if err != nil { + return "", err + } + pulpClient := h.PulpClient.WithDomain(domainName) + href, err := pulpClient.ResolveRepositoryFromBasePath(ctx, repo.PublishedDistBasePath) + if err != nil { + return "", fmt.Errorf("repo %s: %w", repo.UUID, err) + } + if href == nil { + return "", fmt.Errorf("repo %s: distribution not found", repo.UUID) + } + return *href, nil +} + +// filterVersionsByResolvingCve keeps only versions that fix the given CVE. +func (h *LightwellPackagesHandler) filterVersionsByResolvingCve(ctx context.Context, items []api.LightwellPackageVersionResponse, cveID string) ([]api.LightwellPackageVersionResponse, error) { + advisories, err := h.Store.ListAdvisoriesByCveID(ctx, cveID) + if err != nil { + return nil, err + } + + fixedSet := make(map[string]map[string]bool) // package_name -> set of fixed versions + for _, adv := range advisories { + if fixedSet[adv.PackageName] == nil { + fixedSet[adv.PackageName] = make(map[string]bool) + } + for _, v := range adv.FixedVersions { + fixedSet[adv.PackageName][v] = true + } + } + + var result []api.LightwellPackageVersionResponse + for _, item := range items { + if versions, ok := fixedSet[item.Name]; ok && versions[item.Version] { + result = append(result, item) + } + } + return result, nil +} + +// filterVersionsByVulnerableCve keeps only versions of packages affected by +// the given CVE that are NOT in the fixed-versions list. +func (h *LightwellPackagesHandler) filterVersionsByVulnerableCve(ctx context.Context, items []api.LightwellPackageVersionResponse, cveID string) ([]api.LightwellPackageVersionResponse, error) { + advisories, err := h.Store.ListAdvisoriesByCveID(ctx, cveID) + if err != nil { + return nil, err + } + + affectedPackages := make(map[string]bool) + fixedSet := make(map[string]map[string]bool) + for _, adv := range advisories { + affectedPackages[adv.PackageName] = true + if fixedSet[adv.PackageName] == nil { + fixedSet[adv.PackageName] = make(map[string]bool) + } + for _, v := range adv.FixedVersions { + fixedSet[adv.PackageName][v] = true + } + } + + var result []api.LightwellPackageVersionResponse + for _, item := range items { + if affectedPackages[item.Name] && !fixedSet[item.Name][item.Version] { + result = append(result, item) + } + } + return result, nil +} + +// --- mapping helpers --- + +func mapMavenToLightwellPackages(resp tangy.MavenPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageResponse { + out := make([]api.LightwellPackageResponse, 0, len(resp.Results)) + for _, item := range resp.Results { + releases := make([]api.ReleaseInfo, len(item.LatestReleases)) + for j, rel := range item.LatestReleases { + releases[j] = api.ReleaseInfo{Version: rel.Version, Release: rel.Release, CreatedAt: rel.CreatedAt} + } + out = append(out, api.LightwellPackageResponse{ + Name: item.ArtifactID, + Group: item.GroupID, + ContentType: config.ContentTypeMaven, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + Versions: item.Versions, + LatestReleases: releases, + }) + } + return out +} + +func mapPythonToLightwellPackages(resp tangy.PythonPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageResponse { + out := make([]api.LightwellPackageResponse, 0, len(resp.Results)) + for _, item := range resp.Results { + releases := make([]api.ReleaseInfo, len(item.LatestVersions)) + for j, ver := range item.LatestVersions { + releases[j] = api.ReleaseInfo{Version: ver.Version, CreatedAt: ver.CreatedAt} + } + out = append(out, api.LightwellPackageResponse{ + Name: item.NameNormalized, + ContentType: config.ContentTypePython, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + Versions: item.Versions, + LatestReleases: releases, + }) + } + return out +} + +func mapNpmToLightwellPackages(resp tangy.NpmPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageResponse { + out := make([]api.LightwellPackageResponse, 0, len(resp.Results)) + for _, item := range resp.Results { + releases := make([]api.ReleaseInfo, len(item.LatestVersions)) + for j, ver := range item.LatestVersions { + releases[j] = api.ReleaseInfo{Version: ver.Version, CreatedAt: ver.CreatedAt} + } + scope, name := parseNpmPackageName(item.Name) + out = append(out, api.LightwellPackageResponse{ + Name: name, + Group: scope, + ContentType: config.ContentTypeNpm, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + Versions: item.Versions, + LatestReleases: releases, + }) + } + return out +} + +func expandMavenVersions(resp tangy.MavenPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageVersionResponse { + var out []api.LightwellPackageVersionResponse + for _, item := range resp.Results { + relMap := latestReleaseMap(item.LatestReleases) + for _, v := range item.Versions { + ver := api.LightwellPackageVersionResponse{ + Name: item.ArtifactID, + Group: item.GroupID, + Version: v, + ContentType: config.ContentTypeMaven, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + } + if rel, ok := relMap[v]; ok { + ver.Release = rel.Release + ver.CreatedAt = rel.CreatedAt + } + out = append(out, ver) + } + } + return out +} + +func expandPythonVersions(resp tangy.PythonPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageVersionResponse { + var out []api.LightwellPackageVersionResponse + for _, item := range resp.Results { + verMap := latestVersionMap(item.LatestVersions) + for _, v := range item.Versions { + ver := api.LightwellPackageVersionResponse{ + Name: item.NameNormalized, + Version: v, + ContentType: config.ContentTypePython, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + } + if info, ok := verMap[v]; ok { + ver.CreatedAt = info.CreatedAt + } + out = append(out, ver) + } + } + return out +} + +func expandNpmVersions(resp tangy.NpmPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageVersionResponse { + var out []api.LightwellPackageVersionResponse + for _, item := range resp.Results { + scope, name := parseNpmPackageName(item.Name) + verMap := npmVersionMap(item.LatestVersions) + for _, v := range item.Versions { + ver := api.LightwellPackageVersionResponse{ + Name: name, + Group: scope, + Version: v, + ContentType: config.ContentTypeNpm, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + } + if info, ok := verMap[v]; ok { + ver.CreatedAt = info.CreatedAt + } + out = append(out, ver) + } + } + return out +} + +// --- filter / pagination helpers --- + +func parseLightwellPackageFilters(c echo.Context) api.LightwellPackageFilterData { + var f api.LightwellPackageFilterData + _ = echo.QueryParamsBinder(c). + String("type", &f.ContentType). + String("name", &f.Name). + String("security_level", &f.SecurityLevel). + BindError() + return f +} + +func parseLightwellPackageVersionFilters(c echo.Context) api.LightwellPackageVersionFilterData { + var f api.LightwellPackageVersionFilterData + _ = echo.QueryParamsBinder(c). + String("type", &f.ContentType). + String("name", &f.Name). + String("security_level", &f.SecurityLevel). + String("repository", &f.Repository). + String("resolves_cve_id", &f.ResolvesCveID). + String("vulnerable_to_cve_id", &f.VulnerableToCveID). + BindError() + return f +} + +var validContentTypes = map[string]bool{ + config.ContentTypeMaven: true, + config.ContentTypePython: true, + config.ContentTypeNpm: true, +} + +func validateContentType(ct string) error { + if ct == "" { + return nil + } + if !validContentTypes[ct] { + return fmt.Errorf("unsupported type: %s (must be maven, python, or npm)", ct) + } + return nil +} + +func filterReposByName(repos []api.RepositoryResponse, name string) []api.RepositoryResponse { + var out []api.RepositoryResponse + for _, r := range repos { + if strings.EqualFold(r.Name, name) { + out = append(out, r) + } + } + return out +} + +func paginatePackages(items []api.LightwellPackageResponse, offset, limit int) []api.LightwellPackageResponse { + if offset >= len(items) { + return []api.LightwellPackageResponse{} + } + end := offset + limit + if end > len(items) { + end = len(items) + } + return items[offset:end] +} + +func paginateVersions(items []api.LightwellPackageVersionResponse, offset, limit int) []api.LightwellPackageVersionResponse { + if offset >= len(items) { + return []api.LightwellPackageVersionResponse{} + } + end := offset + limit + if end > len(items) { + end = len(items) + } + return items[offset:end] +} + +// release-info lookup helpers for version expansion + +type mavenRelInfo struct { + Release string + CreatedAt string +} + +func latestReleaseMap(releases []tangy.MavenReleaseInfo) map[string]mavenRelInfo { + m := make(map[string]mavenRelInfo, len(releases)) + for _, r := range releases { + m[r.Version] = mavenRelInfo{Release: r.Release, CreatedAt: r.CreatedAt} + } + return m +} + +type versionCreatedAt struct { + CreatedAt string +} + +func latestVersionMap(versions []tangy.PythonVersionInfo) map[string]versionCreatedAt { + m := make(map[string]versionCreatedAt, len(versions)) + for _, v := range versions { + m[v.Version] = versionCreatedAt{CreatedAt: v.CreatedAt} + } + return m +} + +func npmVersionMap(versions []tangy.NpmVersionInfo) map[string]versionCreatedAt { + m := make(map[string]versionCreatedAt, len(versions)) + for _, v := range versions { + m[v.Version] = versionCreatedAt{CreatedAt: v.CreatedAt} + } + return m +} diff --git a/pkg/handler/lightwell_packages_test.go b/pkg/handler/lightwell_packages_test.go new file mode 100644 index 000000000..9e6aa4070 --- /dev/null +++ b/pkg/handler/lightwell_packages_test.go @@ -0,0 +1,392 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" + "github.com/content-services/content-sources-backend/pkg/config" + "github.com/content-services/content-sources-backend/pkg/dao" + "github.com/content-services/content-sources-backend/pkg/middleware" + "github.com/content-services/content-sources-backend/pkg/test" + test_handler "github.com/content-services/content-sources-backend/pkg/test/handler" + "github.com/content-services/tang/pkg/tangy" + "github.com/labstack/echo/v4" + echo_middleware "github.com/labstack/echo/v4/middleware" + "github.com/redhatinsights/platform-go-middlewares/v2/identity" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type LightwellPackagesSuite struct { + suite.Suite + reg *dao.MockDaoRegistry + tangClient *tangy.MockTangy + pulpClient *pulp_client.MockPulpClient + querier *MockQuerier +} + +func TestLightwellPackagesSuite(t *testing.T) { + suite.Run(t, new(LightwellPackagesSuite)) +} + +func (s *LightwellPackagesSuite) SetupTest() { + s.reg = dao.GetMockDaoRegistry(s.T()) + s.tangClient = tangy.NewMockTangy(s.T()) + s.pulpClient = pulp_client.NewMockPulpClient(s.T()) + s.querier = &MockQuerier{} +} + +func (s *LightwellPackagesSuite) serveRouter(req *http.Request) (int, []byte, error) { + router := echo.New() + router.HTTPErrorHandler = config.CustomHTTPErrorHandler + router.Use(echo_middleware.RequestIDWithConfig(echo_middleware.RequestIDConfig{ + TargetHeader: "x-rh-insights-request-id", + })) + router.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) + pathPrefix := router.Group(api.FullRootPath()) + RegisterLightwellPackageRoutes(pathPrefix, s.querier, s.reg.ToDaoRegistry(), s.tangClient, s.pulpClient) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + + response := rr.Result() + defer response.Body.Close() + body, err := io.ReadAll(response.Body) + return response.StatusCode, body, err +} + +// stubLightwellRepos sets up the DAO mock to return the given repos for a List call with origin=lightwell. +func (s *LightwellPackagesSuite) stubLightwellRepos(repos []api.RepositoryResponse) { + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { return f.Origin == config.OriginLightwell }), + ).Return(api.RepositoryCollectionResponse{Data: repos}, int64(len(repos)), nil) +} + +func (s *LightwellPackagesSuite) stubRepoHref(repo api.RepositoryResponse, href string) { + domainName := "test-domain" + s.reg.Domain.On("FetchOrCreateDomain", test.MockCtx(), repo.OrgID).Return(domainName, nil).Maybe() + s.pulpClient.On("WithDomain", domainName).Return(s.pulpClient).Maybe() + s.pulpClient.On("ResolveRepositoryFromBasePath", test.MockCtx(), repo.PublishedDistBasePath).Return(&href, nil).Maybe() +} + +func newMavenRepo() api.RepositoryResponse { + return api.RepositoryResponse{ + UUID: "aaa-bbb-ccc", + Name: "lightwell/java/remediated", + ContentType: config.ContentTypeMaven, + Origin: config.OriginLightwell, + SecurityLevel: "remediated", + PublishedDistBasePath: "java/remediated", + OrgID: test_handler.MockOrgId, + } +} + +func newPythonRepo() api.RepositoryResponse { + return api.RepositoryResponse{ + UUID: "ddd-eee-fff", + Name: "lightwell/python/remediated", + ContentType: config.ContentTypePython, + Origin: config.OriginLightwell, + SecurityLevel: "remediated", + PublishedDistBasePath: "python/remediated", + OrgID: test_handler.MockOrgId, + } +} + +func mavenTangResponse() tangy.MavenPackageListResponse { + return tangy.MavenPackageListResponse{ + Results: []tangy.MavenPackageListItem{ + { + GroupID: "com.fasterxml.jackson.core", + ArtifactID: "jackson-databind", + Versions: []string{"2.15.3.rhlw-00001", "2.14.2.rhlw-00001"}, + LatestReleases: []tangy.MavenReleaseInfo{ + {Version: "2.15.3.rhlw-00001", Release: "rhlw-00001", CreatedAt: "2024-06-01T12:00:00Z"}, + }, + }, + }, + Total: 1, Limit: 200, Offset: 0, + } +} + +func pythonTangResponse() tangy.PythonPackageListResponse { + return tangy.PythonPackageListResponse{ + Results: []tangy.PythonPackageListItem{ + { + Name: "requests", + NameNormalized: "requests", + Versions: []string{"2.31.0.rhlw-00001"}, + LatestVersions: []tangy.PythonVersionInfo{ + {Version: "2.31.0.rhlw-00001", CreatedAt: "2024-05-10T08:00:00Z"}, + }, + }, + }, + Total: 1, Limit: 200, Offset: 0, + } +} + +// --- /lightwell/packages tests --- + +func (s *LightwellPackagesSuite) TestListPackagesSingleRepo() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/default/api/v3/repositories/maven/maven/some-uuid/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, "com.fasterxml.jackson.core", resp.Data[0].Group) + assert.Equal(t, config.ContentTypeMaven, resp.Data[0].ContentType) + assert.Equal(t, "lightwell/java/remediated", resp.Data[0].Repository) + assert.Equal(t, 2, len(resp.Data[0].Versions)) +} + +func (s *LightwellPackagesSuite) TestListPackagesMultiRepo() { + t := s.T() + + mavenRepo := newMavenRepo() + pythonRepo := newPythonRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo, pythonRepo}) + + mavenHref := "/api/pulp/repos/maven/1/" + pythonHref := "/api/pulp/repos/python/1/" + s.stubRepoHref(mavenRepo, mavenHref) + s.stubRepoHref(pythonRepo, pythonHref) + + s.tangClient.On("MavenPackageList", test.MockCtx(), mavenHref, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + s.tangClient.On("PythonPackageList", test.MockCtx(), pythonHref, + tangy.PythonPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(pythonTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) + assert.Len(t, resp.Data, 2) + + contentTypes := map[string]bool{} + for _, p := range resp.Data { + contentTypes[p.ContentType] = true + } + assert.True(t, contentTypes[config.ContentTypeMaven]) + assert.True(t, contentTypes[config.ContentTypePython]) +} + +func (s *LightwellPackagesSuite) TestListPackagesTypeFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + // Only maven repo should be returned when filtering by content_type=maven + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { + return f.Origin == config.OriginLightwell && f.ContentType == config.ContentTypeMaven + }), + ).Return(api.RepositoryCollectionResponse{Data: []api.RepositoryResponse{mavenRepo}}, int64(1), nil) + + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/packages?type=maven", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Len(t, resp.Data, 1) + assert.Equal(t, config.ContentTypeMaven, resp.Data[0].ContentType) +} + +func (s *LightwellPackagesSuite) TestListPackagesInvalidType() { + t := s.T() + + path := fmt.Sprintf("%s/lightwell/packages?type=invalid", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, code) +} + +func (s *LightwellPackagesSuite) TestListPackagesEmptyResult() { + t := s.T() + + s.stubLightwellRepos([]api.RepositoryResponse{}) + + path := fmt.Sprintf("%s/lightwell/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(0), resp.Meta.Count) + assert.NotNil(t, resp.Data) + assert.Empty(t, resp.Data) +} + +// --- /lightwell/package_versions tests --- + +func (s *LightwellPackagesSuite) TestListPackageVersionsSingleRepo() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) // 2 versions for jackson-databind + assert.Len(t, resp.Data, 2) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, config.ContentTypeMaven, resp.Data[0].ContentType) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsWithNameFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{Search: "jackson"}, + tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/package_versions?name=jackson", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + assert.Len(t, resp.Data, 2) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsPagination() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + // Request with limit=1&offset=0 — should get 1 of 2 versions + path := fmt.Sprintf("%s/lightwell/package_versions?limit=1&offset=0", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) // total is 2 + assert.Len(t, resp.Data, 1) // page is 1 + assert.NotEmpty(t, resp.Links.Next) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsInvalidType() { + t := s.T() + + path := fmt.Sprintf("%s/lightwell/package_versions?type=bogus", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, code) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsEmptyResult() { + t := s.T() + + s.stubLightwellRepos([]api.RepositoryResponse{}) + + path := fmt.Sprintf("%s/lightwell/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + assert.Equal(t, int64(0), resp.Meta.Count) + assert.NotNil(t, resp.Data) + assert.Empty(t, resp.Data) +} diff --git a/pkg/handler/repositories.go b/pkg/handler/repositories.go index 22a6b00a2..a89b8985f 100644 --- a/pkg/handler/repositories.go +++ b/pkg/handler/repositories.go @@ -12,6 +12,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" ce "github.com/content-services/content-sources-backend/pkg/errors" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/content-services/content-sources-backend/pkg/rbac" "github.com/content-services/content-sources-backend/pkg/tasks" "github.com/content-services/content-sources-backend/pkg/tasks/client" @@ -33,10 +34,12 @@ type RepositoryHandler struct { DaoRegistry dao.DaoRegistry TaskClient client.TaskClient FeatureServiceClient feature_service_client.FeatureServiceClient + LightwellStore store.Querier // nil when lightwell store is unavailable } func RegisterRepositoryRoutes(engine *echo.Group, daoReg *dao.DaoRegistry, taskClient *client.TaskClient, fsClient *feature_service_client.FeatureServiceClient, + lightwellStore ...store.Querier, ) { if engine == nil { panic("engine is nil") @@ -55,6 +58,9 @@ func RegisterRepositoryRoutes(engine *echo.Group, daoReg *dao.DaoRegistry, TaskClient: *taskClient, FeatureServiceClient: *fsClient, } + if len(lightwellStore) > 0 && lightwellStore[0] != nil { + rh.LightwellStore = lightwellStore[0] + } addRepoRoute(engine, http.MethodGet, "/repositories/", rh.listRepositories, rbac.RbacVerbRead) addRepoRoute(engine, http.MethodGet, "/repositories/:uuid", rh.fetch, rbac.RbacVerbRead) @@ -122,9 +128,43 @@ func (rh *RepositoryHandler) listRepositories(c echo.Context) error { return ce.NewErrorResponse(ce.HttpCodeForDaoError(err), "Error listing repositories", err.Error()) } + rh.enrichLightwellRepoCounts(c, &repos) + return c.JSON(200, setCollectionResponseMetadata(&repos, c, totalRepos)) } +// enrichLightwellRepoCounts populates packages_count, versions_count, and +// remediations_count on Lightwell-origin repositories. These spec-required +// fields are omitted for non-Lightwell repos to avoid breaking existing consumers. +func (rh *RepositoryHandler) enrichLightwellRepoCounts(c echo.Context, repos *api.RepositoryCollectionResponse) { + for i := range repos.Data { + repo := &repos.Data[i] + if repo.Origin != config.OriginLightwell { + continue + } + pkgCount := repo.PackageCount + verCount := repo.VersionCount + repo.PackagesCount = &pkgCount + repo.VersionsCount = &verCount + + if rh.LightwellStore == nil { + continue + } + repoUUID, err := uuid.Parse(repo.UUID) + if err != nil { + log.Ctx(c.Request().Context()).Warn().Err(err).Str("uuid", repo.UUID).Msg("invalid UUID for advisory count") + continue + } + count, err := rh.LightwellStore.CountAdvisoriesByRepo(c.Request().Context(), repoUUID) + if err != nil { + log.Ctx(c.Request().Context()).Warn().Err(err).Str("uuid", repo.UUID).Msg("failed to count advisories") + continue + } + remCount := int(count) + repo.RemediationsCount = &remCount + } +} + // CreateRepository godoc // @Summary Create Repository // @ID createRepository diff --git a/pkg/lightwell/db/queries/advisories.sql b/pkg/lightwell/db/queries/advisories.sql new file mode 100644 index 000000000..dab34cf45 --- /dev/null +++ b/pkg/lightwell/db/queries/advisories.sql @@ -0,0 +1,60 @@ +-- name: ListAdvisories :many +SELECT + la.uuid, + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.reference_urls, + la.package_name, + la.fixed_versions, + la.repo_name, + la.repository_configuration_uuid, + la.created_at, + COUNT(*) OVER() AS total_count +FROM lightwell_advisories la +WHERE 1=1 + AND ( + sqlc.narg(repository_config_uuid)::uuid IS NULL + OR la.repository_configuration_uuid = sqlc.narg(repository_config_uuid)::uuid + ) + AND ( + sqlc.narg(package_name)::text IS NULL + OR la.package_name ILIKE '%' || sqlc.narg(package_name)::text || '%' + ) + AND ( + sqlc.narg(severity_min)::smallint IS NULL + OR la.severity_order >= sqlc.narg(severity_min)::smallint + ) + AND ( + sqlc.narg(cve_id)::text IS NULL + OR la.advisory_id = sqlc.narg(cve_id)::text + ) +ORDER BY la.severity_order DESC, la.created_at DESC +LIMIT sqlc.arg(page_limit) OFFSET sqlc.arg(page_offset); + +-- name: CountAdvisoriesByRepo :one +SELECT COUNT(*)::bigint AS total +FROM lightwell_advisories la +WHERE la.repository_configuration_uuid = sqlc.arg(repository_config_uuid)::uuid; + +-- name: ListAdvisoriesByPackage :many +SELECT + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.fixed_versions, + la.repo_name +FROM lightwell_advisories la +WHERE la.package_name = sqlc.arg(package_name)::text +ORDER BY la.severity_order DESC, la.created_at DESC; + +-- name: ListAdvisoriesByCveID :many +SELECT + la.package_name, + la.fixed_versions, + la.repo_name, + la.severity +FROM lightwell_advisories la +WHERE la.advisory_id = sqlc.arg(cve_id)::text; diff --git a/pkg/lightwell/db/schema.sql b/pkg/lightwell/db/schema.sql index 01a67fd86..e20b595a2 100644 --- a/pkg/lightwell/db/schema.sql +++ b/pkg/lightwell/db/schema.sql @@ -1,4 +1,34 @@ --- sqlc schema snapshot: current lightwell vulnerabilities tables (see db/migrations) +-- sqlc schema snapshot: lightwell tables (see db/migrations) +-- NOTE: This file must be kept in sync with the actual migrations. +-- sqlc uses this for code generation; it is not executed directly. + +CREATE TABLE repository_configurations ( + uuid UUID PRIMARY KEY +); + +CREATE TABLE lightwell_advisories ( + uuid UUID UNIQUE NOT NULL PRIMARY KEY, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + repo_name VARCHAR(255) NOT NULL, + advisory_id VARCHAR(255) NOT NULL, + severity VARCHAR(255) NOT NULL DEFAULT '', + severity_order SMALLINT NOT NULL DEFAULT 0, + details TEXT NOT NULL DEFAULT '', + reference_urls TEXT[], + package_name VARCHAR(255) NOT NULL DEFAULT '', + fixed_version VARCHAR(255) NOT NULL DEFAULT '', + fixed_versions TEXT[] NOT NULL DEFAULT '{}', + repository_configuration_uuid UUID NOT NULL REFERENCES repository_configurations(uuid) ON DELETE CASCADE, + checksum VARCHAR(255) NOT NULL +); + +CREATE UNIQUE INDEX idx_lightwell_advisories_repo_config_advisory + ON lightwell_advisories (repository_configuration_uuid, advisory_id, package_name); +CREATE INDEX idx_lightwell_advisories_severity_order + ON lightwell_advisories (severity_order); +CREATE INDEX idx_lightwell_advisories_package_name + ON lightwell_advisories (package_name); CREATE TABLE lightwell_vulnerabilities ( uuid UUID PRIMARY KEY, diff --git a/pkg/lightwell/db/store/models.go b/pkg/lightwell/db/store/models.go index df7274033..57906e97d 100644 --- a/pkg/lightwell/db/store/models.go +++ b/pkg/lightwell/db/store/models.go @@ -10,6 +10,23 @@ import ( "github.com/google/uuid" ) +type LightwellAdvisory struct { + Uuid uuid.UUID `json:"uuid"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + RepoName string `json:"repo_name"` + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + SeverityOrder int16 `json:"severity_order"` + Details string `json:"details"` + ReferenceUrls []string `json:"reference_urls"` + PackageName string `json:"package_name"` + FixedVersion string `json:"fixed_version"` + FixedVersions []string `json:"fixed_versions"` + RepositoryConfigurationUuid uuid.UUID `json:"repository_configuration_uuid"` + Checksum string `json:"checksum"` +} + type LightwellVulnerability struct { Uuid uuid.UUID `json:"uuid"` VulnerabilityID string `json:"vulnerability_id"` @@ -42,3 +59,7 @@ type LightwellVulnerabilityCustomer struct { VulnerabilityUuid uuid.UUID `json:"vulnerability_uuid"` CreatedAt time.Time `json:"created_at"` } + +type RepositoryConfiguration struct { + Uuid uuid.UUID `json:"uuid"` +} diff --git a/pkg/lightwell/db/store/querier.go b/pkg/lightwell/db/store/querier.go index 1eded932b..64dd7f349 100644 --- a/pkg/lightwell/db/store/querier.go +++ b/pkg/lightwell/db/store/querier.go @@ -6,11 +6,17 @@ package store import ( "context" + + "github.com/google/uuid" ) type Querier interface { + CountAdvisoriesByRepo(ctx context.Context, repositoryConfigUuid uuid.UUID) (int64, error) CountAggregates(ctx context.Context, arg CountAggregatesParams) (CountAggregatesRow, error) CountByStage(ctx context.Context, arg CountByStageParams) ([]CountByStageRow, error) + ListAdvisories(ctx context.Context, arg ListAdvisoriesParams) ([]ListAdvisoriesRow, error) + ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]ListAdvisoriesByCveIDRow, error) + ListAdvisoriesByPackage(ctx context.Context, packageName string) ([]ListAdvisoriesByPackageRow, error) ListCustomerIds(ctx context.Context) ([]string, error) ListVulnerabilities(ctx context.Context, arg ListVulnerabilitiesParams) ([]LightwellVulnerability, error) } From e9906451f6398d674b969fa2d7dd1ea42990a3a4 Mon Sep 17 00:00:00 2001 From: etsien Date: Mon, 24 Aug 2026 12:30:27 -0400 Subject: [PATCH 03/47] Update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 40697b284..01911f3c0 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ content-sources-frontend # local dev certs for testing pulp cert auth compose_files/pulp/assets/certs/dev_certs +/pkg/jfrog_bridge/testdata +pkg/jfrog_bridge/lightwell-catalog.key +.env.catalog From 3373e9294abaec78211d657745d8df1d177eb2a9 Mon Sep 17 00:00:00 2001 From: etsien Date: Mon, 24 Aug 2026 13:18:04 -0400 Subject: [PATCH 04/47] v2 of API buildout Phase 1 --- pkg/api/lightwell_advisories.go | 8 +- pkg/api/lightwell_packages.go | 5 +- pkg/handler/lightwell_advisories.go | 34 ++++---- pkg/handler/lightwell_advisories_test.go | 10 ++- pkg/handler/lightwell_packages.go | 98 +++++++++++++++++++++++- pkg/handler/lightwell_packages_test.go | 6 +- pkg/lightwell/db/queries/advisories.sql | 4 + 7 files changed, 134 insertions(+), 31 deletions(-) diff --git a/pkg/api/lightwell_advisories.go b/pkg/api/lightwell_advisories.go index cf8fe846e..9eb7260a5 100644 --- a/pkg/api/lightwell_advisories.go +++ b/pkg/api/lightwell_advisories.go @@ -22,8 +22,8 @@ func (r *LightwellAdvisoryCollectionResponse) SetMetadata(meta ResponseMetadata, } type LightwellAdvisoryFilterData struct { - RepositoryUUID string `query:"repository_uuid"` - PackageName string `query:"package_name"` - SeverityMin string `query:"severity_min"` - CveID string `query:"cve_id"` + Repository string `query:"repository"` + PackageName string `query:"package_name"` + SeverityMin string `query:"severity_min"` + CveID string `query:"cve_id"` } diff --git a/pkg/api/lightwell_packages.go b/pkg/api/lightwell_packages.go index cc13d4c81..aadaa286c 100644 --- a/pkg/api/lightwell_packages.go +++ b/pkg/api/lightwell_packages.go @@ -49,14 +49,15 @@ func (r *LightwellPackageVersionCollectionResponse) SetMetadata(meta ResponseMet // LightwellPackageFilterData holds query-parameter filters for the cross-repo packages endpoint. type LightwellPackageFilterData struct { - ContentType string `query:"type"` + ContentType string `query:"content_type"` Name string `query:"name"` + Repository string `query:"repository"` SecurityLevel string `query:"security_level"` } // LightwellPackageVersionFilterData holds query-parameter filters for the cross-repo package_versions endpoint. type LightwellPackageVersionFilterData struct { - ContentType string `query:"type"` + ContentType string `query:"content_type"` Name string `query:"name"` SecurityLevel string `query:"security_level"` Repository string `query:"repository"` diff --git a/pkg/handler/lightwell_advisories.go b/pkg/handler/lightwell_advisories.go index 99ed6d14a..250c5255a 100644 --- a/pkg/handler/lightwell_advisories.go +++ b/pkg/handler/lightwell_advisories.go @@ -7,7 +7,6 @@ import ( ce "github.com/content-services/content-sources-backend/pkg/errors" "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/content-services/content-sources-backend/pkg/rbac" - "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" "github.com/labstack/echo/v4" ) @@ -18,7 +17,10 @@ type LightwellAdvisoryHandler struct { func RegisterLightwellAdvisoryRoutes(engine *echo.Group, querier store.Querier) { h := LightwellAdvisoryHandler{Store: querier} + // Flat cross-repo endpoint addRepoRoute(engine, http.MethodGet, "/lightwell/advisories", h.list, rbac.RbacVerbRead) + // Nested repo-scoped alias + addRepoRoute(engine, http.MethodGet, "/lightwell/repositories/:repository_name/advisories", h.listRepoAdvisories, rbac.RbacVerbRead) } // listLightwellAdvisories godoc @@ -47,13 +49,9 @@ func (h *LightwellAdvisoryHandler) list(c echo.Context) error { return ce.NewErrorResponse(http.StatusBadRequest, "Invalid severity_min", err.Error()) } - var repoUUID pgtype.UUID - if filters.RepositoryUUID != "" { - parsed, err := uuid.Parse(filters.RepositoryUUID) - if err != nil { - return ce.NewErrorResponse(http.StatusBadRequest, "Invalid repository_uuid", err.Error()) - } - repoUUID = pgtype.UUID{Bytes: parsed, Valid: true} + var repoName *string + if filters.Repository != "" { + repoName = &filters.Repository } var packageName *string @@ -67,12 +65,12 @@ func (h *LightwellAdvisoryHandler) list(c echo.Context) error { } rows, err := h.Store.ListAdvisories(c.Request().Context(), store.ListAdvisoriesParams{ - RepositoryConfigUuid: repoUUID, - PackageName: packageName, - SeverityMin: severityMin, - CveID: cveID, - PageOffset: int32(page.Offset), //nolint:gosec // bounded by ParsePagination - PageLimit: int32(page.Limit), //nolint:gosec // bounded by MaxLimit (200) + RepoName: repoName, + PackageName: packageName, + SeverityMin: severityMin, + CveID: cveID, + PageOffset: int32(page.Offset), //nolint:gosec // bounded by ParsePagination + PageLimit: int32(page.Limit), //nolint:gosec // bounded by MaxLimit (200) }) if err != nil { return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing advisories", err.Error()) @@ -115,7 +113,7 @@ func mapAdvisoryRowsToResponse(rows []store.ListAdvisoriesRow) api.LightwellAdvi func parseLightwellAdvisoryFilters(c echo.Context) api.LightwellAdvisoryFilterData { var filters api.LightwellAdvisoryFilterData _ = echo.QueryParamsBinder(c). - String("repository_uuid", &filters.RepositoryUUID). + String("repository", &filters.Repository). String("package_name", &filters.PackageName). String("severity_min", &filters.SeverityMin). String("cve_id", &filters.CveID). @@ -148,3 +146,9 @@ type invalidSeverityError struct { func (e *invalidSeverityError) Error() string { return "invalid severity: " + e.severity + " (must be one of: low, moderate, important, critical)" } + +func (h *LightwellAdvisoryHandler) listRepoAdvisories(c echo.Context) error { + repoName := c.Param("repository_name") + c.QueryParams().Set("repository", repoName) + return h.list(c) +} diff --git a/pkg/handler/lightwell_advisories_test.go b/pkg/handler/lightwell_advisories_test.go index e0775cd54..dcd0d33bc 100644 --- a/pkg/handler/lightwell_advisories_test.go +++ b/pkg/handler/lightwell_advisories_test.go @@ -149,16 +149,20 @@ func (s *LightwellAdvisorySuite) TestListAdvisoriesInvalidSeverity() { assert.Equal(t, http.StatusBadRequest, code) } -func (s *LightwellAdvisorySuite) TestListAdvisoriesInvalidRepoUUID() { +func (s *LightwellAdvisorySuite) TestListAdvisoriesFilterByRepoName() { t := s.T() - path := fmt.Sprintf("%s/lightwell/advisories?repository_uuid=not-a-uuid", api.FullRootPath()) + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { + return arg.RepoName != nil && *arg.RepoName == "java-remediated" + })).Return([]store.ListAdvisoriesRow{}, nil) + + path := fmt.Sprintf("%s/lightwell/advisories?repository=java-remediated", api.FullRootPath()) req := httptest.NewRequest(http.MethodGet, path, nil) req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) code, _, err := s.serveRouter(req) require.NoError(t, err) - assert.Equal(t, http.StatusBadRequest, code) + assert.Equal(t, http.StatusOK, code) } func (s *LightwellAdvisorySuite) TestListAdvisoriesEmptyResult() { diff --git a/pkg/handler/lightwell_packages.go b/pkg/handler/lightwell_packages.go index 901137d69..6ef3697ed 100644 --- a/pkg/handler/lightwell_packages.go +++ b/pkg/handler/lightwell_packages.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "sort" "strings" "sync" @@ -34,8 +35,12 @@ func RegisterLightwellPackageRoutes(engine *echo.Group, querier store.Querier, d TangClient: tangClient, PulpClient: pulpClient, } + // Flat cross-repo endpoints addRepoRoute(engine, http.MethodGet, "/lightwell/packages", h.listPackages, rbac.RbacVerbRead) addRepoRoute(engine, http.MethodGet, "/lightwell/package_versions", h.listPackageVersions, rbac.RbacVerbRead) + // Nested repo-scoped aliases + addRepoRoute(engine, http.MethodGet, "/lightwell/repositories/:repository_name/packages", h.listRepoPackages, rbac.RbacVerbRead) + addRepoRoute(engine, http.MethodGet, "/lightwell/repositories/:repository_name/package_versions", h.listRepoPackageVersions, rbac.RbacVerbRead) } // listLightwellPackages godoc @@ -59,19 +64,23 @@ func (h *LightwellPackagesHandler) listPackages(c echo.Context) error { filters := parseLightwellPackageFilters(c) if err := validateContentType(filters.ContentType); err != nil { - return ce.NewErrorResponse(http.StatusBadRequest, "Invalid type filter", err.Error()) + return ce.NewErrorResponse(http.StatusBadRequest, "Invalid content_type filter", err.Error()) } repos, err := h.fetchLightwellRepos(c, filters.ContentType, filters.SecurityLevel) if err != nil { return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing Lightwell repositories", err.Error()) } + if filters.Repository != "" { + repos = filterReposByName(repos, filters.Repository) + } items, err := h.aggregatePackages(c.Request().Context(), repos, filters.Name) if err != nil { return ce.NewErrorResponse(http.StatusInternalServerError, "Error retrieving packages", err.Error()) } + sortLightwellPackages(items, page.SortBy) totalCount := int64(len(items)) paged := paginatePackages(items, page.Offset, page.Limit) resp := api.LightwellPackageCollectionResponse{Data: paged} @@ -103,7 +112,7 @@ func (h *LightwellPackagesHandler) listPackageVersions(c echo.Context) error { filters := parseLightwellPackageVersionFilters(c) if err := validateContentType(filters.ContentType); err != nil { - return ce.NewErrorResponse(http.StatusBadRequest, "Invalid type filter", err.Error()) + return ce.NewErrorResponse(http.StatusBadRequest, "Invalid content_type filter", err.Error()) } repos, err := h.fetchLightwellRepos(c, filters.ContentType, filters.SecurityLevel) @@ -132,6 +141,7 @@ func (h *LightwellPackagesHandler) listPackageVersions(c echo.Context) error { } } + sortLightwellVersions(items, page.SortBy) totalCount := int64(len(items)) paged := paginateVersions(items, page.Offset, page.Limit) resp := api.LightwellPackageVersionCollectionResponse{Data: paged} @@ -534,8 +544,9 @@ func expandNpmVersions(resp tangy.NpmPackageListResponse, repo api.RepositoryRes func parseLightwellPackageFilters(c echo.Context) api.LightwellPackageFilterData { var f api.LightwellPackageFilterData _ = echo.QueryParamsBinder(c). - String("type", &f.ContentType). + String("content_type", &f.ContentType). String("name", &f.Name). + String("repository", &f.Repository). String("security_level", &f.SecurityLevel). BindError() return f @@ -544,7 +555,7 @@ func parseLightwellPackageFilters(c echo.Context) api.LightwellPackageFilterData func parseLightwellPackageVersionFilters(c echo.Context) api.LightwellPackageVersionFilterData { var f api.LightwellPackageVersionFilterData _ = echo.QueryParamsBinder(c). - String("type", &f.ContentType). + String("content_type", &f.ContentType). String("name", &f.Name). String("security_level", &f.SecurityLevel). String("repository", &f.Repository). @@ -636,3 +647,82 @@ func npmVersionMap(versions []tangy.NpmVersionInfo) map[string]versionCreatedAt } return m } + +// --- nested repo-scoped alias handlers --- + +func (h *LightwellPackagesHandler) listRepoPackages(c echo.Context) error { + repoName := c.Param("repository_name") + c.QueryParams().Set("repository", repoName) + return h.listPackages(c) +} + +func (h *LightwellPackagesHandler) listRepoPackageVersions(c echo.Context) error { + repoName := c.Param("repository_name") + c.QueryParams().Set("repository", repoName) + return h.listPackageVersions(c) +} + +// --- sort helpers --- + +func sortLightwellPackages(items []api.LightwellPackageResponse, sortBy string) { + field, dir := parseSortBy(sortBy) + if field == "" { + field = "name" + } + sort.SliceStable(items, func(i, j int) bool { + var less bool + switch field { + case "name": + less = items[i].Name < items[j].Name + case "content_type": + less = items[i].ContentType < items[j].ContentType + case "repository": + less = items[i].Repository < items[j].Repository + default: + less = items[i].Name < items[j].Name + } + if dir == "desc" { + return !less + } + return less + }) +} + +func sortLightwellVersions(items []api.LightwellPackageVersionResponse, sortBy string) { + field, dir := parseSortBy(sortBy) + if field == "" { + field = "name" + } + sort.SliceStable(items, func(i, j int) bool { + var less bool + switch field { + case "name": + less = items[i].Name < items[j].Name + case "version": + less = items[i].Version < items[j].Version + case "content_type": + less = items[i].ContentType < items[j].ContentType + case "repository": + less = items[i].Repository < items[j].Repository + default: + less = items[i].Name < items[j].Name + } + if dir == "desc" { + return !less + } + return less + }) +} + +func parseSortBy(sortBy string) (field, direction string) { + if sortBy == "" { + return "", "asc" + } + parts := strings.Fields(sortBy) + field = strings.ToLower(parts[0]) + direction = "asc" + if len(parts) > 1 && strings.EqualFold(parts[1], "desc") { + direction = "desc" + } + return field, direction +} diff --git a/pkg/handler/lightwell_packages_test.go b/pkg/handler/lightwell_packages_test.go index 9e6aa4070..eb2172cb3 100644 --- a/pkg/handler/lightwell_packages_test.go +++ b/pkg/handler/lightwell_packages_test.go @@ -228,7 +228,7 @@ func (s *LightwellPackagesSuite) TestListPackagesTypeFilter() { tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, ).Return(mavenTangResponse(), nil) - path := fmt.Sprintf("%s/lightwell/packages?type=maven", api.FullRootPath()) + path := fmt.Sprintf("%s/lightwell/packages?content_type=maven", api.FullRootPath()) req := httptest.NewRequest(http.MethodGet, path, nil) req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) @@ -246,7 +246,7 @@ func (s *LightwellPackagesSuite) TestListPackagesTypeFilter() { func (s *LightwellPackagesSuite) TestListPackagesInvalidType() { t := s.T() - path := fmt.Sprintf("%s/lightwell/packages?type=invalid", api.FullRootPath()) + path := fmt.Sprintf("%s/lightwell/packages?content_type=invalid", api.FullRootPath()) req := httptest.NewRequest(http.MethodGet, path, nil) req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) @@ -362,7 +362,7 @@ func (s *LightwellPackagesSuite) TestListPackageVersionsPagination() { func (s *LightwellPackagesSuite) TestListPackageVersionsInvalidType() { t := s.T() - path := fmt.Sprintf("%s/lightwell/package_versions?type=bogus", api.FullRootPath()) + path := fmt.Sprintf("%s/lightwell/package_versions?content_type=bogus", api.FullRootPath()) req := httptest.NewRequest(http.MethodGet, path, nil) req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) diff --git a/pkg/lightwell/db/queries/advisories.sql b/pkg/lightwell/db/queries/advisories.sql index dab34cf45..4abf9359c 100644 --- a/pkg/lightwell/db/queries/advisories.sql +++ b/pkg/lightwell/db/queries/advisories.sql @@ -18,6 +18,10 @@ WHERE 1=1 sqlc.narg(repository_config_uuid)::uuid IS NULL OR la.repository_configuration_uuid = sqlc.narg(repository_config_uuid)::uuid ) + AND ( + sqlc.narg(repo_name)::text IS NULL + OR la.repo_name = sqlc.narg(repo_name)::text + ) AND ( sqlc.narg(package_name)::text IS NULL OR la.package_name ILIKE '%' || sqlc.narg(package_name)::text || '%' From d18fec37d5c9e57f4aed6341efbc5ea53b20a610 Mon Sep 17 00:00:00 2001 From: etsien Date: Mon, 24 Aug 2026 14:18:14 -0400 Subject: [PATCH 05/47] add tests --- pkg/handler/lightwell_advisories_test.go | 39 ++++++ pkg/handler/lightwell_packages_test.go | 146 +++++++++++++++++++++ pkg/lightwell/db/store/store_test.go | 156 +++++++++++++++++++++++ 3 files changed, 341 insertions(+) diff --git a/pkg/handler/lightwell_advisories_test.go b/pkg/handler/lightwell_advisories_test.go index dcd0d33bc..9229e4c15 100644 --- a/pkg/handler/lightwell_advisories_test.go +++ b/pkg/handler/lightwell_advisories_test.go @@ -165,6 +165,45 @@ func (s *LightwellAdvisorySuite) TestListAdvisoriesFilterByRepoName() { assert.Equal(t, http.StatusOK, code) } +func (s *LightwellAdvisorySuite) TestNestedRepoAdvisoriesAlias() { + t := s.T() + + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { + return arg.RepoName != nil && *arg.RepoName == "java-remediated" + })).Return([]store.ListAdvisoriesRow{ + { + Uuid: uuid.New(), + AdvisoryID: "CVE-2024-5678", + Severity: "important", + SeverityOrder: 3, + Details: "Test advisory via nested route", + ReferenceUrls: []string{}, + PackageName: "spring-core", + FixedVersions: []string{"5.3.18.rhlw-00003"}, + RepoName: "java-remediated", + CreatedAt: time.Now(), + TotalCount: 1, + }, + }, nil) + + path := fmt.Sprintf("%s/lightwell/repositories/java-remediated/advisories", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "CVE-2024-5678", resp.Data[0].AdvisoryID) + assert.Equal(t, "java-remediated", resp.Data[0].Repository) +} + func (s *LightwellAdvisorySuite) TestListAdvisoriesEmptyResult() { t := s.T() diff --git a/pkg/handler/lightwell_packages_test.go b/pkg/handler/lightwell_packages_test.go index eb2172cb3..39d99a623 100644 --- a/pkg/handler/lightwell_packages_test.go +++ b/pkg/handler/lightwell_packages_test.go @@ -12,6 +12,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/content-services/content-sources-backend/pkg/middleware" "github.com/content-services/content-sources-backend/pkg/test" test_handler "github.com/content-services/content-sources-backend/pkg/test/handler" @@ -390,3 +391,148 @@ func (s *LightwellPackagesSuite) TestListPackageVersionsEmptyResult() { assert.NotNil(t, resp.Data) assert.Empty(t, resp.Data) } + +// --- resolves_cve_id / vulnerable_to_cve_id filter tests --- + +func (s *LightwellPackagesSuite) TestListPackageVersionsResolvesCveFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + s.querier.On("ListAdvisoriesByCveID", test.MockCtx(), "CVE-2024-9999").Return([]store.ListAdvisoriesByCveIDRow{ + { + PackageName: "jackson-databind", + FixedVersions: []string{"2.15.3.rhlw-00001"}, + RepoName: "lightwell/java/remediated", + Severity: "critical", + }, + }, nil) + + path := fmt.Sprintf("%s/lightwell/package_versions?resolves_cve_id=CVE-2024-9999", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, "2.15.3.rhlw-00001", resp.Data[0].Version) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsVulnerableToCveFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + // Advisory says jackson-databind is fixed at 2.15.3.rhlw-00001, so + // the older version 2.14.2.rhlw-00001 should be returned as vulnerable. + s.querier.On("ListAdvisoriesByCveID", test.MockCtx(), "CVE-2024-8888").Return([]store.ListAdvisoriesByCveIDRow{ + { + PackageName: "jackson-databind", + FixedVersions: []string{"2.15.3.rhlw-00001"}, + RepoName: "lightwell/java/remediated", + Severity: "important", + }, + }, nil) + + path := fmt.Sprintf("%s/lightwell/package_versions?vulnerable_to_cve_id=CVE-2024-8888", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, "2.14.2.rhlw-00001", resp.Data[0].Version) +} + +// --- nested repo-scoped alias tests --- + +func (s *LightwellPackagesSuite) TestNestedRepoPackagesAlias() { + t := s.T() + + mavenRepo := newMavenRepo() + mavenRepo.Name = "java-remediated" + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { return f.Origin == config.OriginLightwell }), + ).Return(api.RepositoryCollectionResponse{Data: []api.RepositoryResponse{mavenRepo}}, int64(1), nil) + + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/repositories/java-remediated/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) +} + +func (s *LightwellPackagesSuite) TestNestedRepoPackageVersionsAlias() { + t := s.T() + + mavenRepo := newMavenRepo() + mavenRepo.Name = "java-remediated" + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { return f.Origin == config.OriginLightwell }), + ).Return(api.RepositoryCollectionResponse{Data: []api.RepositoryResponse{mavenRepo}}, int64(1), nil) + + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/repositories/java-remediated/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) + assert.Len(t, resp.Data, 2) +} diff --git a/pkg/lightwell/db/store/store_test.go b/pkg/lightwell/db/store/store_test.go index 77a897ea7..e2ef468c0 100644 --- a/pkg/lightwell/db/store/store_test.go +++ b/pkg/lightwell/db/store/store_test.go @@ -11,6 +11,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/google/uuid" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgxpool" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -495,3 +496,158 @@ func TestStore_ListCustomerIds(t *testing.T) { assert.Contains(t, ids, customerA) assert.Contains(t, ids, customerB) } + +// --- Advisory query integration tests --- + +func insertTestAdvisories(t *testing.T, ctx context.Context, tx pgx.Tx) uuid.UUID { + repoUUID := uuid.New() + _, err := tx.Exec(ctx, `INSERT INTO repository_configurations (uuid) VALUES ($1)`, repoUUID) + require.NoError(t, err) + + advisories := []struct { + id string + severity string + severityOrder int + packageName string + fixedVersions []string + repoName string + }{ + {"CVE-2024-1001", "critical", 4, "spring-core", []string{"5.3.18.rhlw-00003"}, "lightwell/java/remediated"}, + {"CVE-2024-1002", "important", 3, "jackson-databind", []string{"2.15.3.rhlw-00001"}, "lightwell/java/remediated"}, + {"CVE-2024-1003", "moderate", 2, "requests", []string{"2.31.0.rhlw-00001"}, "lightwell/python/remediated"}, + {"CVE-2024-1001", "critical", 4, "jackson-databind", []string{"2.14.2.rhlw-00001", "2.15.3.rhlw-00001"}, "lightwell/java/remediated"}, + } + + for _, adv := range advisories { + _, err := tx.Exec(ctx, ` + INSERT INTO lightwell_advisories ( + uuid, advisory_id, severity, severity_order, details, + reference_urls, package_name, fixed_versions, + repo_name, repository_configuration_uuid, checksum + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, + uuid.New(), adv.id, adv.severity, adv.severityOrder, + "test advisory details for "+adv.packageName, + []string{"https://access.redhat.com/security/cve/" + adv.id}, + adv.packageName, adv.fixedVersions, + adv.repoName, repoUUID, fmt.Sprintf("checksum-%s-%s", adv.id, adv.packageName), + ) + require.NoError(t, err) + } + return repoUUID +} + +func TestStore_ListAdvisories(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + PageLimit: 100, + PageOffset: 0, + }) + require.NoError(t, err) + assert.Len(t, rows, 4) + assert.Equal(t, int64(4), rows[0].TotalCount) + // Ordered by severity_order DESC + assert.Equal(t, int16(4), rows[0].SeverityOrder) +} + +func TestStore_ListAdvisoriesFilterByPackageName(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + name := "jackson" + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + PackageName: &name, + PageLimit: 100, + PageOffset: 0, + }) + require.NoError(t, err) + assert.Len(t, rows, 2) + for _, r := range rows { + assert.Contains(t, r.PackageName, "jackson") + } +} + +func TestStore_ListAdvisoriesFilterBySeverityMin(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + SeverityMin: pgtype.Int2{Int16: 3, Valid: true}, + PageLimit: 100, + PageOffset: 0, + }) + require.NoError(t, err) + assert.Len(t, rows, 3) + for _, r := range rows { + assert.GreaterOrEqual(t, r.SeverityOrder, int16(3)) + } +} + +func TestStore_ListAdvisoriesFilterByRepoName(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + repoName := "lightwell/python/remediated" + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + RepoName: &repoName, + PageLimit: 100, + PageOffset: 0, + }) + require.NoError(t, err) + assert.Len(t, rows, 1) + assert.Equal(t, "requests", rows[0].PackageName) +} + +func TestStore_CountAdvisoriesByRepo(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + repoUUID := insertTestAdvisories(t, ctx, tx) + + count, err := q.CountAdvisoriesByRepo(ctx, repoUUID) + require.NoError(t, err) + assert.Equal(t, int64(4), count) +} + +func TestStore_ListAdvisoriesByCveID(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisoriesByCveID(ctx, "CVE-2024-1001") + require.NoError(t, err) + assert.Len(t, rows, 2) + + packageNames := map[string]bool{} + for _, r := range rows { + packageNames[r.PackageName] = true + assert.Equal(t, "critical", r.Severity) + } + assert.True(t, packageNames["spring-core"]) + assert.True(t, packageNames["jackson-databind"]) +} + +func TestStore_ListAdvisoriesByPackage(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisoriesByPackage(ctx, "jackson-databind") + require.NoError(t, err) + assert.Len(t, rows, 2) + for _, r := range rows { + assert.NotEmpty(t, r.AdvisoryID) + assert.NotEmpty(t, r.FixedVersions) + } +} From bcdc0d2a5f966925c1073350c5a557f9bf76ef89 Mon Sep 17 00:00:00 2001 From: etsien Date: Mon, 24 Aug 2026 14:18:17 -0400 Subject: [PATCH 06/47] Create advisories.sql.go --- pkg/lightwell/db/store/advisories.sql.go | 224 +++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 pkg/lightwell/db/store/advisories.sql.go diff --git a/pkg/lightwell/db/store/advisories.sql.go b/pkg/lightwell/db/store/advisories.sql.go new file mode 100644 index 000000000..a94b9c1e3 --- /dev/null +++ b/pkg/lightwell/db/store/advisories.sql.go @@ -0,0 +1,224 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: advisories.sql + +package store + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" +) + +const countAdvisoriesByRepo = `-- name: CountAdvisoriesByRepo :one +SELECT COUNT(*)::bigint AS total +FROM lightwell_advisories la +WHERE la.repository_configuration_uuid = $1::uuid +` + +func (q *Queries) CountAdvisoriesByRepo(ctx context.Context, repositoryConfigUuid uuid.UUID) (int64, error) { + row := q.db.QueryRow(ctx, countAdvisoriesByRepo, repositoryConfigUuid) + var total int64 + err := row.Scan(&total) + return total, err +} + +const listAdvisories = `-- name: ListAdvisories :many +SELECT + la.uuid, + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.reference_urls, + la.package_name, + la.fixed_versions, + la.repo_name, + la.repository_configuration_uuid, + la.created_at, + COUNT(*) OVER() AS total_count +FROM lightwell_advisories la +WHERE 1=1 + AND ( + $1::uuid IS NULL + OR la.repository_configuration_uuid = $1::uuid + ) + AND ( + $2::text IS NULL + OR la.repo_name = $2::text + ) + AND ( + $3::text IS NULL + OR la.package_name ILIKE '%' || $3::text || '%' + ) + AND ( + $4::smallint IS NULL + OR la.severity_order >= $4::smallint + ) + AND ( + $5::text IS NULL + OR la.advisory_id = $5::text + ) +ORDER BY la.severity_order DESC, la.created_at DESC +LIMIT $7 OFFSET $6 +` + +type ListAdvisoriesParams struct { + RepositoryConfigUuid pgtype.UUID `json:"repository_config_uuid"` + RepoName *string `json:"repo_name"` + PackageName *string `json:"package_name"` + SeverityMin pgtype.Int2 `json:"severity_min"` + CveID *string `json:"cve_id"` + PageOffset int32 `json:"page_offset"` + PageLimit int32 `json:"page_limit"` +} + +type ListAdvisoriesRow struct { + Uuid uuid.UUID `json:"uuid"` + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + SeverityOrder int16 `json:"severity_order"` + Details string `json:"details"` + ReferenceUrls []string `json:"reference_urls"` + PackageName string `json:"package_name"` + FixedVersions []string `json:"fixed_versions"` + RepoName string `json:"repo_name"` + RepositoryConfigurationUuid uuid.UUID `json:"repository_configuration_uuid"` + CreatedAt time.Time `json:"created_at"` + TotalCount int64 `json:"total_count"` +} + +func (q *Queries) ListAdvisories(ctx context.Context, arg ListAdvisoriesParams) ([]ListAdvisoriesRow, error) { + rows, err := q.db.Query(ctx, listAdvisories, + arg.RepositoryConfigUuid, + arg.RepoName, + arg.PackageName, + arg.SeverityMin, + arg.CveID, + arg.PageOffset, + arg.PageLimit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListAdvisoriesRow{} + for rows.Next() { + var i ListAdvisoriesRow + if err := rows.Scan( + &i.Uuid, + &i.AdvisoryID, + &i.Severity, + &i.SeverityOrder, + &i.Details, + &i.ReferenceUrls, + &i.PackageName, + &i.FixedVersions, + &i.RepoName, + &i.RepositoryConfigurationUuid, + &i.CreatedAt, + &i.TotalCount, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAdvisoriesByCveID = `-- name: ListAdvisoriesByCveID :many +SELECT + la.package_name, + la.fixed_versions, + la.repo_name, + la.severity +FROM lightwell_advisories la +WHERE la.advisory_id = $1::text +` + +type ListAdvisoriesByCveIDRow struct { + PackageName string `json:"package_name"` + FixedVersions []string `json:"fixed_versions"` + RepoName string `json:"repo_name"` + Severity string `json:"severity"` +} + +func (q *Queries) ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]ListAdvisoriesByCveIDRow, error) { + rows, err := q.db.Query(ctx, listAdvisoriesByCveID, cveID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListAdvisoriesByCveIDRow{} + for rows.Next() { + var i ListAdvisoriesByCveIDRow + if err := rows.Scan( + &i.PackageName, + &i.FixedVersions, + &i.RepoName, + &i.Severity, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAdvisoriesByPackage = `-- name: ListAdvisoriesByPackage :many +SELECT + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.fixed_versions, + la.repo_name +FROM lightwell_advisories la +WHERE la.package_name = $1::text +ORDER BY la.severity_order DESC, la.created_at DESC +` + +type ListAdvisoriesByPackageRow struct { + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + SeverityOrder int16 `json:"severity_order"` + Details string `json:"details"` + FixedVersions []string `json:"fixed_versions"` + RepoName string `json:"repo_name"` +} + +func (q *Queries) ListAdvisoriesByPackage(ctx context.Context, packageName string) ([]ListAdvisoriesByPackageRow, error) { + rows, err := q.db.Query(ctx, listAdvisoriesByPackage, packageName) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListAdvisoriesByPackageRow{} + for rows.Next() { + var i ListAdvisoriesByPackageRow + if err := rows.Scan( + &i.AdvisoryID, + &i.Severity, + &i.SeverityOrder, + &i.Details, + &i.FixedVersions, + &i.RepoName, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} From a71bf0407a12a8f6191bfd45384fc8d13f802dec Mon Sep 17 00:00:00 2001 From: etsien Date: Mon, 24 Aug 2026 14:38:14 -0400 Subject: [PATCH 07/47] revert docs changes --- api/docs.go | 487 +++--------------------------------------- api/openapi.json | 537 +++-------------------------------------------- 2 files changed, 52 insertions(+), 972 deletions(-) diff --git a/api/docs.go b/api/docs.go index 09ba67443..1589607e4 100644 --- a/api/docs.go +++ b/api/docs.go @@ -144,9 +144,9 @@ const docTemplate = `{ "in": "query" }, { - "type": "boolean", - "description": "Filter by coverage status (true = covered, false = not covered)", - "name": "covered", + "type": "string", + "description": "Filter by package match status (possible values: in_network, not_in_network)", + "name": "status", "in": "query" }, { @@ -166,7 +166,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api.CoverageReportPackageCollectionResponse" + "$ref": "#/definitions/api.CoverageReportPackagesResponse" } }, "400": { @@ -282,198 +282,6 @@ const docTemplate = `{ } } }, - "/lightwell/beacon/vulnerabilities/": { - "get": { - "description": "List Lightwell vulnerabilities for a customer, with filters, pagination, and aggregate counts.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "lightwell_vulnerabilities" - ], - "summary": "List Lightwell vulnerabilities", - "operationId": "listLightwellVulnerabilities", - "parameters": [ - { - "type": "string", - "description": "Customer ID (required).", - "name": "customer_id", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "Comma-separated severities to filter on.", - "name": "severity", - "in": "query" - }, - { - "type": "string", - "description": "Comma-separated stages to filter on.", - "name": "stage", - "in": "query" - }, - { - "type": "string", - "description": "Comma-separated complexities to filter on (Standard, Complex, Extensive).", - "name": "complexity", - "in": "query" - }, - { - "type": "string", - "description": "Comma-separated Lightwell support ticket IDs to filter on.", - "name": "ltwlsupt_ticket_id", - "in": "query" - }, - { - "type": "string", - "description": "Comma-separated flags to filter on (embargo, duplicate, blocked).", - "name": "flag", - "in": "query" - }, - { - "type": "string", - "description": "Search vulnerability_id, component_name, and title. Minimum 2 characters.", - "name": "search", - "in": "query" - }, - { - "type": "integer", - "description": "Starting point for retrieving a subset of results. Default value:` + "`" + `0` + "`" + `.", - "name": "offset", - "in": "query" - }, - { - "type": "integer", - "description": "Number of items to include in response. Default value: ` + "`" + `100` + "`" + `. Maximum: ` + "`" + `200` + "`" + `.", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/api.LightwellVulnerabilityCollectionResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/errors.ErrorResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/errors.ErrorResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/errors.ErrorResponse" - } - } - } - } - }, - "/lightwell/beacon/vulnerabilities/customers/": { - "get": { - "description": "List distinct customer IDs that have Lightwell vulnerabilities.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "lightwell_vulnerabilities" - ], - "summary": "List Lightwell customer IDs", - "operationId": "listLightwellCustomerIds", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/api.LightwellCustomerIdsResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/errors.ErrorResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/errors.ErrorResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/errors.ErrorResponse" - } - } - } - } - }, - "/lightwell/beacon/vulnerabilities/ltwlsupt-ticket-ids/": { - "get": { - "description": "List distinct Lightwell support ticket IDs for a customer.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "lightwell_vulnerabilities" - ], - "summary": "List Lightwell support ticket IDs", - "operationId": "listLightwellLtwlsuptTicketIds", - "parameters": [ - { - "type": "string", - "description": "Customer ID (required).", - "name": "customer_id", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/api.LightwellLtwlsuptTicketIdsResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/errors.ErrorResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/errors.ErrorResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/errors.ErrorResponse" - } - } - } - } - }, "/module_streams/search": { "post": { "description": "List modules and their streams for repositories", @@ -4859,52 +4667,40 @@ const docTemplate = `{ } } }, - "api.CoverageReportPackageCollectionResponse": { + "api.CoverageReportPackageItem": { "type": "object", "properties": { - "data": { - "description": "List of packages", - "type": "array", - "items": { - "$ref": "#/definitions/api.CoverageReportPackageResponse" - } + "ecosystem": { + "description": "Ecosystem of the package", + "type": "string" }, - "links": { - "description": "Navigation links", - "allOf": [ - { - "$ref": "#/definitions/api.Links" - } - ] + "name": { + "description": "Package name from the manifest", + "type": "string" }, - "meta": { - "description": "Pagination metadata", - "allOf": [ - { - "$ref": "#/definitions/api.ResponseMetadata" - } - ] + "status": { + "description": "Package match status (in_network, not_in_network)", + "type": "string" } } }, - "api.CoverageReportPackageResponse": { + "api.CoverageReportPackagesResponse": { "type": "object", "properties": { - "covered": { - "description": "Whether the package is covered (true = exact or partial match)", - "type": "boolean" + "limit": { + "type": "integer" }, - "ecosystem": { - "description": "Ecosystem of the package", - "type": "string" + "offset": { + "type": "integer" }, - "name": { - "description": "Package name from the manifest", - "type": "string" + "results": { + "type": "array", + "items": { + "$ref": "#/definitions/api.CoverageReportPackageItem" + } }, - "version": { - "description": "Package version from the manifest", - "type": "string" + "total": { + "type": "integer" } } }, @@ -5097,207 +4893,6 @@ const docTemplate = `{ } } }, - "api.LightwellCustomerIdsResponse": { - "type": "object", - "properties": { - "data": { - "description": "Customer IDs", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "api.LightwellLtwlsuptTicketIdsResponse": { - "type": "object", - "properties": { - "data": { - "description": "Lightwell support ticket IDs", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "api.LightwellVulnerabilityCollectionMeta": { - "type": "object", - "properties": { - "blocked_count": { - "description": "Count of blocked rows matching filters", - "type": "integer" - }, - "count": { - "description": "Total count of results", - "type": "integer" - }, - "critical_count": { - "description": "Count of Critical severity rows matching filters", - "type": "integer" - }, - "embargo_count": { - "description": "Count of embargoed rows matching filters", - "type": "integer" - }, - "limit": { - "description": "Limit of results used for the request", - "type": "integer" - }, - "offset": { - "description": "Offset into results used for the request", - "type": "integer" - }, - "stage_counts": { - "description": "Per-stage counts matching filters", - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int64" - } - } - } - }, - "api.LightwellVulnerabilityCollectionResponse": { - "type": "object", - "properties": { - "data": { - "description": "Requested Data", - "type": "array", - "items": { - "$ref": "#/definitions/api.LightwellVulnerabilityResponse" - } - }, - "links": { - "description": "Links to other pages of results", - "allOf": [ - { - "$ref": "#/definitions/api.Links" - } - ] - }, - "meta": { - "description": "Metadata about the request", - "allOf": [ - { - "$ref": "#/definitions/api.LightwellVulnerabilityCollectionMeta" - } - ] - } - } - }, - "api.LightwellVulnerabilityResponse": { - "type": "object", - "properties": { - "age_days": { - "description": "UTC calendar days since submitted_date", - "type": "integer" - }, - "blocked": { - "description": "True when stage is not Lightwell Network and age_days \u003e 30", - "type": "boolean" - }, - "complexity": { - "description": "Standard, Complex, or Extensive", - "type": "string" - }, - "component_name": { - "description": "Component / package name", - "type": "string" - }, - "component_version": { - "description": "Component version", - "type": "string" - }, - "customer_priority": { - "description": "Customer priority", - "type": "string" - }, - "cvss": { - "description": "CVSS score", - "type": "number" - }, - "cvss_vector": { - "description": "CVSS vector string", - "type": "string" - }, - "cwe": { - "description": "CWE identifier", - "type": "string" - }, - "description": { - "description": "Vulnerability description", - "type": "string" - }, - "duplicate": { - "description": "Duplicate flag", - "type": "boolean" - }, - "duplicate_of": { - "description": "Canonical vulnerability_id when duplicate", - "type": "string" - }, - "embargo": { - "description": "Embargo flag", - "type": "boolean" - }, - "exploit_tested": { - "description": "Whether an exploit was tested", - "type": "boolean" - }, - "language": { - "description": "Derived language (java, python, javascript, csharp)", - "type": "string" - }, - "last_updated": { - "description": "Last update timestamp", - "type": "string" - }, - "ltwlsupt_ticket_ids": { - "description": "Lightwell support ticket IDs", - "type": "array", - "items": { - "type": "string" - } - }, - "package": { - "description": "Alias of component_name", - "type": "string" - }, - "purl": { - "description": "Package URL", - "type": "string" - }, - "reproducer_included": { - "description": "Whether a reproducer is included", - "type": "boolean" - }, - "severity": { - "description": "Severity (Critical, Important, Moderate, Low)", - "type": "string" - }, - "stage": { - "description": "Workflow stage", - "type": "string" - }, - "submitted_date": { - "description": "Date the vulnerability was submitted", - "type": "string" - }, - "title": { - "description": "Vulnerability title", - "type": "string" - }, - "uuid": { - "description": "UUID of the vulnerability", - "type": "string" - }, - "vulnerability_id": { - "description": "Business identifier (e.g. LWL-2026-4401)", - "type": "string" - } - } - }, "api.Links": { "type": "object", "properties": { @@ -6065,11 +5660,6 @@ const docTemplate = `{ "description": "Number of packages last read in the repository", "type": "integer" }, - "packages_count": { - "description": "Lightwell: total distinct packages", - "type": "integer", - "readOnly": true - }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "type": "boolean", @@ -6080,11 +5670,6 @@ const docTemplate = `{ "type": "string", "readOnly": true }, - "remediations_count": { - "description": "Lightwell: total security advisories", - "type": "integer", - "readOnly": true - }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "type": "string", @@ -6111,11 +5696,6 @@ const docTemplate = `{ "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" }, - "versions_count": { - "description": "Lightwell: total distinct versions", - "type": "integer", - "readOnly": true - }, "warnings": { "description": "Warnings to alert user of mismatched fields if there is an existing repo with the same URL", "type": "array", @@ -6410,11 +5990,6 @@ const docTemplate = `{ "description": "Number of packages last read in the repository", "type": "integer" }, - "packages_count": { - "description": "Lightwell: total distinct packages", - "type": "integer", - "readOnly": true - }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "type": "boolean", @@ -6425,11 +6000,6 @@ const docTemplate = `{ "type": "string", "readOnly": true }, - "remediations_count": { - "description": "Lightwell: total security advisories", - "type": "integer", - "readOnly": true - }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "type": "string", @@ -6455,11 +6025,6 @@ const docTemplate = `{ "version_count": { "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" - }, - "versions_count": { - "description": "Lightwell: total distinct versions", - "type": "integer", - "readOnly": true } } }, diff --git a/api/openapi.json b/api/openapi.json index ed4ab8002..72edbafd7 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -87,51 +87,39 @@ }, "type": "object" }, - "api.CoverageReportPackageCollectionResponse": { + "api.CoverageReportPackageItem": { "properties": { - "data": { - "description": "List of packages", - "items": { - "$ref": "#/components/schemas/api.CoverageReportPackageResponse" - }, - "type": "array" + "ecosystem": { + "description": "Ecosystem of the package", + "type": "string" }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/api.Links" - } - ], - "description": "Navigation links" + "name": { + "description": "Package name from the manifest", + "type": "string" }, - "meta": { - "allOf": [ - { - "$ref": "#/components/schemas/api.ResponseMetadata" - } - ], - "description": "Pagination metadata" + "status": { + "description": "Package match status (in_network, not_in_network)", + "type": "string" } }, "type": "object" }, - "api.CoverageReportPackageResponse": { + "api.CoverageReportPackagesResponse": { "properties": { - "covered": { - "description": "Whether the package is covered (true = exact or partial match)", - "type": "boolean" + "limit": { + "type": "integer" }, - "ecosystem": { - "description": "Ecosystem of the package", - "type": "string" + "offset": { + "type": "integer" }, - "name": { - "description": "Package name from the manifest", - "type": "string" + "results": { + "items": { + "$ref": "#/components/schemas/api.CoverageReportPackageItem" + }, + "type": "array" }, - "version": { - "description": "Package version from the manifest", - "type": "string" + "total": { + "type": "integer" } }, "type": "object" @@ -325,207 +313,6 @@ }, "type": "object" }, - "api.LightwellCustomerIdsResponse": { - "properties": { - "data": { - "description": "Customer IDs", - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "api.LightwellLtwlsuptTicketIdsResponse": { - "properties": { - "data": { - "description": "Lightwell support ticket IDs", - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "api.LightwellVulnerabilityCollectionMeta": { - "properties": { - "blocked_count": { - "description": "Count of blocked rows matching filters", - "type": "integer" - }, - "count": { - "description": "Total count of results", - "type": "integer" - }, - "critical_count": { - "description": "Count of Critical severity rows matching filters", - "type": "integer" - }, - "embargo_count": { - "description": "Count of embargoed rows matching filters", - "type": "integer" - }, - "limit": { - "description": "Limit of results used for the request", - "type": "integer" - }, - "offset": { - "description": "Offset into results used for the request", - "type": "integer" - }, - "stage_counts": { - "additionalProperties": { - "format": "int64", - "type": "integer" - }, - "description": "Per-stage counts matching filters", - "type": "object" - } - }, - "type": "object" - }, - "api.LightwellVulnerabilityCollectionResponse": { - "properties": { - "data": { - "description": "Requested Data", - "items": { - "$ref": "#/components/schemas/api.LightwellVulnerabilityResponse" - }, - "type": "array" - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/api.Links" - } - ], - "description": "Links to other pages of results" - }, - "meta": { - "allOf": [ - { - "$ref": "#/components/schemas/api.LightwellVulnerabilityCollectionMeta" - } - ], - "description": "Metadata about the request" - } - }, - "type": "object" - }, - "api.LightwellVulnerabilityResponse": { - "properties": { - "age_days": { - "description": "UTC calendar days since submitted_date", - "type": "integer" - }, - "blocked": { - "description": "True when stage is not Lightwell Network and age_days \u003e 30", - "type": "boolean" - }, - "complexity": { - "description": "Standard, Complex, or Extensive", - "type": "string" - }, - "component_name": { - "description": "Component / package name", - "type": "string" - }, - "component_version": { - "description": "Component version", - "type": "string" - }, - "customer_priority": { - "description": "Customer priority", - "type": "string" - }, - "cvss": { - "description": "CVSS score", - "type": "number" - }, - "cvss_vector": { - "description": "CVSS vector string", - "type": "string" - }, - "cwe": { - "description": "CWE identifier", - "type": "string" - }, - "description": { - "description": "Vulnerability description", - "type": "string" - }, - "duplicate": { - "description": "Duplicate flag", - "type": "boolean" - }, - "duplicate_of": { - "description": "Canonical vulnerability_id when duplicate", - "type": "string" - }, - "embargo": { - "description": "Embargo flag", - "type": "boolean" - }, - "exploit_tested": { - "description": "Whether an exploit was tested", - "type": "boolean" - }, - "language": { - "description": "Derived language (java, python, javascript, csharp)", - "type": "string" - }, - "last_updated": { - "description": "Last update timestamp", - "type": "string" - }, - "ltwlsupt_ticket_ids": { - "description": "Lightwell support ticket IDs", - "items": { - "type": "string" - }, - "type": "array" - }, - "package": { - "description": "Alias of component_name", - "type": "string" - }, - "purl": { - "description": "Package URL", - "type": "string" - }, - "reproducer_included": { - "description": "Whether a reproducer is included", - "type": "boolean" - }, - "severity": { - "description": "Severity (Critical, Important, Moderate, Low)", - "type": "string" - }, - "stage": { - "description": "Workflow stage", - "type": "string" - }, - "submitted_date": { - "description": "Date the vulnerability was submitted", - "type": "string" - }, - "title": { - "description": "Vulnerability title", - "type": "string" - }, - "uuid": { - "description": "UUID of the vulnerability", - "type": "string" - }, - "vulnerability_id": { - "description": "Business identifier (e.g. LWL-2026-4401)", - "type": "string" - } - }, - "type": "object" - }, "api.Links": { "properties": { "first": { @@ -1292,11 +1079,6 @@ "description": "Number of packages last read in the repository", "type": "integer" }, - "packages_count": { - "description": "Lightwell: total distinct packages", - "readOnly": true, - "type": "integer" - }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "readOnly": true, @@ -1307,11 +1089,6 @@ "readOnly": true, "type": "string" }, - "remediations_count": { - "description": "Lightwell: total security advisories", - "readOnly": true, - "type": "integer" - }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "readOnly": true, @@ -1338,11 +1115,6 @@ "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" }, - "versions_count": { - "description": "Lightwell: total distinct versions", - "readOnly": true, - "type": "integer" - }, "warnings": { "description": "Warnings to alert user of mismatched fields if there is an existing repo with the same URL", "items": { @@ -1637,11 +1409,6 @@ "description": "Number of packages last read in the repository", "type": "integer" }, - "packages_count": { - "description": "Lightwell: total distinct packages", - "readOnly": true, - "type": "integer" - }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "readOnly": true, @@ -1652,11 +1419,6 @@ "readOnly": true, "type": "string" }, - "remediations_count": { - "description": "Lightwell: total security advisories", - "readOnly": true, - "type": "integer" - }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "readOnly": true, @@ -1682,11 +1444,6 @@ "version_count": { "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" - }, - "versions_count": { - "description": "Lightwell: total distinct versions", - "readOnly": true, - "type": "integer" } }, "type": "object" @@ -3023,11 +2780,11 @@ } }, { - "description": "Filter by coverage status (true = covered, false = not covered)", + "description": "Filter by package match status (possible values: in_network, not_in_network)", "in": "query", - "name": "covered", + "name": "status", "schema": { - "type": "boolean" + "type": "string" } }, { @@ -3052,7 +2809,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/api.CoverageReportPackageCollectionResponse" + "$ref": "#/components/schemas/api.CoverageReportPackagesResponse" } } }, @@ -3204,248 +2961,6 @@ ] } }, - "/lightwell/beacon/vulnerabilities/": { - "get": { - "description": "List Lightwell vulnerabilities for a customer, with filters, pagination, and aggregate counts.", - "operationId": "listLightwellVulnerabilities", - "parameters": [ - { - "description": "Customer ID (required).", - "in": "query", - "name": "customer_id", - "required": true, - "schema": { - "type": "string" - } - }, - { - "description": "Comma-separated severities to filter on.", - "in": "query", - "name": "severity", - "schema": { - "type": "string" - } - }, - { - "description": "Comma-separated stages to filter on.", - "in": "query", - "name": "stage", - "schema": { - "type": "string" - } - }, - { - "description": "Comma-separated complexities to filter on (Standard, Complex, Extensive).", - "in": "query", - "name": "complexity", - "schema": { - "type": "string" - } - }, - { - "description": "Comma-separated Lightwell support ticket IDs to filter on.", - "in": "query", - "name": "ltwlsupt_ticket_id", - "schema": { - "type": "string" - } - }, - { - "description": "Comma-separated flags to filter on (embargo, duplicate, blocked).", - "in": "query", - "name": "flag", - "schema": { - "type": "string" - } - }, - { - "description": "Search vulnerability_id, component_name, and title. Minimum 2 characters.", - "in": "query", - "name": "search", - "schema": { - "type": "string" - } - }, - { - "description": "Starting point for retrieving a subset of results. Default value:`0`.", - "in": "query", - "name": "offset", - "schema": { - "type": "integer" - } - }, - { - "description": "Number of items to include in response. Default value: `100`. Maximum: `200`.", - "in": "query", - "name": "limit", - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/api.LightwellVulnerabilityCollectionResponse" - } - } - }, - "description": "OK" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errors.ErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "401": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errors.ErrorResponse" - } - } - }, - "description": "Unauthorized" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errors.ErrorResponse" - } - } - }, - "description": "Internal Server Error" - } - }, - "summary": "List Lightwell vulnerabilities", - "tags": [ - "lightwell_vulnerabilities" - ] - } - }, - "/lightwell/beacon/vulnerabilities/customers/": { - "get": { - "description": "List distinct customer IDs that have Lightwell vulnerabilities.", - "operationId": "listLightwellCustomerIds", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/api.LightwellCustomerIdsResponse" - } - } - }, - "description": "OK" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errors.ErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "401": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errors.ErrorResponse" - } - } - }, - "description": "Unauthorized" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errors.ErrorResponse" - } - } - }, - "description": "Internal Server Error" - } - }, - "summary": "List Lightwell customer IDs", - "tags": [ - "lightwell_vulnerabilities" - ] - } - }, - "/lightwell/beacon/vulnerabilities/ltwlsupt-ticket-ids/": { - "get": { - "description": "List distinct Lightwell support ticket IDs for a customer.", - "operationId": "listLightwellLtwlsuptTicketIds", - "parameters": [ - { - "description": "Customer ID (required).", - "in": "query", - "name": "customer_id", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/api.LightwellLtwlsuptTicketIdsResponse" - } - } - }, - "description": "OK" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errors.ErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "401": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errors.ErrorResponse" - } - } - }, - "description": "Unauthorized" - }, - "500": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errors.ErrorResponse" - } - } - }, - "description": "Internal Server Error" - } - }, - "summary": "List Lightwell support ticket IDs", - "tags": [ - "lightwell_vulnerabilities" - ] - } - }, "/module_streams/search": { "post": { "description": "List modules and their streams for repositories", From 0d66a2f8a6994d2292e7613c3008cf2a62212e14 Mon Sep 17 00:00:00 2001 From: etsien Date: Mon, 24 Aug 2026 14:39:23 -0400 Subject: [PATCH 08/47] Revert "revert docs changes" This reverts commit a71bf0407a12a8f6191bfd45384fc8d13f802dec. --- api/docs.go | 487 +++++++++++++++++++++++++++++++++++++++--- api/openapi.json | 537 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 972 insertions(+), 52 deletions(-) diff --git a/api/docs.go b/api/docs.go index 1589607e4..09ba67443 100644 --- a/api/docs.go +++ b/api/docs.go @@ -144,9 +144,9 @@ const docTemplate = `{ "in": "query" }, { - "type": "string", - "description": "Filter by package match status (possible values: in_network, not_in_network)", - "name": "status", + "type": "boolean", + "description": "Filter by coverage status (true = covered, false = not covered)", + "name": "covered", "in": "query" }, { @@ -166,7 +166,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api.CoverageReportPackagesResponse" + "$ref": "#/definitions/api.CoverageReportPackageCollectionResponse" } }, "400": { @@ -282,6 +282,198 @@ const docTemplate = `{ } } }, + "/lightwell/beacon/vulnerabilities/": { + "get": { + "description": "List Lightwell vulnerabilities for a customer, with filters, pagination, and aggregate counts.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell_vulnerabilities" + ], + "summary": "List Lightwell vulnerabilities", + "operationId": "listLightwellVulnerabilities", + "parameters": [ + { + "type": "string", + "description": "Customer ID (required).", + "name": "customer_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Comma-separated severities to filter on.", + "name": "severity", + "in": "query" + }, + { + "type": "string", + "description": "Comma-separated stages to filter on.", + "name": "stage", + "in": "query" + }, + { + "type": "string", + "description": "Comma-separated complexities to filter on (Standard, Complex, Extensive).", + "name": "complexity", + "in": "query" + }, + { + "type": "string", + "description": "Comma-separated Lightwell support ticket IDs to filter on.", + "name": "ltwlsupt_ticket_id", + "in": "query" + }, + { + "type": "string", + "description": "Comma-separated flags to filter on (embargo, duplicate, blocked).", + "name": "flag", + "in": "query" + }, + { + "type": "string", + "description": "Search vulnerability_id, component_name, and title. Minimum 2 characters.", + "name": "search", + "in": "query" + }, + { + "type": "integer", + "description": "Starting point for retrieving a subset of results. Default value:` + "`" + `0` + "`" + `.", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "description": "Number of items to include in response. Default value: ` + "`" + `100` + "`" + `. Maximum: ` + "`" + `200` + "`" + `.", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellVulnerabilityCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, + "/lightwell/beacon/vulnerabilities/customers/": { + "get": { + "description": "List distinct customer IDs that have Lightwell vulnerabilities.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell_vulnerabilities" + ], + "summary": "List Lightwell customer IDs", + "operationId": "listLightwellCustomerIds", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellCustomerIdsResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, + "/lightwell/beacon/vulnerabilities/ltwlsupt-ticket-ids/": { + "get": { + "description": "List distinct Lightwell support ticket IDs for a customer.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell_vulnerabilities" + ], + "summary": "List Lightwell support ticket IDs", + "operationId": "listLightwellLtwlsuptTicketIds", + "parameters": [ + { + "type": "string", + "description": "Customer ID (required).", + "name": "customer_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellLtwlsuptTicketIdsResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, "/module_streams/search": { "post": { "description": "List modules and their streams for repositories", @@ -4667,40 +4859,52 @@ const docTemplate = `{ } } }, - "api.CoverageReportPackageItem": { + "api.CoverageReportPackageCollectionResponse": { "type": "object", "properties": { - "ecosystem": { - "description": "Ecosystem of the package", - "type": "string" + "data": { + "description": "List of packages", + "type": "array", + "items": { + "$ref": "#/definitions/api.CoverageReportPackageResponse" + } }, - "name": { - "description": "Package name from the manifest", - "type": "string" + "links": { + "description": "Navigation links", + "allOf": [ + { + "$ref": "#/definitions/api.Links" + } + ] }, - "status": { - "description": "Package match status (in_network, not_in_network)", - "type": "string" + "meta": { + "description": "Pagination metadata", + "allOf": [ + { + "$ref": "#/definitions/api.ResponseMetadata" + } + ] } } }, - "api.CoverageReportPackagesResponse": { + "api.CoverageReportPackageResponse": { "type": "object", "properties": { - "limit": { - "type": "integer" + "covered": { + "description": "Whether the package is covered (true = exact or partial match)", + "type": "boolean" }, - "offset": { - "type": "integer" + "ecosystem": { + "description": "Ecosystem of the package", + "type": "string" }, - "results": { - "type": "array", - "items": { - "$ref": "#/definitions/api.CoverageReportPackageItem" - } + "name": { + "description": "Package name from the manifest", + "type": "string" }, - "total": { - "type": "integer" + "version": { + "description": "Package version from the manifest", + "type": "string" } } }, @@ -4893,6 +5097,207 @@ const docTemplate = `{ } } }, + "api.LightwellCustomerIdsResponse": { + "type": "object", + "properties": { + "data": { + "description": "Customer IDs", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "api.LightwellLtwlsuptTicketIdsResponse": { + "type": "object", + "properties": { + "data": { + "description": "Lightwell support ticket IDs", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "api.LightwellVulnerabilityCollectionMeta": { + "type": "object", + "properties": { + "blocked_count": { + "description": "Count of blocked rows matching filters", + "type": "integer" + }, + "count": { + "description": "Total count of results", + "type": "integer" + }, + "critical_count": { + "description": "Count of Critical severity rows matching filters", + "type": "integer" + }, + "embargo_count": { + "description": "Count of embargoed rows matching filters", + "type": "integer" + }, + "limit": { + "description": "Limit of results used for the request", + "type": "integer" + }, + "offset": { + "description": "Offset into results used for the request", + "type": "integer" + }, + "stage_counts": { + "description": "Per-stage counts matching filters", + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + } + } + }, + "api.LightwellVulnerabilityCollectionResponse": { + "type": "object", + "properties": { + "data": { + "description": "Requested Data", + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellVulnerabilityResponse" + } + }, + "links": { + "description": "Links to other pages of results", + "allOf": [ + { + "$ref": "#/definitions/api.Links" + } + ] + }, + "meta": { + "description": "Metadata about the request", + "allOf": [ + { + "$ref": "#/definitions/api.LightwellVulnerabilityCollectionMeta" + } + ] + } + } + }, + "api.LightwellVulnerabilityResponse": { + "type": "object", + "properties": { + "age_days": { + "description": "UTC calendar days since submitted_date", + "type": "integer" + }, + "blocked": { + "description": "True when stage is not Lightwell Network and age_days \u003e 30", + "type": "boolean" + }, + "complexity": { + "description": "Standard, Complex, or Extensive", + "type": "string" + }, + "component_name": { + "description": "Component / package name", + "type": "string" + }, + "component_version": { + "description": "Component version", + "type": "string" + }, + "customer_priority": { + "description": "Customer priority", + "type": "string" + }, + "cvss": { + "description": "CVSS score", + "type": "number" + }, + "cvss_vector": { + "description": "CVSS vector string", + "type": "string" + }, + "cwe": { + "description": "CWE identifier", + "type": "string" + }, + "description": { + "description": "Vulnerability description", + "type": "string" + }, + "duplicate": { + "description": "Duplicate flag", + "type": "boolean" + }, + "duplicate_of": { + "description": "Canonical vulnerability_id when duplicate", + "type": "string" + }, + "embargo": { + "description": "Embargo flag", + "type": "boolean" + }, + "exploit_tested": { + "description": "Whether an exploit was tested", + "type": "boolean" + }, + "language": { + "description": "Derived language (java, python, javascript, csharp)", + "type": "string" + }, + "last_updated": { + "description": "Last update timestamp", + "type": "string" + }, + "ltwlsupt_ticket_ids": { + "description": "Lightwell support ticket IDs", + "type": "array", + "items": { + "type": "string" + } + }, + "package": { + "description": "Alias of component_name", + "type": "string" + }, + "purl": { + "description": "Package URL", + "type": "string" + }, + "reproducer_included": { + "description": "Whether a reproducer is included", + "type": "boolean" + }, + "severity": { + "description": "Severity (Critical, Important, Moderate, Low)", + "type": "string" + }, + "stage": { + "description": "Workflow stage", + "type": "string" + }, + "submitted_date": { + "description": "Date the vulnerability was submitted", + "type": "string" + }, + "title": { + "description": "Vulnerability title", + "type": "string" + }, + "uuid": { + "description": "UUID of the vulnerability", + "type": "string" + }, + "vulnerability_id": { + "description": "Business identifier (e.g. LWL-2026-4401)", + "type": "string" + } + } + }, "api.Links": { "type": "object", "properties": { @@ -5660,6 +6065,11 @@ const docTemplate = `{ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "type": "integer", + "readOnly": true + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "type": "boolean", @@ -5670,6 +6080,11 @@ const docTemplate = `{ "type": "string", "readOnly": true }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "type": "integer", + "readOnly": true + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "type": "string", @@ -5696,6 +6111,11 @@ const docTemplate = `{ "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "type": "integer", + "readOnly": true + }, "warnings": { "description": "Warnings to alert user of mismatched fields if there is an existing repo with the same URL", "type": "array", @@ -5990,6 +6410,11 @@ const docTemplate = `{ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "type": "integer", + "readOnly": true + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "type": "boolean", @@ -6000,6 +6425,11 @@ const docTemplate = `{ "type": "string", "readOnly": true }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "type": "integer", + "readOnly": true + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "type": "string", @@ -6025,6 +6455,11 @@ const docTemplate = `{ "version_count": { "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" + }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "type": "integer", + "readOnly": true } } }, diff --git a/api/openapi.json b/api/openapi.json index 72edbafd7..ed4ab8002 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -87,39 +87,51 @@ }, "type": "object" }, - "api.CoverageReportPackageItem": { + "api.CoverageReportPackageCollectionResponse": { "properties": { - "ecosystem": { - "description": "Ecosystem of the package", - "type": "string" + "data": { + "description": "List of packages", + "items": { + "$ref": "#/components/schemas/api.CoverageReportPackageResponse" + }, + "type": "array" }, - "name": { - "description": "Package name from the manifest", - "type": "string" + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/api.Links" + } + ], + "description": "Navigation links" }, - "status": { - "description": "Package match status (in_network, not_in_network)", - "type": "string" + "meta": { + "allOf": [ + { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + ], + "description": "Pagination metadata" } }, "type": "object" }, - "api.CoverageReportPackagesResponse": { + "api.CoverageReportPackageResponse": { "properties": { - "limit": { - "type": "integer" + "covered": { + "description": "Whether the package is covered (true = exact or partial match)", + "type": "boolean" }, - "offset": { - "type": "integer" + "ecosystem": { + "description": "Ecosystem of the package", + "type": "string" }, - "results": { - "items": { - "$ref": "#/components/schemas/api.CoverageReportPackageItem" - }, - "type": "array" + "name": { + "description": "Package name from the manifest", + "type": "string" }, - "total": { - "type": "integer" + "version": { + "description": "Package version from the manifest", + "type": "string" } }, "type": "object" @@ -313,6 +325,207 @@ }, "type": "object" }, + "api.LightwellCustomerIdsResponse": { + "properties": { + "data": { + "description": "Customer IDs", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "api.LightwellLtwlsuptTicketIdsResponse": { + "properties": { + "data": { + "description": "Lightwell support ticket IDs", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "api.LightwellVulnerabilityCollectionMeta": { + "properties": { + "blocked_count": { + "description": "Count of blocked rows matching filters", + "type": "integer" + }, + "count": { + "description": "Total count of results", + "type": "integer" + }, + "critical_count": { + "description": "Count of Critical severity rows matching filters", + "type": "integer" + }, + "embargo_count": { + "description": "Count of embargoed rows matching filters", + "type": "integer" + }, + "limit": { + "description": "Limit of results used for the request", + "type": "integer" + }, + "offset": { + "description": "Offset into results used for the request", + "type": "integer" + }, + "stage_counts": { + "additionalProperties": { + "format": "int64", + "type": "integer" + }, + "description": "Per-stage counts matching filters", + "type": "object" + } + }, + "type": "object" + }, + "api.LightwellVulnerabilityCollectionResponse": { + "properties": { + "data": { + "description": "Requested Data", + "items": { + "$ref": "#/components/schemas/api.LightwellVulnerabilityResponse" + }, + "type": "array" + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/api.Links" + } + ], + "description": "Links to other pages of results" + }, + "meta": { + "allOf": [ + { + "$ref": "#/components/schemas/api.LightwellVulnerabilityCollectionMeta" + } + ], + "description": "Metadata about the request" + } + }, + "type": "object" + }, + "api.LightwellVulnerabilityResponse": { + "properties": { + "age_days": { + "description": "UTC calendar days since submitted_date", + "type": "integer" + }, + "blocked": { + "description": "True when stage is not Lightwell Network and age_days \u003e 30", + "type": "boolean" + }, + "complexity": { + "description": "Standard, Complex, or Extensive", + "type": "string" + }, + "component_name": { + "description": "Component / package name", + "type": "string" + }, + "component_version": { + "description": "Component version", + "type": "string" + }, + "customer_priority": { + "description": "Customer priority", + "type": "string" + }, + "cvss": { + "description": "CVSS score", + "type": "number" + }, + "cvss_vector": { + "description": "CVSS vector string", + "type": "string" + }, + "cwe": { + "description": "CWE identifier", + "type": "string" + }, + "description": { + "description": "Vulnerability description", + "type": "string" + }, + "duplicate": { + "description": "Duplicate flag", + "type": "boolean" + }, + "duplicate_of": { + "description": "Canonical vulnerability_id when duplicate", + "type": "string" + }, + "embargo": { + "description": "Embargo flag", + "type": "boolean" + }, + "exploit_tested": { + "description": "Whether an exploit was tested", + "type": "boolean" + }, + "language": { + "description": "Derived language (java, python, javascript, csharp)", + "type": "string" + }, + "last_updated": { + "description": "Last update timestamp", + "type": "string" + }, + "ltwlsupt_ticket_ids": { + "description": "Lightwell support ticket IDs", + "items": { + "type": "string" + }, + "type": "array" + }, + "package": { + "description": "Alias of component_name", + "type": "string" + }, + "purl": { + "description": "Package URL", + "type": "string" + }, + "reproducer_included": { + "description": "Whether a reproducer is included", + "type": "boolean" + }, + "severity": { + "description": "Severity (Critical, Important, Moderate, Low)", + "type": "string" + }, + "stage": { + "description": "Workflow stage", + "type": "string" + }, + "submitted_date": { + "description": "Date the vulnerability was submitted", + "type": "string" + }, + "title": { + "description": "Vulnerability title", + "type": "string" + }, + "uuid": { + "description": "UUID of the vulnerability", + "type": "string" + }, + "vulnerability_id": { + "description": "Business identifier (e.g. LWL-2026-4401)", + "type": "string" + } + }, + "type": "object" + }, "api.Links": { "properties": { "first": { @@ -1079,6 +1292,11 @@ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "readOnly": true, + "type": "integer" + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "readOnly": true, @@ -1089,6 +1307,11 @@ "readOnly": true, "type": "string" }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "readOnly": true, + "type": "integer" + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "readOnly": true, @@ -1115,6 +1338,11 @@ "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "readOnly": true, + "type": "integer" + }, "warnings": { "description": "Warnings to alert user of mismatched fields if there is an existing repo with the same URL", "items": { @@ -1409,6 +1637,11 @@ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "readOnly": true, + "type": "integer" + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "readOnly": true, @@ -1419,6 +1652,11 @@ "readOnly": true, "type": "string" }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "readOnly": true, + "type": "integer" + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "readOnly": true, @@ -1444,6 +1682,11 @@ "version_count": { "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" + }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "readOnly": true, + "type": "integer" } }, "type": "object" @@ -2780,11 +3023,11 @@ } }, { - "description": "Filter by package match status (possible values: in_network, not_in_network)", + "description": "Filter by coverage status (true = covered, false = not covered)", "in": "query", - "name": "status", + "name": "covered", "schema": { - "type": "string" + "type": "boolean" } }, { @@ -2809,7 +3052,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/api.CoverageReportPackagesResponse" + "$ref": "#/components/schemas/api.CoverageReportPackageCollectionResponse" } } }, @@ -2961,6 +3204,248 @@ ] } }, + "/lightwell/beacon/vulnerabilities/": { + "get": { + "description": "List Lightwell vulnerabilities for a customer, with filters, pagination, and aggregate counts.", + "operationId": "listLightwellVulnerabilities", + "parameters": [ + { + "description": "Customer ID (required).", + "in": "query", + "name": "customer_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Comma-separated severities to filter on.", + "in": "query", + "name": "severity", + "schema": { + "type": "string" + } + }, + { + "description": "Comma-separated stages to filter on.", + "in": "query", + "name": "stage", + "schema": { + "type": "string" + } + }, + { + "description": "Comma-separated complexities to filter on (Standard, Complex, Extensive).", + "in": "query", + "name": "complexity", + "schema": { + "type": "string" + } + }, + { + "description": "Comma-separated Lightwell support ticket IDs to filter on.", + "in": "query", + "name": "ltwlsupt_ticket_id", + "schema": { + "type": "string" + } + }, + { + "description": "Comma-separated flags to filter on (embargo, duplicate, blocked).", + "in": "query", + "name": "flag", + "schema": { + "type": "string" + } + }, + { + "description": "Search vulnerability_id, component_name, and title. Minimum 2 characters.", + "in": "query", + "name": "search", + "schema": { + "type": "string" + } + }, + { + "description": "Starting point for retrieving a subset of results. Default value:`0`.", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + }, + { + "description": "Number of items to include in response. Default value: `100`. Maximum: `200`.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellVulnerabilityCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell vulnerabilities", + "tags": [ + "lightwell_vulnerabilities" + ] + } + }, + "/lightwell/beacon/vulnerabilities/customers/": { + "get": { + "description": "List distinct customer IDs that have Lightwell vulnerabilities.", + "operationId": "listLightwellCustomerIds", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellCustomerIdsResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell customer IDs", + "tags": [ + "lightwell_vulnerabilities" + ] + } + }, + "/lightwell/beacon/vulnerabilities/ltwlsupt-ticket-ids/": { + "get": { + "description": "List distinct Lightwell support ticket IDs for a customer.", + "operationId": "listLightwellLtwlsuptTicketIds", + "parameters": [ + { + "description": "Customer ID (required).", + "in": "query", + "name": "customer_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellLtwlsuptTicketIdsResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell support ticket IDs", + "tags": [ + "lightwell_vulnerabilities" + ] + } + }, "/module_streams/search": { "post": { "description": "List modules and their streams for repositories", From e79f8873feb1242a219149d1c41caff9ccfa7d9f Mon Sep 17 00:00:00 2001 From: etsien Date: Mon, 24 Aug 2026 22:21:59 -0400 Subject: [PATCH 09/47] update docs and api --- api/docs.go | 372 ++++++++++++++++++++++++++++++++++++++++ api/openapi.json | 428 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 800 insertions(+) diff --git a/api/docs.go b/api/docs.go index 09ba67443..8070aa9f7 100644 --- a/api/docs.go +++ b/api/docs.go @@ -282,6 +282,80 @@ const docTemplate = `{ } } }, + "/lightwell/advisories": { + "get": { + "description": "List security advisories for Lightwell remediated packages with optional filtering.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Advisories", + "operationId": "listLightwellAdvisories", + "parameters": [ + { + "type": "string", + "description": "Filter by repository UUID", + "name": "repository_uuid", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "package_name", + "in": "query" + }, + { + "type": "string", + "description": "Minimum severity level (low, moderate, important, critical)", + "name": "severity_min", + "in": "query" + }, + { + "type": "string", + "description": "Filter by CVE ID (exact match)", + "name": "cve_id", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellAdvisoryCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, "/lightwell/beacon/vulnerabilities/": { "get": { "description": "List Lightwell vulnerabilities for a customer, with filters, pagination, and aggregate counts.", @@ -474,6 +548,160 @@ const docTemplate = `{ } } }, + "/lightwell/package_versions": { + "get": { + "description": "List individual package versions aggregated across all Lightwell repositories, with optional CVE-based filtering.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Package Versions (cross-repo)", + "operationId": "listLightwellPackageVersions", + "parameters": [ + { + "type": "string", + "description": "Filter by content type (maven, python, npm)", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by security level (validated, remediated)", + "name": "security_level", + "in": "query" + }, + { + "type": "string", + "description": "Filter by repository name", + "name": "repository", + "in": "query" + }, + { + "type": "string", + "description": "Show only packages that resolve this CVE", + "name": "resolves_cve_id", + "in": "query" + }, + { + "type": "string", + "description": "Show only packages vulnerable to this CVE", + "name": "vulnerable_to_cve_id", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellPackageVersionCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, + "/lightwell/packages": { + "get": { + "description": "List packages aggregated across all Lightwell repositories, with optional filtering by content type, name, and security level.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Packages (cross-repo)", + "operationId": "listLightwellPackages", + "parameters": [ + { + "type": "string", + "description": "Filter by content type (maven, python, npm)", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by security level (validated, remediated)", + "name": "security_level", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellPackageCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, "/module_streams/search": { "post": { "description": "List modules and their streams for repositories", @@ -5097,6 +5325,55 @@ const docTemplate = `{ } } }, + "api.LightwellAdvisoryCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellAdvisoryResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellAdvisoryResponse": { + "type": "object", + "properties": { + "advisory_id": { + "type": "string" + }, + "details": { + "type": "string" + }, + "fixed_versions": { + "type": "array", + "items": { + "type": "string" + } + }, + "package_name": { + "type": "string" + }, + "reference_urls": { + "type": "array", + "items": { + "type": "string" + } + }, + "repository": { + "type": "string" + }, + "severity": { + "type": "string" + } + } + }, "api.LightwellCustomerIdsResponse": { "type": "object", "properties": { @@ -5121,6 +5398,101 @@ const docTemplate = `{ } } }, + "api.LightwellPackageCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellPackageResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellPackageResponse": { + "type": "object", + "properties": { + "content_type": { + "type": "string" + }, + "group": { + "type": "string" + }, + "latest_releases": { + "type": "array", + "items": { + "$ref": "#/definitions/api.ReleaseInfo" + } + }, + "name": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "versions": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "api.LightwellPackageVersionCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellPackageVersionResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellPackageVersionResponse": { + "type": "object", + "properties": { + "content_type": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "group": { + "type": "string" + }, + "name": { + "type": "string" + }, + "release": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, "api.LightwellVulnerabilityCollectionMeta": { "type": "object", "properties": { diff --git a/api/openapi.json b/api/openapi.json index ed4ab8002..9ae7c6ea6 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -325,6 +325,55 @@ }, "type": "object" }, + "api.LightwellAdvisoryCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellAdvisoryResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellAdvisoryResponse": { + "properties": { + "advisory_id": { + "type": "string" + }, + "details": { + "type": "string" + }, + "fixed_versions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "package_name": { + "type": "string" + }, + "reference_urls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repository": { + "type": "string" + }, + "severity": { + "type": "string" + } + }, + "type": "object" + }, "api.LightwellCustomerIdsResponse": { "properties": { "data": { @@ -349,6 +398,101 @@ }, "type": "object" }, + "api.LightwellPackageCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellPackageResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellPackageResponse": { + "properties": { + "content_type": { + "type": "string" + }, + "group": { + "type": "string" + }, + "latest_releases": { + "items": { + "$ref": "#/components/schemas/api.ReleaseInfo" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "versions": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "api.LightwellPackageVersionCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellPackageVersionResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellPackageVersionResponse": { + "properties": { + "content_type": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "group": { + "type": "string" + }, + "name": { + "type": "string" + }, + "release": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, "api.LightwellVulnerabilityCollectionMeta": { "properties": { "blocked_count": { @@ -3204,6 +3348,98 @@ ] } }, + "/lightwell/advisories": { + "get": { + "description": "List security advisories for Lightwell remediated packages with optional filtering.", + "operationId": "listLightwellAdvisories", + "parameters": [ + { + "description": "Filter by repository UUID", + "in": "query", + "name": "repository_uuid", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "package_name", + "schema": { + "type": "string" + } + }, + { + "description": "Minimum severity level (low, moderate, important, critical)", + "in": "query", + "name": "severity_min", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by CVE ID (exact match)", + "in": "query", + "name": "cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellAdvisoryCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Advisories", + "tags": [ + "lightwell" + ] + } + }, "/lightwell/beacon/vulnerabilities/": { "get": { "description": "List Lightwell vulnerabilities for a customer, with filters, pagination, and aggregate counts.", @@ -3446,6 +3682,198 @@ ] } }, + "/lightwell/package_versions": { + "get": { + "description": "List individual package versions aggregated across all Lightwell repositories, with optional CVE-based filtering.", + "operationId": "listLightwellPackageVersions", + "parameters": [ + { + "description": "Filter by content type (maven, python, npm)", + "in": "query", + "name": "type", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "name", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by security level (validated, remediated)", + "in": "query", + "name": "security_level", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by repository name", + "in": "query", + "name": "repository", + "schema": { + "type": "string" + } + }, + { + "description": "Show only packages that resolve this CVE", + "in": "query", + "name": "resolves_cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Show only packages vulnerable to this CVE", + "in": "query", + "name": "vulnerable_to_cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellPackageVersionCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Package Versions (cross-repo)", + "tags": [ + "lightwell" + ] + } + }, + "/lightwell/packages": { + "get": { + "description": "List packages aggregated across all Lightwell repositories, with optional filtering by content type, name, and security level.", + "operationId": "listLightwellPackages", + "parameters": [ + { + "description": "Filter by content type (maven, python, npm)", + "in": "query", + "name": "type", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "name", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by security level (validated, remediated)", + "in": "query", + "name": "security_level", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellPackageCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Packages (cross-repo)", + "tags": [ + "lightwell" + ] + } + }, "/module_streams/search": { "post": { "description": "List modules and their streams for repositories", From 8827f3faa900414be8d7880b0b7ca2f350384b1f Mon Sep 17 00:00:00 2001 From: etsien Date: Mon, 24 Aug 2026 22:22:23 -0400 Subject: [PATCH 10/47] re-add tests --- pkg/handler/lightwell_advisories_test.go | 6 ++++++ pkg/lightwell/db/store/advisories.sql.go | 2 +- pkg/lightwell/db/store/models.go | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/handler/lightwell_advisories_test.go b/pkg/handler/lightwell_advisories_test.go index 9229e4c15..1c97b621b 100644 --- a/pkg/handler/lightwell_advisories_test.go +++ b/pkg/handler/lightwell_advisories_test.go @@ -273,6 +273,12 @@ func (m *MockQuerier) ListCustomerIds(ctx context.Context) ([]string, error) { return val, args.Error(1) } +func (m *MockQuerier) ListLtwlsuptTicketIds(ctx context.Context, customerID string) ([]string, error) { + args := m.Called(ctx, customerID) + val, _ := args.Get(0).([]string) + return val, args.Error(1) +} + func (m *MockQuerier) ListVulnerabilities(ctx context.Context, arg store.ListVulnerabilitiesParams) ([]store.LightwellVulnerability, error) { args := m.Called(ctx, arg) val, _ := args.Get(0).([]store.LightwellVulnerability) diff --git a/pkg/lightwell/db/store/advisories.sql.go b/pkg/lightwell/db/store/advisories.sql.go index a94b9c1e3..500efcd7e 100644 --- a/pkg/lightwell/db/store/advisories.sql.go +++ b/pkg/lightwell/db/store/advisories.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.31.1 // source: advisories.sql package store diff --git a/pkg/lightwell/db/store/models.go b/pkg/lightwell/db/store/models.go index defb464c2..be2f7afd3 100644 --- a/pkg/lightwell/db/store/models.go +++ b/pkg/lightwell/db/store/models.go @@ -60,6 +60,13 @@ type LightwellVulnerabilityCustomer struct { CreatedAt time.Time `json:"created_at"` } +type LightwellVulnerabilitySupportTicket struct { + VulnerabilityUuid uuid.UUID `json:"vulnerability_uuid"` + CustomerID string `json:"customer_id"` + TicketID string `json:"ticket_id"` + CreatedAt time.Time `json:"created_at"` +} + type RepositoryConfiguration struct { Uuid uuid.UUID `json:"uuid"` } From cc7b447172758109918140805375d6462b55bf26 Mon Sep 17 00:00:00 2001 From: etsien Date: Mon, 24 Aug 2026 22:23:08 -0400 Subject: [PATCH 11/47] update migrations --- ...60818130000_add_lightwell_vulnerability_duplicate_of.down.sql} | 0 ...0260818130000_add_lightwell_vulnerability_duplicate_of.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename db/migrations/{20260818120000_add_lightwell_vulnerability_duplicate_of.down.sql => 20260818130000_add_lightwell_vulnerability_duplicate_of.down.sql} (100%) rename db/migrations/{20260818120000_add_lightwell_vulnerability_duplicate_of.up.sql => 20260818130000_add_lightwell_vulnerability_duplicate_of.up.sql} (100%) diff --git a/db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.down.sql b/db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.down.sql similarity index 100% rename from db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.down.sql rename to db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.down.sql diff --git a/db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.up.sql b/db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.up.sql similarity index 100% rename from db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.up.sql rename to db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.up.sql From 949fedd8a02a7dbcd7c58598cb29749e75acbe4c Mon Sep 17 00:00:00 2001 From: etsien Date: Tue, 25 Aug 2026 04:01:40 -0400 Subject: [PATCH 12/47] bugfix --- pkg/handler/lightwell_advisories_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/handler/lightwell_advisories_test.go b/pkg/handler/lightwell_advisories_test.go index 1c97b621b..97467ace6 100644 --- a/pkg/handler/lightwell_advisories_test.go +++ b/pkg/handler/lightwell_advisories_test.go @@ -279,8 +279,8 @@ func (m *MockQuerier) ListLtwlsuptTicketIds(ctx context.Context, customerID stri return val, args.Error(1) } -func (m *MockQuerier) ListVulnerabilities(ctx context.Context, arg store.ListVulnerabilitiesParams) ([]store.LightwellVulnerability, error) { +func (m *MockQuerier) ListVulnerabilities(ctx context.Context, arg store.ListVulnerabilitiesParams) ([]store.ListVulnerabilitiesRow, error) { args := m.Called(ctx, arg) - val, _ := args.Get(0).([]store.LightwellVulnerability) + val, _ := args.Get(0).([]store.ListVulnerabilitiesRow) return val, args.Error(1) } From 9501f5d1ec6ce8fb81711aa47bb17b3b2d18f7a3 Mon Sep 17 00:00:00 2001 From: etsien Date: Tue, 25 Aug 2026 04:01:43 -0400 Subject: [PATCH 13/47] Update migrations.latest --- db/migrations.latest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrations.latest b/db/migrations.latest index a9aea7d61..7efa1f8c7 100644 --- a/db/migrations.latest +++ b/db/migrations.latest @@ -1 +1 @@ -20260818120000 +20260819141200 From 7e92db7b841f9d7c6d2948bf515bbd32a3cb50c7 Mon Sep 17 00:00:00 2001 From: etsien Date: Tue, 25 Aug 2026 08:58:35 -0400 Subject: [PATCH 14/47] Update store_test.go --- pkg/lightwell/db/store/store_test.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pkg/lightwell/db/store/store_test.go b/pkg/lightwell/db/store/store_test.go index 649f3fc8b..11237162b 100644 --- a/pkg/lightwell/db/store/store_test.go +++ b/pkg/lightwell/db/store/store_test.go @@ -762,8 +762,19 @@ func TestStore_ListCustomerIds(t *testing.T) { // --- Advisory query integration tests --- func insertTestAdvisories(t *testing.T, ctx context.Context, tx pgx.Tx) uuid.UUID { + repoConfigUUID := uuid.New() repoUUID := uuid.New() - _, err := tx.Exec(ctx, `INSERT INTO repository_configurations (uuid) VALUES ($1)`, repoUUID) + now := time.Now() + + _, err := tx.Exec(ctx, + `INSERT INTO repositories (uuid, url) VALUES ($1, $2)`, + repoUUID, "https://test.example.com/repo/"+repoConfigUUID.String()) + require.NoError(t, err) + + _, err = tx.Exec(ctx, + `INSERT INTO repository_configurations (uuid, created_at, updated_at, name, arch, org_id, repository_uuid) + VALUES ($1, $2, $3, $4, $5, $6, $7)`, + repoConfigUUID, now, now, "test-advisory-repo", "x86_64", "test-org-"+repoConfigUUID.String(), repoUUID) require.NoError(t, err) advisories := []struct { @@ -791,11 +802,11 @@ func insertTestAdvisories(t *testing.T, ctx context.Context, tx pgx.Tx) uuid.UUI "test advisory details for "+adv.packageName, []string{"https://access.redhat.com/security/cve/" + adv.id}, adv.packageName, adv.fixedVersions, - adv.repoName, repoUUID, fmt.Sprintf("checksum-%s-%s", adv.id, adv.packageName), + adv.repoName, repoConfigUUID, fmt.Sprintf("checksum-%s-%s", adv.id, adv.packageName), ) require.NoError(t, err) } - return repoUUID + return repoConfigUUID } func TestStore_ListAdvisories(t *testing.T) { From 38da107fe2357d94e1f5aff4986a2a05b12e87dd Mon Sep 17 00:00:00 2001 From: etsien Date: Tue, 25 Aug 2026 11:28:45 -0400 Subject: [PATCH 15/47] LWLP-5: add Lightwell advisory schema, sqlc queries, and store tests Add severity_order column to advisories table and duplicate_of to vulnerabilities. Rename the duplicate migration to avoid timestamp collision. Add sqlc queries for listing/counting advisories with filtering and pagination. Extend store_test.go with advisory query coverage. --- .gitignore | 3 + .mockery_v3.yml | 3 - db/migrations.latest | 2 +- ...twell_vulnerability_duplicate_of.down.sql} | 0 ...ghtwell_vulnerability_duplicate_of.up.sql} | 0 ...lightwell_advisory_severity_order.down.sql | 7 + ...d_lightwell_advisory_severity_order.up.sql | 20 ++ pkg/lightwell/db/queries/advisories.sql | 64 +++++ pkg/lightwell/db/schema.sql | 32 ++- pkg/lightwell/db/store/advisories.sql.go | 224 ++++++++++++++++++ pkg/lightwell/db/store/models.go | 21 ++ pkg/lightwell/db/store/querier.go | 6 + pkg/lightwell/db/store/store_test.go | 195 +++++++++++---- 13 files changed, 532 insertions(+), 45 deletions(-) rename db/migrations/{20260818120000_add_lightwell_vulnerability_duplicate_of.down.sql => 20260818130000_add_lightwell_vulnerability_duplicate_of.down.sql} (100%) rename db/migrations/{20260818120000_add_lightwell_vulnerability_duplicate_of.up.sql => 20260818130000_add_lightwell_vulnerability_duplicate_of.up.sql} (100%) create mode 100644 db/migrations/20260825110000_add_lightwell_advisory_severity_order.down.sql create mode 100644 db/migrations/20260825110000_add_lightwell_advisory_severity_order.up.sql create mode 100644 pkg/lightwell/db/queries/advisories.sql create mode 100644 pkg/lightwell/db/store/advisories.sql.go diff --git a/.gitignore b/.gitignore index 40697b284..01911f3c0 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ content-sources-frontend # local dev certs for testing pulp cert auth compose_files/pulp/assets/certs/dev_certs +/pkg/jfrog_bridge/testdata +pkg/jfrog_bridge/lightwell-catalog.key +.env.catalog diff --git a/.mockery_v3.yml b/.mockery_v3.yml index 7543d5d87..005e0ea3c 100644 --- a/.mockery_v3.yml +++ b/.mockery_v3.yml @@ -23,9 +23,6 @@ packages: github.com/content-services/content-sources-backend/pkg/clients/roadmap_client: interfaces: RoadmapClient: {} - github.com/content-services/content-sources-backend/pkg/clients/s3_client: - interfaces: - S3Client: {} github.com/content-services/content-sources-backend/pkg/dao: interfaces: AdminTaskDao: {} diff --git a/db/migrations.latest b/db/migrations.latest index 34529ff58..9331a9b88 100644 --- a/db/migrations.latest +++ b/db/migrations.latest @@ -1 +1 @@ -20260819141200 \ No newline at end of file +20260825110000 diff --git a/db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.down.sql b/db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.down.sql similarity index 100% rename from db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.down.sql rename to db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.down.sql diff --git a/db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.up.sql b/db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.up.sql similarity index 100% rename from db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.up.sql rename to db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.up.sql diff --git a/db/migrations/20260825110000_add_lightwell_advisory_severity_order.down.sql b/db/migrations/20260825110000_add_lightwell_advisory_severity_order.down.sql new file mode 100644 index 000000000..53b3f3983 --- /dev/null +++ b/db/migrations/20260825110000_add_lightwell_advisory_severity_order.down.sql @@ -0,0 +1,7 @@ +BEGIN; + +DROP INDEX IF EXISTS idx_lightwell_advisories_package_name; +DROP INDEX IF EXISTS idx_lightwell_advisories_severity_order; +ALTER TABLE lightwell_advisories DROP COLUMN IF EXISTS severity_order; + +COMMIT; diff --git a/db/migrations/20260825110000_add_lightwell_advisory_severity_order.up.sql b/db/migrations/20260825110000_add_lightwell_advisory_severity_order.up.sql new file mode 100644 index 000000000..544d380e0 --- /dev/null +++ b/db/migrations/20260825110000_add_lightwell_advisory_severity_order.up.sql @@ -0,0 +1,20 @@ +BEGIN; + +ALTER TABLE lightwell_advisories + ADD COLUMN IF NOT EXISTS severity_order SMALLINT NOT NULL DEFAULT 0; + +UPDATE lightwell_advisories SET severity_order = CASE + WHEN severity = 'critical' THEN 4 + WHEN severity = 'important' THEN 3 + WHEN severity = 'moderate' THEN 2 + WHEN severity = 'low' THEN 1 + ELSE 0 +END; + +CREATE INDEX IF NOT EXISTS idx_lightwell_advisories_severity_order + ON lightwell_advisories (severity_order); + +CREATE INDEX IF NOT EXISTS idx_lightwell_advisories_package_name + ON lightwell_advisories (package_name); + +COMMIT; diff --git a/pkg/lightwell/db/queries/advisories.sql b/pkg/lightwell/db/queries/advisories.sql new file mode 100644 index 000000000..4abf9359c --- /dev/null +++ b/pkg/lightwell/db/queries/advisories.sql @@ -0,0 +1,64 @@ +-- name: ListAdvisories :many +SELECT + la.uuid, + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.reference_urls, + la.package_name, + la.fixed_versions, + la.repo_name, + la.repository_configuration_uuid, + la.created_at, + COUNT(*) OVER() AS total_count +FROM lightwell_advisories la +WHERE 1=1 + AND ( + sqlc.narg(repository_config_uuid)::uuid IS NULL + OR la.repository_configuration_uuid = sqlc.narg(repository_config_uuid)::uuid + ) + AND ( + sqlc.narg(repo_name)::text IS NULL + OR la.repo_name = sqlc.narg(repo_name)::text + ) + AND ( + sqlc.narg(package_name)::text IS NULL + OR la.package_name ILIKE '%' || sqlc.narg(package_name)::text || '%' + ) + AND ( + sqlc.narg(severity_min)::smallint IS NULL + OR la.severity_order >= sqlc.narg(severity_min)::smallint + ) + AND ( + sqlc.narg(cve_id)::text IS NULL + OR la.advisory_id = sqlc.narg(cve_id)::text + ) +ORDER BY la.severity_order DESC, la.created_at DESC +LIMIT sqlc.arg(page_limit) OFFSET sqlc.arg(page_offset); + +-- name: CountAdvisoriesByRepo :one +SELECT COUNT(*)::bigint AS total +FROM lightwell_advisories la +WHERE la.repository_configuration_uuid = sqlc.arg(repository_config_uuid)::uuid; + +-- name: ListAdvisoriesByPackage :many +SELECT + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.fixed_versions, + la.repo_name +FROM lightwell_advisories la +WHERE la.package_name = sqlc.arg(package_name)::text +ORDER BY la.severity_order DESC, la.created_at DESC; + +-- name: ListAdvisoriesByCveID :many +SELECT + la.package_name, + la.fixed_versions, + la.repo_name, + la.severity +FROM lightwell_advisories la +WHERE la.advisory_id = sqlc.arg(cve_id)::text; diff --git a/pkg/lightwell/db/schema.sql b/pkg/lightwell/db/schema.sql index 142c6ffb5..a876231d3 100644 --- a/pkg/lightwell/db/schema.sql +++ b/pkg/lightwell/db/schema.sql @@ -1,4 +1,34 @@ --- sqlc schema snapshot: current lightwell vulnerabilities tables and filter function (see db/migrations) +-- sqlc schema snapshot: current lightwell vulnerabilities tables (see db/migrations) +-- NOTE: This file must be kept in sync with the actual migrations. +-- sqlc uses this for code generation; it is not executed directly. + +CREATE TABLE repository_configurations ( + uuid UUID PRIMARY KEY +); + +CREATE TABLE lightwell_advisories ( + uuid UUID UNIQUE NOT NULL PRIMARY KEY, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + repo_name VARCHAR(255) NOT NULL, + advisory_id VARCHAR(255) NOT NULL, + severity VARCHAR(255) NOT NULL DEFAULT '', + severity_order SMALLINT NOT NULL DEFAULT 0, + details TEXT NOT NULL DEFAULT '', + reference_urls TEXT[], + package_name VARCHAR(255) NOT NULL DEFAULT '', + fixed_version VARCHAR(255) NOT NULL DEFAULT '', + fixed_versions TEXT[] NOT NULL DEFAULT '{}', + repository_configuration_uuid UUID NOT NULL REFERENCES repository_configurations(uuid) ON DELETE CASCADE, + checksum VARCHAR(255) NOT NULL +); + +CREATE UNIQUE INDEX idx_lightwell_advisories_repo_config_advisory + ON lightwell_advisories (repository_configuration_uuid, advisory_id, package_name); +CREATE INDEX idx_lightwell_advisories_severity_order + ON lightwell_advisories (severity_order); +CREATE INDEX idx_lightwell_advisories_package_name + ON lightwell_advisories (package_name); CREATE TABLE lightwell_vulnerabilities ( uuid UUID PRIMARY KEY, diff --git a/pkg/lightwell/db/store/advisories.sql.go b/pkg/lightwell/db/store/advisories.sql.go new file mode 100644 index 000000000..500efcd7e --- /dev/null +++ b/pkg/lightwell/db/store/advisories.sql.go @@ -0,0 +1,224 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: advisories.sql + +package store + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" +) + +const countAdvisoriesByRepo = `-- name: CountAdvisoriesByRepo :one +SELECT COUNT(*)::bigint AS total +FROM lightwell_advisories la +WHERE la.repository_configuration_uuid = $1::uuid +` + +func (q *Queries) CountAdvisoriesByRepo(ctx context.Context, repositoryConfigUuid uuid.UUID) (int64, error) { + row := q.db.QueryRow(ctx, countAdvisoriesByRepo, repositoryConfigUuid) + var total int64 + err := row.Scan(&total) + return total, err +} + +const listAdvisories = `-- name: ListAdvisories :many +SELECT + la.uuid, + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.reference_urls, + la.package_name, + la.fixed_versions, + la.repo_name, + la.repository_configuration_uuid, + la.created_at, + COUNT(*) OVER() AS total_count +FROM lightwell_advisories la +WHERE 1=1 + AND ( + $1::uuid IS NULL + OR la.repository_configuration_uuid = $1::uuid + ) + AND ( + $2::text IS NULL + OR la.repo_name = $2::text + ) + AND ( + $3::text IS NULL + OR la.package_name ILIKE '%' || $3::text || '%' + ) + AND ( + $4::smallint IS NULL + OR la.severity_order >= $4::smallint + ) + AND ( + $5::text IS NULL + OR la.advisory_id = $5::text + ) +ORDER BY la.severity_order DESC, la.created_at DESC +LIMIT $7 OFFSET $6 +` + +type ListAdvisoriesParams struct { + RepositoryConfigUuid pgtype.UUID `json:"repository_config_uuid"` + RepoName *string `json:"repo_name"` + PackageName *string `json:"package_name"` + SeverityMin pgtype.Int2 `json:"severity_min"` + CveID *string `json:"cve_id"` + PageOffset int32 `json:"page_offset"` + PageLimit int32 `json:"page_limit"` +} + +type ListAdvisoriesRow struct { + Uuid uuid.UUID `json:"uuid"` + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + SeverityOrder int16 `json:"severity_order"` + Details string `json:"details"` + ReferenceUrls []string `json:"reference_urls"` + PackageName string `json:"package_name"` + FixedVersions []string `json:"fixed_versions"` + RepoName string `json:"repo_name"` + RepositoryConfigurationUuid uuid.UUID `json:"repository_configuration_uuid"` + CreatedAt time.Time `json:"created_at"` + TotalCount int64 `json:"total_count"` +} + +func (q *Queries) ListAdvisories(ctx context.Context, arg ListAdvisoriesParams) ([]ListAdvisoriesRow, error) { + rows, err := q.db.Query(ctx, listAdvisories, + arg.RepositoryConfigUuid, + arg.RepoName, + arg.PackageName, + arg.SeverityMin, + arg.CveID, + arg.PageOffset, + arg.PageLimit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListAdvisoriesRow{} + for rows.Next() { + var i ListAdvisoriesRow + if err := rows.Scan( + &i.Uuid, + &i.AdvisoryID, + &i.Severity, + &i.SeverityOrder, + &i.Details, + &i.ReferenceUrls, + &i.PackageName, + &i.FixedVersions, + &i.RepoName, + &i.RepositoryConfigurationUuid, + &i.CreatedAt, + &i.TotalCount, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAdvisoriesByCveID = `-- name: ListAdvisoriesByCveID :many +SELECT + la.package_name, + la.fixed_versions, + la.repo_name, + la.severity +FROM lightwell_advisories la +WHERE la.advisory_id = $1::text +` + +type ListAdvisoriesByCveIDRow struct { + PackageName string `json:"package_name"` + FixedVersions []string `json:"fixed_versions"` + RepoName string `json:"repo_name"` + Severity string `json:"severity"` +} + +func (q *Queries) ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]ListAdvisoriesByCveIDRow, error) { + rows, err := q.db.Query(ctx, listAdvisoriesByCveID, cveID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListAdvisoriesByCveIDRow{} + for rows.Next() { + var i ListAdvisoriesByCveIDRow + if err := rows.Scan( + &i.PackageName, + &i.FixedVersions, + &i.RepoName, + &i.Severity, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAdvisoriesByPackage = `-- name: ListAdvisoriesByPackage :many +SELECT + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.fixed_versions, + la.repo_name +FROM lightwell_advisories la +WHERE la.package_name = $1::text +ORDER BY la.severity_order DESC, la.created_at DESC +` + +type ListAdvisoriesByPackageRow struct { + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + SeverityOrder int16 `json:"severity_order"` + Details string `json:"details"` + FixedVersions []string `json:"fixed_versions"` + RepoName string `json:"repo_name"` +} + +func (q *Queries) ListAdvisoriesByPackage(ctx context.Context, packageName string) ([]ListAdvisoriesByPackageRow, error) { + rows, err := q.db.Query(ctx, listAdvisoriesByPackage, packageName) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListAdvisoriesByPackageRow{} + for rows.Next() { + var i ListAdvisoriesByPackageRow + if err := rows.Scan( + &i.AdvisoryID, + &i.Severity, + &i.SeverityOrder, + &i.Details, + &i.FixedVersions, + &i.RepoName, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/pkg/lightwell/db/store/models.go b/pkg/lightwell/db/store/models.go index da5842610..be2f7afd3 100644 --- a/pkg/lightwell/db/store/models.go +++ b/pkg/lightwell/db/store/models.go @@ -10,6 +10,23 @@ import ( "github.com/google/uuid" ) +type LightwellAdvisory struct { + Uuid uuid.UUID `json:"uuid"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + RepoName string `json:"repo_name"` + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + SeverityOrder int16 `json:"severity_order"` + Details string `json:"details"` + ReferenceUrls []string `json:"reference_urls"` + PackageName string `json:"package_name"` + FixedVersion string `json:"fixed_version"` + FixedVersions []string `json:"fixed_versions"` + RepositoryConfigurationUuid uuid.UUID `json:"repository_configuration_uuid"` + Checksum string `json:"checksum"` +} + type LightwellVulnerability struct { Uuid uuid.UUID `json:"uuid"` VulnerabilityID string `json:"vulnerability_id"` @@ -49,3 +66,7 @@ type LightwellVulnerabilitySupportTicket struct { TicketID string `json:"ticket_id"` CreatedAt time.Time `json:"created_at"` } + +type RepositoryConfiguration struct { + Uuid uuid.UUID `json:"uuid"` +} diff --git a/pkg/lightwell/db/store/querier.go b/pkg/lightwell/db/store/querier.go index bd1927ae9..659962d35 100644 --- a/pkg/lightwell/db/store/querier.go +++ b/pkg/lightwell/db/store/querier.go @@ -6,11 +6,17 @@ package store import ( "context" + + "github.com/google/uuid" ) type Querier interface { + CountAdvisoriesByRepo(ctx context.Context, repositoryConfigUuid uuid.UUID) (int64, error) CountAggregates(ctx context.Context, arg CountAggregatesParams) (CountAggregatesRow, error) CountByStage(ctx context.Context, arg CountByStageParams) ([]CountByStageRow, error) + ListAdvisories(ctx context.Context, arg ListAdvisoriesParams) ([]ListAdvisoriesRow, error) + ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]ListAdvisoriesByCveIDRow, error) + ListAdvisoriesByPackage(ctx context.Context, packageName string) ([]ListAdvisoriesByPackageRow, error) ListCustomerIds(ctx context.Context) ([]string, error) ListLtwlsuptTicketIds(ctx context.Context, customerID string) ([]string, error) ListVulnerabilities(ctx context.Context, arg ListVulnerabilitiesParams) ([]ListVulnerabilitiesRow, error) diff --git a/pkg/lightwell/db/store/store_test.go b/pkg/lightwell/db/store/store_test.go index 317490658..11237162b 100644 --- a/pkg/lightwell/db/store/store_test.go +++ b/pkg/lightwell/db/store/store_test.go @@ -11,6 +11,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/google/uuid" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgxpool" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -758,54 +759,168 @@ func TestStore_ListCustomerIds(t *testing.T) { assert.Contains(t, ids, customerB) } -func TestStore_ListLtwlsuptTicketIds(t *testing.T) { +// --- Advisory query integration tests --- + +func insertTestAdvisories(t *testing.T, ctx context.Context, tx pgx.Tx) uuid.UUID { + repoConfigUUID := uuid.New() + repoUUID := uuid.New() + now := time.Now() + + _, err := tx.Exec(ctx, + `INSERT INTO repositories (uuid, url) VALUES ($1, $2)`, + repoUUID, "https://test.example.com/repo/"+repoConfigUUID.String()) + require.NoError(t, err) + + _, err = tx.Exec(ctx, + `INSERT INTO repository_configurations (uuid, created_at, updated_at, name, arch, org_id, repository_uuid) + VALUES ($1, $2, $3, $4, $5, $6, $7)`, + repoConfigUUID, now, now, "test-advisory-repo", "x86_64", "test-org-"+repoConfigUUID.String(), repoUUID) + require.NoError(t, err) + + advisories := []struct { + id string + severity string + severityOrder int + packageName string + fixedVersions []string + repoName string + }{ + {"CVE-2024-1001", "critical", 4, "spring-core", []string{"5.3.18.rhlw-00003"}, "lightwell/java/remediated"}, + {"CVE-2024-1002", "important", 3, "jackson-databind", []string{"2.15.3.rhlw-00001"}, "lightwell/java/remediated"}, + {"CVE-2024-1003", "moderate", 2, "requests", []string{"2.31.0.rhlw-00001"}, "lightwell/python/remediated"}, + {"CVE-2024-1001", "critical", 4, "jackson-databind", []string{"2.14.2.rhlw-00001", "2.15.3.rhlw-00001"}, "lightwell/java/remediated"}, + } + + for _, adv := range advisories { + _, err := tx.Exec(ctx, ` + INSERT INTO lightwell_advisories ( + uuid, advisory_id, severity, severity_order, details, + reference_urls, package_name, fixed_versions, + repo_name, repository_configuration_uuid, checksum + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, + uuid.New(), adv.id, adv.severity, adv.severityOrder, + "test advisory details for "+adv.packageName, + []string{"https://access.redhat.com/security/cve/" + adv.id}, + adv.packageName, adv.fixedVersions, + adv.repoName, repoConfigUUID, fmt.Sprintf("checksum-%s-%s", adv.id, adv.packageName), + ) + require.NoError(t, err) + } + return repoConfigUUID +} + +func TestStore_ListAdvisories(t *testing.T) { ctx, tx, q := beginTestTx(t) defer rollbackTestTx(t, tx) - customerA := fmt.Sprintf("lw-tickets-a-%d", time.Now().UnixNano()) - customerB := fmt.Sprintf("lw-tickets-b-%d", time.Now().UnixNano()) - insertTestVulnerabilities(t, ctx, tx, []testVulnSpec{ - { - vulnID: "LWL-TICKETS-1", - severity: "Moderate", - stage: "Submitted", - language: "java", - complexity: "Standard", - ticketIDs: []string{"ticket-c", "ticket-a"}, - daysAgo: 1, - customerIDs: []string{customerA}, - }, - { - vulnID: "LWL-TICKETS-2", - severity: "Low", - stage: "Submitted", - language: "java", - complexity: "Standard", - ticketID: "ticket-a", - daysAgo: 1, - customerIDs: []string{customerA}, - }, - { - vulnID: "LWL-TICKETS-3", - severity: "Low", - stage: "Submitted", - language: "python", - complexity: "Standard", - ticketID: "ticket-b", - daysAgo: 1, - customerIDs: []string{customerB}, - }, + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + PageLimit: 100, + PageOffset: 0, }) + require.NoError(t, err) + assert.Len(t, rows, 4) + assert.Equal(t, int64(4), rows[0].TotalCount) + // Ordered by severity_order DESC + assert.Equal(t, int16(4), rows[0].SeverityOrder) +} - ids, err := q.ListLtwlsuptTicketIds(ctx, customerA) +func TestStore_ListAdvisoriesFilterByPackageName(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + name := "jackson" + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + PackageName: &name, + PageLimit: 100, + PageOffset: 0, + }) require.NoError(t, err) - assert.Equal(t, []string{"ticket-a", "ticket-c"}, ids) + assert.Len(t, rows, 2) + for _, r := range rows { + assert.Contains(t, r.PackageName, "jackson") + } +} - ids, err = q.ListLtwlsuptTicketIds(ctx, customerB) +func TestStore_ListAdvisoriesFilterBySeverityMin(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + SeverityMin: pgtype.Int2{Int16: 3, Valid: true}, + PageLimit: 100, + PageOffset: 0, + }) require.NoError(t, err) - assert.Equal(t, []string{"ticket-b"}, ids) + assert.Len(t, rows, 3) + for _, r := range rows { + assert.GreaterOrEqual(t, r.SeverityOrder, int16(3)) + } +} + +func TestStore_ListAdvisoriesFilterByRepoName(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) - ids, err = q.ListLtwlsuptTicketIds(ctx, "no-such-customer") + repoName := "lightwell/python/remediated" + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + RepoName: &repoName, + PageLimit: 100, + PageOffset: 0, + }) require.NoError(t, err) - assert.Empty(t, ids) + assert.Len(t, rows, 1) + assert.Equal(t, "requests", rows[0].PackageName) +} + +func TestStore_CountAdvisoriesByRepo(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + repoUUID := insertTestAdvisories(t, ctx, tx) + + count, err := q.CountAdvisoriesByRepo(ctx, repoUUID) + require.NoError(t, err) + assert.Equal(t, int64(4), count) +} + +func TestStore_ListAdvisoriesByCveID(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisoriesByCveID(ctx, "CVE-2024-1001") + require.NoError(t, err) + assert.Len(t, rows, 2) + + packageNames := map[string]bool{} + for _, r := range rows { + packageNames[r.PackageName] = true + assert.Equal(t, "critical", r.Severity) + } + assert.True(t, packageNames["spring-core"]) + assert.True(t, packageNames["jackson-databind"]) +} + +func TestStore_ListAdvisoriesByPackage(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisoriesByPackage(ctx, "jackson-databind") + require.NoError(t, err) + assert.Len(t, rows, 2) + for _, r := range rows { + assert.NotEmpty(t, r.AdvisoryID) + assert.NotEmpty(t, r.FixedVersions) + } } From 7344968d18692dd8433331bafe19c39a0f99dfca Mon Sep 17 00:00:00 2001 From: etsien Date: Tue, 25 Aug 2026 11:28:56 -0400 Subject: [PATCH 16/47] LWLP-5: add Lightwell advisories and packages API Add REST handlers for /lightwell/advisories, /lightwell/packages, and /lightwell/package_versions with filtering, pagination, and aggregate counts. Include cross-repo package listing with CVE-based filtering. Add packages_count, versions_count, and remediations_count to the repository response. Full handler test coverage for both endpoints. --- configs/config.yaml.example | 13 - deployments/build/deployment.template.yaml | 1 - deployments/build/env-variables.yaml | 13 - deployments/deployment.yaml | 128 ---- pkg/api/lightwell_advisories.go | 29 + pkg/api/lightwell_packages.go | 66 ++ pkg/api/repositories.go | 3 + pkg/clients/s3_client/client.go | 58 -- pkg/clients/s3_client/s3_client_mock.go | 102 --- pkg/config/config.go | 42 -- pkg/dao/repository_configs.go | 3 - pkg/dao/repository_configs_test.go | 12 - pkg/handler/api.go | 33 +- pkg/handler/coverage_reports.go | 16 +- pkg/handler/coverage_reports_test.go | 15 +- pkg/handler/lightwell_advisories.go | 154 +++++ pkg/handler/lightwell_advisories_test.go | 286 ++++++++ pkg/handler/lightwell_packages.go | 728 +++++++++++++++++++++ pkg/handler/lightwell_packages_test.go | 538 +++++++++++++++ pkg/handler/repositories.go | 40 ++ 20 files changed, 1868 insertions(+), 412 deletions(-) create mode 100644 pkg/api/lightwell_advisories.go create mode 100644 pkg/api/lightwell_packages.go delete mode 100644 pkg/clients/s3_client/client.go delete mode 100644 pkg/clients/s3_client/s3_client_mock.go create mode 100644 pkg/handler/lightwell_advisories.go create mode 100644 pkg/handler/lightwell_advisories_test.go create mode 100644 pkg/handler/lightwell_packages.go create mode 100644 pkg/handler/lightwell_packages_test.go diff --git a/configs/config.yaml.example b/configs/config.yaml.example index 578e4b3ba..212510718 100644 --- a/configs/config.yaml.example +++ b/configs/config.yaml.example @@ -131,14 +131,6 @@ clients: lightwell: username: password: - s3: - coverage_uploads: - url: - name: - access_key: - secret_key: - region: - file_prefix: pulp_log_parser: cloudwatch: key: @@ -230,8 +222,3 @@ features: accounts: #["lightwellAccount"] users: #["lightwellUser"] organizations: #["lightwellOrg"] - lightwell_store_uploads: - enabled: false - accounts: #["lightwellAccount"] - users: #["lightwellUser"] - organizations: #["lightwellOrg"] diff --git a/deployments/build/deployment.template.yaml b/deployments/build/deployment.template.yaml index 9d2aa39b0..b9c462ffe 100644 --- a/deployments/build/deployment.template.yaml +++ b/deployments/build/deployment.template.yaml @@ -420,7 +420,6 @@ objects: inMemoryDb: true objectStore: - content-sources-central-pulp-s3 - - lightwell-ui-coverage-uploads - apiVersion: v1 kind: Service metadata: diff --git a/deployments/build/env-variables.yaml b/deployments/build/env-variables.yaml index a15748158..05aba9613 100644 --- a/deployments/build/env-variables.yaml +++ b/deployments/build/env-variables.yaml @@ -79,12 +79,6 @@ env: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -400,13 +394,6 @@ parameters: description: Comma separated list of account numbers that can access the feature - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS description: Comma separated list of org ids that can access the feature - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - description: Whether this Lightwell feature should be turned on - value: 'false' - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - description: Comma separated list of account numbers that can access the feature - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - description: Comma separated list of org ids that can access the feature - name: OPTIONS_LOAD_LIGHTWELL_DEMO value: 'true' - name: OPTIONS_SEED_LIGHTWELL diff --git a/deployments/deployment.yaml b/deployments/deployment.yaml index cf7b0ba81..a31601780 100644 --- a/deployments/deployment.yaml +++ b/deployments/deployment.yaml @@ -165,12 +165,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -467,12 +461,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -736,12 +724,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -993,12 +975,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -1253,12 +1229,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -1514,12 +1484,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -1777,12 +1741,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -2038,12 +1996,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -2296,12 +2248,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -2554,12 +2500,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -2835,12 +2775,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -3094,12 +3028,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -3370,12 +3298,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -3625,12 +3547,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -3880,12 +3796,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -4135,12 +4045,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -4390,12 +4294,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -4645,12 +4543,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -4900,12 +4792,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -5155,12 +5041,6 @@ objects: value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ACCOUNTS} - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS value: ${FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS} - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - value: ${FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS} - name: OPTIONS_ALWAYS_RUN_CRON_TASKS value: ${OPTIONS_ALWAYS_RUN_CRON_TASKS} - name: OPTIONS_ENABLE_NOTIFICATIONS @@ -5326,7 +5206,6 @@ objects: inMemoryDb: true objectStore: - content-sources-central-pulp-s3 - - lightwell-ui-coverage-uploads - apiVersion: v1 kind: Service metadata: @@ -5521,13 +5400,6 @@ parameters: description: Comma separated list of account numbers that can access the feature - name: FEATURES_LIGHTWELL_BEACON_AND_LENS_ORGANIZATIONS description: Comma separated list of org ids that can access the feature - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ENABLED - description: Whether this Lightwell feature should be turned on - value: 'false' - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ACCOUNTS - description: Comma separated list of account numbers that can access the feature - - name: FEATURES_LIGHTWELL_STORE_UPLOADS_ORGANIZATIONS - description: Comma separated list of org ids that can access the feature - name: OPTIONS_LOAD_LIGHTWELL_DEMO value: 'true' - name: OPTIONS_SEED_LIGHTWELL diff --git a/pkg/api/lightwell_advisories.go b/pkg/api/lightwell_advisories.go new file mode 100644 index 000000000..9eb7260a5 --- /dev/null +++ b/pkg/api/lightwell_advisories.go @@ -0,0 +1,29 @@ +package api + +type LightwellAdvisoryResponse struct { + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + Details string `json:"details"` + ReferenceURLs []string `json:"reference_urls"` + PackageName string `json:"package_name"` + FixedVersions []string `json:"fixed_versions"` + Repository string `json:"repository"` +} + +type LightwellAdvisoryCollectionResponse struct { + Data []LightwellAdvisoryResponse `json:"data"` + Meta ResponseMetadata `json:"meta"` + Links Links `json:"links"` +} + +func (r *LightwellAdvisoryCollectionResponse) SetMetadata(meta ResponseMetadata, links Links) { + r.Meta = meta + r.Links = links +} + +type LightwellAdvisoryFilterData struct { + Repository string `query:"repository"` + PackageName string `query:"package_name"` + SeverityMin string `query:"severity_min"` + CveID string `query:"cve_id"` +} diff --git a/pkg/api/lightwell_packages.go b/pkg/api/lightwell_packages.go new file mode 100644 index 000000000..aadaa286c --- /dev/null +++ b/pkg/api/lightwell_packages.go @@ -0,0 +1,66 @@ +package api + +// LightwellPackageResponse represents a package found across Lightwell repositories. +type LightwellPackageResponse struct { + Name string `json:"name"` + Group string `json:"group,omitempty"` + ContentType string `json:"content_type"` + Repository string `json:"repository"` + RepositoryUUID string `json:"repository_uuid"` + Versions []string `json:"versions"` + LatestReleases []ReleaseInfo `json:"latest_releases"` +} + +// LightwellPackageCollectionResponse is a paginated collection of cross-repo packages. +type LightwellPackageCollectionResponse struct { + Data []LightwellPackageResponse `json:"data"` + Meta ResponseMetadata `json:"meta"` + Links Links `json:"links"` +} + +func (r *LightwellPackageCollectionResponse) SetMetadata(meta ResponseMetadata, links Links) { + r.Meta = meta + r.Links = links +} + +// LightwellPackageVersionResponse represents a single package version across Lightwell repositories. +type LightwellPackageVersionResponse struct { + Name string `json:"name"` + Group string `json:"group,omitempty"` + Version string `json:"version"` + ContentType string `json:"content_type"` + Repository string `json:"repository"` + RepositoryUUID string `json:"repository_uuid"` + Release string `json:"release,omitempty"` + CreatedAt string `json:"created_at,omitempty"` +} + +// LightwellPackageVersionCollectionResponse is a paginated collection of cross-repo package versions. +type LightwellPackageVersionCollectionResponse struct { + Data []LightwellPackageVersionResponse `json:"data"` + Meta ResponseMetadata `json:"meta"` + Links Links `json:"links"` +} + +func (r *LightwellPackageVersionCollectionResponse) SetMetadata(meta ResponseMetadata, links Links) { + r.Meta = meta + r.Links = links +} + +// LightwellPackageFilterData holds query-parameter filters for the cross-repo packages endpoint. +type LightwellPackageFilterData struct { + ContentType string `query:"content_type"` + Name string `query:"name"` + Repository string `query:"repository"` + SecurityLevel string `query:"security_level"` +} + +// LightwellPackageVersionFilterData holds query-parameter filters for the cross-repo package_versions endpoint. +type LightwellPackageVersionFilterData struct { + ContentType string `query:"content_type"` + Name string `query:"name"` + SecurityLevel string `query:"security_level"` + Repository string `query:"repository"` + ResolvesCveID string `query:"resolves_cve_id"` + VulnerableToCveID string `query:"vulnerable_to_cve_id"` +} diff --git a/pkg/api/repositories.go b/pkg/api/repositories.go index 90e25f87c..059ad93a8 100644 --- a/pkg/api/repositories.go +++ b/pkg/api/repositories.go @@ -45,6 +45,9 @@ type RepositoryResponse struct { SecurityLevel string `json:"security_level,omitempty" readonly:"true"` // Security level of the repository (e.g. validated, remediated) PublishedDistURL string `json:"published_distribution_url,omitempty" readonly:"true"` // Published distribution URL from Pulp PublishedDistBasePath string `json:"-"` // Published dist base path from Pulp + PackagesCount *int `json:"packages_count,omitempty" readonly:"true"` // Lightwell: total distinct packages + VersionsCount *int `json:"versions_count,omitempty" readonly:"true"` // Lightwell: total distinct versions + RemediationsCount *int `json:"remediations_count,omitempty" readonly:"true"` // Lightwell: total security advisories } // RepositoryRequest holds data received from request to create repository diff --git a/pkg/clients/s3_client/client.go b/pkg/clients/s3_client/client.go deleted file mode 100644 index 90b78743e..000000000 --- a/pkg/clients/s3_client/client.go +++ /dev/null @@ -1,58 +0,0 @@ -package s3_client - -import ( - "context" - "fmt" - "io" - - "github.com/aws/aws-sdk-go-v2/aws" - awsConfig "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/credentials" - "github.com/aws/aws-sdk-go-v2/service/s3" - cfg "github.com/content-services/content-sources-backend/pkg/config" - "github.com/rs/zerolog/log" -) - -type S3Client interface { - Put(ctx context.Context, key string, body io.Reader) error -} - -type s3Client struct { - client *s3.Client - bucket string -} - -func NewS3Client(store cfg.ObjectStore) (S3Client, error) { - if store.Name == "" { - return nil, fmt.Errorf("s3 not configured") - } - - awsCfg, err := awsConfig.LoadDefaultConfig(context.Background(), - awsConfig.WithRegion(store.Region), - awsConfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(store.AccessKey, store.SecretKey, "")), - ) - if err != nil { - return nil, fmt.Errorf("unable to load SDK config: %w", err) - } - - client := s3.NewFromConfig(awsCfg, func(o *s3.Options) { - o.UsePathStyle = true - if store.URL != "" { - o.BaseEndpoint = aws.String(store.URL) - } - }) - return &s3Client{client: client, bucket: store.Name}, nil -} - -func (c *s3Client) Put(ctx context.Context, storageKey string, body io.Reader) error { - _, err := c.client.PutObject(ctx, &s3.PutObjectInput{ - Bucket: &c.bucket, - Key: &storageKey, - Body: body, - }) - if err != nil { - return fmt.Errorf("failed to upload %s to s3: %w", storageKey, err) - } - log.Info().Msgf("Uploaded %s to s3", storageKey) - return nil -} diff --git a/pkg/clients/s3_client/s3_client_mock.go b/pkg/clients/s3_client/s3_client_mock.go deleted file mode 100644 index 72f904fb3..000000000 --- a/pkg/clients/s3_client/s3_client_mock.go +++ /dev/null @@ -1,102 +0,0 @@ -// Code generated by mockery; DO NOT EDIT. -// github.com/vektra/mockery -// template: testify - -package s3_client - -import ( - "context" - "io" - - mock "github.com/stretchr/testify/mock" -) - -// NewMockS3Client creates a new instance of MockS3Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockS3Client(t interface { - mock.TestingT - Cleanup(func()) -}) *MockS3Client { - mock := &MockS3Client{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} - -// MockS3Client is an autogenerated mock type for the S3Client type -type MockS3Client struct { - mock.Mock -} - -type MockS3Client_Expecter struct { - mock *mock.Mock -} - -func (_m *MockS3Client) EXPECT() *MockS3Client_Expecter { - return &MockS3Client_Expecter{mock: &_m.Mock} -} - -// Put provides a mock function for the type MockS3Client -func (_mock *MockS3Client) Put(ctx context.Context, key string, body io.Reader) error { - ret := _mock.Called(ctx, key, body) - - if len(ret) == 0 { - panic("no return value specified for Put") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string, io.Reader) error); ok { - r0 = returnFunc(ctx, key, body) - } else { - r0 = ret.Error(0) - } - return r0 -} - -// MockS3Client_Put_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Put' -type MockS3Client_Put_Call struct { - *mock.Call -} - -// Put is a helper method to define mock.On call -// - ctx context.Context -// - key string -// - body io.Reader -func (_e *MockS3Client_Expecter) Put(ctx interface{}, key interface{}, body interface{}) *MockS3Client_Put_Call { - return &MockS3Client_Put_Call{Call: _e.mock.On("Put", ctx, key, body)} -} - -func (_c *MockS3Client_Put_Call) Run(run func(ctx context.Context, key string, body io.Reader)) *MockS3Client_Put_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 io.Reader - if args[2] != nil { - arg2 = args[2].(io.Reader) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockS3Client_Put_Call) Return(err error) *MockS3Client_Put_Call { - _c.Call.Return(err) - return _c -} - -func (_c *MockS3Client_Put_Call) RunAndReturn(run func(ctx context.Context, key string, body io.Reader) error) *MockS3Client_Put_Call { - _c.Call.Return(run) - return _c -} diff --git a/pkg/config/config.go b/pkg/config/config.go index f632a6f84..6609925bc 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -83,7 +83,6 @@ type FeatureSet struct { AdminPartnerRepositories Feature `mapstructure:"admin_partner_repositories"` AdminNotifications Feature `mapstructure:"admin_notifications"` LightwellBeaconAndLens Feature `mapstructure:"lightwell_beacon_and_lens"` - LightwellStoreUploads Feature `mapstructure:"lightwell_store_uploads"` } type Feature struct { @@ -120,11 +119,6 @@ type Pulp struct { type Lightwell struct { Username string Password string - S3 S3 `mapstructure:"s3"` -} - -type S3 struct { - CoverageUploads ObjectStore `mapstructure:"coverage_uploads"` } type Candlepin struct { @@ -174,7 +168,6 @@ type KesselAuth struct { } const RepoClowderBucketName = "content-sources-central-pulp-s3" -const LightwellCoverageUploadsBucketName = "lightwell-ui-coverage-uploads" type ObjectStore struct { URL string @@ -439,13 +432,6 @@ func setDefaults(v *viper.Viper) { v.SetDefault("clients.pulp_log_parser.s3.region", "") v.SetDefault("clients.pulp_log_parser.s3.file_prefix", "") - v.SetDefault("clients.lightwell.s3.coverage_uploads.url", "") - v.SetDefault("clients.lightwell.s3.coverage_uploads.name", "") - v.SetDefault("clients.lightwell.s3.coverage_uploads.access_key", "") - v.SetDefault("clients.lightwell.s3.coverage_uploads.secret_key", "") - v.SetDefault("clients.lightwell.s3.coverage_uploads.region", "") - v.SetDefault("clients.lightwell.s3.coverage_uploads.file_prefix", "") - v.SetDefault("tasking.heartbeat", 1*time.Minute) v.SetDefault("tasking.worker_count", 3) v.SetDefault("tasking.pgx_logging", true) @@ -479,10 +465,6 @@ func setDefaults(v *viper.Viper) { v.SetDefault("features.lightwell_beacon_and_lens.accounts", nil) v.SetDefault("features.lightwell_beacon_and_lens.organizations", nil) v.SetDefault("features.lightwell_beacon_and_lens.users", nil) - v.SetDefault("features.lightwell_store_uploads.enabled", false) - v.SetDefault("features.lightwell_store_uploads.accounts", nil) - v.SetDefault("features.lightwell_store_uploads.organizations", nil) - v.SetDefault("features.lightwell_store_uploads.users", nil) v.SetDefault("mocks.kessel.user_read_write", []string{"write-user"}) v.SetDefault("mocks.kessel.user_read", []string{"read-user"}) @@ -570,30 +552,6 @@ func Load() { v.Set("clients.pulp.custom_repo_objects.access_key", bucket.AccessKey) } } - - lightwellBucket, ok := clowder.ObjectBuckets[LightwellCoverageUploadsBucketName] - if !ok { - log.Logger.Error().Msgf("Expected S3 Bucket named %v but not found", LightwellCoverageUploadsBucketName) - } else { - v.Set("clients.lightwell.s3.coverage_uploads.url", ClowderS3Url(*clowder.LoadedConfig.ObjectStore)) - v.Set("clients.lightwell.s3.coverage_uploads.name", lightwellBucket.Name) - log.Logger.Warn().Msgf("Bucket name: %v", lightwellBucket.Name) - if lightwellBucket.Region == nil || *lightwellBucket.Region == "" { - v.Set("clients.lightwell.s3.coverage_uploads.region", "DummyRegion") - } else { - v.Set("clients.lightwell.s3.coverage_uploads.region", lightwellBucket.Region) - } - if lightwellBucket.SecretKey == nil || *lightwellBucket.SecretKey == "" { - log.Error().Msg("Object store secret Key is empty or nil!") - } else { - v.Set("clients.lightwell.s3.coverage_uploads.secret_key", *lightwellBucket.SecretKey) - } - if lightwellBucket.AccessKey == nil || *lightwellBucket.AccessKey == "" { - log.Error().Msg("Object store Access Key is empty or nil!") - } else { - v.Set("clients.lightwell.s3.coverage_uploads.access_key", lightwellBucket.AccessKey) - } - } } // Read configuration for instrumentation diff --git a/pkg/dao/repository_configs.go b/pkg/dao/repository_configs.go index ac3736f9d..d875a068d 100644 --- a/pkg/dao/repository_configs.go +++ b/pkg/dao/repository_configs.go @@ -2070,9 +2070,6 @@ func combineIntrospectionAndSnapshotStatuses(repoConfig *models.RepositoryConfig } else if repoConfig.LastSnapshotTask.Status == config.TaskStatusFailed && repoConfig.LastSnapshotUUID != "" { // Both introspection and snapshot failed and repo has previous snapshots return config.StatusUnavailable - } else if repoConfig.LastSnapshotTask.Status == config.TaskStatusFailed && repoConfig.LastSnapshotUUID == "" { - // Introspection failed (never succeeded), last snapshot failed, and repo has no previous snapshots - return config.StatusInvalid } case config.StatusValid: if repoConfig.LastSnapshotTask == nil { diff --git a/pkg/dao/repository_configs_test.go b/pkg/dao/repository_configs_test.go index 2745ef4a5..302dad36c 100644 --- a/pkg/dao/repository_configs_test.go +++ b/pkg/dao/repository_configs_test.go @@ -3794,18 +3794,6 @@ func (suite *RepositoryConfigSuite) TestCombineStatus() { }, Expected: "Invalid", }, - { - Name: "Introspection failed, last snapshot failed, and repo has no previous snapshots", - RepoConfig: &models.RepositoryConfiguration{ - Snapshot: true, - LastSnapshotTask: &models.TaskInfo{Status: config.TaskStatusFailed}, - LastSnapshotUUID: "", - }, - Repo: &models.Repository{ - LastIntrospectionStatus: config.StatusInvalid, - }, - Expected: "Invalid", - }, { Name: "Introspection successful, last snapshot failed, and repo has no previous snapshots", RepoConfig: &models.RepositoryConfiguration{ diff --git a/pkg/handler/api.go b/pkg/handler/api.go index c1c110d98..bf37ccc74 100644 --- a/pkg/handler/api.go +++ b/pkg/handler/api.go @@ -15,12 +15,13 @@ import ( "github.com/content-services/content-sources-backend/pkg/clients/candlepin_client" "github.com/content-services/content-sources-backend/pkg/clients/feature_service_client" "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" - "github.com/content-services/content-sources-backend/pkg/clients/s3_client" "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" "github.com/content-services/content-sources-backend/pkg/db" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/content-services/content-sources-backend/pkg/tasks/client" "github.com/content-services/content-sources-backend/pkg/tasks/queue" + "github.com/jackc/pgx/v5/pgxpool" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" ) @@ -72,24 +73,23 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { if err != nil { panic(err) } - var s3Client s3_client.S3Client - if config.Get().Clients.Lightwell.S3.CoverageUploads.Name == "" { - log.Warn().Msg("s3 not configured") - } else { - s3Client, err = s3_client.NewS3Client(config.Get().Clients.Lightwell.S3.CoverageUploads) - if err != nil { - panic(err) - } - } - ch := cache.Initialize() + pgxPool, err := pgxpool.New(ctx, db.GetUrl()) + if err != nil { + log.Warn().Err(err).Msg("failed to create pgx pool for lightwell store; advisory endpoints disabled") + } + var lightwellQuerier store.Querier + if pgxPool != nil { + lightwellQuerier = store.New(pgxPool) + } + for i := 0; i < len(paths); i++ { group := engine.Group(paths[i]) group.GET("/openapi.json", openapi) daoReg := dao.GetDaoRegistry(db.DB) - RegisterRepositoryRoutes(group, daoReg, &taskClient, &fsClient) + RegisterRepositoryRoutes(group, daoReg, &taskClient, &fsClient, lightwellQuerier) RegisterRepositoryParameterRoutes(group, daoReg, &fsClient) RegisterRpmRoutes(group, daoReg) RegisterPopularRepositoriesRoutes(group, daoReg) @@ -108,7 +108,11 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { RegisterModuleStreamsRoutes(group, daoReg) RegisterUserPreferencesRoutes(group, daoReg) RegisterLightwellVulnerabilityRoutes(group, daoReg) - RegisterCoverageReportRoutes(group, daoReg, s3Client) + RegisterCoverageReportRoutes(group, daoReg) + + if lightwellQuerier != nil { + RegisterLightwellAdvisoryRoutes(group, lightwellQuerier) + } // Register package and build routes if tang client is available pulpClient := pulp_client.GetPulpClientWithDomain("") @@ -120,6 +124,9 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { } if config.Tang != nil { RegisterPackageRoutes(group, daoReg, *config.Tang, pulpClient) + if lightwellQuerier != nil { + RegisterLightwellPackageRoutes(group, lightwellQuerier, daoReg, *config.Tang, pulpClient) + } } } diff --git a/pkg/handler/coverage_reports.go b/pkg/handler/coverage_reports.go index 19e7d1338..62e4c52c1 100644 --- a/pkg/handler/coverage_reports.go +++ b/pkg/handler/coverage_reports.go @@ -1,7 +1,6 @@ package handler import ( - "bytes" "crypto/sha256" "encoding/hex" "io" @@ -9,7 +8,6 @@ import ( "time" "github.com/content-services/content-sources-backend/pkg/api" - "github.com/content-services/content-sources-backend/pkg/clients/s3_client" "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" "github.com/content-services/content-sources-backend/pkg/db" @@ -26,7 +24,6 @@ const maxCoverageUploadSizeBytes = 500 * 1024 * 1024 // 500 MiB type CoverageReportHandler struct { DaoRegistry dao.DaoRegistry - S3 s3_client.S3Client } func checkLightwellBeaconAndLensAccessible(next echo.HandlerFunc) echo.HandlerFunc { @@ -38,10 +35,9 @@ func checkLightwellBeaconAndLensAccessible(next echo.HandlerFunc) echo.HandlerFu } } -func RegisterCoverageReportRoutes(engine *echo.Group, daoReg *dao.DaoRegistry, s3Client s3_client.S3Client) { +func RegisterCoverageReportRoutes(engine *echo.Group, daoReg *dao.DaoRegistry) { ch := CoverageReportHandler{ DaoRegistry: *daoReg, - S3: s3Client, } addRepoRoute(engine, http.MethodPost, "/coverage_reports/", ch.createCoverageReport, rbac.RbacVerbWrite, checkLightwellBeaconAndLensAccessible) addRepoRoute(engine, http.MethodGet, "/coverage_reports/:uuid", ch.getCoverageReport, rbac.RbacVerbRead, checkLightwellBeaconAndLensAccessible) @@ -90,15 +86,6 @@ func (ch *CoverageReportHandler) createCoverageReport(c echo.Context) error { return ce.NewErrorResponse(http.StatusBadRequest, "Error reading upload", err.Error()) } - if config.FeatureAccessible(c.Request().Context(), config.Get().Features.LightwellStoreUploads) { - if ch.S3 == nil { - return ce.NewErrorResponse(http.StatusInternalServerError, "Error uploading coverage report", "s3 not configured") - } - if err := ch.S3.Put(c.Request().Context(), storageKey, bytes.NewReader(fileBytes)); err != nil { - return ce.NewErrorResponse(http.StatusInternalServerError, "Error uploading coverage report", err.Error()) - } - } - sha256Hex := hex.EncodeToString(hash.Sum(nil)) sizeBytes := int64(len(fileBytes)) @@ -128,6 +115,7 @@ func (ch *CoverageReportHandler) createCoverageReport(c echo.Context) error { log.Error().Err(err).Str("uuid", reportUUID).Msg("failed to seed coverage report") } }(report.UUID) + return c.JSON(http.StatusCreated, report) } return c.JSON(http.StatusCreated, report) diff --git a/pkg/handler/coverage_reports_test.go b/pkg/handler/coverage_reports_test.go index 67937b4ad..0e0ecadcf 100644 --- a/pkg/handler/coverage_reports_test.go +++ b/pkg/handler/coverage_reports_test.go @@ -12,7 +12,6 @@ import ( "time" "github.com/content-services/content-sources-backend/pkg/api" - "github.com/content-services/content-sources-backend/pkg/clients/s3_client" "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" ce "github.com/content-services/content-sources-backend/pkg/errors" @@ -31,8 +30,7 @@ import ( type CoverageReportSuite struct { suite.Suite - reg *dao.MockDaoRegistry - s3Mock *s3_client.MockS3Client + reg *dao.MockDaoRegistry } func TestCoverageReportSuite(t *testing.T) { @@ -41,8 +39,6 @@ func TestCoverageReportSuite(t *testing.T) { func (suite *CoverageReportSuite) SetupTest() { suite.reg = dao.GetMockDaoRegistry(suite.T()) - suite.s3Mock = s3_client.NewMockS3Client(suite.T()) - config.Get().Options.SeedLightwellCoverageReports = false } func (suite *CoverageReportSuite) serveCoverageReportRouter(req *http.Request, enabled bool, authorized bool) (int, []byte, error) { @@ -63,12 +59,7 @@ func (suite *CoverageReportSuite) serveCoverageReportRouter(req *http.Request, e config.Get().Features.LightwellBeaconAndLens.Accounts = &[]string{seeds.RandomAccountId()} } - h := CoverageReportHandler{ - DaoRegistry: *suite.reg.ToDaoRegistry(), - S3: suite.s3Mock, - } - - RegisterCoverageReportRoutes(pathPrefix, &h.DaoRegistry, h.S3) + RegisterCoverageReportRoutes(pathPrefix, suite.reg.ToDaoRegistry()) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) @@ -230,7 +221,6 @@ func (suite *CoverageReportSuite) TestListCoverageReportPackagesWithFilters() { func (suite *CoverageReportSuite) TestCreateCoverageReport() { t := suite.T() - config.Get().Features.LightwellStoreUploads = config.Feature{Enabled: true} reqBody := &bytes.Buffer{} writer := multipart.NewWriter(reqBody) part, err := writer.CreateFormFile("file", "sbom.json") @@ -246,7 +236,6 @@ func (suite *CoverageReportSuite) TestCreateCoverageReport() { } suite.reg.CoverageReport.On("Create", mock.Anything, mock.Anything, mock.Anything). Return(expectedReport, nil) - suite.s3Mock.On("Put", mock.Anything, mock.Anything, mock.Anything).Return(nil) req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s/coverage_reports/", api.FullRootPath()), reqBody) req.Header.Set("Content-Type", writer.FormDataContentType()) diff --git a/pkg/handler/lightwell_advisories.go b/pkg/handler/lightwell_advisories.go new file mode 100644 index 000000000..250c5255a --- /dev/null +++ b/pkg/handler/lightwell_advisories.go @@ -0,0 +1,154 @@ +package handler + +import ( + "net/http" + + "github.com/content-services/content-sources-backend/pkg/api" + ce "github.com/content-services/content-sources-backend/pkg/errors" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" + "github.com/content-services/content-sources-backend/pkg/rbac" + "github.com/jackc/pgx/v5/pgtype" + "github.com/labstack/echo/v4" +) + +type LightwellAdvisoryHandler struct { + Store store.Querier +} + +func RegisterLightwellAdvisoryRoutes(engine *echo.Group, querier store.Querier) { + h := LightwellAdvisoryHandler{Store: querier} + // Flat cross-repo endpoint + addRepoRoute(engine, http.MethodGet, "/lightwell/advisories", h.list, rbac.RbacVerbRead) + // Nested repo-scoped alias + addRepoRoute(engine, http.MethodGet, "/lightwell/repositories/:repository_name/advisories", h.listRepoAdvisories, rbac.RbacVerbRead) +} + +// listLightwellAdvisories godoc +// @Summary List Lightwell Advisories +// @ID listLightwellAdvisories +// @Description List security advisories for Lightwell remediated packages with optional filtering. +// @Tags lightwell +// @Accept json +// @Produce json +// @Param repository_uuid query string false "Filter by repository UUID" +// @Param package_name query string false "Filter by package name (substring match)" +// @Param severity_min query string false "Minimum severity level (low, moderate, important, critical)" +// @Param cve_id query string false "Filter by CVE ID (exact match)" +// @Param limit query int false "Limit of results to return" +// @Param offset query int false "Offset into results" +// @Success 200 {object} api.LightwellAdvisoryCollectionResponse +// @Failure 400 {object} ce.ErrorResponse +// @Failure 500 {object} ce.ErrorResponse +// @Router /lightwell/advisories [get] +func (h *LightwellAdvisoryHandler) list(c echo.Context) error { + page := ParsePagination(c) + filters := parseLightwellAdvisoryFilters(c) + + severityMin, err := parseSeverityMin(filters.SeverityMin) + if err != nil { + return ce.NewErrorResponse(http.StatusBadRequest, "Invalid severity_min", err.Error()) + } + + var repoName *string + if filters.Repository != "" { + repoName = &filters.Repository + } + + var packageName *string + if filters.PackageName != "" { + packageName = &filters.PackageName + } + + var cveID *string + if filters.CveID != "" { + cveID = &filters.CveID + } + + rows, err := h.Store.ListAdvisories(c.Request().Context(), store.ListAdvisoriesParams{ + RepoName: repoName, + PackageName: packageName, + SeverityMin: severityMin, + CveID: cveID, + PageOffset: int32(page.Offset), //nolint:gosec // bounded by ParsePagination + PageLimit: int32(page.Limit), //nolint:gosec // bounded by MaxLimit (200) + }) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing advisories", err.Error()) + } + + var totalCount int64 + if len(rows) > 0 { + totalCount = rows[0].TotalCount + } + + resp := mapAdvisoryRowsToResponse(rows) + collResp := setCollectionResponseMetadata(&resp, c, totalCount) + return c.JSON(http.StatusOK, collResp) +} + +func mapAdvisoryRowsToResponse(rows []store.ListAdvisoriesRow) api.LightwellAdvisoryCollectionResponse { + data := make([]api.LightwellAdvisoryResponse, 0, len(rows)) + for _, row := range rows { + refURLs := row.ReferenceUrls + if refURLs == nil { + refURLs = []string{} + } + fixedVersions := row.FixedVersions + if fixedVersions == nil { + fixedVersions = []string{} + } + data = append(data, api.LightwellAdvisoryResponse{ + AdvisoryID: row.AdvisoryID, + Severity: row.Severity, + Details: row.Details, + ReferenceURLs: refURLs, + PackageName: row.PackageName, + FixedVersions: fixedVersions, + Repository: row.RepoName, + }) + } + return api.LightwellAdvisoryCollectionResponse{Data: data} +} + +func parseLightwellAdvisoryFilters(c echo.Context) api.LightwellAdvisoryFilterData { + var filters api.LightwellAdvisoryFilterData + _ = echo.QueryParamsBinder(c). + String("repository", &filters.Repository). + String("package_name", &filters.PackageName). + String("severity_min", &filters.SeverityMin). + String("cve_id", &filters.CveID). + BindError() + return filters +} + +var severityMap = map[string]int16{ + "low": 1, + "moderate": 2, + "important": 3, + "critical": 4, +} + +func parseSeverityMin(s string) (pgtype.Int2, error) { + if s == "" { + return pgtype.Int2{}, nil + } + val, ok := severityMap[s] + if !ok { + return pgtype.Int2{}, &invalidSeverityError{severity: s} + } + return pgtype.Int2{Int16: val, Valid: true}, nil +} + +type invalidSeverityError struct { + severity string +} + +func (e *invalidSeverityError) Error() string { + return "invalid severity: " + e.severity + " (must be one of: low, moderate, important, critical)" +} + +func (h *LightwellAdvisoryHandler) listRepoAdvisories(c echo.Context) error { + repoName := c.Param("repository_name") + c.QueryParams().Set("repository", repoName) + return h.list(c) +} diff --git a/pkg/handler/lightwell_advisories_test.go b/pkg/handler/lightwell_advisories_test.go new file mode 100644 index 000000000..97467ace6 --- /dev/null +++ b/pkg/handler/lightwell_advisories_test.go @@ -0,0 +1,286 @@ +package handler + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/config" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" + "github.com/content-services/content-sources-backend/pkg/middleware" + test_handler "github.com/content-services/content-sources-backend/pkg/test/handler" + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" + "github.com/labstack/echo/v4" + echo_middleware "github.com/labstack/echo/v4/middleware" + "github.com/redhatinsights/platform-go-middlewares/v2/identity" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type LightwellAdvisorySuite struct { + suite.Suite + echo *echo.Echo + mockQuerier *MockQuerier +} + +func TestLightwellAdvisorySuite(t *testing.T) { + suite.Run(t, new(LightwellAdvisorySuite)) +} + +func (s *LightwellAdvisorySuite) SetupTest() { + s.echo = echo.New() + s.echo.Use(echo_middleware.RequestIDWithConfig(echo_middleware.RequestIDConfig{ + TargetHeader: "x-rh-insights-request-id", + })) + s.echo.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) + s.mockQuerier = &MockQuerier{} +} + +func (s *LightwellAdvisorySuite) TearDownTest() { + require.NoError(s.T(), s.echo.Shutdown(context.Background())) +} + +func (s *LightwellAdvisorySuite) serveRouter(req *http.Request) (int, []byte, error) { + router := echo.New() + router.HTTPErrorHandler = config.CustomHTTPErrorHandler + router.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) + pathPrefix := router.Group(api.FullRootPath()) + RegisterLightwellAdvisoryRoutes(pathPrefix, s.mockQuerier) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + + response := rr.Result() + defer response.Body.Close() + + body, err := io.ReadAll(response.Body) + return response.StatusCode, body, err +} + +func (s *LightwellAdvisorySuite) TestListAdvisories() { + t := s.T() + + repoUUID := uuid.New() + rows := []store.ListAdvisoriesRow{ + { + Uuid: uuid.New(), + AdvisoryID: "CVE-2024-1234", + Severity: "critical", + SeverityOrder: 4, + Details: "Remote code execution vulnerability", + ReferenceUrls: []string{"https://access.redhat.com/security/cve/CVE-2024-1234"}, + PackageName: "spring-core", + FixedVersions: []string{"5.3.18.rhlw-00003"}, + RepoName: "lightwell/java/remediated", + RepositoryConfigurationUuid: repoUUID, + CreatedAt: time.Now(), + TotalCount: 1, + }, + } + + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { + return arg.PageLimit == int32(DefaultLimit) && arg.PageOffset == 0 + })).Return(rows, nil) + + path := fmt.Sprintf("%s/lightwell/advisories", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "CVE-2024-1234", resp.Data[0].AdvisoryID) + assert.Equal(t, "critical", resp.Data[0].Severity) + assert.Equal(t, "spring-core", resp.Data[0].PackageName) + assert.Equal(t, []string{"5.3.18.rhlw-00003"}, resp.Data[0].FixedVersions) + assert.Equal(t, "lightwell/java/remediated", resp.Data[0].Repository) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesWithFilters() { + t := s.T() + + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { + return arg.PackageName != nil && *arg.PackageName == "spring" && + arg.SeverityMin == pgtype.Int2{Int16: 3, Valid: true} && + arg.PageLimit == 10 && arg.PageOffset == 5 + })).Return([]store.ListAdvisoriesRow{}, nil) + + path := fmt.Sprintf("%s/lightwell/advisories?package_name=spring&severity_min=important&limit=10&offset=5", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(0), resp.Meta.Count) + assert.Empty(t, resp.Data) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesInvalidSeverity() { + t := s.T() + + path := fmt.Sprintf("%s/lightwell/advisories?severity_min=bogus", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, code) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesFilterByRepoName() { + t := s.T() + + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { + return arg.RepoName != nil && *arg.RepoName == "java-remediated" + })).Return([]store.ListAdvisoriesRow{}, nil) + + path := fmt.Sprintf("%s/lightwell/advisories?repository=java-remediated", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) +} + +func (s *LightwellAdvisorySuite) TestNestedRepoAdvisoriesAlias() { + t := s.T() + + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { + return arg.RepoName != nil && *arg.RepoName == "java-remediated" + })).Return([]store.ListAdvisoriesRow{ + { + Uuid: uuid.New(), + AdvisoryID: "CVE-2024-5678", + Severity: "important", + SeverityOrder: 3, + Details: "Test advisory via nested route", + ReferenceUrls: []string{}, + PackageName: "spring-core", + FixedVersions: []string{"5.3.18.rhlw-00003"}, + RepoName: "java-remediated", + CreatedAt: time.Now(), + TotalCount: 1, + }, + }, nil) + + path := fmt.Sprintf("%s/lightwell/repositories/java-remediated/advisories", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "CVE-2024-5678", resp.Data[0].AdvisoryID) + assert.Equal(t, "java-remediated", resp.Data[0].Repository) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesEmptyResult() { + t := s.T() + + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.Anything).Return([]store.ListAdvisoriesRow{}, nil) + + path := fmt.Sprintf("%s/lightwell/advisories", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(0), resp.Meta.Count) + assert.NotNil(t, resp.Data) + assert.Empty(t, resp.Data) +} + +// MockQuerier implements store.Querier for testing +type MockQuerier struct { + mock.Mock +} + +func (m *MockQuerier) ListAdvisories(ctx context.Context, arg store.ListAdvisoriesParams) ([]store.ListAdvisoriesRow, error) { + args := m.Called(ctx, arg) + val, _ := args.Get(0).([]store.ListAdvisoriesRow) + return val, args.Error(1) +} + +func (m *MockQuerier) CountAdvisoriesByRepo(ctx context.Context, repositoryConfigUuid uuid.UUID) (int64, error) { + args := m.Called(ctx, repositoryConfigUuid) + val, _ := args.Get(0).(int64) + return val, args.Error(1) +} + +func (m *MockQuerier) ListAdvisoriesByPackage(ctx context.Context, packageName string) ([]store.ListAdvisoriesByPackageRow, error) { + args := m.Called(ctx, packageName) + val, _ := args.Get(0).([]store.ListAdvisoriesByPackageRow) + return val, args.Error(1) +} + +func (m *MockQuerier) ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]store.ListAdvisoriesByCveIDRow, error) { + args := m.Called(ctx, cveID) + val, _ := args.Get(0).([]store.ListAdvisoriesByCveIDRow) + return val, args.Error(1) +} + +func (m *MockQuerier) CountAggregates(ctx context.Context, arg store.CountAggregatesParams) (store.CountAggregatesRow, error) { + args := m.Called(ctx, arg) + val, _ := args.Get(0).(store.CountAggregatesRow) + return val, args.Error(1) +} + +func (m *MockQuerier) CountByStage(ctx context.Context, arg store.CountByStageParams) ([]store.CountByStageRow, error) { + args := m.Called(ctx, arg) + val, _ := args.Get(0).([]store.CountByStageRow) + return val, args.Error(1) +} + +func (m *MockQuerier) ListCustomerIds(ctx context.Context) ([]string, error) { + args := m.Called(ctx) + val, _ := args.Get(0).([]string) + return val, args.Error(1) +} + +func (m *MockQuerier) ListLtwlsuptTicketIds(ctx context.Context, customerID string) ([]string, error) { + args := m.Called(ctx, customerID) + val, _ := args.Get(0).([]string) + return val, args.Error(1) +} + +func (m *MockQuerier) ListVulnerabilities(ctx context.Context, arg store.ListVulnerabilitiesParams) ([]store.ListVulnerabilitiesRow, error) { + args := m.Called(ctx, arg) + val, _ := args.Get(0).([]store.ListVulnerabilitiesRow) + return val, args.Error(1) +} diff --git a/pkg/handler/lightwell_packages.go b/pkg/handler/lightwell_packages.go new file mode 100644 index 000000000..6ef3697ed --- /dev/null +++ b/pkg/handler/lightwell_packages.go @@ -0,0 +1,728 @@ +package handler + +import ( + "context" + "errors" + "fmt" + "net/http" + "sort" + "strings" + "sync" + + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" + "github.com/content-services/content-sources-backend/pkg/config" + "github.com/content-services/content-sources-backend/pkg/dao" + ce "github.com/content-services/content-sources-backend/pkg/errors" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" + "github.com/content-services/content-sources-backend/pkg/rbac" + "github.com/content-services/tang/pkg/tangy" + "github.com/labstack/echo/v4" + "github.com/rs/zerolog/log" +) + +type LightwellPackagesHandler struct { + Store store.Querier + DaoRegistry dao.DaoRegistry + TangClient tangy.Tangy + PulpClient pulp_client.PulpClient +} + +func RegisterLightwellPackageRoutes(engine *echo.Group, querier store.Querier, daoReg *dao.DaoRegistry, tangClient tangy.Tangy, pulpClient pulp_client.PulpClient) { + h := LightwellPackagesHandler{ + Store: querier, + DaoRegistry: *daoReg, + TangClient: tangClient, + PulpClient: pulpClient, + } + // Flat cross-repo endpoints + addRepoRoute(engine, http.MethodGet, "/lightwell/packages", h.listPackages, rbac.RbacVerbRead) + addRepoRoute(engine, http.MethodGet, "/lightwell/package_versions", h.listPackageVersions, rbac.RbacVerbRead) + // Nested repo-scoped aliases + addRepoRoute(engine, http.MethodGet, "/lightwell/repositories/:repository_name/packages", h.listRepoPackages, rbac.RbacVerbRead) + addRepoRoute(engine, http.MethodGet, "/lightwell/repositories/:repository_name/package_versions", h.listRepoPackageVersions, rbac.RbacVerbRead) +} + +// listLightwellPackages godoc +// @Summary List Lightwell Packages (cross-repo) +// @ID listLightwellPackages +// @Description List packages aggregated across all Lightwell repositories, with optional filtering by content type, name, and security level. +// @Tags lightwell +// @Accept json +// @Produce json +// @Param type query string false "Filter by content type (maven, python, npm)" +// @Param name query string false "Filter by package name (substring match)" +// @Param security_level query string false "Filter by security level (validated, remediated)" +// @Param limit query int false "Limit of results to return" +// @Param offset query int false "Offset into results" +// @Success 200 {object} api.LightwellPackageCollectionResponse +// @Failure 400 {object} ce.ErrorResponse +// @Failure 500 {object} ce.ErrorResponse +// @Router /lightwell/packages [get] +func (h *LightwellPackagesHandler) listPackages(c echo.Context) error { + page := ParsePagination(c) + filters := parseLightwellPackageFilters(c) + + if err := validateContentType(filters.ContentType); err != nil { + return ce.NewErrorResponse(http.StatusBadRequest, "Invalid content_type filter", err.Error()) + } + + repos, err := h.fetchLightwellRepos(c, filters.ContentType, filters.SecurityLevel) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing Lightwell repositories", err.Error()) + } + if filters.Repository != "" { + repos = filterReposByName(repos, filters.Repository) + } + + items, err := h.aggregatePackages(c.Request().Context(), repos, filters.Name) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error retrieving packages", err.Error()) + } + + sortLightwellPackages(items, page.SortBy) + totalCount := int64(len(items)) + paged := paginatePackages(items, page.Offset, page.Limit) + resp := api.LightwellPackageCollectionResponse{Data: paged} + collResp := setCollectionResponseMetadata(&resp, c, totalCount) + return c.JSON(http.StatusOK, collResp) +} + +// listLightwellPackageVersions godoc +// @Summary List Lightwell Package Versions (cross-repo) +// @ID listLightwellPackageVersions +// @Description List individual package versions aggregated across all Lightwell repositories, with optional CVE-based filtering. +// @Tags lightwell +// @Accept json +// @Produce json +// @Param type query string false "Filter by content type (maven, python, npm)" +// @Param name query string false "Filter by package name (substring match)" +// @Param security_level query string false "Filter by security level (validated, remediated)" +// @Param repository query string false "Filter by repository name" +// @Param resolves_cve_id query string false "Show only packages that resolve this CVE" +// @Param vulnerable_to_cve_id query string false "Show only packages vulnerable to this CVE" +// @Param limit query int false "Limit of results to return" +// @Param offset query int false "Offset into results" +// @Success 200 {object} api.LightwellPackageVersionCollectionResponse +// @Failure 400 {object} ce.ErrorResponse +// @Failure 500 {object} ce.ErrorResponse +// @Router /lightwell/package_versions [get] +func (h *LightwellPackagesHandler) listPackageVersions(c echo.Context) error { + page := ParsePagination(c) + filters := parseLightwellPackageVersionFilters(c) + + if err := validateContentType(filters.ContentType); err != nil { + return ce.NewErrorResponse(http.StatusBadRequest, "Invalid content_type filter", err.Error()) + } + + repos, err := h.fetchLightwellRepos(c, filters.ContentType, filters.SecurityLevel) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing Lightwell repositories", err.Error()) + } + if filters.Repository != "" { + repos = filterReposByName(repos, filters.Repository) + } + + items, err := h.aggregatePackageVersions(c.Request().Context(), repos, filters.Name) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error retrieving package versions", err.Error()) + } + + if filters.ResolvesCveID != "" { + items, err = h.filterVersionsByResolvingCve(c.Request().Context(), items, filters.ResolvesCveID) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error filtering by CVE", err.Error()) + } + } + if filters.VulnerableToCveID != "" { + items, err = h.filterVersionsByVulnerableCve(c.Request().Context(), items, filters.VulnerableToCveID) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error filtering by CVE", err.Error()) + } + } + + sortLightwellVersions(items, page.SortBy) + totalCount := int64(len(items)) + paged := paginateVersions(items, page.Offset, page.Limit) + resp := api.LightwellPackageVersionCollectionResponse{Data: paged} + collResp := setCollectionResponseMetadata(&resp, c, totalCount) + return c.JSON(http.StatusOK, collResp) +} + +// fetchLightwellRepos returns Lightwell repos for the caller's org, optionally +// filtered by content type and security level. +func (h *LightwellPackagesHandler) fetchLightwellRepos(c echo.Context, contentType, securityLevel string) ([]api.RepositoryResponse, error) { + _, orgID := getAccountIdOrgId(c) + ctx := c.Request().Context() + + filter := api.FilterData{Origin: config.OriginLightwell} + if contentType != "" { + filter.ContentType = contentType + } + + repos, _, err := h.DaoRegistry.RepositoryConfig.List(ctx, orgID, api.PaginationData{Limit: MaxLimit}, filter) + if err != nil { + return nil, err + } + + if securityLevel == "" { + return repos.Data, nil + } + filtered := make([]api.RepositoryResponse, 0, len(repos.Data)) + for _, r := range repos.Data { + if strings.EqualFold(r.SecurityLevel, securityLevel) { + filtered = append(filtered, r) + } + } + return filtered, nil +} + +type repoPackageResult struct { + repo api.RepositoryResponse + pkgs []api.LightwellPackageResponse + err error +} + +// aggregatePackages queries Tang for each repo in parallel and merges results. +func (h *LightwellPackagesHandler) aggregatePackages(ctx context.Context, repos []api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageResponse, error) { + results := make([]repoPackageResult, len(repos)) + var wg sync.WaitGroup + + for i, repo := range repos { + wg.Add(1) + go func(idx int, r api.RepositoryResponse) { + defer wg.Done() + pkgs, err := h.fetchPackagesFromRepo(ctx, r, nameSearch) + results[idx] = repoPackageResult{repo: r, pkgs: pkgs, err: err} + }(i, repo) + } + wg.Wait() + + var combined []api.LightwellPackageResponse + var errs []error + for _, res := range results { + if res.err != nil { + errs = append(errs, fmt.Errorf("repo %s: %w", res.repo.Name, res.err)) + continue + } + combined = append(combined, res.pkgs...) + } + + if len(errs) > 0 && len(combined) == 0 { + return nil, errors.Join(errs...) + } + if len(errs) > 0 { + log.Warn().Errs("errors", errs).Msg("partial failure fetching cross-repo packages") + } + + return combined, nil +} + +func (h *LightwellPackagesHandler) fetchPackagesFromRepo(ctx context.Context, repo api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageResponse, error) { + if repo.PublishedDistBasePath == "" { + return nil, nil + } + + repositoryHref, err := h.resolveRepositoryHref(ctx, repo) + if err != nil { + return nil, err + } + + // Fetch all packages from this repo (no server-side pagination — small datasets) + pageOpts := tangy.PageOptions{Offset: 0, Limit: MaxLimit} + + switch repo.ContentType { + case config.ContentTypeMaven: + tangResp, err := h.TangClient.MavenPackageList(ctx, repositoryHref, tangy.MavenPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return mapMavenToLightwellPackages(tangResp, repo), nil + + case config.ContentTypePython: + tangResp, err := h.TangClient.PythonPackageList(ctx, repositoryHref, tangy.PythonPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return mapPythonToLightwellPackages(tangResp, repo), nil + + case config.ContentTypeNpm: + tangResp, err := h.TangClient.NpmPackageList(ctx, repositoryHref, tangy.NpmPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return mapNpmToLightwellPackages(tangResp, repo), nil + + default: + return nil, nil + } +} + +type repoVersionResult struct { + repo api.RepositoryResponse + versions []api.LightwellPackageVersionResponse + err error +} + +// aggregatePackageVersions queries Tang for each repo in parallel and expands +// every package into individual version items. +func (h *LightwellPackagesHandler) aggregatePackageVersions(ctx context.Context, repos []api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageVersionResponse, error) { + results := make([]repoVersionResult, len(repos)) + var wg sync.WaitGroup + + for i, repo := range repos { + wg.Add(1) + go func(idx int, r api.RepositoryResponse) { + defer wg.Done() + versions, err := h.fetchVersionsFromRepo(ctx, r, nameSearch) + results[idx] = repoVersionResult{repo: r, versions: versions, err: err} + }(i, repo) + } + wg.Wait() + + var combined []api.LightwellPackageVersionResponse + var errs []error + for _, res := range results { + if res.err != nil { + errs = append(errs, fmt.Errorf("repo %s: %w", res.repo.Name, res.err)) + continue + } + combined = append(combined, res.versions...) + } + + if len(errs) > 0 && len(combined) == 0 { + return nil, errors.Join(errs...) + } + if len(errs) > 0 { + log.Warn().Errs("errors", errs).Msg("partial failure fetching cross-repo versions") + } + + return combined, nil +} + +func (h *LightwellPackagesHandler) fetchVersionsFromRepo(ctx context.Context, repo api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageVersionResponse, error) { + if repo.PublishedDistBasePath == "" { + return nil, nil + } + + repositoryHref, err := h.resolveRepositoryHref(ctx, repo) + if err != nil { + return nil, err + } + + pageOpts := tangy.PageOptions{Offset: 0, Limit: MaxLimit} + + switch repo.ContentType { + case config.ContentTypeMaven: + tangResp, err := h.TangClient.MavenPackageList(ctx, repositoryHref, tangy.MavenPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return expandMavenVersions(tangResp, repo), nil + + case config.ContentTypePython: + tangResp, err := h.TangClient.PythonPackageList(ctx, repositoryHref, tangy.PythonPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return expandPythonVersions(tangResp, repo), nil + + case config.ContentTypeNpm: + tangResp, err := h.TangClient.NpmPackageList(ctx, repositoryHref, tangy.NpmPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return expandNpmVersions(tangResp, repo), nil + + default: + return nil, nil + } +} + +func (h *LightwellPackagesHandler) resolveRepositoryHref(ctx context.Context, repo api.RepositoryResponse) (string, error) { + domainName, err := h.DaoRegistry.Domain.FetchOrCreateDomain(ctx, repo.OrgID) + if err != nil { + return "", err + } + pulpClient := h.PulpClient.WithDomain(domainName) + href, err := pulpClient.ResolveRepositoryFromBasePath(ctx, repo.PublishedDistBasePath) + if err != nil { + return "", fmt.Errorf("repo %s: %w", repo.UUID, err) + } + if href == nil { + return "", fmt.Errorf("repo %s: distribution not found", repo.UUID) + } + return *href, nil +} + +// filterVersionsByResolvingCve keeps only versions that fix the given CVE. +func (h *LightwellPackagesHandler) filterVersionsByResolvingCve(ctx context.Context, items []api.LightwellPackageVersionResponse, cveID string) ([]api.LightwellPackageVersionResponse, error) { + advisories, err := h.Store.ListAdvisoriesByCveID(ctx, cveID) + if err != nil { + return nil, err + } + + fixedSet := make(map[string]map[string]bool) // package_name -> set of fixed versions + for _, adv := range advisories { + if fixedSet[adv.PackageName] == nil { + fixedSet[adv.PackageName] = make(map[string]bool) + } + for _, v := range adv.FixedVersions { + fixedSet[adv.PackageName][v] = true + } + } + + var result []api.LightwellPackageVersionResponse + for _, item := range items { + if versions, ok := fixedSet[item.Name]; ok && versions[item.Version] { + result = append(result, item) + } + } + return result, nil +} + +// filterVersionsByVulnerableCve keeps only versions of packages affected by +// the given CVE that are NOT in the fixed-versions list. +func (h *LightwellPackagesHandler) filterVersionsByVulnerableCve(ctx context.Context, items []api.LightwellPackageVersionResponse, cveID string) ([]api.LightwellPackageVersionResponse, error) { + advisories, err := h.Store.ListAdvisoriesByCveID(ctx, cveID) + if err != nil { + return nil, err + } + + affectedPackages := make(map[string]bool) + fixedSet := make(map[string]map[string]bool) + for _, adv := range advisories { + affectedPackages[adv.PackageName] = true + if fixedSet[adv.PackageName] == nil { + fixedSet[adv.PackageName] = make(map[string]bool) + } + for _, v := range adv.FixedVersions { + fixedSet[adv.PackageName][v] = true + } + } + + var result []api.LightwellPackageVersionResponse + for _, item := range items { + if affectedPackages[item.Name] && !fixedSet[item.Name][item.Version] { + result = append(result, item) + } + } + return result, nil +} + +// --- mapping helpers --- + +func mapMavenToLightwellPackages(resp tangy.MavenPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageResponse { + out := make([]api.LightwellPackageResponse, 0, len(resp.Results)) + for _, item := range resp.Results { + releases := make([]api.ReleaseInfo, len(item.LatestReleases)) + for j, rel := range item.LatestReleases { + releases[j] = api.ReleaseInfo{Version: rel.Version, Release: rel.Release, CreatedAt: rel.CreatedAt} + } + out = append(out, api.LightwellPackageResponse{ + Name: item.ArtifactID, + Group: item.GroupID, + ContentType: config.ContentTypeMaven, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + Versions: item.Versions, + LatestReleases: releases, + }) + } + return out +} + +func mapPythonToLightwellPackages(resp tangy.PythonPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageResponse { + out := make([]api.LightwellPackageResponse, 0, len(resp.Results)) + for _, item := range resp.Results { + releases := make([]api.ReleaseInfo, len(item.LatestVersions)) + for j, ver := range item.LatestVersions { + releases[j] = api.ReleaseInfo{Version: ver.Version, CreatedAt: ver.CreatedAt} + } + out = append(out, api.LightwellPackageResponse{ + Name: item.NameNormalized, + ContentType: config.ContentTypePython, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + Versions: item.Versions, + LatestReleases: releases, + }) + } + return out +} + +func mapNpmToLightwellPackages(resp tangy.NpmPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageResponse { + out := make([]api.LightwellPackageResponse, 0, len(resp.Results)) + for _, item := range resp.Results { + releases := make([]api.ReleaseInfo, len(item.LatestVersions)) + for j, ver := range item.LatestVersions { + releases[j] = api.ReleaseInfo{Version: ver.Version, CreatedAt: ver.CreatedAt} + } + scope, name := parseNpmPackageName(item.Name) + out = append(out, api.LightwellPackageResponse{ + Name: name, + Group: scope, + ContentType: config.ContentTypeNpm, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + Versions: item.Versions, + LatestReleases: releases, + }) + } + return out +} + +func expandMavenVersions(resp tangy.MavenPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageVersionResponse { + var out []api.LightwellPackageVersionResponse + for _, item := range resp.Results { + relMap := latestReleaseMap(item.LatestReleases) + for _, v := range item.Versions { + ver := api.LightwellPackageVersionResponse{ + Name: item.ArtifactID, + Group: item.GroupID, + Version: v, + ContentType: config.ContentTypeMaven, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + } + if rel, ok := relMap[v]; ok { + ver.Release = rel.Release + ver.CreatedAt = rel.CreatedAt + } + out = append(out, ver) + } + } + return out +} + +func expandPythonVersions(resp tangy.PythonPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageVersionResponse { + var out []api.LightwellPackageVersionResponse + for _, item := range resp.Results { + verMap := latestVersionMap(item.LatestVersions) + for _, v := range item.Versions { + ver := api.LightwellPackageVersionResponse{ + Name: item.NameNormalized, + Version: v, + ContentType: config.ContentTypePython, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + } + if info, ok := verMap[v]; ok { + ver.CreatedAt = info.CreatedAt + } + out = append(out, ver) + } + } + return out +} + +func expandNpmVersions(resp tangy.NpmPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageVersionResponse { + var out []api.LightwellPackageVersionResponse + for _, item := range resp.Results { + scope, name := parseNpmPackageName(item.Name) + verMap := npmVersionMap(item.LatestVersions) + for _, v := range item.Versions { + ver := api.LightwellPackageVersionResponse{ + Name: name, + Group: scope, + Version: v, + ContentType: config.ContentTypeNpm, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + } + if info, ok := verMap[v]; ok { + ver.CreatedAt = info.CreatedAt + } + out = append(out, ver) + } + } + return out +} + +// --- filter / pagination helpers --- + +func parseLightwellPackageFilters(c echo.Context) api.LightwellPackageFilterData { + var f api.LightwellPackageFilterData + _ = echo.QueryParamsBinder(c). + String("content_type", &f.ContentType). + String("name", &f.Name). + String("repository", &f.Repository). + String("security_level", &f.SecurityLevel). + BindError() + return f +} + +func parseLightwellPackageVersionFilters(c echo.Context) api.LightwellPackageVersionFilterData { + var f api.LightwellPackageVersionFilterData + _ = echo.QueryParamsBinder(c). + String("content_type", &f.ContentType). + String("name", &f.Name). + String("security_level", &f.SecurityLevel). + String("repository", &f.Repository). + String("resolves_cve_id", &f.ResolvesCveID). + String("vulnerable_to_cve_id", &f.VulnerableToCveID). + BindError() + return f +} + +var validContentTypes = map[string]bool{ + config.ContentTypeMaven: true, + config.ContentTypePython: true, + config.ContentTypeNpm: true, +} + +func validateContentType(ct string) error { + if ct == "" { + return nil + } + if !validContentTypes[ct] { + return fmt.Errorf("unsupported type: %s (must be maven, python, or npm)", ct) + } + return nil +} + +func filterReposByName(repos []api.RepositoryResponse, name string) []api.RepositoryResponse { + var out []api.RepositoryResponse + for _, r := range repos { + if strings.EqualFold(r.Name, name) { + out = append(out, r) + } + } + return out +} + +func paginatePackages(items []api.LightwellPackageResponse, offset, limit int) []api.LightwellPackageResponse { + if offset >= len(items) { + return []api.LightwellPackageResponse{} + } + end := offset + limit + if end > len(items) { + end = len(items) + } + return items[offset:end] +} + +func paginateVersions(items []api.LightwellPackageVersionResponse, offset, limit int) []api.LightwellPackageVersionResponse { + if offset >= len(items) { + return []api.LightwellPackageVersionResponse{} + } + end := offset + limit + if end > len(items) { + end = len(items) + } + return items[offset:end] +} + +// release-info lookup helpers for version expansion + +type mavenRelInfo struct { + Release string + CreatedAt string +} + +func latestReleaseMap(releases []tangy.MavenReleaseInfo) map[string]mavenRelInfo { + m := make(map[string]mavenRelInfo, len(releases)) + for _, r := range releases { + m[r.Version] = mavenRelInfo{Release: r.Release, CreatedAt: r.CreatedAt} + } + return m +} + +type versionCreatedAt struct { + CreatedAt string +} + +func latestVersionMap(versions []tangy.PythonVersionInfo) map[string]versionCreatedAt { + m := make(map[string]versionCreatedAt, len(versions)) + for _, v := range versions { + m[v.Version] = versionCreatedAt{CreatedAt: v.CreatedAt} + } + return m +} + +func npmVersionMap(versions []tangy.NpmVersionInfo) map[string]versionCreatedAt { + m := make(map[string]versionCreatedAt, len(versions)) + for _, v := range versions { + m[v.Version] = versionCreatedAt{CreatedAt: v.CreatedAt} + } + return m +} + +// --- nested repo-scoped alias handlers --- + +func (h *LightwellPackagesHandler) listRepoPackages(c echo.Context) error { + repoName := c.Param("repository_name") + c.QueryParams().Set("repository", repoName) + return h.listPackages(c) +} + +func (h *LightwellPackagesHandler) listRepoPackageVersions(c echo.Context) error { + repoName := c.Param("repository_name") + c.QueryParams().Set("repository", repoName) + return h.listPackageVersions(c) +} + +// --- sort helpers --- + +func sortLightwellPackages(items []api.LightwellPackageResponse, sortBy string) { + field, dir := parseSortBy(sortBy) + if field == "" { + field = "name" + } + sort.SliceStable(items, func(i, j int) bool { + var less bool + switch field { + case "name": + less = items[i].Name < items[j].Name + case "content_type": + less = items[i].ContentType < items[j].ContentType + case "repository": + less = items[i].Repository < items[j].Repository + default: + less = items[i].Name < items[j].Name + } + if dir == "desc" { + return !less + } + return less + }) +} + +func sortLightwellVersions(items []api.LightwellPackageVersionResponse, sortBy string) { + field, dir := parseSortBy(sortBy) + if field == "" { + field = "name" + } + sort.SliceStable(items, func(i, j int) bool { + var less bool + switch field { + case "name": + less = items[i].Name < items[j].Name + case "version": + less = items[i].Version < items[j].Version + case "content_type": + less = items[i].ContentType < items[j].ContentType + case "repository": + less = items[i].Repository < items[j].Repository + default: + less = items[i].Name < items[j].Name + } + if dir == "desc" { + return !less + } + return less + }) +} + +func parseSortBy(sortBy string) (field, direction string) { + if sortBy == "" { + return "", "asc" + } + parts := strings.Fields(sortBy) + field = strings.ToLower(parts[0]) + direction = "asc" + if len(parts) > 1 && strings.EqualFold(parts[1], "desc") { + direction = "desc" + } + return field, direction +} diff --git a/pkg/handler/lightwell_packages_test.go b/pkg/handler/lightwell_packages_test.go new file mode 100644 index 000000000..39d99a623 --- /dev/null +++ b/pkg/handler/lightwell_packages_test.go @@ -0,0 +1,538 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" + "github.com/content-services/content-sources-backend/pkg/config" + "github.com/content-services/content-sources-backend/pkg/dao" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" + "github.com/content-services/content-sources-backend/pkg/middleware" + "github.com/content-services/content-sources-backend/pkg/test" + test_handler "github.com/content-services/content-sources-backend/pkg/test/handler" + "github.com/content-services/tang/pkg/tangy" + "github.com/labstack/echo/v4" + echo_middleware "github.com/labstack/echo/v4/middleware" + "github.com/redhatinsights/platform-go-middlewares/v2/identity" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type LightwellPackagesSuite struct { + suite.Suite + reg *dao.MockDaoRegistry + tangClient *tangy.MockTangy + pulpClient *pulp_client.MockPulpClient + querier *MockQuerier +} + +func TestLightwellPackagesSuite(t *testing.T) { + suite.Run(t, new(LightwellPackagesSuite)) +} + +func (s *LightwellPackagesSuite) SetupTest() { + s.reg = dao.GetMockDaoRegistry(s.T()) + s.tangClient = tangy.NewMockTangy(s.T()) + s.pulpClient = pulp_client.NewMockPulpClient(s.T()) + s.querier = &MockQuerier{} +} + +func (s *LightwellPackagesSuite) serveRouter(req *http.Request) (int, []byte, error) { + router := echo.New() + router.HTTPErrorHandler = config.CustomHTTPErrorHandler + router.Use(echo_middleware.RequestIDWithConfig(echo_middleware.RequestIDConfig{ + TargetHeader: "x-rh-insights-request-id", + })) + router.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) + pathPrefix := router.Group(api.FullRootPath()) + RegisterLightwellPackageRoutes(pathPrefix, s.querier, s.reg.ToDaoRegistry(), s.tangClient, s.pulpClient) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + + response := rr.Result() + defer response.Body.Close() + body, err := io.ReadAll(response.Body) + return response.StatusCode, body, err +} + +// stubLightwellRepos sets up the DAO mock to return the given repos for a List call with origin=lightwell. +func (s *LightwellPackagesSuite) stubLightwellRepos(repos []api.RepositoryResponse) { + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { return f.Origin == config.OriginLightwell }), + ).Return(api.RepositoryCollectionResponse{Data: repos}, int64(len(repos)), nil) +} + +func (s *LightwellPackagesSuite) stubRepoHref(repo api.RepositoryResponse, href string) { + domainName := "test-domain" + s.reg.Domain.On("FetchOrCreateDomain", test.MockCtx(), repo.OrgID).Return(domainName, nil).Maybe() + s.pulpClient.On("WithDomain", domainName).Return(s.pulpClient).Maybe() + s.pulpClient.On("ResolveRepositoryFromBasePath", test.MockCtx(), repo.PublishedDistBasePath).Return(&href, nil).Maybe() +} + +func newMavenRepo() api.RepositoryResponse { + return api.RepositoryResponse{ + UUID: "aaa-bbb-ccc", + Name: "lightwell/java/remediated", + ContentType: config.ContentTypeMaven, + Origin: config.OriginLightwell, + SecurityLevel: "remediated", + PublishedDistBasePath: "java/remediated", + OrgID: test_handler.MockOrgId, + } +} + +func newPythonRepo() api.RepositoryResponse { + return api.RepositoryResponse{ + UUID: "ddd-eee-fff", + Name: "lightwell/python/remediated", + ContentType: config.ContentTypePython, + Origin: config.OriginLightwell, + SecurityLevel: "remediated", + PublishedDistBasePath: "python/remediated", + OrgID: test_handler.MockOrgId, + } +} + +func mavenTangResponse() tangy.MavenPackageListResponse { + return tangy.MavenPackageListResponse{ + Results: []tangy.MavenPackageListItem{ + { + GroupID: "com.fasterxml.jackson.core", + ArtifactID: "jackson-databind", + Versions: []string{"2.15.3.rhlw-00001", "2.14.2.rhlw-00001"}, + LatestReleases: []tangy.MavenReleaseInfo{ + {Version: "2.15.3.rhlw-00001", Release: "rhlw-00001", CreatedAt: "2024-06-01T12:00:00Z"}, + }, + }, + }, + Total: 1, Limit: 200, Offset: 0, + } +} + +func pythonTangResponse() tangy.PythonPackageListResponse { + return tangy.PythonPackageListResponse{ + Results: []tangy.PythonPackageListItem{ + { + Name: "requests", + NameNormalized: "requests", + Versions: []string{"2.31.0.rhlw-00001"}, + LatestVersions: []tangy.PythonVersionInfo{ + {Version: "2.31.0.rhlw-00001", CreatedAt: "2024-05-10T08:00:00Z"}, + }, + }, + }, + Total: 1, Limit: 200, Offset: 0, + } +} + +// --- /lightwell/packages tests --- + +func (s *LightwellPackagesSuite) TestListPackagesSingleRepo() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/default/api/v3/repositories/maven/maven/some-uuid/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, "com.fasterxml.jackson.core", resp.Data[0].Group) + assert.Equal(t, config.ContentTypeMaven, resp.Data[0].ContentType) + assert.Equal(t, "lightwell/java/remediated", resp.Data[0].Repository) + assert.Equal(t, 2, len(resp.Data[0].Versions)) +} + +func (s *LightwellPackagesSuite) TestListPackagesMultiRepo() { + t := s.T() + + mavenRepo := newMavenRepo() + pythonRepo := newPythonRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo, pythonRepo}) + + mavenHref := "/api/pulp/repos/maven/1/" + pythonHref := "/api/pulp/repos/python/1/" + s.stubRepoHref(mavenRepo, mavenHref) + s.stubRepoHref(pythonRepo, pythonHref) + + s.tangClient.On("MavenPackageList", test.MockCtx(), mavenHref, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + s.tangClient.On("PythonPackageList", test.MockCtx(), pythonHref, + tangy.PythonPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(pythonTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) + assert.Len(t, resp.Data, 2) + + contentTypes := map[string]bool{} + for _, p := range resp.Data { + contentTypes[p.ContentType] = true + } + assert.True(t, contentTypes[config.ContentTypeMaven]) + assert.True(t, contentTypes[config.ContentTypePython]) +} + +func (s *LightwellPackagesSuite) TestListPackagesTypeFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + // Only maven repo should be returned when filtering by content_type=maven + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { + return f.Origin == config.OriginLightwell && f.ContentType == config.ContentTypeMaven + }), + ).Return(api.RepositoryCollectionResponse{Data: []api.RepositoryResponse{mavenRepo}}, int64(1), nil) + + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/packages?content_type=maven", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Len(t, resp.Data, 1) + assert.Equal(t, config.ContentTypeMaven, resp.Data[0].ContentType) +} + +func (s *LightwellPackagesSuite) TestListPackagesInvalidType() { + t := s.T() + + path := fmt.Sprintf("%s/lightwell/packages?content_type=invalid", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, code) +} + +func (s *LightwellPackagesSuite) TestListPackagesEmptyResult() { + t := s.T() + + s.stubLightwellRepos([]api.RepositoryResponse{}) + + path := fmt.Sprintf("%s/lightwell/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(0), resp.Meta.Count) + assert.NotNil(t, resp.Data) + assert.Empty(t, resp.Data) +} + +// --- /lightwell/package_versions tests --- + +func (s *LightwellPackagesSuite) TestListPackageVersionsSingleRepo() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) // 2 versions for jackson-databind + assert.Len(t, resp.Data, 2) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, config.ContentTypeMaven, resp.Data[0].ContentType) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsWithNameFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{Search: "jackson"}, + tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/package_versions?name=jackson", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + assert.Len(t, resp.Data, 2) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsPagination() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + // Request with limit=1&offset=0 — should get 1 of 2 versions + path := fmt.Sprintf("%s/lightwell/package_versions?limit=1&offset=0", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) // total is 2 + assert.Len(t, resp.Data, 1) // page is 1 + assert.NotEmpty(t, resp.Links.Next) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsInvalidType() { + t := s.T() + + path := fmt.Sprintf("%s/lightwell/package_versions?content_type=bogus", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, code) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsEmptyResult() { + t := s.T() + + s.stubLightwellRepos([]api.RepositoryResponse{}) + + path := fmt.Sprintf("%s/lightwell/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + assert.Equal(t, int64(0), resp.Meta.Count) + assert.NotNil(t, resp.Data) + assert.Empty(t, resp.Data) +} + +// --- resolves_cve_id / vulnerable_to_cve_id filter tests --- + +func (s *LightwellPackagesSuite) TestListPackageVersionsResolvesCveFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + s.querier.On("ListAdvisoriesByCveID", test.MockCtx(), "CVE-2024-9999").Return([]store.ListAdvisoriesByCveIDRow{ + { + PackageName: "jackson-databind", + FixedVersions: []string{"2.15.3.rhlw-00001"}, + RepoName: "lightwell/java/remediated", + Severity: "critical", + }, + }, nil) + + path := fmt.Sprintf("%s/lightwell/package_versions?resolves_cve_id=CVE-2024-9999", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, "2.15.3.rhlw-00001", resp.Data[0].Version) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsVulnerableToCveFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + // Advisory says jackson-databind is fixed at 2.15.3.rhlw-00001, so + // the older version 2.14.2.rhlw-00001 should be returned as vulnerable. + s.querier.On("ListAdvisoriesByCveID", test.MockCtx(), "CVE-2024-8888").Return([]store.ListAdvisoriesByCveIDRow{ + { + PackageName: "jackson-databind", + FixedVersions: []string{"2.15.3.rhlw-00001"}, + RepoName: "lightwell/java/remediated", + Severity: "important", + }, + }, nil) + + path := fmt.Sprintf("%s/lightwell/package_versions?vulnerable_to_cve_id=CVE-2024-8888", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, "2.14.2.rhlw-00001", resp.Data[0].Version) +} + +// --- nested repo-scoped alias tests --- + +func (s *LightwellPackagesSuite) TestNestedRepoPackagesAlias() { + t := s.T() + + mavenRepo := newMavenRepo() + mavenRepo.Name = "java-remediated" + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { return f.Origin == config.OriginLightwell }), + ).Return(api.RepositoryCollectionResponse{Data: []api.RepositoryResponse{mavenRepo}}, int64(1), nil) + + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/repositories/java-remediated/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) +} + +func (s *LightwellPackagesSuite) TestNestedRepoPackageVersionsAlias() { + t := s.T() + + mavenRepo := newMavenRepo() + mavenRepo.Name = "java-remediated" + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { return f.Origin == config.OriginLightwell }), + ).Return(api.RepositoryCollectionResponse{Data: []api.RepositoryResponse{mavenRepo}}, int64(1), nil) + + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/repositories/java-remediated/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) + assert.Len(t, resp.Data, 2) +} diff --git a/pkg/handler/repositories.go b/pkg/handler/repositories.go index 22a6b00a2..a89b8985f 100644 --- a/pkg/handler/repositories.go +++ b/pkg/handler/repositories.go @@ -12,6 +12,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" ce "github.com/content-services/content-sources-backend/pkg/errors" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/content-services/content-sources-backend/pkg/rbac" "github.com/content-services/content-sources-backend/pkg/tasks" "github.com/content-services/content-sources-backend/pkg/tasks/client" @@ -33,10 +34,12 @@ type RepositoryHandler struct { DaoRegistry dao.DaoRegistry TaskClient client.TaskClient FeatureServiceClient feature_service_client.FeatureServiceClient + LightwellStore store.Querier // nil when lightwell store is unavailable } func RegisterRepositoryRoutes(engine *echo.Group, daoReg *dao.DaoRegistry, taskClient *client.TaskClient, fsClient *feature_service_client.FeatureServiceClient, + lightwellStore ...store.Querier, ) { if engine == nil { panic("engine is nil") @@ -55,6 +58,9 @@ func RegisterRepositoryRoutes(engine *echo.Group, daoReg *dao.DaoRegistry, TaskClient: *taskClient, FeatureServiceClient: *fsClient, } + if len(lightwellStore) > 0 && lightwellStore[0] != nil { + rh.LightwellStore = lightwellStore[0] + } addRepoRoute(engine, http.MethodGet, "/repositories/", rh.listRepositories, rbac.RbacVerbRead) addRepoRoute(engine, http.MethodGet, "/repositories/:uuid", rh.fetch, rbac.RbacVerbRead) @@ -122,9 +128,43 @@ func (rh *RepositoryHandler) listRepositories(c echo.Context) error { return ce.NewErrorResponse(ce.HttpCodeForDaoError(err), "Error listing repositories", err.Error()) } + rh.enrichLightwellRepoCounts(c, &repos) + return c.JSON(200, setCollectionResponseMetadata(&repos, c, totalRepos)) } +// enrichLightwellRepoCounts populates packages_count, versions_count, and +// remediations_count on Lightwell-origin repositories. These spec-required +// fields are omitted for non-Lightwell repos to avoid breaking existing consumers. +func (rh *RepositoryHandler) enrichLightwellRepoCounts(c echo.Context, repos *api.RepositoryCollectionResponse) { + for i := range repos.Data { + repo := &repos.Data[i] + if repo.Origin != config.OriginLightwell { + continue + } + pkgCount := repo.PackageCount + verCount := repo.VersionCount + repo.PackagesCount = &pkgCount + repo.VersionsCount = &verCount + + if rh.LightwellStore == nil { + continue + } + repoUUID, err := uuid.Parse(repo.UUID) + if err != nil { + log.Ctx(c.Request().Context()).Warn().Err(err).Str("uuid", repo.UUID).Msg("invalid UUID for advisory count") + continue + } + count, err := rh.LightwellStore.CountAdvisoriesByRepo(c.Request().Context(), repoUUID) + if err != nil { + log.Ctx(c.Request().Context()).Warn().Err(err).Str("uuid", repo.UUID).Msg("failed to count advisories") + continue + } + remCount := int(count) + repo.RemediationsCount = &remCount + } +} + // CreateRepository godoc // @Summary Create Repository // @ID createRepository From d6351b941c67318b47440115f97c3890771e6ad6 Mon Sep 17 00:00:00 2001 From: etsien Date: Tue, 25 Aug 2026 11:29:05 -0400 Subject: [PATCH 17/47] LWLP-5: regenerate OpenAPI spec for Lightwell endpoints --- api/docs.go | 402 +++++++++++++++++++++++++++++++++++++++++ api/openapi.json | 458 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 860 insertions(+) diff --git a/api/docs.go b/api/docs.go index b76f7581c..8070aa9f7 100644 --- a/api/docs.go +++ b/api/docs.go @@ -282,6 +282,80 @@ const docTemplate = `{ } } }, + "/lightwell/advisories": { + "get": { + "description": "List security advisories for Lightwell remediated packages with optional filtering.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Advisories", + "operationId": "listLightwellAdvisories", + "parameters": [ + { + "type": "string", + "description": "Filter by repository UUID", + "name": "repository_uuid", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "package_name", + "in": "query" + }, + { + "type": "string", + "description": "Minimum severity level (low, moderate, important, critical)", + "name": "severity_min", + "in": "query" + }, + { + "type": "string", + "description": "Filter by CVE ID (exact match)", + "name": "cve_id", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellAdvisoryCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, "/lightwell/beacon/vulnerabilities/": { "get": { "description": "List Lightwell vulnerabilities for a customer, with filters, pagination, and aggregate counts.", @@ -474,6 +548,160 @@ const docTemplate = `{ } } }, + "/lightwell/package_versions": { + "get": { + "description": "List individual package versions aggregated across all Lightwell repositories, with optional CVE-based filtering.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Package Versions (cross-repo)", + "operationId": "listLightwellPackageVersions", + "parameters": [ + { + "type": "string", + "description": "Filter by content type (maven, python, npm)", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by security level (validated, remediated)", + "name": "security_level", + "in": "query" + }, + { + "type": "string", + "description": "Filter by repository name", + "name": "repository", + "in": "query" + }, + { + "type": "string", + "description": "Show only packages that resolve this CVE", + "name": "resolves_cve_id", + "in": "query" + }, + { + "type": "string", + "description": "Show only packages vulnerable to this CVE", + "name": "vulnerable_to_cve_id", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellPackageVersionCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, + "/lightwell/packages": { + "get": { + "description": "List packages aggregated across all Lightwell repositories, with optional filtering by content type, name, and security level.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Packages (cross-repo)", + "operationId": "listLightwellPackages", + "parameters": [ + { + "type": "string", + "description": "Filter by content type (maven, python, npm)", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by security level (validated, remediated)", + "name": "security_level", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellPackageCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, "/module_streams/search": { "post": { "description": "List modules and their streams for repositories", @@ -5097,6 +5325,55 @@ const docTemplate = `{ } } }, + "api.LightwellAdvisoryCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellAdvisoryResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellAdvisoryResponse": { + "type": "object", + "properties": { + "advisory_id": { + "type": "string" + }, + "details": { + "type": "string" + }, + "fixed_versions": { + "type": "array", + "items": { + "type": "string" + } + }, + "package_name": { + "type": "string" + }, + "reference_urls": { + "type": "array", + "items": { + "type": "string" + } + }, + "repository": { + "type": "string" + }, + "severity": { + "type": "string" + } + } + }, "api.LightwellCustomerIdsResponse": { "type": "object", "properties": { @@ -5121,6 +5398,101 @@ const docTemplate = `{ } } }, + "api.LightwellPackageCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellPackageResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellPackageResponse": { + "type": "object", + "properties": { + "content_type": { + "type": "string" + }, + "group": { + "type": "string" + }, + "latest_releases": { + "type": "array", + "items": { + "$ref": "#/definitions/api.ReleaseInfo" + } + }, + "name": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "versions": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "api.LightwellPackageVersionCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellPackageVersionResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellPackageVersionResponse": { + "type": "object", + "properties": { + "content_type": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "group": { + "type": "string" + }, + "name": { + "type": "string" + }, + "release": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, "api.LightwellVulnerabilityCollectionMeta": { "type": "object", "properties": { @@ -6065,6 +6437,11 @@ const docTemplate = `{ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "type": "integer", + "readOnly": true + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "type": "boolean", @@ -6075,6 +6452,11 @@ const docTemplate = `{ "type": "string", "readOnly": true }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "type": "integer", + "readOnly": true + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "type": "string", @@ -6101,6 +6483,11 @@ const docTemplate = `{ "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "type": "integer", + "readOnly": true + }, "warnings": { "description": "Warnings to alert user of mismatched fields if there is an existing repo with the same URL", "type": "array", @@ -6395,6 +6782,11 @@ const docTemplate = `{ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "type": "integer", + "readOnly": true + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "type": "boolean", @@ -6405,6 +6797,11 @@ const docTemplate = `{ "type": "string", "readOnly": true }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "type": "integer", + "readOnly": true + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "type": "string", @@ -6430,6 +6827,11 @@ const docTemplate = `{ "version_count": { "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" + }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "type": "integer", + "readOnly": true } } }, diff --git a/api/openapi.json b/api/openapi.json index 9c53230b0..9ae7c6ea6 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -325,6 +325,55 @@ }, "type": "object" }, + "api.LightwellAdvisoryCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellAdvisoryResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellAdvisoryResponse": { + "properties": { + "advisory_id": { + "type": "string" + }, + "details": { + "type": "string" + }, + "fixed_versions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "package_name": { + "type": "string" + }, + "reference_urls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repository": { + "type": "string" + }, + "severity": { + "type": "string" + } + }, + "type": "object" + }, "api.LightwellCustomerIdsResponse": { "properties": { "data": { @@ -349,6 +398,101 @@ }, "type": "object" }, + "api.LightwellPackageCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellPackageResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellPackageResponse": { + "properties": { + "content_type": { + "type": "string" + }, + "group": { + "type": "string" + }, + "latest_releases": { + "items": { + "$ref": "#/components/schemas/api.ReleaseInfo" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "versions": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "api.LightwellPackageVersionCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellPackageVersionResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellPackageVersionResponse": { + "properties": { + "content_type": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "group": { + "type": "string" + }, + "name": { + "type": "string" + }, + "release": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, "api.LightwellVulnerabilityCollectionMeta": { "properties": { "blocked_count": { @@ -1292,6 +1436,11 @@ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "readOnly": true, + "type": "integer" + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "readOnly": true, @@ -1302,6 +1451,11 @@ "readOnly": true, "type": "string" }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "readOnly": true, + "type": "integer" + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "readOnly": true, @@ -1328,6 +1482,11 @@ "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "readOnly": true, + "type": "integer" + }, "warnings": { "description": "Warnings to alert user of mismatched fields if there is an existing repo with the same URL", "items": { @@ -1622,6 +1781,11 @@ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "readOnly": true, + "type": "integer" + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "readOnly": true, @@ -1632,6 +1796,11 @@ "readOnly": true, "type": "string" }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "readOnly": true, + "type": "integer" + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "readOnly": true, @@ -1657,6 +1826,11 @@ "version_count": { "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" + }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "readOnly": true, + "type": "integer" } }, "type": "object" @@ -3174,6 +3348,98 @@ ] } }, + "/lightwell/advisories": { + "get": { + "description": "List security advisories for Lightwell remediated packages with optional filtering.", + "operationId": "listLightwellAdvisories", + "parameters": [ + { + "description": "Filter by repository UUID", + "in": "query", + "name": "repository_uuid", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "package_name", + "schema": { + "type": "string" + } + }, + { + "description": "Minimum severity level (low, moderate, important, critical)", + "in": "query", + "name": "severity_min", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by CVE ID (exact match)", + "in": "query", + "name": "cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellAdvisoryCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Advisories", + "tags": [ + "lightwell" + ] + } + }, "/lightwell/beacon/vulnerabilities/": { "get": { "description": "List Lightwell vulnerabilities for a customer, with filters, pagination, and aggregate counts.", @@ -3416,6 +3682,198 @@ ] } }, + "/lightwell/package_versions": { + "get": { + "description": "List individual package versions aggregated across all Lightwell repositories, with optional CVE-based filtering.", + "operationId": "listLightwellPackageVersions", + "parameters": [ + { + "description": "Filter by content type (maven, python, npm)", + "in": "query", + "name": "type", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "name", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by security level (validated, remediated)", + "in": "query", + "name": "security_level", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by repository name", + "in": "query", + "name": "repository", + "schema": { + "type": "string" + } + }, + { + "description": "Show only packages that resolve this CVE", + "in": "query", + "name": "resolves_cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Show only packages vulnerable to this CVE", + "in": "query", + "name": "vulnerable_to_cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellPackageVersionCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Package Versions (cross-repo)", + "tags": [ + "lightwell" + ] + } + }, + "/lightwell/packages": { + "get": { + "description": "List packages aggregated across all Lightwell repositories, with optional filtering by content type, name, and security level.", + "operationId": "listLightwellPackages", + "parameters": [ + { + "description": "Filter by content type (maven, python, npm)", + "in": "query", + "name": "type", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "name", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by security level (validated, remediated)", + "in": "query", + "name": "security_level", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellPackageCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Packages (cross-repo)", + "tags": [ + "lightwell" + ] + } + }, "/module_streams/search": { "post": { "description": "List modules and their streams for repositories", From 0b5baac6f881747eb2acd3f854d3d2d9723fc871 Mon Sep 17 00:00:00 2001 From: etsien Date: Tue, 25 Aug 2026 14:56:25 -0400 Subject: [PATCH 18/47] LWLP-5: add Lightwell advisory schema, sqlc queries, and store tests Add severity_order column to advisories table and duplicate_of to vulnerabilities. Rename the duplicate migration to avoid timestamp collision. Add sqlc queries for listing/counting advisories with filtering and pagination. Extend store_test.go with advisory query coverage. --- .gitignore | 3 + .mockery_v3.yml | 3 - db/migrations.latest | 2 +- ...twell_vulnerability_duplicate_of.down.sql} | 0 ...ghtwell_vulnerability_duplicate_of.up.sql} | 0 ...lightwell_advisory_severity_order.down.sql | 7 + ...d_lightwell_advisory_severity_order.up.sql | 20 ++ pkg/lightwell/db/queries/advisories.sql | 64 +++++ pkg/lightwell/db/schema.sql | 32 ++- pkg/lightwell/db/store/advisories.sql.go | 224 ++++++++++++++++++ pkg/lightwell/db/store/models.go | 21 ++ pkg/lightwell/db/store/querier.go | 6 + pkg/lightwell/db/store/store_test.go | 195 +++++++++++---- 13 files changed, 532 insertions(+), 45 deletions(-) rename db/migrations/{20260818120000_add_lightwell_vulnerability_duplicate_of.down.sql => 20260818130000_add_lightwell_vulnerability_duplicate_of.down.sql} (100%) rename db/migrations/{20260818120000_add_lightwell_vulnerability_duplicate_of.up.sql => 20260818130000_add_lightwell_vulnerability_duplicate_of.up.sql} (100%) create mode 100644 db/migrations/20260825110000_add_lightwell_advisory_severity_order.down.sql create mode 100644 db/migrations/20260825110000_add_lightwell_advisory_severity_order.up.sql create mode 100644 pkg/lightwell/db/queries/advisories.sql create mode 100644 pkg/lightwell/db/store/advisories.sql.go diff --git a/.gitignore b/.gitignore index 40697b284..01911f3c0 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ content-sources-frontend # local dev certs for testing pulp cert auth compose_files/pulp/assets/certs/dev_certs +/pkg/jfrog_bridge/testdata +pkg/jfrog_bridge/lightwell-catalog.key +.env.catalog diff --git a/.mockery_v3.yml b/.mockery_v3.yml index 7543d5d87..005e0ea3c 100644 --- a/.mockery_v3.yml +++ b/.mockery_v3.yml @@ -23,9 +23,6 @@ packages: github.com/content-services/content-sources-backend/pkg/clients/roadmap_client: interfaces: RoadmapClient: {} - github.com/content-services/content-sources-backend/pkg/clients/s3_client: - interfaces: - S3Client: {} github.com/content-services/content-sources-backend/pkg/dao: interfaces: AdminTaskDao: {} diff --git a/db/migrations.latest b/db/migrations.latest index 34529ff58..9331a9b88 100644 --- a/db/migrations.latest +++ b/db/migrations.latest @@ -1 +1 @@ -20260819141200 \ No newline at end of file +20260825110000 diff --git a/db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.down.sql b/db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.down.sql similarity index 100% rename from db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.down.sql rename to db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.down.sql diff --git a/db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.up.sql b/db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.up.sql similarity index 100% rename from db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.up.sql rename to db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.up.sql diff --git a/db/migrations/20260825110000_add_lightwell_advisory_severity_order.down.sql b/db/migrations/20260825110000_add_lightwell_advisory_severity_order.down.sql new file mode 100644 index 000000000..53b3f3983 --- /dev/null +++ b/db/migrations/20260825110000_add_lightwell_advisory_severity_order.down.sql @@ -0,0 +1,7 @@ +BEGIN; + +DROP INDEX IF EXISTS idx_lightwell_advisories_package_name; +DROP INDEX IF EXISTS idx_lightwell_advisories_severity_order; +ALTER TABLE lightwell_advisories DROP COLUMN IF EXISTS severity_order; + +COMMIT; diff --git a/db/migrations/20260825110000_add_lightwell_advisory_severity_order.up.sql b/db/migrations/20260825110000_add_lightwell_advisory_severity_order.up.sql new file mode 100644 index 000000000..544d380e0 --- /dev/null +++ b/db/migrations/20260825110000_add_lightwell_advisory_severity_order.up.sql @@ -0,0 +1,20 @@ +BEGIN; + +ALTER TABLE lightwell_advisories + ADD COLUMN IF NOT EXISTS severity_order SMALLINT NOT NULL DEFAULT 0; + +UPDATE lightwell_advisories SET severity_order = CASE + WHEN severity = 'critical' THEN 4 + WHEN severity = 'important' THEN 3 + WHEN severity = 'moderate' THEN 2 + WHEN severity = 'low' THEN 1 + ELSE 0 +END; + +CREATE INDEX IF NOT EXISTS idx_lightwell_advisories_severity_order + ON lightwell_advisories (severity_order); + +CREATE INDEX IF NOT EXISTS idx_lightwell_advisories_package_name + ON lightwell_advisories (package_name); + +COMMIT; diff --git a/pkg/lightwell/db/queries/advisories.sql b/pkg/lightwell/db/queries/advisories.sql new file mode 100644 index 000000000..4abf9359c --- /dev/null +++ b/pkg/lightwell/db/queries/advisories.sql @@ -0,0 +1,64 @@ +-- name: ListAdvisories :many +SELECT + la.uuid, + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.reference_urls, + la.package_name, + la.fixed_versions, + la.repo_name, + la.repository_configuration_uuid, + la.created_at, + COUNT(*) OVER() AS total_count +FROM lightwell_advisories la +WHERE 1=1 + AND ( + sqlc.narg(repository_config_uuid)::uuid IS NULL + OR la.repository_configuration_uuid = sqlc.narg(repository_config_uuid)::uuid + ) + AND ( + sqlc.narg(repo_name)::text IS NULL + OR la.repo_name = sqlc.narg(repo_name)::text + ) + AND ( + sqlc.narg(package_name)::text IS NULL + OR la.package_name ILIKE '%' || sqlc.narg(package_name)::text || '%' + ) + AND ( + sqlc.narg(severity_min)::smallint IS NULL + OR la.severity_order >= sqlc.narg(severity_min)::smallint + ) + AND ( + sqlc.narg(cve_id)::text IS NULL + OR la.advisory_id = sqlc.narg(cve_id)::text + ) +ORDER BY la.severity_order DESC, la.created_at DESC +LIMIT sqlc.arg(page_limit) OFFSET sqlc.arg(page_offset); + +-- name: CountAdvisoriesByRepo :one +SELECT COUNT(*)::bigint AS total +FROM lightwell_advisories la +WHERE la.repository_configuration_uuid = sqlc.arg(repository_config_uuid)::uuid; + +-- name: ListAdvisoriesByPackage :many +SELECT + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.fixed_versions, + la.repo_name +FROM lightwell_advisories la +WHERE la.package_name = sqlc.arg(package_name)::text +ORDER BY la.severity_order DESC, la.created_at DESC; + +-- name: ListAdvisoriesByCveID :many +SELECT + la.package_name, + la.fixed_versions, + la.repo_name, + la.severity +FROM lightwell_advisories la +WHERE la.advisory_id = sqlc.arg(cve_id)::text; diff --git a/pkg/lightwell/db/schema.sql b/pkg/lightwell/db/schema.sql index 142c6ffb5..a876231d3 100644 --- a/pkg/lightwell/db/schema.sql +++ b/pkg/lightwell/db/schema.sql @@ -1,4 +1,34 @@ --- sqlc schema snapshot: current lightwell vulnerabilities tables and filter function (see db/migrations) +-- sqlc schema snapshot: current lightwell vulnerabilities tables (see db/migrations) +-- NOTE: This file must be kept in sync with the actual migrations. +-- sqlc uses this for code generation; it is not executed directly. + +CREATE TABLE repository_configurations ( + uuid UUID PRIMARY KEY +); + +CREATE TABLE lightwell_advisories ( + uuid UUID UNIQUE NOT NULL PRIMARY KEY, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + repo_name VARCHAR(255) NOT NULL, + advisory_id VARCHAR(255) NOT NULL, + severity VARCHAR(255) NOT NULL DEFAULT '', + severity_order SMALLINT NOT NULL DEFAULT 0, + details TEXT NOT NULL DEFAULT '', + reference_urls TEXT[], + package_name VARCHAR(255) NOT NULL DEFAULT '', + fixed_version VARCHAR(255) NOT NULL DEFAULT '', + fixed_versions TEXT[] NOT NULL DEFAULT '{}', + repository_configuration_uuid UUID NOT NULL REFERENCES repository_configurations(uuid) ON DELETE CASCADE, + checksum VARCHAR(255) NOT NULL +); + +CREATE UNIQUE INDEX idx_lightwell_advisories_repo_config_advisory + ON lightwell_advisories (repository_configuration_uuid, advisory_id, package_name); +CREATE INDEX idx_lightwell_advisories_severity_order + ON lightwell_advisories (severity_order); +CREATE INDEX idx_lightwell_advisories_package_name + ON lightwell_advisories (package_name); CREATE TABLE lightwell_vulnerabilities ( uuid UUID PRIMARY KEY, diff --git a/pkg/lightwell/db/store/advisories.sql.go b/pkg/lightwell/db/store/advisories.sql.go new file mode 100644 index 000000000..500efcd7e --- /dev/null +++ b/pkg/lightwell/db/store/advisories.sql.go @@ -0,0 +1,224 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: advisories.sql + +package store + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" +) + +const countAdvisoriesByRepo = `-- name: CountAdvisoriesByRepo :one +SELECT COUNT(*)::bigint AS total +FROM lightwell_advisories la +WHERE la.repository_configuration_uuid = $1::uuid +` + +func (q *Queries) CountAdvisoriesByRepo(ctx context.Context, repositoryConfigUuid uuid.UUID) (int64, error) { + row := q.db.QueryRow(ctx, countAdvisoriesByRepo, repositoryConfigUuid) + var total int64 + err := row.Scan(&total) + return total, err +} + +const listAdvisories = `-- name: ListAdvisories :many +SELECT + la.uuid, + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.reference_urls, + la.package_name, + la.fixed_versions, + la.repo_name, + la.repository_configuration_uuid, + la.created_at, + COUNT(*) OVER() AS total_count +FROM lightwell_advisories la +WHERE 1=1 + AND ( + $1::uuid IS NULL + OR la.repository_configuration_uuid = $1::uuid + ) + AND ( + $2::text IS NULL + OR la.repo_name = $2::text + ) + AND ( + $3::text IS NULL + OR la.package_name ILIKE '%' || $3::text || '%' + ) + AND ( + $4::smallint IS NULL + OR la.severity_order >= $4::smallint + ) + AND ( + $5::text IS NULL + OR la.advisory_id = $5::text + ) +ORDER BY la.severity_order DESC, la.created_at DESC +LIMIT $7 OFFSET $6 +` + +type ListAdvisoriesParams struct { + RepositoryConfigUuid pgtype.UUID `json:"repository_config_uuid"` + RepoName *string `json:"repo_name"` + PackageName *string `json:"package_name"` + SeverityMin pgtype.Int2 `json:"severity_min"` + CveID *string `json:"cve_id"` + PageOffset int32 `json:"page_offset"` + PageLimit int32 `json:"page_limit"` +} + +type ListAdvisoriesRow struct { + Uuid uuid.UUID `json:"uuid"` + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + SeverityOrder int16 `json:"severity_order"` + Details string `json:"details"` + ReferenceUrls []string `json:"reference_urls"` + PackageName string `json:"package_name"` + FixedVersions []string `json:"fixed_versions"` + RepoName string `json:"repo_name"` + RepositoryConfigurationUuid uuid.UUID `json:"repository_configuration_uuid"` + CreatedAt time.Time `json:"created_at"` + TotalCount int64 `json:"total_count"` +} + +func (q *Queries) ListAdvisories(ctx context.Context, arg ListAdvisoriesParams) ([]ListAdvisoriesRow, error) { + rows, err := q.db.Query(ctx, listAdvisories, + arg.RepositoryConfigUuid, + arg.RepoName, + arg.PackageName, + arg.SeverityMin, + arg.CveID, + arg.PageOffset, + arg.PageLimit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListAdvisoriesRow{} + for rows.Next() { + var i ListAdvisoriesRow + if err := rows.Scan( + &i.Uuid, + &i.AdvisoryID, + &i.Severity, + &i.SeverityOrder, + &i.Details, + &i.ReferenceUrls, + &i.PackageName, + &i.FixedVersions, + &i.RepoName, + &i.RepositoryConfigurationUuid, + &i.CreatedAt, + &i.TotalCount, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAdvisoriesByCveID = `-- name: ListAdvisoriesByCveID :many +SELECT + la.package_name, + la.fixed_versions, + la.repo_name, + la.severity +FROM lightwell_advisories la +WHERE la.advisory_id = $1::text +` + +type ListAdvisoriesByCveIDRow struct { + PackageName string `json:"package_name"` + FixedVersions []string `json:"fixed_versions"` + RepoName string `json:"repo_name"` + Severity string `json:"severity"` +} + +func (q *Queries) ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]ListAdvisoriesByCveIDRow, error) { + rows, err := q.db.Query(ctx, listAdvisoriesByCveID, cveID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListAdvisoriesByCveIDRow{} + for rows.Next() { + var i ListAdvisoriesByCveIDRow + if err := rows.Scan( + &i.PackageName, + &i.FixedVersions, + &i.RepoName, + &i.Severity, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAdvisoriesByPackage = `-- name: ListAdvisoriesByPackage :many +SELECT + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.fixed_versions, + la.repo_name +FROM lightwell_advisories la +WHERE la.package_name = $1::text +ORDER BY la.severity_order DESC, la.created_at DESC +` + +type ListAdvisoriesByPackageRow struct { + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + SeverityOrder int16 `json:"severity_order"` + Details string `json:"details"` + FixedVersions []string `json:"fixed_versions"` + RepoName string `json:"repo_name"` +} + +func (q *Queries) ListAdvisoriesByPackage(ctx context.Context, packageName string) ([]ListAdvisoriesByPackageRow, error) { + rows, err := q.db.Query(ctx, listAdvisoriesByPackage, packageName) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListAdvisoriesByPackageRow{} + for rows.Next() { + var i ListAdvisoriesByPackageRow + if err := rows.Scan( + &i.AdvisoryID, + &i.Severity, + &i.SeverityOrder, + &i.Details, + &i.FixedVersions, + &i.RepoName, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/pkg/lightwell/db/store/models.go b/pkg/lightwell/db/store/models.go index da5842610..be2f7afd3 100644 --- a/pkg/lightwell/db/store/models.go +++ b/pkg/lightwell/db/store/models.go @@ -10,6 +10,23 @@ import ( "github.com/google/uuid" ) +type LightwellAdvisory struct { + Uuid uuid.UUID `json:"uuid"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + RepoName string `json:"repo_name"` + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + SeverityOrder int16 `json:"severity_order"` + Details string `json:"details"` + ReferenceUrls []string `json:"reference_urls"` + PackageName string `json:"package_name"` + FixedVersion string `json:"fixed_version"` + FixedVersions []string `json:"fixed_versions"` + RepositoryConfigurationUuid uuid.UUID `json:"repository_configuration_uuid"` + Checksum string `json:"checksum"` +} + type LightwellVulnerability struct { Uuid uuid.UUID `json:"uuid"` VulnerabilityID string `json:"vulnerability_id"` @@ -49,3 +66,7 @@ type LightwellVulnerabilitySupportTicket struct { TicketID string `json:"ticket_id"` CreatedAt time.Time `json:"created_at"` } + +type RepositoryConfiguration struct { + Uuid uuid.UUID `json:"uuid"` +} diff --git a/pkg/lightwell/db/store/querier.go b/pkg/lightwell/db/store/querier.go index bd1927ae9..659962d35 100644 --- a/pkg/lightwell/db/store/querier.go +++ b/pkg/lightwell/db/store/querier.go @@ -6,11 +6,17 @@ package store import ( "context" + + "github.com/google/uuid" ) type Querier interface { + CountAdvisoriesByRepo(ctx context.Context, repositoryConfigUuid uuid.UUID) (int64, error) CountAggregates(ctx context.Context, arg CountAggregatesParams) (CountAggregatesRow, error) CountByStage(ctx context.Context, arg CountByStageParams) ([]CountByStageRow, error) + ListAdvisories(ctx context.Context, arg ListAdvisoriesParams) ([]ListAdvisoriesRow, error) + ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]ListAdvisoriesByCveIDRow, error) + ListAdvisoriesByPackage(ctx context.Context, packageName string) ([]ListAdvisoriesByPackageRow, error) ListCustomerIds(ctx context.Context) ([]string, error) ListLtwlsuptTicketIds(ctx context.Context, customerID string) ([]string, error) ListVulnerabilities(ctx context.Context, arg ListVulnerabilitiesParams) ([]ListVulnerabilitiesRow, error) diff --git a/pkg/lightwell/db/store/store_test.go b/pkg/lightwell/db/store/store_test.go index 317490658..11237162b 100644 --- a/pkg/lightwell/db/store/store_test.go +++ b/pkg/lightwell/db/store/store_test.go @@ -11,6 +11,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/google/uuid" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgxpool" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -758,54 +759,168 @@ func TestStore_ListCustomerIds(t *testing.T) { assert.Contains(t, ids, customerB) } -func TestStore_ListLtwlsuptTicketIds(t *testing.T) { +// --- Advisory query integration tests --- + +func insertTestAdvisories(t *testing.T, ctx context.Context, tx pgx.Tx) uuid.UUID { + repoConfigUUID := uuid.New() + repoUUID := uuid.New() + now := time.Now() + + _, err := tx.Exec(ctx, + `INSERT INTO repositories (uuid, url) VALUES ($1, $2)`, + repoUUID, "https://test.example.com/repo/"+repoConfigUUID.String()) + require.NoError(t, err) + + _, err = tx.Exec(ctx, + `INSERT INTO repository_configurations (uuid, created_at, updated_at, name, arch, org_id, repository_uuid) + VALUES ($1, $2, $3, $4, $5, $6, $7)`, + repoConfigUUID, now, now, "test-advisory-repo", "x86_64", "test-org-"+repoConfigUUID.String(), repoUUID) + require.NoError(t, err) + + advisories := []struct { + id string + severity string + severityOrder int + packageName string + fixedVersions []string + repoName string + }{ + {"CVE-2024-1001", "critical", 4, "spring-core", []string{"5.3.18.rhlw-00003"}, "lightwell/java/remediated"}, + {"CVE-2024-1002", "important", 3, "jackson-databind", []string{"2.15.3.rhlw-00001"}, "lightwell/java/remediated"}, + {"CVE-2024-1003", "moderate", 2, "requests", []string{"2.31.0.rhlw-00001"}, "lightwell/python/remediated"}, + {"CVE-2024-1001", "critical", 4, "jackson-databind", []string{"2.14.2.rhlw-00001", "2.15.3.rhlw-00001"}, "lightwell/java/remediated"}, + } + + for _, adv := range advisories { + _, err := tx.Exec(ctx, ` + INSERT INTO lightwell_advisories ( + uuid, advisory_id, severity, severity_order, details, + reference_urls, package_name, fixed_versions, + repo_name, repository_configuration_uuid, checksum + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, + uuid.New(), adv.id, adv.severity, adv.severityOrder, + "test advisory details for "+adv.packageName, + []string{"https://access.redhat.com/security/cve/" + adv.id}, + adv.packageName, adv.fixedVersions, + adv.repoName, repoConfigUUID, fmt.Sprintf("checksum-%s-%s", adv.id, adv.packageName), + ) + require.NoError(t, err) + } + return repoConfigUUID +} + +func TestStore_ListAdvisories(t *testing.T) { ctx, tx, q := beginTestTx(t) defer rollbackTestTx(t, tx) - customerA := fmt.Sprintf("lw-tickets-a-%d", time.Now().UnixNano()) - customerB := fmt.Sprintf("lw-tickets-b-%d", time.Now().UnixNano()) - insertTestVulnerabilities(t, ctx, tx, []testVulnSpec{ - { - vulnID: "LWL-TICKETS-1", - severity: "Moderate", - stage: "Submitted", - language: "java", - complexity: "Standard", - ticketIDs: []string{"ticket-c", "ticket-a"}, - daysAgo: 1, - customerIDs: []string{customerA}, - }, - { - vulnID: "LWL-TICKETS-2", - severity: "Low", - stage: "Submitted", - language: "java", - complexity: "Standard", - ticketID: "ticket-a", - daysAgo: 1, - customerIDs: []string{customerA}, - }, - { - vulnID: "LWL-TICKETS-3", - severity: "Low", - stage: "Submitted", - language: "python", - complexity: "Standard", - ticketID: "ticket-b", - daysAgo: 1, - customerIDs: []string{customerB}, - }, + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + PageLimit: 100, + PageOffset: 0, }) + require.NoError(t, err) + assert.Len(t, rows, 4) + assert.Equal(t, int64(4), rows[0].TotalCount) + // Ordered by severity_order DESC + assert.Equal(t, int16(4), rows[0].SeverityOrder) +} - ids, err := q.ListLtwlsuptTicketIds(ctx, customerA) +func TestStore_ListAdvisoriesFilterByPackageName(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + name := "jackson" + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + PackageName: &name, + PageLimit: 100, + PageOffset: 0, + }) require.NoError(t, err) - assert.Equal(t, []string{"ticket-a", "ticket-c"}, ids) + assert.Len(t, rows, 2) + for _, r := range rows { + assert.Contains(t, r.PackageName, "jackson") + } +} - ids, err = q.ListLtwlsuptTicketIds(ctx, customerB) +func TestStore_ListAdvisoriesFilterBySeverityMin(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + SeverityMin: pgtype.Int2{Int16: 3, Valid: true}, + PageLimit: 100, + PageOffset: 0, + }) require.NoError(t, err) - assert.Equal(t, []string{"ticket-b"}, ids) + assert.Len(t, rows, 3) + for _, r := range rows { + assert.GreaterOrEqual(t, r.SeverityOrder, int16(3)) + } +} + +func TestStore_ListAdvisoriesFilterByRepoName(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) - ids, err = q.ListLtwlsuptTicketIds(ctx, "no-such-customer") + repoName := "lightwell/python/remediated" + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + RepoName: &repoName, + PageLimit: 100, + PageOffset: 0, + }) require.NoError(t, err) - assert.Empty(t, ids) + assert.Len(t, rows, 1) + assert.Equal(t, "requests", rows[0].PackageName) +} + +func TestStore_CountAdvisoriesByRepo(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + repoUUID := insertTestAdvisories(t, ctx, tx) + + count, err := q.CountAdvisoriesByRepo(ctx, repoUUID) + require.NoError(t, err) + assert.Equal(t, int64(4), count) +} + +func TestStore_ListAdvisoriesByCveID(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisoriesByCveID(ctx, "CVE-2024-1001") + require.NoError(t, err) + assert.Len(t, rows, 2) + + packageNames := map[string]bool{} + for _, r := range rows { + packageNames[r.PackageName] = true + assert.Equal(t, "critical", r.Severity) + } + assert.True(t, packageNames["spring-core"]) + assert.True(t, packageNames["jackson-databind"]) +} + +func TestStore_ListAdvisoriesByPackage(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisoriesByPackage(ctx, "jackson-databind") + require.NoError(t, err) + assert.Len(t, rows, 2) + for _, r := range rows { + assert.NotEmpty(t, r.AdvisoryID) + assert.NotEmpty(t, r.FixedVersions) + } } From 5e7a9219e9429e42a78aab4f340ec037b614ff4e Mon Sep 17 00:00:00 2001 From: etsien Date: Tue, 25 Aug 2026 14:56:34 -0400 Subject: [PATCH 19/47] LWLP-5: add Lightwell advisories and packages API Add REST handlers for /lightwell/advisories, /lightwell/packages, and /lightwell/package_versions with filtering, pagination, and aggregate counts. Include cross-repo package listing with CVE-based filtering. Add packages_count, versions_count, and remediations_count to the repository response. Full handler test coverage for both endpoints. --- pkg/api/lightwell_advisories.go | 29 + pkg/api/lightwell_packages.go | 66 ++ pkg/api/repositories.go | 3 + pkg/handler/api.go | 33 +- pkg/handler/coverage_reports.go | 16 +- pkg/handler/coverage_reports_test.go | 15 +- pkg/handler/lightwell_advisories.go | 154 +++++ pkg/handler/lightwell_advisories_test.go | 286 +++++++++ pkg/handler/lightwell_packages.go | 728 +++++++++++++++++++++++ pkg/handler/lightwell_packages_test.go | 538 +++++++++++++++++ pkg/handler/repositories.go | 40 ++ pkg/handler/user_preferences.go | 5 +- 12 files changed, 1871 insertions(+), 42 deletions(-) create mode 100644 pkg/api/lightwell_advisories.go create mode 100644 pkg/api/lightwell_packages.go create mode 100644 pkg/handler/lightwell_advisories.go create mode 100644 pkg/handler/lightwell_advisories_test.go create mode 100644 pkg/handler/lightwell_packages.go create mode 100644 pkg/handler/lightwell_packages_test.go diff --git a/pkg/api/lightwell_advisories.go b/pkg/api/lightwell_advisories.go new file mode 100644 index 000000000..9eb7260a5 --- /dev/null +++ b/pkg/api/lightwell_advisories.go @@ -0,0 +1,29 @@ +package api + +type LightwellAdvisoryResponse struct { + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + Details string `json:"details"` + ReferenceURLs []string `json:"reference_urls"` + PackageName string `json:"package_name"` + FixedVersions []string `json:"fixed_versions"` + Repository string `json:"repository"` +} + +type LightwellAdvisoryCollectionResponse struct { + Data []LightwellAdvisoryResponse `json:"data"` + Meta ResponseMetadata `json:"meta"` + Links Links `json:"links"` +} + +func (r *LightwellAdvisoryCollectionResponse) SetMetadata(meta ResponseMetadata, links Links) { + r.Meta = meta + r.Links = links +} + +type LightwellAdvisoryFilterData struct { + Repository string `query:"repository"` + PackageName string `query:"package_name"` + SeverityMin string `query:"severity_min"` + CveID string `query:"cve_id"` +} diff --git a/pkg/api/lightwell_packages.go b/pkg/api/lightwell_packages.go new file mode 100644 index 000000000..aadaa286c --- /dev/null +++ b/pkg/api/lightwell_packages.go @@ -0,0 +1,66 @@ +package api + +// LightwellPackageResponse represents a package found across Lightwell repositories. +type LightwellPackageResponse struct { + Name string `json:"name"` + Group string `json:"group,omitempty"` + ContentType string `json:"content_type"` + Repository string `json:"repository"` + RepositoryUUID string `json:"repository_uuid"` + Versions []string `json:"versions"` + LatestReleases []ReleaseInfo `json:"latest_releases"` +} + +// LightwellPackageCollectionResponse is a paginated collection of cross-repo packages. +type LightwellPackageCollectionResponse struct { + Data []LightwellPackageResponse `json:"data"` + Meta ResponseMetadata `json:"meta"` + Links Links `json:"links"` +} + +func (r *LightwellPackageCollectionResponse) SetMetadata(meta ResponseMetadata, links Links) { + r.Meta = meta + r.Links = links +} + +// LightwellPackageVersionResponse represents a single package version across Lightwell repositories. +type LightwellPackageVersionResponse struct { + Name string `json:"name"` + Group string `json:"group,omitempty"` + Version string `json:"version"` + ContentType string `json:"content_type"` + Repository string `json:"repository"` + RepositoryUUID string `json:"repository_uuid"` + Release string `json:"release,omitempty"` + CreatedAt string `json:"created_at,omitempty"` +} + +// LightwellPackageVersionCollectionResponse is a paginated collection of cross-repo package versions. +type LightwellPackageVersionCollectionResponse struct { + Data []LightwellPackageVersionResponse `json:"data"` + Meta ResponseMetadata `json:"meta"` + Links Links `json:"links"` +} + +func (r *LightwellPackageVersionCollectionResponse) SetMetadata(meta ResponseMetadata, links Links) { + r.Meta = meta + r.Links = links +} + +// LightwellPackageFilterData holds query-parameter filters for the cross-repo packages endpoint. +type LightwellPackageFilterData struct { + ContentType string `query:"content_type"` + Name string `query:"name"` + Repository string `query:"repository"` + SecurityLevel string `query:"security_level"` +} + +// LightwellPackageVersionFilterData holds query-parameter filters for the cross-repo package_versions endpoint. +type LightwellPackageVersionFilterData struct { + ContentType string `query:"content_type"` + Name string `query:"name"` + SecurityLevel string `query:"security_level"` + Repository string `query:"repository"` + ResolvesCveID string `query:"resolves_cve_id"` + VulnerableToCveID string `query:"vulnerable_to_cve_id"` +} diff --git a/pkg/api/repositories.go b/pkg/api/repositories.go index 90e25f87c..059ad93a8 100644 --- a/pkg/api/repositories.go +++ b/pkg/api/repositories.go @@ -45,6 +45,9 @@ type RepositoryResponse struct { SecurityLevel string `json:"security_level,omitempty" readonly:"true"` // Security level of the repository (e.g. validated, remediated) PublishedDistURL string `json:"published_distribution_url,omitempty" readonly:"true"` // Published distribution URL from Pulp PublishedDistBasePath string `json:"-"` // Published dist base path from Pulp + PackagesCount *int `json:"packages_count,omitempty" readonly:"true"` // Lightwell: total distinct packages + VersionsCount *int `json:"versions_count,omitempty" readonly:"true"` // Lightwell: total distinct versions + RemediationsCount *int `json:"remediations_count,omitempty" readonly:"true"` // Lightwell: total security advisories } // RepositoryRequest holds data received from request to create repository diff --git a/pkg/handler/api.go b/pkg/handler/api.go index c1c110d98..bf37ccc74 100644 --- a/pkg/handler/api.go +++ b/pkg/handler/api.go @@ -15,12 +15,13 @@ import ( "github.com/content-services/content-sources-backend/pkg/clients/candlepin_client" "github.com/content-services/content-sources-backend/pkg/clients/feature_service_client" "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" - "github.com/content-services/content-sources-backend/pkg/clients/s3_client" "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" "github.com/content-services/content-sources-backend/pkg/db" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/content-services/content-sources-backend/pkg/tasks/client" "github.com/content-services/content-sources-backend/pkg/tasks/queue" + "github.com/jackc/pgx/v5/pgxpool" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" ) @@ -72,24 +73,23 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { if err != nil { panic(err) } - var s3Client s3_client.S3Client - if config.Get().Clients.Lightwell.S3.CoverageUploads.Name == "" { - log.Warn().Msg("s3 not configured") - } else { - s3Client, err = s3_client.NewS3Client(config.Get().Clients.Lightwell.S3.CoverageUploads) - if err != nil { - panic(err) - } - } - ch := cache.Initialize() + pgxPool, err := pgxpool.New(ctx, db.GetUrl()) + if err != nil { + log.Warn().Err(err).Msg("failed to create pgx pool for lightwell store; advisory endpoints disabled") + } + var lightwellQuerier store.Querier + if pgxPool != nil { + lightwellQuerier = store.New(pgxPool) + } + for i := 0; i < len(paths); i++ { group := engine.Group(paths[i]) group.GET("/openapi.json", openapi) daoReg := dao.GetDaoRegistry(db.DB) - RegisterRepositoryRoutes(group, daoReg, &taskClient, &fsClient) + RegisterRepositoryRoutes(group, daoReg, &taskClient, &fsClient, lightwellQuerier) RegisterRepositoryParameterRoutes(group, daoReg, &fsClient) RegisterRpmRoutes(group, daoReg) RegisterPopularRepositoriesRoutes(group, daoReg) @@ -108,7 +108,11 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { RegisterModuleStreamsRoutes(group, daoReg) RegisterUserPreferencesRoutes(group, daoReg) RegisterLightwellVulnerabilityRoutes(group, daoReg) - RegisterCoverageReportRoutes(group, daoReg, s3Client) + RegisterCoverageReportRoutes(group, daoReg) + + if lightwellQuerier != nil { + RegisterLightwellAdvisoryRoutes(group, lightwellQuerier) + } // Register package and build routes if tang client is available pulpClient := pulp_client.GetPulpClientWithDomain("") @@ -120,6 +124,9 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { } if config.Tang != nil { RegisterPackageRoutes(group, daoReg, *config.Tang, pulpClient) + if lightwellQuerier != nil { + RegisterLightwellPackageRoutes(group, lightwellQuerier, daoReg, *config.Tang, pulpClient) + } } } diff --git a/pkg/handler/coverage_reports.go b/pkg/handler/coverage_reports.go index 19e7d1338..62e4c52c1 100644 --- a/pkg/handler/coverage_reports.go +++ b/pkg/handler/coverage_reports.go @@ -1,7 +1,6 @@ package handler import ( - "bytes" "crypto/sha256" "encoding/hex" "io" @@ -9,7 +8,6 @@ import ( "time" "github.com/content-services/content-sources-backend/pkg/api" - "github.com/content-services/content-sources-backend/pkg/clients/s3_client" "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" "github.com/content-services/content-sources-backend/pkg/db" @@ -26,7 +24,6 @@ const maxCoverageUploadSizeBytes = 500 * 1024 * 1024 // 500 MiB type CoverageReportHandler struct { DaoRegistry dao.DaoRegistry - S3 s3_client.S3Client } func checkLightwellBeaconAndLensAccessible(next echo.HandlerFunc) echo.HandlerFunc { @@ -38,10 +35,9 @@ func checkLightwellBeaconAndLensAccessible(next echo.HandlerFunc) echo.HandlerFu } } -func RegisterCoverageReportRoutes(engine *echo.Group, daoReg *dao.DaoRegistry, s3Client s3_client.S3Client) { +func RegisterCoverageReportRoutes(engine *echo.Group, daoReg *dao.DaoRegistry) { ch := CoverageReportHandler{ DaoRegistry: *daoReg, - S3: s3Client, } addRepoRoute(engine, http.MethodPost, "/coverage_reports/", ch.createCoverageReport, rbac.RbacVerbWrite, checkLightwellBeaconAndLensAccessible) addRepoRoute(engine, http.MethodGet, "/coverage_reports/:uuid", ch.getCoverageReport, rbac.RbacVerbRead, checkLightwellBeaconAndLensAccessible) @@ -90,15 +86,6 @@ func (ch *CoverageReportHandler) createCoverageReport(c echo.Context) error { return ce.NewErrorResponse(http.StatusBadRequest, "Error reading upload", err.Error()) } - if config.FeatureAccessible(c.Request().Context(), config.Get().Features.LightwellStoreUploads) { - if ch.S3 == nil { - return ce.NewErrorResponse(http.StatusInternalServerError, "Error uploading coverage report", "s3 not configured") - } - if err := ch.S3.Put(c.Request().Context(), storageKey, bytes.NewReader(fileBytes)); err != nil { - return ce.NewErrorResponse(http.StatusInternalServerError, "Error uploading coverage report", err.Error()) - } - } - sha256Hex := hex.EncodeToString(hash.Sum(nil)) sizeBytes := int64(len(fileBytes)) @@ -128,6 +115,7 @@ func (ch *CoverageReportHandler) createCoverageReport(c echo.Context) error { log.Error().Err(err).Str("uuid", reportUUID).Msg("failed to seed coverage report") } }(report.UUID) + return c.JSON(http.StatusCreated, report) } return c.JSON(http.StatusCreated, report) diff --git a/pkg/handler/coverage_reports_test.go b/pkg/handler/coverage_reports_test.go index 67937b4ad..0e0ecadcf 100644 --- a/pkg/handler/coverage_reports_test.go +++ b/pkg/handler/coverage_reports_test.go @@ -12,7 +12,6 @@ import ( "time" "github.com/content-services/content-sources-backend/pkg/api" - "github.com/content-services/content-sources-backend/pkg/clients/s3_client" "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" ce "github.com/content-services/content-sources-backend/pkg/errors" @@ -31,8 +30,7 @@ import ( type CoverageReportSuite struct { suite.Suite - reg *dao.MockDaoRegistry - s3Mock *s3_client.MockS3Client + reg *dao.MockDaoRegistry } func TestCoverageReportSuite(t *testing.T) { @@ -41,8 +39,6 @@ func TestCoverageReportSuite(t *testing.T) { func (suite *CoverageReportSuite) SetupTest() { suite.reg = dao.GetMockDaoRegistry(suite.T()) - suite.s3Mock = s3_client.NewMockS3Client(suite.T()) - config.Get().Options.SeedLightwellCoverageReports = false } func (suite *CoverageReportSuite) serveCoverageReportRouter(req *http.Request, enabled bool, authorized bool) (int, []byte, error) { @@ -63,12 +59,7 @@ func (suite *CoverageReportSuite) serveCoverageReportRouter(req *http.Request, e config.Get().Features.LightwellBeaconAndLens.Accounts = &[]string{seeds.RandomAccountId()} } - h := CoverageReportHandler{ - DaoRegistry: *suite.reg.ToDaoRegistry(), - S3: suite.s3Mock, - } - - RegisterCoverageReportRoutes(pathPrefix, &h.DaoRegistry, h.S3) + RegisterCoverageReportRoutes(pathPrefix, suite.reg.ToDaoRegistry()) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) @@ -230,7 +221,6 @@ func (suite *CoverageReportSuite) TestListCoverageReportPackagesWithFilters() { func (suite *CoverageReportSuite) TestCreateCoverageReport() { t := suite.T() - config.Get().Features.LightwellStoreUploads = config.Feature{Enabled: true} reqBody := &bytes.Buffer{} writer := multipart.NewWriter(reqBody) part, err := writer.CreateFormFile("file", "sbom.json") @@ -246,7 +236,6 @@ func (suite *CoverageReportSuite) TestCreateCoverageReport() { } suite.reg.CoverageReport.On("Create", mock.Anything, mock.Anything, mock.Anything). Return(expectedReport, nil) - suite.s3Mock.On("Put", mock.Anything, mock.Anything, mock.Anything).Return(nil) req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s/coverage_reports/", api.FullRootPath()), reqBody) req.Header.Set("Content-Type", writer.FormDataContentType()) diff --git a/pkg/handler/lightwell_advisories.go b/pkg/handler/lightwell_advisories.go new file mode 100644 index 000000000..250c5255a --- /dev/null +++ b/pkg/handler/lightwell_advisories.go @@ -0,0 +1,154 @@ +package handler + +import ( + "net/http" + + "github.com/content-services/content-sources-backend/pkg/api" + ce "github.com/content-services/content-sources-backend/pkg/errors" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" + "github.com/content-services/content-sources-backend/pkg/rbac" + "github.com/jackc/pgx/v5/pgtype" + "github.com/labstack/echo/v4" +) + +type LightwellAdvisoryHandler struct { + Store store.Querier +} + +func RegisterLightwellAdvisoryRoutes(engine *echo.Group, querier store.Querier) { + h := LightwellAdvisoryHandler{Store: querier} + // Flat cross-repo endpoint + addRepoRoute(engine, http.MethodGet, "/lightwell/advisories", h.list, rbac.RbacVerbRead) + // Nested repo-scoped alias + addRepoRoute(engine, http.MethodGet, "/lightwell/repositories/:repository_name/advisories", h.listRepoAdvisories, rbac.RbacVerbRead) +} + +// listLightwellAdvisories godoc +// @Summary List Lightwell Advisories +// @ID listLightwellAdvisories +// @Description List security advisories for Lightwell remediated packages with optional filtering. +// @Tags lightwell +// @Accept json +// @Produce json +// @Param repository_uuid query string false "Filter by repository UUID" +// @Param package_name query string false "Filter by package name (substring match)" +// @Param severity_min query string false "Minimum severity level (low, moderate, important, critical)" +// @Param cve_id query string false "Filter by CVE ID (exact match)" +// @Param limit query int false "Limit of results to return" +// @Param offset query int false "Offset into results" +// @Success 200 {object} api.LightwellAdvisoryCollectionResponse +// @Failure 400 {object} ce.ErrorResponse +// @Failure 500 {object} ce.ErrorResponse +// @Router /lightwell/advisories [get] +func (h *LightwellAdvisoryHandler) list(c echo.Context) error { + page := ParsePagination(c) + filters := parseLightwellAdvisoryFilters(c) + + severityMin, err := parseSeverityMin(filters.SeverityMin) + if err != nil { + return ce.NewErrorResponse(http.StatusBadRequest, "Invalid severity_min", err.Error()) + } + + var repoName *string + if filters.Repository != "" { + repoName = &filters.Repository + } + + var packageName *string + if filters.PackageName != "" { + packageName = &filters.PackageName + } + + var cveID *string + if filters.CveID != "" { + cveID = &filters.CveID + } + + rows, err := h.Store.ListAdvisories(c.Request().Context(), store.ListAdvisoriesParams{ + RepoName: repoName, + PackageName: packageName, + SeverityMin: severityMin, + CveID: cveID, + PageOffset: int32(page.Offset), //nolint:gosec // bounded by ParsePagination + PageLimit: int32(page.Limit), //nolint:gosec // bounded by MaxLimit (200) + }) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing advisories", err.Error()) + } + + var totalCount int64 + if len(rows) > 0 { + totalCount = rows[0].TotalCount + } + + resp := mapAdvisoryRowsToResponse(rows) + collResp := setCollectionResponseMetadata(&resp, c, totalCount) + return c.JSON(http.StatusOK, collResp) +} + +func mapAdvisoryRowsToResponse(rows []store.ListAdvisoriesRow) api.LightwellAdvisoryCollectionResponse { + data := make([]api.LightwellAdvisoryResponse, 0, len(rows)) + for _, row := range rows { + refURLs := row.ReferenceUrls + if refURLs == nil { + refURLs = []string{} + } + fixedVersions := row.FixedVersions + if fixedVersions == nil { + fixedVersions = []string{} + } + data = append(data, api.LightwellAdvisoryResponse{ + AdvisoryID: row.AdvisoryID, + Severity: row.Severity, + Details: row.Details, + ReferenceURLs: refURLs, + PackageName: row.PackageName, + FixedVersions: fixedVersions, + Repository: row.RepoName, + }) + } + return api.LightwellAdvisoryCollectionResponse{Data: data} +} + +func parseLightwellAdvisoryFilters(c echo.Context) api.LightwellAdvisoryFilterData { + var filters api.LightwellAdvisoryFilterData + _ = echo.QueryParamsBinder(c). + String("repository", &filters.Repository). + String("package_name", &filters.PackageName). + String("severity_min", &filters.SeverityMin). + String("cve_id", &filters.CveID). + BindError() + return filters +} + +var severityMap = map[string]int16{ + "low": 1, + "moderate": 2, + "important": 3, + "critical": 4, +} + +func parseSeverityMin(s string) (pgtype.Int2, error) { + if s == "" { + return pgtype.Int2{}, nil + } + val, ok := severityMap[s] + if !ok { + return pgtype.Int2{}, &invalidSeverityError{severity: s} + } + return pgtype.Int2{Int16: val, Valid: true}, nil +} + +type invalidSeverityError struct { + severity string +} + +func (e *invalidSeverityError) Error() string { + return "invalid severity: " + e.severity + " (must be one of: low, moderate, important, critical)" +} + +func (h *LightwellAdvisoryHandler) listRepoAdvisories(c echo.Context) error { + repoName := c.Param("repository_name") + c.QueryParams().Set("repository", repoName) + return h.list(c) +} diff --git a/pkg/handler/lightwell_advisories_test.go b/pkg/handler/lightwell_advisories_test.go new file mode 100644 index 000000000..97467ace6 --- /dev/null +++ b/pkg/handler/lightwell_advisories_test.go @@ -0,0 +1,286 @@ +package handler + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/config" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" + "github.com/content-services/content-sources-backend/pkg/middleware" + test_handler "github.com/content-services/content-sources-backend/pkg/test/handler" + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" + "github.com/labstack/echo/v4" + echo_middleware "github.com/labstack/echo/v4/middleware" + "github.com/redhatinsights/platform-go-middlewares/v2/identity" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type LightwellAdvisorySuite struct { + suite.Suite + echo *echo.Echo + mockQuerier *MockQuerier +} + +func TestLightwellAdvisorySuite(t *testing.T) { + suite.Run(t, new(LightwellAdvisorySuite)) +} + +func (s *LightwellAdvisorySuite) SetupTest() { + s.echo = echo.New() + s.echo.Use(echo_middleware.RequestIDWithConfig(echo_middleware.RequestIDConfig{ + TargetHeader: "x-rh-insights-request-id", + })) + s.echo.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) + s.mockQuerier = &MockQuerier{} +} + +func (s *LightwellAdvisorySuite) TearDownTest() { + require.NoError(s.T(), s.echo.Shutdown(context.Background())) +} + +func (s *LightwellAdvisorySuite) serveRouter(req *http.Request) (int, []byte, error) { + router := echo.New() + router.HTTPErrorHandler = config.CustomHTTPErrorHandler + router.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) + pathPrefix := router.Group(api.FullRootPath()) + RegisterLightwellAdvisoryRoutes(pathPrefix, s.mockQuerier) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + + response := rr.Result() + defer response.Body.Close() + + body, err := io.ReadAll(response.Body) + return response.StatusCode, body, err +} + +func (s *LightwellAdvisorySuite) TestListAdvisories() { + t := s.T() + + repoUUID := uuid.New() + rows := []store.ListAdvisoriesRow{ + { + Uuid: uuid.New(), + AdvisoryID: "CVE-2024-1234", + Severity: "critical", + SeverityOrder: 4, + Details: "Remote code execution vulnerability", + ReferenceUrls: []string{"https://access.redhat.com/security/cve/CVE-2024-1234"}, + PackageName: "spring-core", + FixedVersions: []string{"5.3.18.rhlw-00003"}, + RepoName: "lightwell/java/remediated", + RepositoryConfigurationUuid: repoUUID, + CreatedAt: time.Now(), + TotalCount: 1, + }, + } + + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { + return arg.PageLimit == int32(DefaultLimit) && arg.PageOffset == 0 + })).Return(rows, nil) + + path := fmt.Sprintf("%s/lightwell/advisories", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "CVE-2024-1234", resp.Data[0].AdvisoryID) + assert.Equal(t, "critical", resp.Data[0].Severity) + assert.Equal(t, "spring-core", resp.Data[0].PackageName) + assert.Equal(t, []string{"5.3.18.rhlw-00003"}, resp.Data[0].FixedVersions) + assert.Equal(t, "lightwell/java/remediated", resp.Data[0].Repository) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesWithFilters() { + t := s.T() + + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { + return arg.PackageName != nil && *arg.PackageName == "spring" && + arg.SeverityMin == pgtype.Int2{Int16: 3, Valid: true} && + arg.PageLimit == 10 && arg.PageOffset == 5 + })).Return([]store.ListAdvisoriesRow{}, nil) + + path := fmt.Sprintf("%s/lightwell/advisories?package_name=spring&severity_min=important&limit=10&offset=5", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(0), resp.Meta.Count) + assert.Empty(t, resp.Data) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesInvalidSeverity() { + t := s.T() + + path := fmt.Sprintf("%s/lightwell/advisories?severity_min=bogus", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, code) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesFilterByRepoName() { + t := s.T() + + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { + return arg.RepoName != nil && *arg.RepoName == "java-remediated" + })).Return([]store.ListAdvisoriesRow{}, nil) + + path := fmt.Sprintf("%s/lightwell/advisories?repository=java-remediated", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) +} + +func (s *LightwellAdvisorySuite) TestNestedRepoAdvisoriesAlias() { + t := s.T() + + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { + return arg.RepoName != nil && *arg.RepoName == "java-remediated" + })).Return([]store.ListAdvisoriesRow{ + { + Uuid: uuid.New(), + AdvisoryID: "CVE-2024-5678", + Severity: "important", + SeverityOrder: 3, + Details: "Test advisory via nested route", + ReferenceUrls: []string{}, + PackageName: "spring-core", + FixedVersions: []string{"5.3.18.rhlw-00003"}, + RepoName: "java-remediated", + CreatedAt: time.Now(), + TotalCount: 1, + }, + }, nil) + + path := fmt.Sprintf("%s/lightwell/repositories/java-remediated/advisories", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "CVE-2024-5678", resp.Data[0].AdvisoryID) + assert.Equal(t, "java-remediated", resp.Data[0].Repository) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesEmptyResult() { + t := s.T() + + s.mockQuerier.On("ListAdvisories", mock.Anything, mock.Anything).Return([]store.ListAdvisoriesRow{}, nil) + + path := fmt.Sprintf("%s/lightwell/advisories", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(0), resp.Meta.Count) + assert.NotNil(t, resp.Data) + assert.Empty(t, resp.Data) +} + +// MockQuerier implements store.Querier for testing +type MockQuerier struct { + mock.Mock +} + +func (m *MockQuerier) ListAdvisories(ctx context.Context, arg store.ListAdvisoriesParams) ([]store.ListAdvisoriesRow, error) { + args := m.Called(ctx, arg) + val, _ := args.Get(0).([]store.ListAdvisoriesRow) + return val, args.Error(1) +} + +func (m *MockQuerier) CountAdvisoriesByRepo(ctx context.Context, repositoryConfigUuid uuid.UUID) (int64, error) { + args := m.Called(ctx, repositoryConfigUuid) + val, _ := args.Get(0).(int64) + return val, args.Error(1) +} + +func (m *MockQuerier) ListAdvisoriesByPackage(ctx context.Context, packageName string) ([]store.ListAdvisoriesByPackageRow, error) { + args := m.Called(ctx, packageName) + val, _ := args.Get(0).([]store.ListAdvisoriesByPackageRow) + return val, args.Error(1) +} + +func (m *MockQuerier) ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]store.ListAdvisoriesByCveIDRow, error) { + args := m.Called(ctx, cveID) + val, _ := args.Get(0).([]store.ListAdvisoriesByCveIDRow) + return val, args.Error(1) +} + +func (m *MockQuerier) CountAggregates(ctx context.Context, arg store.CountAggregatesParams) (store.CountAggregatesRow, error) { + args := m.Called(ctx, arg) + val, _ := args.Get(0).(store.CountAggregatesRow) + return val, args.Error(1) +} + +func (m *MockQuerier) CountByStage(ctx context.Context, arg store.CountByStageParams) ([]store.CountByStageRow, error) { + args := m.Called(ctx, arg) + val, _ := args.Get(0).([]store.CountByStageRow) + return val, args.Error(1) +} + +func (m *MockQuerier) ListCustomerIds(ctx context.Context) ([]string, error) { + args := m.Called(ctx) + val, _ := args.Get(0).([]string) + return val, args.Error(1) +} + +func (m *MockQuerier) ListLtwlsuptTicketIds(ctx context.Context, customerID string) ([]string, error) { + args := m.Called(ctx, customerID) + val, _ := args.Get(0).([]string) + return val, args.Error(1) +} + +func (m *MockQuerier) ListVulnerabilities(ctx context.Context, arg store.ListVulnerabilitiesParams) ([]store.ListVulnerabilitiesRow, error) { + args := m.Called(ctx, arg) + val, _ := args.Get(0).([]store.ListVulnerabilitiesRow) + return val, args.Error(1) +} diff --git a/pkg/handler/lightwell_packages.go b/pkg/handler/lightwell_packages.go new file mode 100644 index 000000000..6ef3697ed --- /dev/null +++ b/pkg/handler/lightwell_packages.go @@ -0,0 +1,728 @@ +package handler + +import ( + "context" + "errors" + "fmt" + "net/http" + "sort" + "strings" + "sync" + + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" + "github.com/content-services/content-sources-backend/pkg/config" + "github.com/content-services/content-sources-backend/pkg/dao" + ce "github.com/content-services/content-sources-backend/pkg/errors" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" + "github.com/content-services/content-sources-backend/pkg/rbac" + "github.com/content-services/tang/pkg/tangy" + "github.com/labstack/echo/v4" + "github.com/rs/zerolog/log" +) + +type LightwellPackagesHandler struct { + Store store.Querier + DaoRegistry dao.DaoRegistry + TangClient tangy.Tangy + PulpClient pulp_client.PulpClient +} + +func RegisterLightwellPackageRoutes(engine *echo.Group, querier store.Querier, daoReg *dao.DaoRegistry, tangClient tangy.Tangy, pulpClient pulp_client.PulpClient) { + h := LightwellPackagesHandler{ + Store: querier, + DaoRegistry: *daoReg, + TangClient: tangClient, + PulpClient: pulpClient, + } + // Flat cross-repo endpoints + addRepoRoute(engine, http.MethodGet, "/lightwell/packages", h.listPackages, rbac.RbacVerbRead) + addRepoRoute(engine, http.MethodGet, "/lightwell/package_versions", h.listPackageVersions, rbac.RbacVerbRead) + // Nested repo-scoped aliases + addRepoRoute(engine, http.MethodGet, "/lightwell/repositories/:repository_name/packages", h.listRepoPackages, rbac.RbacVerbRead) + addRepoRoute(engine, http.MethodGet, "/lightwell/repositories/:repository_name/package_versions", h.listRepoPackageVersions, rbac.RbacVerbRead) +} + +// listLightwellPackages godoc +// @Summary List Lightwell Packages (cross-repo) +// @ID listLightwellPackages +// @Description List packages aggregated across all Lightwell repositories, with optional filtering by content type, name, and security level. +// @Tags lightwell +// @Accept json +// @Produce json +// @Param type query string false "Filter by content type (maven, python, npm)" +// @Param name query string false "Filter by package name (substring match)" +// @Param security_level query string false "Filter by security level (validated, remediated)" +// @Param limit query int false "Limit of results to return" +// @Param offset query int false "Offset into results" +// @Success 200 {object} api.LightwellPackageCollectionResponse +// @Failure 400 {object} ce.ErrorResponse +// @Failure 500 {object} ce.ErrorResponse +// @Router /lightwell/packages [get] +func (h *LightwellPackagesHandler) listPackages(c echo.Context) error { + page := ParsePagination(c) + filters := parseLightwellPackageFilters(c) + + if err := validateContentType(filters.ContentType); err != nil { + return ce.NewErrorResponse(http.StatusBadRequest, "Invalid content_type filter", err.Error()) + } + + repos, err := h.fetchLightwellRepos(c, filters.ContentType, filters.SecurityLevel) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing Lightwell repositories", err.Error()) + } + if filters.Repository != "" { + repos = filterReposByName(repos, filters.Repository) + } + + items, err := h.aggregatePackages(c.Request().Context(), repos, filters.Name) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error retrieving packages", err.Error()) + } + + sortLightwellPackages(items, page.SortBy) + totalCount := int64(len(items)) + paged := paginatePackages(items, page.Offset, page.Limit) + resp := api.LightwellPackageCollectionResponse{Data: paged} + collResp := setCollectionResponseMetadata(&resp, c, totalCount) + return c.JSON(http.StatusOK, collResp) +} + +// listLightwellPackageVersions godoc +// @Summary List Lightwell Package Versions (cross-repo) +// @ID listLightwellPackageVersions +// @Description List individual package versions aggregated across all Lightwell repositories, with optional CVE-based filtering. +// @Tags lightwell +// @Accept json +// @Produce json +// @Param type query string false "Filter by content type (maven, python, npm)" +// @Param name query string false "Filter by package name (substring match)" +// @Param security_level query string false "Filter by security level (validated, remediated)" +// @Param repository query string false "Filter by repository name" +// @Param resolves_cve_id query string false "Show only packages that resolve this CVE" +// @Param vulnerable_to_cve_id query string false "Show only packages vulnerable to this CVE" +// @Param limit query int false "Limit of results to return" +// @Param offset query int false "Offset into results" +// @Success 200 {object} api.LightwellPackageVersionCollectionResponse +// @Failure 400 {object} ce.ErrorResponse +// @Failure 500 {object} ce.ErrorResponse +// @Router /lightwell/package_versions [get] +func (h *LightwellPackagesHandler) listPackageVersions(c echo.Context) error { + page := ParsePagination(c) + filters := parseLightwellPackageVersionFilters(c) + + if err := validateContentType(filters.ContentType); err != nil { + return ce.NewErrorResponse(http.StatusBadRequest, "Invalid content_type filter", err.Error()) + } + + repos, err := h.fetchLightwellRepos(c, filters.ContentType, filters.SecurityLevel) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing Lightwell repositories", err.Error()) + } + if filters.Repository != "" { + repos = filterReposByName(repos, filters.Repository) + } + + items, err := h.aggregatePackageVersions(c.Request().Context(), repos, filters.Name) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error retrieving package versions", err.Error()) + } + + if filters.ResolvesCveID != "" { + items, err = h.filterVersionsByResolvingCve(c.Request().Context(), items, filters.ResolvesCveID) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error filtering by CVE", err.Error()) + } + } + if filters.VulnerableToCveID != "" { + items, err = h.filterVersionsByVulnerableCve(c.Request().Context(), items, filters.VulnerableToCveID) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error filtering by CVE", err.Error()) + } + } + + sortLightwellVersions(items, page.SortBy) + totalCount := int64(len(items)) + paged := paginateVersions(items, page.Offset, page.Limit) + resp := api.LightwellPackageVersionCollectionResponse{Data: paged} + collResp := setCollectionResponseMetadata(&resp, c, totalCount) + return c.JSON(http.StatusOK, collResp) +} + +// fetchLightwellRepos returns Lightwell repos for the caller's org, optionally +// filtered by content type and security level. +func (h *LightwellPackagesHandler) fetchLightwellRepos(c echo.Context, contentType, securityLevel string) ([]api.RepositoryResponse, error) { + _, orgID := getAccountIdOrgId(c) + ctx := c.Request().Context() + + filter := api.FilterData{Origin: config.OriginLightwell} + if contentType != "" { + filter.ContentType = contentType + } + + repos, _, err := h.DaoRegistry.RepositoryConfig.List(ctx, orgID, api.PaginationData{Limit: MaxLimit}, filter) + if err != nil { + return nil, err + } + + if securityLevel == "" { + return repos.Data, nil + } + filtered := make([]api.RepositoryResponse, 0, len(repos.Data)) + for _, r := range repos.Data { + if strings.EqualFold(r.SecurityLevel, securityLevel) { + filtered = append(filtered, r) + } + } + return filtered, nil +} + +type repoPackageResult struct { + repo api.RepositoryResponse + pkgs []api.LightwellPackageResponse + err error +} + +// aggregatePackages queries Tang for each repo in parallel and merges results. +func (h *LightwellPackagesHandler) aggregatePackages(ctx context.Context, repos []api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageResponse, error) { + results := make([]repoPackageResult, len(repos)) + var wg sync.WaitGroup + + for i, repo := range repos { + wg.Add(1) + go func(idx int, r api.RepositoryResponse) { + defer wg.Done() + pkgs, err := h.fetchPackagesFromRepo(ctx, r, nameSearch) + results[idx] = repoPackageResult{repo: r, pkgs: pkgs, err: err} + }(i, repo) + } + wg.Wait() + + var combined []api.LightwellPackageResponse + var errs []error + for _, res := range results { + if res.err != nil { + errs = append(errs, fmt.Errorf("repo %s: %w", res.repo.Name, res.err)) + continue + } + combined = append(combined, res.pkgs...) + } + + if len(errs) > 0 && len(combined) == 0 { + return nil, errors.Join(errs...) + } + if len(errs) > 0 { + log.Warn().Errs("errors", errs).Msg("partial failure fetching cross-repo packages") + } + + return combined, nil +} + +func (h *LightwellPackagesHandler) fetchPackagesFromRepo(ctx context.Context, repo api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageResponse, error) { + if repo.PublishedDistBasePath == "" { + return nil, nil + } + + repositoryHref, err := h.resolveRepositoryHref(ctx, repo) + if err != nil { + return nil, err + } + + // Fetch all packages from this repo (no server-side pagination — small datasets) + pageOpts := tangy.PageOptions{Offset: 0, Limit: MaxLimit} + + switch repo.ContentType { + case config.ContentTypeMaven: + tangResp, err := h.TangClient.MavenPackageList(ctx, repositoryHref, tangy.MavenPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return mapMavenToLightwellPackages(tangResp, repo), nil + + case config.ContentTypePython: + tangResp, err := h.TangClient.PythonPackageList(ctx, repositoryHref, tangy.PythonPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return mapPythonToLightwellPackages(tangResp, repo), nil + + case config.ContentTypeNpm: + tangResp, err := h.TangClient.NpmPackageList(ctx, repositoryHref, tangy.NpmPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return mapNpmToLightwellPackages(tangResp, repo), nil + + default: + return nil, nil + } +} + +type repoVersionResult struct { + repo api.RepositoryResponse + versions []api.LightwellPackageVersionResponse + err error +} + +// aggregatePackageVersions queries Tang for each repo in parallel and expands +// every package into individual version items. +func (h *LightwellPackagesHandler) aggregatePackageVersions(ctx context.Context, repos []api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageVersionResponse, error) { + results := make([]repoVersionResult, len(repos)) + var wg sync.WaitGroup + + for i, repo := range repos { + wg.Add(1) + go func(idx int, r api.RepositoryResponse) { + defer wg.Done() + versions, err := h.fetchVersionsFromRepo(ctx, r, nameSearch) + results[idx] = repoVersionResult{repo: r, versions: versions, err: err} + }(i, repo) + } + wg.Wait() + + var combined []api.LightwellPackageVersionResponse + var errs []error + for _, res := range results { + if res.err != nil { + errs = append(errs, fmt.Errorf("repo %s: %w", res.repo.Name, res.err)) + continue + } + combined = append(combined, res.versions...) + } + + if len(errs) > 0 && len(combined) == 0 { + return nil, errors.Join(errs...) + } + if len(errs) > 0 { + log.Warn().Errs("errors", errs).Msg("partial failure fetching cross-repo versions") + } + + return combined, nil +} + +func (h *LightwellPackagesHandler) fetchVersionsFromRepo(ctx context.Context, repo api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageVersionResponse, error) { + if repo.PublishedDistBasePath == "" { + return nil, nil + } + + repositoryHref, err := h.resolveRepositoryHref(ctx, repo) + if err != nil { + return nil, err + } + + pageOpts := tangy.PageOptions{Offset: 0, Limit: MaxLimit} + + switch repo.ContentType { + case config.ContentTypeMaven: + tangResp, err := h.TangClient.MavenPackageList(ctx, repositoryHref, tangy.MavenPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return expandMavenVersions(tangResp, repo), nil + + case config.ContentTypePython: + tangResp, err := h.TangClient.PythonPackageList(ctx, repositoryHref, tangy.PythonPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return expandPythonVersions(tangResp, repo), nil + + case config.ContentTypeNpm: + tangResp, err := h.TangClient.NpmPackageList(ctx, repositoryHref, tangy.NpmPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return expandNpmVersions(tangResp, repo), nil + + default: + return nil, nil + } +} + +func (h *LightwellPackagesHandler) resolveRepositoryHref(ctx context.Context, repo api.RepositoryResponse) (string, error) { + domainName, err := h.DaoRegistry.Domain.FetchOrCreateDomain(ctx, repo.OrgID) + if err != nil { + return "", err + } + pulpClient := h.PulpClient.WithDomain(domainName) + href, err := pulpClient.ResolveRepositoryFromBasePath(ctx, repo.PublishedDistBasePath) + if err != nil { + return "", fmt.Errorf("repo %s: %w", repo.UUID, err) + } + if href == nil { + return "", fmt.Errorf("repo %s: distribution not found", repo.UUID) + } + return *href, nil +} + +// filterVersionsByResolvingCve keeps only versions that fix the given CVE. +func (h *LightwellPackagesHandler) filterVersionsByResolvingCve(ctx context.Context, items []api.LightwellPackageVersionResponse, cveID string) ([]api.LightwellPackageVersionResponse, error) { + advisories, err := h.Store.ListAdvisoriesByCveID(ctx, cveID) + if err != nil { + return nil, err + } + + fixedSet := make(map[string]map[string]bool) // package_name -> set of fixed versions + for _, adv := range advisories { + if fixedSet[adv.PackageName] == nil { + fixedSet[adv.PackageName] = make(map[string]bool) + } + for _, v := range adv.FixedVersions { + fixedSet[adv.PackageName][v] = true + } + } + + var result []api.LightwellPackageVersionResponse + for _, item := range items { + if versions, ok := fixedSet[item.Name]; ok && versions[item.Version] { + result = append(result, item) + } + } + return result, nil +} + +// filterVersionsByVulnerableCve keeps only versions of packages affected by +// the given CVE that are NOT in the fixed-versions list. +func (h *LightwellPackagesHandler) filterVersionsByVulnerableCve(ctx context.Context, items []api.LightwellPackageVersionResponse, cveID string) ([]api.LightwellPackageVersionResponse, error) { + advisories, err := h.Store.ListAdvisoriesByCveID(ctx, cveID) + if err != nil { + return nil, err + } + + affectedPackages := make(map[string]bool) + fixedSet := make(map[string]map[string]bool) + for _, adv := range advisories { + affectedPackages[adv.PackageName] = true + if fixedSet[adv.PackageName] == nil { + fixedSet[adv.PackageName] = make(map[string]bool) + } + for _, v := range adv.FixedVersions { + fixedSet[adv.PackageName][v] = true + } + } + + var result []api.LightwellPackageVersionResponse + for _, item := range items { + if affectedPackages[item.Name] && !fixedSet[item.Name][item.Version] { + result = append(result, item) + } + } + return result, nil +} + +// --- mapping helpers --- + +func mapMavenToLightwellPackages(resp tangy.MavenPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageResponse { + out := make([]api.LightwellPackageResponse, 0, len(resp.Results)) + for _, item := range resp.Results { + releases := make([]api.ReleaseInfo, len(item.LatestReleases)) + for j, rel := range item.LatestReleases { + releases[j] = api.ReleaseInfo{Version: rel.Version, Release: rel.Release, CreatedAt: rel.CreatedAt} + } + out = append(out, api.LightwellPackageResponse{ + Name: item.ArtifactID, + Group: item.GroupID, + ContentType: config.ContentTypeMaven, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + Versions: item.Versions, + LatestReleases: releases, + }) + } + return out +} + +func mapPythonToLightwellPackages(resp tangy.PythonPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageResponse { + out := make([]api.LightwellPackageResponse, 0, len(resp.Results)) + for _, item := range resp.Results { + releases := make([]api.ReleaseInfo, len(item.LatestVersions)) + for j, ver := range item.LatestVersions { + releases[j] = api.ReleaseInfo{Version: ver.Version, CreatedAt: ver.CreatedAt} + } + out = append(out, api.LightwellPackageResponse{ + Name: item.NameNormalized, + ContentType: config.ContentTypePython, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + Versions: item.Versions, + LatestReleases: releases, + }) + } + return out +} + +func mapNpmToLightwellPackages(resp tangy.NpmPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageResponse { + out := make([]api.LightwellPackageResponse, 0, len(resp.Results)) + for _, item := range resp.Results { + releases := make([]api.ReleaseInfo, len(item.LatestVersions)) + for j, ver := range item.LatestVersions { + releases[j] = api.ReleaseInfo{Version: ver.Version, CreatedAt: ver.CreatedAt} + } + scope, name := parseNpmPackageName(item.Name) + out = append(out, api.LightwellPackageResponse{ + Name: name, + Group: scope, + ContentType: config.ContentTypeNpm, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + Versions: item.Versions, + LatestReleases: releases, + }) + } + return out +} + +func expandMavenVersions(resp tangy.MavenPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageVersionResponse { + var out []api.LightwellPackageVersionResponse + for _, item := range resp.Results { + relMap := latestReleaseMap(item.LatestReleases) + for _, v := range item.Versions { + ver := api.LightwellPackageVersionResponse{ + Name: item.ArtifactID, + Group: item.GroupID, + Version: v, + ContentType: config.ContentTypeMaven, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + } + if rel, ok := relMap[v]; ok { + ver.Release = rel.Release + ver.CreatedAt = rel.CreatedAt + } + out = append(out, ver) + } + } + return out +} + +func expandPythonVersions(resp tangy.PythonPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageVersionResponse { + var out []api.LightwellPackageVersionResponse + for _, item := range resp.Results { + verMap := latestVersionMap(item.LatestVersions) + for _, v := range item.Versions { + ver := api.LightwellPackageVersionResponse{ + Name: item.NameNormalized, + Version: v, + ContentType: config.ContentTypePython, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + } + if info, ok := verMap[v]; ok { + ver.CreatedAt = info.CreatedAt + } + out = append(out, ver) + } + } + return out +} + +func expandNpmVersions(resp tangy.NpmPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageVersionResponse { + var out []api.LightwellPackageVersionResponse + for _, item := range resp.Results { + scope, name := parseNpmPackageName(item.Name) + verMap := npmVersionMap(item.LatestVersions) + for _, v := range item.Versions { + ver := api.LightwellPackageVersionResponse{ + Name: name, + Group: scope, + Version: v, + ContentType: config.ContentTypeNpm, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + } + if info, ok := verMap[v]; ok { + ver.CreatedAt = info.CreatedAt + } + out = append(out, ver) + } + } + return out +} + +// --- filter / pagination helpers --- + +func parseLightwellPackageFilters(c echo.Context) api.LightwellPackageFilterData { + var f api.LightwellPackageFilterData + _ = echo.QueryParamsBinder(c). + String("content_type", &f.ContentType). + String("name", &f.Name). + String("repository", &f.Repository). + String("security_level", &f.SecurityLevel). + BindError() + return f +} + +func parseLightwellPackageVersionFilters(c echo.Context) api.LightwellPackageVersionFilterData { + var f api.LightwellPackageVersionFilterData + _ = echo.QueryParamsBinder(c). + String("content_type", &f.ContentType). + String("name", &f.Name). + String("security_level", &f.SecurityLevel). + String("repository", &f.Repository). + String("resolves_cve_id", &f.ResolvesCveID). + String("vulnerable_to_cve_id", &f.VulnerableToCveID). + BindError() + return f +} + +var validContentTypes = map[string]bool{ + config.ContentTypeMaven: true, + config.ContentTypePython: true, + config.ContentTypeNpm: true, +} + +func validateContentType(ct string) error { + if ct == "" { + return nil + } + if !validContentTypes[ct] { + return fmt.Errorf("unsupported type: %s (must be maven, python, or npm)", ct) + } + return nil +} + +func filterReposByName(repos []api.RepositoryResponse, name string) []api.RepositoryResponse { + var out []api.RepositoryResponse + for _, r := range repos { + if strings.EqualFold(r.Name, name) { + out = append(out, r) + } + } + return out +} + +func paginatePackages(items []api.LightwellPackageResponse, offset, limit int) []api.LightwellPackageResponse { + if offset >= len(items) { + return []api.LightwellPackageResponse{} + } + end := offset + limit + if end > len(items) { + end = len(items) + } + return items[offset:end] +} + +func paginateVersions(items []api.LightwellPackageVersionResponse, offset, limit int) []api.LightwellPackageVersionResponse { + if offset >= len(items) { + return []api.LightwellPackageVersionResponse{} + } + end := offset + limit + if end > len(items) { + end = len(items) + } + return items[offset:end] +} + +// release-info lookup helpers for version expansion + +type mavenRelInfo struct { + Release string + CreatedAt string +} + +func latestReleaseMap(releases []tangy.MavenReleaseInfo) map[string]mavenRelInfo { + m := make(map[string]mavenRelInfo, len(releases)) + for _, r := range releases { + m[r.Version] = mavenRelInfo{Release: r.Release, CreatedAt: r.CreatedAt} + } + return m +} + +type versionCreatedAt struct { + CreatedAt string +} + +func latestVersionMap(versions []tangy.PythonVersionInfo) map[string]versionCreatedAt { + m := make(map[string]versionCreatedAt, len(versions)) + for _, v := range versions { + m[v.Version] = versionCreatedAt{CreatedAt: v.CreatedAt} + } + return m +} + +func npmVersionMap(versions []tangy.NpmVersionInfo) map[string]versionCreatedAt { + m := make(map[string]versionCreatedAt, len(versions)) + for _, v := range versions { + m[v.Version] = versionCreatedAt{CreatedAt: v.CreatedAt} + } + return m +} + +// --- nested repo-scoped alias handlers --- + +func (h *LightwellPackagesHandler) listRepoPackages(c echo.Context) error { + repoName := c.Param("repository_name") + c.QueryParams().Set("repository", repoName) + return h.listPackages(c) +} + +func (h *LightwellPackagesHandler) listRepoPackageVersions(c echo.Context) error { + repoName := c.Param("repository_name") + c.QueryParams().Set("repository", repoName) + return h.listPackageVersions(c) +} + +// --- sort helpers --- + +func sortLightwellPackages(items []api.LightwellPackageResponse, sortBy string) { + field, dir := parseSortBy(sortBy) + if field == "" { + field = "name" + } + sort.SliceStable(items, func(i, j int) bool { + var less bool + switch field { + case "name": + less = items[i].Name < items[j].Name + case "content_type": + less = items[i].ContentType < items[j].ContentType + case "repository": + less = items[i].Repository < items[j].Repository + default: + less = items[i].Name < items[j].Name + } + if dir == "desc" { + return !less + } + return less + }) +} + +func sortLightwellVersions(items []api.LightwellPackageVersionResponse, sortBy string) { + field, dir := parseSortBy(sortBy) + if field == "" { + field = "name" + } + sort.SliceStable(items, func(i, j int) bool { + var less bool + switch field { + case "name": + less = items[i].Name < items[j].Name + case "version": + less = items[i].Version < items[j].Version + case "content_type": + less = items[i].ContentType < items[j].ContentType + case "repository": + less = items[i].Repository < items[j].Repository + default: + less = items[i].Name < items[j].Name + } + if dir == "desc" { + return !less + } + return less + }) +} + +func parseSortBy(sortBy string) (field, direction string) { + if sortBy == "" { + return "", "asc" + } + parts := strings.Fields(sortBy) + field = strings.ToLower(parts[0]) + direction = "asc" + if len(parts) > 1 && strings.EqualFold(parts[1], "desc") { + direction = "desc" + } + return field, direction +} diff --git a/pkg/handler/lightwell_packages_test.go b/pkg/handler/lightwell_packages_test.go new file mode 100644 index 000000000..39d99a623 --- /dev/null +++ b/pkg/handler/lightwell_packages_test.go @@ -0,0 +1,538 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" + "github.com/content-services/content-sources-backend/pkg/config" + "github.com/content-services/content-sources-backend/pkg/dao" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" + "github.com/content-services/content-sources-backend/pkg/middleware" + "github.com/content-services/content-sources-backend/pkg/test" + test_handler "github.com/content-services/content-sources-backend/pkg/test/handler" + "github.com/content-services/tang/pkg/tangy" + "github.com/labstack/echo/v4" + echo_middleware "github.com/labstack/echo/v4/middleware" + "github.com/redhatinsights/platform-go-middlewares/v2/identity" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type LightwellPackagesSuite struct { + suite.Suite + reg *dao.MockDaoRegistry + tangClient *tangy.MockTangy + pulpClient *pulp_client.MockPulpClient + querier *MockQuerier +} + +func TestLightwellPackagesSuite(t *testing.T) { + suite.Run(t, new(LightwellPackagesSuite)) +} + +func (s *LightwellPackagesSuite) SetupTest() { + s.reg = dao.GetMockDaoRegistry(s.T()) + s.tangClient = tangy.NewMockTangy(s.T()) + s.pulpClient = pulp_client.NewMockPulpClient(s.T()) + s.querier = &MockQuerier{} +} + +func (s *LightwellPackagesSuite) serveRouter(req *http.Request) (int, []byte, error) { + router := echo.New() + router.HTTPErrorHandler = config.CustomHTTPErrorHandler + router.Use(echo_middleware.RequestIDWithConfig(echo_middleware.RequestIDConfig{ + TargetHeader: "x-rh-insights-request-id", + })) + router.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) + pathPrefix := router.Group(api.FullRootPath()) + RegisterLightwellPackageRoutes(pathPrefix, s.querier, s.reg.ToDaoRegistry(), s.tangClient, s.pulpClient) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + + response := rr.Result() + defer response.Body.Close() + body, err := io.ReadAll(response.Body) + return response.StatusCode, body, err +} + +// stubLightwellRepos sets up the DAO mock to return the given repos for a List call with origin=lightwell. +func (s *LightwellPackagesSuite) stubLightwellRepos(repos []api.RepositoryResponse) { + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { return f.Origin == config.OriginLightwell }), + ).Return(api.RepositoryCollectionResponse{Data: repos}, int64(len(repos)), nil) +} + +func (s *LightwellPackagesSuite) stubRepoHref(repo api.RepositoryResponse, href string) { + domainName := "test-domain" + s.reg.Domain.On("FetchOrCreateDomain", test.MockCtx(), repo.OrgID).Return(domainName, nil).Maybe() + s.pulpClient.On("WithDomain", domainName).Return(s.pulpClient).Maybe() + s.pulpClient.On("ResolveRepositoryFromBasePath", test.MockCtx(), repo.PublishedDistBasePath).Return(&href, nil).Maybe() +} + +func newMavenRepo() api.RepositoryResponse { + return api.RepositoryResponse{ + UUID: "aaa-bbb-ccc", + Name: "lightwell/java/remediated", + ContentType: config.ContentTypeMaven, + Origin: config.OriginLightwell, + SecurityLevel: "remediated", + PublishedDistBasePath: "java/remediated", + OrgID: test_handler.MockOrgId, + } +} + +func newPythonRepo() api.RepositoryResponse { + return api.RepositoryResponse{ + UUID: "ddd-eee-fff", + Name: "lightwell/python/remediated", + ContentType: config.ContentTypePython, + Origin: config.OriginLightwell, + SecurityLevel: "remediated", + PublishedDistBasePath: "python/remediated", + OrgID: test_handler.MockOrgId, + } +} + +func mavenTangResponse() tangy.MavenPackageListResponse { + return tangy.MavenPackageListResponse{ + Results: []tangy.MavenPackageListItem{ + { + GroupID: "com.fasterxml.jackson.core", + ArtifactID: "jackson-databind", + Versions: []string{"2.15.3.rhlw-00001", "2.14.2.rhlw-00001"}, + LatestReleases: []tangy.MavenReleaseInfo{ + {Version: "2.15.3.rhlw-00001", Release: "rhlw-00001", CreatedAt: "2024-06-01T12:00:00Z"}, + }, + }, + }, + Total: 1, Limit: 200, Offset: 0, + } +} + +func pythonTangResponse() tangy.PythonPackageListResponse { + return tangy.PythonPackageListResponse{ + Results: []tangy.PythonPackageListItem{ + { + Name: "requests", + NameNormalized: "requests", + Versions: []string{"2.31.0.rhlw-00001"}, + LatestVersions: []tangy.PythonVersionInfo{ + {Version: "2.31.0.rhlw-00001", CreatedAt: "2024-05-10T08:00:00Z"}, + }, + }, + }, + Total: 1, Limit: 200, Offset: 0, + } +} + +// --- /lightwell/packages tests --- + +func (s *LightwellPackagesSuite) TestListPackagesSingleRepo() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/default/api/v3/repositories/maven/maven/some-uuid/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, "com.fasterxml.jackson.core", resp.Data[0].Group) + assert.Equal(t, config.ContentTypeMaven, resp.Data[0].ContentType) + assert.Equal(t, "lightwell/java/remediated", resp.Data[0].Repository) + assert.Equal(t, 2, len(resp.Data[0].Versions)) +} + +func (s *LightwellPackagesSuite) TestListPackagesMultiRepo() { + t := s.T() + + mavenRepo := newMavenRepo() + pythonRepo := newPythonRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo, pythonRepo}) + + mavenHref := "/api/pulp/repos/maven/1/" + pythonHref := "/api/pulp/repos/python/1/" + s.stubRepoHref(mavenRepo, mavenHref) + s.stubRepoHref(pythonRepo, pythonHref) + + s.tangClient.On("MavenPackageList", test.MockCtx(), mavenHref, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + s.tangClient.On("PythonPackageList", test.MockCtx(), pythonHref, + tangy.PythonPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(pythonTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) + assert.Len(t, resp.Data, 2) + + contentTypes := map[string]bool{} + for _, p := range resp.Data { + contentTypes[p.ContentType] = true + } + assert.True(t, contentTypes[config.ContentTypeMaven]) + assert.True(t, contentTypes[config.ContentTypePython]) +} + +func (s *LightwellPackagesSuite) TestListPackagesTypeFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + // Only maven repo should be returned when filtering by content_type=maven + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { + return f.Origin == config.OriginLightwell && f.ContentType == config.ContentTypeMaven + }), + ).Return(api.RepositoryCollectionResponse{Data: []api.RepositoryResponse{mavenRepo}}, int64(1), nil) + + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/packages?content_type=maven", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Len(t, resp.Data, 1) + assert.Equal(t, config.ContentTypeMaven, resp.Data[0].ContentType) +} + +func (s *LightwellPackagesSuite) TestListPackagesInvalidType() { + t := s.T() + + path := fmt.Sprintf("%s/lightwell/packages?content_type=invalid", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, code) +} + +func (s *LightwellPackagesSuite) TestListPackagesEmptyResult() { + t := s.T() + + s.stubLightwellRepos([]api.RepositoryResponse{}) + + path := fmt.Sprintf("%s/lightwell/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(0), resp.Meta.Count) + assert.NotNil(t, resp.Data) + assert.Empty(t, resp.Data) +} + +// --- /lightwell/package_versions tests --- + +func (s *LightwellPackagesSuite) TestListPackageVersionsSingleRepo() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) // 2 versions for jackson-databind + assert.Len(t, resp.Data, 2) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, config.ContentTypeMaven, resp.Data[0].ContentType) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsWithNameFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{Search: "jackson"}, + tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/package_versions?name=jackson", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + assert.Len(t, resp.Data, 2) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsPagination() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + // Request with limit=1&offset=0 — should get 1 of 2 versions + path := fmt.Sprintf("%s/lightwell/package_versions?limit=1&offset=0", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) // total is 2 + assert.Len(t, resp.Data, 1) // page is 1 + assert.NotEmpty(t, resp.Links.Next) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsInvalidType() { + t := s.T() + + path := fmt.Sprintf("%s/lightwell/package_versions?content_type=bogus", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, code) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsEmptyResult() { + t := s.T() + + s.stubLightwellRepos([]api.RepositoryResponse{}) + + path := fmt.Sprintf("%s/lightwell/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + assert.Equal(t, int64(0), resp.Meta.Count) + assert.NotNil(t, resp.Data) + assert.Empty(t, resp.Data) +} + +// --- resolves_cve_id / vulnerable_to_cve_id filter tests --- + +func (s *LightwellPackagesSuite) TestListPackageVersionsResolvesCveFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + s.querier.On("ListAdvisoriesByCveID", test.MockCtx(), "CVE-2024-9999").Return([]store.ListAdvisoriesByCveIDRow{ + { + PackageName: "jackson-databind", + FixedVersions: []string{"2.15.3.rhlw-00001"}, + RepoName: "lightwell/java/remediated", + Severity: "critical", + }, + }, nil) + + path := fmt.Sprintf("%s/lightwell/package_versions?resolves_cve_id=CVE-2024-9999", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, "2.15.3.rhlw-00001", resp.Data[0].Version) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsVulnerableToCveFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + // Advisory says jackson-databind is fixed at 2.15.3.rhlw-00001, so + // the older version 2.14.2.rhlw-00001 should be returned as vulnerable. + s.querier.On("ListAdvisoriesByCveID", test.MockCtx(), "CVE-2024-8888").Return([]store.ListAdvisoriesByCveIDRow{ + { + PackageName: "jackson-databind", + FixedVersions: []string{"2.15.3.rhlw-00001"}, + RepoName: "lightwell/java/remediated", + Severity: "important", + }, + }, nil) + + path := fmt.Sprintf("%s/lightwell/package_versions?vulnerable_to_cve_id=CVE-2024-8888", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, "2.14.2.rhlw-00001", resp.Data[0].Version) +} + +// --- nested repo-scoped alias tests --- + +func (s *LightwellPackagesSuite) TestNestedRepoPackagesAlias() { + t := s.T() + + mavenRepo := newMavenRepo() + mavenRepo.Name = "java-remediated" + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { return f.Origin == config.OriginLightwell }), + ).Return(api.RepositoryCollectionResponse{Data: []api.RepositoryResponse{mavenRepo}}, int64(1), nil) + + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/repositories/java-remediated/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) +} + +func (s *LightwellPackagesSuite) TestNestedRepoPackageVersionsAlias() { + t := s.T() + + mavenRepo := newMavenRepo() + mavenRepo.Name = "java-remediated" + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { return f.Origin == config.OriginLightwell }), + ).Return(api.RepositoryCollectionResponse{Data: []api.RepositoryResponse{mavenRepo}}, int64(1), nil) + + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/repositories/java-remediated/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) + assert.Len(t, resp.Data, 2) +} diff --git a/pkg/handler/repositories.go b/pkg/handler/repositories.go index 22a6b00a2..a89b8985f 100644 --- a/pkg/handler/repositories.go +++ b/pkg/handler/repositories.go @@ -12,6 +12,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" ce "github.com/content-services/content-sources-backend/pkg/errors" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/content-services/content-sources-backend/pkg/rbac" "github.com/content-services/content-sources-backend/pkg/tasks" "github.com/content-services/content-sources-backend/pkg/tasks/client" @@ -33,10 +34,12 @@ type RepositoryHandler struct { DaoRegistry dao.DaoRegistry TaskClient client.TaskClient FeatureServiceClient feature_service_client.FeatureServiceClient + LightwellStore store.Querier // nil when lightwell store is unavailable } func RegisterRepositoryRoutes(engine *echo.Group, daoReg *dao.DaoRegistry, taskClient *client.TaskClient, fsClient *feature_service_client.FeatureServiceClient, + lightwellStore ...store.Querier, ) { if engine == nil { panic("engine is nil") @@ -55,6 +58,9 @@ func RegisterRepositoryRoutes(engine *echo.Group, daoReg *dao.DaoRegistry, TaskClient: *taskClient, FeatureServiceClient: *fsClient, } + if len(lightwellStore) > 0 && lightwellStore[0] != nil { + rh.LightwellStore = lightwellStore[0] + } addRepoRoute(engine, http.MethodGet, "/repositories/", rh.listRepositories, rbac.RbacVerbRead) addRepoRoute(engine, http.MethodGet, "/repositories/:uuid", rh.fetch, rbac.RbacVerbRead) @@ -122,9 +128,43 @@ func (rh *RepositoryHandler) listRepositories(c echo.Context) error { return ce.NewErrorResponse(ce.HttpCodeForDaoError(err), "Error listing repositories", err.Error()) } + rh.enrichLightwellRepoCounts(c, &repos) + return c.JSON(200, setCollectionResponseMetadata(&repos, c, totalRepos)) } +// enrichLightwellRepoCounts populates packages_count, versions_count, and +// remediations_count on Lightwell-origin repositories. These spec-required +// fields are omitted for non-Lightwell repos to avoid breaking existing consumers. +func (rh *RepositoryHandler) enrichLightwellRepoCounts(c echo.Context, repos *api.RepositoryCollectionResponse) { + for i := range repos.Data { + repo := &repos.Data[i] + if repo.Origin != config.OriginLightwell { + continue + } + pkgCount := repo.PackageCount + verCount := repo.VersionCount + repo.PackagesCount = &pkgCount + repo.VersionsCount = &verCount + + if rh.LightwellStore == nil { + continue + } + repoUUID, err := uuid.Parse(repo.UUID) + if err != nil { + log.Ctx(c.Request().Context()).Warn().Err(err).Str("uuid", repo.UUID).Msg("invalid UUID for advisory count") + continue + } + count, err := rh.LightwellStore.CountAdvisoriesByRepo(c.Request().Context(), repoUUID) + if err != nil { + log.Ctx(c.Request().Context()).Warn().Err(err).Str("uuid", repo.UUID).Msg("failed to count advisories") + continue + } + remCount := int(count) + repo.RemediationsCount = &remCount + } +} + // CreateRepository godoc // @Summary Create Repository // @ID createRepository diff --git a/pkg/handler/user_preferences.go b/pkg/handler/user_preferences.go index c329d0219..98ac04bfb 100644 --- a/pkg/handler/user_preferences.go +++ b/pkg/handler/user_preferences.go @@ -7,6 +7,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/dao" ce "github.com/content-services/content-sources-backend/pkg/errors" + "github.com/content-services/content-sources-backend/pkg/rbac" "github.com/labstack/echo/v4" "github.com/redhatinsights/platform-go-middlewares/v2/identity" ) @@ -17,8 +18,8 @@ type UserPreferencesHandler struct { func RegisterUserPreferencesRoutes(engine *echo.Group, daoReg *dao.DaoRegistry) { h := UserPreferencesHandler{DaoRegistry: *daoReg} - engine.GET("/user_preferences/", h.listUserPreferences) - engine.PUT("/user_preferences/:label", h.setUserPreference) + addRepoRoute(engine, http.MethodGet, "/user_preferences/", h.listUserPreferences, rbac.RbacVerbRead) + addRepoRoute(engine, http.MethodPut, "/user_preferences/:label", h.setUserPreference, rbac.RbacVerbWrite) } // ListUserPreferences godoc From 32508fa4e32a0e6bed0176ee9910a0606a98f5db Mon Sep 17 00:00:00 2001 From: etsien Date: Tue, 25 Aug 2026 14:56:41 -0400 Subject: [PATCH 20/47] LWLP-5: regenerate OpenAPI spec for Lightwell endpoints --- api/docs.go | 402 +++++++++++++++++++++++++++++++++++++++++ api/openapi.json | 458 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 860 insertions(+) diff --git a/api/docs.go b/api/docs.go index b76f7581c..8070aa9f7 100644 --- a/api/docs.go +++ b/api/docs.go @@ -282,6 +282,80 @@ const docTemplate = `{ } } }, + "/lightwell/advisories": { + "get": { + "description": "List security advisories for Lightwell remediated packages with optional filtering.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Advisories", + "operationId": "listLightwellAdvisories", + "parameters": [ + { + "type": "string", + "description": "Filter by repository UUID", + "name": "repository_uuid", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "package_name", + "in": "query" + }, + { + "type": "string", + "description": "Minimum severity level (low, moderate, important, critical)", + "name": "severity_min", + "in": "query" + }, + { + "type": "string", + "description": "Filter by CVE ID (exact match)", + "name": "cve_id", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellAdvisoryCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, "/lightwell/beacon/vulnerabilities/": { "get": { "description": "List Lightwell vulnerabilities for a customer, with filters, pagination, and aggregate counts.", @@ -474,6 +548,160 @@ const docTemplate = `{ } } }, + "/lightwell/package_versions": { + "get": { + "description": "List individual package versions aggregated across all Lightwell repositories, with optional CVE-based filtering.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Package Versions (cross-repo)", + "operationId": "listLightwellPackageVersions", + "parameters": [ + { + "type": "string", + "description": "Filter by content type (maven, python, npm)", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by security level (validated, remediated)", + "name": "security_level", + "in": "query" + }, + { + "type": "string", + "description": "Filter by repository name", + "name": "repository", + "in": "query" + }, + { + "type": "string", + "description": "Show only packages that resolve this CVE", + "name": "resolves_cve_id", + "in": "query" + }, + { + "type": "string", + "description": "Show only packages vulnerable to this CVE", + "name": "vulnerable_to_cve_id", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellPackageVersionCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, + "/lightwell/packages": { + "get": { + "description": "List packages aggregated across all Lightwell repositories, with optional filtering by content type, name, and security level.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Packages (cross-repo)", + "operationId": "listLightwellPackages", + "parameters": [ + { + "type": "string", + "description": "Filter by content type (maven, python, npm)", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by security level (validated, remediated)", + "name": "security_level", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellPackageCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, "/module_streams/search": { "post": { "description": "List modules and their streams for repositories", @@ -5097,6 +5325,55 @@ const docTemplate = `{ } } }, + "api.LightwellAdvisoryCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellAdvisoryResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellAdvisoryResponse": { + "type": "object", + "properties": { + "advisory_id": { + "type": "string" + }, + "details": { + "type": "string" + }, + "fixed_versions": { + "type": "array", + "items": { + "type": "string" + } + }, + "package_name": { + "type": "string" + }, + "reference_urls": { + "type": "array", + "items": { + "type": "string" + } + }, + "repository": { + "type": "string" + }, + "severity": { + "type": "string" + } + } + }, "api.LightwellCustomerIdsResponse": { "type": "object", "properties": { @@ -5121,6 +5398,101 @@ const docTemplate = `{ } } }, + "api.LightwellPackageCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellPackageResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellPackageResponse": { + "type": "object", + "properties": { + "content_type": { + "type": "string" + }, + "group": { + "type": "string" + }, + "latest_releases": { + "type": "array", + "items": { + "$ref": "#/definitions/api.ReleaseInfo" + } + }, + "name": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "versions": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "api.LightwellPackageVersionCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellPackageVersionResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellPackageVersionResponse": { + "type": "object", + "properties": { + "content_type": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "group": { + "type": "string" + }, + "name": { + "type": "string" + }, + "release": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, "api.LightwellVulnerabilityCollectionMeta": { "type": "object", "properties": { @@ -6065,6 +6437,11 @@ const docTemplate = `{ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "type": "integer", + "readOnly": true + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "type": "boolean", @@ -6075,6 +6452,11 @@ const docTemplate = `{ "type": "string", "readOnly": true }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "type": "integer", + "readOnly": true + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "type": "string", @@ -6101,6 +6483,11 @@ const docTemplate = `{ "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "type": "integer", + "readOnly": true + }, "warnings": { "description": "Warnings to alert user of mismatched fields if there is an existing repo with the same URL", "type": "array", @@ -6395,6 +6782,11 @@ const docTemplate = `{ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "type": "integer", + "readOnly": true + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "type": "boolean", @@ -6405,6 +6797,11 @@ const docTemplate = `{ "type": "string", "readOnly": true }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "type": "integer", + "readOnly": true + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "type": "string", @@ -6430,6 +6827,11 @@ const docTemplate = `{ "version_count": { "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" + }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "type": "integer", + "readOnly": true } } }, diff --git a/api/openapi.json b/api/openapi.json index 9c53230b0..9ae7c6ea6 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -325,6 +325,55 @@ }, "type": "object" }, + "api.LightwellAdvisoryCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellAdvisoryResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellAdvisoryResponse": { + "properties": { + "advisory_id": { + "type": "string" + }, + "details": { + "type": "string" + }, + "fixed_versions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "package_name": { + "type": "string" + }, + "reference_urls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repository": { + "type": "string" + }, + "severity": { + "type": "string" + } + }, + "type": "object" + }, "api.LightwellCustomerIdsResponse": { "properties": { "data": { @@ -349,6 +398,101 @@ }, "type": "object" }, + "api.LightwellPackageCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellPackageResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellPackageResponse": { + "properties": { + "content_type": { + "type": "string" + }, + "group": { + "type": "string" + }, + "latest_releases": { + "items": { + "$ref": "#/components/schemas/api.ReleaseInfo" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "versions": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "api.LightwellPackageVersionCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellPackageVersionResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellPackageVersionResponse": { + "properties": { + "content_type": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "group": { + "type": "string" + }, + "name": { + "type": "string" + }, + "release": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, "api.LightwellVulnerabilityCollectionMeta": { "properties": { "blocked_count": { @@ -1292,6 +1436,11 @@ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "readOnly": true, + "type": "integer" + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "readOnly": true, @@ -1302,6 +1451,11 @@ "readOnly": true, "type": "string" }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "readOnly": true, + "type": "integer" + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "readOnly": true, @@ -1328,6 +1482,11 @@ "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "readOnly": true, + "type": "integer" + }, "warnings": { "description": "Warnings to alert user of mismatched fields if there is an existing repo with the same URL", "items": { @@ -1622,6 +1781,11 @@ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "readOnly": true, + "type": "integer" + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "readOnly": true, @@ -1632,6 +1796,11 @@ "readOnly": true, "type": "string" }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "readOnly": true, + "type": "integer" + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "readOnly": true, @@ -1657,6 +1826,11 @@ "version_count": { "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" + }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "readOnly": true, + "type": "integer" } }, "type": "object" @@ -3174,6 +3348,98 @@ ] } }, + "/lightwell/advisories": { + "get": { + "description": "List security advisories for Lightwell remediated packages with optional filtering.", + "operationId": "listLightwellAdvisories", + "parameters": [ + { + "description": "Filter by repository UUID", + "in": "query", + "name": "repository_uuid", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "package_name", + "schema": { + "type": "string" + } + }, + { + "description": "Minimum severity level (low, moderate, important, critical)", + "in": "query", + "name": "severity_min", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by CVE ID (exact match)", + "in": "query", + "name": "cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellAdvisoryCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Advisories", + "tags": [ + "lightwell" + ] + } + }, "/lightwell/beacon/vulnerabilities/": { "get": { "description": "List Lightwell vulnerabilities for a customer, with filters, pagination, and aggregate counts.", @@ -3416,6 +3682,198 @@ ] } }, + "/lightwell/package_versions": { + "get": { + "description": "List individual package versions aggregated across all Lightwell repositories, with optional CVE-based filtering.", + "operationId": "listLightwellPackageVersions", + "parameters": [ + { + "description": "Filter by content type (maven, python, npm)", + "in": "query", + "name": "type", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "name", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by security level (validated, remediated)", + "in": "query", + "name": "security_level", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by repository name", + "in": "query", + "name": "repository", + "schema": { + "type": "string" + } + }, + { + "description": "Show only packages that resolve this CVE", + "in": "query", + "name": "resolves_cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Show only packages vulnerable to this CVE", + "in": "query", + "name": "vulnerable_to_cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellPackageVersionCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Package Versions (cross-repo)", + "tags": [ + "lightwell" + ] + } + }, + "/lightwell/packages": { + "get": { + "description": "List packages aggregated across all Lightwell repositories, with optional filtering by content type, name, and security level.", + "operationId": "listLightwellPackages", + "parameters": [ + { + "description": "Filter by content type (maven, python, npm)", + "in": "query", + "name": "type", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "name", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by security level (validated, remediated)", + "in": "query", + "name": "security_level", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellPackageCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Packages (cross-repo)", + "tags": [ + "lightwell" + ] + } + }, "/module_streams/search": { "post": { "description": "List modules and their streams for repositories", From 500fa1cf23eba31b2411c7db09ea201e8146d3da Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 26 Aug 2026 12:46:05 -0400 Subject: [PATCH 21/47] revert and delete duplicates sql files --- ...lightwell_advisory_severity_order.down.sql | 7 ------- ...d_lightwell_advisory_severity_order.up.sql | 20 ------------------- ...twell_vulnerability_duplicate_of.down.sql} | 0 ...ghtwell_vulnerability_duplicate_of.up.sql} | 0 4 files changed, 27 deletions(-) delete mode 100644 db/migrations/20260818120000_add_lightwell_advisory_severity_order.down.sql delete mode 100644 db/migrations/20260818120000_add_lightwell_advisory_severity_order.up.sql rename db/migrations/{20260818130000_add_lightwell_vulnerability_duplicate_of.down.sql => 20260818120000_add_lightwell_vulnerability_duplicate_of.down.sql} (100%) rename db/migrations/{20260818130000_add_lightwell_vulnerability_duplicate_of.up.sql => 20260818120000_add_lightwell_vulnerability_duplicate_of.up.sql} (100%) diff --git a/db/migrations/20260818120000_add_lightwell_advisory_severity_order.down.sql b/db/migrations/20260818120000_add_lightwell_advisory_severity_order.down.sql deleted file mode 100644 index 53b3f3983..000000000 --- a/db/migrations/20260818120000_add_lightwell_advisory_severity_order.down.sql +++ /dev/null @@ -1,7 +0,0 @@ -BEGIN; - -DROP INDEX IF EXISTS idx_lightwell_advisories_package_name; -DROP INDEX IF EXISTS idx_lightwell_advisories_severity_order; -ALTER TABLE lightwell_advisories DROP COLUMN IF EXISTS severity_order; - -COMMIT; diff --git a/db/migrations/20260818120000_add_lightwell_advisory_severity_order.up.sql b/db/migrations/20260818120000_add_lightwell_advisory_severity_order.up.sql deleted file mode 100644 index 544d380e0..000000000 --- a/db/migrations/20260818120000_add_lightwell_advisory_severity_order.up.sql +++ /dev/null @@ -1,20 +0,0 @@ -BEGIN; - -ALTER TABLE lightwell_advisories - ADD COLUMN IF NOT EXISTS severity_order SMALLINT NOT NULL DEFAULT 0; - -UPDATE lightwell_advisories SET severity_order = CASE - WHEN severity = 'critical' THEN 4 - WHEN severity = 'important' THEN 3 - WHEN severity = 'moderate' THEN 2 - WHEN severity = 'low' THEN 1 - ELSE 0 -END; - -CREATE INDEX IF NOT EXISTS idx_lightwell_advisories_severity_order - ON lightwell_advisories (severity_order); - -CREATE INDEX IF NOT EXISTS idx_lightwell_advisories_package_name - ON lightwell_advisories (package_name); - -COMMIT; diff --git a/db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.down.sql b/db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.down.sql similarity index 100% rename from db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.down.sql rename to db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.down.sql diff --git a/db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.up.sql b/db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.up.sql similarity index 100% rename from db/migrations/20260818130000_add_lightwell_vulnerability_duplicate_of.up.sql rename to db/migrations/20260818120000_add_lightwell_vulnerability_duplicate_of.up.sql From 8aa9d3d6948b1ed165ef91600ee6ff1ee9afddaa Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 26 Aug 2026 14:48:18 -0400 Subject: [PATCH 22/47] update calls to use DAO package --- pkg/cache/cache_mock.go | 24 +- .../candlepin_client/candlepin_client_mock.go | 60 +- .../feature_service_client_mock.go | 6 +- pkg/clients/pulp_client/pulp_client_mock.go | 108 ++-- .../roadmap_client/roadmap_client_mock.go | 6 +- pkg/dao/dao_mock.go | 523 ++++++++++++------ pkg/dao/interfaces.go | 6 +- pkg/dao/lightwell_advisory.go | 112 +++- pkg/handler/api.go | 23 +- pkg/handler/lightwell_advisories.go | 96 +--- pkg/handler/lightwell_advisories_test.go | 144 ++--- pkg/handler/lightwell_packages.go | 33 +- pkg/handler/lightwell_packages_test.go | 9 +- pkg/handler/repositories.go | 12 +- pkg/tasks/client/client_mock.go | 4 +- pkg/tasks/queue/queue_mock.go | 24 +- 16 files changed, 674 insertions(+), 516 deletions(-) diff --git a/pkg/cache/cache_mock.go b/pkg/cache/cache_mock.go index 200cb4866..72a4b4e09 100644 --- a/pkg/cache/cache_mock.go +++ b/pkg/cache/cache_mock.go @@ -74,7 +74,7 @@ type MockCache_GetAccessList_Call struct { // GetAccessList is a helper method to define mock.On call // - ctx context.Context -func (_e *MockCache_Expecter) GetAccessList(ctx interface{}) *MockCache_GetAccessList_Call { +func (_e *MockCache_Expecter) GetAccessList(ctx any) *MockCache_GetAccessList_Call { return &MockCache_GetAccessList_Call{Call: _e.mock.On("GetAccessList", ctx)} } @@ -138,7 +138,7 @@ type MockCache_GetContentCounts_Call struct { // - ctx context.Context // - domainName string // - repoUUID string -func (_e *MockCache_Expecter) GetContentCounts(ctx interface{}, domainName interface{}, repoUUID interface{}) *MockCache_GetContentCounts_Call { +func (_e *MockCache_Expecter) GetContentCounts(ctx any, domainName any, repoUUID any) *MockCache_GetContentCounts_Call { return &MockCache_GetContentCounts_Call{Call: _e.mock.On("GetContentCounts", ctx, domainName, repoUUID)} } @@ -210,7 +210,7 @@ type MockCache_GetFeatureStatus_Call struct { // GetFeatureStatus is a helper method to define mock.On call // - ctx context.Context -func (_e *MockCache_Expecter) GetFeatureStatus(ctx interface{}) *MockCache_GetFeatureStatus_Call { +func (_e *MockCache_Expecter) GetFeatureStatus(ctx any) *MockCache_GetFeatureStatus_Call { return &MockCache_GetFeatureStatus_Call{Call: _e.mock.On("GetFeatureStatus", ctx)} } @@ -272,7 +272,7 @@ type MockCache_GetRoadmapAppstreams_Call struct { // GetRoadmapAppstreams is a helper method to define mock.On call // - ctx context.Context -func (_e *MockCache_Expecter) GetRoadmapAppstreams(ctx interface{}) *MockCache_GetRoadmapAppstreams_Call { +func (_e *MockCache_Expecter) GetRoadmapAppstreams(ctx any) *MockCache_GetRoadmapAppstreams_Call { return &MockCache_GetRoadmapAppstreams_Call{Call: _e.mock.On("GetRoadmapAppstreams", ctx)} } @@ -334,7 +334,7 @@ type MockCache_GetRoadmapRhelLifecycle_Call struct { // GetRoadmapRhelLifecycle is a helper method to define mock.On call // - ctx context.Context -func (_e *MockCache_Expecter) GetRoadmapRhelLifecycle(ctx interface{}) *MockCache_GetRoadmapRhelLifecycle_Call { +func (_e *MockCache_Expecter) GetRoadmapRhelLifecycle(ctx any) *MockCache_GetRoadmapRhelLifecycle_Call { return &MockCache_GetRoadmapRhelLifecycle_Call{Call: _e.mock.On("GetRoadmapRhelLifecycle", ctx)} } @@ -396,7 +396,7 @@ type MockCache_GetSubscriptionCheck_Call struct { // GetSubscriptionCheck is a helper method to define mock.On call // - ctx context.Context -func (_e *MockCache_Expecter) GetSubscriptionCheck(ctx interface{}) *MockCache_GetSubscriptionCheck_Call { +func (_e *MockCache_Expecter) GetSubscriptionCheck(ctx any) *MockCache_GetSubscriptionCheck_Call { return &MockCache_GetSubscriptionCheck_Call{Call: _e.mock.On("GetSubscriptionCheck", ctx)} } @@ -448,7 +448,7 @@ type MockCache_SetAccessList_Call struct { // SetAccessList is a helper method to define mock.On call // - ctx context.Context // - accessList rbac.AccessList -func (_e *MockCache_Expecter) SetAccessList(ctx interface{}, accessList interface{}) *MockCache_SetAccessList_Call { +func (_e *MockCache_Expecter) SetAccessList(ctx any, accessList any) *MockCache_SetAccessList_Call { return &MockCache_SetAccessList_Call{Call: _e.mock.On("SetAccessList", ctx, accessList)} } @@ -507,7 +507,7 @@ type MockCache_SetContentCounts_Call struct { // - domainName string // - repoUUID string // - contentCounts RepoContentCount -func (_e *MockCache_Expecter) SetContentCounts(ctx interface{}, domainName interface{}, repoUUID interface{}, contentCounts interface{}) *MockCache_SetContentCounts_Call { +func (_e *MockCache_Expecter) SetContentCounts(ctx any, domainName any, repoUUID any, contentCounts any) *MockCache_SetContentCounts_Call { return &MockCache_SetContentCounts_Call{Call: _e.mock.On("SetContentCounts", ctx, domainName, repoUUID, contentCounts)} } @@ -574,7 +574,7 @@ type MockCache_SetFeatureStatus_Call struct { // SetFeatureStatus is a helper method to define mock.On call // - ctx context.Context // - response api.FeatureStatus -func (_e *MockCache_Expecter) SetFeatureStatus(ctx interface{}, response interface{}) *MockCache_SetFeatureStatus_Call { +func (_e *MockCache_Expecter) SetFeatureStatus(ctx any, response any) *MockCache_SetFeatureStatus_Call { return &MockCache_SetFeatureStatus_Call{Call: _e.mock.On("SetFeatureStatus", ctx, response)} } @@ -620,7 +620,7 @@ type MockCache_SetRoadmapAppstreams_Call struct { // SetRoadmapAppstreams is a helper method to define mock.On call // - ctx context.Context // - roadmapAppstreamsResponse []byte -func (_e *MockCache_Expecter) SetRoadmapAppstreams(ctx interface{}, roadmapAppstreamsResponse interface{}) *MockCache_SetRoadmapAppstreams_Call { +func (_e *MockCache_Expecter) SetRoadmapAppstreams(ctx any, roadmapAppstreamsResponse any) *MockCache_SetRoadmapAppstreams_Call { return &MockCache_SetRoadmapAppstreams_Call{Call: _e.mock.On("SetRoadmapAppstreams", ctx, roadmapAppstreamsResponse)} } @@ -666,7 +666,7 @@ type MockCache_SetRoadmapRhelLifecycle_Call struct { // SetRoadmapRhelLifecycle is a helper method to define mock.On call // - ctx context.Context // - rhelLifecyleResponse []byte -func (_e *MockCache_Expecter) SetRoadmapRhelLifecycle(ctx interface{}, rhelLifecyleResponse interface{}) *MockCache_SetRoadmapRhelLifecycle_Call { +func (_e *MockCache_Expecter) SetRoadmapRhelLifecycle(ctx any, rhelLifecyleResponse any) *MockCache_SetRoadmapRhelLifecycle_Call { return &MockCache_SetRoadmapRhelLifecycle_Call{Call: _e.mock.On("SetRoadmapRhelLifecycle", ctx, rhelLifecyleResponse)} } @@ -723,7 +723,7 @@ type MockCache_SetSubscriptionCheck_Call struct { // SetSubscriptionCheck is a helper method to define mock.On call // - ctx context.Context // - response api.SubscriptionCheckResponse -func (_e *MockCache_Expecter) SetSubscriptionCheck(ctx interface{}, response interface{}) *MockCache_SetSubscriptionCheck_Call { +func (_e *MockCache_Expecter) SetSubscriptionCheck(ctx any, response any) *MockCache_SetSubscriptionCheck_Call { return &MockCache_SetSubscriptionCheck_Call{Call: _e.mock.On("SetSubscriptionCheck", ctx, response)} } diff --git a/pkg/clients/candlepin_client/candlepin_client_mock.go b/pkg/clients/candlepin_client/candlepin_client_mock.go index f2a416c7d..be655dbe2 100644 --- a/pkg/clients/candlepin_client/candlepin_client_mock.go +++ b/pkg/clients/candlepin_client/candlepin_client_mock.go @@ -64,7 +64,7 @@ type MockCandlepinClient_AddContentBatchToProduct_Call struct { // - ctx context.Context // - orgID string // - contentIDs []string -func (_e *MockCandlepinClient_Expecter) AddContentBatchToProduct(ctx interface{}, orgID interface{}, contentIDs interface{}) *MockCandlepinClient_AddContentBatchToProduct_Call { +func (_e *MockCandlepinClient_Expecter) AddContentBatchToProduct(ctx any, orgID any, contentIDs any) *MockCandlepinClient_AddContentBatchToProduct_Call { return &MockCandlepinClient_AddContentBatchToProduct_Call{Call: _e.mock.On("AddContentBatchToProduct", ctx, orgID, contentIDs)} } @@ -128,7 +128,7 @@ type MockCandlepinClient_AssociateEnvironment_Call struct { // - orgID string // - templateName string // - consumerUuid string -func (_e *MockCandlepinClient_Expecter) AssociateEnvironment(ctx interface{}, orgID interface{}, templateName interface{}, consumerUuid interface{}) *MockCandlepinClient_AssociateEnvironment_Call { +func (_e *MockCandlepinClient_Expecter) AssociateEnvironment(ctx any, orgID any, templateName any, consumerUuid any) *MockCandlepinClient_AssociateEnvironment_Call { return &MockCandlepinClient_AssociateEnvironment_Call{Call: _e.mock.On("AssociateEnvironment", ctx, orgID, templateName, consumerUuid)} } @@ -207,7 +207,7 @@ type MockCandlepinClient_CreateConsumer_Call struct { // - ctx context.Context // - orgID string // - name string -func (_e *MockCandlepinClient_Expecter) CreateConsumer(ctx interface{}, orgID interface{}, name interface{}) *MockCandlepinClient_CreateConsumer_Call { +func (_e *MockCandlepinClient_Expecter) CreateConsumer(ctx any, orgID any, name any) *MockCandlepinClient_CreateConsumer_Call { return &MockCandlepinClient_CreateConsumer_Call{Call: _e.mock.On("CreateConsumer", ctx, orgID, name)} } @@ -270,7 +270,7 @@ type MockCandlepinClient_CreateContent_Call struct { // - ctx context.Context // - orgID string // - content caliri.ContentDTO -func (_e *MockCandlepinClient_Expecter) CreateContent(ctx interface{}, orgID interface{}, content interface{}) *MockCandlepinClient_CreateContent_Call { +func (_e *MockCandlepinClient_Expecter) CreateContent(ctx any, orgID any, content any) *MockCandlepinClient_CreateContent_Call { return &MockCandlepinClient_CreateContent_Call{Call: _e.mock.On("CreateContent", ctx, orgID, content)} } @@ -333,7 +333,7 @@ type MockCandlepinClient_CreateContentBatch_Call struct { // - ctx context.Context // - orgID string // - content []caliri.ContentDTO -func (_e *MockCandlepinClient_Expecter) CreateContentBatch(ctx interface{}, orgID interface{}, content interface{}) *MockCandlepinClient_CreateContentBatch_Call { +func (_e *MockCandlepinClient_Expecter) CreateContentBatch(ctx any, orgID any, content any) *MockCandlepinClient_CreateContentBatch_Call { return &MockCandlepinClient_CreateContentBatch_Call{Call: _e.mock.On("CreateContentBatch", ctx, orgID, content)} } @@ -409,7 +409,7 @@ type MockCandlepinClient_CreateEnvironment_Call struct { // - name string // - id string // - prefix string -func (_e *MockCandlepinClient_Expecter) CreateEnvironment(ctx interface{}, orgID interface{}, name interface{}, id interface{}, prefix interface{}) *MockCandlepinClient_CreateEnvironment_Call { +func (_e *MockCandlepinClient_Expecter) CreateEnvironment(ctx any, orgID any, name any, id any, prefix any) *MockCandlepinClient_CreateEnvironment_Call { return &MockCandlepinClient_CreateEnvironment_Call{Call: _e.mock.On("CreateEnvironment", ctx, orgID, name, id, prefix)} } @@ -480,7 +480,7 @@ type MockCandlepinClient_CreateOwner_Call struct { // CreateOwner is a helper method to define mock.On call // - ctx context.Context -func (_e *MockCandlepinClient_Expecter) CreateOwner(ctx interface{}) *MockCandlepinClient_CreateOwner_Call { +func (_e *MockCandlepinClient_Expecter) CreateOwner(ctx any) *MockCandlepinClient_CreateOwner_Call { return &MockCandlepinClient_CreateOwner_Call{Call: _e.mock.On("CreateOwner", ctx)} } @@ -541,7 +541,7 @@ type MockCandlepinClient_CreatePool_Call struct { // CreatePool is a helper method to define mock.On call // - ctx context.Context // - orgID string -func (_e *MockCandlepinClient_Expecter) CreatePool(ctx interface{}, orgID interface{}) *MockCandlepinClient_CreatePool_Call { +func (_e *MockCandlepinClient_Expecter) CreatePool(ctx any, orgID any) *MockCandlepinClient_CreatePool_Call { return &MockCandlepinClient_CreatePool_Call{Call: _e.mock.On("CreatePool", ctx, orgID)} } @@ -598,7 +598,7 @@ type MockCandlepinClient_CreateProduct_Call struct { // CreateProduct is a helper method to define mock.On call // - ctx context.Context // - orgID string -func (_e *MockCandlepinClient_Expecter) CreateProduct(ctx interface{}, orgID interface{}) *MockCandlepinClient_CreateProduct_Call { +func (_e *MockCandlepinClient_Expecter) CreateProduct(ctx any, orgID any) *MockCandlepinClient_CreateProduct_Call { return &MockCandlepinClient_CreateProduct_Call{Call: _e.mock.On("CreateProduct", ctx, orgID)} } @@ -655,7 +655,7 @@ type MockCandlepinClient_DeleteConsumer_Call struct { // DeleteConsumer is a helper method to define mock.On call // - ctx context.Context // - consumerUUID string -func (_e *MockCandlepinClient_Expecter) DeleteConsumer(ctx interface{}, consumerUUID interface{}) *MockCandlepinClient_DeleteConsumer_Call { +func (_e *MockCandlepinClient_Expecter) DeleteConsumer(ctx any, consumerUUID any) *MockCandlepinClient_DeleteConsumer_Call { return &MockCandlepinClient_DeleteConsumer_Call{Call: _e.mock.On("DeleteConsumer", ctx, consumerUUID)} } @@ -713,7 +713,7 @@ type MockCandlepinClient_DeleteContent_Call struct { // - ctx context.Context // - ownerKey string // - repoConfigUUID string -func (_e *MockCandlepinClient_Expecter) DeleteContent(ctx interface{}, ownerKey interface{}, repoConfigUUID interface{}) *MockCandlepinClient_DeleteContent_Call { +func (_e *MockCandlepinClient_Expecter) DeleteContent(ctx any, ownerKey any, repoConfigUUID any) *MockCandlepinClient_DeleteContent_Call { return &MockCandlepinClient_DeleteContent_Call{Call: _e.mock.On("DeleteContent", ctx, ownerKey, repoConfigUUID)} } @@ -775,7 +775,7 @@ type MockCandlepinClient_DeleteEnvironment_Call struct { // DeleteEnvironment is a helper method to define mock.On call // - ctx context.Context // - templateUUID string -func (_e *MockCandlepinClient_Expecter) DeleteEnvironment(ctx interface{}, templateUUID interface{}) *MockCandlepinClient_DeleteEnvironment_Call { +func (_e *MockCandlepinClient_Expecter) DeleteEnvironment(ctx any, templateUUID any) *MockCandlepinClient_DeleteEnvironment_Call { return &MockCandlepinClient_DeleteEnvironment_Call{Call: _e.mock.On("DeleteEnvironment", ctx, templateUUID)} } @@ -833,7 +833,7 @@ type MockCandlepinClient_DemoteContentFromEnvironment_Call struct { // - ctx context.Context // - templateUUID string // - customRepoConfigUUIDs []string -func (_e *MockCandlepinClient_Expecter) DemoteContentFromEnvironment(ctx interface{}, templateUUID interface{}, customRepoConfigUUIDs interface{}) *MockCandlepinClient_DemoteContentFromEnvironment_Call { +func (_e *MockCandlepinClient_Expecter) DemoteContentFromEnvironment(ctx any, templateUUID any, customRepoConfigUUIDs any) *MockCandlepinClient_DemoteContentFromEnvironment_Call { return &MockCandlepinClient_DemoteContentFromEnvironment_Call{Call: _e.mock.On("DemoteContentFromEnvironment", ctx, templateUUID, customRepoConfigUUIDs)} } @@ -906,7 +906,7 @@ type MockCandlepinClient_FetchConsumer_Call struct { // FetchConsumer is a helper method to define mock.On call // - ctx context.Context // - consumerUUID string -func (_e *MockCandlepinClient_Expecter) FetchConsumer(ctx interface{}, consumerUUID interface{}) *MockCandlepinClient_FetchConsumer_Call { +func (_e *MockCandlepinClient_Expecter) FetchConsumer(ctx any, consumerUUID any) *MockCandlepinClient_FetchConsumer_Call { return &MockCandlepinClient_FetchConsumer_Call{Call: _e.mock.On("FetchConsumer", ctx, consumerUUID)} } @@ -975,7 +975,7 @@ type MockCandlepinClient_FetchContent_Call struct { // - ctx context.Context // - orgID string // - repoConfigUUID string -func (_e *MockCandlepinClient_Expecter) FetchContent(ctx interface{}, orgID interface{}, repoConfigUUID interface{}) *MockCandlepinClient_FetchContent_Call { +func (_e *MockCandlepinClient_Expecter) FetchContent(ctx any, orgID any, repoConfigUUID any) *MockCandlepinClient_FetchContent_Call { return &MockCandlepinClient_FetchContent_Call{Call: _e.mock.On("FetchContent", ctx, orgID, repoConfigUUID)} } @@ -1048,7 +1048,7 @@ type MockCandlepinClient_FetchContentOverrides_Call struct { // FetchContentOverrides is a helper method to define mock.On call // - ctx context.Context // - templateUUID string -func (_e *MockCandlepinClient_Expecter) FetchContentOverrides(ctx interface{}, templateUUID interface{}) *MockCandlepinClient_FetchContentOverrides_Call { +func (_e *MockCandlepinClient_Expecter) FetchContentOverrides(ctx any, templateUUID any) *MockCandlepinClient_FetchContentOverrides_Call { return &MockCandlepinClient_FetchContentOverrides_Call{Call: _e.mock.On("FetchContentOverrides", ctx, templateUUID)} } @@ -1117,7 +1117,7 @@ type MockCandlepinClient_FetchContentOverridesForRepo_Call struct { // - ctx context.Context // - templateUUID string // - label string -func (_e *MockCandlepinClient_Expecter) FetchContentOverridesForRepo(ctx interface{}, templateUUID interface{}, label interface{}) *MockCandlepinClient_FetchContentOverridesForRepo_Call { +func (_e *MockCandlepinClient_Expecter) FetchContentOverridesForRepo(ctx any, templateUUID any, label any) *MockCandlepinClient_FetchContentOverridesForRepo_Call { return &MockCandlepinClient_FetchContentOverridesForRepo_Call{Call: _e.mock.On("FetchContentOverridesForRepo", ctx, templateUUID, label)} } @@ -1191,7 +1191,7 @@ type MockCandlepinClient_FetchContentsByLabel_Call struct { // - ctx context.Context // - orgID string // - labels []string -func (_e *MockCandlepinClient_Expecter) FetchContentsByLabel(ctx interface{}, orgID interface{}, labels interface{}) *MockCandlepinClient_FetchContentsByLabel_Call { +func (_e *MockCandlepinClient_Expecter) FetchContentsByLabel(ctx any, orgID any, labels any) *MockCandlepinClient_FetchContentsByLabel_Call { return &MockCandlepinClient_FetchContentsByLabel_Call{Call: _e.mock.On("FetchContentsByLabel", ctx, orgID, labels)} } @@ -1264,7 +1264,7 @@ type MockCandlepinClient_FetchEnvironment_Call struct { // FetchEnvironment is a helper method to define mock.On call // - ctx context.Context // - templateUUID string -func (_e *MockCandlepinClient_Expecter) FetchEnvironment(ctx interface{}, templateUUID interface{}) *MockCandlepinClient_FetchEnvironment_Call { +func (_e *MockCandlepinClient_Expecter) FetchEnvironment(ctx any, templateUUID any) *MockCandlepinClient_FetchEnvironment_Call { return &MockCandlepinClient_FetchEnvironment_Call{Call: _e.mock.On("FetchEnvironment", ctx, templateUUID)} } @@ -1332,7 +1332,7 @@ type MockCandlepinClient_FetchPool_Call struct { // FetchPool is a helper method to define mock.On call // - ctx context.Context // - orgID string -func (_e *MockCandlepinClient_Expecter) FetchPool(ctx interface{}, orgID interface{}) *MockCandlepinClient_FetchPool_Call { +func (_e *MockCandlepinClient_Expecter) FetchPool(ctx any, orgID any) *MockCandlepinClient_FetchPool_Call { return &MockCandlepinClient_FetchPool_Call{Call: _e.mock.On("FetchPool", ctx, orgID)} } @@ -1401,7 +1401,7 @@ type MockCandlepinClient_FetchProduct_Call struct { // - ctx context.Context // - orgID string // - productID string -func (_e *MockCandlepinClient_Expecter) FetchProduct(ctx interface{}, orgID interface{}, productID interface{}) *MockCandlepinClient_FetchProduct_Call { +func (_e *MockCandlepinClient_Expecter) FetchProduct(ctx any, orgID any, productID any) *MockCandlepinClient_FetchProduct_Call { return &MockCandlepinClient_FetchProduct_Call{Call: _e.mock.On("FetchProduct", ctx, orgID, productID)} } @@ -1463,7 +1463,7 @@ type MockCandlepinClient_ImportManifest_Call struct { // ImportManifest is a helper method to define mock.On call // - ctx context.Context // - filename string -func (_e *MockCandlepinClient_Expecter) ImportManifest(ctx interface{}, filename interface{}) *MockCandlepinClient_ImportManifest_Call { +func (_e *MockCandlepinClient_Expecter) ImportManifest(ctx any, filename any) *MockCandlepinClient_ImportManifest_Call { return &MockCandlepinClient_ImportManifest_Call{Call: _e.mock.On("ImportManifest", ctx, filename)} } @@ -1539,7 +1539,7 @@ type MockCandlepinClient_ListContents_Call struct { // ListContents is a helper method to define mock.On call // - ctx context.Context // - orgID string -func (_e *MockCandlepinClient_Expecter) ListContents(ctx interface{}, orgID interface{}) *MockCandlepinClient_ListContents_Call { +func (_e *MockCandlepinClient_Expecter) ListContents(ctx any, orgID any) *MockCandlepinClient_ListContents_Call { return &MockCandlepinClient_ListContents_Call{Call: _e.mock.On("ListContents", ctx, orgID)} } @@ -1608,7 +1608,7 @@ type MockCandlepinClient_ListProducts_Call struct { // - ctx context.Context // - orgID string // - productIDs []string -func (_e *MockCandlepinClient_Expecter) ListProducts(ctx interface{}, orgID interface{}, productIDs interface{}) *MockCandlepinClient_ListProducts_Call { +func (_e *MockCandlepinClient_Expecter) ListProducts(ctx any, orgID any, productIDs any) *MockCandlepinClient_ListProducts_Call { return &MockCandlepinClient_ListProducts_Call{Call: _e.mock.On("ListProducts", ctx, orgID, productIDs)} } @@ -1671,7 +1671,7 @@ type MockCandlepinClient_PromoteContentToEnvironment_Call struct { // - ctx context.Context // - templateUUID string // - repoConfigUUIDs []string -func (_e *MockCandlepinClient_Expecter) PromoteContentToEnvironment(ctx interface{}, templateUUID interface{}, repoConfigUUIDs interface{}) *MockCandlepinClient_PromoteContentToEnvironment_Call { +func (_e *MockCandlepinClient_Expecter) PromoteContentToEnvironment(ctx any, templateUUID any, repoConfigUUIDs any) *MockCandlepinClient_PromoteContentToEnvironment_Call { return &MockCandlepinClient_PromoteContentToEnvironment_Call{Call: _e.mock.On("PromoteContentToEnvironment", ctx, templateUUID, repoConfigUUIDs)} } @@ -1734,7 +1734,7 @@ type MockCandlepinClient_RemoveContentFromProduct_Call struct { // - ctx context.Context // - orgID string // - repoConfigUUID string -func (_e *MockCandlepinClient_Expecter) RemoveContentFromProduct(ctx interface{}, orgID interface{}, repoConfigUUID interface{}) *MockCandlepinClient_RemoveContentFromProduct_Call { +func (_e *MockCandlepinClient_Expecter) RemoveContentFromProduct(ctx any, orgID any, repoConfigUUID any) *MockCandlepinClient_RemoveContentFromProduct_Call { return &MockCandlepinClient_RemoveContentFromProduct_Call{Call: _e.mock.On("RemoveContentFromProduct", ctx, orgID, repoConfigUUID)} } @@ -1797,7 +1797,7 @@ type MockCandlepinClient_RemoveContentOverrides_Call struct { // - ctx context.Context // - templateUUID string // - toRemove []caliri.ContentOverrideDTO -func (_e *MockCandlepinClient_Expecter) RemoveContentOverrides(ctx interface{}, templateUUID interface{}, toRemove interface{}) *MockCandlepinClient_RemoveContentOverrides_Call { +func (_e *MockCandlepinClient_Expecter) RemoveContentOverrides(ctx any, templateUUID any, toRemove any) *MockCandlepinClient_RemoveContentOverrides_Call { return &MockCandlepinClient_RemoveContentOverrides_Call{Call: _e.mock.On("RemoveContentOverrides", ctx, templateUUID, toRemove)} } @@ -1871,7 +1871,7 @@ type MockCandlepinClient_RenameEnvironment_Call struct { // - ctx context.Context // - templateUUID string // - name string -func (_e *MockCandlepinClient_Expecter) RenameEnvironment(ctx interface{}, templateUUID interface{}, name interface{}) *MockCandlepinClient_RenameEnvironment_Call { +func (_e *MockCandlepinClient_Expecter) RenameEnvironment(ctx any, templateUUID any, name any) *MockCandlepinClient_RenameEnvironment_Call { return &MockCandlepinClient_RenameEnvironment_Call{Call: _e.mock.On("RenameEnvironment", ctx, templateUUID, name)} } @@ -1935,7 +1935,7 @@ type MockCandlepinClient_UpdateContent_Call struct { // - orgID string // - repoConfigUUID string // - content caliri.ContentDTO -func (_e *MockCandlepinClient_Expecter) UpdateContent(ctx interface{}, orgID interface{}, repoConfigUUID interface{}, content interface{}) *MockCandlepinClient_UpdateContent_Call { +func (_e *MockCandlepinClient_Expecter) UpdateContent(ctx any, orgID any, repoConfigUUID any, content any) *MockCandlepinClient_UpdateContent_Call { return &MockCandlepinClient_UpdateContent_Call{Call: _e.mock.On("UpdateContent", ctx, orgID, repoConfigUUID, content)} } @@ -2003,7 +2003,7 @@ type MockCandlepinClient_UpdateContentOverrides_Call struct { // - ctx context.Context // - templateUUID string // - dtos []caliri.ContentOverrideDTO -func (_e *MockCandlepinClient_Expecter) UpdateContentOverrides(ctx interface{}, templateUUID interface{}, dtos interface{}) *MockCandlepinClient_UpdateContentOverrides_Call { +func (_e *MockCandlepinClient_Expecter) UpdateContentOverrides(ctx any, templateUUID any, dtos any) *MockCandlepinClient_UpdateContentOverrides_Call { return &MockCandlepinClient_UpdateContentOverrides_Call{Call: _e.mock.On("UpdateContentOverrides", ctx, templateUUID, dtos)} } diff --git a/pkg/clients/feature_service_client/feature_service_client_mock.go b/pkg/clients/feature_service_client/feature_service_client_mock.go index 5e92862e1..df34598e1 100644 --- a/pkg/clients/feature_service_client/feature_service_client_mock.go +++ b/pkg/clients/feature_service_client/feature_service_client_mock.go @@ -74,7 +74,7 @@ type MockFeatureServiceClient_GetEntitledFeatures_Call struct { // GetEntitledFeatures is a helper method to define mock.On call // - ctx context.Context // - orgID string -func (_e *MockFeatureServiceClient_Expecter) GetEntitledFeatures(ctx interface{}, orgID interface{}) *MockFeatureServiceClient_GetEntitledFeatures_Call { +func (_e *MockFeatureServiceClient_Expecter) GetEntitledFeatures(ctx any, orgID any) *MockFeatureServiceClient_GetEntitledFeatures_Call { return &MockFeatureServiceClient_GetEntitledFeatures_Call{Call: _e.mock.On("GetEntitledFeatures", ctx, orgID)} } @@ -146,7 +146,7 @@ type MockFeatureServiceClient_GetFeatureStatusByOrgID_Call struct { // GetFeatureStatusByOrgID is a helper method to define mock.On call // - ctx context.Context // - orgID string -func (_e *MockFeatureServiceClient_Expecter) GetFeatureStatusByOrgID(ctx interface{}, orgID interface{}) *MockFeatureServiceClient_GetFeatureStatusByOrgID_Call { +func (_e *MockFeatureServiceClient_Expecter) GetFeatureStatusByOrgID(ctx any, orgID any) *MockFeatureServiceClient_GetFeatureStatusByOrgID_Call { return &MockFeatureServiceClient_GetFeatureStatusByOrgID_Call{Call: _e.mock.On("GetFeatureStatusByOrgID", ctx, orgID)} } @@ -217,7 +217,7 @@ type MockFeatureServiceClient_ListFeatures_Call struct { // ListFeatures is a helper method to define mock.On call // - ctx context.Context -func (_e *MockFeatureServiceClient_Expecter) ListFeatures(ctx interface{}) *MockFeatureServiceClient_ListFeatures_Call { +func (_e *MockFeatureServiceClient_Expecter) ListFeatures(ctx any) *MockFeatureServiceClient_ListFeatures_Call { return &MockFeatureServiceClient_ListFeatures_Call{Call: _e.mock.On("ListFeatures", ctx)} } diff --git a/pkg/clients/pulp_client/pulp_client_mock.go b/pkg/clients/pulp_client/pulp_client_mock.go index cc118035d..cd7320fb8 100644 --- a/pkg/clients/pulp_client/pulp_client_mock.go +++ b/pkg/clients/pulp_client/pulp_client_mock.go @@ -73,7 +73,7 @@ type MockPulpGlobalClient_CancelTask_Call struct { // CancelTask is a helper method to define mock.On call // - ctx context.Context // - taskHref string -func (_e *MockPulpGlobalClient_Expecter) CancelTask(ctx interface{}, taskHref interface{}) *MockPulpGlobalClient_CancelTask_Call { +func (_e *MockPulpGlobalClient_Expecter) CancelTask(ctx any, taskHref any) *MockPulpGlobalClient_CancelTask_Call { return &MockPulpGlobalClient_CancelTask_Call{Call: _e.mock.On("CancelTask", ctx, taskHref)} } @@ -192,7 +192,7 @@ type MockPulpGlobalClient_GetTask_Call struct { // GetTask is a helper method to define mock.On call // - ctx context.Context // - taskHref string -func (_e *MockPulpGlobalClient_Expecter) GetTask(ctx interface{}, taskHref interface{}) *MockPulpGlobalClient_GetTask_Call { +func (_e *MockPulpGlobalClient_Expecter) GetTask(ctx any, taskHref any) *MockPulpGlobalClient_GetTask_Call { return &MockPulpGlobalClient_GetTask_Call{Call: _e.mock.On("GetTask", ctx, taskHref)} } @@ -248,7 +248,7 @@ type MockPulpGlobalClient_Livez_Call struct { // Livez is a helper method to define mock.On call // - ctx context.Context -func (_e *MockPulpGlobalClient_Expecter) Livez(ctx interface{}) *MockPulpGlobalClient_Livez_Call { +func (_e *MockPulpGlobalClient_Expecter) Livez(ctx any) *MockPulpGlobalClient_Livez_Call { return &MockPulpGlobalClient_Livez_Call{Call: _e.mock.On("Livez", ctx)} } @@ -309,7 +309,7 @@ type MockPulpGlobalClient_LookupDomain_Call struct { // LookupDomain is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpGlobalClient_Expecter) LookupDomain(ctx interface{}, name interface{}) *MockPulpGlobalClient_LookupDomain_Call { +func (_e *MockPulpGlobalClient_Expecter) LookupDomain(ctx any, name any) *MockPulpGlobalClient_LookupDomain_Call { return &MockPulpGlobalClient_LookupDomain_Call{Call: _e.mock.On("LookupDomain", ctx, name)} } @@ -375,7 +375,7 @@ type MockPulpGlobalClient_LookupOrCreateDomain_Call struct { // LookupOrCreateDomain is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpGlobalClient_Expecter) LookupOrCreateDomain(ctx interface{}, name interface{}) *MockPulpGlobalClient_LookupOrCreateDomain_Call { +func (_e *MockPulpGlobalClient_Expecter) LookupOrCreateDomain(ctx any, name any) *MockPulpGlobalClient_LookupOrCreateDomain_Call { return &MockPulpGlobalClient_LookupOrCreateDomain_Call{Call: _e.mock.On("LookupOrCreateDomain", ctx, name)} } @@ -443,7 +443,7 @@ type MockPulpGlobalClient_PollTask_Call struct { // PollTask is a helper method to define mock.On call // - ctx context.Context // - taskHref string -func (_e *MockPulpGlobalClient_Expecter) PollTask(ctx interface{}, taskHref interface{}) *MockPulpGlobalClient_PollTask_Call { +func (_e *MockPulpGlobalClient_Expecter) PollTask(ctx any, taskHref any) *MockPulpGlobalClient_PollTask_Call { return &MockPulpGlobalClient_PollTask_Call{Call: _e.mock.On("PollTask", ctx, taskHref)} } @@ -502,7 +502,7 @@ type MockPulpGlobalClient_SetDomainLabel_Call struct { // - pulpHref string // - key string // - value string -func (_e *MockPulpGlobalClient_Expecter) SetDomainLabel(ctx interface{}, pulpHref interface{}, key interface{}, value interface{}) *MockPulpGlobalClient_SetDomainLabel_Call { +func (_e *MockPulpGlobalClient_Expecter) SetDomainLabel(ctx any, pulpHref any, key any, value any) *MockPulpGlobalClient_SetDomainLabel_Call { return &MockPulpGlobalClient_SetDomainLabel_Call{Call: _e.mock.On("SetDomainLabel", ctx, pulpHref, key, value)} } @@ -569,7 +569,7 @@ type MockPulpGlobalClient_UpdateDomainIfNeeded_Call struct { // UpdateDomainIfNeeded is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpGlobalClient_Expecter) UpdateDomainIfNeeded(ctx interface{}, name interface{}) *MockPulpGlobalClient_UpdateDomainIfNeeded_Call { +func (_e *MockPulpGlobalClient_Expecter) UpdateDomainIfNeeded(ctx any, name any) *MockPulpGlobalClient_UpdateDomainIfNeeded_Call { return &MockPulpGlobalClient_UpdateDomainIfNeeded_Call{Call: _e.mock.On("UpdateDomainIfNeeded", ctx, name)} } @@ -662,7 +662,7 @@ type MockPulpClient_CancelTask_Call struct { // CancelTask is a helper method to define mock.On call // - ctx context.Context // - taskHref string -func (_e *MockPulpClient_Expecter) CancelTask(ctx interface{}, taskHref interface{}) *MockPulpClient_CancelTask_Call { +func (_e *MockPulpClient_Expecter) CancelTask(ctx any, taskHref any) *MockPulpClient_CancelTask_Call { return &MockPulpClient_CancelTask_Call{Call: _e.mock.On("CancelTask", ctx, taskHref)} } @@ -728,7 +728,7 @@ type MockPulpClient_CreateOrUpdateFeatureGuard_Call struct { // CreateOrUpdateFeatureGuard is a helper method to define mock.On call // - ctx context.Context // - featureName string -func (_e *MockPulpClient_Expecter) CreateOrUpdateFeatureGuard(ctx interface{}, featureName interface{}) *MockPulpClient_CreateOrUpdateFeatureGuard_Call { +func (_e *MockPulpClient_Expecter) CreateOrUpdateFeatureGuard(ctx any, featureName any) *MockPulpClient_CreateOrUpdateFeatureGuard_Call { return &MockPulpClient_CreateOrUpdateFeatureGuard_Call{Call: _e.mock.On("CreateOrUpdateFeatureGuard", ctx, featureName)} } @@ -794,7 +794,7 @@ type MockPulpClient_CreateOrUpdateGuardsForOrg_Call struct { // CreateOrUpdateGuardsForOrg is a helper method to define mock.On call // - ctx context.Context // - orgId string -func (_e *MockPulpClient_Expecter) CreateOrUpdateGuardsForOrg(ctx interface{}, orgId interface{}) *MockPulpClient_CreateOrUpdateGuardsForOrg_Call { +func (_e *MockPulpClient_Expecter) CreateOrUpdateGuardsForOrg(ctx any, orgId any) *MockPulpClient_CreateOrUpdateGuardsForOrg_Call { return &MockPulpClient_CreateOrUpdateGuardsForOrg_Call{Call: _e.mock.On("CreateOrUpdateGuardsForOrg", ctx, orgId)} } @@ -860,7 +860,7 @@ type MockPulpClient_CreateOrUpdateGuardsForRhelRepo_Call struct { // CreateOrUpdateGuardsForRhelRepo is a helper method to define mock.On call // - ctx context.Context // - featureName string -func (_e *MockPulpClient_Expecter) CreateOrUpdateGuardsForRhelRepo(ctx interface{}, featureName interface{}) *MockPulpClient_CreateOrUpdateGuardsForRhelRepo_Call { +func (_e *MockPulpClient_Expecter) CreateOrUpdateGuardsForRhelRepo(ctx any, featureName any) *MockPulpClient_CreateOrUpdateGuardsForRhelRepo_Call { return &MockPulpClient_CreateOrUpdateGuardsForRhelRepo_Call{Call: _e.mock.On("CreateOrUpdateGuardsForRhelRepo", ctx, featureName)} } @@ -927,7 +927,7 @@ type MockPulpClient_CreatePackage_Call struct { // - ctx context.Context // - artifactHref *string // - uploadHref *string -func (_e *MockPulpClient_Expecter) CreatePackage(ctx interface{}, artifactHref interface{}, uploadHref interface{}) *MockPulpClient_CreatePackage_Call { +func (_e *MockPulpClient_Expecter) CreatePackage(ctx any, artifactHref any, uploadHref any) *MockPulpClient_CreatePackage_Call { return &MockPulpClient_CreatePackage_Call{Call: _e.mock.On("CreatePackage", ctx, artifactHref, uploadHref)} } @@ -1003,7 +1003,7 @@ type MockPulpClient_CreateRpmDistribution_Call struct { // - name string // - basePath string // - contentGuardHref *string -func (_e *MockPulpClient_Expecter) CreateRpmDistribution(ctx interface{}, publicationHref interface{}, name interface{}, basePath interface{}, contentGuardHref interface{}) *MockPulpClient_CreateRpmDistribution_Call { +func (_e *MockPulpClient_Expecter) CreateRpmDistribution(ctx any, publicationHref any, name any, basePath any, contentGuardHref any) *MockPulpClient_CreateRpmDistribution_Call { return &MockPulpClient_CreateRpmDistribution_Call{Call: _e.mock.On("CreateRpmDistribution", ctx, publicationHref, name, basePath, contentGuardHref)} } @@ -1086,7 +1086,7 @@ type MockPulpClient_CreateRpmPublication_Call struct { // CreateRpmPublication is a helper method to define mock.On call // - ctx context.Context // - versionHref string -func (_e *MockPulpClient_Expecter) CreateRpmPublication(ctx interface{}, versionHref interface{}) *MockPulpClient_CreateRpmPublication_Call { +func (_e *MockPulpClient_Expecter) CreateRpmPublication(ctx any, versionHref any) *MockPulpClient_CreateRpmPublication_Call { return &MockPulpClient_CreateRpmPublication_Call{Call: _e.mock.On("CreateRpmPublication", ctx, versionHref)} } @@ -1158,7 +1158,7 @@ type MockPulpClient_CreateRpmRemote_Call struct { // - clientCert *string // - clientKey *string // - caCert *string -func (_e *MockPulpClient_Expecter) CreateRpmRemote(ctx interface{}, name interface{}, url interface{}, clientCert interface{}, clientKey interface{}, caCert interface{}) *MockPulpClient_CreateRpmRemote_Call { +func (_e *MockPulpClient_Expecter) CreateRpmRemote(ctx any, name any, url any, clientCert any, clientKey any, caCert any) *MockPulpClient_CreateRpmRemote_Call { return &MockPulpClient_CreateRpmRemote_Call{Call: _e.mock.On("CreateRpmRemote", ctx, name, url, clientCert, clientKey, caCert)} } @@ -1247,7 +1247,7 @@ type MockPulpClient_CreateRpmRepository_Call struct { // - ctx context.Context // - uuid string // - rpmRemotePulpRef *string -func (_e *MockPulpClient_Expecter) CreateRpmRepository(ctx interface{}, uuid interface{}, rpmRemotePulpRef interface{}) *MockPulpClient_CreateRpmRepository_Call { +func (_e *MockPulpClient_Expecter) CreateRpmRepository(ctx any, uuid any, rpmRemotePulpRef any) *MockPulpClient_CreateRpmRepository_Call { return &MockPulpClient_CreateRpmRepository_Call{Call: _e.mock.On("CreateRpmRepository", ctx, uuid, rpmRemotePulpRef)} } @@ -1326,7 +1326,7 @@ type MockPulpClient_CreateUpload_Call struct { // CreateUpload is a helper method to define mock.On call // - ctx context.Context // - size int64 -func (_e *MockPulpClient_Expecter) CreateUpload(ctx interface{}, size interface{}) *MockPulpClient_CreateUpload_Call { +func (_e *MockPulpClient_Expecter) CreateUpload(ctx any, size any) *MockPulpClient_CreateUpload_Call { return &MockPulpClient_CreateUpload_Call{Call: _e.mock.On("CreateUpload", ctx, size)} } @@ -1394,7 +1394,7 @@ type MockPulpClient_DeleteRpmDistribution_Call struct { // DeleteRpmDistribution is a helper method to define mock.On call // - ctx context.Context // - rpmDistributionHref string -func (_e *MockPulpClient_Expecter) DeleteRpmDistribution(ctx interface{}, rpmDistributionHref interface{}) *MockPulpClient_DeleteRpmDistribution_Call { +func (_e *MockPulpClient_Expecter) DeleteRpmDistribution(ctx any, rpmDistributionHref any) *MockPulpClient_DeleteRpmDistribution_Call { return &MockPulpClient_DeleteRpmDistribution_Call{Call: _e.mock.On("DeleteRpmDistribution", ctx, rpmDistributionHref)} } @@ -1460,7 +1460,7 @@ type MockPulpClient_DeleteRpmRemote_Call struct { // DeleteRpmRemote is a helper method to define mock.On call // - ctx context.Context // - pulpHref string -func (_e *MockPulpClient_Expecter) DeleteRpmRemote(ctx interface{}, pulpHref interface{}) *MockPulpClient_DeleteRpmRemote_Call { +func (_e *MockPulpClient_Expecter) DeleteRpmRemote(ctx any, pulpHref any) *MockPulpClient_DeleteRpmRemote_Call { return &MockPulpClient_DeleteRpmRemote_Call{Call: _e.mock.On("DeleteRpmRemote", ctx, pulpHref)} } @@ -1526,7 +1526,7 @@ type MockPulpClient_DeleteRpmRepository_Call struct { // DeleteRpmRepository is a helper method to define mock.On call // - ctx context.Context // - rpmRepositoryHref string -func (_e *MockPulpClient_Expecter) DeleteRpmRepository(ctx interface{}, rpmRepositoryHref interface{}) *MockPulpClient_DeleteRpmRepository_Call { +func (_e *MockPulpClient_Expecter) DeleteRpmRepository(ctx any, rpmRepositoryHref any) *MockPulpClient_DeleteRpmRepository_Call { return &MockPulpClient_DeleteRpmRepository_Call{Call: _e.mock.On("DeleteRpmRepository", ctx, rpmRepositoryHref)} } @@ -1594,7 +1594,7 @@ type MockPulpClient_DeleteRpmRepositoryVersion_Call struct { // DeleteRpmRepositoryVersion is a helper method to define mock.On call // - ctx context.Context // - href string -func (_e *MockPulpClient_Expecter) DeleteRpmRepositoryVersion(ctx interface{}, href interface{}) *MockPulpClient_DeleteRpmRepositoryVersion_Call { +func (_e *MockPulpClient_Expecter) DeleteRpmRepositoryVersion(ctx any, href any) *MockPulpClient_DeleteRpmRepositoryVersion_Call { return &MockPulpClient_DeleteRpmRepositoryVersion_Call{Call: _e.mock.On("DeleteRpmRepositoryVersion", ctx, href)} } @@ -1660,7 +1660,7 @@ type MockPulpClient_DeleteUpload_Call struct { // DeleteUpload is a helper method to define mock.On call // - ctx context.Context // - uploadHref string -func (_e *MockPulpClient_Expecter) DeleteUpload(ctx interface{}, uploadHref interface{}) *MockPulpClient_DeleteUpload_Call { +func (_e *MockPulpClient_Expecter) DeleteUpload(ctx any, uploadHref any) *MockPulpClient_DeleteUpload_Call { return &MockPulpClient_DeleteUpload_Call{Call: _e.mock.On("DeleteUpload", ctx, uploadHref)} } @@ -1728,7 +1728,7 @@ type MockPulpClient_FindDistributionByPath_Call struct { // FindDistributionByPath is a helper method to define mock.On call // - ctx context.Context // - path string -func (_e *MockPulpClient_Expecter) FindDistributionByPath(ctx interface{}, path interface{}) *MockPulpClient_FindDistributionByPath_Call { +func (_e *MockPulpClient_Expecter) FindDistributionByPath(ctx any, path any) *MockPulpClient_FindDistributionByPath_Call { return &MockPulpClient_FindDistributionByPath_Call{Call: _e.mock.On("FindDistributionByPath", ctx, path)} } @@ -1796,7 +1796,7 @@ type MockPulpClient_FindGenericDistributionByBasePath_Call struct { // FindGenericDistributionByBasePath is a helper method to define mock.On call // - ctx context.Context // - basePath string -func (_e *MockPulpClient_Expecter) FindGenericDistributionByBasePath(ctx interface{}, basePath interface{}) *MockPulpClient_FindGenericDistributionByBasePath_Call { +func (_e *MockPulpClient_Expecter) FindGenericDistributionByBasePath(ctx any, basePath any) *MockPulpClient_FindGenericDistributionByBasePath_Call { return &MockPulpClient_FindGenericDistributionByBasePath_Call{Call: _e.mock.On("FindGenericDistributionByBasePath", ctx, basePath)} } @@ -1864,7 +1864,7 @@ type MockPulpClient_FindGenericRepositoryByName_Call struct { // FindGenericRepositoryByName is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpClient_Expecter) FindGenericRepositoryByName(ctx interface{}, name interface{}) *MockPulpClient_FindGenericRepositoryByName_Call { +func (_e *MockPulpClient_Expecter) FindGenericRepositoryByName(ctx any, name any) *MockPulpClient_FindGenericRepositoryByName_Call { return &MockPulpClient_FindGenericRepositoryByName_Call{Call: _e.mock.On("FindGenericRepositoryByName", ctx, name)} } @@ -1932,7 +1932,7 @@ type MockPulpClient_FindRpmPublicationByVersion_Call struct { // FindRpmPublicationByVersion is a helper method to define mock.On call // - ctx context.Context // - versionHref string -func (_e *MockPulpClient_Expecter) FindRpmPublicationByVersion(ctx interface{}, versionHref interface{}) *MockPulpClient_FindRpmPublicationByVersion_Call { +func (_e *MockPulpClient_Expecter) FindRpmPublicationByVersion(ctx any, versionHref any) *MockPulpClient_FindRpmPublicationByVersion_Call { return &MockPulpClient_FindRpmPublicationByVersion_Call{Call: _e.mock.On("FindRpmPublicationByVersion", ctx, versionHref)} } @@ -2007,7 +2007,7 @@ type MockPulpClient_FinishUpload_Call struct { // - ctx context.Context // - uploadHref string // - sha256 string -func (_e *MockPulpClient_Expecter) FinishUpload(ctx interface{}, uploadHref interface{}, sha256 interface{}) *MockPulpClient_FinishUpload_Call { +func (_e *MockPulpClient_Expecter) FinishUpload(ctx any, uploadHref any, sha256 any) *MockPulpClient_FinishUpload_Call { return &MockPulpClient_FinishUpload_Call{Call: _e.mock.On("FinishUpload", ctx, uploadHref, sha256)} } @@ -2177,7 +2177,7 @@ type MockPulpClient_GetRpmRemoteByName_Call struct { // GetRpmRemoteByName is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpClient_Expecter) GetRpmRemoteByName(ctx interface{}, name interface{}) *MockPulpClient_GetRpmRemoteByName_Call { +func (_e *MockPulpClient_Expecter) GetRpmRemoteByName(ctx any, name any) *MockPulpClient_GetRpmRemoteByName_Call { return &MockPulpClient_GetRpmRemoteByName_Call{Call: _e.mock.On("GetRpmRemoteByName", ctx, name)} } @@ -2244,7 +2244,7 @@ type MockPulpClient_GetRpmRemoteList_Call struct { // GetRpmRemoteList is a helper method to define mock.On call // - ctx context.Context -func (_e *MockPulpClient_Expecter) GetRpmRemoteList(ctx interface{}) *MockPulpClient_GetRpmRemoteList_Call { +func (_e *MockPulpClient_Expecter) GetRpmRemoteList(ctx any) *MockPulpClient_GetRpmRemoteList_Call { return &MockPulpClient_GetRpmRemoteList_Call{Call: _e.mock.On("GetRpmRemoteList", ctx)} } @@ -2307,7 +2307,7 @@ type MockPulpClient_GetRpmRepositoryByName_Call struct { // GetRpmRepositoryByName is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpClient_Expecter) GetRpmRepositoryByName(ctx interface{}, name interface{}) *MockPulpClient_GetRpmRepositoryByName_Call { +func (_e *MockPulpClient_Expecter) GetRpmRepositoryByName(ctx any, name any) *MockPulpClient_GetRpmRepositoryByName_Call { return &MockPulpClient_GetRpmRepositoryByName_Call{Call: _e.mock.On("GetRpmRepositoryByName", ctx, name)} } @@ -2375,7 +2375,7 @@ type MockPulpClient_GetRpmRepositoryByRemote_Call struct { // GetRpmRepositoryByRemote is a helper method to define mock.On call // - ctx context.Context // - pulpHref string -func (_e *MockPulpClient_Expecter) GetRpmRepositoryByRemote(ctx interface{}, pulpHref interface{}) *MockPulpClient_GetRpmRepositoryByRemote_Call { +func (_e *MockPulpClient_Expecter) GetRpmRepositoryByRemote(ctx any, pulpHref any) *MockPulpClient_GetRpmRepositoryByRemote_Call { return &MockPulpClient_GetRpmRepositoryByRemote_Call{Call: _e.mock.On("GetRpmRepositoryByRemote", ctx, pulpHref)} } @@ -2443,7 +2443,7 @@ type MockPulpClient_GetRpmRepositoryVersion_Call struct { // GetRpmRepositoryVersion is a helper method to define mock.On call // - ctx context.Context // - href string -func (_e *MockPulpClient_Expecter) GetRpmRepositoryVersion(ctx interface{}, href interface{}) *MockPulpClient_GetRpmRepositoryVersion_Call { +func (_e *MockPulpClient_Expecter) GetRpmRepositoryVersion(ctx any, href any) *MockPulpClient_GetRpmRepositoryVersion_Call { return &MockPulpClient_GetRpmRepositoryVersion_Call{Call: _e.mock.On("GetRpmRepositoryVersion", ctx, href)} } @@ -2509,7 +2509,7 @@ type MockPulpClient_GetTask_Call struct { // GetTask is a helper method to define mock.On call // - ctx context.Context // - taskHref string -func (_e *MockPulpClient_Expecter) GetTask(ctx interface{}, taskHref interface{}) *MockPulpClient_GetTask_Call { +func (_e *MockPulpClient_Expecter) GetTask(ctx any, taskHref any) *MockPulpClient_GetTask_Call { return &MockPulpClient_GetTask_Call{Call: _e.mock.On("GetTask", ctx, taskHref)} } @@ -2576,7 +2576,7 @@ type MockPulpClient_ListDistributions_Call struct { // ListDistributions is a helper method to define mock.On call // - ctx context.Context -func (_e *MockPulpClient_Expecter) ListDistributions(ctx interface{}) *MockPulpClient_ListDistributions_Call { +func (_e *MockPulpClient_Expecter) ListDistributions(ctx any) *MockPulpClient_ListDistributions_Call { return &MockPulpClient_ListDistributions_Call{Call: _e.mock.On("ListDistributions", ctx)} } @@ -2639,7 +2639,7 @@ type MockPulpClient_ListVersionAllPackages_Call struct { // ListVersionAllPackages is a helper method to define mock.On call // - ctx context.Context // - versionHref string -func (_e *MockPulpClient_Expecter) ListVersionAllPackages(ctx interface{}, versionHref interface{}) *MockPulpClient_ListVersionAllPackages_Call { +func (_e *MockPulpClient_Expecter) ListVersionAllPackages(ctx any, versionHref any) *MockPulpClient_ListVersionAllPackages_Call { return &MockPulpClient_ListVersionAllPackages_Call{Call: _e.mock.On("ListVersionAllPackages", ctx, versionHref)} } @@ -2695,7 +2695,7 @@ type MockPulpClient_Livez_Call struct { // Livez is a helper method to define mock.On call // - ctx context.Context -func (_e *MockPulpClient_Expecter) Livez(ctx interface{}) *MockPulpClient_Livez_Call { +func (_e *MockPulpClient_Expecter) Livez(ctx any) *MockPulpClient_Livez_Call { return &MockPulpClient_Livez_Call{Call: _e.mock.On("Livez", ctx)} } @@ -2758,7 +2758,7 @@ type MockPulpClient_LookupArtifact_Call struct { // LookupArtifact is a helper method to define mock.On call // - ctx context.Context // - sha256sum string -func (_e *MockPulpClient_Expecter) LookupArtifact(ctx interface{}, sha256sum interface{}) *MockPulpClient_LookupArtifact_Call { +func (_e *MockPulpClient_Expecter) LookupArtifact(ctx any, sha256sum any) *MockPulpClient_LookupArtifact_Call { return &MockPulpClient_LookupArtifact_Call{Call: _e.mock.On("LookupArtifact", ctx, sha256sum)} } @@ -2824,7 +2824,7 @@ type MockPulpClient_LookupDomain_Call struct { // LookupDomain is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpClient_Expecter) LookupDomain(ctx interface{}, name interface{}) *MockPulpClient_LookupDomain_Call { +func (_e *MockPulpClient_Expecter) LookupDomain(ctx any, name any) *MockPulpClient_LookupDomain_Call { return &MockPulpClient_LookupDomain_Call{Call: _e.mock.On("LookupDomain", ctx, name)} } @@ -2890,7 +2890,7 @@ type MockPulpClient_LookupOrCreateDomain_Call struct { // LookupOrCreateDomain is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpClient_Expecter) LookupOrCreateDomain(ctx interface{}, name interface{}) *MockPulpClient_LookupOrCreateDomain_Call { +func (_e *MockPulpClient_Expecter) LookupOrCreateDomain(ctx any, name any) *MockPulpClient_LookupOrCreateDomain_Call { return &MockPulpClient_LookupOrCreateDomain_Call{Call: _e.mock.On("LookupOrCreateDomain", ctx, name)} } @@ -2958,7 +2958,7 @@ type MockPulpClient_LookupPackage_Call struct { // LookupPackage is a helper method to define mock.On call // - ctx context.Context // - sha256sum string -func (_e *MockPulpClient_Expecter) LookupPackage(ctx interface{}, sha256sum interface{}) *MockPulpClient_LookupPackage_Call { +func (_e *MockPulpClient_Expecter) LookupPackage(ctx any, sha256sum any) *MockPulpClient_LookupPackage_Call { return &MockPulpClient_LookupPackage_Call{Call: _e.mock.On("LookupPackage", ctx, sha256sum)} } @@ -3026,7 +3026,7 @@ type MockPulpClient_ModifyRpmRepositoryContent_Call struct { // - repoHref string // - contentHrefsToAdd []string // - contentHrefsToRemove []string -func (_e *MockPulpClient_Expecter) ModifyRpmRepositoryContent(ctx interface{}, repoHref interface{}, contentHrefsToAdd interface{}, contentHrefsToRemove interface{}) *MockPulpClient_ModifyRpmRepositoryContent_Call { +func (_e *MockPulpClient_Expecter) ModifyRpmRepositoryContent(ctx any, repoHref any, contentHrefsToAdd any, contentHrefsToRemove any) *MockPulpClient_ModifyRpmRepositoryContent_Call { return &MockPulpClient_ModifyRpmRepositoryContent_Call{Call: _e.mock.On("ModifyRpmRepositoryContent", ctx, repoHref, contentHrefsToAdd, contentHrefsToRemove)} } @@ -3101,7 +3101,7 @@ type MockPulpClient_OrphanCleanup_Call struct { // OrphanCleanup is a helper method to define mock.On call // - ctx context.Context -func (_e *MockPulpClient_Expecter) OrphanCleanup(ctx interface{}) *MockPulpClient_OrphanCleanup_Call { +func (_e *MockPulpClient_Expecter) OrphanCleanup(ctx any) *MockPulpClient_OrphanCleanup_Call { return &MockPulpClient_OrphanCleanup_Call{Call: _e.mock.On("OrphanCleanup", ctx)} } @@ -3164,7 +3164,7 @@ type MockPulpClient_PollTask_Call struct { // PollTask is a helper method to define mock.On call // - ctx context.Context // - taskHref string -func (_e *MockPulpClient_Expecter) PollTask(ctx interface{}, taskHref interface{}) *MockPulpClient_PollTask_Call { +func (_e *MockPulpClient_Expecter) PollTask(ctx any, taskHref any) *MockPulpClient_PollTask_Call { return &MockPulpClient_PollTask_Call{Call: _e.mock.On("PollTask", ctx, taskHref)} } @@ -3230,7 +3230,7 @@ type MockPulpClient_RepairRpmRepositoryVersion_Call struct { // RepairRpmRepositoryVersion is a helper method to define mock.On call // - ctx context.Context // - href string -func (_e *MockPulpClient_Expecter) RepairRpmRepositoryVersion(ctx interface{}, href interface{}) *MockPulpClient_RepairRpmRepositoryVersion_Call { +func (_e *MockPulpClient_Expecter) RepairRpmRepositoryVersion(ctx any, href any) *MockPulpClient_RepairRpmRepositoryVersion_Call { return &MockPulpClient_RepairRpmRepositoryVersion_Call{Call: _e.mock.On("RepairRpmRepositoryVersion", ctx, href)} } @@ -3298,7 +3298,7 @@ type MockPulpClient_ResolveRepositoryFromBasePath_Call struct { // ResolveRepositoryFromBasePath is a helper method to define mock.On call // - ctx context.Context // - basePath string -func (_e *MockPulpClient_Expecter) ResolveRepositoryFromBasePath(ctx interface{}, basePath interface{}) *MockPulpClient_ResolveRepositoryFromBasePath_Call { +func (_e *MockPulpClient_Expecter) ResolveRepositoryFromBasePath(ctx any, basePath any) *MockPulpClient_ResolveRepositoryFromBasePath_Call { return &MockPulpClient_ResolveRepositoryFromBasePath_Call{Call: _e.mock.On("ResolveRepositoryFromBasePath", ctx, basePath)} } @@ -3357,7 +3357,7 @@ type MockPulpClient_SetDomainLabel_Call struct { // - pulpHref string // - key string // - value string -func (_e *MockPulpClient_Expecter) SetDomainLabel(ctx interface{}, pulpHref interface{}, key interface{}, value interface{}) *MockPulpClient_SetDomainLabel_Call { +func (_e *MockPulpClient_Expecter) SetDomainLabel(ctx any, pulpHref any, key any, value any) *MockPulpClient_SetDomainLabel_Call { return &MockPulpClient_SetDomainLabel_Call{Call: _e.mock.On("SetDomainLabel", ctx, pulpHref, key, value)} } @@ -3434,7 +3434,7 @@ type MockPulpClient_Status_Call struct { // Status is a helper method to define mock.On call // - ctx context.Context -func (_e *MockPulpClient_Expecter) Status(ctx interface{}) *MockPulpClient_Status_Call { +func (_e *MockPulpClient_Expecter) Status(ctx any) *MockPulpClient_Status_Call { return &MockPulpClient_Status_Call{Call: _e.mock.On("Status", ctx)} } @@ -3496,7 +3496,7 @@ type MockPulpClient_SyncRpmRepository_Call struct { // - ctx context.Context // - rpmRpmRepositoryHref string // - remoteHref *string -func (_e *MockPulpClient_Expecter) SyncRpmRepository(ctx interface{}, rpmRpmRepositoryHref interface{}, remoteHref interface{}) *MockPulpClient_SyncRpmRepository_Call { +func (_e *MockPulpClient_Expecter) SyncRpmRepository(ctx any, rpmRpmRepositoryHref any, remoteHref any) *MockPulpClient_SyncRpmRepository_Call { return &MockPulpClient_SyncRpmRepository_Call{Call: _e.mock.On("SyncRpmRepository", ctx, rpmRpmRepositoryHref, remoteHref)} } @@ -3558,7 +3558,7 @@ type MockPulpClient_UpdateDomainIfNeeded_Call struct { // UpdateDomainIfNeeded is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpClient_Expecter) UpdateDomainIfNeeded(ctx interface{}, name interface{}) *MockPulpClient_UpdateDomainIfNeeded_Call { +func (_e *MockPulpClient_Expecter) UpdateDomainIfNeeded(ctx any, name any) *MockPulpClient_UpdateDomainIfNeeded_Call { return &MockPulpClient_UpdateDomainIfNeeded_Call{Call: _e.mock.On("UpdateDomainIfNeeded", ctx, name)} } @@ -3628,7 +3628,7 @@ type MockPulpClient_UpdateRpmDistribution_Call struct { // - distributionName string // - basePath string // - contentGuardHref *string -func (_e *MockPulpClient_Expecter) UpdateRpmDistribution(ctx interface{}, rpmDistributionHref interface{}, rpmPublicationHref interface{}, distributionName interface{}, basePath interface{}, contentGuardHref interface{}) *MockPulpClient_UpdateRpmDistribution_Call { +func (_e *MockPulpClient_Expecter) UpdateRpmDistribution(ctx any, rpmDistributionHref any, rpmPublicationHref any, distributionName any, basePath any, contentGuardHref any) *MockPulpClient_UpdateRpmDistribution_Call { return &MockPulpClient_UpdateRpmDistribution_Call{Call: _e.mock.On("UpdateRpmDistribution", ctx, rpmDistributionHref, rpmPublicationHref, distributionName, basePath, contentGuardHref)} } @@ -3718,7 +3718,7 @@ type MockPulpClient_UpdateRpmRemote_Call struct { // - clientCert *string // - clientKey *string // - caCert *string -func (_e *MockPulpClient_Expecter) UpdateRpmRemote(ctx interface{}, pulpHref interface{}, url interface{}, clientCert interface{}, clientKey interface{}, caCert interface{}) *MockPulpClient_UpdateRpmRemote_Call { +func (_e *MockPulpClient_Expecter) UpdateRpmRemote(ctx any, pulpHref any, url any, clientCert any, clientKey any, caCert any) *MockPulpClient_UpdateRpmRemote_Call { return &MockPulpClient_UpdateRpmRemote_Call{Call: _e.mock.On("UpdateRpmRemote", ctx, pulpHref, url, clientCert, clientKey, caCert)} } @@ -3815,7 +3815,7 @@ type MockPulpClient_UploadChunk_Call struct { // - contentRange string // - file *os.File // - sha256 string -func (_e *MockPulpClient_Expecter) UploadChunk(ctx interface{}, uploadHref interface{}, contentRange interface{}, file interface{}, sha256 interface{}) *MockPulpClient_UploadChunk_Call { +func (_e *MockPulpClient_Expecter) UploadChunk(ctx any, uploadHref any, contentRange any, file any, sha256 any) *MockPulpClient_UploadChunk_Call { return &MockPulpClient_UploadChunk_Call{Call: _e.mock.On("UploadChunk", ctx, uploadHref, contentRange, file, sha256)} } @@ -3888,7 +3888,7 @@ type MockPulpClient_WithDomain_Call struct { // WithDomain is a helper method to define mock.On call // - domainName string -func (_e *MockPulpClient_Expecter) WithDomain(domainName interface{}) *MockPulpClient_WithDomain_Call { +func (_e *MockPulpClient_Expecter) WithDomain(domainName any) *MockPulpClient_WithDomain_Call { return &MockPulpClient_WithDomain_Call{Call: _e.mock.On("WithDomain", domainName)} } diff --git a/pkg/clients/roadmap_client/roadmap_client_mock.go b/pkg/clients/roadmap_client/roadmap_client_mock.go index 6c981c050..893259a69 100644 --- a/pkg/clients/roadmap_client/roadmap_client_mock.go +++ b/pkg/clients/roadmap_client/roadmap_client_mock.go @@ -76,7 +76,7 @@ type MockRoadmapClient_GetAppstreams_Call struct { // GetAppstreams is a helper method to define mock.On call // - ctx context.Context -func (_e *MockRoadmapClient_Expecter) GetAppstreams(ctx interface{}) *MockRoadmapClient_GetAppstreams_Call { +func (_e *MockRoadmapClient_Expecter) GetAppstreams(ctx any) *MockRoadmapClient_GetAppstreams_Call { return &MockRoadmapClient_GetAppstreams_Call{Call: _e.mock.On("GetAppstreams", ctx)} } @@ -142,7 +142,7 @@ type MockRoadmapClient_GetRhelLifecycle_Call struct { // GetRhelLifecycle is a helper method to define mock.On call // - ctx context.Context -func (_e *MockRoadmapClient_Expecter) GetRhelLifecycle(ctx interface{}) *MockRoadmapClient_GetRhelLifecycle_Call { +func (_e *MockRoadmapClient_Expecter) GetRhelLifecycle(ctx any) *MockRoadmapClient_GetRhelLifecycle_Call { return &MockRoadmapClient_GetRhelLifecycle_Call{Call: _e.mock.On("GetRhelLifecycle", ctx)} } @@ -204,7 +204,7 @@ type MockRoadmapClient_GetRhelLifecycleForLatestMajorVersions_Call struct { // GetRhelLifecycleForLatestMajorVersions is a helper method to define mock.On call // - ctx context.Context -func (_e *MockRoadmapClient_Expecter) GetRhelLifecycleForLatestMajorVersions(ctx interface{}) *MockRoadmapClient_GetRhelLifecycleForLatestMajorVersions_Call { +func (_e *MockRoadmapClient_Expecter) GetRhelLifecycleForLatestMajorVersions(ctx any) *MockRoadmapClient_GetRhelLifecycleForLatestMajorVersions_Call { return &MockRoadmapClient_GetRhelLifecycleForLatestMajorVersions_Call{Call: _e.mock.On("GetRhelLifecycleForLatestMajorVersions", ctx)} } diff --git a/pkg/dao/dao_mock.go b/pkg/dao/dao_mock.go index 8f96c33eb..e00a9bcf6 100644 --- a/pkg/dao/dao_mock.go +++ b/pkg/dao/dao_mock.go @@ -13,6 +13,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/models" "github.com/content-services/tang/pkg/tangy" "github.com/content-services/yummy/pkg/yum" + "github.com/google/uuid" mock "github.com/stretchr/testify/mock" ) @@ -81,7 +82,7 @@ type MockRepositoryConfigDao_BulkCreate_Call struct { // BulkCreate is a helper method to define mock.On call // - ctx context.Context // - newRepositories []api.RepositoryRequest -func (_e *MockRepositoryConfigDao_Expecter) BulkCreate(ctx interface{}, newRepositories interface{}) *MockRepositoryConfigDao_BulkCreate_Call { +func (_e *MockRepositoryConfigDao_Expecter) BulkCreate(ctx any, newRepositories any) *MockRepositoryConfigDao_BulkCreate_Call { return &MockRepositoryConfigDao_BulkCreate_Call{Call: _e.mock.On("BulkCreate", ctx, newRepositories)} } @@ -141,7 +142,7 @@ type MockRepositoryConfigDao_BulkDelete_Call struct { // - ctx context.Context // - orgID string // - uuids []string -func (_e *MockRepositoryConfigDao_Expecter) BulkDelete(ctx interface{}, orgID interface{}, uuids interface{}) *MockRepositoryConfigDao_BulkDelete_Call { +func (_e *MockRepositoryConfigDao_Expecter) BulkDelete(ctx any, orgID any, uuids any) *MockRepositoryConfigDao_BulkDelete_Call { return &MockRepositoryConfigDao_BulkDelete_Call{Call: _e.mock.On("BulkDelete", ctx, orgID, uuids)} } @@ -215,7 +216,7 @@ type MockRepositoryConfigDao_BulkExport_Call struct { // - ctx context.Context // - orgID string // - reposToExport api.RepositoryExportRequest -func (_e *MockRepositoryConfigDao_Expecter) BulkExport(ctx interface{}, orgID interface{}, reposToExport interface{}) *MockRepositoryConfigDao_BulkExport_Call { +func (_e *MockRepositoryConfigDao_Expecter) BulkExport(ctx any, orgID any, reposToExport any) *MockRepositoryConfigDao_BulkExport_Call { return &MockRepositoryConfigDao_BulkExport_Call{Call: _e.mock.On("BulkExport", ctx, orgID, reposToExport)} } @@ -290,7 +291,7 @@ type MockRepositoryConfigDao_BulkImport_Call struct { // BulkImport is a helper method to define mock.On call // - ctx context.Context // - reposToImport []api.RepositoryRequest -func (_e *MockRepositoryConfigDao_Expecter) BulkImport(ctx interface{}, reposToImport interface{}) *MockRepositoryConfigDao_BulkImport_Call { +func (_e *MockRepositoryConfigDao_Expecter) BulkImport(ctx any, reposToImport any) *MockRepositoryConfigDao_BulkImport_Call { return &MockRepositoryConfigDao_BulkImport_Call{Call: _e.mock.On("BulkImport", ctx, reposToImport)} } @@ -356,7 +357,7 @@ type MockRepositoryConfigDao_Create_Call struct { // Create is a helper method to define mock.On call // - ctx context.Context // - newRepo api.RepositoryRequest -func (_e *MockRepositoryConfigDao_Expecter) Create(ctx interface{}, newRepo interface{}) *MockRepositoryConfigDao_Create_Call { +func (_e *MockRepositoryConfigDao_Expecter) Create(ctx any, newRepo any) *MockRepositoryConfigDao_Create_Call { return &MockRepositoryConfigDao_Create_Call{Call: _e.mock.On("Create", ctx, newRepo)} } @@ -414,7 +415,7 @@ type MockRepositoryConfigDao_Delete_Call struct { // - ctx context.Context // - orgID string // - uuid string -func (_e *MockRepositoryConfigDao_Expecter) Delete(ctx interface{}, orgID interface{}, uuid interface{}) *MockRepositoryConfigDao_Delete_Call { +func (_e *MockRepositoryConfigDao_Expecter) Delete(ctx any, orgID any, uuid any) *MockRepositoryConfigDao_Delete_Call { return &MockRepositoryConfigDao_Delete_Call{Call: _e.mock.On("Delete", ctx, orgID, uuid)} } @@ -486,7 +487,7 @@ type MockRepositoryConfigDao_Fetch_Call struct { // - ctx context.Context // - orgID string // - uuid string -func (_e *MockRepositoryConfigDao_Expecter) Fetch(ctx interface{}, orgID interface{}, uuid interface{}) *MockRepositoryConfigDao_Fetch_Call { +func (_e *MockRepositoryConfigDao_Expecter) Fetch(ctx any, orgID any, uuid any) *MockRepositoryConfigDao_Fetch_Call { return &MockRepositoryConfigDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, orgID, uuid)} } @@ -558,7 +559,7 @@ type MockRepositoryConfigDao_FetchByRepoUuid_Call struct { // - ctx context.Context // - orgID string // - repoUuid string -func (_e *MockRepositoryConfigDao_Expecter) FetchByRepoUuid(ctx interface{}, orgID interface{}, repoUuid interface{}) *MockRepositoryConfigDao_FetchByRepoUuid_Call { +func (_e *MockRepositoryConfigDao_Expecter) FetchByRepoUuid(ctx any, orgID any, repoUuid any) *MockRepositoryConfigDao_FetchByRepoUuid_Call { return &MockRepositoryConfigDao_FetchByRepoUuid_Call{Call: _e.mock.On("FetchByRepoUuid", ctx, orgID, repoUuid)} } @@ -632,7 +633,7 @@ type MockRepositoryConfigDao_FetchRepoUUIDsByURLs_Call struct { // - ctx context.Context // - orgID string // - repoURLs []string -func (_e *MockRepositoryConfigDao_Expecter) FetchRepoUUIDsByURLs(ctx interface{}, orgID interface{}, repoURLs interface{}) *MockRepositoryConfigDao_FetchRepoUUIDsByURLs_Call { +func (_e *MockRepositoryConfigDao_Expecter) FetchRepoUUIDsByURLs(ctx any, orgID any, repoURLs any) *MockRepositoryConfigDao_FetchRepoUUIDsByURLs_Call { return &MockRepositoryConfigDao_FetchRepoUUIDsByURLs_Call{Call: _e.mock.On("FetchRepoUUIDsByURLs", ctx, orgID, repoURLs)} } @@ -704,7 +705,7 @@ type MockRepositoryConfigDao_FetchWithoutOrgID_Call struct { // - ctx context.Context // - uuid string // - includeSoftDel bool -func (_e *MockRepositoryConfigDao_Expecter) FetchWithoutOrgID(ctx interface{}, uuid interface{}, includeSoftDel interface{}) *MockRepositoryConfigDao_FetchWithoutOrgID_Call { +func (_e *MockRepositoryConfigDao_Expecter) FetchWithoutOrgID(ctx any, uuid any, includeSoftDel any) *MockRepositoryConfigDao_FetchWithoutOrgID_Call { return &MockRepositoryConfigDao_FetchWithoutOrgID_Call{Call: _e.mock.On("FetchWithoutOrgID", ctx, uuid, includeSoftDel)} } @@ -776,7 +777,7 @@ type MockRepositoryConfigDao_InternalOnly_FetchRepoConfigByName_Call struct { // - ctx context.Context // - orgID string // - name string -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigByName(ctx interface{}, orgID interface{}, name interface{}) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigByName_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigByName(ctx any, orgID any, name any) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigByName_Call { return &MockRepositoryConfigDao_InternalOnly_FetchRepoConfigByName_Call{Call: _e.mock.On("InternalOnly_FetchRepoConfigByName", ctx, orgID, name)} } @@ -849,7 +850,7 @@ type MockRepositoryConfigDao_InternalOnly_FetchRepoConfigForOrg_Call struct { // InternalOnly_FetchRepoConfigForOrg is a helper method to define mock.On call // - ctx context.Context // - orgID string -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigForOrg(ctx interface{}, orgID interface{}) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigForOrg_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigForOrg(ctx any, orgID any) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigForOrg_Call { return &MockRepositoryConfigDao_InternalOnly_FetchRepoConfigForOrg_Call{Call: _e.mock.On("InternalOnly_FetchRepoConfigForOrg", ctx, orgID)} } @@ -908,7 +909,7 @@ type MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForRepoUUID_Call struc // InternalOnly_FetchRepoConfigsForRepoUUID is a helper method to define mock.On call // - ctx context.Context // - uuid string -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigsForRepoUUID(ctx interface{}, uuid interface{}) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForRepoUUID_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigsForRepoUUID(ctx any, uuid any) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForRepoUUID_Call { return &MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForRepoUUID_Call{Call: _e.mock.On("InternalOnly_FetchRepoConfigsForRepoUUID", ctx, uuid)} } @@ -976,7 +977,7 @@ type MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForTemplate_Call struc // InternalOnly_FetchRepoConfigsForTemplate is a helper method to define mock.On call // - ctx context.Context // - template models.Template -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigsForTemplate(ctx interface{}, template interface{}) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForTemplate_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigsForTemplate(ctx any, template any) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForTemplate_Call { return &MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForTemplate_Call{Call: _e.mock.On("InternalOnly_FetchRepoConfigsForTemplate", ctx, template)} } @@ -1033,7 +1034,7 @@ type MockRepositoryConfigDao_InternalOnly_IncrementFailedSnapshotCount_Call stru // InternalOnly_IncrementFailedSnapshotCount is a helper method to define mock.On call // - ctx context.Context // - rcUuid string -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_IncrementFailedSnapshotCount(ctx interface{}, rcUuid interface{}) *MockRepositoryConfigDao_InternalOnly_IncrementFailedSnapshotCount_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_IncrementFailedSnapshotCount(ctx any, rcUuid any) *MockRepositoryConfigDao_InternalOnly_IncrementFailedSnapshotCount_Call { return &MockRepositoryConfigDao_InternalOnly_IncrementFailedSnapshotCount_Call{Call: _e.mock.On("InternalOnly_IncrementFailedSnapshotCount", ctx, rcUuid)} } @@ -1101,7 +1102,7 @@ type MockRepositoryConfigDao_InternalOnly_ListReposToSnapshot_Call struct { // InternalOnly_ListReposToSnapshot is a helper method to define mock.On call // - ctx context.Context // - filter *ListRepoFilter -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_ListReposToSnapshot(ctx interface{}, filter interface{}) *MockRepositoryConfigDao_InternalOnly_ListReposToSnapshot_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_ListReposToSnapshot(ctx any, filter any) *MockRepositoryConfigDao_InternalOnly_ListReposToSnapshot_Call { return &MockRepositoryConfigDao_InternalOnly_ListReposToSnapshot_Call{Call: _e.mock.On("InternalOnly_ListReposToSnapshot", ctx, filter)} } @@ -1175,7 +1176,7 @@ type MockRepositoryConfigDao_InternalOnly_RefreshLightwellRepo_Call struct { // - publishedDistURL string // - basePath string // - featureName string -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_RefreshLightwellRepo(ctx interface{}, orgID interface{}, name interface{}, securityLevel interface{}, contentType interface{}, publishedDistURL interface{}, basePath interface{}, featureName interface{}) *MockRepositoryConfigDao_InternalOnly_RefreshLightwellRepo_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_RefreshLightwellRepo(ctx any, orgID any, name any, securityLevel any, contentType any, publishedDistURL any, basePath any, featureName any) *MockRepositoryConfigDao_InternalOnly_RefreshLightwellRepo_Call { return &MockRepositoryConfigDao_InternalOnly_RefreshLightwellRepo_Call{Call: _e.mock.On("InternalOnly_RefreshLightwellRepo", ctx, orgID, name, securityLevel, contentType, publishedDistURL, basePath, featureName)} } @@ -1275,7 +1276,7 @@ type MockRepositoryConfigDao_InternalOnly_RefreshPredefinedSnapshotRepo_Call str // - request api.RepositoryRequest // - label string // - featureName string -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_RefreshPredefinedSnapshotRepo(ctx interface{}, request interface{}, label interface{}, featureName interface{}) *MockRepositoryConfigDao_InternalOnly_RefreshPredefinedSnapshotRepo_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_RefreshPredefinedSnapshotRepo(ctx any, request any, label any, featureName any) *MockRepositoryConfigDao_InternalOnly_RefreshPredefinedSnapshotRepo_Call { return &MockRepositoryConfigDao_InternalOnly_RefreshPredefinedSnapshotRepo_Call{Call: _e.mock.On("InternalOnly_RefreshPredefinedSnapshotRepo", ctx, request, label, featureName)} } @@ -1342,7 +1343,7 @@ type MockRepositoryConfigDao_InternalOnly_ResetFailedSnapshotCount_Call struct { // InternalOnly_ResetFailedSnapshotCount is a helper method to define mock.On call // - ctx context.Context // - rcUuid string -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_ResetFailedSnapshotCount(ctx interface{}, rcUuid interface{}) *MockRepositoryConfigDao_InternalOnly_ResetFailedSnapshotCount_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_ResetFailedSnapshotCount(ctx any, rcUuid any) *MockRepositoryConfigDao_InternalOnly_ResetFailedSnapshotCount_Call { return &MockRepositoryConfigDao_InternalOnly_ResetFailedSnapshotCount_Call{Call: _e.mock.On("InternalOnly_ResetFailedSnapshotCount", ctx, rcUuid)} } @@ -1416,7 +1417,7 @@ type MockRepositoryConfigDao_List_Call struct { // - orgID string // - paginationData api.PaginationData // - filterData api.FilterData -func (_e *MockRepositoryConfigDao_Expecter) List(ctx interface{}, orgID interface{}, paginationData interface{}, filterData interface{}) *MockRepositoryConfigDao_List_Call { +func (_e *MockRepositoryConfigDao_Expecter) List(ctx any, orgID any, paginationData any, filterData any) *MockRepositoryConfigDao_List_Call { return &MockRepositoryConfigDao_List_Call{Call: _e.mock.On("List", ctx, orgID, paginationData, filterData)} } @@ -1494,7 +1495,7 @@ type MockRepositoryConfigDao_ListReposWithOutdatedSnapshots_Call struct { // ListReposWithOutdatedSnapshots is a helper method to define mock.On call // - ctx context.Context // - olderThanDays int -func (_e *MockRepositoryConfigDao_Expecter) ListReposWithOutdatedSnapshots(ctx interface{}, olderThanDays interface{}) *MockRepositoryConfigDao_ListReposWithOutdatedSnapshots_Call { +func (_e *MockRepositoryConfigDao_Expecter) ListReposWithOutdatedSnapshots(ctx any, olderThanDays any) *MockRepositoryConfigDao_ListReposWithOutdatedSnapshots_Call { return &MockRepositoryConfigDao_ListReposWithOutdatedSnapshots_Call{Call: _e.mock.On("ListReposWithOutdatedSnapshots", ctx, olderThanDays)} } @@ -1551,7 +1552,7 @@ type MockRepositoryConfigDao_SavePublicRepos_Call struct { // SavePublicRepos is a helper method to define mock.On call // - ctx context.Context // - urls []string -func (_e *MockRepositoryConfigDao_Expecter) SavePublicRepos(ctx interface{}, urls interface{}) *MockRepositoryConfigDao_SavePublicRepos_Call { +func (_e *MockRepositoryConfigDao_Expecter) SavePublicRepos(ctx any, urls any) *MockRepositoryConfigDao_SavePublicRepos_Call { return &MockRepositoryConfigDao_SavePublicRepos_Call{Call: _e.mock.On("SavePublicRepos", ctx, urls)} } @@ -1609,7 +1610,7 @@ type MockRepositoryConfigDao_SetPartnerRepo_Call struct { // - ctx context.Context // - repoConfigUUID string // - partner bool -func (_e *MockRepositoryConfigDao_Expecter) SetPartnerRepo(ctx interface{}, repoConfigUUID interface{}, partner interface{}) *MockRepositoryConfigDao_SetPartnerRepo_Call { +func (_e *MockRepositoryConfigDao_Expecter) SetPartnerRepo(ctx any, repoConfigUUID any, partner any) *MockRepositoryConfigDao_SetPartnerRepo_Call { return &MockRepositoryConfigDao_SetPartnerRepo_Call{Call: _e.mock.On("SetPartnerRepo", ctx, repoConfigUUID, partner)} } @@ -1672,7 +1673,7 @@ type MockRepositoryConfigDao_SoftDelete_Call struct { // - ctx context.Context // - orgID string // - uuid string -func (_e *MockRepositoryConfigDao_Expecter) SoftDelete(ctx interface{}, orgID interface{}, uuid interface{}) *MockRepositoryConfigDao_SoftDelete_Call { +func (_e *MockRepositoryConfigDao_Expecter) SoftDelete(ctx any, orgID any, uuid any) *MockRepositoryConfigDao_SoftDelete_Call { return &MockRepositoryConfigDao_SoftDelete_Call{Call: _e.mock.On("SoftDelete", ctx, orgID, uuid)} } @@ -1745,7 +1746,7 @@ type MockRepositoryConfigDao_Update_Call struct { // - orgID string // - uuid string // - repoParams api.RepositoryUpdateRequest -func (_e *MockRepositoryConfigDao_Expecter) Update(ctx interface{}, orgID interface{}, uuid interface{}, repoParams interface{}) *MockRepositoryConfigDao_Update_Call { +func (_e *MockRepositoryConfigDao_Expecter) Update(ctx any, orgID any, uuid any, repoParams any) *MockRepositoryConfigDao_Update_Call { return &MockRepositoryConfigDao_Update_Call{Call: _e.mock.On("Update", ctx, orgID, uuid, repoParams)} } @@ -1814,7 +1815,7 @@ type MockRepositoryConfigDao_UpdateLastSnapshot_Call struct { // - orgID string // - repoConfigUUID string // - snapUUID string -func (_e *MockRepositoryConfigDao_Expecter) UpdateLastSnapshot(ctx interface{}, orgID interface{}, repoConfigUUID interface{}, snapUUID interface{}) *MockRepositoryConfigDao_UpdateLastSnapshot_Call { +func (_e *MockRepositoryConfigDao_Expecter) UpdateLastSnapshot(ctx any, orgID any, repoConfigUUID any, snapUUID any) *MockRepositoryConfigDao_UpdateLastSnapshot_Call { return &MockRepositoryConfigDao_UpdateLastSnapshot_Call{Call: _e.mock.On("UpdateLastSnapshot", ctx, orgID, repoConfigUUID, snapUUID)} } @@ -1883,7 +1884,7 @@ type MockRepositoryConfigDao_UpdateLastSnapshotTask_Call struct { // - taskUUID string // - orgID string // - repoUUID string -func (_e *MockRepositoryConfigDao_Expecter) UpdateLastSnapshotTask(ctx interface{}, taskUUID interface{}, orgID interface{}, repoUUID interface{}) *MockRepositoryConfigDao_UpdateLastSnapshotTask_Call { +func (_e *MockRepositoryConfigDao_Expecter) UpdateLastSnapshotTask(ctx any, taskUUID any, orgID any, repoUUID any) *MockRepositoryConfigDao_UpdateLastSnapshotTask_Call { return &MockRepositoryConfigDao_UpdateLastSnapshotTask_Call{Call: _e.mock.On("UpdateLastSnapshotTask", ctx, taskUUID, orgID, repoUUID)} } @@ -1961,7 +1962,7 @@ type MockRepositoryConfigDao_ValidateParameters_Call struct { // - orgId string // - params api.RepositoryValidationRequest // - excludedUUIDS []string -func (_e *MockRepositoryConfigDao_Expecter) ValidateParameters(ctx interface{}, orgId interface{}, params interface{}, excludedUUIDS interface{}) *MockRepositoryConfigDao_ValidateParameters_Call { +func (_e *MockRepositoryConfigDao_Expecter) ValidateParameters(ctx any, orgId any, params any, excludedUUIDS any) *MockRepositoryConfigDao_ValidateParameters_Call { return &MockRepositoryConfigDao_ValidateParameters_Call{Call: _e.mock.On("ValidateParameters", ctx, orgId, params, excludedUUIDS)} } @@ -2065,7 +2066,7 @@ type MockModuleStreamDao_InsertForRepository_Call struct { // - ctx context.Context // - repoUuid string // - pkgGroups []yum.ModuleMD -func (_e *MockModuleStreamDao_Expecter) InsertForRepository(ctx interface{}, repoUuid interface{}, pkgGroups interface{}) *MockModuleStreamDao_InsertForRepository_Call { +func (_e *MockModuleStreamDao_Expecter) InsertForRepository(ctx any, repoUuid any, pkgGroups any) *MockModuleStreamDao_InsertForRepository_Call { return &MockModuleStreamDao_InsertForRepository_Call{Call: _e.mock.On("InsertForRepository", ctx, repoUuid, pkgGroups)} } @@ -2126,7 +2127,7 @@ type MockModuleStreamDao_OrphanCleanup_Call struct { // OrphanCleanup is a helper method to define mock.On call // - ctx context.Context -func (_e *MockModuleStreamDao_Expecter) OrphanCleanup(ctx interface{}) *MockModuleStreamDao_OrphanCleanup_Call { +func (_e *MockModuleStreamDao_Expecter) OrphanCleanup(ctx any) *MockModuleStreamDao_OrphanCleanup_Call { return &MockModuleStreamDao_OrphanCleanup_Call{Call: _e.mock.On("OrphanCleanup", ctx)} } @@ -2190,7 +2191,7 @@ type MockModuleStreamDao_SearchRepositoryModuleStreams_Call struct { // - ctx context.Context // - orgID string // - request api.SearchModuleStreamsRequest -func (_e *MockModuleStreamDao_Expecter) SearchRepositoryModuleStreams(ctx interface{}, orgID interface{}, request interface{}) *MockModuleStreamDao_SearchRepositoryModuleStreams_Call { +func (_e *MockModuleStreamDao_Expecter) SearchRepositoryModuleStreams(ctx any, orgID any, request any) *MockModuleStreamDao_SearchRepositoryModuleStreams_Call { return &MockModuleStreamDao_SearchRepositoryModuleStreams_Call{Call: _e.mock.On("SearchRepositoryModuleStreams", ctx, orgID, request)} } @@ -2264,7 +2265,7 @@ type MockModuleStreamDao_SearchSnapshotModuleStreams_Call struct { // - ctx context.Context // - orgID string // - request api.SearchSnapshotModuleStreamsRequest -func (_e *MockModuleStreamDao_Expecter) SearchSnapshotModuleStreams(ctx interface{}, orgID interface{}, request interface{}) *MockModuleStreamDao_SearchSnapshotModuleStreams_Call { +func (_e *MockModuleStreamDao_Expecter) SearchSnapshotModuleStreams(ctx any, orgID any, request any) *MockModuleStreamDao_SearchSnapshotModuleStreams_Call { return &MockModuleStreamDao_SearchSnapshotModuleStreams_Call{Call: _e.mock.On("SearchSnapshotModuleStreams", ctx, orgID, request)} } @@ -2366,7 +2367,7 @@ type MockRpmDao_FetchForRepository_Call struct { // - orgID string // - repositoryConfigUUID string // - rpmUUIDs []string -func (_e *MockRpmDao_Expecter) FetchForRepository(ctx interface{}, orgID interface{}, repositoryConfigUUID interface{}, rpmUUIDs interface{}) *MockRpmDao_FetchForRepository_Call { +func (_e *MockRpmDao_Expecter) FetchForRepository(ctx any, orgID any, repositoryConfigUUID any, rpmUUIDs any) *MockRpmDao_FetchForRepository_Call { return &MockRpmDao_FetchForRepository_Call{Call: _e.mock.On("FetchForRepository", ctx, orgID, repositoryConfigUUID, rpmUUIDs)} } @@ -2445,7 +2446,7 @@ type MockRpmDao_FetchTemplateErrataIDs_Call struct { // - ctx context.Context // - orgId string // - templateUUID string -func (_e *MockRpmDao_Expecter) FetchTemplateErrataIDs(ctx interface{}, orgId interface{}, templateUUID interface{}) *MockRpmDao_FetchTemplateErrataIDs_Call { +func (_e *MockRpmDao_Expecter) FetchTemplateErrataIDs(ctx any, orgId any, templateUUID any) *MockRpmDao_FetchTemplateErrataIDs_Call { return &MockRpmDao_FetchTemplateErrataIDs_Call{Call: _e.mock.On("FetchTemplateErrataIDs", ctx, orgId, templateUUID)} } @@ -2517,7 +2518,7 @@ type MockRpmDao_InsertForRepository_Call struct { // - ctx context.Context // - repoUuid string // - pkgs []yum.Package -func (_e *MockRpmDao_Expecter) InsertForRepository(ctx interface{}, repoUuid interface{}, pkgs interface{}) *MockRpmDao_InsertForRepository_Call { +func (_e *MockRpmDao_Expecter) InsertForRepository(ctx any, repoUuid any, pkgs any) *MockRpmDao_InsertForRepository_Call { return &MockRpmDao_InsertForRepository_Call{Call: _e.mock.On("InsertForRepository", ctx, repoUuid, pkgs)} } @@ -2599,7 +2600,7 @@ type MockRpmDao_List_Call struct { // - offset int // - search string // - sortBy string -func (_e *MockRpmDao_Expecter) List(ctx interface{}, orgID interface{}, uuidRepo interface{}, limit interface{}, offset interface{}, search interface{}, sortBy interface{}) *MockRpmDao_List_Call { +func (_e *MockRpmDao_Expecter) List(ctx any, orgID any, uuidRepo any, limit any, offset any, search any, sortBy any) *MockRpmDao_List_Call { return &MockRpmDao_List_Call{Call: _e.mock.On("List", ctx, orgID, uuidRepo, limit, offset, search, sortBy)} } @@ -2701,7 +2702,7 @@ type MockRpmDao_ListSnapshotErrata_Call struct { // - snapshotUUIDs []string // - filters tangy.ErrataListFilters // - pageOpts api.PaginationData -func (_e *MockRpmDao_Expecter) ListSnapshotErrata(ctx interface{}, orgId interface{}, snapshotUUIDs interface{}, filters interface{}, pageOpts interface{}) *MockRpmDao_ListSnapshotErrata_Call { +func (_e *MockRpmDao_Expecter) ListSnapshotErrata(ctx any, orgId any, snapshotUUIDs any, filters any, pageOpts any) *MockRpmDao_ListSnapshotErrata_Call { return &MockRpmDao_ListSnapshotErrata_Call{Call: _e.mock.On("ListSnapshotErrata", ctx, orgId, snapshotUUIDs, filters, pageOpts)} } @@ -2793,7 +2794,7 @@ type MockRpmDao_ListSnapshotRpms_Call struct { // - snapshotUUIDs []string // - search string // - pageOpts api.PaginationData -func (_e *MockRpmDao_Expecter) ListSnapshotRpms(ctx interface{}, orgId interface{}, snapshotUUIDs interface{}, search interface{}, pageOpts interface{}) *MockRpmDao_ListSnapshotRpms_Call { +func (_e *MockRpmDao_Expecter) ListSnapshotRpms(ctx any, orgId any, snapshotUUIDs any, search any, pageOpts any) *MockRpmDao_ListSnapshotRpms_Call { return &MockRpmDao_ListSnapshotRpms_Call{Call: _e.mock.On("ListSnapshotRpms", ctx, orgId, snapshotUUIDs, search, pageOpts)} } @@ -2885,7 +2886,7 @@ type MockRpmDao_ListTemplateErrata_Call struct { // - templateUUID string // - filters tangy.ErrataListFilters // - pageOpts api.PaginationData -func (_e *MockRpmDao_Expecter) ListTemplateErrata(ctx interface{}, orgId interface{}, templateUUID interface{}, filters interface{}, pageOpts interface{}) *MockRpmDao_ListTemplateErrata_Call { +func (_e *MockRpmDao_Expecter) ListTemplateErrata(ctx any, orgId any, templateUUID any, filters any, pageOpts any) *MockRpmDao_ListTemplateErrata_Call { return &MockRpmDao_ListTemplateErrata_Call{Call: _e.mock.On("ListTemplateErrata", ctx, orgId, templateUUID, filters, pageOpts)} } @@ -2977,7 +2978,7 @@ type MockRpmDao_ListTemplateRpms_Call struct { // - templateUUID string // - search string // - pageOpts api.PaginationData -func (_e *MockRpmDao_Expecter) ListTemplateRpms(ctx interface{}, orgId interface{}, templateUUID interface{}, search interface{}, pageOpts interface{}) *MockRpmDao_ListTemplateRpms_Call { +func (_e *MockRpmDao_Expecter) ListTemplateRpms(ctx any, orgId any, templateUUID any, search any, pageOpts any) *MockRpmDao_ListTemplateRpms_Call { return &MockRpmDao_ListTemplateRpms_Call{Call: _e.mock.On("ListTemplateRpms", ctx, orgId, templateUUID, search, pageOpts)} } @@ -3048,7 +3049,7 @@ type MockRpmDao_OrphanCleanup_Call struct { // OrphanCleanup is a helper method to define mock.On call // - ctx context.Context -func (_e *MockRpmDao_Expecter) OrphanCleanup(ctx interface{}) *MockRpmDao_OrphanCleanup_Call { +func (_e *MockRpmDao_Expecter) OrphanCleanup(ctx any) *MockRpmDao_OrphanCleanup_Call { return &MockRpmDao_OrphanCleanup_Call{Call: _e.mock.On("OrphanCleanup", ctx)} } @@ -3112,7 +3113,7 @@ type MockRpmDao_Search_Call struct { // - ctx context.Context // - orgID string // - request api.ContentUnitSearchRequest -func (_e *MockRpmDao_Expecter) Search(ctx interface{}, orgID interface{}, request interface{}) *MockRpmDao_Search_Call { +func (_e *MockRpmDao_Expecter) Search(ctx any, orgID any, request any) *MockRpmDao_Search_Call { return &MockRpmDao_Search_Call{Call: _e.mock.On("Search", ctx, orgID, request)} } @@ -3186,7 +3187,7 @@ type MockRpmDao_SearchSnapshotRpms_Call struct { // - ctx context.Context // - orgId string // - request api.SnapshotSearchRpmRequest -func (_e *MockRpmDao_Expecter) SearchSnapshotRpms(ctx interface{}, orgId interface{}, request interface{}) *MockRpmDao_SearchSnapshotRpms_Call { +func (_e *MockRpmDao_Expecter) SearchSnapshotRpms(ctx any, orgId any, request any) *MockRpmDao_SearchSnapshotRpms_Call { return &MockRpmDao_SearchSnapshotRpms_Call{Call: _e.mock.On("SearchSnapshotRpms", ctx, orgId, request)} } @@ -3285,7 +3286,7 @@ type MockRepositoryDao_FetchForUrl_Call struct { // - ctx context.Context // - url string // - origin *string -func (_e *MockRepositoryDao_Expecter) FetchForUrl(ctx interface{}, url interface{}, origin interface{}) *MockRepositoryDao_FetchForUrl_Call { +func (_e *MockRepositoryDao_Expecter) FetchForUrl(ctx any, url any, origin any) *MockRepositoryDao_FetchForUrl_Call { return &MockRepositoryDao_FetchForUrl_Call{Call: _e.mock.On("FetchForUrl", ctx, url, origin)} } @@ -3356,7 +3357,7 @@ type MockRepositoryDao_FetchRepositoryRPMCount_Call struct { // FetchRepositoryRPMCount is a helper method to define mock.On call // - ctx context.Context // - repoUUID string -func (_e *MockRepositoryDao_Expecter) FetchRepositoryRPMCount(ctx interface{}, repoUUID interface{}) *MockRepositoryDao_FetchRepositoryRPMCount_Call { +func (_e *MockRepositoryDao_Expecter) FetchRepositoryRPMCount(ctx any, repoUUID any) *MockRepositoryDao_FetchRepositoryRPMCount_Call { return &MockRepositoryDao_FetchRepositoryRPMCount_Call{Call: _e.mock.On("FetchRepositoryRPMCount", ctx, repoUUID)} } @@ -3416,7 +3417,7 @@ type MockRepositoryDao_InternalOnly_UpdateCounts_Call struct { // - packageCount int // - buildCount int // - versionCount int -func (_e *MockRepositoryDao_Expecter) InternalOnly_UpdateCounts(ctx interface{}, repoUUID interface{}, packageCount interface{}, buildCount interface{}, versionCount interface{}) *MockRepositoryDao_InternalOnly_UpdateCounts_Call { +func (_e *MockRepositoryDao_Expecter) InternalOnly_UpdateCounts(ctx any, repoUUID any, packageCount any, buildCount any, versionCount any) *MockRepositoryDao_InternalOnly_UpdateCounts_Call { return &MockRepositoryDao_InternalOnly_UpdateCounts_Call{Call: _e.mock.On("InternalOnly_UpdateCounts", ctx, repoUUID, packageCount, buildCount, versionCount)} } @@ -3500,7 +3501,7 @@ type MockRepositoryDao_ListForIntrospection_Call struct { // - ctx context.Context // - urls *[]string // - force bool -func (_e *MockRepositoryDao_Expecter) ListForIntrospection(ctx interface{}, urls interface{}, force interface{}) *MockRepositoryDao_ListForIntrospection_Call { +func (_e *MockRepositoryDao_Expecter) ListForIntrospection(ctx any, urls any, force any) *MockRepositoryDao_ListForIntrospection_Call { return &MockRepositoryDao_ListForIntrospection_Call{Call: _e.mock.On("ListForIntrospection", ctx, urls, force)} } @@ -3578,7 +3579,7 @@ type MockRepositoryDao_ListPublic_Call struct { // - ctx context.Context // - paginationData api.PaginationData // - filterData api.FilterData -func (_e *MockRepositoryDao_Expecter) ListPublic(ctx interface{}, paginationData interface{}, filterData interface{}) *MockRepositoryDao_ListPublic_Call { +func (_e *MockRepositoryDao_Expecter) ListPublic(ctx any, paginationData any, filterData any) *MockRepositoryDao_ListPublic_Call { return &MockRepositoryDao_ListPublic_Call{Call: _e.mock.On("ListPublic", ctx, paginationData, filterData)} } @@ -3640,7 +3641,7 @@ type MockRepositoryDao_MarkAsNotPublic_Call struct { // MarkAsNotPublic is a helper method to define mock.On call // - ctx context.Context // - url string -func (_e *MockRepositoryDao_Expecter) MarkAsNotPublic(ctx interface{}, url interface{}) *MockRepositoryDao_MarkAsNotPublic_Call { +func (_e *MockRepositoryDao_Expecter) MarkAsNotPublic(ctx any, url any) *MockRepositoryDao_MarkAsNotPublic_Call { return &MockRepositoryDao_MarkAsNotPublic_Call{Call: _e.mock.On("MarkAsNotPublic", ctx, url)} } @@ -3696,7 +3697,7 @@ type MockRepositoryDao_OrphanCleanup_Call struct { // OrphanCleanup is a helper method to define mock.On call // - ctx context.Context -func (_e *MockRepositoryDao_Expecter) OrphanCleanup(ctx interface{}) *MockRepositoryDao_OrphanCleanup_Call { +func (_e *MockRepositoryDao_Expecter) OrphanCleanup(ctx any) *MockRepositoryDao_OrphanCleanup_Call { return &MockRepositoryDao_OrphanCleanup_Call{Call: _e.mock.On("OrphanCleanup", ctx)} } @@ -3748,7 +3749,7 @@ type MockRepositoryDao_Update_Call struct { // Update is a helper method to define mock.On call // - ctx context.Context // - repo RepositoryUpdate -func (_e *MockRepositoryDao_Expecter) Update(ctx interface{}, repo interface{}) *MockRepositoryDao_Update_Call { +func (_e *MockRepositoryDao_Expecter) Update(ctx any, repo any) *MockRepositoryDao_Update_Call { return &MockRepositoryDao_Update_Call{Call: _e.mock.On("Update", ctx, repo)} } @@ -3834,7 +3835,7 @@ type MockSnapshotDao_BulkDelete_Call struct { // BulkDelete is a helper method to define mock.On call // - ctx context.Context // - uuids []string -func (_e *MockSnapshotDao_Expecter) BulkDelete(ctx interface{}, uuids interface{}) *MockSnapshotDao_BulkDelete_Call { +func (_e *MockSnapshotDao_Expecter) BulkDelete(ctx any, uuids any) *MockSnapshotDao_BulkDelete_Call { return &MockSnapshotDao_BulkDelete_Call{Call: _e.mock.On("BulkDelete", ctx, uuids)} } @@ -3891,7 +3892,7 @@ type MockSnapshotDao_ClearDeletedAt_Call struct { // ClearDeletedAt is a helper method to define mock.On call // - ctx context.Context // - snapUUID string -func (_e *MockSnapshotDao_Expecter) ClearDeletedAt(ctx interface{}, snapUUID interface{}) *MockSnapshotDao_ClearDeletedAt_Call { +func (_e *MockSnapshotDao_Expecter) ClearDeletedAt(ctx any, snapUUID any) *MockSnapshotDao_ClearDeletedAt_Call { return &MockSnapshotDao_ClearDeletedAt_Call{Call: _e.mock.On("ClearDeletedAt", ctx, snapUUID)} } @@ -3948,7 +3949,7 @@ type MockSnapshotDao_Create_Call struct { // Create is a helper method to define mock.On call // - ctx context.Context // - snap *models.Snapshot -func (_e *MockSnapshotDao_Expecter) Create(ctx interface{}, snap interface{}) *MockSnapshotDao_Create_Call { +func (_e *MockSnapshotDao_Expecter) Create(ctx any, snap any) *MockSnapshotDao_Create_Call { return &MockSnapshotDao_Create_Call{Call: _e.mock.On("Create", ctx, snap)} } @@ -4005,7 +4006,7 @@ type MockSnapshotDao_Delete_Call struct { // Delete is a helper method to define mock.On call // - ctx context.Context // - snapUUID string -func (_e *MockSnapshotDao_Expecter) Delete(ctx interface{}, snapUUID interface{}) *MockSnapshotDao_Delete_Call { +func (_e *MockSnapshotDao_Expecter) Delete(ctx any, snapUUID any) *MockSnapshotDao_Delete_Call { return &MockSnapshotDao_Delete_Call{Call: _e.mock.On("Delete", ctx, snapUUID)} } @@ -4072,7 +4073,7 @@ type MockSnapshotDao_Fetch_Call struct { // - ctx context.Context // - orgID string // - uuid string -func (_e *MockSnapshotDao_Expecter) Fetch(ctx interface{}, orgID interface{}, uuid interface{}) *MockSnapshotDao_Fetch_Call { +func (_e *MockSnapshotDao_Expecter) Fetch(ctx any, orgID any, uuid any) *MockSnapshotDao_Fetch_Call { return &MockSnapshotDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, orgID, uuid)} } @@ -4146,7 +4147,7 @@ type MockSnapshotDao_FetchForRepoConfigUUID_Call struct { // - ctx context.Context // - repoConfigUUID string // - inclSoftDel bool -func (_e *MockSnapshotDao_Expecter) FetchForRepoConfigUUID(ctx interface{}, repoConfigUUID interface{}, inclSoftDel interface{}) *MockSnapshotDao_FetchForRepoConfigUUID_Call { +func (_e *MockSnapshotDao_Expecter) FetchForRepoConfigUUID(ctx any, repoConfigUUID any, inclSoftDel any) *MockSnapshotDao_FetchForRepoConfigUUID_Call { return &MockSnapshotDao_FetchForRepoConfigUUID_Call{Call: _e.mock.On("FetchForRepoConfigUUID", ctx, repoConfigUUID, inclSoftDel)} } @@ -4217,7 +4218,7 @@ type MockSnapshotDao_FetchLatestPublishedSnapshotModel_Call struct { // FetchLatestPublishedSnapshotModel is a helper method to define mock.On call // - ctx context.Context // - repoConfigUUID string -func (_e *MockSnapshotDao_Expecter) FetchLatestPublishedSnapshotModel(ctx interface{}, repoConfigUUID interface{}) *MockSnapshotDao_FetchLatestPublishedSnapshotModel_Call { +func (_e *MockSnapshotDao_Expecter) FetchLatestPublishedSnapshotModel(ctx any, repoConfigUUID any) *MockSnapshotDao_FetchLatestPublishedSnapshotModel_Call { return &MockSnapshotDao_FetchLatestPublishedSnapshotModel_Call{Call: _e.mock.On("FetchLatestPublishedSnapshotModel", ctx, repoConfigUUID)} } @@ -4283,7 +4284,7 @@ type MockSnapshotDao_FetchLatestSnapshot_Call struct { // FetchLatestSnapshot is a helper method to define mock.On call // - ctx context.Context // - repoConfigUUID string -func (_e *MockSnapshotDao_Expecter) FetchLatestSnapshot(ctx interface{}, repoConfigUUID interface{}) *MockSnapshotDao_FetchLatestSnapshot_Call { +func (_e *MockSnapshotDao_Expecter) FetchLatestSnapshot(ctx any, repoConfigUUID any) *MockSnapshotDao_FetchLatestSnapshot_Call { return &MockSnapshotDao_FetchLatestSnapshot_Call{Call: _e.mock.On("FetchLatestSnapshot", ctx, repoConfigUUID)} } @@ -4349,7 +4350,7 @@ type MockSnapshotDao_FetchLatestSnapshotForDistribution_Call struct { // FetchLatestSnapshotForDistribution is a helper method to define mock.On call // - ctx context.Context // - repoConfigUUID string -func (_e *MockSnapshotDao_Expecter) FetchLatestSnapshotForDistribution(ctx interface{}, repoConfigUUID interface{}) *MockSnapshotDao_FetchLatestSnapshotForDistribution_Call { +func (_e *MockSnapshotDao_Expecter) FetchLatestSnapshotForDistribution(ctx any, repoConfigUUID any) *MockSnapshotDao_FetchLatestSnapshotForDistribution_Call { return &MockSnapshotDao_FetchLatestSnapshotForDistribution_Call{Call: _e.mock.On("FetchLatestSnapshotForDistribution", ctx, repoConfigUUID)} } @@ -4415,7 +4416,7 @@ type MockSnapshotDao_FetchLatestSnapshotModel_Call struct { // FetchLatestSnapshotModel is a helper method to define mock.On call // - ctx context.Context // - repoConfigUUID string -func (_e *MockSnapshotDao_Expecter) FetchLatestSnapshotModel(ctx interface{}, repoConfigUUID interface{}) *MockSnapshotDao_FetchLatestSnapshotModel_Call { +func (_e *MockSnapshotDao_Expecter) FetchLatestSnapshotModel(ctx any, repoConfigUUID any) *MockSnapshotDao_FetchLatestSnapshotModel_Call { return &MockSnapshotDao_FetchLatestSnapshotModel_Call{Call: _e.mock.On("FetchLatestSnapshotModel", ctx, repoConfigUUID)} } @@ -4482,7 +4483,7 @@ type MockSnapshotDao_FetchModel_Call struct { // - ctx context.Context // - uuid string // - includeSoftDel bool -func (_e *MockSnapshotDao_Expecter) FetchModel(ctx interface{}, uuid interface{}, includeSoftDel interface{}) *MockSnapshotDao_FetchModel_Call { +func (_e *MockSnapshotDao_Expecter) FetchModel(ctx any, uuid any, includeSoftDel any) *MockSnapshotDao_FetchModel_Call { return &MockSnapshotDao_FetchModel_Call{Call: _e.mock.On("FetchModel", ctx, uuid, includeSoftDel)} } @@ -4556,7 +4557,7 @@ type MockSnapshotDao_FetchSnapshotByVersionHref_Call struct { // - ctx context.Context // - repoConfigUUID string // - versionHref string -func (_e *MockSnapshotDao_Expecter) FetchSnapshotByVersionHref(ctx interface{}, repoConfigUUID interface{}, versionHref interface{}) *MockSnapshotDao_FetchSnapshotByVersionHref_Call { +func (_e *MockSnapshotDao_Expecter) FetchSnapshotByVersionHref(ctx any, repoConfigUUID any, versionHref any) *MockSnapshotDao_FetchSnapshotByVersionHref_Call { return &MockSnapshotDao_FetchSnapshotByVersionHref_Call{Call: _e.mock.On("FetchSnapshotByVersionHref", ctx, repoConfigUUID, versionHref)} } @@ -4628,7 +4629,7 @@ type MockSnapshotDao_FetchSnapshotsByDateAndRepository_Call struct { // - ctx context.Context // - orgID string // - request api.ListSnapshotByDateRequest -func (_e *MockSnapshotDao_Expecter) FetchSnapshotsByDateAndRepository(ctx interface{}, orgID interface{}, request interface{}) *MockSnapshotDao_FetchSnapshotsByDateAndRepository_Call { +func (_e *MockSnapshotDao_Expecter) FetchSnapshotsByDateAndRepository(ctx any, orgID any, request any) *MockSnapshotDao_FetchSnapshotsByDateAndRepository_Call { return &MockSnapshotDao_FetchSnapshotsByDateAndRepository_Call{Call: _e.mock.On("FetchSnapshotsByDateAndRepository", ctx, orgID, request)} } @@ -4702,7 +4703,7 @@ type MockSnapshotDao_FetchSnapshotsModelByDateAndRepository_Call struct { // - ctx context.Context // - orgID string // - request api.ListSnapshotByDateRequest -func (_e *MockSnapshotDao_Expecter) FetchSnapshotsModelByDateAndRepository(ctx interface{}, orgID interface{}, request interface{}) *MockSnapshotDao_FetchSnapshotsModelByDateAndRepository_Call { +func (_e *MockSnapshotDao_Expecter) FetchSnapshotsModelByDateAndRepository(ctx any, orgID any, request any) *MockSnapshotDao_FetchSnapshotsModelByDateAndRepository_Call { return &MockSnapshotDao_FetchSnapshotsModelByDateAndRepository_Call{Call: _e.mock.On("FetchSnapshotsModelByDateAndRepository", ctx, orgID, request)} } @@ -4775,7 +4776,7 @@ type MockSnapshotDao_GetRepositoryConfigurationFile_Call struct { // - orgID string // - snapshotUUID string // - isLatest bool -func (_e *MockSnapshotDao_Expecter) GetRepositoryConfigurationFile(ctx interface{}, orgID interface{}, snapshotUUID interface{}, isLatest interface{}) *MockSnapshotDao_GetRepositoryConfigurationFile_Call { +func (_e *MockSnapshotDao_Expecter) GetRepositoryConfigurationFile(ctx any, orgID any, snapshotUUID any, isLatest any) *MockSnapshotDao_GetRepositoryConfigurationFile_Call { return &MockSnapshotDao_GetRepositoryConfigurationFile_Call{Call: _e.mock.On("GetRepositoryConfigurationFile", ctx, orgID, snapshotUUID, isLatest)} } @@ -4851,7 +4852,7 @@ type MockSnapshotDao_HasPublishedSnapshot_Call struct { // HasPublishedSnapshot is a helper method to define mock.On call // - ctx context.Context // - repoConfigUUID string -func (_e *MockSnapshotDao_Expecter) HasPublishedSnapshot(ctx interface{}, repoConfigUUID interface{}) *MockSnapshotDao_HasPublishedSnapshot_Call { +func (_e *MockSnapshotDao_Expecter) HasPublishedSnapshot(ctx any, repoConfigUUID any) *MockSnapshotDao_HasPublishedSnapshot_Call { return &MockSnapshotDao_HasPublishedSnapshot_Call{Call: _e.mock.On("HasPublishedSnapshot", ctx, repoConfigUUID)} } @@ -4926,7 +4927,7 @@ type MockSnapshotDao_List_Call struct { // - repoConfigUuid string // - paginationData api.PaginationData // - filterData api.FilterData -func (_e *MockSnapshotDao_Expecter) List(ctx interface{}, orgID interface{}, repoConfigUuid interface{}, paginationData interface{}, filterData interface{}) *MockSnapshotDao_List_Call { +func (_e *MockSnapshotDao_Expecter) List(ctx any, orgID any, repoConfigUuid any, paginationData any, filterData any) *MockSnapshotDao_List_Call { return &MockSnapshotDao_List_Call{Call: _e.mock.On("List", ctx, orgID, repoConfigUuid, paginationData, filterData)} } @@ -5016,7 +5017,7 @@ type MockSnapshotDao_ListByTemplate_Call struct { // - template api.TemplateResponse // - repositorySearch string // - paginationData api.PaginationData -func (_e *MockSnapshotDao_Expecter) ListByTemplate(ctx interface{}, orgID interface{}, template interface{}, repositorySearch interface{}, paginationData interface{}) *MockSnapshotDao_ListByTemplate_Call { +func (_e *MockSnapshotDao_Expecter) ListByTemplate(ctx any, orgID any, template any, repositorySearch any, paginationData any) *MockSnapshotDao_ListByTemplate_Call { return &MockSnapshotDao_ListByTemplate_Call{Call: _e.mock.On("ListByTemplate", ctx, orgID, template, repositorySearch, paginationData)} } @@ -5097,7 +5098,7 @@ type MockSnapshotDao_SetDetectedOSVersion_Call struct { // SetDetectedOSVersion is a helper method to define mock.On call // - ctx context.Context // - uuid string -func (_e *MockSnapshotDao_Expecter) SetDetectedOSVersion(ctx interface{}, uuid interface{}) *MockSnapshotDao_SetDetectedOSVersion_Call { +func (_e *MockSnapshotDao_Expecter) SetDetectedOSVersion(ctx any, uuid any) *MockSnapshotDao_SetDetectedOSVersion_Call { return &MockSnapshotDao_SetDetectedOSVersion_Call{Call: _e.mock.On("SetDetectedOSVersion", ctx, uuid)} } @@ -5154,7 +5155,7 @@ type MockSnapshotDao_SoftDelete_Call struct { // SoftDelete is a helper method to define mock.On call // - ctx context.Context // - snapUUID string -func (_e *MockSnapshotDao_Expecter) SoftDelete(ctx interface{}, snapUUID interface{}) *MockSnapshotDao_SoftDelete_Call { +func (_e *MockSnapshotDao_Expecter) SoftDelete(ctx any, snapUUID any) *MockSnapshotDao_SoftDelete_Call { return &MockSnapshotDao_SoftDelete_Call{Call: _e.mock.On("SoftDelete", ctx, snapUUID)} } @@ -5223,7 +5224,7 @@ type MockSnapshotDao_UpdatePublishedStatus_Call struct { // - published bool // - repoConfigUUID string // - snapshotUUID string -func (_e *MockSnapshotDao_Expecter) UpdatePublishedStatus(ctx interface{}, orgID interface{}, published interface{}, repoConfigUUID interface{}, snapshotUUID interface{}) *MockSnapshotDao_UpdatePublishedStatus_Call { +func (_e *MockSnapshotDao_Expecter) UpdatePublishedStatus(ctx any, orgID any, published any, repoConfigUUID any, snapshotUUID any) *MockSnapshotDao_UpdatePublishedStatus_Call { return &MockSnapshotDao_UpdatePublishedStatus_Call{Call: _e.mock.On("UpdatePublishedStatus", ctx, orgID, published, repoConfigUUID, snapshotUUID)} } @@ -5321,7 +5322,7 @@ type MockMetricsDao_OrganizationTotal_Call struct { // OrganizationTotal is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) OrganizationTotal(ctx interface{}) *MockMetricsDao_OrganizationTotal_Call { +func (_e *MockMetricsDao_Expecter) OrganizationTotal(ctx any) *MockMetricsDao_OrganizationTotal_Call { return &MockMetricsDao_OrganizationTotal_Call{Call: _e.mock.On("OrganizationTotal", ctx)} } @@ -5372,7 +5373,7 @@ type MockMetricsDao_PendingTasksAverageLatency_Call struct { // PendingTasksAverageLatency is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) PendingTasksAverageLatency(ctx interface{}) *MockMetricsDao_PendingTasksAverageLatency_Call { +func (_e *MockMetricsDao_Expecter) PendingTasksAverageLatency(ctx any) *MockMetricsDao_PendingTasksAverageLatency_Call { return &MockMetricsDao_PendingTasksAverageLatency_Call{Call: _e.mock.On("PendingTasksAverageLatency", ctx)} } @@ -5423,7 +5424,7 @@ type MockMetricsDao_PendingTasksCount_Call struct { // PendingTasksCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) PendingTasksCount(ctx interface{}) *MockMetricsDao_PendingTasksCount_Call { +func (_e *MockMetricsDao_Expecter) PendingTasksCount(ctx any) *MockMetricsDao_PendingTasksCount_Call { return &MockMetricsDao_PendingTasksCount_Call{Call: _e.mock.On("PendingTasksCount", ctx)} } @@ -5474,7 +5475,7 @@ type MockMetricsDao_PendingTasksOldestTask_Call struct { // PendingTasksOldestTask is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) PendingTasksOldestTask(ctx interface{}) *MockMetricsDao_PendingTasksOldestTask_Call { +func (_e *MockMetricsDao_Expecter) PendingTasksOldestTask(ctx any) *MockMetricsDao_PendingTasksOldestTask_Call { return &MockMetricsDao_PendingTasksOldestTask_Call{Call: _e.mock.On("PendingTasksOldestTask", ctx)} } @@ -5525,7 +5526,7 @@ type MockMetricsDao_PublicRepositoriesFailedIntrospectionCount_Call struct { // PublicRepositoriesFailedIntrospectionCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) PublicRepositoriesFailedIntrospectionCount(ctx interface{}) *MockMetricsDao_PublicRepositoriesFailedIntrospectionCount_Call { +func (_e *MockMetricsDao_Expecter) PublicRepositoriesFailedIntrospectionCount(ctx any) *MockMetricsDao_PublicRepositoriesFailedIntrospectionCount_Call { return &MockMetricsDao_PublicRepositoriesFailedIntrospectionCount_Call{Call: _e.mock.On("PublicRepositoriesFailedIntrospectionCount", ctx)} } @@ -5576,7 +5577,7 @@ type MockMetricsDao_RHReposSnapshotNotCompletedInLast36HoursCount_Call struct { // RHReposSnapshotNotCompletedInLast36HoursCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) RHReposSnapshotNotCompletedInLast36HoursCount(ctx interface{}) *MockMetricsDao_RHReposSnapshotNotCompletedInLast36HoursCount_Call { +func (_e *MockMetricsDao_Expecter) RHReposSnapshotNotCompletedInLast36HoursCount(ctx any) *MockMetricsDao_RHReposSnapshotNotCompletedInLast36HoursCount_Call { return &MockMetricsDao_RHReposSnapshotNotCompletedInLast36HoursCount_Call{Call: _e.mock.On("RHReposSnapshotNotCompletedInLast36HoursCount", ctx)} } @@ -5627,7 +5628,7 @@ type MockMetricsDao_RepositoriesCount_Call struct { // RepositoriesCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) RepositoriesCount(ctx interface{}) *MockMetricsDao_RepositoriesCount_Call { +func (_e *MockMetricsDao_Expecter) RepositoriesCount(ctx any) *MockMetricsDao_RepositoriesCount_Call { return &MockMetricsDao_RepositoriesCount_Call{Call: _e.mock.On("RepositoriesCount", ctx)} } @@ -5680,7 +5681,7 @@ type MockMetricsDao_RepositoriesIntrospectionCount_Call struct { // - ctx context.Context // - hours int // - public bool -func (_e *MockMetricsDao_Expecter) RepositoriesIntrospectionCount(ctx interface{}, hours interface{}, public interface{}) *MockMetricsDao_RepositoriesIntrospectionCount_Call { +func (_e *MockMetricsDao_Expecter) RepositoriesIntrospectionCount(ctx any, hours any, public any) *MockMetricsDao_RepositoriesIntrospectionCount_Call { return &MockMetricsDao_RepositoriesIntrospectionCount_Call{Call: _e.mock.On("RepositoriesIntrospectionCount", ctx, hours, public)} } @@ -5741,7 +5742,7 @@ type MockMetricsDao_RepositoryConfigsCount_Call struct { // RepositoryConfigsCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) RepositoryConfigsCount(ctx interface{}) *MockMetricsDao_RepositoryConfigsCount_Call { +func (_e *MockMetricsDao_Expecter) RepositoryConfigsCount(ctx any) *MockMetricsDao_RepositoryConfigsCount_Call { return &MockMetricsDao_RepositoryConfigsCount_Call{Call: _e.mock.On("RepositoryConfigsCount", ctx)} } @@ -5794,7 +5795,7 @@ type MockMetricsDao_TaskPendingTimeAverageByType_Call struct { // TaskPendingTimeAverageByType is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) TaskPendingTimeAverageByType(ctx interface{}) *MockMetricsDao_TaskPendingTimeAverageByType_Call { +func (_e *MockMetricsDao_Expecter) TaskPendingTimeAverageByType(ctx any) *MockMetricsDao_TaskPendingTimeAverageByType_Call { return &MockMetricsDao_TaskPendingTimeAverageByType_Call{Call: _e.mock.On("TaskPendingTimeAverageByType", ctx)} } @@ -5845,7 +5846,7 @@ type MockMetricsDao_TemplatesAgeAverage_Call struct { // TemplatesAgeAverage is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) TemplatesAgeAverage(ctx interface{}) *MockMetricsDao_TemplatesAgeAverage_Call { +func (_e *MockMetricsDao_Expecter) TemplatesAgeAverage(ctx any) *MockMetricsDao_TemplatesAgeAverage_Call { return &MockMetricsDao_TemplatesAgeAverage_Call{Call: _e.mock.On("TemplatesAgeAverage", ctx)} } @@ -5896,7 +5897,7 @@ type MockMetricsDao_TemplatesUpdatedInLast24HoursCount_Call struct { // TemplatesUpdatedInLast24HoursCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) TemplatesUpdatedInLast24HoursCount(ctx interface{}) *MockMetricsDao_TemplatesUpdatedInLast24HoursCount_Call { +func (_e *MockMetricsDao_Expecter) TemplatesUpdatedInLast24HoursCount(ctx any) *MockMetricsDao_TemplatesUpdatedInLast24HoursCount_Call { return &MockMetricsDao_TemplatesUpdatedInLast24HoursCount_Call{Call: _e.mock.On("TemplatesUpdatedInLast24HoursCount", ctx)} } @@ -5947,7 +5948,7 @@ type MockMetricsDao_TemplatesUseDateCount_Call struct { // TemplatesUseDateCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) TemplatesUseDateCount(ctx interface{}) *MockMetricsDao_TemplatesUseDateCount_Call { +func (_e *MockMetricsDao_Expecter) TemplatesUseDateCount(ctx any) *MockMetricsDao_TemplatesUseDateCount_Call { return &MockMetricsDao_TemplatesUseDateCount_Call{Call: _e.mock.On("TemplatesUseDateCount", ctx)} } @@ -5998,7 +5999,7 @@ type MockMetricsDao_TemplatesUseLatestCount_Call struct { // TemplatesUseLatestCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) TemplatesUseLatestCount(ctx interface{}) *MockMetricsDao_TemplatesUseLatestCount_Call { +func (_e *MockMetricsDao_Expecter) TemplatesUseLatestCount(ctx any) *MockMetricsDao_TemplatesUseLatestCount_Call { return &MockMetricsDao_TemplatesUseLatestCount_Call{Call: _e.mock.On("TemplatesUseLatestCount", ctx)} } @@ -6076,7 +6077,7 @@ type MockTaskInfoDao_Cleanup_Call struct { // Cleanup is a helper method to define mock.On call // - ctx context.Context -func (_e *MockTaskInfoDao_Expecter) Cleanup(ctx interface{}) *MockTaskInfoDao_Cleanup_Call { +func (_e *MockTaskInfoDao_Expecter) Cleanup(ctx any) *MockTaskInfoDao_Cleanup_Call { return &MockTaskInfoDao_Cleanup_Call{Call: _e.mock.On("Cleanup", ctx)} } @@ -6138,7 +6139,7 @@ type MockTaskInfoDao_Fetch_Call struct { // - ctx context.Context // - OrgID string // - id string -func (_e *MockTaskInfoDao_Expecter) Fetch(ctx interface{}, OrgID interface{}, id interface{}) *MockTaskInfoDao_Fetch_Call { +func (_e *MockTaskInfoDao_Expecter) Fetch(ctx any, OrgID any, id any) *MockTaskInfoDao_Fetch_Call { return &MockTaskInfoDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, OrgID, id)} } @@ -6178,11 +6179,11 @@ func (_c *MockTaskInfoDao_Fetch_Call) RunAndReturn(run func(ctx context.Context, // FetchActiveTasks provides a mock function for the type MockTaskInfoDao func (_mock *MockTaskInfoDao) FetchActiveTasks(ctx context.Context, orgID string, objectUUID string, taskTypes ...string) ([]string, error) { // string - _va := make([]interface{}, len(taskTypes)) + _va := make([]any, len(taskTypes)) for _i := range taskTypes { _va[_i] = taskTypes[_i] } - var _ca []interface{} + var _ca []any _ca = append(_ca, ctx, orgID, objectUUID) _ca = append(_ca, _va...) ret := _mock.Called(_ca...) @@ -6221,9 +6222,9 @@ type MockTaskInfoDao_FetchActiveTasks_Call struct { // - orgID string // - objectUUID string // - taskTypes ...string -func (_e *MockTaskInfoDao_Expecter) FetchActiveTasks(ctx interface{}, orgID interface{}, objectUUID interface{}, taskTypes ...interface{}) *MockTaskInfoDao_FetchActiveTasks_Call { +func (_e *MockTaskInfoDao_Expecter) FetchActiveTasks(ctx any, orgID any, objectUUID any, taskTypes ...any) *MockTaskInfoDao_FetchActiveTasks_Call { return &MockTaskInfoDao_FetchActiveTasks_Call{Call: _e.mock.On("FetchActiveTasks", - append([]interface{}{ctx, orgID, objectUUID}, taskTypes...)...)} + append([]any{ctx, orgID, objectUUID}, taskTypes...)...)} } func (_c *MockTaskInfoDao_FetchActiveTasks_Call) Run(run func(ctx context.Context, orgID string, objectUUID string, taskTypes ...string)) *MockTaskInfoDao_FetchActiveTasks_Call { @@ -6310,7 +6311,7 @@ type MockTaskInfoDao_List_Call struct { // - OrgID string // - pageData api.PaginationData // - filterData api.TaskInfoFilterData -func (_e *MockTaskInfoDao_Expecter) List(ctx interface{}, OrgID interface{}, pageData interface{}, filterData interface{}) *MockTaskInfoDao_List_Call { +func (_e *MockTaskInfoDao_Expecter) List(ctx any, OrgID any, pageData any, filterData any) *MockTaskInfoDao_List_Call { return &MockTaskInfoDao_List_Call{Call: _e.mock.On("List", ctx, OrgID, pageData, filterData)} } @@ -6413,7 +6414,7 @@ type MockAdminTaskDao_Fetch_Call struct { // Fetch is a helper method to define mock.On call // - ctx context.Context // - id string -func (_e *MockAdminTaskDao_Expecter) Fetch(ctx interface{}, id interface{}) *MockAdminTaskDao_Fetch_Call { +func (_e *MockAdminTaskDao_Expecter) Fetch(ctx any, id any) *MockAdminTaskDao_Fetch_Call { return &MockAdminTaskDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, id)} } @@ -6486,7 +6487,7 @@ type MockAdminTaskDao_List_Call struct { // - ctx context.Context // - pageData api.PaginationData // - filterData api.AdminTaskFilterData -func (_e *MockAdminTaskDao_Expecter) List(ctx interface{}, pageData interface{}, filterData interface{}) *MockAdminTaskDao_List_Call { +func (_e *MockAdminTaskDao_Expecter) List(ctx any, pageData any, filterData any) *MockAdminTaskDao_List_Call { return &MockAdminTaskDao_List_Call{Call: _e.mock.On("List", ctx, pageData, filterData)} } @@ -6576,7 +6577,7 @@ type MockDomainDao_Delete_Call struct { // - ctx context.Context // - orgId string // - domainName string -func (_e *MockDomainDao_Expecter) Delete(ctx interface{}, orgId interface{}, domainName interface{}) *MockDomainDao_Delete_Call { +func (_e *MockDomainDao_Expecter) Delete(ctx any, orgId any, domainName any) *MockDomainDao_Delete_Call { return &MockDomainDao_Delete_Call{Call: _e.mock.On("Delete", ctx, orgId, domainName)} } @@ -6647,7 +6648,7 @@ type MockDomainDao_Fetch_Call struct { // Fetch is a helper method to define mock.On call // - ctx context.Context // - orgId string -func (_e *MockDomainDao_Expecter) Fetch(ctx interface{}, orgId interface{}) *MockDomainDao_Fetch_Call { +func (_e *MockDomainDao_Expecter) Fetch(ctx any, orgId any) *MockDomainDao_Fetch_Call { return &MockDomainDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, orgId)} } @@ -6713,7 +6714,7 @@ type MockDomainDao_FetchOrCreateDomain_Call struct { // FetchOrCreateDomain is a helper method to define mock.On call // - ctx context.Context // - orgId string -func (_e *MockDomainDao_Expecter) FetchOrCreateDomain(ctx interface{}, orgId interface{}) *MockDomainDao_FetchOrCreateDomain_Call { +func (_e *MockDomainDao_Expecter) FetchOrCreateDomain(ctx any, orgId any) *MockDomainDao_FetchOrCreateDomain_Call { return &MockDomainDao_FetchOrCreateDomain_Call{Call: _e.mock.On("FetchOrCreateDomain", ctx, orgId)} } @@ -6780,7 +6781,7 @@ type MockDomainDao_List_Call struct { // List is a helper method to define mock.On call // - ctx context.Context -func (_e *MockDomainDao_Expecter) List(ctx interface{}) *MockDomainDao_List_Call { +func (_e *MockDomainDao_Expecter) List(ctx any) *MockDomainDao_List_Call { return &MockDomainDao_List_Call{Call: _e.mock.On("List", ctx)} } @@ -6869,7 +6870,7 @@ type MockPackageGroupDao_InsertForRepository_Call struct { // - ctx context.Context // - repoUuid string // - pkgGroups []yum.PackageGroup -func (_e *MockPackageGroupDao_Expecter) InsertForRepository(ctx interface{}, repoUuid interface{}, pkgGroups interface{}) *MockPackageGroupDao_InsertForRepository_Call { +func (_e *MockPackageGroupDao_Expecter) InsertForRepository(ctx any, repoUuid any, pkgGroups any) *MockPackageGroupDao_InsertForRepository_Call { return &MockPackageGroupDao_InsertForRepository_Call{Call: _e.mock.On("InsertForRepository", ctx, repoUuid, pkgGroups)} } @@ -6951,7 +6952,7 @@ type MockPackageGroupDao_List_Call struct { // - offset int // - search string // - sortBy string -func (_e *MockPackageGroupDao_Expecter) List(ctx interface{}, orgID interface{}, uuidRepo interface{}, limit interface{}, offset interface{}, search interface{}, sortBy interface{}) *MockPackageGroupDao_List_Call { +func (_e *MockPackageGroupDao_Expecter) List(ctx any, orgID any, uuidRepo any, limit any, offset any, search any, sortBy any) *MockPackageGroupDao_List_Call { return &MockPackageGroupDao_List_Call{Call: _e.mock.On("List", ctx, orgID, uuidRepo, limit, offset, search, sortBy)} } @@ -7032,7 +7033,7 @@ type MockPackageGroupDao_OrphanCleanup_Call struct { // OrphanCleanup is a helper method to define mock.On call // - ctx context.Context -func (_e *MockPackageGroupDao_Expecter) OrphanCleanup(ctx interface{}) *MockPackageGroupDao_OrphanCleanup_Call { +func (_e *MockPackageGroupDao_Expecter) OrphanCleanup(ctx any) *MockPackageGroupDao_OrphanCleanup_Call { return &MockPackageGroupDao_OrphanCleanup_Call{Call: _e.mock.On("OrphanCleanup", ctx)} } @@ -7096,7 +7097,7 @@ type MockPackageGroupDao_Search_Call struct { // - ctx context.Context // - orgID string // - request api.ContentUnitSearchRequest -func (_e *MockPackageGroupDao_Expecter) Search(ctx interface{}, orgID interface{}, request interface{}) *MockPackageGroupDao_Search_Call { +func (_e *MockPackageGroupDao_Expecter) Search(ctx any, orgID any, request any) *MockPackageGroupDao_Search_Call { return &MockPackageGroupDao_Search_Call{Call: _e.mock.On("Search", ctx, orgID, request)} } @@ -7170,7 +7171,7 @@ type MockPackageGroupDao_SearchSnapshotPackageGroups_Call struct { // - ctx context.Context // - orgId string // - request api.SnapshotSearchRpmRequest -func (_e *MockPackageGroupDao_Expecter) SearchSnapshotPackageGroups(ctx interface{}, orgId interface{}, request interface{}) *MockPackageGroupDao_SearchSnapshotPackageGroups_Call { +func (_e *MockPackageGroupDao_Expecter) SearchSnapshotPackageGroups(ctx any, orgId any, request any) *MockPackageGroupDao_SearchSnapshotPackageGroups_Call { return &MockPackageGroupDao_SearchSnapshotPackageGroups_Call{Call: _e.mock.On("SearchSnapshotPackageGroups", ctx, orgId, request)} } @@ -7269,7 +7270,7 @@ type MockEnvironmentDao_InsertForRepository_Call struct { // - ctx context.Context // - repoUuid string // - environments []yum.Environment -func (_e *MockEnvironmentDao_Expecter) InsertForRepository(ctx interface{}, repoUuid interface{}, environments interface{}) *MockEnvironmentDao_InsertForRepository_Call { +func (_e *MockEnvironmentDao_Expecter) InsertForRepository(ctx any, repoUuid any, environments any) *MockEnvironmentDao_InsertForRepository_Call { return &MockEnvironmentDao_InsertForRepository_Call{Call: _e.mock.On("InsertForRepository", ctx, repoUuid, environments)} } @@ -7351,7 +7352,7 @@ type MockEnvironmentDao_List_Call struct { // - offset int // - search string // - sortBy string -func (_e *MockEnvironmentDao_Expecter) List(ctx interface{}, orgID interface{}, uuidRepo interface{}, limit interface{}, offset interface{}, search interface{}, sortBy interface{}) *MockEnvironmentDao_List_Call { +func (_e *MockEnvironmentDao_Expecter) List(ctx any, orgID any, uuidRepo any, limit any, offset any, search any, sortBy any) *MockEnvironmentDao_List_Call { return &MockEnvironmentDao_List_Call{Call: _e.mock.On("List", ctx, orgID, uuidRepo, limit, offset, search, sortBy)} } @@ -7432,7 +7433,7 @@ type MockEnvironmentDao_OrphanCleanup_Call struct { // OrphanCleanup is a helper method to define mock.On call // - ctx context.Context -func (_e *MockEnvironmentDao_Expecter) OrphanCleanup(ctx interface{}) *MockEnvironmentDao_OrphanCleanup_Call { +func (_e *MockEnvironmentDao_Expecter) OrphanCleanup(ctx any) *MockEnvironmentDao_OrphanCleanup_Call { return &MockEnvironmentDao_OrphanCleanup_Call{Call: _e.mock.On("OrphanCleanup", ctx)} } @@ -7496,7 +7497,7 @@ type MockEnvironmentDao_Search_Call struct { // - ctx context.Context // - orgID string // - request api.ContentUnitSearchRequest -func (_e *MockEnvironmentDao_Expecter) Search(ctx interface{}, orgID interface{}, request interface{}) *MockEnvironmentDao_Search_Call { +func (_e *MockEnvironmentDao_Expecter) Search(ctx any, orgID any, request any) *MockEnvironmentDao_Search_Call { return &MockEnvironmentDao_Search_Call{Call: _e.mock.On("Search", ctx, orgID, request)} } @@ -7570,7 +7571,7 @@ type MockEnvironmentDao_SearchSnapshotEnvironments_Call struct { // - ctx context.Context // - orgId string // - request api.SnapshotSearchRpmRequest -func (_e *MockEnvironmentDao_Expecter) SearchSnapshotEnvironments(ctx interface{}, orgId interface{}, request interface{}) *MockEnvironmentDao_SearchSnapshotEnvironments_Call { +func (_e *MockEnvironmentDao_Expecter) SearchSnapshotEnvironments(ctx any, orgId any, request any) *MockEnvironmentDao_SearchSnapshotEnvironments_Call { return &MockEnvironmentDao_SearchSnapshotEnvironments_Call{Call: _e.mock.On("SearchSnapshotEnvironments", ctx, orgId, request)} } @@ -7660,7 +7661,7 @@ type MockTemplateDao_ClearDeletedAt_Call struct { // - ctx context.Context // - orgID string // - uuid string -func (_e *MockTemplateDao_Expecter) ClearDeletedAt(ctx interface{}, orgID interface{}, uuid interface{}) *MockTemplateDao_ClearDeletedAt_Call { +func (_e *MockTemplateDao_Expecter) ClearDeletedAt(ctx any, orgID any, uuid any) *MockTemplateDao_ClearDeletedAt_Call { return &MockTemplateDao_ClearDeletedAt_Call{Call: _e.mock.On("ClearDeletedAt", ctx, orgID, uuid)} } @@ -7731,7 +7732,7 @@ type MockTemplateDao_Create_Call struct { // Create is a helper method to define mock.On call // - ctx context.Context // - templateRequest api.TemplateRequest -func (_e *MockTemplateDao_Expecter) Create(ctx interface{}, templateRequest interface{}) *MockTemplateDao_Create_Call { +func (_e *MockTemplateDao_Expecter) Create(ctx any, templateRequest any) *MockTemplateDao_Create_Call { return &MockTemplateDao_Create_Call{Call: _e.mock.On("Create", ctx, templateRequest)} } @@ -7789,7 +7790,7 @@ type MockTemplateDao_Delete_Call struct { // - ctx context.Context // - orgID string // - uuid string -func (_e *MockTemplateDao_Expecter) Delete(ctx interface{}, orgID interface{}, uuid interface{}) *MockTemplateDao_Delete_Call { +func (_e *MockTemplateDao_Expecter) Delete(ctx any, orgID any, uuid any) *MockTemplateDao_Delete_Call { return &MockTemplateDao_Delete_Call{Call: _e.mock.On("Delete", ctx, orgID, uuid)} } @@ -7852,7 +7853,7 @@ type MockTemplateDao_DeleteTemplateRepoConfigs_Call struct { // - ctx context.Context // - templateUUID string // - keepRepoConfigUUIDs []string -func (_e *MockTemplateDao_Expecter) DeleteTemplateRepoConfigs(ctx interface{}, templateUUID interface{}, keepRepoConfigUUIDs interface{}) *MockTemplateDao_DeleteTemplateRepoConfigs_Call { +func (_e *MockTemplateDao_Expecter) DeleteTemplateRepoConfigs(ctx any, templateUUID any, keepRepoConfigUUIDs any) *MockTemplateDao_DeleteTemplateRepoConfigs_Call { return &MockTemplateDao_DeleteTemplateRepoConfigs_Call{Call: _e.mock.On("DeleteTemplateRepoConfigs", ctx, templateUUID, keepRepoConfigUUIDs)} } @@ -7914,7 +7915,7 @@ type MockTemplateDao_DeleteTemplateSnapshot_Call struct { // DeleteTemplateSnapshot is a helper method to define mock.On call // - ctx context.Context // - snapshotUUID string -func (_e *MockTemplateDao_Expecter) DeleteTemplateSnapshot(ctx interface{}, snapshotUUID interface{}) *MockTemplateDao_DeleteTemplateSnapshot_Call { +func (_e *MockTemplateDao_Expecter) DeleteTemplateSnapshot(ctx any, snapshotUUID any) *MockTemplateDao_DeleteTemplateSnapshot_Call { return &MockTemplateDao_DeleteTemplateSnapshot_Call{Call: _e.mock.On("DeleteTemplateSnapshot", ctx, snapshotUUID)} } @@ -7982,7 +7983,7 @@ type MockTemplateDao_Fetch_Call struct { // - orgID string // - uuid string // - includeSoftDel bool -func (_e *MockTemplateDao_Expecter) Fetch(ctx interface{}, orgID interface{}, uuid interface{}, includeSoftDel interface{}) *MockTemplateDao_Fetch_Call { +func (_e *MockTemplateDao_Expecter) Fetch(ctx any, orgID any, uuid any, includeSoftDel any) *MockTemplateDao_Fetch_Call { return &MockTemplateDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, orgID, uuid, includeSoftDel)} } @@ -8061,7 +8062,7 @@ type MockTemplateDao_GetDistributionHref_Call struct { // - ctx context.Context // - templateUUID string // - repoConfigUUID string -func (_e *MockTemplateDao_Expecter) GetDistributionHref(ctx interface{}, templateUUID interface{}, repoConfigUUID interface{}) *MockTemplateDao_GetDistributionHref_Call { +func (_e *MockTemplateDao_Expecter) GetDistributionHref(ctx any, templateUUID any, repoConfigUUID any) *MockTemplateDao_GetDistributionHref_Call { return &MockTemplateDao_GetDistributionHref_Call{Call: _e.mock.On("GetDistributionHref", ctx, templateUUID, repoConfigUUID)} } @@ -8159,7 +8160,7 @@ type MockTemplateDao_GetRepoChanges_Call struct { // - ctx context.Context // - templateUUID string // - newRepoConfigUUIDs []string -func (_e *MockTemplateDao_Expecter) GetRepoChanges(ctx interface{}, templateUUID interface{}, newRepoConfigUUIDs interface{}) *MockTemplateDao_GetRepoChanges_Call { +func (_e *MockTemplateDao_Expecter) GetRepoChanges(ctx any, templateUUID any, newRepoConfigUUIDs any) *MockTemplateDao_GetRepoChanges_Call { return &MockTemplateDao_GetRepoChanges_Call{Call: _e.mock.On("GetRepoChanges", ctx, templateUUID, newRepoConfigUUIDs)} } @@ -8231,7 +8232,7 @@ type MockTemplateDao_GetRepositoryConfigurationFile_Call struct { // - ctx context.Context // - orgID string // - templateUUID string -func (_e *MockTemplateDao_Expecter) GetRepositoryConfigurationFile(ctx interface{}, orgID interface{}, templateUUID interface{}) *MockTemplateDao_GetRepositoryConfigurationFile_Call { +func (_e *MockTemplateDao_Expecter) GetRepositoryConfigurationFile(ctx any, orgID any, templateUUID any) *MockTemplateDao_GetRepositoryConfigurationFile_Call { return &MockTemplateDao_GetRepositoryConfigurationFile_Call{Call: _e.mock.On("GetRepositoryConfigurationFile", ctx, orgID, templateUUID)} } @@ -8302,7 +8303,7 @@ type MockTemplateDao_InternalOnlyFetchByName_Call struct { // InternalOnlyFetchByName is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockTemplateDao_Expecter) InternalOnlyFetchByName(ctx interface{}, name interface{}) *MockTemplateDao_InternalOnlyFetchByName_Call { +func (_e *MockTemplateDao_Expecter) InternalOnlyFetchByName(ctx any, name any) *MockTemplateDao_InternalOnlyFetchByName_Call { return &MockTemplateDao_InternalOnlyFetchByName_Call{Call: _e.mock.On("InternalOnlyFetchByName", ctx, name)} } @@ -8371,7 +8372,7 @@ type MockTemplateDao_InternalOnlyGetTemplatesForRepoConfig_Call struct { // - ctx context.Context // - repoUUID string // - useLatestOnly bool -func (_e *MockTemplateDao_Expecter) InternalOnlyGetTemplatesForRepoConfig(ctx interface{}, repoUUID interface{}, useLatestOnly interface{}) *MockTemplateDao_InternalOnlyGetTemplatesForRepoConfig_Call { +func (_e *MockTemplateDao_Expecter) InternalOnlyGetTemplatesForRepoConfig(ctx any, repoUUID any, useLatestOnly any) *MockTemplateDao_InternalOnlyGetTemplatesForRepoConfig_Call { return &MockTemplateDao_InternalOnlyGetTemplatesForRepoConfig_Call{Call: _e.mock.On("InternalOnlyGetTemplatesForRepoConfig", ctx, repoUUID, useLatestOnly)} } @@ -8444,7 +8445,7 @@ type MockTemplateDao_InternalOnlyGetTemplatesForSnapshots_Call struct { // InternalOnlyGetTemplatesForSnapshots is a helper method to define mock.On call // - ctx context.Context // - snapUUIDs []string -func (_e *MockTemplateDao_Expecter) InternalOnlyGetTemplatesForSnapshots(ctx interface{}, snapUUIDs interface{}) *MockTemplateDao_InternalOnlyGetTemplatesForSnapshots_Call { +func (_e *MockTemplateDao_Expecter) InternalOnlyGetTemplatesForSnapshots(ctx any, snapUUIDs any) *MockTemplateDao_InternalOnlyGetTemplatesForSnapshots_Call { return &MockTemplateDao_InternalOnlyGetTemplatesForSnapshots_Call{Call: _e.mock.On("InternalOnlyGetTemplatesForSnapshots", ctx, snapUUIDs)} } @@ -8519,7 +8520,7 @@ type MockTemplateDao_List_Call struct { // - includeSoftDel bool // - paginationData api.PaginationData // - filterData api.TemplateFilterData -func (_e *MockTemplateDao_Expecter) List(ctx interface{}, orgID interface{}, includeSoftDel interface{}, paginationData interface{}, filterData interface{}) *MockTemplateDao_List_Call { +func (_e *MockTemplateDao_Expecter) List(ctx any, orgID any, includeSoftDel any, paginationData any, filterData any) *MockTemplateDao_List_Call { return &MockTemplateDao_List_Call{Call: _e.mock.On("List", ctx, orgID, includeSoftDel, paginationData, filterData)} } @@ -8591,7 +8592,7 @@ type MockTemplateDao_SetEnvironmentCreated_Call struct { // SetEnvironmentCreated is a helper method to define mock.On call // - ctx context.Context // - templateUUID string -func (_e *MockTemplateDao_Expecter) SetEnvironmentCreated(ctx interface{}, templateUUID interface{}) *MockTemplateDao_SetEnvironmentCreated_Call { +func (_e *MockTemplateDao_Expecter) SetEnvironmentCreated(ctx any, templateUUID any) *MockTemplateDao_SetEnvironmentCreated_Call { return &MockTemplateDao_SetEnvironmentCreated_Call{Call: _e.mock.On("SetEnvironmentCreated", ctx, templateUUID)} } @@ -8649,7 +8650,7 @@ type MockTemplateDao_SoftDelete_Call struct { // - ctx context.Context // - orgID string // - uuid string -func (_e *MockTemplateDao_Expecter) SoftDelete(ctx interface{}, orgID interface{}, uuid interface{}) *MockTemplateDao_SoftDelete_Call { +func (_e *MockTemplateDao_Expecter) SoftDelete(ctx any, orgID any, uuid any) *MockTemplateDao_SoftDelete_Call { return &MockTemplateDao_SoftDelete_Call{Call: _e.mock.On("SoftDelete", ctx, orgID, uuid)} } @@ -8722,7 +8723,7 @@ type MockTemplateDao_Update_Call struct { // - orgID string // - uuid string // - templParams api.TemplateUpdateRequest -func (_e *MockTemplateDao_Expecter) Update(ctx interface{}, orgID interface{}, uuid interface{}, templParams interface{}) *MockTemplateDao_Update_Call { +func (_e *MockTemplateDao_Expecter) Update(ctx any, orgID any, uuid any, templParams any) *MockTemplateDao_Update_Call { return &MockTemplateDao_Update_Call{Call: _e.mock.On("Update", ctx, orgID, uuid, templParams)} } @@ -8792,7 +8793,7 @@ type MockTemplateDao_UpdateDistributionHrefs_Call struct { // - repoUUIDs []string // - snapshots []models.Snapshot // - repoDistributionMap map[string]string -func (_e *MockTemplateDao_Expecter) UpdateDistributionHrefs(ctx interface{}, templateUUID interface{}, repoUUIDs interface{}, snapshots interface{}, repoDistributionMap interface{}) *MockTemplateDao_UpdateDistributionHrefs_Call { +func (_e *MockTemplateDao_Expecter) UpdateDistributionHrefs(ctx any, templateUUID any, repoUUIDs any, snapshots any, repoDistributionMap any) *MockTemplateDao_UpdateDistributionHrefs_Call { return &MockTemplateDao_UpdateDistributionHrefs_Call{Call: _e.mock.On("UpdateDistributionHrefs", ctx, templateUUID, repoUUIDs, snapshots, repoDistributionMap)} } @@ -8866,7 +8867,7 @@ type MockTemplateDao_UpdateLastError_Call struct { // - orgID string // - templateUUID string // - lastUpdateSnapshotError string -func (_e *MockTemplateDao_Expecter) UpdateLastError(ctx interface{}, orgID interface{}, templateUUID interface{}, lastUpdateSnapshotError interface{}) *MockTemplateDao_UpdateLastError_Call { +func (_e *MockTemplateDao_Expecter) UpdateLastError(ctx any, orgID any, templateUUID any, lastUpdateSnapshotError any) *MockTemplateDao_UpdateLastError_Call { return &MockTemplateDao_UpdateLastError_Call{Call: _e.mock.On("UpdateLastError", ctx, orgID, templateUUID, lastUpdateSnapshotError)} } @@ -8935,7 +8936,7 @@ type MockTemplateDao_UpdateLastUpdateTask_Call struct { // - taskUUID string // - orgID string // - templateUUID string -func (_e *MockTemplateDao_Expecter) UpdateLastUpdateTask(ctx interface{}, taskUUID interface{}, orgID interface{}, templateUUID interface{}) *MockTemplateDao_UpdateLastUpdateTask_Call { +func (_e *MockTemplateDao_Expecter) UpdateLastUpdateTask(ctx any, taskUUID any, orgID any, templateUUID any) *MockTemplateDao_UpdateLastUpdateTask_Call { return &MockTemplateDao_UpdateLastUpdateTask_Call{Call: _e.mock.On("UpdateLastUpdateTask", ctx, taskUUID, orgID, templateUUID)} } @@ -9004,7 +9005,7 @@ type MockTemplateDao_UpdateSnapshots_Call struct { // - templateUUID string // - repoUUIDs []string // - snapshots []models.Snapshot -func (_e *MockTemplateDao_Expecter) UpdateSnapshots(ctx interface{}, templateUUID interface{}, repoUUIDs interface{}, snapshots interface{}) *MockTemplateDao_UpdateSnapshots_Call { +func (_e *MockTemplateDao_Expecter) UpdateSnapshots(ctx any, templateUUID any, repoUUIDs any, snapshots any) *MockTemplateDao_UpdateSnapshots_Call { return &MockTemplateDao_UpdateSnapshots_Call{Call: _e.mock.On("UpdateSnapshots", ctx, templateUUID, repoUUIDs, snapshots)} } @@ -9106,7 +9107,7 @@ type MockMemoDao_GetLastSuccessfulPulpLogDate_Call struct { // GetLastSuccessfulPulpLogDate is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMemoDao_Expecter) GetLastSuccessfulPulpLogDate(ctx interface{}) *MockMemoDao_GetLastSuccessfulPulpLogDate_Call { +func (_e *MockMemoDao_Expecter) GetLastSuccessfulPulpLogDate(ctx any) *MockMemoDao_GetLastSuccessfulPulpLogDate_Call { return &MockMemoDao_GetLastSuccessfulPulpLogDate_Call{Call: _e.mock.On("GetLastSuccessfulPulpLogDate", ctx)} } @@ -9169,7 +9170,7 @@ type MockMemoDao_Read_Call struct { // Read is a helper method to define mock.On call // - ctx context.Context // - key string -func (_e *MockMemoDao_Expecter) Read(ctx interface{}, key interface{}) *MockMemoDao_Read_Call { +func (_e *MockMemoDao_Expecter) Read(ctx any, key any) *MockMemoDao_Read_Call { return &MockMemoDao_Read_Call{Call: _e.mock.On("Read", ctx, key)} } @@ -9226,7 +9227,7 @@ type MockMemoDao_SaveLastSuccessfulPulpLogDate_Call struct { // SaveLastSuccessfulPulpLogDate is a helper method to define mock.On call // - ctx context.Context // - date time.Time -func (_e *MockMemoDao_Expecter) SaveLastSuccessfulPulpLogDate(ctx interface{}, date interface{}) *MockMemoDao_SaveLastSuccessfulPulpLogDate_Call { +func (_e *MockMemoDao_Expecter) SaveLastSuccessfulPulpLogDate(ctx any, date any) *MockMemoDao_SaveLastSuccessfulPulpLogDate_Call { return &MockMemoDao_SaveLastSuccessfulPulpLogDate_Call{Call: _e.mock.On("SaveLastSuccessfulPulpLogDate", ctx, date)} } @@ -9295,7 +9296,7 @@ type MockMemoDao_Write_Call struct { // - ctx context.Context // - key string // - memo json.RawMessage -func (_e *MockMemoDao_Expecter) Write(ctx interface{}, key interface{}, memo interface{}) *MockMemoDao_Write_Call { +func (_e *MockMemoDao_Expecter) Write(ctx any, key any, memo any) *MockMemoDao_Write_Call { return &MockMemoDao_Write_Call{Call: _e.mock.On("Write", ctx, key, memo)} } @@ -9384,7 +9385,7 @@ type MockMavenPackagesDao_Create_Call struct { // Create is a helper method to define mock.On call // - ctx context.Context // - mavenPackage *models.MavenPackage -func (_e *MockMavenPackagesDao_Expecter) Create(ctx interface{}, mavenPackage interface{}) *MockMavenPackagesDao_Create_Call { +func (_e *MockMavenPackagesDao_Expecter) Create(ctx any, mavenPackage any) *MockMavenPackagesDao_Create_Call { return &MockMavenPackagesDao_Create_Call{Call: _e.mock.On("Create", ctx, mavenPackage)} } @@ -9453,7 +9454,7 @@ type MockMavenPackagesDao_Fetch_Call struct { // - ctx context.Context // - groupID string // - name string -func (_e *MockMavenPackagesDao_Expecter) Fetch(ctx interface{}, groupID interface{}, name interface{}) *MockMavenPackagesDao_Fetch_Call { +func (_e *MockMavenPackagesDao_Expecter) Fetch(ctx any, groupID any, name any) *MockMavenPackagesDao_Fetch_Call { return &MockMavenPackagesDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, groupID, name)} } @@ -9517,6 +9518,214 @@ func (_m *MockLightwellAdvisoryDao) EXPECT() *MockLightwellAdvisoryDao_Expecter return &MockLightwellAdvisoryDao_Expecter{mock: &_m.Mock} } +// CountAdvisoriesByRepo provides a mock function for the type MockLightwellAdvisoryDao +func (_mock *MockLightwellAdvisoryDao) CountAdvisoriesByRepo(ctx context.Context, repoConfigUUID uuid.UUID) (int64, error) { + ret := _mock.Called(ctx, repoConfigUUID) + + if len(ret) == 0 { + panic("no return value specified for CountAdvisoriesByRepo") + } + + var r0 int64 + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID) (int64, error)); ok { + return returnFunc(ctx, repoConfigUUID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID) int64); ok { + r0 = returnFunc(ctx, repoConfigUUID) + } else { + r0 = ret.Get(0).(int64) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { + r1 = returnFunc(ctx, repoConfigUUID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CountAdvisoriesByRepo' +type MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call struct { + *mock.Call +} + +// CountAdvisoriesByRepo is a helper method to define mock.On call +// - ctx context.Context +// - repoConfigUUID uuid.UUID +func (_e *MockLightwellAdvisoryDao_Expecter) CountAdvisoriesByRepo(ctx any, repoConfigUUID any) *MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call { + return &MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call{Call: _e.mock.On("CountAdvisoriesByRepo", ctx, repoConfigUUID)} +} + +func (_c *MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call) Run(run func(ctx context.Context, repoConfigUUID uuid.UUID)) *MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uuid.UUID + if args[1] != nil { + arg1 = args[1].(uuid.UUID) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call) Return(n int64, err error) *MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call { + _c.Call.Return(n, err) + return _c +} + +func (_c *MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call) RunAndReturn(run func(ctx context.Context, repoConfigUUID uuid.UUID) (int64, error)) *MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call { + _c.Call.Return(run) + return _c +} + +// ListAdvisories provides a mock function for the type MockLightwellAdvisoryDao +func (_mock *MockLightwellAdvisoryDao) ListAdvisories(ctx context.Context, opts ListLightwellAdvisoriesOptions) ([]api.LightwellAdvisoryResponse, int64, error) { + ret := _mock.Called(ctx, opts) + + if len(ret) == 0 { + panic("no return value specified for ListAdvisories") + } + + var r0 []api.LightwellAdvisoryResponse + var r1 int64 + var r2 error + if returnFunc, ok := ret.Get(0).(func(context.Context, ListLightwellAdvisoriesOptions) ([]api.LightwellAdvisoryResponse, int64, error)); ok { + return returnFunc(ctx, opts) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, ListLightwellAdvisoriesOptions) []api.LightwellAdvisoryResponse); ok { + r0 = returnFunc(ctx, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]api.LightwellAdvisoryResponse) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, ListLightwellAdvisoriesOptions) int64); ok { + r1 = returnFunc(ctx, opts) + } else { + r1 = ret.Get(1).(int64) + } + if returnFunc, ok := ret.Get(2).(func(context.Context, ListLightwellAdvisoriesOptions) error); ok { + r2 = returnFunc(ctx, opts) + } else { + r2 = ret.Error(2) + } + return r0, r1, r2 +} + +// MockLightwellAdvisoryDao_ListAdvisories_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAdvisories' +type MockLightwellAdvisoryDao_ListAdvisories_Call struct { + *mock.Call +} + +// ListAdvisories is a helper method to define mock.On call +// - ctx context.Context +// - opts ListLightwellAdvisoriesOptions +func (_e *MockLightwellAdvisoryDao_Expecter) ListAdvisories(ctx any, opts any) *MockLightwellAdvisoryDao_ListAdvisories_Call { + return &MockLightwellAdvisoryDao_ListAdvisories_Call{Call: _e.mock.On("ListAdvisories", ctx, opts)} +} + +func (_c *MockLightwellAdvisoryDao_ListAdvisories_Call) Run(run func(ctx context.Context, opts ListLightwellAdvisoriesOptions)) *MockLightwellAdvisoryDao_ListAdvisories_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 ListLightwellAdvisoriesOptions + if args[1] != nil { + arg1 = args[1].(ListLightwellAdvisoriesOptions) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockLightwellAdvisoryDao_ListAdvisories_Call) Return(lightwellAdvisoryResponses []api.LightwellAdvisoryResponse, n int64, err error) *MockLightwellAdvisoryDao_ListAdvisories_Call { + _c.Call.Return(lightwellAdvisoryResponses, n, err) + return _c +} + +func (_c *MockLightwellAdvisoryDao_ListAdvisories_Call) RunAndReturn(run func(ctx context.Context, opts ListLightwellAdvisoriesOptions) ([]api.LightwellAdvisoryResponse, int64, error)) *MockLightwellAdvisoryDao_ListAdvisories_Call { + _c.Call.Return(run) + return _c +} + +// ListAdvisoriesByCveID provides a mock function for the type MockLightwellAdvisoryDao +func (_mock *MockLightwellAdvisoryDao) ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]LightwellAdvisoryCveMatch, error) { + ret := _mock.Called(ctx, cveID) + + if len(ret) == 0 { + panic("no return value specified for ListAdvisoriesByCveID") + } + + var r0 []LightwellAdvisoryCveMatch + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string) ([]LightwellAdvisoryCveMatch, error)); ok { + return returnFunc(ctx, cveID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string) []LightwellAdvisoryCveMatch); ok { + r0 = returnFunc(ctx, cveID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]LightwellAdvisoryCveMatch) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = returnFunc(ctx, cveID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAdvisoriesByCveID' +type MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call struct { + *mock.Call +} + +// ListAdvisoriesByCveID is a helper method to define mock.On call +// - ctx context.Context +// - cveID string +func (_e *MockLightwellAdvisoryDao_Expecter) ListAdvisoriesByCveID(ctx any, cveID any) *MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call { + return &MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call{Call: _e.mock.On("ListAdvisoriesByCveID", ctx, cveID)} +} + +func (_c *MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call) Run(run func(ctx context.Context, cveID string)) *MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call) Return(lightwellAdvisoryCveMatchs []LightwellAdvisoryCveMatch, err error) *MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call { + _c.Call.Return(lightwellAdvisoryCveMatchs, err) + return _c +} + +func (_c *MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call) RunAndReturn(run func(ctx context.Context, cveID string) ([]LightwellAdvisoryCveMatch, error)) *MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call { + _c.Call.Return(run) + return _c +} + // ListByRepository provides a mock function for the type MockLightwellAdvisoryDao func (_mock *MockLightwellAdvisoryDao) ListByRepository(ctx context.Context, repoConfigUUID string) ([]LightwellAdvisoryInput, error) { ret := _mock.Called(ctx, repoConfigUUID) @@ -9553,7 +9762,7 @@ type MockLightwellAdvisoryDao_ListByRepository_Call struct { // ListByRepository is a helper method to define mock.On call // - ctx context.Context // - repoConfigUUID string -func (_e *MockLightwellAdvisoryDao_Expecter) ListByRepository(ctx interface{}, repoConfigUUID interface{}) *MockLightwellAdvisoryDao_ListByRepository_Call { +func (_e *MockLightwellAdvisoryDao_Expecter) ListByRepository(ctx any, repoConfigUUID any) *MockLightwellAdvisoryDao_ListByRepository_Call { return &MockLightwellAdvisoryDao_ListByRepository_Call{Call: _e.mock.On("ListByRepository", ctx, repoConfigUUID)} } @@ -9621,7 +9830,7 @@ type MockLightwellAdvisoryDao_ListUnnotifiedAdvisories_Call struct { // ListUnnotifiedAdvisories is a helper method to define mock.On call // - ctx context.Context // - repoConfigUUID string -func (_e *MockLightwellAdvisoryDao_Expecter) ListUnnotifiedAdvisories(ctx interface{}, repoConfigUUID interface{}) *MockLightwellAdvisoryDao_ListUnnotifiedAdvisories_Call { +func (_e *MockLightwellAdvisoryDao_Expecter) ListUnnotifiedAdvisories(ctx any, repoConfigUUID any) *MockLightwellAdvisoryDao_ListUnnotifiedAdvisories_Call { return &MockLightwellAdvisoryDao_ListUnnotifiedAdvisories_Call{Call: _e.mock.On("ListUnnotifiedAdvisories", ctx, repoConfigUUID)} } @@ -9679,7 +9888,7 @@ type MockLightwellAdvisoryDao_MarkAsNotified_Call struct { // - ctx context.Context // - repoConfigUUID string // - data []LightwellNotificationData -func (_e *MockLightwellAdvisoryDao_Expecter) MarkAsNotified(ctx interface{}, repoConfigUUID interface{}, data interface{}) *MockLightwellAdvisoryDao_MarkAsNotified_Call { +func (_e *MockLightwellAdvisoryDao_Expecter) MarkAsNotified(ctx any, repoConfigUUID any, data any) *MockLightwellAdvisoryDao_MarkAsNotified_Call { return &MockLightwellAdvisoryDao_MarkAsNotified_Call{Call: _e.mock.On("MarkAsNotified", ctx, repoConfigUUID, data)} } @@ -9743,7 +9952,7 @@ type MockLightwellAdvisoryDao_SyncForRepository_Call struct { // - repoConfigUUID string // - repoName string // - advisories []LightwellAdvisoryInput -func (_e *MockLightwellAdvisoryDao_Expecter) SyncForRepository(ctx interface{}, repoConfigUUID interface{}, repoName interface{}, advisories interface{}) *MockLightwellAdvisoryDao_SyncForRepository_Call { +func (_e *MockLightwellAdvisoryDao_Expecter) SyncForRepository(ctx any, repoConfigUUID any, repoName any, advisories any) *MockLightwellAdvisoryDao_SyncForRepository_Call { return &MockLightwellAdvisoryDao_SyncForRepository_Call{Call: _e.mock.On("SyncForRepository", ctx, repoConfigUUID, repoName, advisories)} } @@ -9868,7 +10077,7 @@ type MockLightwellVulnerabilityDao_List_Call struct { // List is a helper method to define mock.On call // - ctx context.Context // - opts ListLightwellVulnerabilitiesOptions -func (_e *MockLightwellVulnerabilityDao_Expecter) List(ctx interface{}, opts interface{}) *MockLightwellVulnerabilityDao_List_Call { +func (_e *MockLightwellVulnerabilityDao_Expecter) List(ctx any, opts any) *MockLightwellVulnerabilityDao_List_Call { return &MockLightwellVulnerabilityDao_List_Call{Call: _e.mock.On("List", ctx, opts)} } @@ -9935,7 +10144,7 @@ type MockLightwellVulnerabilityDao_ListCustomerIds_Call struct { // ListCustomerIds is a helper method to define mock.On call // - ctx context.Context -func (_e *MockLightwellVulnerabilityDao_Expecter) ListCustomerIds(ctx interface{}) *MockLightwellVulnerabilityDao_ListCustomerIds_Call { +func (_e *MockLightwellVulnerabilityDao_Expecter) ListCustomerIds(ctx any) *MockLightwellVulnerabilityDao_ListCustomerIds_Call { return &MockLightwellVulnerabilityDao_ListCustomerIds_Call{Call: _e.mock.On("ListCustomerIds", ctx)} } @@ -9998,7 +10207,7 @@ type MockLightwellVulnerabilityDao_ListLtwlsuptTicketIds_Call struct { // ListLtwlsuptTicketIds is a helper method to define mock.On call // - ctx context.Context // - customerID string -func (_e *MockLightwellVulnerabilityDao_Expecter) ListLtwlsuptTicketIds(ctx interface{}, customerID interface{}) *MockLightwellVulnerabilityDao_ListLtwlsuptTicketIds_Call { +func (_e *MockLightwellVulnerabilityDao_Expecter) ListLtwlsuptTicketIds(ctx any, customerID any) *MockLightwellVulnerabilityDao_ListLtwlsuptTicketIds_Call { return &MockLightwellVulnerabilityDao_ListLtwlsuptTicketIds_Call{Call: _e.mock.On("ListLtwlsuptTicketIds", ctx, customerID)} } @@ -10094,7 +10303,7 @@ type MockUserPreferenceDao_List_Call struct { // - ctx context.Context // - orgID string // - userID string -func (_e *MockUserPreferenceDao_Expecter) List(ctx interface{}, orgID interface{}, userID interface{}) *MockUserPreferenceDao_List_Call { +func (_e *MockUserPreferenceDao_Expecter) List(ctx any, orgID any, userID any) *MockUserPreferenceDao_List_Call { return &MockUserPreferenceDao_List_Call{Call: _e.mock.On("List", ctx, orgID, userID)} } @@ -10168,7 +10377,7 @@ type MockUserPreferenceDao_ListDistinctOrgsByPreference_Call struct { // - ctx context.Context // - label string // - value string -func (_e *MockUserPreferenceDao_Expecter) ListDistinctOrgsByPreference(ctx interface{}, label interface{}, value interface{}) *MockUserPreferenceDao_ListDistinctOrgsByPreference_Call { +func (_e *MockUserPreferenceDao_Expecter) ListDistinctOrgsByPreference(ctx any, label any, value any) *MockUserPreferenceDao_ListDistinctOrgsByPreference_Call { return &MockUserPreferenceDao_ListDistinctOrgsByPreference_Call{Call: _e.mock.On("ListDistinctOrgsByPreference", ctx, label, value)} } @@ -10242,7 +10451,7 @@ type MockUserPreferenceDao_Set_Call struct { // - userID string // - label string // - value string -func (_e *MockUserPreferenceDao_Expecter) Set(ctx interface{}, orgID interface{}, userID interface{}, label interface{}, value interface{}) *MockUserPreferenceDao_Set_Call { +func (_e *MockUserPreferenceDao_Expecter) Set(ctx any, orgID any, userID any, label any, value any) *MockUserPreferenceDao_Set_Call { return &MockUserPreferenceDao_Set_Call{Call: _e.mock.On("Set", ctx, orgID, userID, label, value)} } @@ -10351,7 +10560,7 @@ type MockCoverageReportDao_Create_Call struct { // - ctx context.Context // - report CreateCoverageReportParams // - upload CreateCoverageUploadParams -func (_e *MockCoverageReportDao_Expecter) Create(ctx interface{}, report interface{}, upload interface{}) *MockCoverageReportDao_Create_Call { +func (_e *MockCoverageReportDao_Expecter) Create(ctx any, report any, upload any) *MockCoverageReportDao_Create_Call { return &MockCoverageReportDao_Create_Call{Call: _e.mock.On("Create", ctx, report, upload)} } @@ -10389,8 +10598,8 @@ func (_c *MockCoverageReportDao_Create_Call) RunAndReturn(run func(ctx context.C } // Fetch provides a mock function for the type MockCoverageReportDao -func (_mock *MockCoverageReportDao) Fetch(ctx context.Context, orgID string, uuid string) (api.CoverageReportResponse, error) { - ret := _mock.Called(ctx, orgID, uuid) +func (_mock *MockCoverageReportDao) Fetch(ctx context.Context, orgID string, uuid1 string) (api.CoverageReportResponse, error) { + ret := _mock.Called(ctx, orgID, uuid1) if len(ret) == 0 { panic("no return value specified for Fetch") @@ -10399,15 +10608,15 @@ func (_mock *MockCoverageReportDao) Fetch(ctx context.Context, orgID string, uui var r0 api.CoverageReportResponse var r1 error if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) (api.CoverageReportResponse, error)); ok { - return returnFunc(ctx, orgID, uuid) + return returnFunc(ctx, orgID, uuid1) } if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) api.CoverageReportResponse); ok { - r0 = returnFunc(ctx, orgID, uuid) + r0 = returnFunc(ctx, orgID, uuid1) } else { r0 = ret.Get(0).(api.CoverageReportResponse) } if returnFunc, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = returnFunc(ctx, orgID, uuid) + r1 = returnFunc(ctx, orgID, uuid1) } else { r1 = ret.Error(1) } @@ -10422,12 +10631,12 @@ type MockCoverageReportDao_Fetch_Call struct { // Fetch is a helper method to define mock.On call // - ctx context.Context // - orgID string -// - uuid string -func (_e *MockCoverageReportDao_Expecter) Fetch(ctx interface{}, orgID interface{}, uuid interface{}) *MockCoverageReportDao_Fetch_Call { - return &MockCoverageReportDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, orgID, uuid)} +// - uuid1 string +func (_e *MockCoverageReportDao_Expecter) Fetch(ctx any, orgID any, uuid1 any) *MockCoverageReportDao_Fetch_Call { + return &MockCoverageReportDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, orgID, uuid1)} } -func (_c *MockCoverageReportDao_Fetch_Call) Run(run func(ctx context.Context, orgID string, uuid string)) *MockCoverageReportDao_Fetch_Call { +func (_c *MockCoverageReportDao_Fetch_Call) Run(run func(ctx context.Context, orgID string, uuid1 string)) *MockCoverageReportDao_Fetch_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -10455,7 +10664,7 @@ func (_c *MockCoverageReportDao_Fetch_Call) Return(coverageReportResponse api.Co return _c } -func (_c *MockCoverageReportDao_Fetch_Call) RunAndReturn(run func(ctx context.Context, orgID string, uuid string) (api.CoverageReportResponse, error)) *MockCoverageReportDao_Fetch_Call { +func (_c *MockCoverageReportDao_Fetch_Call) RunAndReturn(run func(ctx context.Context, orgID string, uuid1 string) (api.CoverageReportResponse, error)) *MockCoverageReportDao_Fetch_Call { _c.Call.Return(run) return _c } @@ -10503,7 +10712,7 @@ type MockCoverageReportDao_ListPackages_Call struct { // - reportUUID string // - pageData api.PaginationData // - filterData api.ListCoverageReportPackagesRequest -func (_e *MockCoverageReportDao_Expecter) ListPackages(ctx interface{}, orgID interface{}, reportUUID interface{}, pageData interface{}, filterData interface{}) *MockCoverageReportDao_ListPackages_Call { +func (_e *MockCoverageReportDao_Expecter) ListPackages(ctx any, orgID any, reportUUID any, pageData any, filterData any) *MockCoverageReportDao_ListPackages_Call { return &MockCoverageReportDao_ListPackages_Call{Call: _e.mock.On("ListPackages", ctx, orgID, reportUUID, pageData, filterData)} } diff --git a/pkg/dao/interfaces.go b/pkg/dao/interfaces.go index cfc510cd5..00202b97a 100644 --- a/pkg/dao/interfaces.go +++ b/pkg/dao/interfaces.go @@ -11,6 +11,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" "github.com/content-services/content-sources-backend/pkg/clients/roadmap_client" csdb "github.com/content-services/content-sources-backend/pkg/db" + "github.com/google/uuid" "github.com/content-services/content-sources-backend/pkg/models" "github.com/content-services/tang/pkg/tangy" "github.com/content-services/yummy/pkg/yum" @@ -80,7 +81,7 @@ func GetDaoRegistry(db *gorm.DB) *DaoRegistry { Uploads: uploadDaoImpl{db: db, pulpClient: pulp_client.GetPulpClientWithDomain("")}, Memo: memoDaoImpl{db: db}, MavenPackages: mavenPackagesDaoImpl{db: db}, - LightwellAdvisory: lightwellAdvisoryDaoImpl{db: db}, + LightwellAdvisory: lightwellAdvisoryDaoImpl{db: db, querier: csdb.LightwellQueries}, LightwellVulnerability: newLightwellVulnerabilityDao(csdb.LightwellQueries), UserPreference: userPreferenceDaoImpl{db: db}, CoverageReport: coverageReportDaoImpl{db: db}, @@ -277,6 +278,9 @@ type LightwellAdvisoryDao interface { ListByRepository(ctx context.Context, repoConfigUUID string) ([]LightwellAdvisoryInput, error) ListUnnotifiedAdvisories(ctx context.Context, repoConfigUUID string) ([]LightwellNotificationData, error) MarkAsNotified(ctx context.Context, repoConfigUUID string, data []LightwellNotificationData) error + ListAdvisories(ctx context.Context, opts ListLightwellAdvisoriesOptions) ([]api.LightwellAdvisoryResponse, int64, error) + ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]LightwellAdvisoryCveMatch, error) + CountAdvisoriesByRepo(ctx context.Context, repoConfigUUID uuid.UUID) (int64, error) } type LightwellVulnerabilityDao interface { diff --git a/pkg/dao/lightwell_advisory.go b/pkg/dao/lightwell_advisory.go index 933719304..bc2975d8a 100644 --- a/pkg/dao/lightwell_advisory.go +++ b/pkg/dao/lightwell_advisory.go @@ -5,7 +5,11 @@ import ( "fmt" "strings" + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/content-services/content-sources-backend/pkg/models" + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" "gorm.io/gorm" "gorm.io/gorm/clause" ) @@ -28,8 +32,25 @@ type LightwellNotificationData struct { ReferenceURLs []string } +type ListLightwellAdvisoriesOptions struct { + RepoName *string + PackageName *string + SeverityMin string + CveID *string + Limit int32 + Offset int32 +} + +type LightwellAdvisoryCveMatch struct { + PackageName string + FixedVersions []string + RepoName string + Severity string +} + type lightwellAdvisoryDaoImpl struct { - db *gorm.DB + db *gorm.DB + querier store.Querier } func GetLightwellAdvisoryDao(db *gorm.DB) LightwellAdvisoryDao { @@ -162,3 +183,92 @@ func (d lightwellAdvisoryDaoImpl) MarkAsNotified(ctx context.Context, repoConfig } return nil } + +var severityMap = map[string]int16{ + "low": 1, + "moderate": 2, + "important": 3, + "critical": 4, +} + +func parseSeverityMin(s string) (pgtype.Int2, error) { + if s == "" { + return pgtype.Int2{}, nil + } + val, ok := severityMap[s] + if !ok { + return pgtype.Int2{}, fmt.Errorf("invalid severity: %s (must be one of: low, moderate, important, critical)", s) + } + return pgtype.Int2{Int16: val, Valid: true}, nil +} + +func (d lightwellAdvisoryDaoImpl) ListAdvisories(ctx context.Context, opts ListLightwellAdvisoriesOptions) ([]api.LightwellAdvisoryResponse, int64, error) { + severityMin, err := parseSeverityMin(opts.SeverityMin) + if err != nil { + return nil, 0, err + } + + rows, err := d.querier.ListAdvisories(ctx, store.ListAdvisoriesParams{ + RepoName: opts.RepoName, + PackageName: opts.PackageName, + SeverityMin: severityMin, + CveID: opts.CveID, + PageOffset: opts.Offset, + PageLimit: opts.Limit, + }) + if err != nil { + return nil, 0, fmt.Errorf("failed to list advisories: %w", err) + } + + var totalCount int64 + if len(rows) > 0 { + totalCount = rows[0].TotalCount + } + + data := make([]api.LightwellAdvisoryResponse, 0, len(rows)) + for _, row := range rows { + refURLs := row.ReferenceUrls + if refURLs == nil { + refURLs = []string{} + } + fixedVersions := row.FixedVersions + if fixedVersions == nil { + fixedVersions = []string{} + } + data = append(data, api.LightwellAdvisoryResponse{ + AdvisoryID: row.AdvisoryID, + Severity: row.Severity, + Details: row.Details, + ReferenceURLs: refURLs, + PackageName: row.PackageName, + FixedVersions: fixedVersions, + Repository: row.RepoName, + }) + } + return data, totalCount, nil +} + +func (d lightwellAdvisoryDaoImpl) ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]LightwellAdvisoryCveMatch, error) { + rows, err := d.querier.ListAdvisoriesByCveID(ctx, cveID) + if err != nil { + return nil, fmt.Errorf("failed to list advisories by CVE ID: %w", err) + } + matches := make([]LightwellAdvisoryCveMatch, 0, len(rows)) + for _, row := range rows { + matches = append(matches, LightwellAdvisoryCveMatch{ + PackageName: row.PackageName, + FixedVersions: row.FixedVersions, + RepoName: row.RepoName, + Severity: row.Severity, + }) + } + return matches, nil +} + +func (d lightwellAdvisoryDaoImpl) CountAdvisoriesByRepo(ctx context.Context, repoConfigUUID uuid.UUID) (int64, error) { + count, err := d.querier.CountAdvisoriesByRepo(ctx, repoConfigUUID) + if err != nil { + return 0, fmt.Errorf("failed to count advisories for repo %s: %w", repoConfigUUID, err) + } + return count, nil +} diff --git a/pkg/handler/api.go b/pkg/handler/api.go index bf37ccc74..d71046fe1 100644 --- a/pkg/handler/api.go +++ b/pkg/handler/api.go @@ -18,10 +18,8 @@ import ( "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" "github.com/content-services/content-sources-backend/pkg/db" - "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/content-services/content-sources-backend/pkg/tasks/client" "github.com/content-services/content-sources-backend/pkg/tasks/queue" - "github.com/jackc/pgx/v5/pgxpool" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" ) @@ -75,21 +73,12 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { } ch := cache.Initialize() - pgxPool, err := pgxpool.New(ctx, db.GetUrl()) - if err != nil { - log.Warn().Err(err).Msg("failed to create pgx pool for lightwell store; advisory endpoints disabled") - } - var lightwellQuerier store.Querier - if pgxPool != nil { - lightwellQuerier = store.New(pgxPool) - } - for i := 0; i < len(paths); i++ { group := engine.Group(paths[i]) group.GET("/openapi.json", openapi) daoReg := dao.GetDaoRegistry(db.DB) - RegisterRepositoryRoutes(group, daoReg, &taskClient, &fsClient, lightwellQuerier) + RegisterRepositoryRoutes(group, daoReg, &taskClient, &fsClient) RegisterRepositoryParameterRoutes(group, daoReg, &fsClient) RegisterRpmRoutes(group, daoReg) RegisterPopularRepositoriesRoutes(group, daoReg) @@ -109,12 +98,8 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { RegisterUserPreferencesRoutes(group, daoReg) RegisterLightwellVulnerabilityRoutes(group, daoReg) RegisterCoverageReportRoutes(group, daoReg) + RegisterLightwellAdvisoryRoutes(group, daoReg) - if lightwellQuerier != nil { - RegisterLightwellAdvisoryRoutes(group, lightwellQuerier) - } - - // Register package and build routes if tang client is available pulpClient := pulp_client.GetPulpClientWithDomain("") if config.Tang == nil { err = config.ConfigureTang() @@ -124,9 +109,7 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { } if config.Tang != nil { RegisterPackageRoutes(group, daoReg, *config.Tang, pulpClient) - if lightwellQuerier != nil { - RegisterLightwellPackageRoutes(group, lightwellQuerier, daoReg, *config.Tang, pulpClient) - } + RegisterLightwellPackageRoutes(group, daoReg, *config.Tang, pulpClient) } } diff --git a/pkg/handler/lightwell_advisories.go b/pkg/handler/lightwell_advisories.go index 250c5255a..79a71c9fa 100644 --- a/pkg/handler/lightwell_advisories.go +++ b/pkg/handler/lightwell_advisories.go @@ -4,22 +4,19 @@ import ( "net/http" "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/dao" ce "github.com/content-services/content-sources-backend/pkg/errors" - "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/content-services/content-sources-backend/pkg/rbac" - "github.com/jackc/pgx/v5/pgtype" "github.com/labstack/echo/v4" ) type LightwellAdvisoryHandler struct { - Store store.Querier + DaoRegistry dao.DaoRegistry } -func RegisterLightwellAdvisoryRoutes(engine *echo.Group, querier store.Querier) { - h := LightwellAdvisoryHandler{Store: querier} - // Flat cross-repo endpoint +func RegisterLightwellAdvisoryRoutes(engine *echo.Group, daoReg *dao.DaoRegistry) { + h := LightwellAdvisoryHandler{DaoRegistry: *daoReg} addRepoRoute(engine, http.MethodGet, "/lightwell/advisories", h.list, rbac.RbacVerbRead) - // Nested repo-scoped alias addRepoRoute(engine, http.MethodGet, "/lightwell/repositories/:repository_name/advisories", h.listRepoAdvisories, rbac.RbacVerbRead) } @@ -44,72 +41,31 @@ func (h *LightwellAdvisoryHandler) list(c echo.Context) error { page := ParsePagination(c) filters := parseLightwellAdvisoryFilters(c) - severityMin, err := parseSeverityMin(filters.SeverityMin) - if err != nil { - return ce.NewErrorResponse(http.StatusBadRequest, "Invalid severity_min", err.Error()) + opts := dao.ListLightwellAdvisoriesOptions{ + SeverityMin: filters.SeverityMin, + Limit: int32(page.Limit), //nolint:gosec // bounded by MaxLimit (200) + Offset: int32(page.Offset), //nolint:gosec // bounded by ParsePagination } - - var repoName *string if filters.Repository != "" { - repoName = &filters.Repository + opts.RepoName = &filters.Repository } - - var packageName *string if filters.PackageName != "" { - packageName = &filters.PackageName + opts.PackageName = &filters.PackageName } - - var cveID *string if filters.CveID != "" { - cveID = &filters.CveID + opts.CveID = &filters.CveID } - rows, err := h.Store.ListAdvisories(c.Request().Context(), store.ListAdvisoriesParams{ - RepoName: repoName, - PackageName: packageName, - SeverityMin: severityMin, - CveID: cveID, - PageOffset: int32(page.Offset), //nolint:gosec // bounded by ParsePagination - PageLimit: int32(page.Limit), //nolint:gosec // bounded by MaxLimit (200) - }) + data, totalCount, err := h.DaoRegistry.LightwellAdvisory.ListAdvisories(c.Request().Context(), opts) if err != nil { return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing advisories", err.Error()) } - var totalCount int64 - if len(rows) > 0 { - totalCount = rows[0].TotalCount - } - - resp := mapAdvisoryRowsToResponse(rows) + resp := api.LightwellAdvisoryCollectionResponse{Data: data} collResp := setCollectionResponseMetadata(&resp, c, totalCount) return c.JSON(http.StatusOK, collResp) } -func mapAdvisoryRowsToResponse(rows []store.ListAdvisoriesRow) api.LightwellAdvisoryCollectionResponse { - data := make([]api.LightwellAdvisoryResponse, 0, len(rows)) - for _, row := range rows { - refURLs := row.ReferenceUrls - if refURLs == nil { - refURLs = []string{} - } - fixedVersions := row.FixedVersions - if fixedVersions == nil { - fixedVersions = []string{} - } - data = append(data, api.LightwellAdvisoryResponse{ - AdvisoryID: row.AdvisoryID, - Severity: row.Severity, - Details: row.Details, - ReferenceURLs: refURLs, - PackageName: row.PackageName, - FixedVersions: fixedVersions, - Repository: row.RepoName, - }) - } - return api.LightwellAdvisoryCollectionResponse{Data: data} -} - func parseLightwellAdvisoryFilters(c echo.Context) api.LightwellAdvisoryFilterData { var filters api.LightwellAdvisoryFilterData _ = echo.QueryParamsBinder(c). @@ -121,32 +77,6 @@ func parseLightwellAdvisoryFilters(c echo.Context) api.LightwellAdvisoryFilterDa return filters } -var severityMap = map[string]int16{ - "low": 1, - "moderate": 2, - "important": 3, - "critical": 4, -} - -func parseSeverityMin(s string) (pgtype.Int2, error) { - if s == "" { - return pgtype.Int2{}, nil - } - val, ok := severityMap[s] - if !ok { - return pgtype.Int2{}, &invalidSeverityError{severity: s} - } - return pgtype.Int2{Int16: val, Valid: true}, nil -} - -type invalidSeverityError struct { - severity string -} - -func (e *invalidSeverityError) Error() string { - return "invalid severity: " + e.severity + " (must be one of: low, moderate, important, critical)" -} - func (h *LightwellAdvisoryHandler) listRepoAdvisories(c echo.Context) error { repoName := c.Param("repository_name") c.QueryParams().Set("repository", repoName) diff --git a/pkg/handler/lightwell_advisories_test.go b/pkg/handler/lightwell_advisories_test.go index 97467ace6..a16d82e1b 100644 --- a/pkg/handler/lightwell_advisories_test.go +++ b/pkg/handler/lightwell_advisories_test.go @@ -8,15 +8,13 @@ import ( "net/http" "net/http/httptest" "testing" - "time" "github.com/content-services/content-sources-backend/pkg/api" "github.com/content-services/content-sources-backend/pkg/config" - "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" + "github.com/content-services/content-sources-backend/pkg/dao" "github.com/content-services/content-sources-backend/pkg/middleware" + "github.com/content-services/content-sources-backend/pkg/test" test_handler "github.com/content-services/content-sources-backend/pkg/test/handler" - "github.com/google/uuid" - "github.com/jackc/pgx/v5/pgtype" "github.com/labstack/echo/v4" echo_middleware "github.com/labstack/echo/v4/middleware" "github.com/redhatinsights/platform-go-middlewares/v2/identity" @@ -28,8 +26,8 @@ import ( type LightwellAdvisorySuite struct { suite.Suite - echo *echo.Echo - mockQuerier *MockQuerier + echo *echo.Echo + reg *dao.MockDaoRegistry } func TestLightwellAdvisorySuite(t *testing.T) { @@ -42,7 +40,7 @@ func (s *LightwellAdvisorySuite) SetupTest() { TargetHeader: "x-rh-insights-request-id", })) s.echo.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) - s.mockQuerier = &MockQuerier{} + s.reg = dao.GetMockDaoRegistry(s.T()) } func (s *LightwellAdvisorySuite) TearDownTest() { @@ -54,7 +52,7 @@ func (s *LightwellAdvisorySuite) serveRouter(req *http.Request) (int, []byte, er router.HTTPErrorHandler = config.CustomHTTPErrorHandler router.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) pathPrefix := router.Group(api.FullRootPath()) - RegisterLightwellAdvisoryRoutes(pathPrefix, s.mockQuerier) + RegisterLightwellAdvisoryRoutes(pathPrefix, s.reg.ToDaoRegistry()) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) @@ -69,27 +67,21 @@ func (s *LightwellAdvisorySuite) serveRouter(req *http.Request) (int, []byte, er func (s *LightwellAdvisorySuite) TestListAdvisories() { t := s.T() - repoUUID := uuid.New() - rows := []store.ListAdvisoriesRow{ + data := []api.LightwellAdvisoryResponse{ { - Uuid: uuid.New(), - AdvisoryID: "CVE-2024-1234", - Severity: "critical", - SeverityOrder: 4, - Details: "Remote code execution vulnerability", - ReferenceUrls: []string{"https://access.redhat.com/security/cve/CVE-2024-1234"}, - PackageName: "spring-core", - FixedVersions: []string{"5.3.18.rhlw-00003"}, - RepoName: "lightwell/java/remediated", - RepositoryConfigurationUuid: repoUUID, - CreatedAt: time.Now(), - TotalCount: 1, + AdvisoryID: "CVE-2024-1234", + Severity: "critical", + Details: "Remote code execution vulnerability", + ReferenceURLs: []string{"https://access.redhat.com/security/cve/CVE-2024-1234"}, + PackageName: "spring-core", + FixedVersions: []string{"5.3.18.rhlw-00003"}, + Repository: "lightwell/java/remediated", }, } - s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { - return arg.PageLimit == int32(DefaultLimit) && arg.PageOffset == 0 - })).Return(rows, nil) + s.reg.LightwellAdvisory.On("ListAdvisories", test.MockCtx(), mock.MatchedBy(func(opts dao.ListLightwellAdvisoriesOptions) bool { + return opts.Limit == int32(DefaultLimit) && opts.Offset == 0 + })).Return(data, int64(1), nil) path := fmt.Sprintf("%s/lightwell/advisories", api.FullRootPath()) req := httptest.NewRequest(http.MethodGet, path, nil) @@ -115,11 +107,11 @@ func (s *LightwellAdvisorySuite) TestListAdvisories() { func (s *LightwellAdvisorySuite) TestListAdvisoriesWithFilters() { t := s.T() - s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { - return arg.PackageName != nil && *arg.PackageName == "spring" && - arg.SeverityMin == pgtype.Int2{Int16: 3, Valid: true} && - arg.PageLimit == 10 && arg.PageOffset == 5 - })).Return([]store.ListAdvisoriesRow{}, nil) + s.reg.LightwellAdvisory.On("ListAdvisories", test.MockCtx(), mock.MatchedBy(func(opts dao.ListLightwellAdvisoriesOptions) bool { + return opts.PackageName != nil && *opts.PackageName == "spring" && + opts.SeverityMin == "important" && + opts.Limit == 10 && opts.Offset == 5 + })).Return([]api.LightwellAdvisoryResponse{}, int64(0), nil) path := fmt.Sprintf("%s/lightwell/advisories?package_name=spring&severity_min=important&limit=10&offset=5", api.FullRootPath()) req := httptest.NewRequest(http.MethodGet, path, nil) @@ -140,21 +132,25 @@ func (s *LightwellAdvisorySuite) TestListAdvisoriesWithFilters() { func (s *LightwellAdvisorySuite) TestListAdvisoriesInvalidSeverity() { t := s.T() + s.reg.LightwellAdvisory.On("ListAdvisories", test.MockCtx(), mock.MatchedBy(func(opts dao.ListLightwellAdvisoriesOptions) bool { + return opts.SeverityMin == "bogus" + })).Return(nil, int64(0), fmt.Errorf("invalid severity: bogus (must be one of: low, moderate, important, critical)")) + path := fmt.Sprintf("%s/lightwell/advisories?severity_min=bogus", api.FullRootPath()) req := httptest.NewRequest(http.MethodGet, path, nil) req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) code, _, err := s.serveRouter(req) require.NoError(t, err) - assert.Equal(t, http.StatusBadRequest, code) + assert.Equal(t, http.StatusInternalServerError, code) } func (s *LightwellAdvisorySuite) TestListAdvisoriesFilterByRepoName() { t := s.T() - s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { - return arg.RepoName != nil && *arg.RepoName == "java-remediated" - })).Return([]store.ListAdvisoriesRow{}, nil) + s.reg.LightwellAdvisory.On("ListAdvisories", test.MockCtx(), mock.MatchedBy(func(opts dao.ListLightwellAdvisoriesOptions) bool { + return opts.RepoName != nil && *opts.RepoName == "java-remediated" + })).Return([]api.LightwellAdvisoryResponse{}, int64(0), nil) path := fmt.Sprintf("%s/lightwell/advisories?repository=java-remediated", api.FullRootPath()) req := httptest.NewRequest(http.MethodGet, path, nil) @@ -168,23 +164,21 @@ func (s *LightwellAdvisorySuite) TestListAdvisoriesFilterByRepoName() { func (s *LightwellAdvisorySuite) TestNestedRepoAdvisoriesAlias() { t := s.T() - s.mockQuerier.On("ListAdvisories", mock.Anything, mock.MatchedBy(func(arg store.ListAdvisoriesParams) bool { - return arg.RepoName != nil && *arg.RepoName == "java-remediated" - })).Return([]store.ListAdvisoriesRow{ + data := []api.LightwellAdvisoryResponse{ { - Uuid: uuid.New(), AdvisoryID: "CVE-2024-5678", Severity: "important", - SeverityOrder: 3, Details: "Test advisory via nested route", - ReferenceUrls: []string{}, + ReferenceURLs: []string{}, PackageName: "spring-core", FixedVersions: []string{"5.3.18.rhlw-00003"}, - RepoName: "java-remediated", - CreatedAt: time.Now(), - TotalCount: 1, + Repository: "java-remediated", }, - }, nil) + } + + s.reg.LightwellAdvisory.On("ListAdvisories", test.MockCtx(), mock.MatchedBy(func(opts dao.ListLightwellAdvisoriesOptions) bool { + return opts.RepoName != nil && *opts.RepoName == "java-remediated" + })).Return(data, int64(1), nil) path := fmt.Sprintf("%s/lightwell/repositories/java-remediated/advisories", api.FullRootPath()) req := httptest.NewRequest(http.MethodGet, path, nil) @@ -207,7 +201,8 @@ func (s *LightwellAdvisorySuite) TestNestedRepoAdvisoriesAlias() { func (s *LightwellAdvisorySuite) TestListAdvisoriesEmptyResult() { t := s.T() - s.mockQuerier.On("ListAdvisories", mock.Anything, mock.Anything).Return([]store.ListAdvisoriesRow{}, nil) + s.reg.LightwellAdvisory.On("ListAdvisories", test.MockCtx(), mock.Anything). + Return([]api.LightwellAdvisoryResponse{}, int64(0), nil) path := fmt.Sprintf("%s/lightwell/advisories", api.FullRootPath()) req := httptest.NewRequest(http.MethodGet, path, nil) @@ -225,62 +220,3 @@ func (s *LightwellAdvisorySuite) TestListAdvisoriesEmptyResult() { assert.NotNil(t, resp.Data) assert.Empty(t, resp.Data) } - -// MockQuerier implements store.Querier for testing -type MockQuerier struct { - mock.Mock -} - -func (m *MockQuerier) ListAdvisories(ctx context.Context, arg store.ListAdvisoriesParams) ([]store.ListAdvisoriesRow, error) { - args := m.Called(ctx, arg) - val, _ := args.Get(0).([]store.ListAdvisoriesRow) - return val, args.Error(1) -} - -func (m *MockQuerier) CountAdvisoriesByRepo(ctx context.Context, repositoryConfigUuid uuid.UUID) (int64, error) { - args := m.Called(ctx, repositoryConfigUuid) - val, _ := args.Get(0).(int64) - return val, args.Error(1) -} - -func (m *MockQuerier) ListAdvisoriesByPackage(ctx context.Context, packageName string) ([]store.ListAdvisoriesByPackageRow, error) { - args := m.Called(ctx, packageName) - val, _ := args.Get(0).([]store.ListAdvisoriesByPackageRow) - return val, args.Error(1) -} - -func (m *MockQuerier) ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]store.ListAdvisoriesByCveIDRow, error) { - args := m.Called(ctx, cveID) - val, _ := args.Get(0).([]store.ListAdvisoriesByCveIDRow) - return val, args.Error(1) -} - -func (m *MockQuerier) CountAggregates(ctx context.Context, arg store.CountAggregatesParams) (store.CountAggregatesRow, error) { - args := m.Called(ctx, arg) - val, _ := args.Get(0).(store.CountAggregatesRow) - return val, args.Error(1) -} - -func (m *MockQuerier) CountByStage(ctx context.Context, arg store.CountByStageParams) ([]store.CountByStageRow, error) { - args := m.Called(ctx, arg) - val, _ := args.Get(0).([]store.CountByStageRow) - return val, args.Error(1) -} - -func (m *MockQuerier) ListCustomerIds(ctx context.Context) ([]string, error) { - args := m.Called(ctx) - val, _ := args.Get(0).([]string) - return val, args.Error(1) -} - -func (m *MockQuerier) ListLtwlsuptTicketIds(ctx context.Context, customerID string) ([]string, error) { - args := m.Called(ctx, customerID) - val, _ := args.Get(0).([]string) - return val, args.Error(1) -} - -func (m *MockQuerier) ListVulnerabilities(ctx context.Context, arg store.ListVulnerabilitiesParams) ([]store.ListVulnerabilitiesRow, error) { - args := m.Called(ctx, arg) - val, _ := args.Get(0).([]store.ListVulnerabilitiesRow) - return val, args.Error(1) -} diff --git a/pkg/handler/lightwell_packages.go b/pkg/handler/lightwell_packages.go index 6ef3697ed..694e81337 100644 --- a/pkg/handler/lightwell_packages.go +++ b/pkg/handler/lightwell_packages.go @@ -14,7 +14,6 @@ import ( "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" ce "github.com/content-services/content-sources-backend/pkg/errors" - "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/content-services/content-sources-backend/pkg/rbac" "github.com/content-services/tang/pkg/tangy" "github.com/labstack/echo/v4" @@ -22,15 +21,13 @@ import ( ) type LightwellPackagesHandler struct { - Store store.Querier DaoRegistry dao.DaoRegistry TangClient tangy.Tangy PulpClient pulp_client.PulpClient } -func RegisterLightwellPackageRoutes(engine *echo.Group, querier store.Querier, daoReg *dao.DaoRegistry, tangClient tangy.Tangy, pulpClient pulp_client.PulpClient) { +func RegisterLightwellPackageRoutes(engine *echo.Group, daoReg *dao.DaoRegistry, tangClient tangy.Tangy, pulpClient pulp_client.PulpClient) { h := LightwellPackagesHandler{ - Store: querier, DaoRegistry: *daoReg, TangClient: tangClient, PulpClient: pulpClient, @@ -357,18 +354,18 @@ func (h *LightwellPackagesHandler) resolveRepositoryHref(ctx context.Context, re // filterVersionsByResolvingCve keeps only versions that fix the given CVE. func (h *LightwellPackagesHandler) filterVersionsByResolvingCve(ctx context.Context, items []api.LightwellPackageVersionResponse, cveID string) ([]api.LightwellPackageVersionResponse, error) { - advisories, err := h.Store.ListAdvisoriesByCveID(ctx, cveID) + matches, err := h.DaoRegistry.LightwellAdvisory.ListAdvisoriesByCveID(ctx, cveID) if err != nil { return nil, err } - fixedSet := make(map[string]map[string]bool) // package_name -> set of fixed versions - for _, adv := range advisories { - if fixedSet[adv.PackageName] == nil { - fixedSet[adv.PackageName] = make(map[string]bool) + fixedSet := make(map[string]map[string]bool) + for _, m := range matches { + if fixedSet[m.PackageName] == nil { + fixedSet[m.PackageName] = make(map[string]bool) } - for _, v := range adv.FixedVersions { - fixedSet[adv.PackageName][v] = true + for _, v := range m.FixedVersions { + fixedSet[m.PackageName][v] = true } } @@ -384,20 +381,20 @@ func (h *LightwellPackagesHandler) filterVersionsByResolvingCve(ctx context.Cont // filterVersionsByVulnerableCve keeps only versions of packages affected by // the given CVE that are NOT in the fixed-versions list. func (h *LightwellPackagesHandler) filterVersionsByVulnerableCve(ctx context.Context, items []api.LightwellPackageVersionResponse, cveID string) ([]api.LightwellPackageVersionResponse, error) { - advisories, err := h.Store.ListAdvisoriesByCveID(ctx, cveID) + matches, err := h.DaoRegistry.LightwellAdvisory.ListAdvisoriesByCveID(ctx, cveID) if err != nil { return nil, err } affectedPackages := make(map[string]bool) fixedSet := make(map[string]map[string]bool) - for _, adv := range advisories { - affectedPackages[adv.PackageName] = true - if fixedSet[adv.PackageName] == nil { - fixedSet[adv.PackageName] = make(map[string]bool) + for _, m := range matches { + affectedPackages[m.PackageName] = true + if fixedSet[m.PackageName] == nil { + fixedSet[m.PackageName] = make(map[string]bool) } - for _, v := range adv.FixedVersions { - fixedSet[adv.PackageName][v] = true + for _, v := range m.FixedVersions { + fixedSet[m.PackageName][v] = true } } diff --git a/pkg/handler/lightwell_packages_test.go b/pkg/handler/lightwell_packages_test.go index 39d99a623..622b7a15b 100644 --- a/pkg/handler/lightwell_packages_test.go +++ b/pkg/handler/lightwell_packages_test.go @@ -12,7 +12,6 @@ import ( "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" - "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/content-services/content-sources-backend/pkg/middleware" "github.com/content-services/content-sources-backend/pkg/test" test_handler "github.com/content-services/content-sources-backend/pkg/test/handler" @@ -31,7 +30,6 @@ type LightwellPackagesSuite struct { reg *dao.MockDaoRegistry tangClient *tangy.MockTangy pulpClient *pulp_client.MockPulpClient - querier *MockQuerier } func TestLightwellPackagesSuite(t *testing.T) { @@ -42,7 +40,6 @@ func (s *LightwellPackagesSuite) SetupTest() { s.reg = dao.GetMockDaoRegistry(s.T()) s.tangClient = tangy.NewMockTangy(s.T()) s.pulpClient = pulp_client.NewMockPulpClient(s.T()) - s.querier = &MockQuerier{} } func (s *LightwellPackagesSuite) serveRouter(req *http.Request) (int, []byte, error) { @@ -53,7 +50,7 @@ func (s *LightwellPackagesSuite) serveRouter(req *http.Request) (int, []byte, er })) router.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) pathPrefix := router.Group(api.FullRootPath()) - RegisterLightwellPackageRoutes(pathPrefix, s.querier, s.reg.ToDaoRegistry(), s.tangClient, s.pulpClient) + RegisterLightwellPackageRoutes(pathPrefix, s.reg.ToDaoRegistry(), s.tangClient, s.pulpClient) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) @@ -405,7 +402,7 @@ func (s *LightwellPackagesSuite) TestListPackageVersionsResolvesCveFilter() { tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, ).Return(mavenTangResponse(), nil) - s.querier.On("ListAdvisoriesByCveID", test.MockCtx(), "CVE-2024-9999").Return([]store.ListAdvisoriesByCveIDRow{ + s.reg.LightwellAdvisory.On("ListAdvisoriesByCveID", test.MockCtx(), "CVE-2024-9999").Return([]dao.LightwellAdvisoryCveMatch{ { PackageName: "jackson-databind", FixedVersions: []string{"2.15.3.rhlw-00001"}, @@ -444,7 +441,7 @@ func (s *LightwellPackagesSuite) TestListPackageVersionsVulnerableToCveFilter() // Advisory says jackson-databind is fixed at 2.15.3.rhlw-00001, so // the older version 2.14.2.rhlw-00001 should be returned as vulnerable. - s.querier.On("ListAdvisoriesByCveID", test.MockCtx(), "CVE-2024-8888").Return([]store.ListAdvisoriesByCveIDRow{ + s.reg.LightwellAdvisory.On("ListAdvisoriesByCveID", test.MockCtx(), "CVE-2024-8888").Return([]dao.LightwellAdvisoryCveMatch{ { PackageName: "jackson-databind", FixedVersions: []string{"2.15.3.rhlw-00001"}, diff --git a/pkg/handler/repositories.go b/pkg/handler/repositories.go index a89b8985f..01a941bb4 100644 --- a/pkg/handler/repositories.go +++ b/pkg/handler/repositories.go @@ -12,7 +12,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" ce "github.com/content-services/content-sources-backend/pkg/errors" - "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" + "github.com/content-services/content-sources-backend/pkg/rbac" "github.com/content-services/content-sources-backend/pkg/tasks" "github.com/content-services/content-sources-backend/pkg/tasks/client" @@ -34,12 +34,10 @@ type RepositoryHandler struct { DaoRegistry dao.DaoRegistry TaskClient client.TaskClient FeatureServiceClient feature_service_client.FeatureServiceClient - LightwellStore store.Querier // nil when lightwell store is unavailable } func RegisterRepositoryRoutes(engine *echo.Group, daoReg *dao.DaoRegistry, taskClient *client.TaskClient, fsClient *feature_service_client.FeatureServiceClient, - lightwellStore ...store.Querier, ) { if engine == nil { panic("engine is nil") @@ -58,9 +56,6 @@ func RegisterRepositoryRoutes(engine *echo.Group, daoReg *dao.DaoRegistry, TaskClient: *taskClient, FeatureServiceClient: *fsClient, } - if len(lightwellStore) > 0 && lightwellStore[0] != nil { - rh.LightwellStore = lightwellStore[0] - } addRepoRoute(engine, http.MethodGet, "/repositories/", rh.listRepositories, rbac.RbacVerbRead) addRepoRoute(engine, http.MethodGet, "/repositories/:uuid", rh.fetch, rbac.RbacVerbRead) @@ -147,15 +142,12 @@ func (rh *RepositoryHandler) enrichLightwellRepoCounts(c echo.Context, repos *ap repo.PackagesCount = &pkgCount repo.VersionsCount = &verCount - if rh.LightwellStore == nil { - continue - } repoUUID, err := uuid.Parse(repo.UUID) if err != nil { log.Ctx(c.Request().Context()).Warn().Err(err).Str("uuid", repo.UUID).Msg("invalid UUID for advisory count") continue } - count, err := rh.LightwellStore.CountAdvisoriesByRepo(c.Request().Context(), repoUUID) + count, err := rh.DaoRegistry.LightwellAdvisory.CountAdvisoriesByRepo(c.Request().Context(), repoUUID) if err != nil { log.Ctx(c.Request().Context()).Warn().Err(err).Str("uuid", repo.UUID).Msg("failed to count advisories") continue diff --git a/pkg/tasks/client/client_mock.go b/pkg/tasks/client/client_mock.go index 683336721..ff3b3d07c 100644 --- a/pkg/tasks/client/client_mock.go +++ b/pkg/tasks/client/client_mock.go @@ -64,7 +64,7 @@ type MockTaskClient_Cancel_Call struct { // Cancel is a helper method to define mock.On call // - ctx context.Context // - taskId string -func (_e *MockTaskClient_Expecter) Cancel(ctx interface{}, taskId interface{}) *MockTaskClient_Cancel_Call { +func (_e *MockTaskClient_Expecter) Cancel(ctx any, taskId any) *MockTaskClient_Cancel_Call { return &MockTaskClient_Cancel_Call{Call: _e.mock.On("Cancel", ctx, taskId)} } @@ -131,7 +131,7 @@ type MockTaskClient_Enqueue_Call struct { // Enqueue is a helper method to define mock.On call // - task queue.Task -func (_e *MockTaskClient_Expecter) Enqueue(task interface{}) *MockTaskClient_Enqueue_Call { +func (_e *MockTaskClient_Expecter) Enqueue(task any) *MockTaskClient_Enqueue_Call { return &MockTaskClient_Enqueue_Call{Call: _e.mock.On("Enqueue", task)} } diff --git a/pkg/tasks/queue/queue_mock.go b/pkg/tasks/queue/queue_mock.go index 33f4e113b..1775a8ea7 100644 --- a/pkg/tasks/queue/queue_mock.go +++ b/pkg/tasks/queue/queue_mock.go @@ -65,7 +65,7 @@ type MockQueue_Cancel_Call struct { // Cancel is a helper method to define mock.On call // - ctx context.Context // - taskId uuid.UUID -func (_e *MockQueue_Expecter) Cancel(ctx interface{}, taskId interface{}) *MockQueue_Cancel_Call { +func (_e *MockQueue_Expecter) Cancel(ctx any, taskId any) *MockQueue_Cancel_Call { return &MockQueue_Cancel_Call{Call: _e.mock.On("Cancel", ctx, taskId)} } @@ -133,7 +133,7 @@ type MockQueue_Dequeue_Call struct { // Dequeue is a helper method to define mock.On call // - ctx context.Context // - taskTypes []string -func (_e *MockQueue_Expecter) Dequeue(ctx interface{}, taskTypes interface{}) *MockQueue_Dequeue_Call { +func (_e *MockQueue_Expecter) Dequeue(ctx any, taskTypes any) *MockQueue_Dequeue_Call { return &MockQueue_Dequeue_Call{Call: _e.mock.On("Dequeue", ctx, taskTypes)} } @@ -200,7 +200,7 @@ type MockQueue_Enqueue_Call struct { // Enqueue is a helper method to define mock.On call // - task *Task -func (_e *MockQueue_Expecter) Enqueue(task interface{}) *MockQueue_Enqueue_Call { +func (_e *MockQueue_Expecter) Enqueue(task any) *MockQueue_Enqueue_Call { return &MockQueue_Enqueue_Call{Call: _e.mock.On("Enqueue", task)} } @@ -252,7 +252,7 @@ type MockQueue_Finish_Call struct { // Finish is a helper method to define mock.On call // - taskId uuid.UUID // - taskError error -func (_e *MockQueue_Expecter) Finish(taskId interface{}, taskError interface{}) *MockQueue_Finish_Call { +func (_e *MockQueue_Expecter) Finish(taskId any, taskError any) *MockQueue_Finish_Call { return &MockQueue_Finish_Call{Call: _e.mock.On("Finish", taskId, taskError)} } @@ -310,7 +310,7 @@ type MockQueue_Heartbeats_Call struct { // Heartbeats is a helper method to define mock.On call // - olderThan time.Duration -func (_e *MockQueue_Expecter) Heartbeats(olderThan interface{}) *MockQueue_Heartbeats_Call { +func (_e *MockQueue_Expecter) Heartbeats(olderThan any) *MockQueue_Heartbeats_Call { return &MockQueue_Heartbeats_Call{Call: _e.mock.On("Heartbeats", olderThan)} } @@ -378,7 +378,7 @@ type MockQueue_IdFromToken_Call struct { // IdFromToken is a helper method to define mock.On call // - token uuid.UUID -func (_e *MockQueue_Expecter) IdFromToken(token interface{}) *MockQueue_IdFromToken_Call { +func (_e *MockQueue_Expecter) IdFromToken(token any) *MockQueue_IdFromToken_Call { return &MockQueue_IdFromToken_Call{Call: _e.mock.On("IdFromToken", token)} } @@ -440,7 +440,7 @@ type MockQueue_ListenForCanceledTask_Call struct { // ListenForCanceledTask is a helper method to define mock.On call // - ctx context.Context -func (_e *MockQueue_Expecter) ListenForCanceledTask(ctx interface{}) *MockQueue_ListenForCanceledTask_Call { +func (_e *MockQueue_Expecter) ListenForCanceledTask(ctx any) *MockQueue_ListenForCanceledTask_Call { return &MockQueue_ListenForCanceledTask_Call{Call: _e.mock.On("ListenForCanceledTask", ctx)} } @@ -491,7 +491,7 @@ type MockQueue_RefreshHeartbeat_Call struct { // RefreshHeartbeat is a helper method to define mock.On call // - token uuid.UUID -func (_e *MockQueue_Expecter) RefreshHeartbeat(token interface{}) *MockQueue_RefreshHeartbeat_Call { +func (_e *MockQueue_Expecter) RefreshHeartbeat(token any) *MockQueue_RefreshHeartbeat_Call { return &MockQueue_RefreshHeartbeat_Call{Call: _e.mock.On("RefreshHeartbeat", token)} } @@ -542,7 +542,7 @@ type MockQueue_Requeue_Call struct { // Requeue is a helper method to define mock.On call // - taskId uuid.UUID -func (_e *MockQueue_Expecter) Requeue(taskId interface{}) *MockQueue_Requeue_Call { +func (_e *MockQueue_Expecter) Requeue(taskId any) *MockQueue_Requeue_Call { return &MockQueue_Requeue_Call{Call: _e.mock.On("Requeue", taskId)} } @@ -593,7 +593,7 @@ type MockQueue_RequeueFailedTasks_Call struct { // RequeueFailedTasks is a helper method to define mock.On call // - taskTypes []string -func (_e *MockQueue_Expecter) RequeueFailedTasks(taskTypes interface{}) *MockQueue_RequeueFailedTasks_Call { +func (_e *MockQueue_Expecter) RequeueFailedTasks(taskTypes any) *MockQueue_RequeueFailedTasks_Call { return &MockQueue_RequeueFailedTasks_Call{Call: _e.mock.On("RequeueFailedTasks", taskTypes)} } @@ -655,7 +655,7 @@ type MockQueue_Status_Call struct { // Status is a helper method to define mock.On call // - taskId uuid.UUID -func (_e *MockQueue_Expecter) Status(taskId interface{}) *MockQueue_Status_Call { +func (_e *MockQueue_Expecter) Status(taskId any) *MockQueue_Status_Call { return &MockQueue_Status_Call{Call: _e.mock.On("Status", taskId)} } @@ -718,7 +718,7 @@ type MockQueue_UpdatePayload_Call struct { // UpdatePayload is a helper method to define mock.On call // - task *models.TaskInfo // - payload interface{} -func (_e *MockQueue_Expecter) UpdatePayload(task interface{}, payload interface{}) *MockQueue_UpdatePayload_Call { +func (_e *MockQueue_Expecter) UpdatePayload(task any, payload any) *MockQueue_UpdatePayload_Call { return &MockQueue_UpdatePayload_Call{Call: _e.mock.On("UpdatePayload", task, payload)} } From fe1c192ed1ae395d6ffa20c52df4e5c74bfc01c2 Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 26 Aug 2026 17:10:02 -0400 Subject: [PATCH 23/47] LWLP-5: add Lightwell advisory schema, sqlc queries, and store tests Add severity_order column to advisories table. Add sqlc queries for listing/counting advisories with filtering and pagination. Extend store_test.go with advisory query coverage. --- .gitignore | 3 + .mockery_v3.yml | 3 - ...lightwell_advisory_severity_order.down.sql | 7 + ...d_lightwell_advisory_severity_order.up.sql | 20 ++ pkg/lightwell/db/queries/advisories.sql | 64 +++++ pkg/lightwell/db/schema.sql | 32 ++- pkg/lightwell/db/store/advisories.sql.go | 224 ++++++++++++++++++ pkg/lightwell/db/store/models.go | 21 ++ pkg/lightwell/db/store/querier.go | 6 + pkg/lightwell/db/store/store_test.go | 195 +++++++++++---- 10 files changed, 531 insertions(+), 44 deletions(-) create mode 100644 db/migrations/20260825110000_add_lightwell_advisory_severity_order.down.sql create mode 100644 db/migrations/20260825110000_add_lightwell_advisory_severity_order.up.sql create mode 100644 pkg/lightwell/db/queries/advisories.sql create mode 100644 pkg/lightwell/db/store/advisories.sql.go diff --git a/.gitignore b/.gitignore index 40697b284..01911f3c0 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ content-sources-frontend # local dev certs for testing pulp cert auth compose_files/pulp/assets/certs/dev_certs +/pkg/jfrog_bridge/testdata +pkg/jfrog_bridge/lightwell-catalog.key +.env.catalog diff --git a/.mockery_v3.yml b/.mockery_v3.yml index 7543d5d87..005e0ea3c 100644 --- a/.mockery_v3.yml +++ b/.mockery_v3.yml @@ -23,9 +23,6 @@ packages: github.com/content-services/content-sources-backend/pkg/clients/roadmap_client: interfaces: RoadmapClient: {} - github.com/content-services/content-sources-backend/pkg/clients/s3_client: - interfaces: - S3Client: {} github.com/content-services/content-sources-backend/pkg/dao: interfaces: AdminTaskDao: {} diff --git a/db/migrations/20260825110000_add_lightwell_advisory_severity_order.down.sql b/db/migrations/20260825110000_add_lightwell_advisory_severity_order.down.sql new file mode 100644 index 000000000..53b3f3983 --- /dev/null +++ b/db/migrations/20260825110000_add_lightwell_advisory_severity_order.down.sql @@ -0,0 +1,7 @@ +BEGIN; + +DROP INDEX IF EXISTS idx_lightwell_advisories_package_name; +DROP INDEX IF EXISTS idx_lightwell_advisories_severity_order; +ALTER TABLE lightwell_advisories DROP COLUMN IF EXISTS severity_order; + +COMMIT; diff --git a/db/migrations/20260825110000_add_lightwell_advisory_severity_order.up.sql b/db/migrations/20260825110000_add_lightwell_advisory_severity_order.up.sql new file mode 100644 index 000000000..544d380e0 --- /dev/null +++ b/db/migrations/20260825110000_add_lightwell_advisory_severity_order.up.sql @@ -0,0 +1,20 @@ +BEGIN; + +ALTER TABLE lightwell_advisories + ADD COLUMN IF NOT EXISTS severity_order SMALLINT NOT NULL DEFAULT 0; + +UPDATE lightwell_advisories SET severity_order = CASE + WHEN severity = 'critical' THEN 4 + WHEN severity = 'important' THEN 3 + WHEN severity = 'moderate' THEN 2 + WHEN severity = 'low' THEN 1 + ELSE 0 +END; + +CREATE INDEX IF NOT EXISTS idx_lightwell_advisories_severity_order + ON lightwell_advisories (severity_order); + +CREATE INDEX IF NOT EXISTS idx_lightwell_advisories_package_name + ON lightwell_advisories (package_name); + +COMMIT; diff --git a/pkg/lightwell/db/queries/advisories.sql b/pkg/lightwell/db/queries/advisories.sql new file mode 100644 index 000000000..4abf9359c --- /dev/null +++ b/pkg/lightwell/db/queries/advisories.sql @@ -0,0 +1,64 @@ +-- name: ListAdvisories :many +SELECT + la.uuid, + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.reference_urls, + la.package_name, + la.fixed_versions, + la.repo_name, + la.repository_configuration_uuid, + la.created_at, + COUNT(*) OVER() AS total_count +FROM lightwell_advisories la +WHERE 1=1 + AND ( + sqlc.narg(repository_config_uuid)::uuid IS NULL + OR la.repository_configuration_uuid = sqlc.narg(repository_config_uuid)::uuid + ) + AND ( + sqlc.narg(repo_name)::text IS NULL + OR la.repo_name = sqlc.narg(repo_name)::text + ) + AND ( + sqlc.narg(package_name)::text IS NULL + OR la.package_name ILIKE '%' || sqlc.narg(package_name)::text || '%' + ) + AND ( + sqlc.narg(severity_min)::smallint IS NULL + OR la.severity_order >= sqlc.narg(severity_min)::smallint + ) + AND ( + sqlc.narg(cve_id)::text IS NULL + OR la.advisory_id = sqlc.narg(cve_id)::text + ) +ORDER BY la.severity_order DESC, la.created_at DESC +LIMIT sqlc.arg(page_limit) OFFSET sqlc.arg(page_offset); + +-- name: CountAdvisoriesByRepo :one +SELECT COUNT(*)::bigint AS total +FROM lightwell_advisories la +WHERE la.repository_configuration_uuid = sqlc.arg(repository_config_uuid)::uuid; + +-- name: ListAdvisoriesByPackage :many +SELECT + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.fixed_versions, + la.repo_name +FROM lightwell_advisories la +WHERE la.package_name = sqlc.arg(package_name)::text +ORDER BY la.severity_order DESC, la.created_at DESC; + +-- name: ListAdvisoriesByCveID :many +SELECT + la.package_name, + la.fixed_versions, + la.repo_name, + la.severity +FROM lightwell_advisories la +WHERE la.advisory_id = sqlc.arg(cve_id)::text; diff --git a/pkg/lightwell/db/schema.sql b/pkg/lightwell/db/schema.sql index 142c6ffb5..a876231d3 100644 --- a/pkg/lightwell/db/schema.sql +++ b/pkg/lightwell/db/schema.sql @@ -1,4 +1,34 @@ --- sqlc schema snapshot: current lightwell vulnerabilities tables and filter function (see db/migrations) +-- sqlc schema snapshot: current lightwell vulnerabilities tables (see db/migrations) +-- NOTE: This file must be kept in sync with the actual migrations. +-- sqlc uses this for code generation; it is not executed directly. + +CREATE TABLE repository_configurations ( + uuid UUID PRIMARY KEY +); + +CREATE TABLE lightwell_advisories ( + uuid UUID UNIQUE NOT NULL PRIMARY KEY, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + repo_name VARCHAR(255) NOT NULL, + advisory_id VARCHAR(255) NOT NULL, + severity VARCHAR(255) NOT NULL DEFAULT '', + severity_order SMALLINT NOT NULL DEFAULT 0, + details TEXT NOT NULL DEFAULT '', + reference_urls TEXT[], + package_name VARCHAR(255) NOT NULL DEFAULT '', + fixed_version VARCHAR(255) NOT NULL DEFAULT '', + fixed_versions TEXT[] NOT NULL DEFAULT '{}', + repository_configuration_uuid UUID NOT NULL REFERENCES repository_configurations(uuid) ON DELETE CASCADE, + checksum VARCHAR(255) NOT NULL +); + +CREATE UNIQUE INDEX idx_lightwell_advisories_repo_config_advisory + ON lightwell_advisories (repository_configuration_uuid, advisory_id, package_name); +CREATE INDEX idx_lightwell_advisories_severity_order + ON lightwell_advisories (severity_order); +CREATE INDEX idx_lightwell_advisories_package_name + ON lightwell_advisories (package_name); CREATE TABLE lightwell_vulnerabilities ( uuid UUID PRIMARY KEY, diff --git a/pkg/lightwell/db/store/advisories.sql.go b/pkg/lightwell/db/store/advisories.sql.go new file mode 100644 index 000000000..500efcd7e --- /dev/null +++ b/pkg/lightwell/db/store/advisories.sql.go @@ -0,0 +1,224 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: advisories.sql + +package store + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" +) + +const countAdvisoriesByRepo = `-- name: CountAdvisoriesByRepo :one +SELECT COUNT(*)::bigint AS total +FROM lightwell_advisories la +WHERE la.repository_configuration_uuid = $1::uuid +` + +func (q *Queries) CountAdvisoriesByRepo(ctx context.Context, repositoryConfigUuid uuid.UUID) (int64, error) { + row := q.db.QueryRow(ctx, countAdvisoriesByRepo, repositoryConfigUuid) + var total int64 + err := row.Scan(&total) + return total, err +} + +const listAdvisories = `-- name: ListAdvisories :many +SELECT + la.uuid, + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.reference_urls, + la.package_name, + la.fixed_versions, + la.repo_name, + la.repository_configuration_uuid, + la.created_at, + COUNT(*) OVER() AS total_count +FROM lightwell_advisories la +WHERE 1=1 + AND ( + $1::uuid IS NULL + OR la.repository_configuration_uuid = $1::uuid + ) + AND ( + $2::text IS NULL + OR la.repo_name = $2::text + ) + AND ( + $3::text IS NULL + OR la.package_name ILIKE '%' || $3::text || '%' + ) + AND ( + $4::smallint IS NULL + OR la.severity_order >= $4::smallint + ) + AND ( + $5::text IS NULL + OR la.advisory_id = $5::text + ) +ORDER BY la.severity_order DESC, la.created_at DESC +LIMIT $7 OFFSET $6 +` + +type ListAdvisoriesParams struct { + RepositoryConfigUuid pgtype.UUID `json:"repository_config_uuid"` + RepoName *string `json:"repo_name"` + PackageName *string `json:"package_name"` + SeverityMin pgtype.Int2 `json:"severity_min"` + CveID *string `json:"cve_id"` + PageOffset int32 `json:"page_offset"` + PageLimit int32 `json:"page_limit"` +} + +type ListAdvisoriesRow struct { + Uuid uuid.UUID `json:"uuid"` + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + SeverityOrder int16 `json:"severity_order"` + Details string `json:"details"` + ReferenceUrls []string `json:"reference_urls"` + PackageName string `json:"package_name"` + FixedVersions []string `json:"fixed_versions"` + RepoName string `json:"repo_name"` + RepositoryConfigurationUuid uuid.UUID `json:"repository_configuration_uuid"` + CreatedAt time.Time `json:"created_at"` + TotalCount int64 `json:"total_count"` +} + +func (q *Queries) ListAdvisories(ctx context.Context, arg ListAdvisoriesParams) ([]ListAdvisoriesRow, error) { + rows, err := q.db.Query(ctx, listAdvisories, + arg.RepositoryConfigUuid, + arg.RepoName, + arg.PackageName, + arg.SeverityMin, + arg.CveID, + arg.PageOffset, + arg.PageLimit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListAdvisoriesRow{} + for rows.Next() { + var i ListAdvisoriesRow + if err := rows.Scan( + &i.Uuid, + &i.AdvisoryID, + &i.Severity, + &i.SeverityOrder, + &i.Details, + &i.ReferenceUrls, + &i.PackageName, + &i.FixedVersions, + &i.RepoName, + &i.RepositoryConfigurationUuid, + &i.CreatedAt, + &i.TotalCount, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAdvisoriesByCveID = `-- name: ListAdvisoriesByCveID :many +SELECT + la.package_name, + la.fixed_versions, + la.repo_name, + la.severity +FROM lightwell_advisories la +WHERE la.advisory_id = $1::text +` + +type ListAdvisoriesByCveIDRow struct { + PackageName string `json:"package_name"` + FixedVersions []string `json:"fixed_versions"` + RepoName string `json:"repo_name"` + Severity string `json:"severity"` +} + +func (q *Queries) ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]ListAdvisoriesByCveIDRow, error) { + rows, err := q.db.Query(ctx, listAdvisoriesByCveID, cveID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListAdvisoriesByCveIDRow{} + for rows.Next() { + var i ListAdvisoriesByCveIDRow + if err := rows.Scan( + &i.PackageName, + &i.FixedVersions, + &i.RepoName, + &i.Severity, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAdvisoriesByPackage = `-- name: ListAdvisoriesByPackage :many +SELECT + la.advisory_id, + la.severity, + la.severity_order, + la.details, + la.fixed_versions, + la.repo_name +FROM lightwell_advisories la +WHERE la.package_name = $1::text +ORDER BY la.severity_order DESC, la.created_at DESC +` + +type ListAdvisoriesByPackageRow struct { + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + SeverityOrder int16 `json:"severity_order"` + Details string `json:"details"` + FixedVersions []string `json:"fixed_versions"` + RepoName string `json:"repo_name"` +} + +func (q *Queries) ListAdvisoriesByPackage(ctx context.Context, packageName string) ([]ListAdvisoriesByPackageRow, error) { + rows, err := q.db.Query(ctx, listAdvisoriesByPackage, packageName) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListAdvisoriesByPackageRow{} + for rows.Next() { + var i ListAdvisoriesByPackageRow + if err := rows.Scan( + &i.AdvisoryID, + &i.Severity, + &i.SeverityOrder, + &i.Details, + &i.FixedVersions, + &i.RepoName, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/pkg/lightwell/db/store/models.go b/pkg/lightwell/db/store/models.go index da5842610..be2f7afd3 100644 --- a/pkg/lightwell/db/store/models.go +++ b/pkg/lightwell/db/store/models.go @@ -10,6 +10,23 @@ import ( "github.com/google/uuid" ) +type LightwellAdvisory struct { + Uuid uuid.UUID `json:"uuid"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + RepoName string `json:"repo_name"` + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + SeverityOrder int16 `json:"severity_order"` + Details string `json:"details"` + ReferenceUrls []string `json:"reference_urls"` + PackageName string `json:"package_name"` + FixedVersion string `json:"fixed_version"` + FixedVersions []string `json:"fixed_versions"` + RepositoryConfigurationUuid uuid.UUID `json:"repository_configuration_uuid"` + Checksum string `json:"checksum"` +} + type LightwellVulnerability struct { Uuid uuid.UUID `json:"uuid"` VulnerabilityID string `json:"vulnerability_id"` @@ -49,3 +66,7 @@ type LightwellVulnerabilitySupportTicket struct { TicketID string `json:"ticket_id"` CreatedAt time.Time `json:"created_at"` } + +type RepositoryConfiguration struct { + Uuid uuid.UUID `json:"uuid"` +} diff --git a/pkg/lightwell/db/store/querier.go b/pkg/lightwell/db/store/querier.go index bd1927ae9..659962d35 100644 --- a/pkg/lightwell/db/store/querier.go +++ b/pkg/lightwell/db/store/querier.go @@ -6,11 +6,17 @@ package store import ( "context" + + "github.com/google/uuid" ) type Querier interface { + CountAdvisoriesByRepo(ctx context.Context, repositoryConfigUuid uuid.UUID) (int64, error) CountAggregates(ctx context.Context, arg CountAggregatesParams) (CountAggregatesRow, error) CountByStage(ctx context.Context, arg CountByStageParams) ([]CountByStageRow, error) + ListAdvisories(ctx context.Context, arg ListAdvisoriesParams) ([]ListAdvisoriesRow, error) + ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]ListAdvisoriesByCveIDRow, error) + ListAdvisoriesByPackage(ctx context.Context, packageName string) ([]ListAdvisoriesByPackageRow, error) ListCustomerIds(ctx context.Context) ([]string, error) ListLtwlsuptTicketIds(ctx context.Context, customerID string) ([]string, error) ListVulnerabilities(ctx context.Context, arg ListVulnerabilitiesParams) ([]ListVulnerabilitiesRow, error) diff --git a/pkg/lightwell/db/store/store_test.go b/pkg/lightwell/db/store/store_test.go index 317490658..11237162b 100644 --- a/pkg/lightwell/db/store/store_test.go +++ b/pkg/lightwell/db/store/store_test.go @@ -11,6 +11,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/google/uuid" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgxpool" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -758,54 +759,168 @@ func TestStore_ListCustomerIds(t *testing.T) { assert.Contains(t, ids, customerB) } -func TestStore_ListLtwlsuptTicketIds(t *testing.T) { +// --- Advisory query integration tests --- + +func insertTestAdvisories(t *testing.T, ctx context.Context, tx pgx.Tx) uuid.UUID { + repoConfigUUID := uuid.New() + repoUUID := uuid.New() + now := time.Now() + + _, err := tx.Exec(ctx, + `INSERT INTO repositories (uuid, url) VALUES ($1, $2)`, + repoUUID, "https://test.example.com/repo/"+repoConfigUUID.String()) + require.NoError(t, err) + + _, err = tx.Exec(ctx, + `INSERT INTO repository_configurations (uuid, created_at, updated_at, name, arch, org_id, repository_uuid) + VALUES ($1, $2, $3, $4, $5, $6, $7)`, + repoConfigUUID, now, now, "test-advisory-repo", "x86_64", "test-org-"+repoConfigUUID.String(), repoUUID) + require.NoError(t, err) + + advisories := []struct { + id string + severity string + severityOrder int + packageName string + fixedVersions []string + repoName string + }{ + {"CVE-2024-1001", "critical", 4, "spring-core", []string{"5.3.18.rhlw-00003"}, "lightwell/java/remediated"}, + {"CVE-2024-1002", "important", 3, "jackson-databind", []string{"2.15.3.rhlw-00001"}, "lightwell/java/remediated"}, + {"CVE-2024-1003", "moderate", 2, "requests", []string{"2.31.0.rhlw-00001"}, "lightwell/python/remediated"}, + {"CVE-2024-1001", "critical", 4, "jackson-databind", []string{"2.14.2.rhlw-00001", "2.15.3.rhlw-00001"}, "lightwell/java/remediated"}, + } + + for _, adv := range advisories { + _, err := tx.Exec(ctx, ` + INSERT INTO lightwell_advisories ( + uuid, advisory_id, severity, severity_order, details, + reference_urls, package_name, fixed_versions, + repo_name, repository_configuration_uuid, checksum + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, + uuid.New(), adv.id, adv.severity, adv.severityOrder, + "test advisory details for "+adv.packageName, + []string{"https://access.redhat.com/security/cve/" + adv.id}, + adv.packageName, adv.fixedVersions, + adv.repoName, repoConfigUUID, fmt.Sprintf("checksum-%s-%s", adv.id, adv.packageName), + ) + require.NoError(t, err) + } + return repoConfigUUID +} + +func TestStore_ListAdvisories(t *testing.T) { ctx, tx, q := beginTestTx(t) defer rollbackTestTx(t, tx) - customerA := fmt.Sprintf("lw-tickets-a-%d", time.Now().UnixNano()) - customerB := fmt.Sprintf("lw-tickets-b-%d", time.Now().UnixNano()) - insertTestVulnerabilities(t, ctx, tx, []testVulnSpec{ - { - vulnID: "LWL-TICKETS-1", - severity: "Moderate", - stage: "Submitted", - language: "java", - complexity: "Standard", - ticketIDs: []string{"ticket-c", "ticket-a"}, - daysAgo: 1, - customerIDs: []string{customerA}, - }, - { - vulnID: "LWL-TICKETS-2", - severity: "Low", - stage: "Submitted", - language: "java", - complexity: "Standard", - ticketID: "ticket-a", - daysAgo: 1, - customerIDs: []string{customerA}, - }, - { - vulnID: "LWL-TICKETS-3", - severity: "Low", - stage: "Submitted", - language: "python", - complexity: "Standard", - ticketID: "ticket-b", - daysAgo: 1, - customerIDs: []string{customerB}, - }, + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + PageLimit: 100, + PageOffset: 0, }) + require.NoError(t, err) + assert.Len(t, rows, 4) + assert.Equal(t, int64(4), rows[0].TotalCount) + // Ordered by severity_order DESC + assert.Equal(t, int16(4), rows[0].SeverityOrder) +} - ids, err := q.ListLtwlsuptTicketIds(ctx, customerA) +func TestStore_ListAdvisoriesFilterByPackageName(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + name := "jackson" + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + PackageName: &name, + PageLimit: 100, + PageOffset: 0, + }) require.NoError(t, err) - assert.Equal(t, []string{"ticket-a", "ticket-c"}, ids) + assert.Len(t, rows, 2) + for _, r := range rows { + assert.Contains(t, r.PackageName, "jackson") + } +} - ids, err = q.ListLtwlsuptTicketIds(ctx, customerB) +func TestStore_ListAdvisoriesFilterBySeverityMin(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + SeverityMin: pgtype.Int2{Int16: 3, Valid: true}, + PageLimit: 100, + PageOffset: 0, + }) require.NoError(t, err) - assert.Equal(t, []string{"ticket-b"}, ids) + assert.Len(t, rows, 3) + for _, r := range rows { + assert.GreaterOrEqual(t, r.SeverityOrder, int16(3)) + } +} + +func TestStore_ListAdvisoriesFilterByRepoName(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) - ids, err = q.ListLtwlsuptTicketIds(ctx, "no-such-customer") + repoName := "lightwell/python/remediated" + rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ + RepoName: &repoName, + PageLimit: 100, + PageOffset: 0, + }) require.NoError(t, err) - assert.Empty(t, ids) + assert.Len(t, rows, 1) + assert.Equal(t, "requests", rows[0].PackageName) +} + +func TestStore_CountAdvisoriesByRepo(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + repoUUID := insertTestAdvisories(t, ctx, tx) + + count, err := q.CountAdvisoriesByRepo(ctx, repoUUID) + require.NoError(t, err) + assert.Equal(t, int64(4), count) +} + +func TestStore_ListAdvisoriesByCveID(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisoriesByCveID(ctx, "CVE-2024-1001") + require.NoError(t, err) + assert.Len(t, rows, 2) + + packageNames := map[string]bool{} + for _, r := range rows { + packageNames[r.PackageName] = true + assert.Equal(t, "critical", r.Severity) + } + assert.True(t, packageNames["spring-core"]) + assert.True(t, packageNames["jackson-databind"]) +} + +func TestStore_ListAdvisoriesByPackage(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + insertTestAdvisories(t, ctx, tx) + + rows, err := q.ListAdvisoriesByPackage(ctx, "jackson-databind") + require.NoError(t, err) + assert.Len(t, rows, 2) + for _, r := range rows { + assert.NotEmpty(t, r.AdvisoryID) + assert.NotEmpty(t, r.FixedVersions) + } } From 57cb035c63560910b77dab1f93ed2b21b5f182ba Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 26 Aug 2026 17:10:02 -0400 Subject: [PATCH 24/47] LWLP-5: add Lightwell advisories and packages API via DAO layer Add REST handlers for /lightwell/advisories, /lightwell/packages, and /lightwell/package_versions with filtering, pagination, and aggregate counts. Route all advisory queries through the DAO layer using the shared connection pool instead of a standalone pgxpool. Add packages_count, versions_count, and remediations_count to the repository response. --- pkg/api/lightwell_advisories.go | 29 + pkg/api/lightwell_packages.go | 66 +++ pkg/api/repositories.go | 3 + pkg/dao/interfaces.go | 6 +- pkg/dao/lightwell_advisory.go | 112 +++- pkg/handler/api.go | 6 +- pkg/handler/lightwell_advisories.go | 84 +++ pkg/handler/lightwell_advisories_test.go | 222 +++++++ pkg/handler/lightwell_packages.go | 725 +++++++++++++++++++++++ pkg/handler/lightwell_packages_test.go | 535 +++++++++++++++++ pkg/handler/repositories.go | 32 + 11 files changed, 1815 insertions(+), 5 deletions(-) create mode 100644 pkg/api/lightwell_advisories.go create mode 100644 pkg/api/lightwell_packages.go create mode 100644 pkg/handler/lightwell_advisories.go create mode 100644 pkg/handler/lightwell_advisories_test.go create mode 100644 pkg/handler/lightwell_packages.go create mode 100644 pkg/handler/lightwell_packages_test.go diff --git a/pkg/api/lightwell_advisories.go b/pkg/api/lightwell_advisories.go new file mode 100644 index 000000000..9eb7260a5 --- /dev/null +++ b/pkg/api/lightwell_advisories.go @@ -0,0 +1,29 @@ +package api + +type LightwellAdvisoryResponse struct { + AdvisoryID string `json:"advisory_id"` + Severity string `json:"severity"` + Details string `json:"details"` + ReferenceURLs []string `json:"reference_urls"` + PackageName string `json:"package_name"` + FixedVersions []string `json:"fixed_versions"` + Repository string `json:"repository"` +} + +type LightwellAdvisoryCollectionResponse struct { + Data []LightwellAdvisoryResponse `json:"data"` + Meta ResponseMetadata `json:"meta"` + Links Links `json:"links"` +} + +func (r *LightwellAdvisoryCollectionResponse) SetMetadata(meta ResponseMetadata, links Links) { + r.Meta = meta + r.Links = links +} + +type LightwellAdvisoryFilterData struct { + Repository string `query:"repository"` + PackageName string `query:"package_name"` + SeverityMin string `query:"severity_min"` + CveID string `query:"cve_id"` +} diff --git a/pkg/api/lightwell_packages.go b/pkg/api/lightwell_packages.go new file mode 100644 index 000000000..aadaa286c --- /dev/null +++ b/pkg/api/lightwell_packages.go @@ -0,0 +1,66 @@ +package api + +// LightwellPackageResponse represents a package found across Lightwell repositories. +type LightwellPackageResponse struct { + Name string `json:"name"` + Group string `json:"group,omitempty"` + ContentType string `json:"content_type"` + Repository string `json:"repository"` + RepositoryUUID string `json:"repository_uuid"` + Versions []string `json:"versions"` + LatestReleases []ReleaseInfo `json:"latest_releases"` +} + +// LightwellPackageCollectionResponse is a paginated collection of cross-repo packages. +type LightwellPackageCollectionResponse struct { + Data []LightwellPackageResponse `json:"data"` + Meta ResponseMetadata `json:"meta"` + Links Links `json:"links"` +} + +func (r *LightwellPackageCollectionResponse) SetMetadata(meta ResponseMetadata, links Links) { + r.Meta = meta + r.Links = links +} + +// LightwellPackageVersionResponse represents a single package version across Lightwell repositories. +type LightwellPackageVersionResponse struct { + Name string `json:"name"` + Group string `json:"group,omitempty"` + Version string `json:"version"` + ContentType string `json:"content_type"` + Repository string `json:"repository"` + RepositoryUUID string `json:"repository_uuid"` + Release string `json:"release,omitempty"` + CreatedAt string `json:"created_at,omitempty"` +} + +// LightwellPackageVersionCollectionResponse is a paginated collection of cross-repo package versions. +type LightwellPackageVersionCollectionResponse struct { + Data []LightwellPackageVersionResponse `json:"data"` + Meta ResponseMetadata `json:"meta"` + Links Links `json:"links"` +} + +func (r *LightwellPackageVersionCollectionResponse) SetMetadata(meta ResponseMetadata, links Links) { + r.Meta = meta + r.Links = links +} + +// LightwellPackageFilterData holds query-parameter filters for the cross-repo packages endpoint. +type LightwellPackageFilterData struct { + ContentType string `query:"content_type"` + Name string `query:"name"` + Repository string `query:"repository"` + SecurityLevel string `query:"security_level"` +} + +// LightwellPackageVersionFilterData holds query-parameter filters for the cross-repo package_versions endpoint. +type LightwellPackageVersionFilterData struct { + ContentType string `query:"content_type"` + Name string `query:"name"` + SecurityLevel string `query:"security_level"` + Repository string `query:"repository"` + ResolvesCveID string `query:"resolves_cve_id"` + VulnerableToCveID string `query:"vulnerable_to_cve_id"` +} diff --git a/pkg/api/repositories.go b/pkg/api/repositories.go index 90e25f87c..059ad93a8 100644 --- a/pkg/api/repositories.go +++ b/pkg/api/repositories.go @@ -45,6 +45,9 @@ type RepositoryResponse struct { SecurityLevel string `json:"security_level,omitempty" readonly:"true"` // Security level of the repository (e.g. validated, remediated) PublishedDistURL string `json:"published_distribution_url,omitempty" readonly:"true"` // Published distribution URL from Pulp PublishedDistBasePath string `json:"-"` // Published dist base path from Pulp + PackagesCount *int `json:"packages_count,omitempty" readonly:"true"` // Lightwell: total distinct packages + VersionsCount *int `json:"versions_count,omitempty" readonly:"true"` // Lightwell: total distinct versions + RemediationsCount *int `json:"remediations_count,omitempty" readonly:"true"` // Lightwell: total security advisories } // RepositoryRequest holds data received from request to create repository diff --git a/pkg/dao/interfaces.go b/pkg/dao/interfaces.go index 6273670e2..ab6f7f523 100644 --- a/pkg/dao/interfaces.go +++ b/pkg/dao/interfaces.go @@ -11,6 +11,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" "github.com/content-services/content-sources-backend/pkg/clients/roadmap_client" csdb "github.com/content-services/content-sources-backend/pkg/db" + "github.com/google/uuid" "github.com/content-services/content-sources-backend/pkg/models" "github.com/content-services/tang/pkg/tangy" "github.com/content-services/yummy/pkg/yum" @@ -80,7 +81,7 @@ func GetDaoRegistry(db *gorm.DB) *DaoRegistry { Uploads: uploadDaoImpl{db: db, pulpClient: pulp_client.GetPulpClientWithDomain("")}, Memo: memoDaoImpl{db: db}, MavenPackages: mavenPackagesDaoImpl{db: db}, - LightwellAdvisory: lightwellAdvisoryDaoImpl{db: db}, + LightwellAdvisory: lightwellAdvisoryDaoImpl{db: db, querier: csdb.LightwellQueries}, LightwellVulnerability: newLightwellVulnerabilityDao(csdb.LightwellQueries), UserPreference: userPreferenceDaoImpl{db: db}, CoverageReport: coverageReportDaoImpl{db: db}, @@ -277,6 +278,9 @@ type LightwellAdvisoryDao interface { ListByRepository(ctx context.Context, repoConfigUUID string) ([]LightwellAdvisoryInput, error) ListUnnotifiedAdvisories(ctx context.Context, repoConfigUUID string, orgID string) ([]LightwellNotificationData, error) MarkAsNotified(ctx context.Context, repoConfigUUID string, orgID string, data []LightwellNotificationData) error + ListAdvisories(ctx context.Context, opts ListLightwellAdvisoriesOptions) ([]api.LightwellAdvisoryResponse, int64, error) + ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]LightwellAdvisoryCveMatch, error) + CountAdvisoriesByRepo(ctx context.Context, repoConfigUUID uuid.UUID) (int64, error) } type LightwellVulnerabilityDao interface { diff --git a/pkg/dao/lightwell_advisory.go b/pkg/dao/lightwell_advisory.go index cbb8a7670..e7193f28c 100644 --- a/pkg/dao/lightwell_advisory.go +++ b/pkg/dao/lightwell_advisory.go @@ -5,7 +5,11 @@ import ( "fmt" "strings" + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/lightwell/db/store" "github.com/content-services/content-sources-backend/pkg/models" + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" "gorm.io/gorm" "gorm.io/gorm/clause" ) @@ -28,8 +32,25 @@ type LightwellNotificationData struct { ReferenceURLs []string } +type ListLightwellAdvisoriesOptions struct { + RepoName *string + PackageName *string + SeverityMin string + CveID *string + Limit int32 + Offset int32 +} + +type LightwellAdvisoryCveMatch struct { + PackageName string + FixedVersions []string + RepoName string + Severity string +} + type lightwellAdvisoryDaoImpl struct { - db *gorm.DB + db *gorm.DB + querier store.Querier } func GetLightwellAdvisoryDao(db *gorm.DB) LightwellAdvisoryDao { @@ -164,3 +185,92 @@ func (d lightwellAdvisoryDaoImpl) MarkAsNotified(ctx context.Context, repoConfig } return nil } + +var severityMap = map[string]int16{ + "low": 1, + "moderate": 2, + "important": 3, + "critical": 4, +} + +func parseSeverityMin(s string) (pgtype.Int2, error) { + if s == "" { + return pgtype.Int2{}, nil + } + val, ok := severityMap[s] + if !ok { + return pgtype.Int2{}, fmt.Errorf("invalid severity: %s (must be one of: low, moderate, important, critical)", s) + } + return pgtype.Int2{Int16: val, Valid: true}, nil +} + +func (d lightwellAdvisoryDaoImpl) ListAdvisories(ctx context.Context, opts ListLightwellAdvisoriesOptions) ([]api.LightwellAdvisoryResponse, int64, error) { + severityMin, err := parseSeverityMin(opts.SeverityMin) + if err != nil { + return nil, 0, err + } + + rows, err := d.querier.ListAdvisories(ctx, store.ListAdvisoriesParams{ + RepoName: opts.RepoName, + PackageName: opts.PackageName, + SeverityMin: severityMin, + CveID: opts.CveID, + PageOffset: opts.Offset, + PageLimit: opts.Limit, + }) + if err != nil { + return nil, 0, fmt.Errorf("failed to list advisories: %w", err) + } + + var totalCount int64 + if len(rows) > 0 { + totalCount = rows[0].TotalCount + } + + data := make([]api.LightwellAdvisoryResponse, 0, len(rows)) + for _, row := range rows { + refURLs := row.ReferenceUrls + if refURLs == nil { + refURLs = []string{} + } + fixedVersions := row.FixedVersions + if fixedVersions == nil { + fixedVersions = []string{} + } + data = append(data, api.LightwellAdvisoryResponse{ + AdvisoryID: row.AdvisoryID, + Severity: row.Severity, + Details: row.Details, + ReferenceURLs: refURLs, + PackageName: row.PackageName, + FixedVersions: fixedVersions, + Repository: row.RepoName, + }) + } + return data, totalCount, nil +} + +func (d lightwellAdvisoryDaoImpl) ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]LightwellAdvisoryCveMatch, error) { + rows, err := d.querier.ListAdvisoriesByCveID(ctx, cveID) + if err != nil { + return nil, fmt.Errorf("failed to list advisories by CVE ID: %w", err) + } + matches := make([]LightwellAdvisoryCveMatch, 0, len(rows)) + for _, row := range rows { + matches = append(matches, LightwellAdvisoryCveMatch{ + PackageName: row.PackageName, + FixedVersions: row.FixedVersions, + RepoName: row.RepoName, + Severity: row.Severity, + }) + } + return matches, nil +} + +func (d lightwellAdvisoryDaoImpl) CountAdvisoriesByRepo(ctx context.Context, repoConfigUUID uuid.UUID) (int64, error) { + count, err := d.querier.CountAdvisoriesByRepo(ctx, repoConfigUUID) + if err != nil { + return 0, fmt.Errorf("failed to count advisories for repo %s: %w", repoConfigUUID, err) + } + return count, nil +} diff --git a/pkg/handler/api.go b/pkg/handler/api.go index c1c110d98..9e141a2ba 100644 --- a/pkg/handler/api.go +++ b/pkg/handler/api.go @@ -78,10 +78,9 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { } else { s3Client, err = s3_client.NewS3Client(config.Get().Clients.Lightwell.S3.CoverageUploads) if err != nil { - panic(err) + log.Warn().Err(err).Msg("failed to create s3 client") } } - ch := cache.Initialize() for i := 0; i < len(paths); i++ { @@ -109,8 +108,8 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { RegisterUserPreferencesRoutes(group, daoReg) RegisterLightwellVulnerabilityRoutes(group, daoReg) RegisterCoverageReportRoutes(group, daoReg, s3Client) + RegisterLightwellAdvisoryRoutes(group, daoReg) - // Register package and build routes if tang client is available pulpClient := pulp_client.GetPulpClientWithDomain("") if config.Tang == nil { err = config.ConfigureTang() @@ -120,6 +119,7 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { } if config.Tang != nil { RegisterPackageRoutes(group, daoReg, *config.Tang, pulpClient) + RegisterLightwellPackageRoutes(group, daoReg, *config.Tang, pulpClient) } } diff --git a/pkg/handler/lightwell_advisories.go b/pkg/handler/lightwell_advisories.go new file mode 100644 index 000000000..79a71c9fa --- /dev/null +++ b/pkg/handler/lightwell_advisories.go @@ -0,0 +1,84 @@ +package handler + +import ( + "net/http" + + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/dao" + ce "github.com/content-services/content-sources-backend/pkg/errors" + "github.com/content-services/content-sources-backend/pkg/rbac" + "github.com/labstack/echo/v4" +) + +type LightwellAdvisoryHandler struct { + DaoRegistry dao.DaoRegistry +} + +func RegisterLightwellAdvisoryRoutes(engine *echo.Group, daoReg *dao.DaoRegistry) { + h := LightwellAdvisoryHandler{DaoRegistry: *daoReg} + addRepoRoute(engine, http.MethodGet, "/lightwell/advisories", h.list, rbac.RbacVerbRead) + addRepoRoute(engine, http.MethodGet, "/lightwell/repositories/:repository_name/advisories", h.listRepoAdvisories, rbac.RbacVerbRead) +} + +// listLightwellAdvisories godoc +// @Summary List Lightwell Advisories +// @ID listLightwellAdvisories +// @Description List security advisories for Lightwell remediated packages with optional filtering. +// @Tags lightwell +// @Accept json +// @Produce json +// @Param repository_uuid query string false "Filter by repository UUID" +// @Param package_name query string false "Filter by package name (substring match)" +// @Param severity_min query string false "Minimum severity level (low, moderate, important, critical)" +// @Param cve_id query string false "Filter by CVE ID (exact match)" +// @Param limit query int false "Limit of results to return" +// @Param offset query int false "Offset into results" +// @Success 200 {object} api.LightwellAdvisoryCollectionResponse +// @Failure 400 {object} ce.ErrorResponse +// @Failure 500 {object} ce.ErrorResponse +// @Router /lightwell/advisories [get] +func (h *LightwellAdvisoryHandler) list(c echo.Context) error { + page := ParsePagination(c) + filters := parseLightwellAdvisoryFilters(c) + + opts := dao.ListLightwellAdvisoriesOptions{ + SeverityMin: filters.SeverityMin, + Limit: int32(page.Limit), //nolint:gosec // bounded by MaxLimit (200) + Offset: int32(page.Offset), //nolint:gosec // bounded by ParsePagination + } + if filters.Repository != "" { + opts.RepoName = &filters.Repository + } + if filters.PackageName != "" { + opts.PackageName = &filters.PackageName + } + if filters.CveID != "" { + opts.CveID = &filters.CveID + } + + data, totalCount, err := h.DaoRegistry.LightwellAdvisory.ListAdvisories(c.Request().Context(), opts) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing advisories", err.Error()) + } + + resp := api.LightwellAdvisoryCollectionResponse{Data: data} + collResp := setCollectionResponseMetadata(&resp, c, totalCount) + return c.JSON(http.StatusOK, collResp) +} + +func parseLightwellAdvisoryFilters(c echo.Context) api.LightwellAdvisoryFilterData { + var filters api.LightwellAdvisoryFilterData + _ = echo.QueryParamsBinder(c). + String("repository", &filters.Repository). + String("package_name", &filters.PackageName). + String("severity_min", &filters.SeverityMin). + String("cve_id", &filters.CveID). + BindError() + return filters +} + +func (h *LightwellAdvisoryHandler) listRepoAdvisories(c echo.Context) error { + repoName := c.Param("repository_name") + c.QueryParams().Set("repository", repoName) + return h.list(c) +} diff --git a/pkg/handler/lightwell_advisories_test.go b/pkg/handler/lightwell_advisories_test.go new file mode 100644 index 000000000..a16d82e1b --- /dev/null +++ b/pkg/handler/lightwell_advisories_test.go @@ -0,0 +1,222 @@ +package handler + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/config" + "github.com/content-services/content-sources-backend/pkg/dao" + "github.com/content-services/content-sources-backend/pkg/middleware" + "github.com/content-services/content-sources-backend/pkg/test" + test_handler "github.com/content-services/content-sources-backend/pkg/test/handler" + "github.com/labstack/echo/v4" + echo_middleware "github.com/labstack/echo/v4/middleware" + "github.com/redhatinsights/platform-go-middlewares/v2/identity" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type LightwellAdvisorySuite struct { + suite.Suite + echo *echo.Echo + reg *dao.MockDaoRegistry +} + +func TestLightwellAdvisorySuite(t *testing.T) { + suite.Run(t, new(LightwellAdvisorySuite)) +} + +func (s *LightwellAdvisorySuite) SetupTest() { + s.echo = echo.New() + s.echo.Use(echo_middleware.RequestIDWithConfig(echo_middleware.RequestIDConfig{ + TargetHeader: "x-rh-insights-request-id", + })) + s.echo.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) + s.reg = dao.GetMockDaoRegistry(s.T()) +} + +func (s *LightwellAdvisorySuite) TearDownTest() { + require.NoError(s.T(), s.echo.Shutdown(context.Background())) +} + +func (s *LightwellAdvisorySuite) serveRouter(req *http.Request) (int, []byte, error) { + router := echo.New() + router.HTTPErrorHandler = config.CustomHTTPErrorHandler + router.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) + pathPrefix := router.Group(api.FullRootPath()) + RegisterLightwellAdvisoryRoutes(pathPrefix, s.reg.ToDaoRegistry()) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + + response := rr.Result() + defer response.Body.Close() + + body, err := io.ReadAll(response.Body) + return response.StatusCode, body, err +} + +func (s *LightwellAdvisorySuite) TestListAdvisories() { + t := s.T() + + data := []api.LightwellAdvisoryResponse{ + { + AdvisoryID: "CVE-2024-1234", + Severity: "critical", + Details: "Remote code execution vulnerability", + ReferenceURLs: []string{"https://access.redhat.com/security/cve/CVE-2024-1234"}, + PackageName: "spring-core", + FixedVersions: []string{"5.3.18.rhlw-00003"}, + Repository: "lightwell/java/remediated", + }, + } + + s.reg.LightwellAdvisory.On("ListAdvisories", test.MockCtx(), mock.MatchedBy(func(opts dao.ListLightwellAdvisoriesOptions) bool { + return opts.Limit == int32(DefaultLimit) && opts.Offset == 0 + })).Return(data, int64(1), nil) + + path := fmt.Sprintf("%s/lightwell/advisories", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "CVE-2024-1234", resp.Data[0].AdvisoryID) + assert.Equal(t, "critical", resp.Data[0].Severity) + assert.Equal(t, "spring-core", resp.Data[0].PackageName) + assert.Equal(t, []string{"5.3.18.rhlw-00003"}, resp.Data[0].FixedVersions) + assert.Equal(t, "lightwell/java/remediated", resp.Data[0].Repository) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesWithFilters() { + t := s.T() + + s.reg.LightwellAdvisory.On("ListAdvisories", test.MockCtx(), mock.MatchedBy(func(opts dao.ListLightwellAdvisoriesOptions) bool { + return opts.PackageName != nil && *opts.PackageName == "spring" && + opts.SeverityMin == "important" && + opts.Limit == 10 && opts.Offset == 5 + })).Return([]api.LightwellAdvisoryResponse{}, int64(0), nil) + + path := fmt.Sprintf("%s/lightwell/advisories?package_name=spring&severity_min=important&limit=10&offset=5", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(0), resp.Meta.Count) + assert.Empty(t, resp.Data) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesInvalidSeverity() { + t := s.T() + + s.reg.LightwellAdvisory.On("ListAdvisories", test.MockCtx(), mock.MatchedBy(func(opts dao.ListLightwellAdvisoriesOptions) bool { + return opts.SeverityMin == "bogus" + })).Return(nil, int64(0), fmt.Errorf("invalid severity: bogus (must be one of: low, moderate, important, critical)")) + + path := fmt.Sprintf("%s/lightwell/advisories?severity_min=bogus", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusInternalServerError, code) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesFilterByRepoName() { + t := s.T() + + s.reg.LightwellAdvisory.On("ListAdvisories", test.MockCtx(), mock.MatchedBy(func(opts dao.ListLightwellAdvisoriesOptions) bool { + return opts.RepoName != nil && *opts.RepoName == "java-remediated" + })).Return([]api.LightwellAdvisoryResponse{}, int64(0), nil) + + path := fmt.Sprintf("%s/lightwell/advisories?repository=java-remediated", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) +} + +func (s *LightwellAdvisorySuite) TestNestedRepoAdvisoriesAlias() { + t := s.T() + + data := []api.LightwellAdvisoryResponse{ + { + AdvisoryID: "CVE-2024-5678", + Severity: "important", + Details: "Test advisory via nested route", + ReferenceURLs: []string{}, + PackageName: "spring-core", + FixedVersions: []string{"5.3.18.rhlw-00003"}, + Repository: "java-remediated", + }, + } + + s.reg.LightwellAdvisory.On("ListAdvisories", test.MockCtx(), mock.MatchedBy(func(opts dao.ListLightwellAdvisoriesOptions) bool { + return opts.RepoName != nil && *opts.RepoName == "java-remediated" + })).Return(data, int64(1), nil) + + path := fmt.Sprintf("%s/lightwell/repositories/java-remediated/advisories", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "CVE-2024-5678", resp.Data[0].AdvisoryID) + assert.Equal(t, "java-remediated", resp.Data[0].Repository) +} + +func (s *LightwellAdvisorySuite) TestListAdvisoriesEmptyResult() { + t := s.T() + + s.reg.LightwellAdvisory.On("ListAdvisories", test.MockCtx(), mock.Anything). + Return([]api.LightwellAdvisoryResponse{}, int64(0), nil) + + path := fmt.Sprintf("%s/lightwell/advisories", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellAdvisoryCollectionResponse + err = json.Unmarshal(body, &resp) + require.NoError(t, err) + + assert.Equal(t, int64(0), resp.Meta.Count) + assert.NotNil(t, resp.Data) + assert.Empty(t, resp.Data) +} diff --git a/pkg/handler/lightwell_packages.go b/pkg/handler/lightwell_packages.go new file mode 100644 index 000000000..694e81337 --- /dev/null +++ b/pkg/handler/lightwell_packages.go @@ -0,0 +1,725 @@ +package handler + +import ( + "context" + "errors" + "fmt" + "net/http" + "sort" + "strings" + "sync" + + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" + "github.com/content-services/content-sources-backend/pkg/config" + "github.com/content-services/content-sources-backend/pkg/dao" + ce "github.com/content-services/content-sources-backend/pkg/errors" + "github.com/content-services/content-sources-backend/pkg/rbac" + "github.com/content-services/tang/pkg/tangy" + "github.com/labstack/echo/v4" + "github.com/rs/zerolog/log" +) + +type LightwellPackagesHandler struct { + DaoRegistry dao.DaoRegistry + TangClient tangy.Tangy + PulpClient pulp_client.PulpClient +} + +func RegisterLightwellPackageRoutes(engine *echo.Group, daoReg *dao.DaoRegistry, tangClient tangy.Tangy, pulpClient pulp_client.PulpClient) { + h := LightwellPackagesHandler{ + DaoRegistry: *daoReg, + TangClient: tangClient, + PulpClient: pulpClient, + } + // Flat cross-repo endpoints + addRepoRoute(engine, http.MethodGet, "/lightwell/packages", h.listPackages, rbac.RbacVerbRead) + addRepoRoute(engine, http.MethodGet, "/lightwell/package_versions", h.listPackageVersions, rbac.RbacVerbRead) + // Nested repo-scoped aliases + addRepoRoute(engine, http.MethodGet, "/lightwell/repositories/:repository_name/packages", h.listRepoPackages, rbac.RbacVerbRead) + addRepoRoute(engine, http.MethodGet, "/lightwell/repositories/:repository_name/package_versions", h.listRepoPackageVersions, rbac.RbacVerbRead) +} + +// listLightwellPackages godoc +// @Summary List Lightwell Packages (cross-repo) +// @ID listLightwellPackages +// @Description List packages aggregated across all Lightwell repositories, with optional filtering by content type, name, and security level. +// @Tags lightwell +// @Accept json +// @Produce json +// @Param type query string false "Filter by content type (maven, python, npm)" +// @Param name query string false "Filter by package name (substring match)" +// @Param security_level query string false "Filter by security level (validated, remediated)" +// @Param limit query int false "Limit of results to return" +// @Param offset query int false "Offset into results" +// @Success 200 {object} api.LightwellPackageCollectionResponse +// @Failure 400 {object} ce.ErrorResponse +// @Failure 500 {object} ce.ErrorResponse +// @Router /lightwell/packages [get] +func (h *LightwellPackagesHandler) listPackages(c echo.Context) error { + page := ParsePagination(c) + filters := parseLightwellPackageFilters(c) + + if err := validateContentType(filters.ContentType); err != nil { + return ce.NewErrorResponse(http.StatusBadRequest, "Invalid content_type filter", err.Error()) + } + + repos, err := h.fetchLightwellRepos(c, filters.ContentType, filters.SecurityLevel) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing Lightwell repositories", err.Error()) + } + if filters.Repository != "" { + repos = filterReposByName(repos, filters.Repository) + } + + items, err := h.aggregatePackages(c.Request().Context(), repos, filters.Name) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error retrieving packages", err.Error()) + } + + sortLightwellPackages(items, page.SortBy) + totalCount := int64(len(items)) + paged := paginatePackages(items, page.Offset, page.Limit) + resp := api.LightwellPackageCollectionResponse{Data: paged} + collResp := setCollectionResponseMetadata(&resp, c, totalCount) + return c.JSON(http.StatusOK, collResp) +} + +// listLightwellPackageVersions godoc +// @Summary List Lightwell Package Versions (cross-repo) +// @ID listLightwellPackageVersions +// @Description List individual package versions aggregated across all Lightwell repositories, with optional CVE-based filtering. +// @Tags lightwell +// @Accept json +// @Produce json +// @Param type query string false "Filter by content type (maven, python, npm)" +// @Param name query string false "Filter by package name (substring match)" +// @Param security_level query string false "Filter by security level (validated, remediated)" +// @Param repository query string false "Filter by repository name" +// @Param resolves_cve_id query string false "Show only packages that resolve this CVE" +// @Param vulnerable_to_cve_id query string false "Show only packages vulnerable to this CVE" +// @Param limit query int false "Limit of results to return" +// @Param offset query int false "Offset into results" +// @Success 200 {object} api.LightwellPackageVersionCollectionResponse +// @Failure 400 {object} ce.ErrorResponse +// @Failure 500 {object} ce.ErrorResponse +// @Router /lightwell/package_versions [get] +func (h *LightwellPackagesHandler) listPackageVersions(c echo.Context) error { + page := ParsePagination(c) + filters := parseLightwellPackageVersionFilters(c) + + if err := validateContentType(filters.ContentType); err != nil { + return ce.NewErrorResponse(http.StatusBadRequest, "Invalid content_type filter", err.Error()) + } + + repos, err := h.fetchLightwellRepos(c, filters.ContentType, filters.SecurityLevel) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error listing Lightwell repositories", err.Error()) + } + if filters.Repository != "" { + repos = filterReposByName(repos, filters.Repository) + } + + items, err := h.aggregatePackageVersions(c.Request().Context(), repos, filters.Name) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error retrieving package versions", err.Error()) + } + + if filters.ResolvesCveID != "" { + items, err = h.filterVersionsByResolvingCve(c.Request().Context(), items, filters.ResolvesCveID) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error filtering by CVE", err.Error()) + } + } + if filters.VulnerableToCveID != "" { + items, err = h.filterVersionsByVulnerableCve(c.Request().Context(), items, filters.VulnerableToCveID) + if err != nil { + return ce.NewErrorResponse(http.StatusInternalServerError, "Error filtering by CVE", err.Error()) + } + } + + sortLightwellVersions(items, page.SortBy) + totalCount := int64(len(items)) + paged := paginateVersions(items, page.Offset, page.Limit) + resp := api.LightwellPackageVersionCollectionResponse{Data: paged} + collResp := setCollectionResponseMetadata(&resp, c, totalCount) + return c.JSON(http.StatusOK, collResp) +} + +// fetchLightwellRepos returns Lightwell repos for the caller's org, optionally +// filtered by content type and security level. +func (h *LightwellPackagesHandler) fetchLightwellRepos(c echo.Context, contentType, securityLevel string) ([]api.RepositoryResponse, error) { + _, orgID := getAccountIdOrgId(c) + ctx := c.Request().Context() + + filter := api.FilterData{Origin: config.OriginLightwell} + if contentType != "" { + filter.ContentType = contentType + } + + repos, _, err := h.DaoRegistry.RepositoryConfig.List(ctx, orgID, api.PaginationData{Limit: MaxLimit}, filter) + if err != nil { + return nil, err + } + + if securityLevel == "" { + return repos.Data, nil + } + filtered := make([]api.RepositoryResponse, 0, len(repos.Data)) + for _, r := range repos.Data { + if strings.EqualFold(r.SecurityLevel, securityLevel) { + filtered = append(filtered, r) + } + } + return filtered, nil +} + +type repoPackageResult struct { + repo api.RepositoryResponse + pkgs []api.LightwellPackageResponse + err error +} + +// aggregatePackages queries Tang for each repo in parallel and merges results. +func (h *LightwellPackagesHandler) aggregatePackages(ctx context.Context, repos []api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageResponse, error) { + results := make([]repoPackageResult, len(repos)) + var wg sync.WaitGroup + + for i, repo := range repos { + wg.Add(1) + go func(idx int, r api.RepositoryResponse) { + defer wg.Done() + pkgs, err := h.fetchPackagesFromRepo(ctx, r, nameSearch) + results[idx] = repoPackageResult{repo: r, pkgs: pkgs, err: err} + }(i, repo) + } + wg.Wait() + + var combined []api.LightwellPackageResponse + var errs []error + for _, res := range results { + if res.err != nil { + errs = append(errs, fmt.Errorf("repo %s: %w", res.repo.Name, res.err)) + continue + } + combined = append(combined, res.pkgs...) + } + + if len(errs) > 0 && len(combined) == 0 { + return nil, errors.Join(errs...) + } + if len(errs) > 0 { + log.Warn().Errs("errors", errs).Msg("partial failure fetching cross-repo packages") + } + + return combined, nil +} + +func (h *LightwellPackagesHandler) fetchPackagesFromRepo(ctx context.Context, repo api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageResponse, error) { + if repo.PublishedDistBasePath == "" { + return nil, nil + } + + repositoryHref, err := h.resolveRepositoryHref(ctx, repo) + if err != nil { + return nil, err + } + + // Fetch all packages from this repo (no server-side pagination — small datasets) + pageOpts := tangy.PageOptions{Offset: 0, Limit: MaxLimit} + + switch repo.ContentType { + case config.ContentTypeMaven: + tangResp, err := h.TangClient.MavenPackageList(ctx, repositoryHref, tangy.MavenPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return mapMavenToLightwellPackages(tangResp, repo), nil + + case config.ContentTypePython: + tangResp, err := h.TangClient.PythonPackageList(ctx, repositoryHref, tangy.PythonPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return mapPythonToLightwellPackages(tangResp, repo), nil + + case config.ContentTypeNpm: + tangResp, err := h.TangClient.NpmPackageList(ctx, repositoryHref, tangy.NpmPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return mapNpmToLightwellPackages(tangResp, repo), nil + + default: + return nil, nil + } +} + +type repoVersionResult struct { + repo api.RepositoryResponse + versions []api.LightwellPackageVersionResponse + err error +} + +// aggregatePackageVersions queries Tang for each repo in parallel and expands +// every package into individual version items. +func (h *LightwellPackagesHandler) aggregatePackageVersions(ctx context.Context, repos []api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageVersionResponse, error) { + results := make([]repoVersionResult, len(repos)) + var wg sync.WaitGroup + + for i, repo := range repos { + wg.Add(1) + go func(idx int, r api.RepositoryResponse) { + defer wg.Done() + versions, err := h.fetchVersionsFromRepo(ctx, r, nameSearch) + results[idx] = repoVersionResult{repo: r, versions: versions, err: err} + }(i, repo) + } + wg.Wait() + + var combined []api.LightwellPackageVersionResponse + var errs []error + for _, res := range results { + if res.err != nil { + errs = append(errs, fmt.Errorf("repo %s: %w", res.repo.Name, res.err)) + continue + } + combined = append(combined, res.versions...) + } + + if len(errs) > 0 && len(combined) == 0 { + return nil, errors.Join(errs...) + } + if len(errs) > 0 { + log.Warn().Errs("errors", errs).Msg("partial failure fetching cross-repo versions") + } + + return combined, nil +} + +func (h *LightwellPackagesHandler) fetchVersionsFromRepo(ctx context.Context, repo api.RepositoryResponse, nameSearch string) ([]api.LightwellPackageVersionResponse, error) { + if repo.PublishedDistBasePath == "" { + return nil, nil + } + + repositoryHref, err := h.resolveRepositoryHref(ctx, repo) + if err != nil { + return nil, err + } + + pageOpts := tangy.PageOptions{Offset: 0, Limit: MaxLimit} + + switch repo.ContentType { + case config.ContentTypeMaven: + tangResp, err := h.TangClient.MavenPackageList(ctx, repositoryHref, tangy.MavenPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return expandMavenVersions(tangResp, repo), nil + + case config.ContentTypePython: + tangResp, err := h.TangClient.PythonPackageList(ctx, repositoryHref, tangy.PythonPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return expandPythonVersions(tangResp, repo), nil + + case config.ContentTypeNpm: + tangResp, err := h.TangClient.NpmPackageList(ctx, repositoryHref, tangy.NpmPackageListFilters{Search: nameSearch}, pageOpts) + if err != nil { + return nil, err + } + return expandNpmVersions(tangResp, repo), nil + + default: + return nil, nil + } +} + +func (h *LightwellPackagesHandler) resolveRepositoryHref(ctx context.Context, repo api.RepositoryResponse) (string, error) { + domainName, err := h.DaoRegistry.Domain.FetchOrCreateDomain(ctx, repo.OrgID) + if err != nil { + return "", err + } + pulpClient := h.PulpClient.WithDomain(domainName) + href, err := pulpClient.ResolveRepositoryFromBasePath(ctx, repo.PublishedDistBasePath) + if err != nil { + return "", fmt.Errorf("repo %s: %w", repo.UUID, err) + } + if href == nil { + return "", fmt.Errorf("repo %s: distribution not found", repo.UUID) + } + return *href, nil +} + +// filterVersionsByResolvingCve keeps only versions that fix the given CVE. +func (h *LightwellPackagesHandler) filterVersionsByResolvingCve(ctx context.Context, items []api.LightwellPackageVersionResponse, cveID string) ([]api.LightwellPackageVersionResponse, error) { + matches, err := h.DaoRegistry.LightwellAdvisory.ListAdvisoriesByCveID(ctx, cveID) + if err != nil { + return nil, err + } + + fixedSet := make(map[string]map[string]bool) + for _, m := range matches { + if fixedSet[m.PackageName] == nil { + fixedSet[m.PackageName] = make(map[string]bool) + } + for _, v := range m.FixedVersions { + fixedSet[m.PackageName][v] = true + } + } + + var result []api.LightwellPackageVersionResponse + for _, item := range items { + if versions, ok := fixedSet[item.Name]; ok && versions[item.Version] { + result = append(result, item) + } + } + return result, nil +} + +// filterVersionsByVulnerableCve keeps only versions of packages affected by +// the given CVE that are NOT in the fixed-versions list. +func (h *LightwellPackagesHandler) filterVersionsByVulnerableCve(ctx context.Context, items []api.LightwellPackageVersionResponse, cveID string) ([]api.LightwellPackageVersionResponse, error) { + matches, err := h.DaoRegistry.LightwellAdvisory.ListAdvisoriesByCveID(ctx, cveID) + if err != nil { + return nil, err + } + + affectedPackages := make(map[string]bool) + fixedSet := make(map[string]map[string]bool) + for _, m := range matches { + affectedPackages[m.PackageName] = true + if fixedSet[m.PackageName] == nil { + fixedSet[m.PackageName] = make(map[string]bool) + } + for _, v := range m.FixedVersions { + fixedSet[m.PackageName][v] = true + } + } + + var result []api.LightwellPackageVersionResponse + for _, item := range items { + if affectedPackages[item.Name] && !fixedSet[item.Name][item.Version] { + result = append(result, item) + } + } + return result, nil +} + +// --- mapping helpers --- + +func mapMavenToLightwellPackages(resp tangy.MavenPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageResponse { + out := make([]api.LightwellPackageResponse, 0, len(resp.Results)) + for _, item := range resp.Results { + releases := make([]api.ReleaseInfo, len(item.LatestReleases)) + for j, rel := range item.LatestReleases { + releases[j] = api.ReleaseInfo{Version: rel.Version, Release: rel.Release, CreatedAt: rel.CreatedAt} + } + out = append(out, api.LightwellPackageResponse{ + Name: item.ArtifactID, + Group: item.GroupID, + ContentType: config.ContentTypeMaven, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + Versions: item.Versions, + LatestReleases: releases, + }) + } + return out +} + +func mapPythonToLightwellPackages(resp tangy.PythonPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageResponse { + out := make([]api.LightwellPackageResponse, 0, len(resp.Results)) + for _, item := range resp.Results { + releases := make([]api.ReleaseInfo, len(item.LatestVersions)) + for j, ver := range item.LatestVersions { + releases[j] = api.ReleaseInfo{Version: ver.Version, CreatedAt: ver.CreatedAt} + } + out = append(out, api.LightwellPackageResponse{ + Name: item.NameNormalized, + ContentType: config.ContentTypePython, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + Versions: item.Versions, + LatestReleases: releases, + }) + } + return out +} + +func mapNpmToLightwellPackages(resp tangy.NpmPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageResponse { + out := make([]api.LightwellPackageResponse, 0, len(resp.Results)) + for _, item := range resp.Results { + releases := make([]api.ReleaseInfo, len(item.LatestVersions)) + for j, ver := range item.LatestVersions { + releases[j] = api.ReleaseInfo{Version: ver.Version, CreatedAt: ver.CreatedAt} + } + scope, name := parseNpmPackageName(item.Name) + out = append(out, api.LightwellPackageResponse{ + Name: name, + Group: scope, + ContentType: config.ContentTypeNpm, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + Versions: item.Versions, + LatestReleases: releases, + }) + } + return out +} + +func expandMavenVersions(resp tangy.MavenPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageVersionResponse { + var out []api.LightwellPackageVersionResponse + for _, item := range resp.Results { + relMap := latestReleaseMap(item.LatestReleases) + for _, v := range item.Versions { + ver := api.LightwellPackageVersionResponse{ + Name: item.ArtifactID, + Group: item.GroupID, + Version: v, + ContentType: config.ContentTypeMaven, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + } + if rel, ok := relMap[v]; ok { + ver.Release = rel.Release + ver.CreatedAt = rel.CreatedAt + } + out = append(out, ver) + } + } + return out +} + +func expandPythonVersions(resp tangy.PythonPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageVersionResponse { + var out []api.LightwellPackageVersionResponse + for _, item := range resp.Results { + verMap := latestVersionMap(item.LatestVersions) + for _, v := range item.Versions { + ver := api.LightwellPackageVersionResponse{ + Name: item.NameNormalized, + Version: v, + ContentType: config.ContentTypePython, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + } + if info, ok := verMap[v]; ok { + ver.CreatedAt = info.CreatedAt + } + out = append(out, ver) + } + } + return out +} + +func expandNpmVersions(resp tangy.NpmPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageVersionResponse { + var out []api.LightwellPackageVersionResponse + for _, item := range resp.Results { + scope, name := parseNpmPackageName(item.Name) + verMap := npmVersionMap(item.LatestVersions) + for _, v := range item.Versions { + ver := api.LightwellPackageVersionResponse{ + Name: name, + Group: scope, + Version: v, + ContentType: config.ContentTypeNpm, + Repository: repo.Name, + RepositoryUUID: repo.UUID, + } + if info, ok := verMap[v]; ok { + ver.CreatedAt = info.CreatedAt + } + out = append(out, ver) + } + } + return out +} + +// --- filter / pagination helpers --- + +func parseLightwellPackageFilters(c echo.Context) api.LightwellPackageFilterData { + var f api.LightwellPackageFilterData + _ = echo.QueryParamsBinder(c). + String("content_type", &f.ContentType). + String("name", &f.Name). + String("repository", &f.Repository). + String("security_level", &f.SecurityLevel). + BindError() + return f +} + +func parseLightwellPackageVersionFilters(c echo.Context) api.LightwellPackageVersionFilterData { + var f api.LightwellPackageVersionFilterData + _ = echo.QueryParamsBinder(c). + String("content_type", &f.ContentType). + String("name", &f.Name). + String("security_level", &f.SecurityLevel). + String("repository", &f.Repository). + String("resolves_cve_id", &f.ResolvesCveID). + String("vulnerable_to_cve_id", &f.VulnerableToCveID). + BindError() + return f +} + +var validContentTypes = map[string]bool{ + config.ContentTypeMaven: true, + config.ContentTypePython: true, + config.ContentTypeNpm: true, +} + +func validateContentType(ct string) error { + if ct == "" { + return nil + } + if !validContentTypes[ct] { + return fmt.Errorf("unsupported type: %s (must be maven, python, or npm)", ct) + } + return nil +} + +func filterReposByName(repos []api.RepositoryResponse, name string) []api.RepositoryResponse { + var out []api.RepositoryResponse + for _, r := range repos { + if strings.EqualFold(r.Name, name) { + out = append(out, r) + } + } + return out +} + +func paginatePackages(items []api.LightwellPackageResponse, offset, limit int) []api.LightwellPackageResponse { + if offset >= len(items) { + return []api.LightwellPackageResponse{} + } + end := offset + limit + if end > len(items) { + end = len(items) + } + return items[offset:end] +} + +func paginateVersions(items []api.LightwellPackageVersionResponse, offset, limit int) []api.LightwellPackageVersionResponse { + if offset >= len(items) { + return []api.LightwellPackageVersionResponse{} + } + end := offset + limit + if end > len(items) { + end = len(items) + } + return items[offset:end] +} + +// release-info lookup helpers for version expansion + +type mavenRelInfo struct { + Release string + CreatedAt string +} + +func latestReleaseMap(releases []tangy.MavenReleaseInfo) map[string]mavenRelInfo { + m := make(map[string]mavenRelInfo, len(releases)) + for _, r := range releases { + m[r.Version] = mavenRelInfo{Release: r.Release, CreatedAt: r.CreatedAt} + } + return m +} + +type versionCreatedAt struct { + CreatedAt string +} + +func latestVersionMap(versions []tangy.PythonVersionInfo) map[string]versionCreatedAt { + m := make(map[string]versionCreatedAt, len(versions)) + for _, v := range versions { + m[v.Version] = versionCreatedAt{CreatedAt: v.CreatedAt} + } + return m +} + +func npmVersionMap(versions []tangy.NpmVersionInfo) map[string]versionCreatedAt { + m := make(map[string]versionCreatedAt, len(versions)) + for _, v := range versions { + m[v.Version] = versionCreatedAt{CreatedAt: v.CreatedAt} + } + return m +} + +// --- nested repo-scoped alias handlers --- + +func (h *LightwellPackagesHandler) listRepoPackages(c echo.Context) error { + repoName := c.Param("repository_name") + c.QueryParams().Set("repository", repoName) + return h.listPackages(c) +} + +func (h *LightwellPackagesHandler) listRepoPackageVersions(c echo.Context) error { + repoName := c.Param("repository_name") + c.QueryParams().Set("repository", repoName) + return h.listPackageVersions(c) +} + +// --- sort helpers --- + +func sortLightwellPackages(items []api.LightwellPackageResponse, sortBy string) { + field, dir := parseSortBy(sortBy) + if field == "" { + field = "name" + } + sort.SliceStable(items, func(i, j int) bool { + var less bool + switch field { + case "name": + less = items[i].Name < items[j].Name + case "content_type": + less = items[i].ContentType < items[j].ContentType + case "repository": + less = items[i].Repository < items[j].Repository + default: + less = items[i].Name < items[j].Name + } + if dir == "desc" { + return !less + } + return less + }) +} + +func sortLightwellVersions(items []api.LightwellPackageVersionResponse, sortBy string) { + field, dir := parseSortBy(sortBy) + if field == "" { + field = "name" + } + sort.SliceStable(items, func(i, j int) bool { + var less bool + switch field { + case "name": + less = items[i].Name < items[j].Name + case "version": + less = items[i].Version < items[j].Version + case "content_type": + less = items[i].ContentType < items[j].ContentType + case "repository": + less = items[i].Repository < items[j].Repository + default: + less = items[i].Name < items[j].Name + } + if dir == "desc" { + return !less + } + return less + }) +} + +func parseSortBy(sortBy string) (field, direction string) { + if sortBy == "" { + return "", "asc" + } + parts := strings.Fields(sortBy) + field = strings.ToLower(parts[0]) + direction = "asc" + if len(parts) > 1 && strings.EqualFold(parts[1], "desc") { + direction = "desc" + } + return field, direction +} diff --git a/pkg/handler/lightwell_packages_test.go b/pkg/handler/lightwell_packages_test.go new file mode 100644 index 000000000..622b7a15b --- /dev/null +++ b/pkg/handler/lightwell_packages_test.go @@ -0,0 +1,535 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/content-services/content-sources-backend/pkg/api" + "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" + "github.com/content-services/content-sources-backend/pkg/config" + "github.com/content-services/content-sources-backend/pkg/dao" + "github.com/content-services/content-sources-backend/pkg/middleware" + "github.com/content-services/content-sources-backend/pkg/test" + test_handler "github.com/content-services/content-sources-backend/pkg/test/handler" + "github.com/content-services/tang/pkg/tangy" + "github.com/labstack/echo/v4" + echo_middleware "github.com/labstack/echo/v4/middleware" + "github.com/redhatinsights/platform-go-middlewares/v2/identity" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type LightwellPackagesSuite struct { + suite.Suite + reg *dao.MockDaoRegistry + tangClient *tangy.MockTangy + pulpClient *pulp_client.MockPulpClient +} + +func TestLightwellPackagesSuite(t *testing.T) { + suite.Run(t, new(LightwellPackagesSuite)) +} + +func (s *LightwellPackagesSuite) SetupTest() { + s.reg = dao.GetMockDaoRegistry(s.T()) + s.tangClient = tangy.NewMockTangy(s.T()) + s.pulpClient = pulp_client.NewMockPulpClient(s.T()) +} + +func (s *LightwellPackagesSuite) serveRouter(req *http.Request) (int, []byte, error) { + router := echo.New() + router.HTTPErrorHandler = config.CustomHTTPErrorHandler + router.Use(echo_middleware.RequestIDWithConfig(echo_middleware.RequestIDConfig{ + TargetHeader: "x-rh-insights-request-id", + })) + router.Use(middleware.WrapMiddlewareWithSkipper(identity.EnforceIdentity, middleware.SkipMiddleware)) + pathPrefix := router.Group(api.FullRootPath()) + RegisterLightwellPackageRoutes(pathPrefix, s.reg.ToDaoRegistry(), s.tangClient, s.pulpClient) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + + response := rr.Result() + defer response.Body.Close() + body, err := io.ReadAll(response.Body) + return response.StatusCode, body, err +} + +// stubLightwellRepos sets up the DAO mock to return the given repos for a List call with origin=lightwell. +func (s *LightwellPackagesSuite) stubLightwellRepos(repos []api.RepositoryResponse) { + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { return f.Origin == config.OriginLightwell }), + ).Return(api.RepositoryCollectionResponse{Data: repos}, int64(len(repos)), nil) +} + +func (s *LightwellPackagesSuite) stubRepoHref(repo api.RepositoryResponse, href string) { + domainName := "test-domain" + s.reg.Domain.On("FetchOrCreateDomain", test.MockCtx(), repo.OrgID).Return(domainName, nil).Maybe() + s.pulpClient.On("WithDomain", domainName).Return(s.pulpClient).Maybe() + s.pulpClient.On("ResolveRepositoryFromBasePath", test.MockCtx(), repo.PublishedDistBasePath).Return(&href, nil).Maybe() +} + +func newMavenRepo() api.RepositoryResponse { + return api.RepositoryResponse{ + UUID: "aaa-bbb-ccc", + Name: "lightwell/java/remediated", + ContentType: config.ContentTypeMaven, + Origin: config.OriginLightwell, + SecurityLevel: "remediated", + PublishedDistBasePath: "java/remediated", + OrgID: test_handler.MockOrgId, + } +} + +func newPythonRepo() api.RepositoryResponse { + return api.RepositoryResponse{ + UUID: "ddd-eee-fff", + Name: "lightwell/python/remediated", + ContentType: config.ContentTypePython, + Origin: config.OriginLightwell, + SecurityLevel: "remediated", + PublishedDistBasePath: "python/remediated", + OrgID: test_handler.MockOrgId, + } +} + +func mavenTangResponse() tangy.MavenPackageListResponse { + return tangy.MavenPackageListResponse{ + Results: []tangy.MavenPackageListItem{ + { + GroupID: "com.fasterxml.jackson.core", + ArtifactID: "jackson-databind", + Versions: []string{"2.15.3.rhlw-00001", "2.14.2.rhlw-00001"}, + LatestReleases: []tangy.MavenReleaseInfo{ + {Version: "2.15.3.rhlw-00001", Release: "rhlw-00001", CreatedAt: "2024-06-01T12:00:00Z"}, + }, + }, + }, + Total: 1, Limit: 200, Offset: 0, + } +} + +func pythonTangResponse() tangy.PythonPackageListResponse { + return tangy.PythonPackageListResponse{ + Results: []tangy.PythonPackageListItem{ + { + Name: "requests", + NameNormalized: "requests", + Versions: []string{"2.31.0.rhlw-00001"}, + LatestVersions: []tangy.PythonVersionInfo{ + {Version: "2.31.0.rhlw-00001", CreatedAt: "2024-05-10T08:00:00Z"}, + }, + }, + }, + Total: 1, Limit: 200, Offset: 0, + } +} + +// --- /lightwell/packages tests --- + +func (s *LightwellPackagesSuite) TestListPackagesSingleRepo() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/default/api/v3/repositories/maven/maven/some-uuid/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, "com.fasterxml.jackson.core", resp.Data[0].Group) + assert.Equal(t, config.ContentTypeMaven, resp.Data[0].ContentType) + assert.Equal(t, "lightwell/java/remediated", resp.Data[0].Repository) + assert.Equal(t, 2, len(resp.Data[0].Versions)) +} + +func (s *LightwellPackagesSuite) TestListPackagesMultiRepo() { + t := s.T() + + mavenRepo := newMavenRepo() + pythonRepo := newPythonRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo, pythonRepo}) + + mavenHref := "/api/pulp/repos/maven/1/" + pythonHref := "/api/pulp/repos/python/1/" + s.stubRepoHref(mavenRepo, mavenHref) + s.stubRepoHref(pythonRepo, pythonHref) + + s.tangClient.On("MavenPackageList", test.MockCtx(), mavenHref, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + s.tangClient.On("PythonPackageList", test.MockCtx(), pythonHref, + tangy.PythonPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(pythonTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) + assert.Len(t, resp.Data, 2) + + contentTypes := map[string]bool{} + for _, p := range resp.Data { + contentTypes[p.ContentType] = true + } + assert.True(t, contentTypes[config.ContentTypeMaven]) + assert.True(t, contentTypes[config.ContentTypePython]) +} + +func (s *LightwellPackagesSuite) TestListPackagesTypeFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + // Only maven repo should be returned when filtering by content_type=maven + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { + return f.Origin == config.OriginLightwell && f.ContentType == config.ContentTypeMaven + }), + ).Return(api.RepositoryCollectionResponse{Data: []api.RepositoryResponse{mavenRepo}}, int64(1), nil) + + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/packages?content_type=maven", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Len(t, resp.Data, 1) + assert.Equal(t, config.ContentTypeMaven, resp.Data[0].ContentType) +} + +func (s *LightwellPackagesSuite) TestListPackagesInvalidType() { + t := s.T() + + path := fmt.Sprintf("%s/lightwell/packages?content_type=invalid", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, code) +} + +func (s *LightwellPackagesSuite) TestListPackagesEmptyResult() { + t := s.T() + + s.stubLightwellRepos([]api.RepositoryResponse{}) + + path := fmt.Sprintf("%s/lightwell/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(0), resp.Meta.Count) + assert.NotNil(t, resp.Data) + assert.Empty(t, resp.Data) +} + +// --- /lightwell/package_versions tests --- + +func (s *LightwellPackagesSuite) TestListPackageVersionsSingleRepo() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) // 2 versions for jackson-databind + assert.Len(t, resp.Data, 2) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, config.ContentTypeMaven, resp.Data[0].ContentType) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsWithNameFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{Search: "jackson"}, + tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/package_versions?name=jackson", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + assert.Len(t, resp.Data, 2) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsPagination() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + // Request with limit=1&offset=0 — should get 1 of 2 versions + path := fmt.Sprintf("%s/lightwell/package_versions?limit=1&offset=0", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) // total is 2 + assert.Len(t, resp.Data, 1) // page is 1 + assert.NotEmpty(t, resp.Links.Next) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsInvalidType() { + t := s.T() + + path := fmt.Sprintf("%s/lightwell/package_versions?content_type=bogus", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, _, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, code) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsEmptyResult() { + t := s.T() + + s.stubLightwellRepos([]api.RepositoryResponse{}) + + path := fmt.Sprintf("%s/lightwell/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + assert.Equal(t, int64(0), resp.Meta.Count) + assert.NotNil(t, resp.Data) + assert.Empty(t, resp.Data) +} + +// --- resolves_cve_id / vulnerable_to_cve_id filter tests --- + +func (s *LightwellPackagesSuite) TestListPackageVersionsResolvesCveFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + s.reg.LightwellAdvisory.On("ListAdvisoriesByCveID", test.MockCtx(), "CVE-2024-9999").Return([]dao.LightwellAdvisoryCveMatch{ + { + PackageName: "jackson-databind", + FixedVersions: []string{"2.15.3.rhlw-00001"}, + RepoName: "lightwell/java/remediated", + Severity: "critical", + }, + }, nil) + + path := fmt.Sprintf("%s/lightwell/package_versions?resolves_cve_id=CVE-2024-9999", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, "2.15.3.rhlw-00001", resp.Data[0].Version) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsVulnerableToCveFilter() { + t := s.T() + + mavenRepo := newMavenRepo() + s.stubLightwellRepos([]api.RepositoryResponse{mavenRepo}) + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + // Advisory says jackson-databind is fixed at 2.15.3.rhlw-00001, so + // the older version 2.14.2.rhlw-00001 should be returned as vulnerable. + s.reg.LightwellAdvisory.On("ListAdvisoriesByCveID", test.MockCtx(), "CVE-2024-8888").Return([]dao.LightwellAdvisoryCveMatch{ + { + PackageName: "jackson-databind", + FixedVersions: []string{"2.15.3.rhlw-00001"}, + RepoName: "lightwell/java/remediated", + Severity: "important", + }, + }, nil) + + path := fmt.Sprintf("%s/lightwell/package_versions?vulnerable_to_cve_id=CVE-2024-8888", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) + assert.Equal(t, "2.14.2.rhlw-00001", resp.Data[0].Version) +} + +// --- nested repo-scoped alias tests --- + +func (s *LightwellPackagesSuite) TestNestedRepoPackagesAlias() { + t := s.T() + + mavenRepo := newMavenRepo() + mavenRepo.Name = "java-remediated" + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { return f.Origin == config.OriginLightwell }), + ).Return(api.RepositoryCollectionResponse{Data: []api.RepositoryResponse{mavenRepo}}, int64(1), nil) + + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/repositories/java-remediated/packages", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(1), resp.Meta.Count) + assert.Len(t, resp.Data, 1) + assert.Equal(t, "jackson-databind", resp.Data[0].Name) +} + +func (s *LightwellPackagesSuite) TestNestedRepoPackageVersionsAlias() { + t := s.T() + + mavenRepo := newMavenRepo() + mavenRepo.Name = "java-remediated" + s.reg.RepositoryConfig.On( + "List", test.MockCtx(), test_handler.MockOrgId, + mock.MatchedBy(func(p api.PaginationData) bool { return p.Limit == MaxLimit }), + mock.MatchedBy(func(f api.FilterData) bool { return f.Origin == config.OriginLightwell }), + ).Return(api.RepositoryCollectionResponse{Data: []api.RepositoryResponse{mavenRepo}}, int64(1), nil) + + href := "/api/pulp/repos/maven/1/" + s.stubRepoHref(mavenRepo, href) + s.tangClient.On("MavenPackageList", test.MockCtx(), href, + tangy.MavenPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(mavenTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/repositories/java-remediated/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Equal(t, int64(2), resp.Meta.Count) + assert.Len(t, resp.Data, 2) +} diff --git a/pkg/handler/repositories.go b/pkg/handler/repositories.go index 22a6b00a2..01a941bb4 100644 --- a/pkg/handler/repositories.go +++ b/pkg/handler/repositories.go @@ -12,6 +12,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" ce "github.com/content-services/content-sources-backend/pkg/errors" + "github.com/content-services/content-sources-backend/pkg/rbac" "github.com/content-services/content-sources-backend/pkg/tasks" "github.com/content-services/content-sources-backend/pkg/tasks/client" @@ -122,9 +123,40 @@ func (rh *RepositoryHandler) listRepositories(c echo.Context) error { return ce.NewErrorResponse(ce.HttpCodeForDaoError(err), "Error listing repositories", err.Error()) } + rh.enrichLightwellRepoCounts(c, &repos) + return c.JSON(200, setCollectionResponseMetadata(&repos, c, totalRepos)) } +// enrichLightwellRepoCounts populates packages_count, versions_count, and +// remediations_count on Lightwell-origin repositories. These spec-required +// fields are omitted for non-Lightwell repos to avoid breaking existing consumers. +func (rh *RepositoryHandler) enrichLightwellRepoCounts(c echo.Context, repos *api.RepositoryCollectionResponse) { + for i := range repos.Data { + repo := &repos.Data[i] + if repo.Origin != config.OriginLightwell { + continue + } + pkgCount := repo.PackageCount + verCount := repo.VersionCount + repo.PackagesCount = &pkgCount + repo.VersionsCount = &verCount + + repoUUID, err := uuid.Parse(repo.UUID) + if err != nil { + log.Ctx(c.Request().Context()).Warn().Err(err).Str("uuid", repo.UUID).Msg("invalid UUID for advisory count") + continue + } + count, err := rh.DaoRegistry.LightwellAdvisory.CountAdvisoriesByRepo(c.Request().Context(), repoUUID) + if err != nil { + log.Ctx(c.Request().Context()).Warn().Err(err).Str("uuid", repo.UUID).Msg("failed to count advisories") + continue + } + remCount := int(count) + repo.RemediationsCount = &remCount + } +} + // CreateRepository godoc // @Summary Create Repository // @ID createRepository From 7dad8d682354f3363ff513846916b603a28e839c Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 26 Aug 2026 17:10:02 -0400 Subject: [PATCH 25/47] LWLP-5: regenerate OpenAPI spec for Lightwell endpoints --- api/docs.go | 402 +++++++++++++++++++++++++++++++++++++++++ api/openapi.json | 458 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 860 insertions(+) diff --git a/api/docs.go b/api/docs.go index b76f7581c..8070aa9f7 100644 --- a/api/docs.go +++ b/api/docs.go @@ -282,6 +282,80 @@ const docTemplate = `{ } } }, + "/lightwell/advisories": { + "get": { + "description": "List security advisories for Lightwell remediated packages with optional filtering.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Advisories", + "operationId": "listLightwellAdvisories", + "parameters": [ + { + "type": "string", + "description": "Filter by repository UUID", + "name": "repository_uuid", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "package_name", + "in": "query" + }, + { + "type": "string", + "description": "Minimum severity level (low, moderate, important, critical)", + "name": "severity_min", + "in": "query" + }, + { + "type": "string", + "description": "Filter by CVE ID (exact match)", + "name": "cve_id", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellAdvisoryCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, "/lightwell/beacon/vulnerabilities/": { "get": { "description": "List Lightwell vulnerabilities for a customer, with filters, pagination, and aggregate counts.", @@ -474,6 +548,160 @@ const docTemplate = `{ } } }, + "/lightwell/package_versions": { + "get": { + "description": "List individual package versions aggregated across all Lightwell repositories, with optional CVE-based filtering.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Package Versions (cross-repo)", + "operationId": "listLightwellPackageVersions", + "parameters": [ + { + "type": "string", + "description": "Filter by content type (maven, python, npm)", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by security level (validated, remediated)", + "name": "security_level", + "in": "query" + }, + { + "type": "string", + "description": "Filter by repository name", + "name": "repository", + "in": "query" + }, + { + "type": "string", + "description": "Show only packages that resolve this CVE", + "name": "resolves_cve_id", + "in": "query" + }, + { + "type": "string", + "description": "Show only packages vulnerable to this CVE", + "name": "vulnerable_to_cve_id", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellPackageVersionCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, + "/lightwell/packages": { + "get": { + "description": "List packages aggregated across all Lightwell repositories, with optional filtering by content type, name, and security level.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "lightwell" + ], + "summary": "List Lightwell Packages (cross-repo)", + "operationId": "listLightwellPackages", + "parameters": [ + { + "type": "string", + "description": "Filter by content type (maven, python, npm)", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "Filter by package name (substring match)", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by security level (validated, remediated)", + "name": "security_level", + "in": "query" + }, + { + "type": "integer", + "description": "Limit of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset into results", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LightwellPackageCollectionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/errors.ErrorResponse" + } + } + } + } + }, "/module_streams/search": { "post": { "description": "List modules and their streams for repositories", @@ -5097,6 +5325,55 @@ const docTemplate = `{ } } }, + "api.LightwellAdvisoryCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellAdvisoryResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellAdvisoryResponse": { + "type": "object", + "properties": { + "advisory_id": { + "type": "string" + }, + "details": { + "type": "string" + }, + "fixed_versions": { + "type": "array", + "items": { + "type": "string" + } + }, + "package_name": { + "type": "string" + }, + "reference_urls": { + "type": "array", + "items": { + "type": "string" + } + }, + "repository": { + "type": "string" + }, + "severity": { + "type": "string" + } + } + }, "api.LightwellCustomerIdsResponse": { "type": "object", "properties": { @@ -5121,6 +5398,101 @@ const docTemplate = `{ } } }, + "api.LightwellPackageCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellPackageResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellPackageResponse": { + "type": "object", + "properties": { + "content_type": { + "type": "string" + }, + "group": { + "type": "string" + }, + "latest_releases": { + "type": "array", + "items": { + "$ref": "#/definitions/api.ReleaseInfo" + } + }, + "name": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "versions": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "api.LightwellPackageVersionCollectionResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.LightwellPackageVersionResponse" + } + }, + "links": { + "$ref": "#/definitions/api.Links" + }, + "meta": { + "$ref": "#/definitions/api.ResponseMetadata" + } + } + }, + "api.LightwellPackageVersionResponse": { + "type": "object", + "properties": { + "content_type": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "group": { + "type": "string" + }, + "name": { + "type": "string" + }, + "release": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, "api.LightwellVulnerabilityCollectionMeta": { "type": "object", "properties": { @@ -6065,6 +6437,11 @@ const docTemplate = `{ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "type": "integer", + "readOnly": true + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "type": "boolean", @@ -6075,6 +6452,11 @@ const docTemplate = `{ "type": "string", "readOnly": true }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "type": "integer", + "readOnly": true + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "type": "string", @@ -6101,6 +6483,11 @@ const docTemplate = `{ "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "type": "integer", + "readOnly": true + }, "warnings": { "description": "Warnings to alert user of mismatched fields if there is an existing repo with the same URL", "type": "array", @@ -6395,6 +6782,11 @@ const docTemplate = `{ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "type": "integer", + "readOnly": true + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "type": "boolean", @@ -6405,6 +6797,11 @@ const docTemplate = `{ "type": "string", "readOnly": true }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "type": "integer", + "readOnly": true + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "type": "string", @@ -6430,6 +6827,11 @@ const docTemplate = `{ "version_count": { "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" + }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "type": "integer", + "readOnly": true } } }, diff --git a/api/openapi.json b/api/openapi.json index 9c53230b0..9ae7c6ea6 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -325,6 +325,55 @@ }, "type": "object" }, + "api.LightwellAdvisoryCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellAdvisoryResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellAdvisoryResponse": { + "properties": { + "advisory_id": { + "type": "string" + }, + "details": { + "type": "string" + }, + "fixed_versions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "package_name": { + "type": "string" + }, + "reference_urls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repository": { + "type": "string" + }, + "severity": { + "type": "string" + } + }, + "type": "object" + }, "api.LightwellCustomerIdsResponse": { "properties": { "data": { @@ -349,6 +398,101 @@ }, "type": "object" }, + "api.LightwellPackageCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellPackageResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellPackageResponse": { + "properties": { + "content_type": { + "type": "string" + }, + "group": { + "type": "string" + }, + "latest_releases": { + "items": { + "$ref": "#/components/schemas/api.ReleaseInfo" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "versions": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "api.LightwellPackageVersionCollectionResponse": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/api.LightwellPackageVersionResponse" + }, + "type": "array" + }, + "links": { + "$ref": "#/components/schemas/api.Links" + }, + "meta": { + "$ref": "#/components/schemas/api.ResponseMetadata" + } + }, + "type": "object" + }, + "api.LightwellPackageVersionResponse": { + "properties": { + "content_type": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "group": { + "type": "string" + }, + "name": { + "type": "string" + }, + "release": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "repository_uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, "api.LightwellVulnerabilityCollectionMeta": { "properties": { "blocked_count": { @@ -1292,6 +1436,11 @@ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "readOnly": true, + "type": "integer" + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "readOnly": true, @@ -1302,6 +1451,11 @@ "readOnly": true, "type": "string" }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "readOnly": true, + "type": "integer" + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "readOnly": true, @@ -1328,6 +1482,11 @@ "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "readOnly": true, + "type": "integer" + }, "warnings": { "description": "Warnings to alert user of mismatched fields if there is an existing repo with the same URL", "items": { @@ -1622,6 +1781,11 @@ "description": "Number of packages last read in the repository", "type": "integer" }, + "packages_count": { + "description": "Lightwell: total distinct packages", + "readOnly": true, + "type": "integer" + }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "readOnly": true, @@ -1632,6 +1796,11 @@ "readOnly": true, "type": "string" }, + "remediations_count": { + "description": "Lightwell: total security advisories", + "readOnly": true, + "type": "integer" + }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "readOnly": true, @@ -1657,6 +1826,11 @@ "version_count": { "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" + }, + "versions_count": { + "description": "Lightwell: total distinct versions", + "readOnly": true, + "type": "integer" } }, "type": "object" @@ -3174,6 +3348,98 @@ ] } }, + "/lightwell/advisories": { + "get": { + "description": "List security advisories for Lightwell remediated packages with optional filtering.", + "operationId": "listLightwellAdvisories", + "parameters": [ + { + "description": "Filter by repository UUID", + "in": "query", + "name": "repository_uuid", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "package_name", + "schema": { + "type": "string" + } + }, + { + "description": "Minimum severity level (low, moderate, important, critical)", + "in": "query", + "name": "severity_min", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by CVE ID (exact match)", + "in": "query", + "name": "cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellAdvisoryCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Advisories", + "tags": [ + "lightwell" + ] + } + }, "/lightwell/beacon/vulnerabilities/": { "get": { "description": "List Lightwell vulnerabilities for a customer, with filters, pagination, and aggregate counts.", @@ -3416,6 +3682,198 @@ ] } }, + "/lightwell/package_versions": { + "get": { + "description": "List individual package versions aggregated across all Lightwell repositories, with optional CVE-based filtering.", + "operationId": "listLightwellPackageVersions", + "parameters": [ + { + "description": "Filter by content type (maven, python, npm)", + "in": "query", + "name": "type", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "name", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by security level (validated, remediated)", + "in": "query", + "name": "security_level", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by repository name", + "in": "query", + "name": "repository", + "schema": { + "type": "string" + } + }, + { + "description": "Show only packages that resolve this CVE", + "in": "query", + "name": "resolves_cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Show only packages vulnerable to this CVE", + "in": "query", + "name": "vulnerable_to_cve_id", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellPackageVersionCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Package Versions (cross-repo)", + "tags": [ + "lightwell" + ] + } + }, + "/lightwell/packages": { + "get": { + "description": "List packages aggregated across all Lightwell repositories, with optional filtering by content type, name, and security level.", + "operationId": "listLightwellPackages", + "parameters": [ + { + "description": "Filter by content type (maven, python, npm)", + "in": "query", + "name": "type", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by package name (substring match)", + "in": "query", + "name": "name", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by security level (validated, remediated)", + "in": "query", + "name": "security_level", + "schema": { + "type": "string" + } + }, + { + "description": "Limit of results to return", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "Offset into results", + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/api.LightwellPackageCollectionResponse" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errors.ErrorResponse" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "List Lightwell Packages (cross-repo)", + "tags": [ + "lightwell" + ] + } + }, "/module_streams/search": { "post": { "description": "List modules and their streams for repositories", From e3e7cf4cf4ac0bfc4ed1773d0d200728721e643f Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 26 Aug 2026 17:10:02 -0400 Subject: [PATCH 26/47] LWLP-5: regenerate mocks --- pkg/cache/cache_mock.go | 24 +- .../candlepin_client/candlepin_client_mock.go | 60 +- .../feature_service_client_mock.go | 6 +- pkg/clients/pulp_client/pulp_client_mock.go | 108 ++-- .../roadmap_client/roadmap_client_mock.go | 6 +- pkg/dao/dao_mock.go | 523 ++++++++++++------ pkg/tasks/client/client_mock.go | 4 +- pkg/tasks/queue/queue_mock.go | 24 +- 8 files changed, 482 insertions(+), 273 deletions(-) diff --git a/pkg/cache/cache_mock.go b/pkg/cache/cache_mock.go index 200cb4866..72a4b4e09 100644 --- a/pkg/cache/cache_mock.go +++ b/pkg/cache/cache_mock.go @@ -74,7 +74,7 @@ type MockCache_GetAccessList_Call struct { // GetAccessList is a helper method to define mock.On call // - ctx context.Context -func (_e *MockCache_Expecter) GetAccessList(ctx interface{}) *MockCache_GetAccessList_Call { +func (_e *MockCache_Expecter) GetAccessList(ctx any) *MockCache_GetAccessList_Call { return &MockCache_GetAccessList_Call{Call: _e.mock.On("GetAccessList", ctx)} } @@ -138,7 +138,7 @@ type MockCache_GetContentCounts_Call struct { // - ctx context.Context // - domainName string // - repoUUID string -func (_e *MockCache_Expecter) GetContentCounts(ctx interface{}, domainName interface{}, repoUUID interface{}) *MockCache_GetContentCounts_Call { +func (_e *MockCache_Expecter) GetContentCounts(ctx any, domainName any, repoUUID any) *MockCache_GetContentCounts_Call { return &MockCache_GetContentCounts_Call{Call: _e.mock.On("GetContentCounts", ctx, domainName, repoUUID)} } @@ -210,7 +210,7 @@ type MockCache_GetFeatureStatus_Call struct { // GetFeatureStatus is a helper method to define mock.On call // - ctx context.Context -func (_e *MockCache_Expecter) GetFeatureStatus(ctx interface{}) *MockCache_GetFeatureStatus_Call { +func (_e *MockCache_Expecter) GetFeatureStatus(ctx any) *MockCache_GetFeatureStatus_Call { return &MockCache_GetFeatureStatus_Call{Call: _e.mock.On("GetFeatureStatus", ctx)} } @@ -272,7 +272,7 @@ type MockCache_GetRoadmapAppstreams_Call struct { // GetRoadmapAppstreams is a helper method to define mock.On call // - ctx context.Context -func (_e *MockCache_Expecter) GetRoadmapAppstreams(ctx interface{}) *MockCache_GetRoadmapAppstreams_Call { +func (_e *MockCache_Expecter) GetRoadmapAppstreams(ctx any) *MockCache_GetRoadmapAppstreams_Call { return &MockCache_GetRoadmapAppstreams_Call{Call: _e.mock.On("GetRoadmapAppstreams", ctx)} } @@ -334,7 +334,7 @@ type MockCache_GetRoadmapRhelLifecycle_Call struct { // GetRoadmapRhelLifecycle is a helper method to define mock.On call // - ctx context.Context -func (_e *MockCache_Expecter) GetRoadmapRhelLifecycle(ctx interface{}) *MockCache_GetRoadmapRhelLifecycle_Call { +func (_e *MockCache_Expecter) GetRoadmapRhelLifecycle(ctx any) *MockCache_GetRoadmapRhelLifecycle_Call { return &MockCache_GetRoadmapRhelLifecycle_Call{Call: _e.mock.On("GetRoadmapRhelLifecycle", ctx)} } @@ -396,7 +396,7 @@ type MockCache_GetSubscriptionCheck_Call struct { // GetSubscriptionCheck is a helper method to define mock.On call // - ctx context.Context -func (_e *MockCache_Expecter) GetSubscriptionCheck(ctx interface{}) *MockCache_GetSubscriptionCheck_Call { +func (_e *MockCache_Expecter) GetSubscriptionCheck(ctx any) *MockCache_GetSubscriptionCheck_Call { return &MockCache_GetSubscriptionCheck_Call{Call: _e.mock.On("GetSubscriptionCheck", ctx)} } @@ -448,7 +448,7 @@ type MockCache_SetAccessList_Call struct { // SetAccessList is a helper method to define mock.On call // - ctx context.Context // - accessList rbac.AccessList -func (_e *MockCache_Expecter) SetAccessList(ctx interface{}, accessList interface{}) *MockCache_SetAccessList_Call { +func (_e *MockCache_Expecter) SetAccessList(ctx any, accessList any) *MockCache_SetAccessList_Call { return &MockCache_SetAccessList_Call{Call: _e.mock.On("SetAccessList", ctx, accessList)} } @@ -507,7 +507,7 @@ type MockCache_SetContentCounts_Call struct { // - domainName string // - repoUUID string // - contentCounts RepoContentCount -func (_e *MockCache_Expecter) SetContentCounts(ctx interface{}, domainName interface{}, repoUUID interface{}, contentCounts interface{}) *MockCache_SetContentCounts_Call { +func (_e *MockCache_Expecter) SetContentCounts(ctx any, domainName any, repoUUID any, contentCounts any) *MockCache_SetContentCounts_Call { return &MockCache_SetContentCounts_Call{Call: _e.mock.On("SetContentCounts", ctx, domainName, repoUUID, contentCounts)} } @@ -574,7 +574,7 @@ type MockCache_SetFeatureStatus_Call struct { // SetFeatureStatus is a helper method to define mock.On call // - ctx context.Context // - response api.FeatureStatus -func (_e *MockCache_Expecter) SetFeatureStatus(ctx interface{}, response interface{}) *MockCache_SetFeatureStatus_Call { +func (_e *MockCache_Expecter) SetFeatureStatus(ctx any, response any) *MockCache_SetFeatureStatus_Call { return &MockCache_SetFeatureStatus_Call{Call: _e.mock.On("SetFeatureStatus", ctx, response)} } @@ -620,7 +620,7 @@ type MockCache_SetRoadmapAppstreams_Call struct { // SetRoadmapAppstreams is a helper method to define mock.On call // - ctx context.Context // - roadmapAppstreamsResponse []byte -func (_e *MockCache_Expecter) SetRoadmapAppstreams(ctx interface{}, roadmapAppstreamsResponse interface{}) *MockCache_SetRoadmapAppstreams_Call { +func (_e *MockCache_Expecter) SetRoadmapAppstreams(ctx any, roadmapAppstreamsResponse any) *MockCache_SetRoadmapAppstreams_Call { return &MockCache_SetRoadmapAppstreams_Call{Call: _e.mock.On("SetRoadmapAppstreams", ctx, roadmapAppstreamsResponse)} } @@ -666,7 +666,7 @@ type MockCache_SetRoadmapRhelLifecycle_Call struct { // SetRoadmapRhelLifecycle is a helper method to define mock.On call // - ctx context.Context // - rhelLifecyleResponse []byte -func (_e *MockCache_Expecter) SetRoadmapRhelLifecycle(ctx interface{}, rhelLifecyleResponse interface{}) *MockCache_SetRoadmapRhelLifecycle_Call { +func (_e *MockCache_Expecter) SetRoadmapRhelLifecycle(ctx any, rhelLifecyleResponse any) *MockCache_SetRoadmapRhelLifecycle_Call { return &MockCache_SetRoadmapRhelLifecycle_Call{Call: _e.mock.On("SetRoadmapRhelLifecycle", ctx, rhelLifecyleResponse)} } @@ -723,7 +723,7 @@ type MockCache_SetSubscriptionCheck_Call struct { // SetSubscriptionCheck is a helper method to define mock.On call // - ctx context.Context // - response api.SubscriptionCheckResponse -func (_e *MockCache_Expecter) SetSubscriptionCheck(ctx interface{}, response interface{}) *MockCache_SetSubscriptionCheck_Call { +func (_e *MockCache_Expecter) SetSubscriptionCheck(ctx any, response any) *MockCache_SetSubscriptionCheck_Call { return &MockCache_SetSubscriptionCheck_Call{Call: _e.mock.On("SetSubscriptionCheck", ctx, response)} } diff --git a/pkg/clients/candlepin_client/candlepin_client_mock.go b/pkg/clients/candlepin_client/candlepin_client_mock.go index f2a416c7d..be655dbe2 100644 --- a/pkg/clients/candlepin_client/candlepin_client_mock.go +++ b/pkg/clients/candlepin_client/candlepin_client_mock.go @@ -64,7 +64,7 @@ type MockCandlepinClient_AddContentBatchToProduct_Call struct { // - ctx context.Context // - orgID string // - contentIDs []string -func (_e *MockCandlepinClient_Expecter) AddContentBatchToProduct(ctx interface{}, orgID interface{}, contentIDs interface{}) *MockCandlepinClient_AddContentBatchToProduct_Call { +func (_e *MockCandlepinClient_Expecter) AddContentBatchToProduct(ctx any, orgID any, contentIDs any) *MockCandlepinClient_AddContentBatchToProduct_Call { return &MockCandlepinClient_AddContentBatchToProduct_Call{Call: _e.mock.On("AddContentBatchToProduct", ctx, orgID, contentIDs)} } @@ -128,7 +128,7 @@ type MockCandlepinClient_AssociateEnvironment_Call struct { // - orgID string // - templateName string // - consumerUuid string -func (_e *MockCandlepinClient_Expecter) AssociateEnvironment(ctx interface{}, orgID interface{}, templateName interface{}, consumerUuid interface{}) *MockCandlepinClient_AssociateEnvironment_Call { +func (_e *MockCandlepinClient_Expecter) AssociateEnvironment(ctx any, orgID any, templateName any, consumerUuid any) *MockCandlepinClient_AssociateEnvironment_Call { return &MockCandlepinClient_AssociateEnvironment_Call{Call: _e.mock.On("AssociateEnvironment", ctx, orgID, templateName, consumerUuid)} } @@ -207,7 +207,7 @@ type MockCandlepinClient_CreateConsumer_Call struct { // - ctx context.Context // - orgID string // - name string -func (_e *MockCandlepinClient_Expecter) CreateConsumer(ctx interface{}, orgID interface{}, name interface{}) *MockCandlepinClient_CreateConsumer_Call { +func (_e *MockCandlepinClient_Expecter) CreateConsumer(ctx any, orgID any, name any) *MockCandlepinClient_CreateConsumer_Call { return &MockCandlepinClient_CreateConsumer_Call{Call: _e.mock.On("CreateConsumer", ctx, orgID, name)} } @@ -270,7 +270,7 @@ type MockCandlepinClient_CreateContent_Call struct { // - ctx context.Context // - orgID string // - content caliri.ContentDTO -func (_e *MockCandlepinClient_Expecter) CreateContent(ctx interface{}, orgID interface{}, content interface{}) *MockCandlepinClient_CreateContent_Call { +func (_e *MockCandlepinClient_Expecter) CreateContent(ctx any, orgID any, content any) *MockCandlepinClient_CreateContent_Call { return &MockCandlepinClient_CreateContent_Call{Call: _e.mock.On("CreateContent", ctx, orgID, content)} } @@ -333,7 +333,7 @@ type MockCandlepinClient_CreateContentBatch_Call struct { // - ctx context.Context // - orgID string // - content []caliri.ContentDTO -func (_e *MockCandlepinClient_Expecter) CreateContentBatch(ctx interface{}, orgID interface{}, content interface{}) *MockCandlepinClient_CreateContentBatch_Call { +func (_e *MockCandlepinClient_Expecter) CreateContentBatch(ctx any, orgID any, content any) *MockCandlepinClient_CreateContentBatch_Call { return &MockCandlepinClient_CreateContentBatch_Call{Call: _e.mock.On("CreateContentBatch", ctx, orgID, content)} } @@ -409,7 +409,7 @@ type MockCandlepinClient_CreateEnvironment_Call struct { // - name string // - id string // - prefix string -func (_e *MockCandlepinClient_Expecter) CreateEnvironment(ctx interface{}, orgID interface{}, name interface{}, id interface{}, prefix interface{}) *MockCandlepinClient_CreateEnvironment_Call { +func (_e *MockCandlepinClient_Expecter) CreateEnvironment(ctx any, orgID any, name any, id any, prefix any) *MockCandlepinClient_CreateEnvironment_Call { return &MockCandlepinClient_CreateEnvironment_Call{Call: _e.mock.On("CreateEnvironment", ctx, orgID, name, id, prefix)} } @@ -480,7 +480,7 @@ type MockCandlepinClient_CreateOwner_Call struct { // CreateOwner is a helper method to define mock.On call // - ctx context.Context -func (_e *MockCandlepinClient_Expecter) CreateOwner(ctx interface{}) *MockCandlepinClient_CreateOwner_Call { +func (_e *MockCandlepinClient_Expecter) CreateOwner(ctx any) *MockCandlepinClient_CreateOwner_Call { return &MockCandlepinClient_CreateOwner_Call{Call: _e.mock.On("CreateOwner", ctx)} } @@ -541,7 +541,7 @@ type MockCandlepinClient_CreatePool_Call struct { // CreatePool is a helper method to define mock.On call // - ctx context.Context // - orgID string -func (_e *MockCandlepinClient_Expecter) CreatePool(ctx interface{}, orgID interface{}) *MockCandlepinClient_CreatePool_Call { +func (_e *MockCandlepinClient_Expecter) CreatePool(ctx any, orgID any) *MockCandlepinClient_CreatePool_Call { return &MockCandlepinClient_CreatePool_Call{Call: _e.mock.On("CreatePool", ctx, orgID)} } @@ -598,7 +598,7 @@ type MockCandlepinClient_CreateProduct_Call struct { // CreateProduct is a helper method to define mock.On call // - ctx context.Context // - orgID string -func (_e *MockCandlepinClient_Expecter) CreateProduct(ctx interface{}, orgID interface{}) *MockCandlepinClient_CreateProduct_Call { +func (_e *MockCandlepinClient_Expecter) CreateProduct(ctx any, orgID any) *MockCandlepinClient_CreateProduct_Call { return &MockCandlepinClient_CreateProduct_Call{Call: _e.mock.On("CreateProduct", ctx, orgID)} } @@ -655,7 +655,7 @@ type MockCandlepinClient_DeleteConsumer_Call struct { // DeleteConsumer is a helper method to define mock.On call // - ctx context.Context // - consumerUUID string -func (_e *MockCandlepinClient_Expecter) DeleteConsumer(ctx interface{}, consumerUUID interface{}) *MockCandlepinClient_DeleteConsumer_Call { +func (_e *MockCandlepinClient_Expecter) DeleteConsumer(ctx any, consumerUUID any) *MockCandlepinClient_DeleteConsumer_Call { return &MockCandlepinClient_DeleteConsumer_Call{Call: _e.mock.On("DeleteConsumer", ctx, consumerUUID)} } @@ -713,7 +713,7 @@ type MockCandlepinClient_DeleteContent_Call struct { // - ctx context.Context // - ownerKey string // - repoConfigUUID string -func (_e *MockCandlepinClient_Expecter) DeleteContent(ctx interface{}, ownerKey interface{}, repoConfigUUID interface{}) *MockCandlepinClient_DeleteContent_Call { +func (_e *MockCandlepinClient_Expecter) DeleteContent(ctx any, ownerKey any, repoConfigUUID any) *MockCandlepinClient_DeleteContent_Call { return &MockCandlepinClient_DeleteContent_Call{Call: _e.mock.On("DeleteContent", ctx, ownerKey, repoConfigUUID)} } @@ -775,7 +775,7 @@ type MockCandlepinClient_DeleteEnvironment_Call struct { // DeleteEnvironment is a helper method to define mock.On call // - ctx context.Context // - templateUUID string -func (_e *MockCandlepinClient_Expecter) DeleteEnvironment(ctx interface{}, templateUUID interface{}) *MockCandlepinClient_DeleteEnvironment_Call { +func (_e *MockCandlepinClient_Expecter) DeleteEnvironment(ctx any, templateUUID any) *MockCandlepinClient_DeleteEnvironment_Call { return &MockCandlepinClient_DeleteEnvironment_Call{Call: _e.mock.On("DeleteEnvironment", ctx, templateUUID)} } @@ -833,7 +833,7 @@ type MockCandlepinClient_DemoteContentFromEnvironment_Call struct { // - ctx context.Context // - templateUUID string // - customRepoConfigUUIDs []string -func (_e *MockCandlepinClient_Expecter) DemoteContentFromEnvironment(ctx interface{}, templateUUID interface{}, customRepoConfigUUIDs interface{}) *MockCandlepinClient_DemoteContentFromEnvironment_Call { +func (_e *MockCandlepinClient_Expecter) DemoteContentFromEnvironment(ctx any, templateUUID any, customRepoConfigUUIDs any) *MockCandlepinClient_DemoteContentFromEnvironment_Call { return &MockCandlepinClient_DemoteContentFromEnvironment_Call{Call: _e.mock.On("DemoteContentFromEnvironment", ctx, templateUUID, customRepoConfigUUIDs)} } @@ -906,7 +906,7 @@ type MockCandlepinClient_FetchConsumer_Call struct { // FetchConsumer is a helper method to define mock.On call // - ctx context.Context // - consumerUUID string -func (_e *MockCandlepinClient_Expecter) FetchConsumer(ctx interface{}, consumerUUID interface{}) *MockCandlepinClient_FetchConsumer_Call { +func (_e *MockCandlepinClient_Expecter) FetchConsumer(ctx any, consumerUUID any) *MockCandlepinClient_FetchConsumer_Call { return &MockCandlepinClient_FetchConsumer_Call{Call: _e.mock.On("FetchConsumer", ctx, consumerUUID)} } @@ -975,7 +975,7 @@ type MockCandlepinClient_FetchContent_Call struct { // - ctx context.Context // - orgID string // - repoConfigUUID string -func (_e *MockCandlepinClient_Expecter) FetchContent(ctx interface{}, orgID interface{}, repoConfigUUID interface{}) *MockCandlepinClient_FetchContent_Call { +func (_e *MockCandlepinClient_Expecter) FetchContent(ctx any, orgID any, repoConfigUUID any) *MockCandlepinClient_FetchContent_Call { return &MockCandlepinClient_FetchContent_Call{Call: _e.mock.On("FetchContent", ctx, orgID, repoConfigUUID)} } @@ -1048,7 +1048,7 @@ type MockCandlepinClient_FetchContentOverrides_Call struct { // FetchContentOverrides is a helper method to define mock.On call // - ctx context.Context // - templateUUID string -func (_e *MockCandlepinClient_Expecter) FetchContentOverrides(ctx interface{}, templateUUID interface{}) *MockCandlepinClient_FetchContentOverrides_Call { +func (_e *MockCandlepinClient_Expecter) FetchContentOverrides(ctx any, templateUUID any) *MockCandlepinClient_FetchContentOverrides_Call { return &MockCandlepinClient_FetchContentOverrides_Call{Call: _e.mock.On("FetchContentOverrides", ctx, templateUUID)} } @@ -1117,7 +1117,7 @@ type MockCandlepinClient_FetchContentOverridesForRepo_Call struct { // - ctx context.Context // - templateUUID string // - label string -func (_e *MockCandlepinClient_Expecter) FetchContentOverridesForRepo(ctx interface{}, templateUUID interface{}, label interface{}) *MockCandlepinClient_FetchContentOverridesForRepo_Call { +func (_e *MockCandlepinClient_Expecter) FetchContentOverridesForRepo(ctx any, templateUUID any, label any) *MockCandlepinClient_FetchContentOverridesForRepo_Call { return &MockCandlepinClient_FetchContentOverridesForRepo_Call{Call: _e.mock.On("FetchContentOverridesForRepo", ctx, templateUUID, label)} } @@ -1191,7 +1191,7 @@ type MockCandlepinClient_FetchContentsByLabel_Call struct { // - ctx context.Context // - orgID string // - labels []string -func (_e *MockCandlepinClient_Expecter) FetchContentsByLabel(ctx interface{}, orgID interface{}, labels interface{}) *MockCandlepinClient_FetchContentsByLabel_Call { +func (_e *MockCandlepinClient_Expecter) FetchContentsByLabel(ctx any, orgID any, labels any) *MockCandlepinClient_FetchContentsByLabel_Call { return &MockCandlepinClient_FetchContentsByLabel_Call{Call: _e.mock.On("FetchContentsByLabel", ctx, orgID, labels)} } @@ -1264,7 +1264,7 @@ type MockCandlepinClient_FetchEnvironment_Call struct { // FetchEnvironment is a helper method to define mock.On call // - ctx context.Context // - templateUUID string -func (_e *MockCandlepinClient_Expecter) FetchEnvironment(ctx interface{}, templateUUID interface{}) *MockCandlepinClient_FetchEnvironment_Call { +func (_e *MockCandlepinClient_Expecter) FetchEnvironment(ctx any, templateUUID any) *MockCandlepinClient_FetchEnvironment_Call { return &MockCandlepinClient_FetchEnvironment_Call{Call: _e.mock.On("FetchEnvironment", ctx, templateUUID)} } @@ -1332,7 +1332,7 @@ type MockCandlepinClient_FetchPool_Call struct { // FetchPool is a helper method to define mock.On call // - ctx context.Context // - orgID string -func (_e *MockCandlepinClient_Expecter) FetchPool(ctx interface{}, orgID interface{}) *MockCandlepinClient_FetchPool_Call { +func (_e *MockCandlepinClient_Expecter) FetchPool(ctx any, orgID any) *MockCandlepinClient_FetchPool_Call { return &MockCandlepinClient_FetchPool_Call{Call: _e.mock.On("FetchPool", ctx, orgID)} } @@ -1401,7 +1401,7 @@ type MockCandlepinClient_FetchProduct_Call struct { // - ctx context.Context // - orgID string // - productID string -func (_e *MockCandlepinClient_Expecter) FetchProduct(ctx interface{}, orgID interface{}, productID interface{}) *MockCandlepinClient_FetchProduct_Call { +func (_e *MockCandlepinClient_Expecter) FetchProduct(ctx any, orgID any, productID any) *MockCandlepinClient_FetchProduct_Call { return &MockCandlepinClient_FetchProduct_Call{Call: _e.mock.On("FetchProduct", ctx, orgID, productID)} } @@ -1463,7 +1463,7 @@ type MockCandlepinClient_ImportManifest_Call struct { // ImportManifest is a helper method to define mock.On call // - ctx context.Context // - filename string -func (_e *MockCandlepinClient_Expecter) ImportManifest(ctx interface{}, filename interface{}) *MockCandlepinClient_ImportManifest_Call { +func (_e *MockCandlepinClient_Expecter) ImportManifest(ctx any, filename any) *MockCandlepinClient_ImportManifest_Call { return &MockCandlepinClient_ImportManifest_Call{Call: _e.mock.On("ImportManifest", ctx, filename)} } @@ -1539,7 +1539,7 @@ type MockCandlepinClient_ListContents_Call struct { // ListContents is a helper method to define mock.On call // - ctx context.Context // - orgID string -func (_e *MockCandlepinClient_Expecter) ListContents(ctx interface{}, orgID interface{}) *MockCandlepinClient_ListContents_Call { +func (_e *MockCandlepinClient_Expecter) ListContents(ctx any, orgID any) *MockCandlepinClient_ListContents_Call { return &MockCandlepinClient_ListContents_Call{Call: _e.mock.On("ListContents", ctx, orgID)} } @@ -1608,7 +1608,7 @@ type MockCandlepinClient_ListProducts_Call struct { // - ctx context.Context // - orgID string // - productIDs []string -func (_e *MockCandlepinClient_Expecter) ListProducts(ctx interface{}, orgID interface{}, productIDs interface{}) *MockCandlepinClient_ListProducts_Call { +func (_e *MockCandlepinClient_Expecter) ListProducts(ctx any, orgID any, productIDs any) *MockCandlepinClient_ListProducts_Call { return &MockCandlepinClient_ListProducts_Call{Call: _e.mock.On("ListProducts", ctx, orgID, productIDs)} } @@ -1671,7 +1671,7 @@ type MockCandlepinClient_PromoteContentToEnvironment_Call struct { // - ctx context.Context // - templateUUID string // - repoConfigUUIDs []string -func (_e *MockCandlepinClient_Expecter) PromoteContentToEnvironment(ctx interface{}, templateUUID interface{}, repoConfigUUIDs interface{}) *MockCandlepinClient_PromoteContentToEnvironment_Call { +func (_e *MockCandlepinClient_Expecter) PromoteContentToEnvironment(ctx any, templateUUID any, repoConfigUUIDs any) *MockCandlepinClient_PromoteContentToEnvironment_Call { return &MockCandlepinClient_PromoteContentToEnvironment_Call{Call: _e.mock.On("PromoteContentToEnvironment", ctx, templateUUID, repoConfigUUIDs)} } @@ -1734,7 +1734,7 @@ type MockCandlepinClient_RemoveContentFromProduct_Call struct { // - ctx context.Context // - orgID string // - repoConfigUUID string -func (_e *MockCandlepinClient_Expecter) RemoveContentFromProduct(ctx interface{}, orgID interface{}, repoConfigUUID interface{}) *MockCandlepinClient_RemoveContentFromProduct_Call { +func (_e *MockCandlepinClient_Expecter) RemoveContentFromProduct(ctx any, orgID any, repoConfigUUID any) *MockCandlepinClient_RemoveContentFromProduct_Call { return &MockCandlepinClient_RemoveContentFromProduct_Call{Call: _e.mock.On("RemoveContentFromProduct", ctx, orgID, repoConfigUUID)} } @@ -1797,7 +1797,7 @@ type MockCandlepinClient_RemoveContentOverrides_Call struct { // - ctx context.Context // - templateUUID string // - toRemove []caliri.ContentOverrideDTO -func (_e *MockCandlepinClient_Expecter) RemoveContentOverrides(ctx interface{}, templateUUID interface{}, toRemove interface{}) *MockCandlepinClient_RemoveContentOverrides_Call { +func (_e *MockCandlepinClient_Expecter) RemoveContentOverrides(ctx any, templateUUID any, toRemove any) *MockCandlepinClient_RemoveContentOverrides_Call { return &MockCandlepinClient_RemoveContentOverrides_Call{Call: _e.mock.On("RemoveContentOverrides", ctx, templateUUID, toRemove)} } @@ -1871,7 +1871,7 @@ type MockCandlepinClient_RenameEnvironment_Call struct { // - ctx context.Context // - templateUUID string // - name string -func (_e *MockCandlepinClient_Expecter) RenameEnvironment(ctx interface{}, templateUUID interface{}, name interface{}) *MockCandlepinClient_RenameEnvironment_Call { +func (_e *MockCandlepinClient_Expecter) RenameEnvironment(ctx any, templateUUID any, name any) *MockCandlepinClient_RenameEnvironment_Call { return &MockCandlepinClient_RenameEnvironment_Call{Call: _e.mock.On("RenameEnvironment", ctx, templateUUID, name)} } @@ -1935,7 +1935,7 @@ type MockCandlepinClient_UpdateContent_Call struct { // - orgID string // - repoConfigUUID string // - content caliri.ContentDTO -func (_e *MockCandlepinClient_Expecter) UpdateContent(ctx interface{}, orgID interface{}, repoConfigUUID interface{}, content interface{}) *MockCandlepinClient_UpdateContent_Call { +func (_e *MockCandlepinClient_Expecter) UpdateContent(ctx any, orgID any, repoConfigUUID any, content any) *MockCandlepinClient_UpdateContent_Call { return &MockCandlepinClient_UpdateContent_Call{Call: _e.mock.On("UpdateContent", ctx, orgID, repoConfigUUID, content)} } @@ -2003,7 +2003,7 @@ type MockCandlepinClient_UpdateContentOverrides_Call struct { // - ctx context.Context // - templateUUID string // - dtos []caliri.ContentOverrideDTO -func (_e *MockCandlepinClient_Expecter) UpdateContentOverrides(ctx interface{}, templateUUID interface{}, dtos interface{}) *MockCandlepinClient_UpdateContentOverrides_Call { +func (_e *MockCandlepinClient_Expecter) UpdateContentOverrides(ctx any, templateUUID any, dtos any) *MockCandlepinClient_UpdateContentOverrides_Call { return &MockCandlepinClient_UpdateContentOverrides_Call{Call: _e.mock.On("UpdateContentOverrides", ctx, templateUUID, dtos)} } diff --git a/pkg/clients/feature_service_client/feature_service_client_mock.go b/pkg/clients/feature_service_client/feature_service_client_mock.go index 5e92862e1..df34598e1 100644 --- a/pkg/clients/feature_service_client/feature_service_client_mock.go +++ b/pkg/clients/feature_service_client/feature_service_client_mock.go @@ -74,7 +74,7 @@ type MockFeatureServiceClient_GetEntitledFeatures_Call struct { // GetEntitledFeatures is a helper method to define mock.On call // - ctx context.Context // - orgID string -func (_e *MockFeatureServiceClient_Expecter) GetEntitledFeatures(ctx interface{}, orgID interface{}) *MockFeatureServiceClient_GetEntitledFeatures_Call { +func (_e *MockFeatureServiceClient_Expecter) GetEntitledFeatures(ctx any, orgID any) *MockFeatureServiceClient_GetEntitledFeatures_Call { return &MockFeatureServiceClient_GetEntitledFeatures_Call{Call: _e.mock.On("GetEntitledFeatures", ctx, orgID)} } @@ -146,7 +146,7 @@ type MockFeatureServiceClient_GetFeatureStatusByOrgID_Call struct { // GetFeatureStatusByOrgID is a helper method to define mock.On call // - ctx context.Context // - orgID string -func (_e *MockFeatureServiceClient_Expecter) GetFeatureStatusByOrgID(ctx interface{}, orgID interface{}) *MockFeatureServiceClient_GetFeatureStatusByOrgID_Call { +func (_e *MockFeatureServiceClient_Expecter) GetFeatureStatusByOrgID(ctx any, orgID any) *MockFeatureServiceClient_GetFeatureStatusByOrgID_Call { return &MockFeatureServiceClient_GetFeatureStatusByOrgID_Call{Call: _e.mock.On("GetFeatureStatusByOrgID", ctx, orgID)} } @@ -217,7 +217,7 @@ type MockFeatureServiceClient_ListFeatures_Call struct { // ListFeatures is a helper method to define mock.On call // - ctx context.Context -func (_e *MockFeatureServiceClient_Expecter) ListFeatures(ctx interface{}) *MockFeatureServiceClient_ListFeatures_Call { +func (_e *MockFeatureServiceClient_Expecter) ListFeatures(ctx any) *MockFeatureServiceClient_ListFeatures_Call { return &MockFeatureServiceClient_ListFeatures_Call{Call: _e.mock.On("ListFeatures", ctx)} } diff --git a/pkg/clients/pulp_client/pulp_client_mock.go b/pkg/clients/pulp_client/pulp_client_mock.go index cc118035d..cd7320fb8 100644 --- a/pkg/clients/pulp_client/pulp_client_mock.go +++ b/pkg/clients/pulp_client/pulp_client_mock.go @@ -73,7 +73,7 @@ type MockPulpGlobalClient_CancelTask_Call struct { // CancelTask is a helper method to define mock.On call // - ctx context.Context // - taskHref string -func (_e *MockPulpGlobalClient_Expecter) CancelTask(ctx interface{}, taskHref interface{}) *MockPulpGlobalClient_CancelTask_Call { +func (_e *MockPulpGlobalClient_Expecter) CancelTask(ctx any, taskHref any) *MockPulpGlobalClient_CancelTask_Call { return &MockPulpGlobalClient_CancelTask_Call{Call: _e.mock.On("CancelTask", ctx, taskHref)} } @@ -192,7 +192,7 @@ type MockPulpGlobalClient_GetTask_Call struct { // GetTask is a helper method to define mock.On call // - ctx context.Context // - taskHref string -func (_e *MockPulpGlobalClient_Expecter) GetTask(ctx interface{}, taskHref interface{}) *MockPulpGlobalClient_GetTask_Call { +func (_e *MockPulpGlobalClient_Expecter) GetTask(ctx any, taskHref any) *MockPulpGlobalClient_GetTask_Call { return &MockPulpGlobalClient_GetTask_Call{Call: _e.mock.On("GetTask", ctx, taskHref)} } @@ -248,7 +248,7 @@ type MockPulpGlobalClient_Livez_Call struct { // Livez is a helper method to define mock.On call // - ctx context.Context -func (_e *MockPulpGlobalClient_Expecter) Livez(ctx interface{}) *MockPulpGlobalClient_Livez_Call { +func (_e *MockPulpGlobalClient_Expecter) Livez(ctx any) *MockPulpGlobalClient_Livez_Call { return &MockPulpGlobalClient_Livez_Call{Call: _e.mock.On("Livez", ctx)} } @@ -309,7 +309,7 @@ type MockPulpGlobalClient_LookupDomain_Call struct { // LookupDomain is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpGlobalClient_Expecter) LookupDomain(ctx interface{}, name interface{}) *MockPulpGlobalClient_LookupDomain_Call { +func (_e *MockPulpGlobalClient_Expecter) LookupDomain(ctx any, name any) *MockPulpGlobalClient_LookupDomain_Call { return &MockPulpGlobalClient_LookupDomain_Call{Call: _e.mock.On("LookupDomain", ctx, name)} } @@ -375,7 +375,7 @@ type MockPulpGlobalClient_LookupOrCreateDomain_Call struct { // LookupOrCreateDomain is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpGlobalClient_Expecter) LookupOrCreateDomain(ctx interface{}, name interface{}) *MockPulpGlobalClient_LookupOrCreateDomain_Call { +func (_e *MockPulpGlobalClient_Expecter) LookupOrCreateDomain(ctx any, name any) *MockPulpGlobalClient_LookupOrCreateDomain_Call { return &MockPulpGlobalClient_LookupOrCreateDomain_Call{Call: _e.mock.On("LookupOrCreateDomain", ctx, name)} } @@ -443,7 +443,7 @@ type MockPulpGlobalClient_PollTask_Call struct { // PollTask is a helper method to define mock.On call // - ctx context.Context // - taskHref string -func (_e *MockPulpGlobalClient_Expecter) PollTask(ctx interface{}, taskHref interface{}) *MockPulpGlobalClient_PollTask_Call { +func (_e *MockPulpGlobalClient_Expecter) PollTask(ctx any, taskHref any) *MockPulpGlobalClient_PollTask_Call { return &MockPulpGlobalClient_PollTask_Call{Call: _e.mock.On("PollTask", ctx, taskHref)} } @@ -502,7 +502,7 @@ type MockPulpGlobalClient_SetDomainLabel_Call struct { // - pulpHref string // - key string // - value string -func (_e *MockPulpGlobalClient_Expecter) SetDomainLabel(ctx interface{}, pulpHref interface{}, key interface{}, value interface{}) *MockPulpGlobalClient_SetDomainLabel_Call { +func (_e *MockPulpGlobalClient_Expecter) SetDomainLabel(ctx any, pulpHref any, key any, value any) *MockPulpGlobalClient_SetDomainLabel_Call { return &MockPulpGlobalClient_SetDomainLabel_Call{Call: _e.mock.On("SetDomainLabel", ctx, pulpHref, key, value)} } @@ -569,7 +569,7 @@ type MockPulpGlobalClient_UpdateDomainIfNeeded_Call struct { // UpdateDomainIfNeeded is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpGlobalClient_Expecter) UpdateDomainIfNeeded(ctx interface{}, name interface{}) *MockPulpGlobalClient_UpdateDomainIfNeeded_Call { +func (_e *MockPulpGlobalClient_Expecter) UpdateDomainIfNeeded(ctx any, name any) *MockPulpGlobalClient_UpdateDomainIfNeeded_Call { return &MockPulpGlobalClient_UpdateDomainIfNeeded_Call{Call: _e.mock.On("UpdateDomainIfNeeded", ctx, name)} } @@ -662,7 +662,7 @@ type MockPulpClient_CancelTask_Call struct { // CancelTask is a helper method to define mock.On call // - ctx context.Context // - taskHref string -func (_e *MockPulpClient_Expecter) CancelTask(ctx interface{}, taskHref interface{}) *MockPulpClient_CancelTask_Call { +func (_e *MockPulpClient_Expecter) CancelTask(ctx any, taskHref any) *MockPulpClient_CancelTask_Call { return &MockPulpClient_CancelTask_Call{Call: _e.mock.On("CancelTask", ctx, taskHref)} } @@ -728,7 +728,7 @@ type MockPulpClient_CreateOrUpdateFeatureGuard_Call struct { // CreateOrUpdateFeatureGuard is a helper method to define mock.On call // - ctx context.Context // - featureName string -func (_e *MockPulpClient_Expecter) CreateOrUpdateFeatureGuard(ctx interface{}, featureName interface{}) *MockPulpClient_CreateOrUpdateFeatureGuard_Call { +func (_e *MockPulpClient_Expecter) CreateOrUpdateFeatureGuard(ctx any, featureName any) *MockPulpClient_CreateOrUpdateFeatureGuard_Call { return &MockPulpClient_CreateOrUpdateFeatureGuard_Call{Call: _e.mock.On("CreateOrUpdateFeatureGuard", ctx, featureName)} } @@ -794,7 +794,7 @@ type MockPulpClient_CreateOrUpdateGuardsForOrg_Call struct { // CreateOrUpdateGuardsForOrg is a helper method to define mock.On call // - ctx context.Context // - orgId string -func (_e *MockPulpClient_Expecter) CreateOrUpdateGuardsForOrg(ctx interface{}, orgId interface{}) *MockPulpClient_CreateOrUpdateGuardsForOrg_Call { +func (_e *MockPulpClient_Expecter) CreateOrUpdateGuardsForOrg(ctx any, orgId any) *MockPulpClient_CreateOrUpdateGuardsForOrg_Call { return &MockPulpClient_CreateOrUpdateGuardsForOrg_Call{Call: _e.mock.On("CreateOrUpdateGuardsForOrg", ctx, orgId)} } @@ -860,7 +860,7 @@ type MockPulpClient_CreateOrUpdateGuardsForRhelRepo_Call struct { // CreateOrUpdateGuardsForRhelRepo is a helper method to define mock.On call // - ctx context.Context // - featureName string -func (_e *MockPulpClient_Expecter) CreateOrUpdateGuardsForRhelRepo(ctx interface{}, featureName interface{}) *MockPulpClient_CreateOrUpdateGuardsForRhelRepo_Call { +func (_e *MockPulpClient_Expecter) CreateOrUpdateGuardsForRhelRepo(ctx any, featureName any) *MockPulpClient_CreateOrUpdateGuardsForRhelRepo_Call { return &MockPulpClient_CreateOrUpdateGuardsForRhelRepo_Call{Call: _e.mock.On("CreateOrUpdateGuardsForRhelRepo", ctx, featureName)} } @@ -927,7 +927,7 @@ type MockPulpClient_CreatePackage_Call struct { // - ctx context.Context // - artifactHref *string // - uploadHref *string -func (_e *MockPulpClient_Expecter) CreatePackage(ctx interface{}, artifactHref interface{}, uploadHref interface{}) *MockPulpClient_CreatePackage_Call { +func (_e *MockPulpClient_Expecter) CreatePackage(ctx any, artifactHref any, uploadHref any) *MockPulpClient_CreatePackage_Call { return &MockPulpClient_CreatePackage_Call{Call: _e.mock.On("CreatePackage", ctx, artifactHref, uploadHref)} } @@ -1003,7 +1003,7 @@ type MockPulpClient_CreateRpmDistribution_Call struct { // - name string // - basePath string // - contentGuardHref *string -func (_e *MockPulpClient_Expecter) CreateRpmDistribution(ctx interface{}, publicationHref interface{}, name interface{}, basePath interface{}, contentGuardHref interface{}) *MockPulpClient_CreateRpmDistribution_Call { +func (_e *MockPulpClient_Expecter) CreateRpmDistribution(ctx any, publicationHref any, name any, basePath any, contentGuardHref any) *MockPulpClient_CreateRpmDistribution_Call { return &MockPulpClient_CreateRpmDistribution_Call{Call: _e.mock.On("CreateRpmDistribution", ctx, publicationHref, name, basePath, contentGuardHref)} } @@ -1086,7 +1086,7 @@ type MockPulpClient_CreateRpmPublication_Call struct { // CreateRpmPublication is a helper method to define mock.On call // - ctx context.Context // - versionHref string -func (_e *MockPulpClient_Expecter) CreateRpmPublication(ctx interface{}, versionHref interface{}) *MockPulpClient_CreateRpmPublication_Call { +func (_e *MockPulpClient_Expecter) CreateRpmPublication(ctx any, versionHref any) *MockPulpClient_CreateRpmPublication_Call { return &MockPulpClient_CreateRpmPublication_Call{Call: _e.mock.On("CreateRpmPublication", ctx, versionHref)} } @@ -1158,7 +1158,7 @@ type MockPulpClient_CreateRpmRemote_Call struct { // - clientCert *string // - clientKey *string // - caCert *string -func (_e *MockPulpClient_Expecter) CreateRpmRemote(ctx interface{}, name interface{}, url interface{}, clientCert interface{}, clientKey interface{}, caCert interface{}) *MockPulpClient_CreateRpmRemote_Call { +func (_e *MockPulpClient_Expecter) CreateRpmRemote(ctx any, name any, url any, clientCert any, clientKey any, caCert any) *MockPulpClient_CreateRpmRemote_Call { return &MockPulpClient_CreateRpmRemote_Call{Call: _e.mock.On("CreateRpmRemote", ctx, name, url, clientCert, clientKey, caCert)} } @@ -1247,7 +1247,7 @@ type MockPulpClient_CreateRpmRepository_Call struct { // - ctx context.Context // - uuid string // - rpmRemotePulpRef *string -func (_e *MockPulpClient_Expecter) CreateRpmRepository(ctx interface{}, uuid interface{}, rpmRemotePulpRef interface{}) *MockPulpClient_CreateRpmRepository_Call { +func (_e *MockPulpClient_Expecter) CreateRpmRepository(ctx any, uuid any, rpmRemotePulpRef any) *MockPulpClient_CreateRpmRepository_Call { return &MockPulpClient_CreateRpmRepository_Call{Call: _e.mock.On("CreateRpmRepository", ctx, uuid, rpmRemotePulpRef)} } @@ -1326,7 +1326,7 @@ type MockPulpClient_CreateUpload_Call struct { // CreateUpload is a helper method to define mock.On call // - ctx context.Context // - size int64 -func (_e *MockPulpClient_Expecter) CreateUpload(ctx interface{}, size interface{}) *MockPulpClient_CreateUpload_Call { +func (_e *MockPulpClient_Expecter) CreateUpload(ctx any, size any) *MockPulpClient_CreateUpload_Call { return &MockPulpClient_CreateUpload_Call{Call: _e.mock.On("CreateUpload", ctx, size)} } @@ -1394,7 +1394,7 @@ type MockPulpClient_DeleteRpmDistribution_Call struct { // DeleteRpmDistribution is a helper method to define mock.On call // - ctx context.Context // - rpmDistributionHref string -func (_e *MockPulpClient_Expecter) DeleteRpmDistribution(ctx interface{}, rpmDistributionHref interface{}) *MockPulpClient_DeleteRpmDistribution_Call { +func (_e *MockPulpClient_Expecter) DeleteRpmDistribution(ctx any, rpmDistributionHref any) *MockPulpClient_DeleteRpmDistribution_Call { return &MockPulpClient_DeleteRpmDistribution_Call{Call: _e.mock.On("DeleteRpmDistribution", ctx, rpmDistributionHref)} } @@ -1460,7 +1460,7 @@ type MockPulpClient_DeleteRpmRemote_Call struct { // DeleteRpmRemote is a helper method to define mock.On call // - ctx context.Context // - pulpHref string -func (_e *MockPulpClient_Expecter) DeleteRpmRemote(ctx interface{}, pulpHref interface{}) *MockPulpClient_DeleteRpmRemote_Call { +func (_e *MockPulpClient_Expecter) DeleteRpmRemote(ctx any, pulpHref any) *MockPulpClient_DeleteRpmRemote_Call { return &MockPulpClient_DeleteRpmRemote_Call{Call: _e.mock.On("DeleteRpmRemote", ctx, pulpHref)} } @@ -1526,7 +1526,7 @@ type MockPulpClient_DeleteRpmRepository_Call struct { // DeleteRpmRepository is a helper method to define mock.On call // - ctx context.Context // - rpmRepositoryHref string -func (_e *MockPulpClient_Expecter) DeleteRpmRepository(ctx interface{}, rpmRepositoryHref interface{}) *MockPulpClient_DeleteRpmRepository_Call { +func (_e *MockPulpClient_Expecter) DeleteRpmRepository(ctx any, rpmRepositoryHref any) *MockPulpClient_DeleteRpmRepository_Call { return &MockPulpClient_DeleteRpmRepository_Call{Call: _e.mock.On("DeleteRpmRepository", ctx, rpmRepositoryHref)} } @@ -1594,7 +1594,7 @@ type MockPulpClient_DeleteRpmRepositoryVersion_Call struct { // DeleteRpmRepositoryVersion is a helper method to define mock.On call // - ctx context.Context // - href string -func (_e *MockPulpClient_Expecter) DeleteRpmRepositoryVersion(ctx interface{}, href interface{}) *MockPulpClient_DeleteRpmRepositoryVersion_Call { +func (_e *MockPulpClient_Expecter) DeleteRpmRepositoryVersion(ctx any, href any) *MockPulpClient_DeleteRpmRepositoryVersion_Call { return &MockPulpClient_DeleteRpmRepositoryVersion_Call{Call: _e.mock.On("DeleteRpmRepositoryVersion", ctx, href)} } @@ -1660,7 +1660,7 @@ type MockPulpClient_DeleteUpload_Call struct { // DeleteUpload is a helper method to define mock.On call // - ctx context.Context // - uploadHref string -func (_e *MockPulpClient_Expecter) DeleteUpload(ctx interface{}, uploadHref interface{}) *MockPulpClient_DeleteUpload_Call { +func (_e *MockPulpClient_Expecter) DeleteUpload(ctx any, uploadHref any) *MockPulpClient_DeleteUpload_Call { return &MockPulpClient_DeleteUpload_Call{Call: _e.mock.On("DeleteUpload", ctx, uploadHref)} } @@ -1728,7 +1728,7 @@ type MockPulpClient_FindDistributionByPath_Call struct { // FindDistributionByPath is a helper method to define mock.On call // - ctx context.Context // - path string -func (_e *MockPulpClient_Expecter) FindDistributionByPath(ctx interface{}, path interface{}) *MockPulpClient_FindDistributionByPath_Call { +func (_e *MockPulpClient_Expecter) FindDistributionByPath(ctx any, path any) *MockPulpClient_FindDistributionByPath_Call { return &MockPulpClient_FindDistributionByPath_Call{Call: _e.mock.On("FindDistributionByPath", ctx, path)} } @@ -1796,7 +1796,7 @@ type MockPulpClient_FindGenericDistributionByBasePath_Call struct { // FindGenericDistributionByBasePath is a helper method to define mock.On call // - ctx context.Context // - basePath string -func (_e *MockPulpClient_Expecter) FindGenericDistributionByBasePath(ctx interface{}, basePath interface{}) *MockPulpClient_FindGenericDistributionByBasePath_Call { +func (_e *MockPulpClient_Expecter) FindGenericDistributionByBasePath(ctx any, basePath any) *MockPulpClient_FindGenericDistributionByBasePath_Call { return &MockPulpClient_FindGenericDistributionByBasePath_Call{Call: _e.mock.On("FindGenericDistributionByBasePath", ctx, basePath)} } @@ -1864,7 +1864,7 @@ type MockPulpClient_FindGenericRepositoryByName_Call struct { // FindGenericRepositoryByName is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpClient_Expecter) FindGenericRepositoryByName(ctx interface{}, name interface{}) *MockPulpClient_FindGenericRepositoryByName_Call { +func (_e *MockPulpClient_Expecter) FindGenericRepositoryByName(ctx any, name any) *MockPulpClient_FindGenericRepositoryByName_Call { return &MockPulpClient_FindGenericRepositoryByName_Call{Call: _e.mock.On("FindGenericRepositoryByName", ctx, name)} } @@ -1932,7 +1932,7 @@ type MockPulpClient_FindRpmPublicationByVersion_Call struct { // FindRpmPublicationByVersion is a helper method to define mock.On call // - ctx context.Context // - versionHref string -func (_e *MockPulpClient_Expecter) FindRpmPublicationByVersion(ctx interface{}, versionHref interface{}) *MockPulpClient_FindRpmPublicationByVersion_Call { +func (_e *MockPulpClient_Expecter) FindRpmPublicationByVersion(ctx any, versionHref any) *MockPulpClient_FindRpmPublicationByVersion_Call { return &MockPulpClient_FindRpmPublicationByVersion_Call{Call: _e.mock.On("FindRpmPublicationByVersion", ctx, versionHref)} } @@ -2007,7 +2007,7 @@ type MockPulpClient_FinishUpload_Call struct { // - ctx context.Context // - uploadHref string // - sha256 string -func (_e *MockPulpClient_Expecter) FinishUpload(ctx interface{}, uploadHref interface{}, sha256 interface{}) *MockPulpClient_FinishUpload_Call { +func (_e *MockPulpClient_Expecter) FinishUpload(ctx any, uploadHref any, sha256 any) *MockPulpClient_FinishUpload_Call { return &MockPulpClient_FinishUpload_Call{Call: _e.mock.On("FinishUpload", ctx, uploadHref, sha256)} } @@ -2177,7 +2177,7 @@ type MockPulpClient_GetRpmRemoteByName_Call struct { // GetRpmRemoteByName is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpClient_Expecter) GetRpmRemoteByName(ctx interface{}, name interface{}) *MockPulpClient_GetRpmRemoteByName_Call { +func (_e *MockPulpClient_Expecter) GetRpmRemoteByName(ctx any, name any) *MockPulpClient_GetRpmRemoteByName_Call { return &MockPulpClient_GetRpmRemoteByName_Call{Call: _e.mock.On("GetRpmRemoteByName", ctx, name)} } @@ -2244,7 +2244,7 @@ type MockPulpClient_GetRpmRemoteList_Call struct { // GetRpmRemoteList is a helper method to define mock.On call // - ctx context.Context -func (_e *MockPulpClient_Expecter) GetRpmRemoteList(ctx interface{}) *MockPulpClient_GetRpmRemoteList_Call { +func (_e *MockPulpClient_Expecter) GetRpmRemoteList(ctx any) *MockPulpClient_GetRpmRemoteList_Call { return &MockPulpClient_GetRpmRemoteList_Call{Call: _e.mock.On("GetRpmRemoteList", ctx)} } @@ -2307,7 +2307,7 @@ type MockPulpClient_GetRpmRepositoryByName_Call struct { // GetRpmRepositoryByName is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpClient_Expecter) GetRpmRepositoryByName(ctx interface{}, name interface{}) *MockPulpClient_GetRpmRepositoryByName_Call { +func (_e *MockPulpClient_Expecter) GetRpmRepositoryByName(ctx any, name any) *MockPulpClient_GetRpmRepositoryByName_Call { return &MockPulpClient_GetRpmRepositoryByName_Call{Call: _e.mock.On("GetRpmRepositoryByName", ctx, name)} } @@ -2375,7 +2375,7 @@ type MockPulpClient_GetRpmRepositoryByRemote_Call struct { // GetRpmRepositoryByRemote is a helper method to define mock.On call // - ctx context.Context // - pulpHref string -func (_e *MockPulpClient_Expecter) GetRpmRepositoryByRemote(ctx interface{}, pulpHref interface{}) *MockPulpClient_GetRpmRepositoryByRemote_Call { +func (_e *MockPulpClient_Expecter) GetRpmRepositoryByRemote(ctx any, pulpHref any) *MockPulpClient_GetRpmRepositoryByRemote_Call { return &MockPulpClient_GetRpmRepositoryByRemote_Call{Call: _e.mock.On("GetRpmRepositoryByRemote", ctx, pulpHref)} } @@ -2443,7 +2443,7 @@ type MockPulpClient_GetRpmRepositoryVersion_Call struct { // GetRpmRepositoryVersion is a helper method to define mock.On call // - ctx context.Context // - href string -func (_e *MockPulpClient_Expecter) GetRpmRepositoryVersion(ctx interface{}, href interface{}) *MockPulpClient_GetRpmRepositoryVersion_Call { +func (_e *MockPulpClient_Expecter) GetRpmRepositoryVersion(ctx any, href any) *MockPulpClient_GetRpmRepositoryVersion_Call { return &MockPulpClient_GetRpmRepositoryVersion_Call{Call: _e.mock.On("GetRpmRepositoryVersion", ctx, href)} } @@ -2509,7 +2509,7 @@ type MockPulpClient_GetTask_Call struct { // GetTask is a helper method to define mock.On call // - ctx context.Context // - taskHref string -func (_e *MockPulpClient_Expecter) GetTask(ctx interface{}, taskHref interface{}) *MockPulpClient_GetTask_Call { +func (_e *MockPulpClient_Expecter) GetTask(ctx any, taskHref any) *MockPulpClient_GetTask_Call { return &MockPulpClient_GetTask_Call{Call: _e.mock.On("GetTask", ctx, taskHref)} } @@ -2576,7 +2576,7 @@ type MockPulpClient_ListDistributions_Call struct { // ListDistributions is a helper method to define mock.On call // - ctx context.Context -func (_e *MockPulpClient_Expecter) ListDistributions(ctx interface{}) *MockPulpClient_ListDistributions_Call { +func (_e *MockPulpClient_Expecter) ListDistributions(ctx any) *MockPulpClient_ListDistributions_Call { return &MockPulpClient_ListDistributions_Call{Call: _e.mock.On("ListDistributions", ctx)} } @@ -2639,7 +2639,7 @@ type MockPulpClient_ListVersionAllPackages_Call struct { // ListVersionAllPackages is a helper method to define mock.On call // - ctx context.Context // - versionHref string -func (_e *MockPulpClient_Expecter) ListVersionAllPackages(ctx interface{}, versionHref interface{}) *MockPulpClient_ListVersionAllPackages_Call { +func (_e *MockPulpClient_Expecter) ListVersionAllPackages(ctx any, versionHref any) *MockPulpClient_ListVersionAllPackages_Call { return &MockPulpClient_ListVersionAllPackages_Call{Call: _e.mock.On("ListVersionAllPackages", ctx, versionHref)} } @@ -2695,7 +2695,7 @@ type MockPulpClient_Livez_Call struct { // Livez is a helper method to define mock.On call // - ctx context.Context -func (_e *MockPulpClient_Expecter) Livez(ctx interface{}) *MockPulpClient_Livez_Call { +func (_e *MockPulpClient_Expecter) Livez(ctx any) *MockPulpClient_Livez_Call { return &MockPulpClient_Livez_Call{Call: _e.mock.On("Livez", ctx)} } @@ -2758,7 +2758,7 @@ type MockPulpClient_LookupArtifact_Call struct { // LookupArtifact is a helper method to define mock.On call // - ctx context.Context // - sha256sum string -func (_e *MockPulpClient_Expecter) LookupArtifact(ctx interface{}, sha256sum interface{}) *MockPulpClient_LookupArtifact_Call { +func (_e *MockPulpClient_Expecter) LookupArtifact(ctx any, sha256sum any) *MockPulpClient_LookupArtifact_Call { return &MockPulpClient_LookupArtifact_Call{Call: _e.mock.On("LookupArtifact", ctx, sha256sum)} } @@ -2824,7 +2824,7 @@ type MockPulpClient_LookupDomain_Call struct { // LookupDomain is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpClient_Expecter) LookupDomain(ctx interface{}, name interface{}) *MockPulpClient_LookupDomain_Call { +func (_e *MockPulpClient_Expecter) LookupDomain(ctx any, name any) *MockPulpClient_LookupDomain_Call { return &MockPulpClient_LookupDomain_Call{Call: _e.mock.On("LookupDomain", ctx, name)} } @@ -2890,7 +2890,7 @@ type MockPulpClient_LookupOrCreateDomain_Call struct { // LookupOrCreateDomain is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpClient_Expecter) LookupOrCreateDomain(ctx interface{}, name interface{}) *MockPulpClient_LookupOrCreateDomain_Call { +func (_e *MockPulpClient_Expecter) LookupOrCreateDomain(ctx any, name any) *MockPulpClient_LookupOrCreateDomain_Call { return &MockPulpClient_LookupOrCreateDomain_Call{Call: _e.mock.On("LookupOrCreateDomain", ctx, name)} } @@ -2958,7 +2958,7 @@ type MockPulpClient_LookupPackage_Call struct { // LookupPackage is a helper method to define mock.On call // - ctx context.Context // - sha256sum string -func (_e *MockPulpClient_Expecter) LookupPackage(ctx interface{}, sha256sum interface{}) *MockPulpClient_LookupPackage_Call { +func (_e *MockPulpClient_Expecter) LookupPackage(ctx any, sha256sum any) *MockPulpClient_LookupPackage_Call { return &MockPulpClient_LookupPackage_Call{Call: _e.mock.On("LookupPackage", ctx, sha256sum)} } @@ -3026,7 +3026,7 @@ type MockPulpClient_ModifyRpmRepositoryContent_Call struct { // - repoHref string // - contentHrefsToAdd []string // - contentHrefsToRemove []string -func (_e *MockPulpClient_Expecter) ModifyRpmRepositoryContent(ctx interface{}, repoHref interface{}, contentHrefsToAdd interface{}, contentHrefsToRemove interface{}) *MockPulpClient_ModifyRpmRepositoryContent_Call { +func (_e *MockPulpClient_Expecter) ModifyRpmRepositoryContent(ctx any, repoHref any, contentHrefsToAdd any, contentHrefsToRemove any) *MockPulpClient_ModifyRpmRepositoryContent_Call { return &MockPulpClient_ModifyRpmRepositoryContent_Call{Call: _e.mock.On("ModifyRpmRepositoryContent", ctx, repoHref, contentHrefsToAdd, contentHrefsToRemove)} } @@ -3101,7 +3101,7 @@ type MockPulpClient_OrphanCleanup_Call struct { // OrphanCleanup is a helper method to define mock.On call // - ctx context.Context -func (_e *MockPulpClient_Expecter) OrphanCleanup(ctx interface{}) *MockPulpClient_OrphanCleanup_Call { +func (_e *MockPulpClient_Expecter) OrphanCleanup(ctx any) *MockPulpClient_OrphanCleanup_Call { return &MockPulpClient_OrphanCleanup_Call{Call: _e.mock.On("OrphanCleanup", ctx)} } @@ -3164,7 +3164,7 @@ type MockPulpClient_PollTask_Call struct { // PollTask is a helper method to define mock.On call // - ctx context.Context // - taskHref string -func (_e *MockPulpClient_Expecter) PollTask(ctx interface{}, taskHref interface{}) *MockPulpClient_PollTask_Call { +func (_e *MockPulpClient_Expecter) PollTask(ctx any, taskHref any) *MockPulpClient_PollTask_Call { return &MockPulpClient_PollTask_Call{Call: _e.mock.On("PollTask", ctx, taskHref)} } @@ -3230,7 +3230,7 @@ type MockPulpClient_RepairRpmRepositoryVersion_Call struct { // RepairRpmRepositoryVersion is a helper method to define mock.On call // - ctx context.Context // - href string -func (_e *MockPulpClient_Expecter) RepairRpmRepositoryVersion(ctx interface{}, href interface{}) *MockPulpClient_RepairRpmRepositoryVersion_Call { +func (_e *MockPulpClient_Expecter) RepairRpmRepositoryVersion(ctx any, href any) *MockPulpClient_RepairRpmRepositoryVersion_Call { return &MockPulpClient_RepairRpmRepositoryVersion_Call{Call: _e.mock.On("RepairRpmRepositoryVersion", ctx, href)} } @@ -3298,7 +3298,7 @@ type MockPulpClient_ResolveRepositoryFromBasePath_Call struct { // ResolveRepositoryFromBasePath is a helper method to define mock.On call // - ctx context.Context // - basePath string -func (_e *MockPulpClient_Expecter) ResolveRepositoryFromBasePath(ctx interface{}, basePath interface{}) *MockPulpClient_ResolveRepositoryFromBasePath_Call { +func (_e *MockPulpClient_Expecter) ResolveRepositoryFromBasePath(ctx any, basePath any) *MockPulpClient_ResolveRepositoryFromBasePath_Call { return &MockPulpClient_ResolveRepositoryFromBasePath_Call{Call: _e.mock.On("ResolveRepositoryFromBasePath", ctx, basePath)} } @@ -3357,7 +3357,7 @@ type MockPulpClient_SetDomainLabel_Call struct { // - pulpHref string // - key string // - value string -func (_e *MockPulpClient_Expecter) SetDomainLabel(ctx interface{}, pulpHref interface{}, key interface{}, value interface{}) *MockPulpClient_SetDomainLabel_Call { +func (_e *MockPulpClient_Expecter) SetDomainLabel(ctx any, pulpHref any, key any, value any) *MockPulpClient_SetDomainLabel_Call { return &MockPulpClient_SetDomainLabel_Call{Call: _e.mock.On("SetDomainLabel", ctx, pulpHref, key, value)} } @@ -3434,7 +3434,7 @@ type MockPulpClient_Status_Call struct { // Status is a helper method to define mock.On call // - ctx context.Context -func (_e *MockPulpClient_Expecter) Status(ctx interface{}) *MockPulpClient_Status_Call { +func (_e *MockPulpClient_Expecter) Status(ctx any) *MockPulpClient_Status_Call { return &MockPulpClient_Status_Call{Call: _e.mock.On("Status", ctx)} } @@ -3496,7 +3496,7 @@ type MockPulpClient_SyncRpmRepository_Call struct { // - ctx context.Context // - rpmRpmRepositoryHref string // - remoteHref *string -func (_e *MockPulpClient_Expecter) SyncRpmRepository(ctx interface{}, rpmRpmRepositoryHref interface{}, remoteHref interface{}) *MockPulpClient_SyncRpmRepository_Call { +func (_e *MockPulpClient_Expecter) SyncRpmRepository(ctx any, rpmRpmRepositoryHref any, remoteHref any) *MockPulpClient_SyncRpmRepository_Call { return &MockPulpClient_SyncRpmRepository_Call{Call: _e.mock.On("SyncRpmRepository", ctx, rpmRpmRepositoryHref, remoteHref)} } @@ -3558,7 +3558,7 @@ type MockPulpClient_UpdateDomainIfNeeded_Call struct { // UpdateDomainIfNeeded is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockPulpClient_Expecter) UpdateDomainIfNeeded(ctx interface{}, name interface{}) *MockPulpClient_UpdateDomainIfNeeded_Call { +func (_e *MockPulpClient_Expecter) UpdateDomainIfNeeded(ctx any, name any) *MockPulpClient_UpdateDomainIfNeeded_Call { return &MockPulpClient_UpdateDomainIfNeeded_Call{Call: _e.mock.On("UpdateDomainIfNeeded", ctx, name)} } @@ -3628,7 +3628,7 @@ type MockPulpClient_UpdateRpmDistribution_Call struct { // - distributionName string // - basePath string // - contentGuardHref *string -func (_e *MockPulpClient_Expecter) UpdateRpmDistribution(ctx interface{}, rpmDistributionHref interface{}, rpmPublicationHref interface{}, distributionName interface{}, basePath interface{}, contentGuardHref interface{}) *MockPulpClient_UpdateRpmDistribution_Call { +func (_e *MockPulpClient_Expecter) UpdateRpmDistribution(ctx any, rpmDistributionHref any, rpmPublicationHref any, distributionName any, basePath any, contentGuardHref any) *MockPulpClient_UpdateRpmDistribution_Call { return &MockPulpClient_UpdateRpmDistribution_Call{Call: _e.mock.On("UpdateRpmDistribution", ctx, rpmDistributionHref, rpmPublicationHref, distributionName, basePath, contentGuardHref)} } @@ -3718,7 +3718,7 @@ type MockPulpClient_UpdateRpmRemote_Call struct { // - clientCert *string // - clientKey *string // - caCert *string -func (_e *MockPulpClient_Expecter) UpdateRpmRemote(ctx interface{}, pulpHref interface{}, url interface{}, clientCert interface{}, clientKey interface{}, caCert interface{}) *MockPulpClient_UpdateRpmRemote_Call { +func (_e *MockPulpClient_Expecter) UpdateRpmRemote(ctx any, pulpHref any, url any, clientCert any, clientKey any, caCert any) *MockPulpClient_UpdateRpmRemote_Call { return &MockPulpClient_UpdateRpmRemote_Call{Call: _e.mock.On("UpdateRpmRemote", ctx, pulpHref, url, clientCert, clientKey, caCert)} } @@ -3815,7 +3815,7 @@ type MockPulpClient_UploadChunk_Call struct { // - contentRange string // - file *os.File // - sha256 string -func (_e *MockPulpClient_Expecter) UploadChunk(ctx interface{}, uploadHref interface{}, contentRange interface{}, file interface{}, sha256 interface{}) *MockPulpClient_UploadChunk_Call { +func (_e *MockPulpClient_Expecter) UploadChunk(ctx any, uploadHref any, contentRange any, file any, sha256 any) *MockPulpClient_UploadChunk_Call { return &MockPulpClient_UploadChunk_Call{Call: _e.mock.On("UploadChunk", ctx, uploadHref, contentRange, file, sha256)} } @@ -3888,7 +3888,7 @@ type MockPulpClient_WithDomain_Call struct { // WithDomain is a helper method to define mock.On call // - domainName string -func (_e *MockPulpClient_Expecter) WithDomain(domainName interface{}) *MockPulpClient_WithDomain_Call { +func (_e *MockPulpClient_Expecter) WithDomain(domainName any) *MockPulpClient_WithDomain_Call { return &MockPulpClient_WithDomain_Call{Call: _e.mock.On("WithDomain", domainName)} } diff --git a/pkg/clients/roadmap_client/roadmap_client_mock.go b/pkg/clients/roadmap_client/roadmap_client_mock.go index 6c981c050..893259a69 100644 --- a/pkg/clients/roadmap_client/roadmap_client_mock.go +++ b/pkg/clients/roadmap_client/roadmap_client_mock.go @@ -76,7 +76,7 @@ type MockRoadmapClient_GetAppstreams_Call struct { // GetAppstreams is a helper method to define mock.On call // - ctx context.Context -func (_e *MockRoadmapClient_Expecter) GetAppstreams(ctx interface{}) *MockRoadmapClient_GetAppstreams_Call { +func (_e *MockRoadmapClient_Expecter) GetAppstreams(ctx any) *MockRoadmapClient_GetAppstreams_Call { return &MockRoadmapClient_GetAppstreams_Call{Call: _e.mock.On("GetAppstreams", ctx)} } @@ -142,7 +142,7 @@ type MockRoadmapClient_GetRhelLifecycle_Call struct { // GetRhelLifecycle is a helper method to define mock.On call // - ctx context.Context -func (_e *MockRoadmapClient_Expecter) GetRhelLifecycle(ctx interface{}) *MockRoadmapClient_GetRhelLifecycle_Call { +func (_e *MockRoadmapClient_Expecter) GetRhelLifecycle(ctx any) *MockRoadmapClient_GetRhelLifecycle_Call { return &MockRoadmapClient_GetRhelLifecycle_Call{Call: _e.mock.On("GetRhelLifecycle", ctx)} } @@ -204,7 +204,7 @@ type MockRoadmapClient_GetRhelLifecycleForLatestMajorVersions_Call struct { // GetRhelLifecycleForLatestMajorVersions is a helper method to define mock.On call // - ctx context.Context -func (_e *MockRoadmapClient_Expecter) GetRhelLifecycleForLatestMajorVersions(ctx interface{}) *MockRoadmapClient_GetRhelLifecycleForLatestMajorVersions_Call { +func (_e *MockRoadmapClient_Expecter) GetRhelLifecycleForLatestMajorVersions(ctx any) *MockRoadmapClient_GetRhelLifecycleForLatestMajorVersions_Call { return &MockRoadmapClient_GetRhelLifecycleForLatestMajorVersions_Call{Call: _e.mock.On("GetRhelLifecycleForLatestMajorVersions", ctx)} } diff --git a/pkg/dao/dao_mock.go b/pkg/dao/dao_mock.go index 350294d6f..d0a0091cf 100644 --- a/pkg/dao/dao_mock.go +++ b/pkg/dao/dao_mock.go @@ -13,6 +13,7 @@ import ( "github.com/content-services/content-sources-backend/pkg/models" "github.com/content-services/tang/pkg/tangy" "github.com/content-services/yummy/pkg/yum" + "github.com/google/uuid" mock "github.com/stretchr/testify/mock" ) @@ -81,7 +82,7 @@ type MockRepositoryConfigDao_BulkCreate_Call struct { // BulkCreate is a helper method to define mock.On call // - ctx context.Context // - newRepositories []api.RepositoryRequest -func (_e *MockRepositoryConfigDao_Expecter) BulkCreate(ctx interface{}, newRepositories interface{}) *MockRepositoryConfigDao_BulkCreate_Call { +func (_e *MockRepositoryConfigDao_Expecter) BulkCreate(ctx any, newRepositories any) *MockRepositoryConfigDao_BulkCreate_Call { return &MockRepositoryConfigDao_BulkCreate_Call{Call: _e.mock.On("BulkCreate", ctx, newRepositories)} } @@ -141,7 +142,7 @@ type MockRepositoryConfigDao_BulkDelete_Call struct { // - ctx context.Context // - orgID string // - uuids []string -func (_e *MockRepositoryConfigDao_Expecter) BulkDelete(ctx interface{}, orgID interface{}, uuids interface{}) *MockRepositoryConfigDao_BulkDelete_Call { +func (_e *MockRepositoryConfigDao_Expecter) BulkDelete(ctx any, orgID any, uuids any) *MockRepositoryConfigDao_BulkDelete_Call { return &MockRepositoryConfigDao_BulkDelete_Call{Call: _e.mock.On("BulkDelete", ctx, orgID, uuids)} } @@ -215,7 +216,7 @@ type MockRepositoryConfigDao_BulkExport_Call struct { // - ctx context.Context // - orgID string // - reposToExport api.RepositoryExportRequest -func (_e *MockRepositoryConfigDao_Expecter) BulkExport(ctx interface{}, orgID interface{}, reposToExport interface{}) *MockRepositoryConfigDao_BulkExport_Call { +func (_e *MockRepositoryConfigDao_Expecter) BulkExport(ctx any, orgID any, reposToExport any) *MockRepositoryConfigDao_BulkExport_Call { return &MockRepositoryConfigDao_BulkExport_Call{Call: _e.mock.On("BulkExport", ctx, orgID, reposToExport)} } @@ -290,7 +291,7 @@ type MockRepositoryConfigDao_BulkImport_Call struct { // BulkImport is a helper method to define mock.On call // - ctx context.Context // - reposToImport []api.RepositoryRequest -func (_e *MockRepositoryConfigDao_Expecter) BulkImport(ctx interface{}, reposToImport interface{}) *MockRepositoryConfigDao_BulkImport_Call { +func (_e *MockRepositoryConfigDao_Expecter) BulkImport(ctx any, reposToImport any) *MockRepositoryConfigDao_BulkImport_Call { return &MockRepositoryConfigDao_BulkImport_Call{Call: _e.mock.On("BulkImport", ctx, reposToImport)} } @@ -356,7 +357,7 @@ type MockRepositoryConfigDao_Create_Call struct { // Create is a helper method to define mock.On call // - ctx context.Context // - newRepo api.RepositoryRequest -func (_e *MockRepositoryConfigDao_Expecter) Create(ctx interface{}, newRepo interface{}) *MockRepositoryConfigDao_Create_Call { +func (_e *MockRepositoryConfigDao_Expecter) Create(ctx any, newRepo any) *MockRepositoryConfigDao_Create_Call { return &MockRepositoryConfigDao_Create_Call{Call: _e.mock.On("Create", ctx, newRepo)} } @@ -414,7 +415,7 @@ type MockRepositoryConfigDao_Delete_Call struct { // - ctx context.Context // - orgID string // - uuid string -func (_e *MockRepositoryConfigDao_Expecter) Delete(ctx interface{}, orgID interface{}, uuid interface{}) *MockRepositoryConfigDao_Delete_Call { +func (_e *MockRepositoryConfigDao_Expecter) Delete(ctx any, orgID any, uuid any) *MockRepositoryConfigDao_Delete_Call { return &MockRepositoryConfigDao_Delete_Call{Call: _e.mock.On("Delete", ctx, orgID, uuid)} } @@ -486,7 +487,7 @@ type MockRepositoryConfigDao_Fetch_Call struct { // - ctx context.Context // - orgID string // - uuid string -func (_e *MockRepositoryConfigDao_Expecter) Fetch(ctx interface{}, orgID interface{}, uuid interface{}) *MockRepositoryConfigDao_Fetch_Call { +func (_e *MockRepositoryConfigDao_Expecter) Fetch(ctx any, orgID any, uuid any) *MockRepositoryConfigDao_Fetch_Call { return &MockRepositoryConfigDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, orgID, uuid)} } @@ -558,7 +559,7 @@ type MockRepositoryConfigDao_FetchByRepoUuid_Call struct { // - ctx context.Context // - orgID string // - repoUuid string -func (_e *MockRepositoryConfigDao_Expecter) FetchByRepoUuid(ctx interface{}, orgID interface{}, repoUuid interface{}) *MockRepositoryConfigDao_FetchByRepoUuid_Call { +func (_e *MockRepositoryConfigDao_Expecter) FetchByRepoUuid(ctx any, orgID any, repoUuid any) *MockRepositoryConfigDao_FetchByRepoUuid_Call { return &MockRepositoryConfigDao_FetchByRepoUuid_Call{Call: _e.mock.On("FetchByRepoUuid", ctx, orgID, repoUuid)} } @@ -632,7 +633,7 @@ type MockRepositoryConfigDao_FetchRepoUUIDsByURLs_Call struct { // - ctx context.Context // - orgID string // - repoURLs []string -func (_e *MockRepositoryConfigDao_Expecter) FetchRepoUUIDsByURLs(ctx interface{}, orgID interface{}, repoURLs interface{}) *MockRepositoryConfigDao_FetchRepoUUIDsByURLs_Call { +func (_e *MockRepositoryConfigDao_Expecter) FetchRepoUUIDsByURLs(ctx any, orgID any, repoURLs any) *MockRepositoryConfigDao_FetchRepoUUIDsByURLs_Call { return &MockRepositoryConfigDao_FetchRepoUUIDsByURLs_Call{Call: _e.mock.On("FetchRepoUUIDsByURLs", ctx, orgID, repoURLs)} } @@ -704,7 +705,7 @@ type MockRepositoryConfigDao_FetchWithoutOrgID_Call struct { // - ctx context.Context // - uuid string // - includeSoftDel bool -func (_e *MockRepositoryConfigDao_Expecter) FetchWithoutOrgID(ctx interface{}, uuid interface{}, includeSoftDel interface{}) *MockRepositoryConfigDao_FetchWithoutOrgID_Call { +func (_e *MockRepositoryConfigDao_Expecter) FetchWithoutOrgID(ctx any, uuid any, includeSoftDel any) *MockRepositoryConfigDao_FetchWithoutOrgID_Call { return &MockRepositoryConfigDao_FetchWithoutOrgID_Call{Call: _e.mock.On("FetchWithoutOrgID", ctx, uuid, includeSoftDel)} } @@ -776,7 +777,7 @@ type MockRepositoryConfigDao_InternalOnly_FetchRepoConfigByName_Call struct { // - ctx context.Context // - orgID string // - name string -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigByName(ctx interface{}, orgID interface{}, name interface{}) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigByName_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigByName(ctx any, orgID any, name any) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigByName_Call { return &MockRepositoryConfigDao_InternalOnly_FetchRepoConfigByName_Call{Call: _e.mock.On("InternalOnly_FetchRepoConfigByName", ctx, orgID, name)} } @@ -849,7 +850,7 @@ type MockRepositoryConfigDao_InternalOnly_FetchRepoConfigForOrg_Call struct { // InternalOnly_FetchRepoConfigForOrg is a helper method to define mock.On call // - ctx context.Context // - orgID string -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigForOrg(ctx interface{}, orgID interface{}) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigForOrg_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigForOrg(ctx any, orgID any) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigForOrg_Call { return &MockRepositoryConfigDao_InternalOnly_FetchRepoConfigForOrg_Call{Call: _e.mock.On("InternalOnly_FetchRepoConfigForOrg", ctx, orgID)} } @@ -908,7 +909,7 @@ type MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForRepoUUID_Call struc // InternalOnly_FetchRepoConfigsForRepoUUID is a helper method to define mock.On call // - ctx context.Context // - uuid string -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigsForRepoUUID(ctx interface{}, uuid interface{}) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForRepoUUID_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigsForRepoUUID(ctx any, uuid any) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForRepoUUID_Call { return &MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForRepoUUID_Call{Call: _e.mock.On("InternalOnly_FetchRepoConfigsForRepoUUID", ctx, uuid)} } @@ -976,7 +977,7 @@ type MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForTemplate_Call struc // InternalOnly_FetchRepoConfigsForTemplate is a helper method to define mock.On call // - ctx context.Context // - template models.Template -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigsForTemplate(ctx interface{}, template interface{}) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForTemplate_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_FetchRepoConfigsForTemplate(ctx any, template any) *MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForTemplate_Call { return &MockRepositoryConfigDao_InternalOnly_FetchRepoConfigsForTemplate_Call{Call: _e.mock.On("InternalOnly_FetchRepoConfigsForTemplate", ctx, template)} } @@ -1033,7 +1034,7 @@ type MockRepositoryConfigDao_InternalOnly_IncrementFailedSnapshotCount_Call stru // InternalOnly_IncrementFailedSnapshotCount is a helper method to define mock.On call // - ctx context.Context // - rcUuid string -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_IncrementFailedSnapshotCount(ctx interface{}, rcUuid interface{}) *MockRepositoryConfigDao_InternalOnly_IncrementFailedSnapshotCount_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_IncrementFailedSnapshotCount(ctx any, rcUuid any) *MockRepositoryConfigDao_InternalOnly_IncrementFailedSnapshotCount_Call { return &MockRepositoryConfigDao_InternalOnly_IncrementFailedSnapshotCount_Call{Call: _e.mock.On("InternalOnly_IncrementFailedSnapshotCount", ctx, rcUuid)} } @@ -1101,7 +1102,7 @@ type MockRepositoryConfigDao_InternalOnly_ListReposToSnapshot_Call struct { // InternalOnly_ListReposToSnapshot is a helper method to define mock.On call // - ctx context.Context // - filter *ListRepoFilter -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_ListReposToSnapshot(ctx interface{}, filter interface{}) *MockRepositoryConfigDao_InternalOnly_ListReposToSnapshot_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_ListReposToSnapshot(ctx any, filter any) *MockRepositoryConfigDao_InternalOnly_ListReposToSnapshot_Call { return &MockRepositoryConfigDao_InternalOnly_ListReposToSnapshot_Call{Call: _e.mock.On("InternalOnly_ListReposToSnapshot", ctx, filter)} } @@ -1175,7 +1176,7 @@ type MockRepositoryConfigDao_InternalOnly_RefreshLightwellRepo_Call struct { // - publishedDistURL string // - basePath string // - featureName string -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_RefreshLightwellRepo(ctx interface{}, orgID interface{}, name interface{}, securityLevel interface{}, contentType interface{}, publishedDistURL interface{}, basePath interface{}, featureName interface{}) *MockRepositoryConfigDao_InternalOnly_RefreshLightwellRepo_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_RefreshLightwellRepo(ctx any, orgID any, name any, securityLevel any, contentType any, publishedDistURL any, basePath any, featureName any) *MockRepositoryConfigDao_InternalOnly_RefreshLightwellRepo_Call { return &MockRepositoryConfigDao_InternalOnly_RefreshLightwellRepo_Call{Call: _e.mock.On("InternalOnly_RefreshLightwellRepo", ctx, orgID, name, securityLevel, contentType, publishedDistURL, basePath, featureName)} } @@ -1275,7 +1276,7 @@ type MockRepositoryConfigDao_InternalOnly_RefreshPredefinedSnapshotRepo_Call str // - request api.RepositoryRequest // - label string // - featureName string -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_RefreshPredefinedSnapshotRepo(ctx interface{}, request interface{}, label interface{}, featureName interface{}) *MockRepositoryConfigDao_InternalOnly_RefreshPredefinedSnapshotRepo_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_RefreshPredefinedSnapshotRepo(ctx any, request any, label any, featureName any) *MockRepositoryConfigDao_InternalOnly_RefreshPredefinedSnapshotRepo_Call { return &MockRepositoryConfigDao_InternalOnly_RefreshPredefinedSnapshotRepo_Call{Call: _e.mock.On("InternalOnly_RefreshPredefinedSnapshotRepo", ctx, request, label, featureName)} } @@ -1342,7 +1343,7 @@ type MockRepositoryConfigDao_InternalOnly_ResetFailedSnapshotCount_Call struct { // InternalOnly_ResetFailedSnapshotCount is a helper method to define mock.On call // - ctx context.Context // - rcUuid string -func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_ResetFailedSnapshotCount(ctx interface{}, rcUuid interface{}) *MockRepositoryConfigDao_InternalOnly_ResetFailedSnapshotCount_Call { +func (_e *MockRepositoryConfigDao_Expecter) InternalOnly_ResetFailedSnapshotCount(ctx any, rcUuid any) *MockRepositoryConfigDao_InternalOnly_ResetFailedSnapshotCount_Call { return &MockRepositoryConfigDao_InternalOnly_ResetFailedSnapshotCount_Call{Call: _e.mock.On("InternalOnly_ResetFailedSnapshotCount", ctx, rcUuid)} } @@ -1416,7 +1417,7 @@ type MockRepositoryConfigDao_List_Call struct { // - orgID string // - paginationData api.PaginationData // - filterData api.FilterData -func (_e *MockRepositoryConfigDao_Expecter) List(ctx interface{}, orgID interface{}, paginationData interface{}, filterData interface{}) *MockRepositoryConfigDao_List_Call { +func (_e *MockRepositoryConfigDao_Expecter) List(ctx any, orgID any, paginationData any, filterData any) *MockRepositoryConfigDao_List_Call { return &MockRepositoryConfigDao_List_Call{Call: _e.mock.On("List", ctx, orgID, paginationData, filterData)} } @@ -1494,7 +1495,7 @@ type MockRepositoryConfigDao_ListReposWithOutdatedSnapshots_Call struct { // ListReposWithOutdatedSnapshots is a helper method to define mock.On call // - ctx context.Context // - olderThanDays int -func (_e *MockRepositoryConfigDao_Expecter) ListReposWithOutdatedSnapshots(ctx interface{}, olderThanDays interface{}) *MockRepositoryConfigDao_ListReposWithOutdatedSnapshots_Call { +func (_e *MockRepositoryConfigDao_Expecter) ListReposWithOutdatedSnapshots(ctx any, olderThanDays any) *MockRepositoryConfigDao_ListReposWithOutdatedSnapshots_Call { return &MockRepositoryConfigDao_ListReposWithOutdatedSnapshots_Call{Call: _e.mock.On("ListReposWithOutdatedSnapshots", ctx, olderThanDays)} } @@ -1551,7 +1552,7 @@ type MockRepositoryConfigDao_SavePublicRepos_Call struct { // SavePublicRepos is a helper method to define mock.On call // - ctx context.Context // - urls []string -func (_e *MockRepositoryConfigDao_Expecter) SavePublicRepos(ctx interface{}, urls interface{}) *MockRepositoryConfigDao_SavePublicRepos_Call { +func (_e *MockRepositoryConfigDao_Expecter) SavePublicRepos(ctx any, urls any) *MockRepositoryConfigDao_SavePublicRepos_Call { return &MockRepositoryConfigDao_SavePublicRepos_Call{Call: _e.mock.On("SavePublicRepos", ctx, urls)} } @@ -1609,7 +1610,7 @@ type MockRepositoryConfigDao_SetPartnerRepo_Call struct { // - ctx context.Context // - repoConfigUUID string // - partner bool -func (_e *MockRepositoryConfigDao_Expecter) SetPartnerRepo(ctx interface{}, repoConfigUUID interface{}, partner interface{}) *MockRepositoryConfigDao_SetPartnerRepo_Call { +func (_e *MockRepositoryConfigDao_Expecter) SetPartnerRepo(ctx any, repoConfigUUID any, partner any) *MockRepositoryConfigDao_SetPartnerRepo_Call { return &MockRepositoryConfigDao_SetPartnerRepo_Call{Call: _e.mock.On("SetPartnerRepo", ctx, repoConfigUUID, partner)} } @@ -1672,7 +1673,7 @@ type MockRepositoryConfigDao_SoftDelete_Call struct { // - ctx context.Context // - orgID string // - uuid string -func (_e *MockRepositoryConfigDao_Expecter) SoftDelete(ctx interface{}, orgID interface{}, uuid interface{}) *MockRepositoryConfigDao_SoftDelete_Call { +func (_e *MockRepositoryConfigDao_Expecter) SoftDelete(ctx any, orgID any, uuid any) *MockRepositoryConfigDao_SoftDelete_Call { return &MockRepositoryConfigDao_SoftDelete_Call{Call: _e.mock.On("SoftDelete", ctx, orgID, uuid)} } @@ -1745,7 +1746,7 @@ type MockRepositoryConfigDao_Update_Call struct { // - orgID string // - uuid string // - repoParams api.RepositoryUpdateRequest -func (_e *MockRepositoryConfigDao_Expecter) Update(ctx interface{}, orgID interface{}, uuid interface{}, repoParams interface{}) *MockRepositoryConfigDao_Update_Call { +func (_e *MockRepositoryConfigDao_Expecter) Update(ctx any, orgID any, uuid any, repoParams any) *MockRepositoryConfigDao_Update_Call { return &MockRepositoryConfigDao_Update_Call{Call: _e.mock.On("Update", ctx, orgID, uuid, repoParams)} } @@ -1814,7 +1815,7 @@ type MockRepositoryConfigDao_UpdateLastSnapshot_Call struct { // - orgID string // - repoConfigUUID string // - snapUUID string -func (_e *MockRepositoryConfigDao_Expecter) UpdateLastSnapshot(ctx interface{}, orgID interface{}, repoConfigUUID interface{}, snapUUID interface{}) *MockRepositoryConfigDao_UpdateLastSnapshot_Call { +func (_e *MockRepositoryConfigDao_Expecter) UpdateLastSnapshot(ctx any, orgID any, repoConfigUUID any, snapUUID any) *MockRepositoryConfigDao_UpdateLastSnapshot_Call { return &MockRepositoryConfigDao_UpdateLastSnapshot_Call{Call: _e.mock.On("UpdateLastSnapshot", ctx, orgID, repoConfigUUID, snapUUID)} } @@ -1883,7 +1884,7 @@ type MockRepositoryConfigDao_UpdateLastSnapshotTask_Call struct { // - taskUUID string // - orgID string // - repoUUID string -func (_e *MockRepositoryConfigDao_Expecter) UpdateLastSnapshotTask(ctx interface{}, taskUUID interface{}, orgID interface{}, repoUUID interface{}) *MockRepositoryConfigDao_UpdateLastSnapshotTask_Call { +func (_e *MockRepositoryConfigDao_Expecter) UpdateLastSnapshotTask(ctx any, taskUUID any, orgID any, repoUUID any) *MockRepositoryConfigDao_UpdateLastSnapshotTask_Call { return &MockRepositoryConfigDao_UpdateLastSnapshotTask_Call{Call: _e.mock.On("UpdateLastSnapshotTask", ctx, taskUUID, orgID, repoUUID)} } @@ -1961,7 +1962,7 @@ type MockRepositoryConfigDao_ValidateParameters_Call struct { // - orgId string // - params api.RepositoryValidationRequest // - excludedUUIDS []string -func (_e *MockRepositoryConfigDao_Expecter) ValidateParameters(ctx interface{}, orgId interface{}, params interface{}, excludedUUIDS interface{}) *MockRepositoryConfigDao_ValidateParameters_Call { +func (_e *MockRepositoryConfigDao_Expecter) ValidateParameters(ctx any, orgId any, params any, excludedUUIDS any) *MockRepositoryConfigDao_ValidateParameters_Call { return &MockRepositoryConfigDao_ValidateParameters_Call{Call: _e.mock.On("ValidateParameters", ctx, orgId, params, excludedUUIDS)} } @@ -2065,7 +2066,7 @@ type MockModuleStreamDao_InsertForRepository_Call struct { // - ctx context.Context // - repoUuid string // - pkgGroups []yum.ModuleMD -func (_e *MockModuleStreamDao_Expecter) InsertForRepository(ctx interface{}, repoUuid interface{}, pkgGroups interface{}) *MockModuleStreamDao_InsertForRepository_Call { +func (_e *MockModuleStreamDao_Expecter) InsertForRepository(ctx any, repoUuid any, pkgGroups any) *MockModuleStreamDao_InsertForRepository_Call { return &MockModuleStreamDao_InsertForRepository_Call{Call: _e.mock.On("InsertForRepository", ctx, repoUuid, pkgGroups)} } @@ -2126,7 +2127,7 @@ type MockModuleStreamDao_OrphanCleanup_Call struct { // OrphanCleanup is a helper method to define mock.On call // - ctx context.Context -func (_e *MockModuleStreamDao_Expecter) OrphanCleanup(ctx interface{}) *MockModuleStreamDao_OrphanCleanup_Call { +func (_e *MockModuleStreamDao_Expecter) OrphanCleanup(ctx any) *MockModuleStreamDao_OrphanCleanup_Call { return &MockModuleStreamDao_OrphanCleanup_Call{Call: _e.mock.On("OrphanCleanup", ctx)} } @@ -2190,7 +2191,7 @@ type MockModuleStreamDao_SearchRepositoryModuleStreams_Call struct { // - ctx context.Context // - orgID string // - request api.SearchModuleStreamsRequest -func (_e *MockModuleStreamDao_Expecter) SearchRepositoryModuleStreams(ctx interface{}, orgID interface{}, request interface{}) *MockModuleStreamDao_SearchRepositoryModuleStreams_Call { +func (_e *MockModuleStreamDao_Expecter) SearchRepositoryModuleStreams(ctx any, orgID any, request any) *MockModuleStreamDao_SearchRepositoryModuleStreams_Call { return &MockModuleStreamDao_SearchRepositoryModuleStreams_Call{Call: _e.mock.On("SearchRepositoryModuleStreams", ctx, orgID, request)} } @@ -2264,7 +2265,7 @@ type MockModuleStreamDao_SearchSnapshotModuleStreams_Call struct { // - ctx context.Context // - orgID string // - request api.SearchSnapshotModuleStreamsRequest -func (_e *MockModuleStreamDao_Expecter) SearchSnapshotModuleStreams(ctx interface{}, orgID interface{}, request interface{}) *MockModuleStreamDao_SearchSnapshotModuleStreams_Call { +func (_e *MockModuleStreamDao_Expecter) SearchSnapshotModuleStreams(ctx any, orgID any, request any) *MockModuleStreamDao_SearchSnapshotModuleStreams_Call { return &MockModuleStreamDao_SearchSnapshotModuleStreams_Call{Call: _e.mock.On("SearchSnapshotModuleStreams", ctx, orgID, request)} } @@ -2366,7 +2367,7 @@ type MockRpmDao_FetchForRepository_Call struct { // - orgID string // - repositoryConfigUUID string // - rpmUUIDs []string -func (_e *MockRpmDao_Expecter) FetchForRepository(ctx interface{}, orgID interface{}, repositoryConfigUUID interface{}, rpmUUIDs interface{}) *MockRpmDao_FetchForRepository_Call { +func (_e *MockRpmDao_Expecter) FetchForRepository(ctx any, orgID any, repositoryConfigUUID any, rpmUUIDs any) *MockRpmDao_FetchForRepository_Call { return &MockRpmDao_FetchForRepository_Call{Call: _e.mock.On("FetchForRepository", ctx, orgID, repositoryConfigUUID, rpmUUIDs)} } @@ -2445,7 +2446,7 @@ type MockRpmDao_FetchTemplateErrataIDs_Call struct { // - ctx context.Context // - orgId string // - templateUUID string -func (_e *MockRpmDao_Expecter) FetchTemplateErrataIDs(ctx interface{}, orgId interface{}, templateUUID interface{}) *MockRpmDao_FetchTemplateErrataIDs_Call { +func (_e *MockRpmDao_Expecter) FetchTemplateErrataIDs(ctx any, orgId any, templateUUID any) *MockRpmDao_FetchTemplateErrataIDs_Call { return &MockRpmDao_FetchTemplateErrataIDs_Call{Call: _e.mock.On("FetchTemplateErrataIDs", ctx, orgId, templateUUID)} } @@ -2517,7 +2518,7 @@ type MockRpmDao_InsertForRepository_Call struct { // - ctx context.Context // - repoUuid string // - pkgs []yum.Package -func (_e *MockRpmDao_Expecter) InsertForRepository(ctx interface{}, repoUuid interface{}, pkgs interface{}) *MockRpmDao_InsertForRepository_Call { +func (_e *MockRpmDao_Expecter) InsertForRepository(ctx any, repoUuid any, pkgs any) *MockRpmDao_InsertForRepository_Call { return &MockRpmDao_InsertForRepository_Call{Call: _e.mock.On("InsertForRepository", ctx, repoUuid, pkgs)} } @@ -2599,7 +2600,7 @@ type MockRpmDao_List_Call struct { // - offset int // - search string // - sortBy string -func (_e *MockRpmDao_Expecter) List(ctx interface{}, orgID interface{}, uuidRepo interface{}, limit interface{}, offset interface{}, search interface{}, sortBy interface{}) *MockRpmDao_List_Call { +func (_e *MockRpmDao_Expecter) List(ctx any, orgID any, uuidRepo any, limit any, offset any, search any, sortBy any) *MockRpmDao_List_Call { return &MockRpmDao_List_Call{Call: _e.mock.On("List", ctx, orgID, uuidRepo, limit, offset, search, sortBy)} } @@ -2701,7 +2702,7 @@ type MockRpmDao_ListSnapshotErrata_Call struct { // - snapshotUUIDs []string // - filters tangy.ErrataListFilters // - pageOpts api.PaginationData -func (_e *MockRpmDao_Expecter) ListSnapshotErrata(ctx interface{}, orgId interface{}, snapshotUUIDs interface{}, filters interface{}, pageOpts interface{}) *MockRpmDao_ListSnapshotErrata_Call { +func (_e *MockRpmDao_Expecter) ListSnapshotErrata(ctx any, orgId any, snapshotUUIDs any, filters any, pageOpts any) *MockRpmDao_ListSnapshotErrata_Call { return &MockRpmDao_ListSnapshotErrata_Call{Call: _e.mock.On("ListSnapshotErrata", ctx, orgId, snapshotUUIDs, filters, pageOpts)} } @@ -2793,7 +2794,7 @@ type MockRpmDao_ListSnapshotRpms_Call struct { // - snapshotUUIDs []string // - search string // - pageOpts api.PaginationData -func (_e *MockRpmDao_Expecter) ListSnapshotRpms(ctx interface{}, orgId interface{}, snapshotUUIDs interface{}, search interface{}, pageOpts interface{}) *MockRpmDao_ListSnapshotRpms_Call { +func (_e *MockRpmDao_Expecter) ListSnapshotRpms(ctx any, orgId any, snapshotUUIDs any, search any, pageOpts any) *MockRpmDao_ListSnapshotRpms_Call { return &MockRpmDao_ListSnapshotRpms_Call{Call: _e.mock.On("ListSnapshotRpms", ctx, orgId, snapshotUUIDs, search, pageOpts)} } @@ -2885,7 +2886,7 @@ type MockRpmDao_ListTemplateErrata_Call struct { // - templateUUID string // - filters tangy.ErrataListFilters // - pageOpts api.PaginationData -func (_e *MockRpmDao_Expecter) ListTemplateErrata(ctx interface{}, orgId interface{}, templateUUID interface{}, filters interface{}, pageOpts interface{}) *MockRpmDao_ListTemplateErrata_Call { +func (_e *MockRpmDao_Expecter) ListTemplateErrata(ctx any, orgId any, templateUUID any, filters any, pageOpts any) *MockRpmDao_ListTemplateErrata_Call { return &MockRpmDao_ListTemplateErrata_Call{Call: _e.mock.On("ListTemplateErrata", ctx, orgId, templateUUID, filters, pageOpts)} } @@ -2977,7 +2978,7 @@ type MockRpmDao_ListTemplateRpms_Call struct { // - templateUUID string // - search string // - pageOpts api.PaginationData -func (_e *MockRpmDao_Expecter) ListTemplateRpms(ctx interface{}, orgId interface{}, templateUUID interface{}, search interface{}, pageOpts interface{}) *MockRpmDao_ListTemplateRpms_Call { +func (_e *MockRpmDao_Expecter) ListTemplateRpms(ctx any, orgId any, templateUUID any, search any, pageOpts any) *MockRpmDao_ListTemplateRpms_Call { return &MockRpmDao_ListTemplateRpms_Call{Call: _e.mock.On("ListTemplateRpms", ctx, orgId, templateUUID, search, pageOpts)} } @@ -3048,7 +3049,7 @@ type MockRpmDao_OrphanCleanup_Call struct { // OrphanCleanup is a helper method to define mock.On call // - ctx context.Context -func (_e *MockRpmDao_Expecter) OrphanCleanup(ctx interface{}) *MockRpmDao_OrphanCleanup_Call { +func (_e *MockRpmDao_Expecter) OrphanCleanup(ctx any) *MockRpmDao_OrphanCleanup_Call { return &MockRpmDao_OrphanCleanup_Call{Call: _e.mock.On("OrphanCleanup", ctx)} } @@ -3112,7 +3113,7 @@ type MockRpmDao_Search_Call struct { // - ctx context.Context // - orgID string // - request api.ContentUnitSearchRequest -func (_e *MockRpmDao_Expecter) Search(ctx interface{}, orgID interface{}, request interface{}) *MockRpmDao_Search_Call { +func (_e *MockRpmDao_Expecter) Search(ctx any, orgID any, request any) *MockRpmDao_Search_Call { return &MockRpmDao_Search_Call{Call: _e.mock.On("Search", ctx, orgID, request)} } @@ -3186,7 +3187,7 @@ type MockRpmDao_SearchSnapshotRpms_Call struct { // - ctx context.Context // - orgId string // - request api.SnapshotSearchRpmRequest -func (_e *MockRpmDao_Expecter) SearchSnapshotRpms(ctx interface{}, orgId interface{}, request interface{}) *MockRpmDao_SearchSnapshotRpms_Call { +func (_e *MockRpmDao_Expecter) SearchSnapshotRpms(ctx any, orgId any, request any) *MockRpmDao_SearchSnapshotRpms_Call { return &MockRpmDao_SearchSnapshotRpms_Call{Call: _e.mock.On("SearchSnapshotRpms", ctx, orgId, request)} } @@ -3285,7 +3286,7 @@ type MockRepositoryDao_FetchForUrl_Call struct { // - ctx context.Context // - url string // - origin *string -func (_e *MockRepositoryDao_Expecter) FetchForUrl(ctx interface{}, url interface{}, origin interface{}) *MockRepositoryDao_FetchForUrl_Call { +func (_e *MockRepositoryDao_Expecter) FetchForUrl(ctx any, url any, origin any) *MockRepositoryDao_FetchForUrl_Call { return &MockRepositoryDao_FetchForUrl_Call{Call: _e.mock.On("FetchForUrl", ctx, url, origin)} } @@ -3356,7 +3357,7 @@ type MockRepositoryDao_FetchRepositoryRPMCount_Call struct { // FetchRepositoryRPMCount is a helper method to define mock.On call // - ctx context.Context // - repoUUID string -func (_e *MockRepositoryDao_Expecter) FetchRepositoryRPMCount(ctx interface{}, repoUUID interface{}) *MockRepositoryDao_FetchRepositoryRPMCount_Call { +func (_e *MockRepositoryDao_Expecter) FetchRepositoryRPMCount(ctx any, repoUUID any) *MockRepositoryDao_FetchRepositoryRPMCount_Call { return &MockRepositoryDao_FetchRepositoryRPMCount_Call{Call: _e.mock.On("FetchRepositoryRPMCount", ctx, repoUUID)} } @@ -3416,7 +3417,7 @@ type MockRepositoryDao_InternalOnly_UpdateCounts_Call struct { // - packageCount int // - buildCount int // - versionCount int -func (_e *MockRepositoryDao_Expecter) InternalOnly_UpdateCounts(ctx interface{}, repoUUID interface{}, packageCount interface{}, buildCount interface{}, versionCount interface{}) *MockRepositoryDao_InternalOnly_UpdateCounts_Call { +func (_e *MockRepositoryDao_Expecter) InternalOnly_UpdateCounts(ctx any, repoUUID any, packageCount any, buildCount any, versionCount any) *MockRepositoryDao_InternalOnly_UpdateCounts_Call { return &MockRepositoryDao_InternalOnly_UpdateCounts_Call{Call: _e.mock.On("InternalOnly_UpdateCounts", ctx, repoUUID, packageCount, buildCount, versionCount)} } @@ -3500,7 +3501,7 @@ type MockRepositoryDao_ListForIntrospection_Call struct { // - ctx context.Context // - urls *[]string // - force bool -func (_e *MockRepositoryDao_Expecter) ListForIntrospection(ctx interface{}, urls interface{}, force interface{}) *MockRepositoryDao_ListForIntrospection_Call { +func (_e *MockRepositoryDao_Expecter) ListForIntrospection(ctx any, urls any, force any) *MockRepositoryDao_ListForIntrospection_Call { return &MockRepositoryDao_ListForIntrospection_Call{Call: _e.mock.On("ListForIntrospection", ctx, urls, force)} } @@ -3578,7 +3579,7 @@ type MockRepositoryDao_ListPublic_Call struct { // - ctx context.Context // - paginationData api.PaginationData // - filterData api.FilterData -func (_e *MockRepositoryDao_Expecter) ListPublic(ctx interface{}, paginationData interface{}, filterData interface{}) *MockRepositoryDao_ListPublic_Call { +func (_e *MockRepositoryDao_Expecter) ListPublic(ctx any, paginationData any, filterData any) *MockRepositoryDao_ListPublic_Call { return &MockRepositoryDao_ListPublic_Call{Call: _e.mock.On("ListPublic", ctx, paginationData, filterData)} } @@ -3640,7 +3641,7 @@ type MockRepositoryDao_MarkAsNotPublic_Call struct { // MarkAsNotPublic is a helper method to define mock.On call // - ctx context.Context // - url string -func (_e *MockRepositoryDao_Expecter) MarkAsNotPublic(ctx interface{}, url interface{}) *MockRepositoryDao_MarkAsNotPublic_Call { +func (_e *MockRepositoryDao_Expecter) MarkAsNotPublic(ctx any, url any) *MockRepositoryDao_MarkAsNotPublic_Call { return &MockRepositoryDao_MarkAsNotPublic_Call{Call: _e.mock.On("MarkAsNotPublic", ctx, url)} } @@ -3696,7 +3697,7 @@ type MockRepositoryDao_OrphanCleanup_Call struct { // OrphanCleanup is a helper method to define mock.On call // - ctx context.Context -func (_e *MockRepositoryDao_Expecter) OrphanCleanup(ctx interface{}) *MockRepositoryDao_OrphanCleanup_Call { +func (_e *MockRepositoryDao_Expecter) OrphanCleanup(ctx any) *MockRepositoryDao_OrphanCleanup_Call { return &MockRepositoryDao_OrphanCleanup_Call{Call: _e.mock.On("OrphanCleanup", ctx)} } @@ -3748,7 +3749,7 @@ type MockRepositoryDao_Update_Call struct { // Update is a helper method to define mock.On call // - ctx context.Context // - repo RepositoryUpdate -func (_e *MockRepositoryDao_Expecter) Update(ctx interface{}, repo interface{}) *MockRepositoryDao_Update_Call { +func (_e *MockRepositoryDao_Expecter) Update(ctx any, repo any) *MockRepositoryDao_Update_Call { return &MockRepositoryDao_Update_Call{Call: _e.mock.On("Update", ctx, repo)} } @@ -3834,7 +3835,7 @@ type MockSnapshotDao_BulkDelete_Call struct { // BulkDelete is a helper method to define mock.On call // - ctx context.Context // - uuids []string -func (_e *MockSnapshotDao_Expecter) BulkDelete(ctx interface{}, uuids interface{}) *MockSnapshotDao_BulkDelete_Call { +func (_e *MockSnapshotDao_Expecter) BulkDelete(ctx any, uuids any) *MockSnapshotDao_BulkDelete_Call { return &MockSnapshotDao_BulkDelete_Call{Call: _e.mock.On("BulkDelete", ctx, uuids)} } @@ -3891,7 +3892,7 @@ type MockSnapshotDao_ClearDeletedAt_Call struct { // ClearDeletedAt is a helper method to define mock.On call // - ctx context.Context // - snapUUID string -func (_e *MockSnapshotDao_Expecter) ClearDeletedAt(ctx interface{}, snapUUID interface{}) *MockSnapshotDao_ClearDeletedAt_Call { +func (_e *MockSnapshotDao_Expecter) ClearDeletedAt(ctx any, snapUUID any) *MockSnapshotDao_ClearDeletedAt_Call { return &MockSnapshotDao_ClearDeletedAt_Call{Call: _e.mock.On("ClearDeletedAt", ctx, snapUUID)} } @@ -3948,7 +3949,7 @@ type MockSnapshotDao_Create_Call struct { // Create is a helper method to define mock.On call // - ctx context.Context // - snap *models.Snapshot -func (_e *MockSnapshotDao_Expecter) Create(ctx interface{}, snap interface{}) *MockSnapshotDao_Create_Call { +func (_e *MockSnapshotDao_Expecter) Create(ctx any, snap any) *MockSnapshotDao_Create_Call { return &MockSnapshotDao_Create_Call{Call: _e.mock.On("Create", ctx, snap)} } @@ -4005,7 +4006,7 @@ type MockSnapshotDao_Delete_Call struct { // Delete is a helper method to define mock.On call // - ctx context.Context // - snapUUID string -func (_e *MockSnapshotDao_Expecter) Delete(ctx interface{}, snapUUID interface{}) *MockSnapshotDao_Delete_Call { +func (_e *MockSnapshotDao_Expecter) Delete(ctx any, snapUUID any) *MockSnapshotDao_Delete_Call { return &MockSnapshotDao_Delete_Call{Call: _e.mock.On("Delete", ctx, snapUUID)} } @@ -4072,7 +4073,7 @@ type MockSnapshotDao_Fetch_Call struct { // - ctx context.Context // - orgID string // - uuid string -func (_e *MockSnapshotDao_Expecter) Fetch(ctx interface{}, orgID interface{}, uuid interface{}) *MockSnapshotDao_Fetch_Call { +func (_e *MockSnapshotDao_Expecter) Fetch(ctx any, orgID any, uuid any) *MockSnapshotDao_Fetch_Call { return &MockSnapshotDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, orgID, uuid)} } @@ -4146,7 +4147,7 @@ type MockSnapshotDao_FetchForRepoConfigUUID_Call struct { // - ctx context.Context // - repoConfigUUID string // - inclSoftDel bool -func (_e *MockSnapshotDao_Expecter) FetchForRepoConfigUUID(ctx interface{}, repoConfigUUID interface{}, inclSoftDel interface{}) *MockSnapshotDao_FetchForRepoConfigUUID_Call { +func (_e *MockSnapshotDao_Expecter) FetchForRepoConfigUUID(ctx any, repoConfigUUID any, inclSoftDel any) *MockSnapshotDao_FetchForRepoConfigUUID_Call { return &MockSnapshotDao_FetchForRepoConfigUUID_Call{Call: _e.mock.On("FetchForRepoConfigUUID", ctx, repoConfigUUID, inclSoftDel)} } @@ -4217,7 +4218,7 @@ type MockSnapshotDao_FetchLatestPublishedSnapshotModel_Call struct { // FetchLatestPublishedSnapshotModel is a helper method to define mock.On call // - ctx context.Context // - repoConfigUUID string -func (_e *MockSnapshotDao_Expecter) FetchLatestPublishedSnapshotModel(ctx interface{}, repoConfigUUID interface{}) *MockSnapshotDao_FetchLatestPublishedSnapshotModel_Call { +func (_e *MockSnapshotDao_Expecter) FetchLatestPublishedSnapshotModel(ctx any, repoConfigUUID any) *MockSnapshotDao_FetchLatestPublishedSnapshotModel_Call { return &MockSnapshotDao_FetchLatestPublishedSnapshotModel_Call{Call: _e.mock.On("FetchLatestPublishedSnapshotModel", ctx, repoConfigUUID)} } @@ -4283,7 +4284,7 @@ type MockSnapshotDao_FetchLatestSnapshot_Call struct { // FetchLatestSnapshot is a helper method to define mock.On call // - ctx context.Context // - repoConfigUUID string -func (_e *MockSnapshotDao_Expecter) FetchLatestSnapshot(ctx interface{}, repoConfigUUID interface{}) *MockSnapshotDao_FetchLatestSnapshot_Call { +func (_e *MockSnapshotDao_Expecter) FetchLatestSnapshot(ctx any, repoConfigUUID any) *MockSnapshotDao_FetchLatestSnapshot_Call { return &MockSnapshotDao_FetchLatestSnapshot_Call{Call: _e.mock.On("FetchLatestSnapshot", ctx, repoConfigUUID)} } @@ -4349,7 +4350,7 @@ type MockSnapshotDao_FetchLatestSnapshotForDistribution_Call struct { // FetchLatestSnapshotForDistribution is a helper method to define mock.On call // - ctx context.Context // - repoConfigUUID string -func (_e *MockSnapshotDao_Expecter) FetchLatestSnapshotForDistribution(ctx interface{}, repoConfigUUID interface{}) *MockSnapshotDao_FetchLatestSnapshotForDistribution_Call { +func (_e *MockSnapshotDao_Expecter) FetchLatestSnapshotForDistribution(ctx any, repoConfigUUID any) *MockSnapshotDao_FetchLatestSnapshotForDistribution_Call { return &MockSnapshotDao_FetchLatestSnapshotForDistribution_Call{Call: _e.mock.On("FetchLatestSnapshotForDistribution", ctx, repoConfigUUID)} } @@ -4415,7 +4416,7 @@ type MockSnapshotDao_FetchLatestSnapshotModel_Call struct { // FetchLatestSnapshotModel is a helper method to define mock.On call // - ctx context.Context // - repoConfigUUID string -func (_e *MockSnapshotDao_Expecter) FetchLatestSnapshotModel(ctx interface{}, repoConfigUUID interface{}) *MockSnapshotDao_FetchLatestSnapshotModel_Call { +func (_e *MockSnapshotDao_Expecter) FetchLatestSnapshotModel(ctx any, repoConfigUUID any) *MockSnapshotDao_FetchLatestSnapshotModel_Call { return &MockSnapshotDao_FetchLatestSnapshotModel_Call{Call: _e.mock.On("FetchLatestSnapshotModel", ctx, repoConfigUUID)} } @@ -4482,7 +4483,7 @@ type MockSnapshotDao_FetchModel_Call struct { // - ctx context.Context // - uuid string // - includeSoftDel bool -func (_e *MockSnapshotDao_Expecter) FetchModel(ctx interface{}, uuid interface{}, includeSoftDel interface{}) *MockSnapshotDao_FetchModel_Call { +func (_e *MockSnapshotDao_Expecter) FetchModel(ctx any, uuid any, includeSoftDel any) *MockSnapshotDao_FetchModel_Call { return &MockSnapshotDao_FetchModel_Call{Call: _e.mock.On("FetchModel", ctx, uuid, includeSoftDel)} } @@ -4556,7 +4557,7 @@ type MockSnapshotDao_FetchSnapshotByVersionHref_Call struct { // - ctx context.Context // - repoConfigUUID string // - versionHref string -func (_e *MockSnapshotDao_Expecter) FetchSnapshotByVersionHref(ctx interface{}, repoConfigUUID interface{}, versionHref interface{}) *MockSnapshotDao_FetchSnapshotByVersionHref_Call { +func (_e *MockSnapshotDao_Expecter) FetchSnapshotByVersionHref(ctx any, repoConfigUUID any, versionHref any) *MockSnapshotDao_FetchSnapshotByVersionHref_Call { return &MockSnapshotDao_FetchSnapshotByVersionHref_Call{Call: _e.mock.On("FetchSnapshotByVersionHref", ctx, repoConfigUUID, versionHref)} } @@ -4628,7 +4629,7 @@ type MockSnapshotDao_FetchSnapshotsByDateAndRepository_Call struct { // - ctx context.Context // - orgID string // - request api.ListSnapshotByDateRequest -func (_e *MockSnapshotDao_Expecter) FetchSnapshotsByDateAndRepository(ctx interface{}, orgID interface{}, request interface{}) *MockSnapshotDao_FetchSnapshotsByDateAndRepository_Call { +func (_e *MockSnapshotDao_Expecter) FetchSnapshotsByDateAndRepository(ctx any, orgID any, request any) *MockSnapshotDao_FetchSnapshotsByDateAndRepository_Call { return &MockSnapshotDao_FetchSnapshotsByDateAndRepository_Call{Call: _e.mock.On("FetchSnapshotsByDateAndRepository", ctx, orgID, request)} } @@ -4702,7 +4703,7 @@ type MockSnapshotDao_FetchSnapshotsModelByDateAndRepository_Call struct { // - ctx context.Context // - orgID string // - request api.ListSnapshotByDateRequest -func (_e *MockSnapshotDao_Expecter) FetchSnapshotsModelByDateAndRepository(ctx interface{}, orgID interface{}, request interface{}) *MockSnapshotDao_FetchSnapshotsModelByDateAndRepository_Call { +func (_e *MockSnapshotDao_Expecter) FetchSnapshotsModelByDateAndRepository(ctx any, orgID any, request any) *MockSnapshotDao_FetchSnapshotsModelByDateAndRepository_Call { return &MockSnapshotDao_FetchSnapshotsModelByDateAndRepository_Call{Call: _e.mock.On("FetchSnapshotsModelByDateAndRepository", ctx, orgID, request)} } @@ -4775,7 +4776,7 @@ type MockSnapshotDao_GetRepositoryConfigurationFile_Call struct { // - orgID string // - snapshotUUID string // - isLatest bool -func (_e *MockSnapshotDao_Expecter) GetRepositoryConfigurationFile(ctx interface{}, orgID interface{}, snapshotUUID interface{}, isLatest interface{}) *MockSnapshotDao_GetRepositoryConfigurationFile_Call { +func (_e *MockSnapshotDao_Expecter) GetRepositoryConfigurationFile(ctx any, orgID any, snapshotUUID any, isLatest any) *MockSnapshotDao_GetRepositoryConfigurationFile_Call { return &MockSnapshotDao_GetRepositoryConfigurationFile_Call{Call: _e.mock.On("GetRepositoryConfigurationFile", ctx, orgID, snapshotUUID, isLatest)} } @@ -4851,7 +4852,7 @@ type MockSnapshotDao_HasPublishedSnapshot_Call struct { // HasPublishedSnapshot is a helper method to define mock.On call // - ctx context.Context // - repoConfigUUID string -func (_e *MockSnapshotDao_Expecter) HasPublishedSnapshot(ctx interface{}, repoConfigUUID interface{}) *MockSnapshotDao_HasPublishedSnapshot_Call { +func (_e *MockSnapshotDao_Expecter) HasPublishedSnapshot(ctx any, repoConfigUUID any) *MockSnapshotDao_HasPublishedSnapshot_Call { return &MockSnapshotDao_HasPublishedSnapshot_Call{Call: _e.mock.On("HasPublishedSnapshot", ctx, repoConfigUUID)} } @@ -4926,7 +4927,7 @@ type MockSnapshotDao_List_Call struct { // - repoConfigUuid string // - paginationData api.PaginationData // - filterData api.FilterData -func (_e *MockSnapshotDao_Expecter) List(ctx interface{}, orgID interface{}, repoConfigUuid interface{}, paginationData interface{}, filterData interface{}) *MockSnapshotDao_List_Call { +func (_e *MockSnapshotDao_Expecter) List(ctx any, orgID any, repoConfigUuid any, paginationData any, filterData any) *MockSnapshotDao_List_Call { return &MockSnapshotDao_List_Call{Call: _e.mock.On("List", ctx, orgID, repoConfigUuid, paginationData, filterData)} } @@ -5016,7 +5017,7 @@ type MockSnapshotDao_ListByTemplate_Call struct { // - template api.TemplateResponse // - repositorySearch string // - paginationData api.PaginationData -func (_e *MockSnapshotDao_Expecter) ListByTemplate(ctx interface{}, orgID interface{}, template interface{}, repositorySearch interface{}, paginationData interface{}) *MockSnapshotDao_ListByTemplate_Call { +func (_e *MockSnapshotDao_Expecter) ListByTemplate(ctx any, orgID any, template any, repositorySearch any, paginationData any) *MockSnapshotDao_ListByTemplate_Call { return &MockSnapshotDao_ListByTemplate_Call{Call: _e.mock.On("ListByTemplate", ctx, orgID, template, repositorySearch, paginationData)} } @@ -5097,7 +5098,7 @@ type MockSnapshotDao_SetDetectedOSVersion_Call struct { // SetDetectedOSVersion is a helper method to define mock.On call // - ctx context.Context // - uuid string -func (_e *MockSnapshotDao_Expecter) SetDetectedOSVersion(ctx interface{}, uuid interface{}) *MockSnapshotDao_SetDetectedOSVersion_Call { +func (_e *MockSnapshotDao_Expecter) SetDetectedOSVersion(ctx any, uuid any) *MockSnapshotDao_SetDetectedOSVersion_Call { return &MockSnapshotDao_SetDetectedOSVersion_Call{Call: _e.mock.On("SetDetectedOSVersion", ctx, uuid)} } @@ -5154,7 +5155,7 @@ type MockSnapshotDao_SoftDelete_Call struct { // SoftDelete is a helper method to define mock.On call // - ctx context.Context // - snapUUID string -func (_e *MockSnapshotDao_Expecter) SoftDelete(ctx interface{}, snapUUID interface{}) *MockSnapshotDao_SoftDelete_Call { +func (_e *MockSnapshotDao_Expecter) SoftDelete(ctx any, snapUUID any) *MockSnapshotDao_SoftDelete_Call { return &MockSnapshotDao_SoftDelete_Call{Call: _e.mock.On("SoftDelete", ctx, snapUUID)} } @@ -5223,7 +5224,7 @@ type MockSnapshotDao_UpdatePublishedStatus_Call struct { // - published bool // - repoConfigUUID string // - snapshotUUID string -func (_e *MockSnapshotDao_Expecter) UpdatePublishedStatus(ctx interface{}, orgID interface{}, published interface{}, repoConfigUUID interface{}, snapshotUUID interface{}) *MockSnapshotDao_UpdatePublishedStatus_Call { +func (_e *MockSnapshotDao_Expecter) UpdatePublishedStatus(ctx any, orgID any, published any, repoConfigUUID any, snapshotUUID any) *MockSnapshotDao_UpdatePublishedStatus_Call { return &MockSnapshotDao_UpdatePublishedStatus_Call{Call: _e.mock.On("UpdatePublishedStatus", ctx, orgID, published, repoConfigUUID, snapshotUUID)} } @@ -5321,7 +5322,7 @@ type MockMetricsDao_OrganizationTotal_Call struct { // OrganizationTotal is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) OrganizationTotal(ctx interface{}) *MockMetricsDao_OrganizationTotal_Call { +func (_e *MockMetricsDao_Expecter) OrganizationTotal(ctx any) *MockMetricsDao_OrganizationTotal_Call { return &MockMetricsDao_OrganizationTotal_Call{Call: _e.mock.On("OrganizationTotal", ctx)} } @@ -5372,7 +5373,7 @@ type MockMetricsDao_PendingTasksAverageLatency_Call struct { // PendingTasksAverageLatency is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) PendingTasksAverageLatency(ctx interface{}) *MockMetricsDao_PendingTasksAverageLatency_Call { +func (_e *MockMetricsDao_Expecter) PendingTasksAverageLatency(ctx any) *MockMetricsDao_PendingTasksAverageLatency_Call { return &MockMetricsDao_PendingTasksAverageLatency_Call{Call: _e.mock.On("PendingTasksAverageLatency", ctx)} } @@ -5423,7 +5424,7 @@ type MockMetricsDao_PendingTasksCount_Call struct { // PendingTasksCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) PendingTasksCount(ctx interface{}) *MockMetricsDao_PendingTasksCount_Call { +func (_e *MockMetricsDao_Expecter) PendingTasksCount(ctx any) *MockMetricsDao_PendingTasksCount_Call { return &MockMetricsDao_PendingTasksCount_Call{Call: _e.mock.On("PendingTasksCount", ctx)} } @@ -5474,7 +5475,7 @@ type MockMetricsDao_PendingTasksOldestTask_Call struct { // PendingTasksOldestTask is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) PendingTasksOldestTask(ctx interface{}) *MockMetricsDao_PendingTasksOldestTask_Call { +func (_e *MockMetricsDao_Expecter) PendingTasksOldestTask(ctx any) *MockMetricsDao_PendingTasksOldestTask_Call { return &MockMetricsDao_PendingTasksOldestTask_Call{Call: _e.mock.On("PendingTasksOldestTask", ctx)} } @@ -5525,7 +5526,7 @@ type MockMetricsDao_PublicRepositoriesFailedIntrospectionCount_Call struct { // PublicRepositoriesFailedIntrospectionCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) PublicRepositoriesFailedIntrospectionCount(ctx interface{}) *MockMetricsDao_PublicRepositoriesFailedIntrospectionCount_Call { +func (_e *MockMetricsDao_Expecter) PublicRepositoriesFailedIntrospectionCount(ctx any) *MockMetricsDao_PublicRepositoriesFailedIntrospectionCount_Call { return &MockMetricsDao_PublicRepositoriesFailedIntrospectionCount_Call{Call: _e.mock.On("PublicRepositoriesFailedIntrospectionCount", ctx)} } @@ -5576,7 +5577,7 @@ type MockMetricsDao_RHReposSnapshotNotCompletedInLast36HoursCount_Call struct { // RHReposSnapshotNotCompletedInLast36HoursCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) RHReposSnapshotNotCompletedInLast36HoursCount(ctx interface{}) *MockMetricsDao_RHReposSnapshotNotCompletedInLast36HoursCount_Call { +func (_e *MockMetricsDao_Expecter) RHReposSnapshotNotCompletedInLast36HoursCount(ctx any) *MockMetricsDao_RHReposSnapshotNotCompletedInLast36HoursCount_Call { return &MockMetricsDao_RHReposSnapshotNotCompletedInLast36HoursCount_Call{Call: _e.mock.On("RHReposSnapshotNotCompletedInLast36HoursCount", ctx)} } @@ -5627,7 +5628,7 @@ type MockMetricsDao_RepositoriesCount_Call struct { // RepositoriesCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) RepositoriesCount(ctx interface{}) *MockMetricsDao_RepositoriesCount_Call { +func (_e *MockMetricsDao_Expecter) RepositoriesCount(ctx any) *MockMetricsDao_RepositoriesCount_Call { return &MockMetricsDao_RepositoriesCount_Call{Call: _e.mock.On("RepositoriesCount", ctx)} } @@ -5680,7 +5681,7 @@ type MockMetricsDao_RepositoriesIntrospectionCount_Call struct { // - ctx context.Context // - hours int // - public bool -func (_e *MockMetricsDao_Expecter) RepositoriesIntrospectionCount(ctx interface{}, hours interface{}, public interface{}) *MockMetricsDao_RepositoriesIntrospectionCount_Call { +func (_e *MockMetricsDao_Expecter) RepositoriesIntrospectionCount(ctx any, hours any, public any) *MockMetricsDao_RepositoriesIntrospectionCount_Call { return &MockMetricsDao_RepositoriesIntrospectionCount_Call{Call: _e.mock.On("RepositoriesIntrospectionCount", ctx, hours, public)} } @@ -5741,7 +5742,7 @@ type MockMetricsDao_RepositoryConfigsCount_Call struct { // RepositoryConfigsCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) RepositoryConfigsCount(ctx interface{}) *MockMetricsDao_RepositoryConfigsCount_Call { +func (_e *MockMetricsDao_Expecter) RepositoryConfigsCount(ctx any) *MockMetricsDao_RepositoryConfigsCount_Call { return &MockMetricsDao_RepositoryConfigsCount_Call{Call: _e.mock.On("RepositoryConfigsCount", ctx)} } @@ -5794,7 +5795,7 @@ type MockMetricsDao_TaskPendingTimeAverageByType_Call struct { // TaskPendingTimeAverageByType is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) TaskPendingTimeAverageByType(ctx interface{}) *MockMetricsDao_TaskPendingTimeAverageByType_Call { +func (_e *MockMetricsDao_Expecter) TaskPendingTimeAverageByType(ctx any) *MockMetricsDao_TaskPendingTimeAverageByType_Call { return &MockMetricsDao_TaskPendingTimeAverageByType_Call{Call: _e.mock.On("TaskPendingTimeAverageByType", ctx)} } @@ -5845,7 +5846,7 @@ type MockMetricsDao_TemplatesAgeAverage_Call struct { // TemplatesAgeAverage is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) TemplatesAgeAverage(ctx interface{}) *MockMetricsDao_TemplatesAgeAverage_Call { +func (_e *MockMetricsDao_Expecter) TemplatesAgeAverage(ctx any) *MockMetricsDao_TemplatesAgeAverage_Call { return &MockMetricsDao_TemplatesAgeAverage_Call{Call: _e.mock.On("TemplatesAgeAverage", ctx)} } @@ -5896,7 +5897,7 @@ type MockMetricsDao_TemplatesUpdatedInLast24HoursCount_Call struct { // TemplatesUpdatedInLast24HoursCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) TemplatesUpdatedInLast24HoursCount(ctx interface{}) *MockMetricsDao_TemplatesUpdatedInLast24HoursCount_Call { +func (_e *MockMetricsDao_Expecter) TemplatesUpdatedInLast24HoursCount(ctx any) *MockMetricsDao_TemplatesUpdatedInLast24HoursCount_Call { return &MockMetricsDao_TemplatesUpdatedInLast24HoursCount_Call{Call: _e.mock.On("TemplatesUpdatedInLast24HoursCount", ctx)} } @@ -5947,7 +5948,7 @@ type MockMetricsDao_TemplatesUseDateCount_Call struct { // TemplatesUseDateCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) TemplatesUseDateCount(ctx interface{}) *MockMetricsDao_TemplatesUseDateCount_Call { +func (_e *MockMetricsDao_Expecter) TemplatesUseDateCount(ctx any) *MockMetricsDao_TemplatesUseDateCount_Call { return &MockMetricsDao_TemplatesUseDateCount_Call{Call: _e.mock.On("TemplatesUseDateCount", ctx)} } @@ -5998,7 +5999,7 @@ type MockMetricsDao_TemplatesUseLatestCount_Call struct { // TemplatesUseLatestCount is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMetricsDao_Expecter) TemplatesUseLatestCount(ctx interface{}) *MockMetricsDao_TemplatesUseLatestCount_Call { +func (_e *MockMetricsDao_Expecter) TemplatesUseLatestCount(ctx any) *MockMetricsDao_TemplatesUseLatestCount_Call { return &MockMetricsDao_TemplatesUseLatestCount_Call{Call: _e.mock.On("TemplatesUseLatestCount", ctx)} } @@ -6076,7 +6077,7 @@ type MockTaskInfoDao_Cleanup_Call struct { // Cleanup is a helper method to define mock.On call // - ctx context.Context -func (_e *MockTaskInfoDao_Expecter) Cleanup(ctx interface{}) *MockTaskInfoDao_Cleanup_Call { +func (_e *MockTaskInfoDao_Expecter) Cleanup(ctx any) *MockTaskInfoDao_Cleanup_Call { return &MockTaskInfoDao_Cleanup_Call{Call: _e.mock.On("Cleanup", ctx)} } @@ -6138,7 +6139,7 @@ type MockTaskInfoDao_Fetch_Call struct { // - ctx context.Context // - OrgID string // - id string -func (_e *MockTaskInfoDao_Expecter) Fetch(ctx interface{}, OrgID interface{}, id interface{}) *MockTaskInfoDao_Fetch_Call { +func (_e *MockTaskInfoDao_Expecter) Fetch(ctx any, OrgID any, id any) *MockTaskInfoDao_Fetch_Call { return &MockTaskInfoDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, OrgID, id)} } @@ -6178,11 +6179,11 @@ func (_c *MockTaskInfoDao_Fetch_Call) RunAndReturn(run func(ctx context.Context, // FetchActiveTasks provides a mock function for the type MockTaskInfoDao func (_mock *MockTaskInfoDao) FetchActiveTasks(ctx context.Context, orgID string, objectUUID string, taskTypes ...string) ([]string, error) { // string - _va := make([]interface{}, len(taskTypes)) + _va := make([]any, len(taskTypes)) for _i := range taskTypes { _va[_i] = taskTypes[_i] } - var _ca []interface{} + var _ca []any _ca = append(_ca, ctx, orgID, objectUUID) _ca = append(_ca, _va...) ret := _mock.Called(_ca...) @@ -6221,9 +6222,9 @@ type MockTaskInfoDao_FetchActiveTasks_Call struct { // - orgID string // - objectUUID string // - taskTypes ...string -func (_e *MockTaskInfoDao_Expecter) FetchActiveTasks(ctx interface{}, orgID interface{}, objectUUID interface{}, taskTypes ...interface{}) *MockTaskInfoDao_FetchActiveTasks_Call { +func (_e *MockTaskInfoDao_Expecter) FetchActiveTasks(ctx any, orgID any, objectUUID any, taskTypes ...any) *MockTaskInfoDao_FetchActiveTasks_Call { return &MockTaskInfoDao_FetchActiveTasks_Call{Call: _e.mock.On("FetchActiveTasks", - append([]interface{}{ctx, orgID, objectUUID}, taskTypes...)...)} + append([]any{ctx, orgID, objectUUID}, taskTypes...)...)} } func (_c *MockTaskInfoDao_FetchActiveTasks_Call) Run(run func(ctx context.Context, orgID string, objectUUID string, taskTypes ...string)) *MockTaskInfoDao_FetchActiveTasks_Call { @@ -6310,7 +6311,7 @@ type MockTaskInfoDao_List_Call struct { // - OrgID string // - pageData api.PaginationData // - filterData api.TaskInfoFilterData -func (_e *MockTaskInfoDao_Expecter) List(ctx interface{}, OrgID interface{}, pageData interface{}, filterData interface{}) *MockTaskInfoDao_List_Call { +func (_e *MockTaskInfoDao_Expecter) List(ctx any, OrgID any, pageData any, filterData any) *MockTaskInfoDao_List_Call { return &MockTaskInfoDao_List_Call{Call: _e.mock.On("List", ctx, OrgID, pageData, filterData)} } @@ -6413,7 +6414,7 @@ type MockAdminTaskDao_Fetch_Call struct { // Fetch is a helper method to define mock.On call // - ctx context.Context // - id string -func (_e *MockAdminTaskDao_Expecter) Fetch(ctx interface{}, id interface{}) *MockAdminTaskDao_Fetch_Call { +func (_e *MockAdminTaskDao_Expecter) Fetch(ctx any, id any) *MockAdminTaskDao_Fetch_Call { return &MockAdminTaskDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, id)} } @@ -6486,7 +6487,7 @@ type MockAdminTaskDao_List_Call struct { // - ctx context.Context // - pageData api.PaginationData // - filterData api.AdminTaskFilterData -func (_e *MockAdminTaskDao_Expecter) List(ctx interface{}, pageData interface{}, filterData interface{}) *MockAdminTaskDao_List_Call { +func (_e *MockAdminTaskDao_Expecter) List(ctx any, pageData any, filterData any) *MockAdminTaskDao_List_Call { return &MockAdminTaskDao_List_Call{Call: _e.mock.On("List", ctx, pageData, filterData)} } @@ -6576,7 +6577,7 @@ type MockDomainDao_Delete_Call struct { // - ctx context.Context // - orgId string // - domainName string -func (_e *MockDomainDao_Expecter) Delete(ctx interface{}, orgId interface{}, domainName interface{}) *MockDomainDao_Delete_Call { +func (_e *MockDomainDao_Expecter) Delete(ctx any, orgId any, domainName any) *MockDomainDao_Delete_Call { return &MockDomainDao_Delete_Call{Call: _e.mock.On("Delete", ctx, orgId, domainName)} } @@ -6647,7 +6648,7 @@ type MockDomainDao_Fetch_Call struct { // Fetch is a helper method to define mock.On call // - ctx context.Context // - orgId string -func (_e *MockDomainDao_Expecter) Fetch(ctx interface{}, orgId interface{}) *MockDomainDao_Fetch_Call { +func (_e *MockDomainDao_Expecter) Fetch(ctx any, orgId any) *MockDomainDao_Fetch_Call { return &MockDomainDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, orgId)} } @@ -6713,7 +6714,7 @@ type MockDomainDao_FetchOrCreateDomain_Call struct { // FetchOrCreateDomain is a helper method to define mock.On call // - ctx context.Context // - orgId string -func (_e *MockDomainDao_Expecter) FetchOrCreateDomain(ctx interface{}, orgId interface{}) *MockDomainDao_FetchOrCreateDomain_Call { +func (_e *MockDomainDao_Expecter) FetchOrCreateDomain(ctx any, orgId any) *MockDomainDao_FetchOrCreateDomain_Call { return &MockDomainDao_FetchOrCreateDomain_Call{Call: _e.mock.On("FetchOrCreateDomain", ctx, orgId)} } @@ -6780,7 +6781,7 @@ type MockDomainDao_List_Call struct { // List is a helper method to define mock.On call // - ctx context.Context -func (_e *MockDomainDao_Expecter) List(ctx interface{}) *MockDomainDao_List_Call { +func (_e *MockDomainDao_Expecter) List(ctx any) *MockDomainDao_List_Call { return &MockDomainDao_List_Call{Call: _e.mock.On("List", ctx)} } @@ -6869,7 +6870,7 @@ type MockPackageGroupDao_InsertForRepository_Call struct { // - ctx context.Context // - repoUuid string // - pkgGroups []yum.PackageGroup -func (_e *MockPackageGroupDao_Expecter) InsertForRepository(ctx interface{}, repoUuid interface{}, pkgGroups interface{}) *MockPackageGroupDao_InsertForRepository_Call { +func (_e *MockPackageGroupDao_Expecter) InsertForRepository(ctx any, repoUuid any, pkgGroups any) *MockPackageGroupDao_InsertForRepository_Call { return &MockPackageGroupDao_InsertForRepository_Call{Call: _e.mock.On("InsertForRepository", ctx, repoUuid, pkgGroups)} } @@ -6951,7 +6952,7 @@ type MockPackageGroupDao_List_Call struct { // - offset int // - search string // - sortBy string -func (_e *MockPackageGroupDao_Expecter) List(ctx interface{}, orgID interface{}, uuidRepo interface{}, limit interface{}, offset interface{}, search interface{}, sortBy interface{}) *MockPackageGroupDao_List_Call { +func (_e *MockPackageGroupDao_Expecter) List(ctx any, orgID any, uuidRepo any, limit any, offset any, search any, sortBy any) *MockPackageGroupDao_List_Call { return &MockPackageGroupDao_List_Call{Call: _e.mock.On("List", ctx, orgID, uuidRepo, limit, offset, search, sortBy)} } @@ -7032,7 +7033,7 @@ type MockPackageGroupDao_OrphanCleanup_Call struct { // OrphanCleanup is a helper method to define mock.On call // - ctx context.Context -func (_e *MockPackageGroupDao_Expecter) OrphanCleanup(ctx interface{}) *MockPackageGroupDao_OrphanCleanup_Call { +func (_e *MockPackageGroupDao_Expecter) OrphanCleanup(ctx any) *MockPackageGroupDao_OrphanCleanup_Call { return &MockPackageGroupDao_OrphanCleanup_Call{Call: _e.mock.On("OrphanCleanup", ctx)} } @@ -7096,7 +7097,7 @@ type MockPackageGroupDao_Search_Call struct { // - ctx context.Context // - orgID string // - request api.ContentUnitSearchRequest -func (_e *MockPackageGroupDao_Expecter) Search(ctx interface{}, orgID interface{}, request interface{}) *MockPackageGroupDao_Search_Call { +func (_e *MockPackageGroupDao_Expecter) Search(ctx any, orgID any, request any) *MockPackageGroupDao_Search_Call { return &MockPackageGroupDao_Search_Call{Call: _e.mock.On("Search", ctx, orgID, request)} } @@ -7170,7 +7171,7 @@ type MockPackageGroupDao_SearchSnapshotPackageGroups_Call struct { // - ctx context.Context // - orgId string // - request api.SnapshotSearchRpmRequest -func (_e *MockPackageGroupDao_Expecter) SearchSnapshotPackageGroups(ctx interface{}, orgId interface{}, request interface{}) *MockPackageGroupDao_SearchSnapshotPackageGroups_Call { +func (_e *MockPackageGroupDao_Expecter) SearchSnapshotPackageGroups(ctx any, orgId any, request any) *MockPackageGroupDao_SearchSnapshotPackageGroups_Call { return &MockPackageGroupDao_SearchSnapshotPackageGroups_Call{Call: _e.mock.On("SearchSnapshotPackageGroups", ctx, orgId, request)} } @@ -7269,7 +7270,7 @@ type MockEnvironmentDao_InsertForRepository_Call struct { // - ctx context.Context // - repoUuid string // - environments []yum.Environment -func (_e *MockEnvironmentDao_Expecter) InsertForRepository(ctx interface{}, repoUuid interface{}, environments interface{}) *MockEnvironmentDao_InsertForRepository_Call { +func (_e *MockEnvironmentDao_Expecter) InsertForRepository(ctx any, repoUuid any, environments any) *MockEnvironmentDao_InsertForRepository_Call { return &MockEnvironmentDao_InsertForRepository_Call{Call: _e.mock.On("InsertForRepository", ctx, repoUuid, environments)} } @@ -7351,7 +7352,7 @@ type MockEnvironmentDao_List_Call struct { // - offset int // - search string // - sortBy string -func (_e *MockEnvironmentDao_Expecter) List(ctx interface{}, orgID interface{}, uuidRepo interface{}, limit interface{}, offset interface{}, search interface{}, sortBy interface{}) *MockEnvironmentDao_List_Call { +func (_e *MockEnvironmentDao_Expecter) List(ctx any, orgID any, uuidRepo any, limit any, offset any, search any, sortBy any) *MockEnvironmentDao_List_Call { return &MockEnvironmentDao_List_Call{Call: _e.mock.On("List", ctx, orgID, uuidRepo, limit, offset, search, sortBy)} } @@ -7432,7 +7433,7 @@ type MockEnvironmentDao_OrphanCleanup_Call struct { // OrphanCleanup is a helper method to define mock.On call // - ctx context.Context -func (_e *MockEnvironmentDao_Expecter) OrphanCleanup(ctx interface{}) *MockEnvironmentDao_OrphanCleanup_Call { +func (_e *MockEnvironmentDao_Expecter) OrphanCleanup(ctx any) *MockEnvironmentDao_OrphanCleanup_Call { return &MockEnvironmentDao_OrphanCleanup_Call{Call: _e.mock.On("OrphanCleanup", ctx)} } @@ -7496,7 +7497,7 @@ type MockEnvironmentDao_Search_Call struct { // - ctx context.Context // - orgID string // - request api.ContentUnitSearchRequest -func (_e *MockEnvironmentDao_Expecter) Search(ctx interface{}, orgID interface{}, request interface{}) *MockEnvironmentDao_Search_Call { +func (_e *MockEnvironmentDao_Expecter) Search(ctx any, orgID any, request any) *MockEnvironmentDao_Search_Call { return &MockEnvironmentDao_Search_Call{Call: _e.mock.On("Search", ctx, orgID, request)} } @@ -7570,7 +7571,7 @@ type MockEnvironmentDao_SearchSnapshotEnvironments_Call struct { // - ctx context.Context // - orgId string // - request api.SnapshotSearchRpmRequest -func (_e *MockEnvironmentDao_Expecter) SearchSnapshotEnvironments(ctx interface{}, orgId interface{}, request interface{}) *MockEnvironmentDao_SearchSnapshotEnvironments_Call { +func (_e *MockEnvironmentDao_Expecter) SearchSnapshotEnvironments(ctx any, orgId any, request any) *MockEnvironmentDao_SearchSnapshotEnvironments_Call { return &MockEnvironmentDao_SearchSnapshotEnvironments_Call{Call: _e.mock.On("SearchSnapshotEnvironments", ctx, orgId, request)} } @@ -7660,7 +7661,7 @@ type MockTemplateDao_ClearDeletedAt_Call struct { // - ctx context.Context // - orgID string // - uuid string -func (_e *MockTemplateDao_Expecter) ClearDeletedAt(ctx interface{}, orgID interface{}, uuid interface{}) *MockTemplateDao_ClearDeletedAt_Call { +func (_e *MockTemplateDao_Expecter) ClearDeletedAt(ctx any, orgID any, uuid any) *MockTemplateDao_ClearDeletedAt_Call { return &MockTemplateDao_ClearDeletedAt_Call{Call: _e.mock.On("ClearDeletedAt", ctx, orgID, uuid)} } @@ -7731,7 +7732,7 @@ type MockTemplateDao_Create_Call struct { // Create is a helper method to define mock.On call // - ctx context.Context // - templateRequest api.TemplateRequest -func (_e *MockTemplateDao_Expecter) Create(ctx interface{}, templateRequest interface{}) *MockTemplateDao_Create_Call { +func (_e *MockTemplateDao_Expecter) Create(ctx any, templateRequest any) *MockTemplateDao_Create_Call { return &MockTemplateDao_Create_Call{Call: _e.mock.On("Create", ctx, templateRequest)} } @@ -7789,7 +7790,7 @@ type MockTemplateDao_Delete_Call struct { // - ctx context.Context // - orgID string // - uuid string -func (_e *MockTemplateDao_Expecter) Delete(ctx interface{}, orgID interface{}, uuid interface{}) *MockTemplateDao_Delete_Call { +func (_e *MockTemplateDao_Expecter) Delete(ctx any, orgID any, uuid any) *MockTemplateDao_Delete_Call { return &MockTemplateDao_Delete_Call{Call: _e.mock.On("Delete", ctx, orgID, uuid)} } @@ -7852,7 +7853,7 @@ type MockTemplateDao_DeleteTemplateRepoConfigs_Call struct { // - ctx context.Context // - templateUUID string // - keepRepoConfigUUIDs []string -func (_e *MockTemplateDao_Expecter) DeleteTemplateRepoConfigs(ctx interface{}, templateUUID interface{}, keepRepoConfigUUIDs interface{}) *MockTemplateDao_DeleteTemplateRepoConfigs_Call { +func (_e *MockTemplateDao_Expecter) DeleteTemplateRepoConfigs(ctx any, templateUUID any, keepRepoConfigUUIDs any) *MockTemplateDao_DeleteTemplateRepoConfigs_Call { return &MockTemplateDao_DeleteTemplateRepoConfigs_Call{Call: _e.mock.On("DeleteTemplateRepoConfigs", ctx, templateUUID, keepRepoConfigUUIDs)} } @@ -7914,7 +7915,7 @@ type MockTemplateDao_DeleteTemplateSnapshot_Call struct { // DeleteTemplateSnapshot is a helper method to define mock.On call // - ctx context.Context // - snapshotUUID string -func (_e *MockTemplateDao_Expecter) DeleteTemplateSnapshot(ctx interface{}, snapshotUUID interface{}) *MockTemplateDao_DeleteTemplateSnapshot_Call { +func (_e *MockTemplateDao_Expecter) DeleteTemplateSnapshot(ctx any, snapshotUUID any) *MockTemplateDao_DeleteTemplateSnapshot_Call { return &MockTemplateDao_DeleteTemplateSnapshot_Call{Call: _e.mock.On("DeleteTemplateSnapshot", ctx, snapshotUUID)} } @@ -7982,7 +7983,7 @@ type MockTemplateDao_Fetch_Call struct { // - orgID string // - uuid string // - includeSoftDel bool -func (_e *MockTemplateDao_Expecter) Fetch(ctx interface{}, orgID interface{}, uuid interface{}, includeSoftDel interface{}) *MockTemplateDao_Fetch_Call { +func (_e *MockTemplateDao_Expecter) Fetch(ctx any, orgID any, uuid any, includeSoftDel any) *MockTemplateDao_Fetch_Call { return &MockTemplateDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, orgID, uuid, includeSoftDel)} } @@ -8061,7 +8062,7 @@ type MockTemplateDao_GetDistributionHref_Call struct { // - ctx context.Context // - templateUUID string // - repoConfigUUID string -func (_e *MockTemplateDao_Expecter) GetDistributionHref(ctx interface{}, templateUUID interface{}, repoConfigUUID interface{}) *MockTemplateDao_GetDistributionHref_Call { +func (_e *MockTemplateDao_Expecter) GetDistributionHref(ctx any, templateUUID any, repoConfigUUID any) *MockTemplateDao_GetDistributionHref_Call { return &MockTemplateDao_GetDistributionHref_Call{Call: _e.mock.On("GetDistributionHref", ctx, templateUUID, repoConfigUUID)} } @@ -8159,7 +8160,7 @@ type MockTemplateDao_GetRepoChanges_Call struct { // - ctx context.Context // - templateUUID string // - newRepoConfigUUIDs []string -func (_e *MockTemplateDao_Expecter) GetRepoChanges(ctx interface{}, templateUUID interface{}, newRepoConfigUUIDs interface{}) *MockTemplateDao_GetRepoChanges_Call { +func (_e *MockTemplateDao_Expecter) GetRepoChanges(ctx any, templateUUID any, newRepoConfigUUIDs any) *MockTemplateDao_GetRepoChanges_Call { return &MockTemplateDao_GetRepoChanges_Call{Call: _e.mock.On("GetRepoChanges", ctx, templateUUID, newRepoConfigUUIDs)} } @@ -8231,7 +8232,7 @@ type MockTemplateDao_GetRepositoryConfigurationFile_Call struct { // - ctx context.Context // - orgID string // - templateUUID string -func (_e *MockTemplateDao_Expecter) GetRepositoryConfigurationFile(ctx interface{}, orgID interface{}, templateUUID interface{}) *MockTemplateDao_GetRepositoryConfigurationFile_Call { +func (_e *MockTemplateDao_Expecter) GetRepositoryConfigurationFile(ctx any, orgID any, templateUUID any) *MockTemplateDao_GetRepositoryConfigurationFile_Call { return &MockTemplateDao_GetRepositoryConfigurationFile_Call{Call: _e.mock.On("GetRepositoryConfigurationFile", ctx, orgID, templateUUID)} } @@ -8302,7 +8303,7 @@ type MockTemplateDao_InternalOnlyFetchByName_Call struct { // InternalOnlyFetchByName is a helper method to define mock.On call // - ctx context.Context // - name string -func (_e *MockTemplateDao_Expecter) InternalOnlyFetchByName(ctx interface{}, name interface{}) *MockTemplateDao_InternalOnlyFetchByName_Call { +func (_e *MockTemplateDao_Expecter) InternalOnlyFetchByName(ctx any, name any) *MockTemplateDao_InternalOnlyFetchByName_Call { return &MockTemplateDao_InternalOnlyFetchByName_Call{Call: _e.mock.On("InternalOnlyFetchByName", ctx, name)} } @@ -8371,7 +8372,7 @@ type MockTemplateDao_InternalOnlyGetTemplatesForRepoConfig_Call struct { // - ctx context.Context // - repoUUID string // - useLatestOnly bool -func (_e *MockTemplateDao_Expecter) InternalOnlyGetTemplatesForRepoConfig(ctx interface{}, repoUUID interface{}, useLatestOnly interface{}) *MockTemplateDao_InternalOnlyGetTemplatesForRepoConfig_Call { +func (_e *MockTemplateDao_Expecter) InternalOnlyGetTemplatesForRepoConfig(ctx any, repoUUID any, useLatestOnly any) *MockTemplateDao_InternalOnlyGetTemplatesForRepoConfig_Call { return &MockTemplateDao_InternalOnlyGetTemplatesForRepoConfig_Call{Call: _e.mock.On("InternalOnlyGetTemplatesForRepoConfig", ctx, repoUUID, useLatestOnly)} } @@ -8444,7 +8445,7 @@ type MockTemplateDao_InternalOnlyGetTemplatesForSnapshots_Call struct { // InternalOnlyGetTemplatesForSnapshots is a helper method to define mock.On call // - ctx context.Context // - snapUUIDs []string -func (_e *MockTemplateDao_Expecter) InternalOnlyGetTemplatesForSnapshots(ctx interface{}, snapUUIDs interface{}) *MockTemplateDao_InternalOnlyGetTemplatesForSnapshots_Call { +func (_e *MockTemplateDao_Expecter) InternalOnlyGetTemplatesForSnapshots(ctx any, snapUUIDs any) *MockTemplateDao_InternalOnlyGetTemplatesForSnapshots_Call { return &MockTemplateDao_InternalOnlyGetTemplatesForSnapshots_Call{Call: _e.mock.On("InternalOnlyGetTemplatesForSnapshots", ctx, snapUUIDs)} } @@ -8519,7 +8520,7 @@ type MockTemplateDao_List_Call struct { // - includeSoftDel bool // - paginationData api.PaginationData // - filterData api.TemplateFilterData -func (_e *MockTemplateDao_Expecter) List(ctx interface{}, orgID interface{}, includeSoftDel interface{}, paginationData interface{}, filterData interface{}) *MockTemplateDao_List_Call { +func (_e *MockTemplateDao_Expecter) List(ctx any, orgID any, includeSoftDel any, paginationData any, filterData any) *MockTemplateDao_List_Call { return &MockTemplateDao_List_Call{Call: _e.mock.On("List", ctx, orgID, includeSoftDel, paginationData, filterData)} } @@ -8591,7 +8592,7 @@ type MockTemplateDao_SetEnvironmentCreated_Call struct { // SetEnvironmentCreated is a helper method to define mock.On call // - ctx context.Context // - templateUUID string -func (_e *MockTemplateDao_Expecter) SetEnvironmentCreated(ctx interface{}, templateUUID interface{}) *MockTemplateDao_SetEnvironmentCreated_Call { +func (_e *MockTemplateDao_Expecter) SetEnvironmentCreated(ctx any, templateUUID any) *MockTemplateDao_SetEnvironmentCreated_Call { return &MockTemplateDao_SetEnvironmentCreated_Call{Call: _e.mock.On("SetEnvironmentCreated", ctx, templateUUID)} } @@ -8649,7 +8650,7 @@ type MockTemplateDao_SoftDelete_Call struct { // - ctx context.Context // - orgID string // - uuid string -func (_e *MockTemplateDao_Expecter) SoftDelete(ctx interface{}, orgID interface{}, uuid interface{}) *MockTemplateDao_SoftDelete_Call { +func (_e *MockTemplateDao_Expecter) SoftDelete(ctx any, orgID any, uuid any) *MockTemplateDao_SoftDelete_Call { return &MockTemplateDao_SoftDelete_Call{Call: _e.mock.On("SoftDelete", ctx, orgID, uuid)} } @@ -8722,7 +8723,7 @@ type MockTemplateDao_Update_Call struct { // - orgID string // - uuid string // - templParams api.TemplateUpdateRequest -func (_e *MockTemplateDao_Expecter) Update(ctx interface{}, orgID interface{}, uuid interface{}, templParams interface{}) *MockTemplateDao_Update_Call { +func (_e *MockTemplateDao_Expecter) Update(ctx any, orgID any, uuid any, templParams any) *MockTemplateDao_Update_Call { return &MockTemplateDao_Update_Call{Call: _e.mock.On("Update", ctx, orgID, uuid, templParams)} } @@ -8792,7 +8793,7 @@ type MockTemplateDao_UpdateDistributionHrefs_Call struct { // - repoUUIDs []string // - snapshots []models.Snapshot // - repoDistributionMap map[string]string -func (_e *MockTemplateDao_Expecter) UpdateDistributionHrefs(ctx interface{}, templateUUID interface{}, repoUUIDs interface{}, snapshots interface{}, repoDistributionMap interface{}) *MockTemplateDao_UpdateDistributionHrefs_Call { +func (_e *MockTemplateDao_Expecter) UpdateDistributionHrefs(ctx any, templateUUID any, repoUUIDs any, snapshots any, repoDistributionMap any) *MockTemplateDao_UpdateDistributionHrefs_Call { return &MockTemplateDao_UpdateDistributionHrefs_Call{Call: _e.mock.On("UpdateDistributionHrefs", ctx, templateUUID, repoUUIDs, snapshots, repoDistributionMap)} } @@ -8866,7 +8867,7 @@ type MockTemplateDao_UpdateLastError_Call struct { // - orgID string // - templateUUID string // - lastUpdateSnapshotError string -func (_e *MockTemplateDao_Expecter) UpdateLastError(ctx interface{}, orgID interface{}, templateUUID interface{}, lastUpdateSnapshotError interface{}) *MockTemplateDao_UpdateLastError_Call { +func (_e *MockTemplateDao_Expecter) UpdateLastError(ctx any, orgID any, templateUUID any, lastUpdateSnapshotError any) *MockTemplateDao_UpdateLastError_Call { return &MockTemplateDao_UpdateLastError_Call{Call: _e.mock.On("UpdateLastError", ctx, orgID, templateUUID, lastUpdateSnapshotError)} } @@ -8935,7 +8936,7 @@ type MockTemplateDao_UpdateLastUpdateTask_Call struct { // - taskUUID string // - orgID string // - templateUUID string -func (_e *MockTemplateDao_Expecter) UpdateLastUpdateTask(ctx interface{}, taskUUID interface{}, orgID interface{}, templateUUID interface{}) *MockTemplateDao_UpdateLastUpdateTask_Call { +func (_e *MockTemplateDao_Expecter) UpdateLastUpdateTask(ctx any, taskUUID any, orgID any, templateUUID any) *MockTemplateDao_UpdateLastUpdateTask_Call { return &MockTemplateDao_UpdateLastUpdateTask_Call{Call: _e.mock.On("UpdateLastUpdateTask", ctx, taskUUID, orgID, templateUUID)} } @@ -9004,7 +9005,7 @@ type MockTemplateDao_UpdateSnapshots_Call struct { // - templateUUID string // - repoUUIDs []string // - snapshots []models.Snapshot -func (_e *MockTemplateDao_Expecter) UpdateSnapshots(ctx interface{}, templateUUID interface{}, repoUUIDs interface{}, snapshots interface{}) *MockTemplateDao_UpdateSnapshots_Call { +func (_e *MockTemplateDao_Expecter) UpdateSnapshots(ctx any, templateUUID any, repoUUIDs any, snapshots any) *MockTemplateDao_UpdateSnapshots_Call { return &MockTemplateDao_UpdateSnapshots_Call{Call: _e.mock.On("UpdateSnapshots", ctx, templateUUID, repoUUIDs, snapshots)} } @@ -9106,7 +9107,7 @@ type MockMemoDao_GetLastSuccessfulPulpLogDate_Call struct { // GetLastSuccessfulPulpLogDate is a helper method to define mock.On call // - ctx context.Context -func (_e *MockMemoDao_Expecter) GetLastSuccessfulPulpLogDate(ctx interface{}) *MockMemoDao_GetLastSuccessfulPulpLogDate_Call { +func (_e *MockMemoDao_Expecter) GetLastSuccessfulPulpLogDate(ctx any) *MockMemoDao_GetLastSuccessfulPulpLogDate_Call { return &MockMemoDao_GetLastSuccessfulPulpLogDate_Call{Call: _e.mock.On("GetLastSuccessfulPulpLogDate", ctx)} } @@ -9169,7 +9170,7 @@ type MockMemoDao_Read_Call struct { // Read is a helper method to define mock.On call // - ctx context.Context // - key string -func (_e *MockMemoDao_Expecter) Read(ctx interface{}, key interface{}) *MockMemoDao_Read_Call { +func (_e *MockMemoDao_Expecter) Read(ctx any, key any) *MockMemoDao_Read_Call { return &MockMemoDao_Read_Call{Call: _e.mock.On("Read", ctx, key)} } @@ -9226,7 +9227,7 @@ type MockMemoDao_SaveLastSuccessfulPulpLogDate_Call struct { // SaveLastSuccessfulPulpLogDate is a helper method to define mock.On call // - ctx context.Context // - date time.Time -func (_e *MockMemoDao_Expecter) SaveLastSuccessfulPulpLogDate(ctx interface{}, date interface{}) *MockMemoDao_SaveLastSuccessfulPulpLogDate_Call { +func (_e *MockMemoDao_Expecter) SaveLastSuccessfulPulpLogDate(ctx any, date any) *MockMemoDao_SaveLastSuccessfulPulpLogDate_Call { return &MockMemoDao_SaveLastSuccessfulPulpLogDate_Call{Call: _e.mock.On("SaveLastSuccessfulPulpLogDate", ctx, date)} } @@ -9295,7 +9296,7 @@ type MockMemoDao_Write_Call struct { // - ctx context.Context // - key string // - memo json.RawMessage -func (_e *MockMemoDao_Expecter) Write(ctx interface{}, key interface{}, memo interface{}) *MockMemoDao_Write_Call { +func (_e *MockMemoDao_Expecter) Write(ctx any, key any, memo any) *MockMemoDao_Write_Call { return &MockMemoDao_Write_Call{Call: _e.mock.On("Write", ctx, key, memo)} } @@ -9384,7 +9385,7 @@ type MockMavenPackagesDao_Create_Call struct { // Create is a helper method to define mock.On call // - ctx context.Context // - mavenPackage *models.MavenPackage -func (_e *MockMavenPackagesDao_Expecter) Create(ctx interface{}, mavenPackage interface{}) *MockMavenPackagesDao_Create_Call { +func (_e *MockMavenPackagesDao_Expecter) Create(ctx any, mavenPackage any) *MockMavenPackagesDao_Create_Call { return &MockMavenPackagesDao_Create_Call{Call: _e.mock.On("Create", ctx, mavenPackage)} } @@ -9453,7 +9454,7 @@ type MockMavenPackagesDao_Fetch_Call struct { // - ctx context.Context // - groupID string // - name string -func (_e *MockMavenPackagesDao_Expecter) Fetch(ctx interface{}, groupID interface{}, name interface{}) *MockMavenPackagesDao_Fetch_Call { +func (_e *MockMavenPackagesDao_Expecter) Fetch(ctx any, groupID any, name any) *MockMavenPackagesDao_Fetch_Call { return &MockMavenPackagesDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, groupID, name)} } @@ -9517,6 +9518,214 @@ func (_m *MockLightwellAdvisoryDao) EXPECT() *MockLightwellAdvisoryDao_Expecter return &MockLightwellAdvisoryDao_Expecter{mock: &_m.Mock} } +// CountAdvisoriesByRepo provides a mock function for the type MockLightwellAdvisoryDao +func (_mock *MockLightwellAdvisoryDao) CountAdvisoriesByRepo(ctx context.Context, repoConfigUUID uuid.UUID) (int64, error) { + ret := _mock.Called(ctx, repoConfigUUID) + + if len(ret) == 0 { + panic("no return value specified for CountAdvisoriesByRepo") + } + + var r0 int64 + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID) (int64, error)); ok { + return returnFunc(ctx, repoConfigUUID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, uuid.UUID) int64); ok { + r0 = returnFunc(ctx, repoConfigUUID) + } else { + r0 = ret.Get(0).(int64) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { + r1 = returnFunc(ctx, repoConfigUUID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CountAdvisoriesByRepo' +type MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call struct { + *mock.Call +} + +// CountAdvisoriesByRepo is a helper method to define mock.On call +// - ctx context.Context +// - repoConfigUUID uuid.UUID +func (_e *MockLightwellAdvisoryDao_Expecter) CountAdvisoriesByRepo(ctx any, repoConfigUUID any) *MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call { + return &MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call{Call: _e.mock.On("CountAdvisoriesByRepo", ctx, repoConfigUUID)} +} + +func (_c *MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call) Run(run func(ctx context.Context, repoConfigUUID uuid.UUID)) *MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 uuid.UUID + if args[1] != nil { + arg1 = args[1].(uuid.UUID) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call) Return(n int64, err error) *MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call { + _c.Call.Return(n, err) + return _c +} + +func (_c *MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call) RunAndReturn(run func(ctx context.Context, repoConfigUUID uuid.UUID) (int64, error)) *MockLightwellAdvisoryDao_CountAdvisoriesByRepo_Call { + _c.Call.Return(run) + return _c +} + +// ListAdvisories provides a mock function for the type MockLightwellAdvisoryDao +func (_mock *MockLightwellAdvisoryDao) ListAdvisories(ctx context.Context, opts ListLightwellAdvisoriesOptions) ([]api.LightwellAdvisoryResponse, int64, error) { + ret := _mock.Called(ctx, opts) + + if len(ret) == 0 { + panic("no return value specified for ListAdvisories") + } + + var r0 []api.LightwellAdvisoryResponse + var r1 int64 + var r2 error + if returnFunc, ok := ret.Get(0).(func(context.Context, ListLightwellAdvisoriesOptions) ([]api.LightwellAdvisoryResponse, int64, error)); ok { + return returnFunc(ctx, opts) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, ListLightwellAdvisoriesOptions) []api.LightwellAdvisoryResponse); ok { + r0 = returnFunc(ctx, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]api.LightwellAdvisoryResponse) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, ListLightwellAdvisoriesOptions) int64); ok { + r1 = returnFunc(ctx, opts) + } else { + r1 = ret.Get(1).(int64) + } + if returnFunc, ok := ret.Get(2).(func(context.Context, ListLightwellAdvisoriesOptions) error); ok { + r2 = returnFunc(ctx, opts) + } else { + r2 = ret.Error(2) + } + return r0, r1, r2 +} + +// MockLightwellAdvisoryDao_ListAdvisories_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAdvisories' +type MockLightwellAdvisoryDao_ListAdvisories_Call struct { + *mock.Call +} + +// ListAdvisories is a helper method to define mock.On call +// - ctx context.Context +// - opts ListLightwellAdvisoriesOptions +func (_e *MockLightwellAdvisoryDao_Expecter) ListAdvisories(ctx any, opts any) *MockLightwellAdvisoryDao_ListAdvisories_Call { + return &MockLightwellAdvisoryDao_ListAdvisories_Call{Call: _e.mock.On("ListAdvisories", ctx, opts)} +} + +func (_c *MockLightwellAdvisoryDao_ListAdvisories_Call) Run(run func(ctx context.Context, opts ListLightwellAdvisoriesOptions)) *MockLightwellAdvisoryDao_ListAdvisories_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 ListLightwellAdvisoriesOptions + if args[1] != nil { + arg1 = args[1].(ListLightwellAdvisoriesOptions) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockLightwellAdvisoryDao_ListAdvisories_Call) Return(lightwellAdvisoryResponses []api.LightwellAdvisoryResponse, n int64, err error) *MockLightwellAdvisoryDao_ListAdvisories_Call { + _c.Call.Return(lightwellAdvisoryResponses, n, err) + return _c +} + +func (_c *MockLightwellAdvisoryDao_ListAdvisories_Call) RunAndReturn(run func(ctx context.Context, opts ListLightwellAdvisoriesOptions) ([]api.LightwellAdvisoryResponse, int64, error)) *MockLightwellAdvisoryDao_ListAdvisories_Call { + _c.Call.Return(run) + return _c +} + +// ListAdvisoriesByCveID provides a mock function for the type MockLightwellAdvisoryDao +func (_mock *MockLightwellAdvisoryDao) ListAdvisoriesByCveID(ctx context.Context, cveID string) ([]LightwellAdvisoryCveMatch, error) { + ret := _mock.Called(ctx, cveID) + + if len(ret) == 0 { + panic("no return value specified for ListAdvisoriesByCveID") + } + + var r0 []LightwellAdvisoryCveMatch + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string) ([]LightwellAdvisoryCveMatch, error)); ok { + return returnFunc(ctx, cveID) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string) []LightwellAdvisoryCveMatch); ok { + r0 = returnFunc(ctx, cveID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]LightwellAdvisoryCveMatch) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = returnFunc(ctx, cveID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAdvisoriesByCveID' +type MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call struct { + *mock.Call +} + +// ListAdvisoriesByCveID is a helper method to define mock.On call +// - ctx context.Context +// - cveID string +func (_e *MockLightwellAdvisoryDao_Expecter) ListAdvisoriesByCveID(ctx any, cveID any) *MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call { + return &MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call{Call: _e.mock.On("ListAdvisoriesByCveID", ctx, cveID)} +} + +func (_c *MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call) Run(run func(ctx context.Context, cveID string)) *MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call) Return(lightwellAdvisoryCveMatchs []LightwellAdvisoryCveMatch, err error) *MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call { + _c.Call.Return(lightwellAdvisoryCveMatchs, err) + return _c +} + +func (_c *MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call) RunAndReturn(run func(ctx context.Context, cveID string) ([]LightwellAdvisoryCveMatch, error)) *MockLightwellAdvisoryDao_ListAdvisoriesByCveID_Call { + _c.Call.Return(run) + return _c +} + // ListByRepository provides a mock function for the type MockLightwellAdvisoryDao func (_mock *MockLightwellAdvisoryDao) ListByRepository(ctx context.Context, repoConfigUUID string) ([]LightwellAdvisoryInput, error) { ret := _mock.Called(ctx, repoConfigUUID) @@ -9553,7 +9762,7 @@ type MockLightwellAdvisoryDao_ListByRepository_Call struct { // ListByRepository is a helper method to define mock.On call // - ctx context.Context // - repoConfigUUID string -func (_e *MockLightwellAdvisoryDao_Expecter) ListByRepository(ctx interface{}, repoConfigUUID interface{}) *MockLightwellAdvisoryDao_ListByRepository_Call { +func (_e *MockLightwellAdvisoryDao_Expecter) ListByRepository(ctx any, repoConfigUUID any) *MockLightwellAdvisoryDao_ListByRepository_Call { return &MockLightwellAdvisoryDao_ListByRepository_Call{Call: _e.mock.On("ListByRepository", ctx, repoConfigUUID)} } @@ -9622,7 +9831,7 @@ type MockLightwellAdvisoryDao_ListUnnotifiedAdvisories_Call struct { // - ctx context.Context // - repoConfigUUID string // - orgID string -func (_e *MockLightwellAdvisoryDao_Expecter) ListUnnotifiedAdvisories(ctx interface{}, repoConfigUUID interface{}, orgID interface{}) *MockLightwellAdvisoryDao_ListUnnotifiedAdvisories_Call { +func (_e *MockLightwellAdvisoryDao_Expecter) ListUnnotifiedAdvisories(ctx any, repoConfigUUID any, orgID any) *MockLightwellAdvisoryDao_ListUnnotifiedAdvisories_Call { return &MockLightwellAdvisoryDao_ListUnnotifiedAdvisories_Call{Call: _e.mock.On("ListUnnotifiedAdvisories", ctx, repoConfigUUID, orgID)} } @@ -9686,7 +9895,7 @@ type MockLightwellAdvisoryDao_MarkAsNotified_Call struct { // - repoConfigUUID string // - orgID string // - data []LightwellNotificationData -func (_e *MockLightwellAdvisoryDao_Expecter) MarkAsNotified(ctx interface{}, repoConfigUUID interface{}, orgID interface{}, data interface{}) *MockLightwellAdvisoryDao_MarkAsNotified_Call { +func (_e *MockLightwellAdvisoryDao_Expecter) MarkAsNotified(ctx any, repoConfigUUID any, orgID any, data any) *MockLightwellAdvisoryDao_MarkAsNotified_Call { return &MockLightwellAdvisoryDao_MarkAsNotified_Call{Call: _e.mock.On("MarkAsNotified", ctx, repoConfigUUID, orgID, data)} } @@ -9755,7 +9964,7 @@ type MockLightwellAdvisoryDao_SyncForRepository_Call struct { // - repoConfigUUID string // - repoName string // - advisories []LightwellAdvisoryInput -func (_e *MockLightwellAdvisoryDao_Expecter) SyncForRepository(ctx interface{}, repoConfigUUID interface{}, repoName interface{}, advisories interface{}) *MockLightwellAdvisoryDao_SyncForRepository_Call { +func (_e *MockLightwellAdvisoryDao_Expecter) SyncForRepository(ctx any, repoConfigUUID any, repoName any, advisories any) *MockLightwellAdvisoryDao_SyncForRepository_Call { return &MockLightwellAdvisoryDao_SyncForRepository_Call{Call: _e.mock.On("SyncForRepository", ctx, repoConfigUUID, repoName, advisories)} } @@ -9880,7 +10089,7 @@ type MockLightwellVulnerabilityDao_List_Call struct { // List is a helper method to define mock.On call // - ctx context.Context // - opts ListLightwellVulnerabilitiesOptions -func (_e *MockLightwellVulnerabilityDao_Expecter) List(ctx interface{}, opts interface{}) *MockLightwellVulnerabilityDao_List_Call { +func (_e *MockLightwellVulnerabilityDao_Expecter) List(ctx any, opts any) *MockLightwellVulnerabilityDao_List_Call { return &MockLightwellVulnerabilityDao_List_Call{Call: _e.mock.On("List", ctx, opts)} } @@ -9947,7 +10156,7 @@ type MockLightwellVulnerabilityDao_ListCustomerIds_Call struct { // ListCustomerIds is a helper method to define mock.On call // - ctx context.Context -func (_e *MockLightwellVulnerabilityDao_Expecter) ListCustomerIds(ctx interface{}) *MockLightwellVulnerabilityDao_ListCustomerIds_Call { +func (_e *MockLightwellVulnerabilityDao_Expecter) ListCustomerIds(ctx any) *MockLightwellVulnerabilityDao_ListCustomerIds_Call { return &MockLightwellVulnerabilityDao_ListCustomerIds_Call{Call: _e.mock.On("ListCustomerIds", ctx)} } @@ -10010,7 +10219,7 @@ type MockLightwellVulnerabilityDao_ListLtwlsuptTicketIds_Call struct { // ListLtwlsuptTicketIds is a helper method to define mock.On call // - ctx context.Context // - customerID string -func (_e *MockLightwellVulnerabilityDao_Expecter) ListLtwlsuptTicketIds(ctx interface{}, customerID interface{}) *MockLightwellVulnerabilityDao_ListLtwlsuptTicketIds_Call { +func (_e *MockLightwellVulnerabilityDao_Expecter) ListLtwlsuptTicketIds(ctx any, customerID any) *MockLightwellVulnerabilityDao_ListLtwlsuptTicketIds_Call { return &MockLightwellVulnerabilityDao_ListLtwlsuptTicketIds_Call{Call: _e.mock.On("ListLtwlsuptTicketIds", ctx, customerID)} } @@ -10106,7 +10315,7 @@ type MockUserPreferenceDao_List_Call struct { // - ctx context.Context // - orgID string // - userID string -func (_e *MockUserPreferenceDao_Expecter) List(ctx interface{}, orgID interface{}, userID interface{}) *MockUserPreferenceDao_List_Call { +func (_e *MockUserPreferenceDao_Expecter) List(ctx any, orgID any, userID any) *MockUserPreferenceDao_List_Call { return &MockUserPreferenceDao_List_Call{Call: _e.mock.On("List", ctx, orgID, userID)} } @@ -10180,7 +10389,7 @@ type MockUserPreferenceDao_ListDistinctOrgsByPreference_Call struct { // - ctx context.Context // - label string // - value string -func (_e *MockUserPreferenceDao_Expecter) ListDistinctOrgsByPreference(ctx interface{}, label interface{}, value interface{}) *MockUserPreferenceDao_ListDistinctOrgsByPreference_Call { +func (_e *MockUserPreferenceDao_Expecter) ListDistinctOrgsByPreference(ctx any, label any, value any) *MockUserPreferenceDao_ListDistinctOrgsByPreference_Call { return &MockUserPreferenceDao_ListDistinctOrgsByPreference_Call{Call: _e.mock.On("ListDistinctOrgsByPreference", ctx, label, value)} } @@ -10254,7 +10463,7 @@ type MockUserPreferenceDao_Set_Call struct { // - userID string // - label string // - value string -func (_e *MockUserPreferenceDao_Expecter) Set(ctx interface{}, orgID interface{}, userID interface{}, label interface{}, value interface{}) *MockUserPreferenceDao_Set_Call { +func (_e *MockUserPreferenceDao_Expecter) Set(ctx any, orgID any, userID any, label any, value any) *MockUserPreferenceDao_Set_Call { return &MockUserPreferenceDao_Set_Call{Call: _e.mock.On("Set", ctx, orgID, userID, label, value)} } @@ -10363,7 +10572,7 @@ type MockCoverageReportDao_Create_Call struct { // - ctx context.Context // - report CreateCoverageReportParams // - upload CreateCoverageUploadParams -func (_e *MockCoverageReportDao_Expecter) Create(ctx interface{}, report interface{}, upload interface{}) *MockCoverageReportDao_Create_Call { +func (_e *MockCoverageReportDao_Expecter) Create(ctx any, report any, upload any) *MockCoverageReportDao_Create_Call { return &MockCoverageReportDao_Create_Call{Call: _e.mock.On("Create", ctx, report, upload)} } @@ -10401,8 +10610,8 @@ func (_c *MockCoverageReportDao_Create_Call) RunAndReturn(run func(ctx context.C } // Fetch provides a mock function for the type MockCoverageReportDao -func (_mock *MockCoverageReportDao) Fetch(ctx context.Context, orgID string, uuid string) (api.CoverageReportResponse, error) { - ret := _mock.Called(ctx, orgID, uuid) +func (_mock *MockCoverageReportDao) Fetch(ctx context.Context, orgID string, uuid1 string) (api.CoverageReportResponse, error) { + ret := _mock.Called(ctx, orgID, uuid1) if len(ret) == 0 { panic("no return value specified for Fetch") @@ -10411,15 +10620,15 @@ func (_mock *MockCoverageReportDao) Fetch(ctx context.Context, orgID string, uui var r0 api.CoverageReportResponse var r1 error if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) (api.CoverageReportResponse, error)); ok { - return returnFunc(ctx, orgID, uuid) + return returnFunc(ctx, orgID, uuid1) } if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) api.CoverageReportResponse); ok { - r0 = returnFunc(ctx, orgID, uuid) + r0 = returnFunc(ctx, orgID, uuid1) } else { r0 = ret.Get(0).(api.CoverageReportResponse) } if returnFunc, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = returnFunc(ctx, orgID, uuid) + r1 = returnFunc(ctx, orgID, uuid1) } else { r1 = ret.Error(1) } @@ -10434,12 +10643,12 @@ type MockCoverageReportDao_Fetch_Call struct { // Fetch is a helper method to define mock.On call // - ctx context.Context // - orgID string -// - uuid string -func (_e *MockCoverageReportDao_Expecter) Fetch(ctx interface{}, orgID interface{}, uuid interface{}) *MockCoverageReportDao_Fetch_Call { - return &MockCoverageReportDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, orgID, uuid)} +// - uuid1 string +func (_e *MockCoverageReportDao_Expecter) Fetch(ctx any, orgID any, uuid1 any) *MockCoverageReportDao_Fetch_Call { + return &MockCoverageReportDao_Fetch_Call{Call: _e.mock.On("Fetch", ctx, orgID, uuid1)} } -func (_c *MockCoverageReportDao_Fetch_Call) Run(run func(ctx context.Context, orgID string, uuid string)) *MockCoverageReportDao_Fetch_Call { +func (_c *MockCoverageReportDao_Fetch_Call) Run(run func(ctx context.Context, orgID string, uuid1 string)) *MockCoverageReportDao_Fetch_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -10467,7 +10676,7 @@ func (_c *MockCoverageReportDao_Fetch_Call) Return(coverageReportResponse api.Co return _c } -func (_c *MockCoverageReportDao_Fetch_Call) RunAndReturn(run func(ctx context.Context, orgID string, uuid string) (api.CoverageReportResponse, error)) *MockCoverageReportDao_Fetch_Call { +func (_c *MockCoverageReportDao_Fetch_Call) RunAndReturn(run func(ctx context.Context, orgID string, uuid1 string) (api.CoverageReportResponse, error)) *MockCoverageReportDao_Fetch_Call { _c.Call.Return(run) return _c } @@ -10515,7 +10724,7 @@ type MockCoverageReportDao_ListPackages_Call struct { // - reportUUID string // - pageData api.PaginationData // - filterData api.ListCoverageReportPackagesRequest -func (_e *MockCoverageReportDao_Expecter) ListPackages(ctx interface{}, orgID interface{}, reportUUID interface{}, pageData interface{}, filterData interface{}) *MockCoverageReportDao_ListPackages_Call { +func (_e *MockCoverageReportDao_Expecter) ListPackages(ctx any, orgID any, reportUUID any, pageData any, filterData any) *MockCoverageReportDao_ListPackages_Call { return &MockCoverageReportDao_ListPackages_Call{Call: _e.mock.On("ListPackages", ctx, orgID, reportUUID, pageData, filterData)} } diff --git a/pkg/tasks/client/client_mock.go b/pkg/tasks/client/client_mock.go index 683336721..ff3b3d07c 100644 --- a/pkg/tasks/client/client_mock.go +++ b/pkg/tasks/client/client_mock.go @@ -64,7 +64,7 @@ type MockTaskClient_Cancel_Call struct { // Cancel is a helper method to define mock.On call // - ctx context.Context // - taskId string -func (_e *MockTaskClient_Expecter) Cancel(ctx interface{}, taskId interface{}) *MockTaskClient_Cancel_Call { +func (_e *MockTaskClient_Expecter) Cancel(ctx any, taskId any) *MockTaskClient_Cancel_Call { return &MockTaskClient_Cancel_Call{Call: _e.mock.On("Cancel", ctx, taskId)} } @@ -131,7 +131,7 @@ type MockTaskClient_Enqueue_Call struct { // Enqueue is a helper method to define mock.On call // - task queue.Task -func (_e *MockTaskClient_Expecter) Enqueue(task interface{}) *MockTaskClient_Enqueue_Call { +func (_e *MockTaskClient_Expecter) Enqueue(task any) *MockTaskClient_Enqueue_Call { return &MockTaskClient_Enqueue_Call{Call: _e.mock.On("Enqueue", task)} } diff --git a/pkg/tasks/queue/queue_mock.go b/pkg/tasks/queue/queue_mock.go index 33f4e113b..1775a8ea7 100644 --- a/pkg/tasks/queue/queue_mock.go +++ b/pkg/tasks/queue/queue_mock.go @@ -65,7 +65,7 @@ type MockQueue_Cancel_Call struct { // Cancel is a helper method to define mock.On call // - ctx context.Context // - taskId uuid.UUID -func (_e *MockQueue_Expecter) Cancel(ctx interface{}, taskId interface{}) *MockQueue_Cancel_Call { +func (_e *MockQueue_Expecter) Cancel(ctx any, taskId any) *MockQueue_Cancel_Call { return &MockQueue_Cancel_Call{Call: _e.mock.On("Cancel", ctx, taskId)} } @@ -133,7 +133,7 @@ type MockQueue_Dequeue_Call struct { // Dequeue is a helper method to define mock.On call // - ctx context.Context // - taskTypes []string -func (_e *MockQueue_Expecter) Dequeue(ctx interface{}, taskTypes interface{}) *MockQueue_Dequeue_Call { +func (_e *MockQueue_Expecter) Dequeue(ctx any, taskTypes any) *MockQueue_Dequeue_Call { return &MockQueue_Dequeue_Call{Call: _e.mock.On("Dequeue", ctx, taskTypes)} } @@ -200,7 +200,7 @@ type MockQueue_Enqueue_Call struct { // Enqueue is a helper method to define mock.On call // - task *Task -func (_e *MockQueue_Expecter) Enqueue(task interface{}) *MockQueue_Enqueue_Call { +func (_e *MockQueue_Expecter) Enqueue(task any) *MockQueue_Enqueue_Call { return &MockQueue_Enqueue_Call{Call: _e.mock.On("Enqueue", task)} } @@ -252,7 +252,7 @@ type MockQueue_Finish_Call struct { // Finish is a helper method to define mock.On call // - taskId uuid.UUID // - taskError error -func (_e *MockQueue_Expecter) Finish(taskId interface{}, taskError interface{}) *MockQueue_Finish_Call { +func (_e *MockQueue_Expecter) Finish(taskId any, taskError any) *MockQueue_Finish_Call { return &MockQueue_Finish_Call{Call: _e.mock.On("Finish", taskId, taskError)} } @@ -310,7 +310,7 @@ type MockQueue_Heartbeats_Call struct { // Heartbeats is a helper method to define mock.On call // - olderThan time.Duration -func (_e *MockQueue_Expecter) Heartbeats(olderThan interface{}) *MockQueue_Heartbeats_Call { +func (_e *MockQueue_Expecter) Heartbeats(olderThan any) *MockQueue_Heartbeats_Call { return &MockQueue_Heartbeats_Call{Call: _e.mock.On("Heartbeats", olderThan)} } @@ -378,7 +378,7 @@ type MockQueue_IdFromToken_Call struct { // IdFromToken is a helper method to define mock.On call // - token uuid.UUID -func (_e *MockQueue_Expecter) IdFromToken(token interface{}) *MockQueue_IdFromToken_Call { +func (_e *MockQueue_Expecter) IdFromToken(token any) *MockQueue_IdFromToken_Call { return &MockQueue_IdFromToken_Call{Call: _e.mock.On("IdFromToken", token)} } @@ -440,7 +440,7 @@ type MockQueue_ListenForCanceledTask_Call struct { // ListenForCanceledTask is a helper method to define mock.On call // - ctx context.Context -func (_e *MockQueue_Expecter) ListenForCanceledTask(ctx interface{}) *MockQueue_ListenForCanceledTask_Call { +func (_e *MockQueue_Expecter) ListenForCanceledTask(ctx any) *MockQueue_ListenForCanceledTask_Call { return &MockQueue_ListenForCanceledTask_Call{Call: _e.mock.On("ListenForCanceledTask", ctx)} } @@ -491,7 +491,7 @@ type MockQueue_RefreshHeartbeat_Call struct { // RefreshHeartbeat is a helper method to define mock.On call // - token uuid.UUID -func (_e *MockQueue_Expecter) RefreshHeartbeat(token interface{}) *MockQueue_RefreshHeartbeat_Call { +func (_e *MockQueue_Expecter) RefreshHeartbeat(token any) *MockQueue_RefreshHeartbeat_Call { return &MockQueue_RefreshHeartbeat_Call{Call: _e.mock.On("RefreshHeartbeat", token)} } @@ -542,7 +542,7 @@ type MockQueue_Requeue_Call struct { // Requeue is a helper method to define mock.On call // - taskId uuid.UUID -func (_e *MockQueue_Expecter) Requeue(taskId interface{}) *MockQueue_Requeue_Call { +func (_e *MockQueue_Expecter) Requeue(taskId any) *MockQueue_Requeue_Call { return &MockQueue_Requeue_Call{Call: _e.mock.On("Requeue", taskId)} } @@ -593,7 +593,7 @@ type MockQueue_RequeueFailedTasks_Call struct { // RequeueFailedTasks is a helper method to define mock.On call // - taskTypes []string -func (_e *MockQueue_Expecter) RequeueFailedTasks(taskTypes interface{}) *MockQueue_RequeueFailedTasks_Call { +func (_e *MockQueue_Expecter) RequeueFailedTasks(taskTypes any) *MockQueue_RequeueFailedTasks_Call { return &MockQueue_RequeueFailedTasks_Call{Call: _e.mock.On("RequeueFailedTasks", taskTypes)} } @@ -655,7 +655,7 @@ type MockQueue_Status_Call struct { // Status is a helper method to define mock.On call // - taskId uuid.UUID -func (_e *MockQueue_Expecter) Status(taskId interface{}) *MockQueue_Status_Call { +func (_e *MockQueue_Expecter) Status(taskId any) *MockQueue_Status_Call { return &MockQueue_Status_Call{Call: _e.mock.On("Status", taskId)} } @@ -718,7 +718,7 @@ type MockQueue_UpdatePayload_Call struct { // UpdatePayload is a helper method to define mock.On call // - task *models.TaskInfo // - payload interface{} -func (_e *MockQueue_Expecter) UpdatePayload(task interface{}, payload interface{}) *MockQueue_UpdatePayload_Call { +func (_e *MockQueue_Expecter) UpdatePayload(task any, payload any) *MockQueue_UpdatePayload_Call { return &MockQueue_UpdatePayload_Call{Call: _e.mock.On("UpdatePayload", task, payload)} } From 8148581ba8f4364cc01bf9f6d40e464c2a58f815 Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 26 Aug 2026 17:31:22 -0400 Subject: [PATCH 27/47] LWLP-5: fix spec deviations and test isolation from merge resolution - Swagger annotation: rename repository_uuid to repository (matches v2 spec and code) - Rename remediations_count to advisories_count on RepositoryResponse (v2 spec) - Scope advisory store tests by RepositoryConfigUuid to avoid seed data leaks - Restore deleted TestStore_ListLtwlsuptTicketIds test - Regenerate OpenAPI spec --- api/docs.go | 24 +++++----- api/openapi.json | 24 +++++----- pkg/api/repositories.go | 2 +- pkg/handler/lightwell_advisories.go | 2 +- pkg/handler/repositories.go | 2 +- pkg/lightwell/db/store/store_test.go | 69 ++++++++++++++++++++++++---- 6 files changed, 88 insertions(+), 35 deletions(-) diff --git a/api/docs.go b/api/docs.go index 8070aa9f7..e3ee6c409 100644 --- a/api/docs.go +++ b/api/docs.go @@ -299,8 +299,8 @@ const docTemplate = `{ "parameters": [ { "type": "string", - "description": "Filter by repository UUID", - "name": "repository_uuid", + "description": "Filter by repository name", + "name": "repository", "in": "query" }, { @@ -6312,6 +6312,11 @@ const docTemplate = `{ "type": "string", "readOnly": true }, + "advisories_count": { + "description": "Lightwell: total security advisories", + "type": "integer", + "readOnly": true + }, "build_count": { "description": "Number of builds last read in the repository, not applicable to all repositories", "type": "integer" @@ -6452,11 +6457,6 @@ const docTemplate = `{ "type": "string", "readOnly": true }, - "remediations_count": { - "description": "Lightwell: total security advisories", - "type": "integer", - "readOnly": true - }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "type": "string", @@ -6657,6 +6657,11 @@ const docTemplate = `{ "type": "string", "readOnly": true }, + "advisories_count": { + "description": "Lightwell: total security advisories", + "type": "integer", + "readOnly": true + }, "build_count": { "description": "Number of builds last read in the repository, not applicable to all repositories", "type": "integer" @@ -6797,11 +6802,6 @@ const docTemplate = `{ "type": "string", "readOnly": true }, - "remediations_count": { - "description": "Lightwell: total security advisories", - "type": "integer", - "readOnly": true - }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "type": "string", diff --git a/api/openapi.json b/api/openapi.json index 9ae7c6ea6..f29a8e239 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -1311,6 +1311,11 @@ "readOnly": true, "type": "string" }, + "advisories_count": { + "description": "Lightwell: total security advisories", + "readOnly": true, + "type": "integer" + }, "build_count": { "description": "Number of builds last read in the repository, not applicable to all repositories", "type": "integer" @@ -1451,11 +1456,6 @@ "readOnly": true, "type": "string" }, - "remediations_count": { - "description": "Lightwell: total security advisories", - "readOnly": true, - "type": "integer" - }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "readOnly": true, @@ -1656,6 +1656,11 @@ "readOnly": true, "type": "string" }, + "advisories_count": { + "description": "Lightwell: total security advisories", + "readOnly": true, + "type": "integer" + }, "build_count": { "description": "Number of builds last read in the repository, not applicable to all repositories", "type": "integer" @@ -1796,11 +1801,6 @@ "readOnly": true, "type": "string" }, - "remediations_count": { - "description": "Lightwell: total security advisories", - "readOnly": true, - "type": "integer" - }, "security_level": { "description": "Security level of the repository (e.g. validated, remediated)", "readOnly": true, @@ -3354,9 +3354,9 @@ "operationId": "listLightwellAdvisories", "parameters": [ { - "description": "Filter by repository UUID", + "description": "Filter by repository name", "in": "query", - "name": "repository_uuid", + "name": "repository", "schema": { "type": "string" } diff --git a/pkg/api/repositories.go b/pkg/api/repositories.go index 059ad93a8..044b6d827 100644 --- a/pkg/api/repositories.go +++ b/pkg/api/repositories.go @@ -47,7 +47,7 @@ type RepositoryResponse struct { PublishedDistBasePath string `json:"-"` // Published dist base path from Pulp PackagesCount *int `json:"packages_count,omitempty" readonly:"true"` // Lightwell: total distinct packages VersionsCount *int `json:"versions_count,omitempty" readonly:"true"` // Lightwell: total distinct versions - RemediationsCount *int `json:"remediations_count,omitempty" readonly:"true"` // Lightwell: total security advisories + AdvisoriesCount *int `json:"advisories_count,omitempty" readonly:"true"` // Lightwell: total security advisories } // RepositoryRequest holds data received from request to create repository diff --git a/pkg/handler/lightwell_advisories.go b/pkg/handler/lightwell_advisories.go index 79a71c9fa..104862340 100644 --- a/pkg/handler/lightwell_advisories.go +++ b/pkg/handler/lightwell_advisories.go @@ -27,7 +27,7 @@ func RegisterLightwellAdvisoryRoutes(engine *echo.Group, daoReg *dao.DaoRegistry // @Tags lightwell // @Accept json // @Produce json -// @Param repository_uuid query string false "Filter by repository UUID" +// @Param repository query string false "Filter by repository name" // @Param package_name query string false "Filter by package name (substring match)" // @Param severity_min query string false "Minimum severity level (low, moderate, important, critical)" // @Param cve_id query string false "Filter by CVE ID (exact match)" diff --git a/pkg/handler/repositories.go b/pkg/handler/repositories.go index 01a941bb4..2f19c4d57 100644 --- a/pkg/handler/repositories.go +++ b/pkg/handler/repositories.go @@ -153,7 +153,7 @@ func (rh *RepositoryHandler) enrichLightwellRepoCounts(c echo.Context, repos *ap continue } remCount := int(count) - repo.RemediationsCount = &remCount + repo.AdvisoriesCount = &remCount } } diff --git a/pkg/lightwell/db/store/store_test.go b/pkg/lightwell/db/store/store_test.go index 11237162b..716470398 100644 --- a/pkg/lightwell/db/store/store_test.go +++ b/pkg/lightwell/db/store/store_test.go @@ -759,6 +759,58 @@ func TestStore_ListCustomerIds(t *testing.T) { assert.Contains(t, ids, customerB) } +func TestStore_ListLtwlsuptTicketIds(t *testing.T) { + ctx, tx, q := beginTestTx(t) + defer rollbackTestTx(t, tx) + + customerA := fmt.Sprintf("lw-tickets-a-%d", time.Now().UnixNano()) + customerB := fmt.Sprintf("lw-tickets-b-%d", time.Now().UnixNano()) + insertTestVulnerabilities(t, ctx, tx, []testVulnSpec{ + { + vulnID: "LWL-TICKETS-1", + severity: "Moderate", + stage: "Submitted", + language: "java", + complexity: "Standard", + ticketIDs: []string{"ticket-c", "ticket-a"}, + daysAgo: 1, + customerIDs: []string{customerA}, + }, + { + vulnID: "LWL-TICKETS-2", + severity: "Low", + stage: "Submitted", + language: "java", + complexity: "Standard", + ticketID: "ticket-a", + daysAgo: 1, + customerIDs: []string{customerA}, + }, + { + vulnID: "LWL-TICKETS-3", + severity: "Low", + stage: "Submitted", + language: "python", + complexity: "Standard", + ticketID: "ticket-b", + daysAgo: 1, + customerIDs: []string{customerB}, + }, + }) + + ids, err := q.ListLtwlsuptTicketIds(ctx, customerA) + require.NoError(t, err) + assert.Equal(t, []string{"ticket-a", "ticket-c"}, ids) + + ids, err = q.ListLtwlsuptTicketIds(ctx, customerB) + require.NoError(t, err) + assert.Equal(t, []string{"ticket-b"}, ids) + + ids, err = q.ListLtwlsuptTicketIds(ctx, "no-such-customer") + require.NoError(t, err) + assert.Empty(t, ids) +} + // --- Advisory query integration tests --- func insertTestAdvisories(t *testing.T, ctx context.Context, tx pgx.Tx) uuid.UUID { @@ -813,16 +865,16 @@ func TestStore_ListAdvisories(t *testing.T) { ctx, tx, q := beginTestTx(t) defer rollbackTestTx(t, tx) - insertTestAdvisories(t, ctx, tx) + repoConfigUUID := insertTestAdvisories(t, ctx, tx) rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ - PageLimit: 100, - PageOffset: 0, + RepositoryConfigUuid: pgtype.UUID{Bytes: repoConfigUUID, Valid: true}, + PageLimit: 100, + PageOffset: 0, }) require.NoError(t, err) assert.Len(t, rows, 4) assert.Equal(t, int64(4), rows[0].TotalCount) - // Ordered by severity_order DESC assert.Equal(t, int16(4), rows[0].SeverityOrder) } @@ -849,12 +901,13 @@ func TestStore_ListAdvisoriesFilterBySeverityMin(t *testing.T) { ctx, tx, q := beginTestTx(t) defer rollbackTestTx(t, tx) - insertTestAdvisories(t, ctx, tx) + repoConfigUUID := insertTestAdvisories(t, ctx, tx) rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ - SeverityMin: pgtype.Int2{Int16: 3, Valid: true}, - PageLimit: 100, - PageOffset: 0, + RepositoryConfigUuid: pgtype.UUID{Bytes: repoConfigUUID, Valid: true}, + SeverityMin: pgtype.Int2{Int16: 3, Valid: true}, + PageLimit: 100, + PageOffset: 0, }) require.NoError(t, err) assert.Len(t, rows, 3) From e939cebee3a77e7a49647bd8ca09f65f7c4956d9 Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 26 Aug 2026 18:40:35 -0400 Subject: [PATCH 28/47] LWLP-5: align with upstream main changes - S3 client init failure is now fatal (panic) to match upstream - Remove RBAC from user_preferences routes (upstream LWLP-743) --- pkg/handler/api.go | 2 +- pkg/handler/user_preferences.go | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/handler/api.go b/pkg/handler/api.go index cd72aea39..820155a56 100644 --- a/pkg/handler/api.go +++ b/pkg/handler/api.go @@ -78,7 +78,7 @@ func RegisterRoutes(ctx context.Context, engine *echo.Echo) { } else { s3Client, err = s3_client.NewS3Client(config.Get().Clients.Lightwell.S3.CoverageUploads) if err != nil { - log.Warn().Err(err).Msg("failed to create s3 client") + panic(err) } } ch := cache.Initialize() diff --git a/pkg/handler/user_preferences.go b/pkg/handler/user_preferences.go index 98ac04bfb..c329d0219 100644 --- a/pkg/handler/user_preferences.go +++ b/pkg/handler/user_preferences.go @@ -7,7 +7,6 @@ import ( "github.com/content-services/content-sources-backend/pkg/dao" ce "github.com/content-services/content-sources-backend/pkg/errors" - "github.com/content-services/content-sources-backend/pkg/rbac" "github.com/labstack/echo/v4" "github.com/redhatinsights/platform-go-middlewares/v2/identity" ) @@ -18,8 +17,8 @@ type UserPreferencesHandler struct { func RegisterUserPreferencesRoutes(engine *echo.Group, daoReg *dao.DaoRegistry) { h := UserPreferencesHandler{DaoRegistry: *daoReg} - addRepoRoute(engine, http.MethodGet, "/user_preferences/", h.listUserPreferences, rbac.RbacVerbRead) - addRepoRoute(engine, http.MethodPut, "/user_preferences/:label", h.setUserPreference, rbac.RbacVerbWrite) + engine.GET("/user_preferences/", h.listUserPreferences) + engine.PUT("/user_preferences/:label", h.setUserPreference) } // ListUserPreferences godoc From b9413dd51d0c6113275a7172567fe7f1b3ac736e Mon Sep 17 00:00:00 2001 From: etsien Date: Thu, 27 Aug 2026 05:11:36 -0400 Subject: [PATCH 29/47] more rebase fixes --- db/migrations.latest | 2 +- ...ightwell_advisory_severity_order.down.sql} | 0 ..._lightwell_advisory_severity_order.up.sql} | 0 deployments/build/deployment.template.yaml | 1 + pkg/lightwell/db/store/store_test.go | 166 ------------------ 5 files changed, 2 insertions(+), 167 deletions(-) rename db/migrations/{20260825110000_add_lightwell_advisory_severity_order.down.sql => 20260827110000_add_lightwell_advisory_severity_order.down.sql} (100%) rename db/migrations/{20260825110000_add_lightwell_advisory_severity_order.up.sql => 20260827110000_add_lightwell_advisory_severity_order.up.sql} (100%) diff --git a/db/migrations.latest b/db/migrations.latest index 12b5acc22..021b03286 100644 --- a/db/migrations.latest +++ b/db/migrations.latest @@ -1 +1 @@ -20260826120000 \ No newline at end of file +20260827110000 \ No newline at end of file diff --git a/db/migrations/20260825110000_add_lightwell_advisory_severity_order.down.sql b/db/migrations/20260827110000_add_lightwell_advisory_severity_order.down.sql similarity index 100% rename from db/migrations/20260825110000_add_lightwell_advisory_severity_order.down.sql rename to db/migrations/20260827110000_add_lightwell_advisory_severity_order.down.sql diff --git a/db/migrations/20260825110000_add_lightwell_advisory_severity_order.up.sql b/db/migrations/20260827110000_add_lightwell_advisory_severity_order.up.sql similarity index 100% rename from db/migrations/20260825110000_add_lightwell_advisory_severity_order.up.sql rename to db/migrations/20260827110000_add_lightwell_advisory_severity_order.up.sql diff --git a/deployments/build/deployment.template.yaml b/deployments/build/deployment.template.yaml index b9c462ffe..9d2aa39b0 100644 --- a/deployments/build/deployment.template.yaml +++ b/deployments/build/deployment.template.yaml @@ -420,6 +420,7 @@ objects: inMemoryDb: true objectStore: - content-sources-central-pulp-s3 + - lightwell-ui-coverage-uploads - apiVersion: v1 kind: Service metadata: diff --git a/pkg/lightwell/db/store/store_test.go b/pkg/lightwell/db/store/store_test.go index b16f9ee89..b14ec1e0f 100644 --- a/pkg/lightwell/db/store/store_test.go +++ b/pkg/lightwell/db/store/store_test.go @@ -809,172 +809,6 @@ func insertTestAdvisories(t *testing.T, ctx context.Context, tx pgx.Tx) uuid.UUI return repoConfigUUID } -func TestStore_ListAdvisories(t *testing.T) { - ctx, tx, q := beginTestTx(t) - defer rollbackTestTx(t, tx) - - insertTestAdvisories(t, ctx, tx) - - rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ - PageLimit: 100, - PageOffset: 0, - }) - require.NoError(t, err) - assert.Len(t, rows, 4) - assert.Equal(t, int64(4), rows[0].TotalCount) - // Ordered by severity_order DESC - assert.Equal(t, int16(4), rows[0].SeverityOrder) -} - -func TestStore_ListAdvisoriesFilterByPackageName(t *testing.T) { - ctx, tx, q := beginTestTx(t) - defer rollbackTestTx(t, tx) - - insertTestAdvisories(t, ctx, tx) - - name := "jackson" - rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ - PackageName: &name, - PageLimit: 100, - PageOffset: 0, - }) - require.NoError(t, err) - assert.Len(t, rows, 2) - for _, r := range rows { - assert.Contains(t, r.PackageName, "jackson") - } -} - -func TestStore_ListAdvisoriesFilterBySeverityMin(t *testing.T) { - ctx, tx, q := beginTestTx(t) - defer rollbackTestTx(t, tx) - - insertTestAdvisories(t, ctx, tx) - - rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ - SeverityMin: pgtype.Int2{Int16: 3, Valid: true}, - PageLimit: 100, - PageOffset: 0, - }) - require.NoError(t, err) - assert.Len(t, rows, 3) - for _, r := range rows { - assert.GreaterOrEqual(t, r.SeverityOrder, int16(3)) - } -} - -func TestStore_ListAdvisoriesFilterByRepoName(t *testing.T) { - ctx, tx, q := beginTestTx(t) - defer rollbackTestTx(t, tx) - - insertTestAdvisories(t, ctx, tx) - - repoName := "lightwell/python/remediated" - rows, err := q.ListAdvisories(ctx, store.ListAdvisoriesParams{ - RepoName: &repoName, - PageLimit: 100, - PageOffset: 0, - }) - require.NoError(t, err) - assert.Len(t, rows, 1) - assert.Equal(t, "requests", rows[0].PackageName) -} - -func TestStore_CountAdvisoriesByRepo(t *testing.T) { - ctx, tx, q := beginTestTx(t) - defer rollbackTestTx(t, tx) - - repoUUID := insertTestAdvisories(t, ctx, tx) - - count, err := q.CountAdvisoriesByRepo(ctx, repoUUID) - require.NoError(t, err) - assert.Equal(t, int64(4), count) -} - -func TestStore_ListAdvisoriesByCveID(t *testing.T) { - ctx, tx, q := beginTestTx(t) - defer rollbackTestTx(t, tx) - - insertTestAdvisories(t, ctx, tx) - - rows, err := q.ListAdvisoriesByCveID(ctx, "CVE-2024-1001") - require.NoError(t, err) - assert.Len(t, rows, 2) - - packageNames := map[string]bool{} - for _, r := range rows { - packageNames[r.PackageName] = true - assert.Equal(t, "critical", r.Severity) - } - assert.True(t, packageNames["spring-core"]) - assert.True(t, packageNames["jackson-databind"]) -} - -func TestStore_ListAdvisoriesByPackage(t *testing.T) { - ctx, tx, q := beginTestTx(t) - defer rollbackTestTx(t, tx) - - insertTestAdvisories(t, ctx, tx) - - rows, err := q.ListAdvisoriesByPackage(ctx, "jackson-databind") - require.NoError(t, err) - assert.Len(t, rows, 2) - for _, r := range rows { - assert.NotEmpty(t, r.AdvisoryID) - assert.NotEmpty(t, r.FixedVersions) - } -} - -// --- Advisory query integration tests --- - -func insertTestAdvisories(t *testing.T, ctx context.Context, tx pgx.Tx) uuid.UUID { - repoConfigUUID := uuid.New() - repoUUID := uuid.New() - now := time.Now() - - _, err := tx.Exec(ctx, - `INSERT INTO repositories (uuid, url) VALUES ($1, $2)`, - repoUUID, "https://test.example.com/repo/"+repoConfigUUID.String()) - require.NoError(t, err) - - _, err = tx.Exec(ctx, - `INSERT INTO repository_configurations (uuid, created_at, updated_at, name, arch, org_id, repository_uuid) - VALUES ($1, $2, $3, $4, $5, $6, $7)`, - repoConfigUUID, now, now, "test-advisory-repo", "x86_64", "test-org-"+repoConfigUUID.String(), repoUUID) - require.NoError(t, err) - - advisories := []struct { - id string - severity string - severityOrder int - packageName string - fixedVersions []string - repoName string - }{ - {"CVE-2024-1001", "critical", 4, "spring-core", []string{"5.3.18.rhlw-00003"}, "lightwell/java/remediated"}, - {"CVE-2024-1002", "important", 3, "jackson-databind", []string{"2.15.3.rhlw-00001"}, "lightwell/java/remediated"}, - {"CVE-2024-1003", "moderate", 2, "requests", []string{"2.31.0.rhlw-00001"}, "lightwell/python/remediated"}, - {"CVE-2024-1001", "critical", 4, "jackson-databind", []string{"2.14.2.rhlw-00001", "2.15.3.rhlw-00001"}, "lightwell/java/remediated"}, - } - - for _, adv := range advisories { - _, err := tx.Exec(ctx, ` - INSERT INTO lightwell_advisories ( - uuid, advisory_id, severity, severity_order, details, - reference_urls, package_name, fixed_versions, - repo_name, repository_configuration_uuid, checksum - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, - uuid.New(), adv.id, adv.severity, adv.severityOrder, - "test advisory details for "+adv.packageName, - []string{"https://access.redhat.com/security/cve/" + adv.id}, - adv.packageName, adv.fixedVersions, - adv.repoName, repoConfigUUID, fmt.Sprintf("checksum-%s-%s", adv.id, adv.packageName), - ) - require.NoError(t, err) - } - return repoConfigUUID -} - func TestStore_ListAdvisories(t *testing.T) { ctx, tx, q := beginTestTx(t) defer rollbackTestTx(t, tx) From 8923b2a7722e92ca806b448dbfe460c2e9635efd Mon Sep 17 00:00:00 2001 From: etsien Date: Thu, 27 Aug 2026 05:20:57 -0400 Subject: [PATCH 30/47] remove repeated call found an issue when auditing codebase after rebase: Found one duplicate: config.ConfigureTang() is called twice in cmd/content-sources/main.go (lines 52 and 59), likely a rebase artifact. exists on upstream/main . --- cmd/content-sources/main.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmd/content-sources/main.go b/cmd/content-sources/main.go index 3c5407d46..c8926da60 100644 --- a/cmd/content-sources/main.go +++ b/cmd/content-sources/main.go @@ -55,11 +55,6 @@ func main() { } metrics := m.NewMetrics(reg) - - err = config.ConfigureTang() - if err != nil { - log.Panic().Err(err).Msg("Could not initialize tang, was pulp database information provided?") - } if config.Tang != nil { defer (*config.Tang).Close() } From e7f7514fa0305af9faddc2205f8efdcad147cec8 Mon Sep 17 00:00:00 2001 From: etsien Date: Thu, 27 Aug 2026 05:42:31 -0400 Subject: [PATCH 31/47] fix swagger annotation --- pkg/handler/lightwell_packages.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/handler/lightwell_packages.go b/pkg/handler/lightwell_packages.go index 694e81337..078a9ebaa 100644 --- a/pkg/handler/lightwell_packages.go +++ b/pkg/handler/lightwell_packages.go @@ -47,7 +47,7 @@ func RegisterLightwellPackageRoutes(engine *echo.Group, daoReg *dao.DaoRegistry, // @Tags lightwell // @Accept json // @Produce json -// @Param type query string false "Filter by content type (maven, python, npm)" +// @Param content_type query string false "Filter by content type (maven, python, npm)" // @Param name query string false "Filter by package name (substring match)" // @Param security_level query string false "Filter by security level (validated, remediated)" // @Param limit query int false "Limit of results to return" @@ -92,7 +92,7 @@ func (h *LightwellPackagesHandler) listPackages(c echo.Context) error { // @Tags lightwell // @Accept json // @Produce json -// @Param type query string false "Filter by content type (maven, python, npm)" +// @Param content_type query string false "Filter by content type (maven, python, npm)" // @Param name query string false "Filter by package name (substring match)" // @Param security_level query string false "Filter by security level (validated, remediated)" // @Param repository query string false "Filter by repository name" From 3b1a2e6e697b7c6863cc867406bd7bae3946ad7c Mon Sep 17 00:00:00 2001 From: etsien Date: Thu, 27 Aug 2026 05:42:41 -0400 Subject: [PATCH 32/47] add the v2 API spec --- api/lightwell-openapi.yaml | 573 +++++++++++++++++++++++++++++++++++++ 1 file changed, 573 insertions(+) create mode 100644 api/lightwell-openapi.yaml diff --git a/api/lightwell-openapi.yaml b/api/lightwell-openapi.yaml new file mode 100644 index 000000000..ea96beacb --- /dev/null +++ b/api/lightwell-openapi.yaml @@ -0,0 +1,573 @@ +openapi: 3.1.0 +info: + title: Lightwell Network API + version: 2.0.0 + description: > + API for querying Lightwell repositories, packages, package versions, and + security advisories. Phase 1 covers the catalog read endpoints. File + download (Phase 2) and file metadata / embargo extensions (Phase 3) are + marked with x-phase and will be implemented when the upstream data sources + are available. + +servers: + - url: /api/content-sources/v1.0/lightwell + +components: + parameters: + Limit: + name: limit + in: query + description: Maximum number of items to return. + schema: + type: integer + minimum: 1 + default: 20 + Offset: + name: offset + in: query + description: Number of items to skip before starting to return results. + schema: + type: integer + minimum: 0 + default: 0 + SortBy: + name: sort_by + in: query + description: > + Sort order. A field name followed by `asc` or `desc`. + Allowed fields are endpoint-specific. + schema: + type: string + example: "name asc" + RepositoryNamePath: + name: repository_name + in: path + required: true + description: Unique name of the Lightwell repository. + schema: + type: string + example: java-validated + + schemas: + ContentType: + type: string + description: Package ecosystem type. + enum: + - python + - maven + - npm + + SecurityLevel: + type: string + description: "Security level: validated or remediated." + enum: + - validated + - remediated + + ResponseMetadata: + type: object + required: [count, limit, offset] + properties: + count: + type: integer + limit: + type: integer + offset: + type: integer + + Links: + type: object + properties: + first: + type: string + last: + type: string + next: + type: string + nullable: true + prev: + type: string + nullable: true + + Repository: + type: object + required: + - name + - security_level + - content_type + - packages_count + - versions_count + - advisories_count + properties: + name: + type: string + example: java-validated + security_level: + $ref: "#/components/schemas/SecurityLevel" + content_type: + $ref: "#/components/schemas/ContentType" + packages_count: + type: integer + example: 456 + versions_count: + type: integer + example: 1024 + advisories_count: + type: integer + example: 200 + + ReleaseInfo: + type: object + properties: + version: + type: string + release: + type: string + created_at: + type: string + + Package: + type: object + required: + - name + - content_type + - repository + - repository_uuid + - versions + - latest_releases + properties: + name: + type: string + example: org.apache.commons:commons-lang3 + group: + type: string + description: Maven groupId or npm scope. Omitted when empty. + content_type: + $ref: "#/components/schemas/ContentType" + repository: + type: string + example: java-validated + repository_uuid: + type: string + versions: + type: array + items: + type: string + latest_releases: + type: array + items: + $ref: "#/components/schemas/ReleaseInfo" + + PackageVersion: + type: object + required: + - name + - version + - content_type + - repository + - repository_uuid + properties: + name: + type: string + example: org.apache.commons:commons-lang3 + group: + type: string + version: + type: string + example: "3.14.0" + content_type: + $ref: "#/components/schemas/ContentType" + repository: + type: string + example: java-validated + repository_uuid: + type: string + release: + type: string + created_at: + type: string + + LightwellAdvisory: + type: object + required: + - advisory_id + - severity + - details + - reference_urls + - package_name + - fixed_versions + - repository + properties: + advisory_id: + type: string + example: rhlw-0005 + severity: + type: string + example: important + details: + type: string + reference_urls: + type: array + items: + type: string + package_name: + type: string + example: org.apache.commons:commons-lang3 + fixed_versions: + type: array + items: + type: string + repository: + type: string + example: java-remediated + + PaginatedRepositories: + type: object + required: [data, meta, links] + properties: + data: + type: array + items: + $ref: "#/components/schemas/Repository" + meta: + $ref: "#/components/schemas/ResponseMetadata" + links: + $ref: "#/components/schemas/Links" + + PaginatedPackages: + type: object + required: [data, meta, links] + properties: + data: + type: array + items: + $ref: "#/components/schemas/Package" + meta: + $ref: "#/components/schemas/ResponseMetadata" + links: + $ref: "#/components/schemas/Links" + + PaginatedPackageVersions: + type: object + required: [data, meta, links] + properties: + data: + type: array + items: + $ref: "#/components/schemas/PackageVersion" + meta: + $ref: "#/components/schemas/ResponseMetadata" + links: + $ref: "#/components/schemas/Links" + + PaginatedAdvisories: + type: object + required: [data, meta, links] + properties: + data: + type: array + items: + $ref: "#/components/schemas/LightwellAdvisory" + meta: + $ref: "#/components/schemas/ResponseMetadata" + links: + $ref: "#/components/schemas/Links" + +paths: + # =========================================================================== + # Flat cross-repo endpoints (primary) + # =========================================================================== + + /packages: + get: + operationId: listPackages + summary: List packages across all repositories + tags: [Packages] + parameters: + - $ref: "#/components/parameters/Limit" + - $ref: "#/components/parameters/Offset" + - $ref: "#/components/parameters/SortBy" + - name: content_type + in: query + description: Filter by package ecosystem type. + schema: + $ref: "#/components/schemas/ContentType" + - name: name + in: query + description: > + Filter by package name (substring match). + When using the flat endpoint, `repository` is required alongside `name` + to avoid cross-ecosystem name collisions. + schema: + type: string + - name: repository + in: query + description: Filter by repository name. + schema: + type: string + - name: security_level + in: query + description: Filter by security level. + schema: + $ref: "#/components/schemas/SecurityLevel" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/PaginatedPackages" + + /package_versions: + get: + operationId: listPackageVersions + summary: List package versions across all repositories + tags: [Package Versions] + parameters: + - $ref: "#/components/parameters/Limit" + - $ref: "#/components/parameters/Offset" + - $ref: "#/components/parameters/SortBy" + - name: content_type + in: query + description: Filter by package ecosystem type. + schema: + $ref: "#/components/schemas/ContentType" + - name: name + in: query + description: Filter by package name (substring match). + schema: + type: string + - name: repository + in: query + description: Filter by repository name. + schema: + type: string + - name: security_level + in: query + description: Filter by security level. + schema: + $ref: "#/components/schemas/SecurityLevel" + - name: resolves_cve_id + in: query + description: > + Show only package versions that resolve this CVE + (i.e. versions listed in a Lightwell advisory's fixed_versions). + schema: + type: string + - name: vulnerable_to_cve_id + in: query + description: > + Show only package versions of packages affected by this CVE + that are NOT in the advisory's fixed_versions list. + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/PaginatedPackageVersions" + + /advisories: + get: + operationId: listAdvisories + summary: List advisories across all repositories + tags: [Advisories] + parameters: + - $ref: "#/components/parameters/Limit" + - $ref: "#/components/parameters/Offset" + - $ref: "#/components/parameters/SortBy" + - name: repository + in: query + description: Filter by repository name. + schema: + type: string + - name: package_name + in: query + description: Filter by package name (substring match). + schema: + type: string + - name: severity_min + in: query + description: Minimum severity level (low, moderate, important, critical). + schema: + type: string + - name: cve_id + in: query + description: Filter by CVE ID (exact match). + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/PaginatedAdvisories" + + # =========================================================================== + # Nested repo-scoped endpoints (aliases) + # =========================================================================== + + /repositories/{repository_name}/packages: + get: + operationId: listRepositoryPackages + summary: List packages within a repository + description: > + Alias for GET /packages?repository={repository_name}. + tags: [Repositories] + parameters: + - $ref: "#/components/parameters/RepositoryNamePath" + - $ref: "#/components/parameters/Limit" + - $ref: "#/components/parameters/Offset" + - $ref: "#/components/parameters/SortBy" + - name: name + in: query + description: Filter packages by name (substring match). + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/PaginatedPackages" + + /repositories/{repository_name}/package_versions: + get: + operationId: listRepositoryPackageVersions + summary: List package versions within a repository + description: > + Alias for GET /package_versions?repository={repository_name}. + tags: [Repositories] + parameters: + - $ref: "#/components/parameters/RepositoryNamePath" + - $ref: "#/components/parameters/Limit" + - $ref: "#/components/parameters/Offset" + - $ref: "#/components/parameters/SortBy" + - name: name + in: query + description: Filter by package name (substring match). + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/PaginatedPackageVersions" + + /repositories/{repository_name}/advisories: + get: + operationId: listRepositoryAdvisories + summary: List advisories within a repository + description: > + Alias for GET /advisories?repository={repository_name}. + tags: [Repositories] + parameters: + - $ref: "#/components/parameters/RepositoryNamePath" + - $ref: "#/components/parameters/Limit" + - $ref: "#/components/parameters/Offset" + - $ref: "#/components/parameters/SortBy" + - name: package_name + in: query + description: Filter by package name (substring match). + schema: + type: string + - name: severity_min + in: query + description: Minimum severity level. + schema: + type: string + - name: cve_id + in: query + description: Filter by CVE ID (exact match). + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/PaginatedAdvisories" + + # =========================================================================== + # Phase 2 -- File download + # =========================================================================== + + /repositories/{repository_name}/packages/{package_name}/{version}/files/{filename}: + get: + x-phase: 2 + operationId: downloadPackageVersionFile + summary: Download a specific file for a package version + tags: [Files] + parameters: + - $ref: "#/components/parameters/RepositoryNamePath" + - name: package_name + in: path + required: true + schema: + type: string + - name: version + in: path + required: true + schema: + type: string + - name: filename + in: path + required: true + schema: + type: string + responses: + "200": + description: File content. + content: + application/octet-stream: + schema: + type: string + format: binary + "404": + description: File not found. + + # =========================================================================== + # Phase 3 -- File metadata + # =========================================================================== + + /repositories/{repository_name}/packages/{package_name}/{version}/files: + get: + x-phase: 3 + operationId: listPackageVersionFiles + summary: List file metadata for a package version + description: > + Returns file-level metadata (VEX, OSV, signatures). + Data source: Lightwell engineering API. + tags: [Files] + parameters: + - $ref: "#/components/parameters/RepositoryNamePath" + - name: package_name + in: path + required: true + schema: + type: string + - name: version + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + type: object + description: Schema TBD pending Lightwell engineering API design. From a4b8f6d43836adca2332a83e5981b3704f2fcd0b Mon Sep 17 00:00:00 2001 From: etsien Date: Thu, 27 Aug 2026 09:29:38 -0400 Subject: [PATCH 33/47] fix linting issues --- pkg/dao/interfaces.go | 2 +- pkg/handler/repositories.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/dao/interfaces.go b/pkg/dao/interfaces.go index a01871f4e..47773655a 100644 --- a/pkg/dao/interfaces.go +++ b/pkg/dao/interfaces.go @@ -11,9 +11,9 @@ import ( "github.com/content-services/content-sources-backend/pkg/clients/pulp_client" "github.com/content-services/content-sources-backend/pkg/clients/roadmap_client" csdb "github.com/content-services/content-sources-backend/pkg/db" - "github.com/google/uuid" "github.com/content-services/content-sources-backend/pkg/models" "github.com/content-services/tang/pkg/tangy" + "github.com/google/uuid" "github.com/content-services/yummy/pkg/yum" "gorm.io/gorm" ) diff --git a/pkg/handler/repositories.go b/pkg/handler/repositories.go index 2f19c4d57..63595a26b 100644 --- a/pkg/handler/repositories.go +++ b/pkg/handler/repositories.go @@ -12,7 +12,6 @@ import ( "github.com/content-services/content-sources-backend/pkg/config" "github.com/content-services/content-sources-backend/pkg/dao" ce "github.com/content-services/content-sources-backend/pkg/errors" - "github.com/content-services/content-sources-backend/pkg/rbac" "github.com/content-services/content-sources-backend/pkg/tasks" "github.com/content-services/content-sources-backend/pkg/tasks/client" From f7490481ef77dc46a8f1e8c306d997f28e30e8a7 Mon Sep 17 00:00:00 2001 From: etsien Date: Thu, 27 Aug 2026 09:29:47 -0400 Subject: [PATCH 34/47] update api docs --- api/docs.go | 4 ++-- api/openapi.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/docs.go b/api/docs.go index e3ee6c409..7fa97b151 100644 --- a/api/docs.go +++ b/api/docs.go @@ -566,7 +566,7 @@ const docTemplate = `{ { "type": "string", "description": "Filter by content type (maven, python, npm)", - "name": "type", + "name": "content_type", "in": "query" }, { @@ -652,7 +652,7 @@ const docTemplate = `{ { "type": "string", "description": "Filter by content type (maven, python, npm)", - "name": "type", + "name": "content_type", "in": "query" }, { diff --git a/api/openapi.json b/api/openapi.json index f29a8e239..4393e002d 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -3690,7 +3690,7 @@ { "description": "Filter by content type (maven, python, npm)", "in": "query", - "name": "type", + "name": "content_type", "schema": { "type": "string" } @@ -3798,7 +3798,7 @@ { "description": "Filter by content type (maven, python, npm)", "in": "query", - "name": "type", + "name": "content_type", "schema": { "type": "string" } From 42f813ba4e450fbba0eb72e65c88fa7307eb4fc8 Mon Sep 17 00:00:00 2001 From: etsien Date: Thu, 27 Aug 2026 09:33:33 -0400 Subject: [PATCH 35/47] Update interfaces.go --- pkg/dao/interfaces.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/dao/interfaces.go b/pkg/dao/interfaces.go index 47773655a..e5875b76e 100644 --- a/pkg/dao/interfaces.go +++ b/pkg/dao/interfaces.go @@ -13,8 +13,8 @@ import ( csdb "github.com/content-services/content-sources-backend/pkg/db" "github.com/content-services/content-sources-backend/pkg/models" "github.com/content-services/tang/pkg/tangy" - "github.com/google/uuid" "github.com/content-services/yummy/pkg/yum" + "github.com/google/uuid" "gorm.io/gorm" ) From 872eed616cb163de683cc596f373cab4eaa884b6 Mon Sep 17 00:00:00 2001 From: etsien Date: Thu, 27 Aug 2026 09:42:01 -0400 Subject: [PATCH 36/47] rename template --- api/{lightwell-openapi.yaml => openapi.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename api/{lightwell-openapi.yaml => openapi.yaml} (100%) diff --git a/api/lightwell-openapi.yaml b/api/openapi.yaml similarity index 100% rename from api/lightwell-openapi.yaml rename to api/openapi.yaml From 7214c79788147db4ead2947718b0e91811caa0a8 Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 2 Sep 2026 05:06:45 -0400 Subject: [PATCH 37/47] bring back accidentally removed rebased stuff --- .mockery_v3.yml | 3 +++ pkg/dao/repository_configs.go | 3 +++ pkg/dao/repository_configs_test.go | 12 ++++++++++++ 3 files changed, 18 insertions(+) diff --git a/.mockery_v3.yml b/.mockery_v3.yml index 005e0ea3c..7543d5d87 100644 --- a/.mockery_v3.yml +++ b/.mockery_v3.yml @@ -23,6 +23,9 @@ packages: github.com/content-services/content-sources-backend/pkg/clients/roadmap_client: interfaces: RoadmapClient: {} + github.com/content-services/content-sources-backend/pkg/clients/s3_client: + interfaces: + S3Client: {} github.com/content-services/content-sources-backend/pkg/dao: interfaces: AdminTaskDao: {} diff --git a/pkg/dao/repository_configs.go b/pkg/dao/repository_configs.go index d875a068d..ac3736f9d 100644 --- a/pkg/dao/repository_configs.go +++ b/pkg/dao/repository_configs.go @@ -2070,6 +2070,9 @@ func combineIntrospectionAndSnapshotStatuses(repoConfig *models.RepositoryConfig } else if repoConfig.LastSnapshotTask.Status == config.TaskStatusFailed && repoConfig.LastSnapshotUUID != "" { // Both introspection and snapshot failed and repo has previous snapshots return config.StatusUnavailable + } else if repoConfig.LastSnapshotTask.Status == config.TaskStatusFailed && repoConfig.LastSnapshotUUID == "" { + // Introspection failed (never succeeded), last snapshot failed, and repo has no previous snapshots + return config.StatusInvalid } case config.StatusValid: if repoConfig.LastSnapshotTask == nil { diff --git a/pkg/dao/repository_configs_test.go b/pkg/dao/repository_configs_test.go index 302dad36c..31234cc71 100644 --- a/pkg/dao/repository_configs_test.go +++ b/pkg/dao/repository_configs_test.go @@ -3818,6 +3818,18 @@ func (suite *RepositoryConfigSuite) TestCombineStatus() { }, Expected: "Unavailable", }, + { + Name: "Introspection failed, last snapshot failed, and repo has no previous snapshots", + RepoConfig: &models.RepositoryConfiguration{ + Snapshot: true, + LastSnapshotTask: &models.TaskInfo{Status: config.TaskStatusFailed}, + LastSnapshotUUID: "", + }, + Repo: &models.Repository{ + LastIntrospectionStatus: config.StatusInvalid, + }, + Expected: "Invalid", + }, { Name: "Introspection unavailable, last snapshot failed, and repo has previous snapshots", RepoConfig: &models.RepositoryConfiguration{ From 2215df84d08a62aa2f895fa09c04cf04e785c115 Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 2 Sep 2026 05:08:31 -0400 Subject: [PATCH 38/47] reuse existing counts, remove redundant fields --- api/docs.go | 24 ++---------------------- pkg/api/repositories.go | 4 +--- pkg/handler/repositories.go | 14 +++++--------- 3 files changed, 8 insertions(+), 34 deletions(-) diff --git a/api/docs.go b/api/docs.go index 677eed1b2..e95005a7a 100644 --- a/api/docs.go +++ b/api/docs.go @@ -6316,7 +6316,7 @@ const docTemplate = `{ "type": "string", "readOnly": true }, - "advisories_count": { + "advisory_count": { "description": "Lightwell: total security advisories", "type": "integer", "readOnly": true @@ -6446,11 +6446,6 @@ const docTemplate = `{ "description": "Number of packages last read in the repository", "type": "integer" }, - "packages_count": { - "description": "Lightwell: total distinct packages", - "type": "integer", - "readOnly": true - }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "type": "boolean", @@ -6487,11 +6482,6 @@ const docTemplate = `{ "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" }, - "versions_count": { - "description": "Lightwell: total distinct versions", - "type": "integer", - "readOnly": true - }, "warnings": { "description": "Warnings to alert user of mismatched fields if there is an existing repo with the same URL", "type": "array", @@ -6661,7 +6651,7 @@ const docTemplate = `{ "type": "string", "readOnly": true }, - "advisories_count": { + "advisory_count": { "description": "Lightwell: total security advisories", "type": "integer", "readOnly": true @@ -6791,11 +6781,6 @@ const docTemplate = `{ "description": "Number of packages last read in the repository", "type": "integer" }, - "packages_count": { - "description": "Lightwell: total distinct packages", - "type": "integer", - "readOnly": true - }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "type": "boolean", @@ -6831,11 +6816,6 @@ const docTemplate = `{ "version_count": { "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" - }, - "versions_count": { - "description": "Lightwell: total distinct versions", - "type": "integer", - "readOnly": true } } }, diff --git a/pkg/api/repositories.go b/pkg/api/repositories.go index 044b6d827..3f02a0a19 100644 --- a/pkg/api/repositories.go +++ b/pkg/api/repositories.go @@ -45,9 +45,7 @@ type RepositoryResponse struct { SecurityLevel string `json:"security_level,omitempty" readonly:"true"` // Security level of the repository (e.g. validated, remediated) PublishedDistURL string `json:"published_distribution_url,omitempty" readonly:"true"` // Published distribution URL from Pulp PublishedDistBasePath string `json:"-"` // Published dist base path from Pulp - PackagesCount *int `json:"packages_count,omitempty" readonly:"true"` // Lightwell: total distinct packages - VersionsCount *int `json:"versions_count,omitempty" readonly:"true"` // Lightwell: total distinct versions - AdvisoriesCount *int `json:"advisories_count,omitempty" readonly:"true"` // Lightwell: total security advisories + AdvisoryCount *int `json:"advisory_count,omitempty" readonly:"true"` // Lightwell: total security advisories } // RepositoryRequest holds data received from request to create repository diff --git a/pkg/handler/repositories.go b/pkg/handler/repositories.go index 63595a26b..cecf1c974 100644 --- a/pkg/handler/repositories.go +++ b/pkg/handler/repositories.go @@ -127,19 +127,15 @@ func (rh *RepositoryHandler) listRepositories(c echo.Context) error { return c.JSON(200, setCollectionResponseMetadata(&repos, c, totalRepos)) } -// enrichLightwellRepoCounts populates packages_count, versions_count, and -// remediations_count on Lightwell-origin repositories. These spec-required -// fields are omitted for non-Lightwell repos to avoid breaking existing consumers. +// enrichLightwellRepoCounts populates advisory_count on Lightwell-origin +// repositories. package_count and version_count are already populated by the +// DAO layer; only advisory_count requires a separate query. func (rh *RepositoryHandler) enrichLightwellRepoCounts(c echo.Context, repos *api.RepositoryCollectionResponse) { for i := range repos.Data { repo := &repos.Data[i] if repo.Origin != config.OriginLightwell { continue } - pkgCount := repo.PackageCount - verCount := repo.VersionCount - repo.PackagesCount = &pkgCount - repo.VersionsCount = &verCount repoUUID, err := uuid.Parse(repo.UUID) if err != nil { @@ -151,8 +147,8 @@ func (rh *RepositoryHandler) enrichLightwellRepoCounts(c echo.Context, repos *ap log.Ctx(c.Request().Context()).Warn().Err(err).Str("uuid", repo.UUID).Msg("failed to count advisories") continue } - remCount := int(count) - repo.AdvisoriesCount = &remCount + advCount := int(count) + repo.AdvisoryCount = &advCount } } From 2d1bfb4a153bd7a5b74a083b0c97d6d0998945a0 Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 2 Sep 2026 05:08:35 -0400 Subject: [PATCH 39/47] regenerate docs --- api/openapi.json | 24 ++---------------------- api/openapi.yaml | 12 ++++++------ 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/api/openapi.json b/api/openapi.json index a19c2befe..963d51580 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -1315,7 +1315,7 @@ "readOnly": true, "type": "string" }, - "advisories_count": { + "advisory_count": { "description": "Lightwell: total security advisories", "readOnly": true, "type": "integer" @@ -1445,11 +1445,6 @@ "description": "Number of packages last read in the repository", "type": "integer" }, - "packages_count": { - "description": "Lightwell: total distinct packages", - "readOnly": true, - "type": "integer" - }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "readOnly": true, @@ -1486,11 +1481,6 @@ "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" }, - "versions_count": { - "description": "Lightwell: total distinct versions", - "readOnly": true, - "type": "integer" - }, "warnings": { "description": "Warnings to alert user of mismatched fields if there is an existing repo with the same URL", "items": { @@ -1660,7 +1650,7 @@ "readOnly": true, "type": "string" }, - "advisories_count": { + "advisory_count": { "description": "Lightwell: total security advisories", "readOnly": true, "type": "integer" @@ -1790,11 +1780,6 @@ "description": "Number of packages last read in the repository", "type": "integer" }, - "packages_count": { - "description": "Lightwell: total distinct packages", - "readOnly": true, - "type": "integer" - }, "partner": { "description": "Whether this upload repository is marked as a partner repository", "readOnly": true, @@ -1830,11 +1815,6 @@ "version_count": { "description": "Number of versions last read in the repository, not applicable to all repositories", "type": "integer" - }, - "versions_count": { - "description": "Lightwell: total distinct versions", - "readOnly": true, - "type": "integer" } }, "type": "object" diff --git a/api/openapi.yaml b/api/openapi.yaml index ea96beacb..f7c9c6578 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -95,9 +95,9 @@ components: - name - security_level - content_type - - packages_count - - versions_count - - advisories_count + - package_count + - version_count + - advisory_count properties: name: type: string @@ -106,13 +106,13 @@ components: $ref: "#/components/schemas/SecurityLevel" content_type: $ref: "#/components/schemas/ContentType" - packages_count: + package_count: type: integer example: 456 - versions_count: + version_count: type: integer example: 1024 - advisories_count: + advisory_count: type: integer example: 200 From 40d31f4ef463938cbd4c84f85c50011dbbd19d0d Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 2 Sep 2026 05:27:11 -0400 Subject: [PATCH 40/47] fix: rename advisory migration to follow upstream timestamp The 20260827110000 timestamp collided with the upstream 20260827120000_add_lightwell_vulnerability_key migration. CI checks that migrations.latest matches new migration files added by the PR; move to 20260828110000 so it sorts last. --- db/migrations.latest | 2 +- ...260828110000_add_lightwell_advisory_severity_order.down.sql} | 0 ...20260828110000_add_lightwell_advisory_severity_order.up.sql} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename db/migrations/{20260827110000_add_lightwell_advisory_severity_order.down.sql => 20260828110000_add_lightwell_advisory_severity_order.down.sql} (100%) rename db/migrations/{20260827110000_add_lightwell_advisory_severity_order.up.sql => 20260828110000_add_lightwell_advisory_severity_order.up.sql} (100%) diff --git a/db/migrations.latest b/db/migrations.latest index 0273c06a4..d3071b8d3 100644 --- a/db/migrations.latest +++ b/db/migrations.latest @@ -1 +1 @@ -20260827120000 \ No newline at end of file +20260828110000 \ No newline at end of file diff --git a/db/migrations/20260827110000_add_lightwell_advisory_severity_order.down.sql b/db/migrations/20260828110000_add_lightwell_advisory_severity_order.down.sql similarity index 100% rename from db/migrations/20260827110000_add_lightwell_advisory_severity_order.down.sql rename to db/migrations/20260828110000_add_lightwell_advisory_severity_order.down.sql diff --git a/db/migrations/20260827110000_add_lightwell_advisory_severity_order.up.sql b/db/migrations/20260828110000_add_lightwell_advisory_severity_order.up.sql similarity index 100% rename from db/migrations/20260827110000_add_lightwell_advisory_severity_order.up.sql rename to db/migrations/20260828110000_add_lightwell_advisory_severity_order.up.sql From cfab2a27e53f886241418dbdb15a67668051fbeb Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 2 Sep 2026 07:44:33 -0400 Subject: [PATCH 41/47] Create handler-test-patterns.mdc --- .cursor/rules/handler-test-patterns.mdc | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .cursor/rules/handler-test-patterns.mdc diff --git a/.cursor/rules/handler-test-patterns.mdc b/.cursor/rules/handler-test-patterns.mdc new file mode 100644 index 000000000..2609666b1 --- /dev/null +++ b/.cursor/rules/handler-test-patterns.mdc @@ -0,0 +1,29 @@ +--- +description: Handler implementation and test patterns +globs: pkg/handler/**,pkg/api/** +alwaysApply: false +--- + +# Handler and Test Patterns + +## Handler Structure + +- Each handler domain has a struct storing **value copies** (not pointers) of dependencies, plus a `RegisterRoutes` function with panic-on-nil guards. +- Route registration uses `addRepoRoute` / `addTemplateRoute` helpers that simultaneously register the Echo route and the RBAC permission. +- Feature-check middleware is passed per-route as variadic `...echo.MiddlewareFunc`, not applied globally. + +## Request / Response + +- Bind with `c.Bind(&req)`, return 400 on error. +- **Pointer fields** in request structs signal optionality (partial updates use `*string`, `*bool`). +- **Nil-slice guard**: convert `nil` slices to `[]T{}` before returning JSON. +- Collections use `setCollectionResponseMetadata` for pagination links. +- Errors return `ce.NewErrorResponse(code, title, detail)` — `ce` is the canonical import alias for `pkg/errors`. + +## Testing + +- **MockDaoRegistry** (`pkg/dao/registry_mock.go`) is **hand-maintained**. When adding a new DAO, update this file, `DaoRegistry` in `interfaces.go`, and `.mockery_v3.yml`. +- Each handler test suite has a `serveRouter` helper that creates a **fresh `echo.New()`** per test method — suites do not share router state. +- Mock expectations use `test.MockCtx()` (matches `*context.valueCtx`). +- Identity injection: `req.WithContext(identity.WithIdentity(...))` — do not use the `EnforceIdentity` HTTP middleware in unit tests. +- `config.LoadedConfig.Loaded = true` is required for any test that calls `config.Get()`. From 168e52ee8ab57427aa0dc6bb02e729fba7fdc8d6 Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 2 Sep 2026 18:26:37 -0400 Subject: [PATCH 42/47] add properties to PackageVersion --- pkg/api/lightwell_packages.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/api/lightwell_packages.go b/pkg/api/lightwell_packages.go index aadaa286c..53251f6f6 100644 --- a/pkg/api/lightwell_packages.go +++ b/pkg/api/lightwell_packages.go @@ -33,6 +33,8 @@ type LightwellPackageVersionResponse struct { RepositoryUUID string `json:"repository_uuid"` Release string `json:"release,omitempty"` CreatedAt string `json:"created_at,omitempty"` + Purl string `json:"purl"` + Coordinates string `json:"coordinates"` } // LightwellPackageVersionCollectionResponse is a paginated collection of cross-repo package versions. From 349cb8c31b1e3736538d742839f5e249863551bb Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 2 Sep 2026 18:27:43 -0400 Subject: [PATCH 43/47] add PURL and coordinate builders, adjust CVE filters to use repo name instead of just package name (possible collisions) --- pkg/handler/lightwell_packages.go | 71 +++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/pkg/handler/lightwell_packages.go b/pkg/handler/lightwell_packages.go index 078a9ebaa..1d4d33231 100644 --- a/pkg/handler/lightwell_packages.go +++ b/pkg/handler/lightwell_packages.go @@ -359,19 +359,22 @@ func (h *LightwellPackagesHandler) filterVersionsByResolvingCve(ctx context.Cont return nil, err } - fixedSet := make(map[string]map[string]bool) + type repoPackage struct{ repo, name string } + fixedSet := make(map[repoPackage]map[string]bool) for _, m := range matches { - if fixedSet[m.PackageName] == nil { - fixedSet[m.PackageName] = make(map[string]bool) + key := repoPackage{repo: m.RepoName, name: m.PackageName} + if fixedSet[key] == nil { + fixedSet[key] = make(map[string]bool) } for _, v := range m.FixedVersions { - fixedSet[m.PackageName][v] = true + fixedSet[key][v] = true } } var result []api.LightwellPackageVersionResponse for _, item := range items { - if versions, ok := fixedSet[item.Name]; ok && versions[item.Version] { + key := repoPackage{repo: item.Repository, name: item.Name} + if versions, ok := fixedSet[key]; ok && versions[item.Version] { result = append(result, item) } } @@ -386,27 +389,65 @@ func (h *LightwellPackagesHandler) filterVersionsByVulnerableCve(ctx context.Con return nil, err } - affectedPackages := make(map[string]bool) - fixedSet := make(map[string]map[string]bool) + type repoPackage struct{ repo, name string } + affectedPackages := make(map[repoPackage]bool) + fixedSet := make(map[repoPackage]map[string]bool) for _, m := range matches { - affectedPackages[m.PackageName] = true - if fixedSet[m.PackageName] == nil { - fixedSet[m.PackageName] = make(map[string]bool) + key := repoPackage{repo: m.RepoName, name: m.PackageName} + affectedPackages[key] = true + if fixedSet[key] == nil { + fixedSet[key] = make(map[string]bool) } for _, v := range m.FixedVersions { - fixedSet[m.PackageName][v] = true + fixedSet[key][v] = true } } var result []api.LightwellPackageVersionResponse for _, item := range items { - if affectedPackages[item.Name] && !fixedSet[item.Name][item.Version] { + key := repoPackage{repo: item.Repository, name: item.Name} + if affectedPackages[key] && !fixedSet[key][item.Version] { result = append(result, item) } } return result, nil } +// --- PURL / coordinate builders --- + +func buildPURL(contentType, group, name, version string) string { + switch contentType { + case config.ContentTypeMaven: + return fmt.Sprintf("pkg:maven/%s/%s@%s", group, name, version) + case config.ContentTypePython: + return fmt.Sprintf("pkg:pypi/%s@%s", name, version) + case config.ContentTypeNpm: + if group == "-" || group == "" { + return fmt.Sprintf("pkg:npm/%s@%s", name, version) + } + scope := strings.TrimPrefix(group, "@") + return fmt.Sprintf("pkg:npm/%%40%s/%s@%s", scope, name, version) + default: + return "" + } +} + +func buildCoordinates(contentType, group, name string) string { + switch contentType { + case config.ContentTypeMaven: + return fmt.Sprintf("%s:%s", group, name) + case config.ContentTypePython: + return name + case config.ContentTypeNpm: + if group == "-" || group == "" { + return name + } + return fmt.Sprintf("%s/%s", group, name) + default: + return "" + } +} + // --- mapping helpers --- func mapMavenToLightwellPackages(resp tangy.MavenPackageListResponse, repo api.RepositoryResponse) []api.LightwellPackageResponse { @@ -481,6 +522,8 @@ func expandMavenVersions(resp tangy.MavenPackageListResponse, repo api.Repositor ContentType: config.ContentTypeMaven, Repository: repo.Name, RepositoryUUID: repo.UUID, + Purl: buildPURL(config.ContentTypeMaven, item.GroupID, item.ArtifactID, v), + Coordinates: buildCoordinates(config.ContentTypeMaven, item.GroupID, item.ArtifactID), } if rel, ok := relMap[v]; ok { ver.Release = rel.Release @@ -503,6 +546,8 @@ func expandPythonVersions(resp tangy.PythonPackageListResponse, repo api.Reposit ContentType: config.ContentTypePython, Repository: repo.Name, RepositoryUUID: repo.UUID, + Purl: buildPURL(config.ContentTypePython, "", item.NameNormalized, v), + Coordinates: buildCoordinates(config.ContentTypePython, "", item.NameNormalized), } if info, ok := verMap[v]; ok { ver.CreatedAt = info.CreatedAt @@ -526,6 +571,8 @@ func expandNpmVersions(resp tangy.NpmPackageListResponse, repo api.RepositoryRes ContentType: config.ContentTypeNpm, Repository: repo.Name, RepositoryUUID: repo.UUID, + Purl: buildPURL(config.ContentTypeNpm, scope, name, v), + Coordinates: buildCoordinates(config.ContentTypeNpm, scope, name), } if info, ok := verMap[v]; ok { ver.CreatedAt = info.CreatedAt From f11d6e9a48174ca9646ef31240e7b7de3789a8ba Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 2 Sep 2026 18:27:52 -0400 Subject: [PATCH 44/47] add tests --- pkg/handler/lightwell_packages_test.go | 134 +++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/pkg/handler/lightwell_packages_test.go b/pkg/handler/lightwell_packages_test.go index 622b7a15b..e5d103197 100644 --- a/pkg/handler/lightwell_packages_test.go +++ b/pkg/handler/lightwell_packages_test.go @@ -117,6 +117,48 @@ func mavenTangResponse() tangy.MavenPackageListResponse { } } +func newNpmRepo() api.RepositoryResponse { + return api.RepositoryResponse{ + UUID: "ggg-hhh-iii", + Name: "lightwell/npm/remediated", + ContentType: config.ContentTypeNpm, + Origin: config.OriginLightwell, + SecurityLevel: "remediated", + PublishedDistBasePath: "npm/remediated", + OrgID: test_handler.MockOrgId, + } +} + +func npmScopedTangResponse() tangy.NpmPackageListResponse { + return tangy.NpmPackageListResponse{ + Results: []tangy.NpmPackageListItem{ + { + Name: "@types/is-odd", + Versions: []string{"3.0.0.rhlw-00001"}, + LatestVersions: []tangy.NpmVersionInfo{ + {Version: "3.0.0.rhlw-00001", CreatedAt: "2024-07-01T10:00:00Z"}, + }, + }, + }, + Total: 1, Limit: 200, Offset: 0, + } +} + +func npmUnscopedTangResponse() tangy.NpmPackageListResponse { + return tangy.NpmPackageListResponse{ + Results: []tangy.NpmPackageListItem{ + { + Name: "lodash", + Versions: []string{"4.17.21.rhlw-00001"}, + LatestVersions: []tangy.NpmVersionInfo{ + {Version: "4.17.21.rhlw-00001", CreatedAt: "2024-07-02T10:00:00Z"}, + }, + }, + }, + Total: 1, Limit: 200, Offset: 0, + } +} + func pythonTangResponse() tangy.PythonPackageListResponse { return tangy.PythonPackageListResponse{ Results: []tangy.PythonPackageListItem{ @@ -302,6 +344,8 @@ func (s *LightwellPackagesSuite) TestListPackageVersionsSingleRepo() { assert.Len(t, resp.Data, 2) assert.Equal(t, "jackson-databind", resp.Data[0].Name) assert.Equal(t, config.ContentTypeMaven, resp.Data[0].ContentType) + assert.Equal(t, "pkg:maven/com.fasterxml.jackson.core/jackson-databind@"+resp.Data[0].Version, resp.Data[0].Purl) + assert.Equal(t, "com.fasterxml.jackson.core:jackson-databind", resp.Data[0].Coordinates) } func (s *LightwellPackagesSuite) TestListPackageVersionsWithNameFilter() { @@ -426,6 +470,8 @@ func (s *LightwellPackagesSuite) TestListPackageVersionsResolvesCveFilter() { assert.Len(t, resp.Data, 1) assert.Equal(t, "jackson-databind", resp.Data[0].Name) assert.Equal(t, "2.15.3.rhlw-00001", resp.Data[0].Version) + assert.Equal(t, "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.3.rhlw-00001", resp.Data[0].Purl) + assert.Equal(t, "com.fasterxml.jackson.core:jackson-databind", resp.Data[0].Coordinates) } func (s *LightwellPackagesSuite) TestListPackageVersionsVulnerableToCveFilter() { @@ -533,3 +579,91 @@ func (s *LightwellPackagesSuite) TestNestedRepoPackageVersionsAlias() { assert.Equal(t, int64(2), resp.Meta.Count) assert.Len(t, resp.Data, 2) } + +// --- npm PURL / coordinates tests --- + +func (s *LightwellPackagesSuite) TestListPackageVersionsNpmScoped() { + t := s.T() + + npmRepo := newNpmRepo() + s.stubLightwellRepos([]api.RepositoryResponse{npmRepo}) + href := "/api/pulp/repos/npm/1/" + s.stubRepoHref(npmRepo, href) + s.tangClient.On("NpmPackageList", test.MockCtx(), href, + tangy.NpmPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(npmScopedTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Len(t, resp.Data, 1) + assert.Equal(t, "is-odd", resp.Data[0].Name) + assert.Equal(t, "@types", resp.Data[0].Group) + assert.Equal(t, "pkg:npm/%40types/is-odd@3.0.0.rhlw-00001", resp.Data[0].Purl) + assert.Equal(t, "@types/is-odd", resp.Data[0].Coordinates) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsNpmUnscoped() { + t := s.T() + + npmRepo := newNpmRepo() + s.stubLightwellRepos([]api.RepositoryResponse{npmRepo}) + href := "/api/pulp/repos/npm/1/" + s.stubRepoHref(npmRepo, href) + s.tangClient.On("NpmPackageList", test.MockCtx(), href, + tangy.NpmPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(npmUnscopedTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Len(t, resp.Data, 1) + assert.Equal(t, "lodash", resp.Data[0].Name) + assert.Equal(t, "-", resp.Data[0].Group) + assert.Equal(t, "pkg:npm/lodash@4.17.21.rhlw-00001", resp.Data[0].Purl) + assert.Equal(t, "lodash", resp.Data[0].Coordinates) +} + +func (s *LightwellPackagesSuite) TestListPackageVersionsPythonPurl() { + t := s.T() + + pythonRepo := newPythonRepo() + s.stubLightwellRepos([]api.RepositoryResponse{pythonRepo}) + href := "/api/pulp/repos/python/1/" + s.stubRepoHref(pythonRepo, href) + s.tangClient.On("PythonPackageList", test.MockCtx(), href, + tangy.PythonPackageListFilters{}, tangy.PageOptions{Offset: 0, Limit: MaxLimit}, + ).Return(pythonTangResponse(), nil) + + path := fmt.Sprintf("%s/lightwell/package_versions", api.FullRootPath()) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(api.IdentityHeader, test_handler.EncodedIdentity(t)) + + code, body, err := s.serveRouter(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, code) + + var resp api.LightwellPackageVersionCollectionResponse + require.NoError(t, json.Unmarshal(body, &resp)) + + assert.Len(t, resp.Data, 1) + assert.Equal(t, "requests", resp.Data[0].Name) + assert.Equal(t, "pkg:pypi/requests@2.31.0.rhlw-00001", resp.Data[0].Purl) + assert.Equal(t, "requests", resp.Data[0].Coordinates) +} From 228baa50851660d7ff652eb16ddb49ef33020b7d Mon Sep 17 00:00:00 2001 From: etsien Date: Wed, 2 Sep 2026 22:31:47 -0400 Subject: [PATCH 45/47] add re-generated docs --- api/docs.go | 6 ++++++ api/openapi.json | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/api/docs.go b/api/docs.go index e95005a7a..4eeb53917 100644 --- a/api/docs.go +++ b/api/docs.go @@ -5474,6 +5474,9 @@ const docTemplate = `{ "content_type": { "type": "string" }, + "coordinates": { + "type": "string" + }, "created_at": { "type": "string" }, @@ -5483,6 +5486,9 @@ const docTemplate = `{ "name": { "type": "string" }, + "purl": { + "type": "string" + }, "release": { "type": "string" }, diff --git a/api/openapi.json b/api/openapi.json index 963d51580..df553eb18 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -473,6 +473,9 @@ "content_type": { "type": "string" }, + "coordinates": { + "type": "string" + }, "created_at": { "type": "string" }, @@ -482,6 +485,9 @@ "name": { "type": "string" }, + "purl": { + "type": "string" + }, "release": { "type": "string" }, From a3eac03cad49adc76e8c04912976eec0a1b833a6 Mon Sep 17 00:00:00 2001 From: etsien Date: Thu, 10 Sep 2026 10:18:21 -0400 Subject: [PATCH 46/47] regenerate docs --- api/docs.go | 6 ++++++ api/openapi.json | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/api/docs.go b/api/docs.go index 3f4ab2a18..9aa4cfed7 100644 --- a/api/docs.go +++ b/api/docs.go @@ -5471,6 +5471,9 @@ const docTemplate = `{ "api.LightwellPackageVersionResponse": { "type": "object", "properties": { + "coordinates": { + "type": "string" + }, "created_at": { "type": "string" }, @@ -5483,6 +5486,9 @@ const docTemplate = `{ "name": { "type": "string" }, + "purl": { + "type": "string" + }, "release": { "type": "string" }, diff --git a/api/openapi.json b/api/openapi.json index b2594c632..d8492e0a7 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -470,6 +470,9 @@ }, "api.LightwellPackageVersionResponse": { "properties": { + "coordinates": { + "type": "string" + }, "created_at": { "type": "string" }, @@ -482,6 +485,9 @@ "name": { "type": "string" }, + "purl": { + "type": "string" + }, "release": { "type": "string" }, From 4333b107bdbcf54c33e15ccd7c72835a375b94af Mon Sep 17 00:00:00 2001 From: etsien Date: Thu, 10 Sep 2026 10:23:22 -0400 Subject: [PATCH 47/47] fix: remove duplicate severity migration superseded by upstream The branch had 20260828110000_add_lightwell_advisory_severity_order (severity_order SMALLINT) which was superseded by upstream's 20260904120000 version (severity_score REAL). The codebase references only severity_score; remove the dead migration. --- ...lightwell_advisory_severity_order.down.sql | 7 ------- ...d_lightwell_advisory_severity_order.up.sql | 20 ------------------- 2 files changed, 27 deletions(-) delete mode 100644 db/migrations/20260828110000_add_lightwell_advisory_severity_order.down.sql delete mode 100644 db/migrations/20260828110000_add_lightwell_advisory_severity_order.up.sql diff --git a/db/migrations/20260828110000_add_lightwell_advisory_severity_order.down.sql b/db/migrations/20260828110000_add_lightwell_advisory_severity_order.down.sql deleted file mode 100644 index 53b3f3983..000000000 --- a/db/migrations/20260828110000_add_lightwell_advisory_severity_order.down.sql +++ /dev/null @@ -1,7 +0,0 @@ -BEGIN; - -DROP INDEX IF EXISTS idx_lightwell_advisories_package_name; -DROP INDEX IF EXISTS idx_lightwell_advisories_severity_order; -ALTER TABLE lightwell_advisories DROP COLUMN IF EXISTS severity_order; - -COMMIT; diff --git a/db/migrations/20260828110000_add_lightwell_advisory_severity_order.up.sql b/db/migrations/20260828110000_add_lightwell_advisory_severity_order.up.sql deleted file mode 100644 index 544d380e0..000000000 --- a/db/migrations/20260828110000_add_lightwell_advisory_severity_order.up.sql +++ /dev/null @@ -1,20 +0,0 @@ -BEGIN; - -ALTER TABLE lightwell_advisories - ADD COLUMN IF NOT EXISTS severity_order SMALLINT NOT NULL DEFAULT 0; - -UPDATE lightwell_advisories SET severity_order = CASE - WHEN severity = 'critical' THEN 4 - WHEN severity = 'important' THEN 3 - WHEN severity = 'moderate' THEN 2 - WHEN severity = 'low' THEN 1 - ELSE 0 -END; - -CREATE INDEX IF NOT EXISTS idx_lightwell_advisories_severity_order - ON lightwell_advisories (severity_order); - -CREATE INDEX IF NOT EXISTS idx_lightwell_advisories_package_name - ON lightwell_advisories (package_name); - -COMMIT;