Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ jobs:
show-progress: false

- name: docker-config
uses: containerbase/internal-tools@44de9c9ba1ad6e62cabecc5295241b7149e07977 # v3.10.70
uses: containerbase/internal-tools@7c3bf36c5b0f3ddd2c628e34417c085c16d1974d # v3.10.71
with:
command: docker-config

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8
uses: github/codeql-action/init@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9
with:
languages: javascript

Expand All @@ -51,7 +51,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8
uses: github/codeql-action/autobuild@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
Expand All @@ -65,4 +65,4 @@ jobs:
# make release

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8
uses: github/codeql-action/analyze@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9
2 changes: 1 addition & 1 deletion .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ jobs:

# Upload the results to GitHub's code scanning dashboard.
- name: 'Upload to code-scanning'
uses: github/codeql-action/upload-sarif@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8
uses: github/codeql-action/upload-sarif@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9
with:
sarif_file: results.sarif
2 changes: 1 addition & 1 deletion .github/workflows/trivy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
format: 'sarif'
output: 'trivy-results.sarif'

- uses: github/codeql-action/upload-sarif@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8
- uses: github/codeql-action/upload-sarif@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9
with:
sarif_file: trivy-results.sarif
category: 'docker-image-${{ matrix.tag }}'
37 changes: 37 additions & 0 deletions docs/usage/config-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,43 @@ By default, it is essentially an empty config with only the Renovate JSON schema

If you configure `onboardingConfig` in either Global config or Inherited config then Renovate will use that config directly instead of the default.

