Commit Graph

16 Commits

Author SHA1 Message Date
James Griffiths 1afa293dda fix(datasource/docker): adjust tag caching for private registries (#45348)
* fix(docker): adjust tag caching for private registries

Tag caching for private registries currently has two issues, observed in
issues #45249 and #40734:
1. `getTags` has no `cacheable` guard (unlike `getReleases`)
2. `getTags` caches `null` tag results

These two combined mean that regardless of `cachePrivatePackages`
settings, private registry tag lookups which fail due to transient/auth
errors are cached. This results in the cache being "poisoned" by
misconfigured jobs, with well-configured jobs unable to recover. This is
especially relevant for the hosted app where caching is shared by
different tenants.

This commit:
1. Aligns `getTags` caching with `getReleases`, ensuring caching
   behaviour is driven by `cachePrivatePackages`.
2. Prevents `null` tag results being cached, allowing for recovery
   regardless of `cachePrivatePackages` configuration.

Note that I have _not_ applied (2) to `getReleases`, because
1. `getReleases` has a slightly different contract in that it sets
   `fallback: true` to intentionally cache failures
2. I have not observed any actual failures in `getReleases` due to (20,
   so it hasn't been empirically proven as requiring a fix
3. `getTags` is internal so is a lower-risk change

Fixes
- #45249

Relates to
- #45249
- #40734

* refactor(docker): undefined for no-auth

Previously `getAuthHeaders()` used `null` as an auth-failed sentinel,
which was inconsistent with `withCache()`, and led to the original "fix"
where nulls were explicitly omitted from the cache.

Instead this implements the suggestion from https://github.com/renovatebot/renovate/pull/45348#issuecomment-5340524967
to uniformly return undefined for `getTags` auth errors, which allows
the cache to recognise failures itself. Then cacheability ends up being
conditional on dockerhub as always.

This also rectifies a mismatch between auth-failure signals, where other
functions like `_getImageConfig` were returning `undefined` when headers
were unavailable.

IMPORTANT: once this lands, since there aren't any cache invalidations,
the existing issue will still be observed until the cache TTL expires
and old `null` entries are evicted.

* fix: whoops, sort out the vacuous tests

I failed to realise caching isn't automatically orchestrated in these
tests. As an eagle-eyed observer [pointed out][1], this meant the tests
weren't actually exercising anything.

This commit fixes that by adding a new test block which does the cache
orchestration using a temp dir cache. I've also opted to add assertions
against the mock server's http trace counter directly to ensure the mock
really is/isn't being hit. This should also serve to make the tests fail
fast if the underlying impl flow (auth probe -> get tags) is adjusted in
the future.

I've also brought in some caching dependencies into the test file, but
those are used elsewhere too (e.g. `withCache` tests).

This time, I have actually verified the tests which we'd expect to fail
do fail against main. Note that the first 'caches tags for Docker Hub'
is expected to pass on both branches (the control test).

```
     ❯ getTags caching (3)
       ✓ caches tags for Docker Hub 21ms 249 MB heap used
       × does not cache tags for a private registry 20ms 212 MB heap used
       × does not cache failed lookups when cachePrivatePackages is enabled 15ms 215 MB heap used
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

 FAIL  lib/modules/datasource/docker/index.spec.ts > modules/datasource/docker/index > getTags caching > does not cache tags for a private registry
AssertionError: expected [ '1.0.0' ] to deeply equal [ '1.0.0', '2.0.0' ]

- Expected
+ Received

  [
    "1.0.0",
-   "2.0.0",
  ]

 ❯ lib/modules/datasource/docker/index.spec.ts:3694:72
    3692|       expect(httpMock.getTrace()).toHaveLength(2);
    3693|
    3694|       expect(await ds.getTags('https://registry.company.com', 'node')).toEqual([
       |                                                                        ^
    3695|         '1.0.0',
    3696|         '2.0.0',

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/4]⎯

 FAIL  lib/modules/datasource/docker/index.spec.ts > modules/datasource/docker/index > getTags caching > does not cache tags for a private registry
Error: *** Unused HTTP mocks ***

- GET https://registry.company.com/v2/node/tags/list

Requests done:

- GET https://registry.company.com/v2/node/tags/list?n=10000 [200]
- GET https://registry.company.com/v2/node/tags/list?n=10000 [200]

---

Renovate testing strategy requires that every HTTP request
has a corresponding mock.

This error occurs because some of the created mocks are unused.

In most cases, you simply need to remove them.

+++
 ❯ <test> lib/modules/datasource/docker/index.spec.ts:3675:9
    3673|     });
    3674|
    3675|     it('does not cache tags for a private registry', async () => {
       |         ^
    3676|       httpMock
    3677|         .scope('https://registry.company.com/v2')

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/4]⎯

 FAIL  lib/modules/datasource/docker/index.spec.ts > modules/datasource/docker/index > getTags caching > does not cache failed lookups when cachePrivatePackages is enabled
AssertionError: expected null to be undefined

- Expected:
undefined

+ Received:
null

 ❯ lib/modules/datasource/docker/index.spec.ts:3717:9
    3715|       expect(
    3716|         await ds.getTags('https://registry.company.com', 'node'),
    3717|       ).toBeUndefined();
       |         ^
    3718|       expect(httpMock.getTrace()).toHaveLength(1);
    3719|

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/4]⎯

 FAIL  lib/modules/datasource/docker/index.spec.ts > modules/datasource/docker/index > getTags caching > does not cache failed lookups when cachePrivatePackages is enabled
Error: *** Unused HTTP mocks ***

- GET https://registry.company.com/v2/node/tags/list

Requests done:

- GET https://registry.company.com/v2/node/tags/list?n=10000 [403]

---

Renovate testing strategy requires that every HTTP request
has a corresponding mock.

This error occurs because some of the created mocks are unused.

In most cases, you simply need to remove them.

+++
 ❯ <test> lib/modules/datasource/docker/index.spec.ts:3701:9
    3699|     });
    3700|
    3701|     it('does not cache failed lookups when cachePrivatePackages is enabled', async () => {
       |         ^
    3702|       GlobalConfig.set({ cachePrivatePackages: true });
    3703|

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/4]⎯

 Test Files  1 failed (1)
      Tests  2 failed | 313 passed (315)
```

[1]: https://github.com/renovatebot/renovate/pull/45348#pullrequestreview-5092478059

* oxlint-fix

---------

Co-authored-by: Jamie Tanna <jamie.tanna@mend.io>
2026-09-07 12:07:52 +00:00
Sebastian Poxhofer 5ad36a2387 test(linters): add linting rule to enforce usage of host rules test util (#44692)
* ci(linter): add renovate/no-host-rules-mock rule

Flags vi.mock()/vi.doMock() calls whose module specifier contains
"host-rules" in spec files. Tests should configure real rules via
hostRules.add() instead of mocking the host-rules module.

Enabled at "warn" severity instead of "error": the repo currently has
35 pre-existing violations across 34 spec files, which exceeds the
agreed fix threshold of 12 files. No violation fixes are included;
migrating each spec to real hostRules.add()/clear() setup is a
semantic test rewrite that should be done incrementally, after which
the rule can be promoted to "error".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(linter): enforce renovate/no-host-rules-mock at error

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(datasource): use real host-rules instead of mocking

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(manager): use real host-rules instead of mocking

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(platform): use real host-rules instead of mocking

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(util,workers): use real host-rules instead of mocking

Also narrow renovate/no-host-rules-mock to the host-rules module itself so that mocking e.g. host-rules-from-env is not flagged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(linter): mention '~test/host-rules.ts' util in no-host-rules-mock message

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 10:36:12 +00:00
Jonathan Morley 5b4e0947cb fix(docker): handle maxResults error on auth probe for ECR repo (#43657)
* fix(docker): handle 405 maxResults error on auth probe for ECR-compatible proxies

Some private Docker registries that front ECR reject `n=10000` on the tags
URL even during the auth-probe request, returning a 405 with the ECR-style
"Member must have value less than or equal to 1000" error body.
`getAuthHeaders` had no handler for this case and returned null, so the
lookup aborted before the main fetch loop's existing n=1000 retry could fire.

Fix: extract `isECRMaxResultsResponse` from `isECRMaxResultsError` in ecr.ts
and use it in `getAuthHeaders` to detect the 405 pattern. When matched, fall
back to probing the base `/v2/` endpoint for auth, then let the existing
`isECRMaxResultsError` retry in the tag-fetch loop handle the n=1000 fallback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(docker): address PR review comments on ECR proxy auth probe fix

- Shorten test token to 'abc' to avoid CodeQL secret-detection false positive
- Replace HttpResponse<any> with HttpResponse<unknown> in common.ts and ecr.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(docker): replace HttpResponse<any> with HttpResponse<unknown> in isECRMaxResultsResponse

Cast body to a typed shape inline instead of widening the whole response to any.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(docker): replace toMatchInlineSnapshot with toEqual in ECR auth probe test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 16:18:10 +00:00
Sebastian Poxhofer 03baf6ee4a test(noNewURL): add new rule enforcing parseUrl() from 'lib/util/url.ts' (#43247)
* test(noNewURL): add new rule enforcing parseUrl() from 'lib/util/url.ts'

* replace newURL with parseUrl

* remove non-null assertions marks if appropiate

* test: add additional tests

* test: add additional tests and ignore where it makes sense

* implement suggestions

* test: further improve coverage

* apply suggestion
2026-05-13 16:23:10 +00:00
Michael Kriese a065a3dd41 fix(datasource/docker): use custom www-authenticate parser (#41813)
* fix(datasource/docker): use custom `www-authenticate` parser

* chore: drop license comments

* test: extend test

* fix: wrong regex

* test: fix tests

* Update lib/util/http/www-authenticate.ts

Co-authored-by: Jamie Tanna <jamie.tanna@mend.io>

* test: add more tests

---------

Co-authored-by: Jamie Tanna <jamie.tanna@mend.io>
2026-03-11 16:48:15 +00:00
Sergei Zharinov 9d857c1f68 refactor(lint): Organize imports with biome (#40966) 2026-02-06 13:40:01 +00:00
Sergei Zharinov 930cf6633c refactor: Rewrite imports with .ts extensions (#40700)
fix: Additional vi.mock path corrections and prettier formatting
2026-01-26 22:48:17 +00:00
Michael Kriese 19c115c813 test: cleanup (#34785) 2025-03-13 17:52:09 +00:00
Michael Kriese 1644d9cb18 test: migrate more to vitest (#34500) 2025-02-26 15:45:17 +00:00
Michael Kriese 997c23502e test: migrate to vitest (#34475)
Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>
2025-02-26 09:35:54 +00:00
Michael Kriese 3df6cd6201 fix(datasource/docker): map registry-1.docker.io to index.docker.io (#26585) 2024-01-10 20:50:24 +00:00
Sergei Zharinov b69416ce17 refactor(prettier): Force trailing commas (#25631) 2023-11-07 15:50:29 +00:00
Michael Kriese 84a97a62fa test(perf): improve test speed (#24323) 2023-09-08 11:40:04 +00:00
Michael Kriese 1295698744 fix(datasource/docker): correctly split registry and repository (#24186) 2023-09-04 15:47:38 +00:00
Michael Kriese 2b245095e0 feat(datasource/docker): use schema and support annotations (#23758)
Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
2023-08-31 10:44:09 +00:00
Michael Kriese 05f365c8c2 refactor(datasouce/docker): split file (#21842) 2023-04-27 18:27:19 +00:00