If you self-host Renovate in GitLab using [`renovate-runner`](https://gitlab.com/gitlab-com/gl-infra/renovate/renovate-runner), the CI will contain a default [RENOVATE_ONBOARDING_CONFIG](https://gitlab.com/renovate-bot/renovate-runner/-/blob/main/templates/renovate.gitlab-ci.yml#L5) that will merge with your own configuration settings. For example, the CI by default contains:

```yml
RENOVATE_ONBOARDING_CONFIG: '{"$$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["config:recommended"] }'
```

If you want to change the `extends` in your own configuration, you need to override the variable in your own `.gitlab-ci.yml`:

```yml
variables:
RENOVATE_ONBOARDING_CONFIG: '{"$$schema":"https://docs.renovatebot.com/renovate-schema.json","extends":["platform>organization/repo:renovate-config"]}'
```

Your `renovate.js` where you run Renovate cannot contain any `extends` definition, it will pick the `extends` from the `RENOVATE_ONBOARDING_CONFIG` variable. For example, your config can look like this:

```js
module.exports = {
...
onboardingConfig: {
"argocd": {
"fileMatch": [
"application\\.yaml$"
]
},
};
```

The resulting onboarding config will be:

```yml
{
'$schema': 'https://docs.renovatebot.com/renovate-schema.json',
'argocd': { 'managerFilePatterns': ["/application\\.yaml$/"] },
'extends': ['platform>organization/repo:renovate-config'],
}
```

Alternatively if you follow Renovate's naming convention for shared presets then it can automatically detect those instead.
If the repository `{{parentOrg}}/renovate-config` has a `default.json` file then this will be treated as the organization's default preset and included in the Onboarding config.
Additionally for platforms which support nested Organization/Group hierarchies, Renovate will "hunt" up such hierarchies for a `renovate-config` repository with default config and stop when it finds the first.
Expand Down
34 changes: 34 additions & 0 deletions lib/modules/datasource/docker/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1530,6 +1530,40 @@ describe('modules/datasource/docker/index', () => {
await expect(getPkgReleases(config)).rejects.toThrow(EXTERNAL_HOST_ERROR);
});

it('uses quay api with fallback from v1 to v2 on 401 Unauthorized', async () => {
httpMock
.scope('https://quay.io')
.get(
'/api/v1/repository/bitnami/redis/tag/?limit=100&page=1&onlyActiveTags=true',
)
.reply(401, 'Unauthorized')
.get('/v2/')
.reply(200, '', {})
.get('/v2/bitnami/redis/tags/list?n=10000')
.reply(200, {})
.get('/v2/bitnami/redis/tags/list?n=10000')
.reply(200, { tags: ['1.0.0', '2.0.0'] })
.get('/v2/bitnami/redis/manifests/2.0.0')
.reply(200, '', {});

const config = {
datasource: DockerDatasource.id,
packageName: 'bitnami/redis',
registryUrls: ['https://quay.io'],
};
const res = await getPkgReleases(config);
expect(res?.releases).toHaveLength(2);

// Verify the debug log for fallback was called
expect(logger.logger.debug).toHaveBeenCalledWith(
expect.objectContaining({
registryHost: 'https://quay.io',
dockerRepository: 'bitnami/redis',
}),
'Quay v1 API unauthorized, falling back to Docker v2 API',
);
});

it('jfrog artifactory - retry tags for official images by injecting `/library` after repository and before image', async () => {
const tags1 = [...range(1, 10000)].map((i) => `${i}.0.0`);
const tags2 = [...range(10000, 20000)].map((i) => `${i}.0.0`);
Expand Down
22 changes: 17 additions & 5 deletions lib/modules/datasource/docker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { HttpError } from '../../../util/http';
import { memCacheProvider } from '../../../util/http/cache/memory-http-cache-provider';
import type { HttpResponse } from '../../../util/http/types';
import { hasKey } from '../../../util/object';
import { regEx } from '../../../util/regex';
import { type AsyncResult, Result } from '../../../util/result';
import { isDockerDigest } from '../../../util/string-match';
import { asTimestamp } from '../../../util/timestamp';
Expand Down Expand Up @@ -672,6 +671,7 @@ export class DockerDatasource extends Datasource {
let page = 0;
const hostsNeedingAllPages = [
'https://ghcr.io', // GHCR sorts from oldest to newest, so we need to get all pages
'https://quay.io', // Quay sorts from oldest to newest, so we need to get all pages
];
const pages = hostsNeedingAllPages.includes(registryHost)
? 1000
Expand Down Expand Up @@ -733,12 +733,24 @@ export class DockerDatasource extends Datasource {
dockerRepository: string,
): Promise<string[] | null> {
try {
const isQuay = regEx(/^https:\/\/quay\.io(?::[1-9][0-9]{0,4})?$/i).test(
registryHost,
);
const isQuay = registryHost === 'https://quay.io';
let tags: string[] | null;
if (isQuay) {
tags = await this.getTagsQuayRegistry(registryHost, dockerRepository);
try {
// Due to pagination and sorting limits on Quay Docker v2 API implementation we try the Quay v1 API first
tags = await this.getTagsQuayRegistry(registryHost, dockerRepository);
} catch (err) {
// If we get a 401 Unauthorized error (v1 API requires separate auth for private images), fall back to Docker v2 API
if (err.statusCode === 401) {
logger.debug(
{ registryHost, dockerRepository },
'Quay v1 API unauthorized, falling back to Docker v2 API',
);
tags = await this.getDockerApiTags(registryHost, dockerRepository);
} else {
throw err;
}
}
} else {
tags = await this.getDockerApiTags(registryHost, dockerRepository);
}
Expand Down
116 changes: 116 additions & 0 deletions lib/modules/manager/bazel-module/extract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,5 +618,121 @@ describe('modules/manager/bazel-module/extract', () => {
],
});
});

it('returns rules_img pull dependencies', async () => {
const input = codeBlock`
pull = use_repo_rule("@rules_img//img:pull.bzl", "pull")

pull(
name = "ubuntu",
digest = "sha256:1e622c5f073b4f6bfad6632f2616c7f59ef256e96fe78bf6a595d1dc4376ac02",
registry = "index.docker.io",
repository = "library/ubuntu",
tag = "24.04",
)
`;
const res = await extractPackageFile(input, 'MODULE.bazel');

expect(res).toEqual({
deps: [
{
datasource: 'docker',
depType: 'rules_img_pull',
depName: 'ubuntu',
packageName: 'index.docker.io/library/ubuntu',
currentValue: '24.04',
currentDigest:
'sha256:1e622c5f073b4f6bfad6632f2616c7f59ef256e96fe78bf6a595d1dc4376ac02',
replaceString: expect.stringContaining('pull('),
},
],
});
});

it('returns rules_img pull dependencies without tag', async () => {
const input = codeBlock`
pull = use_repo_rule("@rules_img//img:pull.bzl", "pull")

pull(
name = "distroless_cc",
digest = "sha256:d1b8e4c52be1111aa108e959ef2a822299bb70fd1819dd250871a2601ca1e4b6",
registry = "gcr.io",
repository = "distroless/cc-debian12",
)
`;
const res = await extractPackageFile(input, 'MODULE.bazel');

expect(res).toEqual({
deps: [
{
datasource: 'docker',
depType: 'rules_img_pull',
depName: 'distroless_cc',
packageName: 'gcr.io/distroless/cc-debian12',
currentDigest:
'sha256:d1b8e4c52be1111aa108e959ef2a822299bb70fd1819dd250871a2601ca1e4b6',
replaceString: expect.stringContaining('pull('),
},
],
});
});

it('ignores non-rules_img use_repo_rule calls', async () => {
const input = codeBlock`
other_rule = use_repo_rule("@some_other//path:rule.bzl", "other_rule")

pull(
name = "ubuntu",
digest = "sha256:1e622c5f073b4f6bfad6632f2616c7f59ef256e96fe78bf6a595d1dc4376ac02",
registry = "index.docker.io",
repository = "library/ubuntu",
tag = "24.04",
)
`;
const res = await extractPackageFile(input, 'MODULE.bazel');

expect(res).toBeNull();
});

it('ignores non-rules_img use_repo_rule calls that use the name pull', async () => {
const input = codeBlock`
pull = use_repo_rule("@some_other//path:rule.bzl", "pull")

pull(
name = "test",
value = "ignored",
)
`;
const res = await extractPackageFile(input, 'MODULE.bazel');

expect(res).toBeNull();
});

it('handles multiple rules_img pulls', async () => {
const input = codeBlock`
pull = use_repo_rule("@rules_img//img:pull.bzl", "pull")

pull(
name = "ubuntu",
digest = "sha256:1e622c5f073b4f6bfad6632f2616c7f59ef256e96fe78bf6a595d1dc4376ac02",
registry = "index.docker.io",
repository = "library/ubuntu",
tag = "24.04",
)

pull(
name = "cuda",
digest = "sha256:f353ffca86e0cd93ab2470fe274ecf766519c24c37ed58cc2f91d915f7ebe53c",
registry = "index.docker.io",
repository = "nvidia/cuda",
tag = "12.8.1-cudnn-devel-ubuntu20.04",
)
`;
const res = await extractPackageFile(input, 'MODULE.bazel');

expect(res?.deps).toHaveLength(2);
expect(res?.deps[0].depName).toBe('ubuntu');
expect(res?.deps[1].depName).toBe('cuda');
});
});
});
10 changes: 10 additions & 0 deletions lib/modules/manager/bazel-module/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { parse } from './parser';
import type { ResultFragment } from './parser/fragments';
import { RuleToMavenPackageDep, fillRegistryUrls } from './parser/maven';
import { RuleToDockerPackageDep } from './parser/oci';
import { RulesImgPullToDockerPackageDep } from './parser/rules-img';
import {
GitRepositoryToPackageDep,
RuleToBazelModulePackageDep,
Expand All @@ -24,6 +25,7 @@ export async function extractPackageFile(
const gitRepositoryDeps = extractGitRepositoryDeps(records);
const mavenDeps = extractMavenDeps(records);
const dockerDeps = LooseArray(RuleToDockerPackageDep).parse(records);
const rulesImgDeps = extractRulesImgDeps(records);

if (gitRepositoryDeps.length) {
pfc.deps.push(...gitRepositoryDeps);
Expand All @@ -37,6 +39,10 @@ export async function extractPackageFile(
pfc.deps.push(...dockerDeps);
}

if (rulesImgDeps.length) {
pfc.deps.push(...rulesImgDeps);
}

return pfc.deps.length ? pfc : null;
} catch (err) {
logger.debug({ err, packageFile }, 'Failed to parse bazel module file.');
Expand Down Expand Up @@ -76,3 +82,7 @@ function extractMavenDeps(records: ResultFragment[]): PackageDependency[] {
.transform(fillRegistryUrls)
.parse(records);
}

function extractRulesImgDeps(records: ResultFragment[]): PackageDependency[] {
return LooseArray(RulesImgPullToDockerPackageDep).parse(records);
}
Loading
Loading