renovate 44.11.1 → 44.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/datasource/docker/index.js +20 -4
- package/dist/modules/datasource/docker/index.js.map +1 -1
- package/dist/modules/datasource/nuget/index.js +2 -1
- package/dist/modules/datasource/nuget/index.js.map +1 -1
- package/dist/modules/datasource/nuget/v2.js +9 -3
- package/dist/modules/datasource/nuget/v2.js.map +1 -1
- package/dist/modules/datasource/util.js +45 -1
- package/dist/modules/datasource/util.js.map +1 -1
- package/dist/util/http/github.js +27 -22
- package/dist/util/http/github.js.map +1 -1
- package/dist/util/http/gitlab.js +9 -4
- package/dist/util/http/gitlab.js.map +1 -1
- package/dist/util/url.d.ts +17 -1
- package/dist/util/url.js +27 -1
- package/dist/util/url.js.map +1 -1
- package/package.json +1 -1
- package/renovate-schema.json +2 -2
|
@@ -13,7 +13,7 @@ import "../../../util/http/index.js";
|
|
|
13
13
|
import { asTimestamp } from "../../../util/timestamp.js";
|
|
14
14
|
import { Datasource } from "../datasource.js";
|
|
15
15
|
import { memCacheProvider } from "../../../util/http/cache/memory-http-cache-provider.js";
|
|
16
|
-
import { isArtifactoryServer } from "../util.js";
|
|
16
|
+
import { isArtifactoryServer, isCrossOriginPaginationAllowed, resolvePaginationUrl } from "../util.js";
|
|
17
17
|
import { hasKey } from "../../../util/object.js";
|
|
18
18
|
import { ecrPublicRegex, ecrRegex, isECRMaxResultsError } from "./ecr.js";
|
|
19
19
|
import { DockerHubTagsPage, ManifestJson, OciHelmConfig, OciImageConfig, QuayTagsResponse, RegistryTagsList } from "./schema.js";
|
|
@@ -373,6 +373,7 @@ var DockerDatasource = class DockerDatasource extends Datasource {
|
|
|
373
373
|
dockerRepository,
|
|
374
374
|
pages
|
|
375
375
|
}, "docker.getTags");
|
|
376
|
+
const allowCrossOrigin = isCrossOriginPaginationAllowed(dockerDatasourceId);
|
|
376
377
|
let foundMaxResultsError = false;
|
|
377
378
|
do {
|
|
378
379
|
let res;
|
|
@@ -403,8 +404,14 @@ var DockerDatasource = class DockerDatasource extends Datasource {
|
|
|
403
404
|
parsed.searchParams.set("last", linkHeader.next.last);
|
|
404
405
|
url = parsed.href;
|
|
405
406
|
} else url = null;
|
|
406
|
-
else if (linkHeader?.next?.url)
|
|
407
|
-
|
|
407
|
+
else if (linkHeader?.next?.url) {
|
|
408
|
+
const nextUrl = resolvePaginationUrl(url, linkHeader.next.url, allowCrossOrigin);
|
|
409
|
+
if (!nextUrl) logger.once.warn({
|
|
410
|
+
registryHost,
|
|
411
|
+
nextUrl: linkHeader.next.url
|
|
412
|
+
}, "Ignoring cross-origin or invalid Docker registry tags pagination link");
|
|
413
|
+
url = nextUrl;
|
|
414
|
+
} else url = null;
|
|
408
415
|
page += 1;
|
|
409
416
|
} while (url && page < pages);
|
|
410
417
|
return tags;
|
|
@@ -576,6 +583,7 @@ var DockerDatasource = class DockerDatasource extends Datasource {
|
|
|
576
583
|
let url = `https://hub.docker.com/v2/repositories/${dockerRepository}/tags?page_size=1000&ordering=last_updated`;
|
|
577
584
|
const cache = await DockerHubCache.init(dockerRepository);
|
|
578
585
|
const maxPages = GlobalConfig.get("dockerMaxPages");
|
|
586
|
+
const allowCrossOrigin = isCrossOriginPaginationAllowed(dockerDatasourceId);
|
|
579
587
|
let page = 0, needNextPage = true;
|
|
580
588
|
while (needNextPage && page < maxPages) {
|
|
581
589
|
const { val, err } = await this.http.getJsonSafe(url, DockerHubTagsPage).unwrap();
|
|
@@ -587,7 +595,15 @@ var DockerDatasource = class DockerDatasource extends Datasource {
|
|
|
587
595
|
const { results, next, count } = val;
|
|
588
596
|
needNextPage = cache.reconcile(results, count);
|
|
589
597
|
if (!next) break;
|
|
590
|
-
|
|
598
|
+
const nextUrl = resolvePaginationUrl(url, next, allowCrossOrigin);
|
|
599
|
+
if (!nextUrl) {
|
|
600
|
+
logger.once.warn({
|
|
601
|
+
dockerRepository,
|
|
602
|
+
nextUrl: next
|
|
603
|
+
}, "Ignoring cross-origin or invalid Docker Hub tags pagination link");
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
url = nextUrl;
|
|
591
607
|
}
|
|
592
608
|
await cache.save();
|
|
593
609
|
return cache.getItems().map(({ name: version, tag_last_pushed }) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["dockerVersioningId","HttpError"],"sources":["../../../../lib/modules/datasource/docker/index.ts"],"sourcesContent":["import { isNonEmptyString } from '@sindresorhus/is';\nimport { GlobalConfig } from '../../../config/global.ts';\nimport { PAGE_NOT_FOUND_ERROR } from '../../../constants/error-messages.ts';\nimport { logger } from '../../../logger/index.ts';\nimport { ExternalHostError } from '../../../types/errors/external-host-error.ts';\nimport { withCache } from '../../../util/cache/package/with-cache.ts';\nimport { getEnv } from '../../../util/env.ts';\nimport { memCacheProvider } from '../../../util/http/cache/memory-http-cache-provider.ts';\nimport { HttpError } from '../../../util/http/index.ts';\nimport type { HttpResponse } from '../../../util/http/types.ts';\nimport { hasKey } from '../../../util/object.ts';\nimport { type AsyncResult, Result } from '../../../util/result.ts';\nimport { isDockerDigest } from '../../../util/string-match.ts';\nimport { asTimestamp } from '../../../util/timestamp.ts';\nimport {\n ensurePathPrefix,\n joinUrlParts,\n parseLinkHeader,\n parseUrl,\n} from '../../../util/url.ts';\nimport { id as dockerVersioningId } from '../../versioning/docker/index.ts';\nimport { Datasource } from '../datasource.ts';\nimport type {\n DigestConfig,\n GetReleasesConfig,\n Release,\n ReleaseResult,\n} from '../types.ts';\nimport { isArtifactoryServer } from '../util.ts';\nimport {\n DOCKER_HUB,\n dockerDatasourceId,\n extractDigestFromResponseBody,\n findHelmSourceUrl,\n findLatestStable,\n getAuthHeaders,\n getRegistryRepository,\n gitRefLabel,\n imageUrlLabel,\n isDockerHost,\n sourceLabel,\n sourceLabels,\n} from './common.ts';\nimport { DockerHubCache } from './dockerhub-cache.ts';\nimport { ecrPublicRegex, ecrRegex, isECRMaxResultsError } from './ecr.ts';\nimport type {\n DistributionManifest,\n Manifest,\n OciImageManifest,\n} from './schema.ts';\nimport {\n DockerHubTagsPage,\n ManifestJson,\n OciHelmConfig,\n OciImageConfig,\n QuayTagsResponse,\n RegistryTagsList,\n} from './schema.ts';\n\nconst defaultConfig = {\n commitMessageTopic: '{{{depName}}} Docker tag',\n commitMessageExtra:\n 'to {{#if isPinDigest}}{{{newDigestShort}}}{{else}}{{#if isMajor}}{{{prettyNewMajor}}}{{else}}{{{prettyNewVersion}}}{{/if}}{{/if}}',\n digest: {\n branchTopic: '{{{depNameSanitized}}}-{{{currentValue}}}',\n commitMessageExtra: 'to {{newDigestShort}}',\n commitMessageTopic:\n '{{{depName}}}{{#if currentValue}}:{{{currentValue}}}{{/if}} Docker digest',\n group: {\n commitMessageTopic: '{{{groupName}}}',\n commitMessageExtra: '',\n },\n },\n pin: {\n commitMessageExtra: '',\n groupName: 'Docker digests',\n group: {\n commitMessageTopic: '{{{groupName}}}',\n branchTopic: 'digests-pin',\n },\n },\n};\n\nexport class DockerDatasource extends Datasource {\n static readonly id = dockerDatasourceId;\n\n override readonly defaultVersioning = dockerVersioningId;\n\n override readonly defaultRegistryUrls = [DOCKER_HUB];\n\n override readonly defaultConfig = defaultConfig;\n\n override readonly releaseTimestampSupport = true;\n override readonly releaseTimestampNote =\n 'Only supported on Docker Hub: The release timestamp is determined from the `tag_last_pushed` field in the results. **NOTE**: Currently, digests will receive the same release timestamp as the `tag_last_pushed`, which means that digests may appear newer than they are - see https://github.com/renovatebot/renovate/issues/38659';\n override readonly sourceUrlSupport = 'package';\n override readonly sourceUrlNote =\n 'The source URL is determined from the `org.opencontainers.image.source` and `org.label-schema.vcs-url` labels present in the metadata of the **latest stable** image found on the Docker registry.';\n\n constructor() {\n super(DockerDatasource.id);\n }\n\n // TODO: debug why quay throws errors (#9612)\n private async getManifestResponse(\n registryHost: string,\n dockerRepository: string,\n tag: string,\n mode: 'head' | 'getText' = 'getText',\n ): Promise<HttpResponse | null> {\n logger.debug(\n `getManifestResponse(${registryHost}, ${dockerRepository}, ${tag}, ${mode})`,\n );\n try {\n const headers = await getAuthHeaders(\n this.http,\n registryHost,\n dockerRepository,\n );\n if (!headers) {\n logger.warn('No docker auth found - returning');\n return null;\n }\n headers.accept = [\n 'application/vnd.docker.distribution.manifest.list.v2+json',\n 'application/vnd.docker.distribution.manifest.v2+json',\n 'application/vnd.oci.image.manifest.v1+json',\n 'application/vnd.oci.image.index.v1+json',\n ].join(', ');\n const url = `${registryHost}/v2/${dockerRepository}/manifests/${tag}`;\n const manifestResponse = await this.http[mode](url, {\n headers,\n noAuth: true,\n cacheProvider: memCacheProvider,\n });\n return manifestResponse;\n } catch (err) /* istanbul ignore next */ {\n if (err instanceof ExternalHostError) {\n throw err;\n }\n if (err.statusCode === 401) {\n logger.debug(\n { registryHost, dockerRepository },\n 'Unauthorized docker lookup',\n );\n logger.debug({ err });\n return null;\n }\n if (err.statusCode === 404) {\n logger.debug(\n {\n err,\n registryHost,\n dockerRepository,\n tag,\n },\n 'Docker Manifest is unknown',\n );\n return null;\n }\n if (err.statusCode === 429 && isDockerHost(registryHost)) {\n throw new ExternalHostError(err);\n }\n if (err.statusCode >= 500 && err.statusCode < 600) {\n throw new ExternalHostError(err);\n }\n if (err.code === 'ETIMEDOUT') {\n logger.debug(\n { registryHost },\n 'Timeout when attempting to connect to docker registry',\n );\n logger.debug({ err });\n return null;\n }\n logger.debug(\n {\n err,\n registryHost,\n dockerRepository,\n tag,\n },\n 'Unknown Error looking up docker manifest',\n );\n return null;\n }\n }\n\n private async _getImageConfig(\n registryHost: string,\n dockerRepository: string,\n configDigest: string,\n ): Promise<HttpResponse<OciImageConfig> | undefined> {\n logger.trace(\n `getImageConfig(${registryHost}, ${dockerRepository}, ${configDigest})`,\n );\n\n const headers = await getAuthHeaders(\n this.http,\n registryHost,\n dockerRepository,\n );\n /* v8 ignore next 4 -- should never happen */\n if (!headers) {\n logger.warn('No docker auth found - returning');\n return undefined;\n }\n const url = joinUrlParts(\n registryHost,\n 'v2',\n dockerRepository,\n 'blobs',\n configDigest,\n );\n return await this.http.getJson(\n url,\n {\n headers,\n noAuth: true,\n },\n OciImageConfig,\n );\n }\n\n getImageConfig(\n registryHost: string,\n dockerRepository: string,\n configDigest: string,\n ): Promise<HttpResponse<OciImageConfig> | undefined> {\n return withCache(\n {\n namespace: 'datasource-docker-imageconfig',\n key: `${registryHost}:${dockerRepository}@${configDigest}`,\n ttlMinutes: 1440 * 28,\n },\n () => this._getImageConfig(registryHost, dockerRepository, configDigest),\n );\n }\n\n private async _getHelmConfig(\n registryHost: string,\n dockerRepository: string,\n configDigest: string,\n ): Promise<HttpResponse<OciHelmConfig> | undefined> {\n logger.trace(\n `getImageConfig(${registryHost}, ${dockerRepository}, ${configDigest})`,\n );\n\n const headers = await getAuthHeaders(\n this.http,\n registryHost,\n dockerRepository,\n );\n /* v8 ignore next 4 -- should never happen */\n if (!headers) {\n logger.warn('No docker auth found - returning');\n return undefined;\n }\n const url = joinUrlParts(\n registryHost,\n 'v2',\n dockerRepository,\n 'blobs',\n configDigest,\n );\n return await this.http.getJson(\n url,\n {\n headers,\n noAuth: true,\n },\n OciHelmConfig,\n );\n }\n\n getHelmConfig(\n registryHost: string,\n dockerRepository: string,\n configDigest: string,\n ): Promise<HttpResponse<OciHelmConfig> | undefined> {\n return withCache(\n {\n namespace: 'datasource-docker-imageconfig',\n key: `${registryHost}:${dockerRepository}@${configDigest}`,\n ttlMinutes: 1440 * 28,\n },\n () => this._getHelmConfig(registryHost, dockerRepository, configDigest),\n );\n }\n\n private async getConfigDigest(\n registry: string,\n dockerRepository: string,\n tag: string,\n ): Promise<string | null> {\n return (\n (await this.getManifest(registry, dockerRepository, tag))?.config\n ?.digest ?? null\n );\n }\n\n private async getManifestDocument(\n registry: string,\n dockerRepository: string,\n tag: string,\n ): Promise<Manifest | null> {\n const manifestResponse = await this.getManifestResponse(\n registry,\n dockerRepository,\n tag,\n );\n\n // If getting the manifest fails here, then abort\n // This means that the latest tag doesn't have a manifest, which shouldn't\n // be possible\n /* v8 ignore next 3 -- should never happen */\n if (!manifestResponse) {\n return null;\n }\n\n // Softfail on invalid manifests\n const parsed = ManifestJson.safeParse(manifestResponse.body);\n if (!parsed.success) {\n logger.debug(\n {\n registry,\n dockerRepository,\n tag,\n body: manifestResponse.body,\n headers: manifestResponse.headers,\n err: parsed.error,\n },\n 'Invalid manifest response',\n );\n return null;\n }\n\n return parsed.data;\n }\n\n private async resolveImageManifest(\n registry: string,\n dockerRepository: string,\n tag: string,\n manifest: Manifest,\n ): Promise<OciImageManifest | DistributionManifest | null> {\n switch (manifest.mediaType) {\n case 'application/vnd.docker.distribution.manifest.v2+json':\n case 'application/vnd.oci.image.manifest.v1+json':\n return manifest;\n case 'application/vnd.docker.distribution.manifest.list.v2+json':\n case 'application/vnd.oci.image.index.v1+json':\n if (!manifest.manifests.length) {\n logger.debug(\n { manifest },\n 'Invalid manifest list with no manifests - returning',\n );\n return null;\n }\n logger.trace(\n { registry, dockerRepository, tag },\n 'Found manifest list, using first image',\n );\n return this.getManifest(\n registry,\n dockerRepository,\n manifest.manifests[0].digest,\n );\n // istanbul ignore next: can't happen\n default:\n return null;\n }\n }\n\n private async getManifest(\n registry: string,\n dockerRepository: string,\n tag: string,\n ): Promise<OciImageManifest | DistributionManifest | null> {\n const manifest = await this.getManifestDocument(\n registry,\n dockerRepository,\n tag,\n );\n if (!manifest) {\n return null;\n }\n return this.resolveImageManifest(registry, dockerRepository, tag, manifest);\n }\n\n private async _getImageArchitecture(\n registryHost: string,\n dockerRepository: string,\n currentDigest: string,\n ): Promise<string | null | undefined> {\n try {\n let manifestResponse: HttpResponse | null;\n\n try {\n manifestResponse = await this.getManifestResponse(\n registryHost,\n dockerRepository,\n currentDigest,\n 'head',\n );\n } catch (_err) {\n const err =\n _err instanceof ExternalHostError\n ? _err.err\n : /* istanbul ignore next: can never happen */ _err;\n\n if (\n typeof err.statusCode === 'number' &&\n err.statusCode >= 500 &&\n err.statusCode < 600\n ) {\n // querying the digest manifest for a non existent image leads to a 500 statusCode\n return null;\n }\n\n /* istanbul ignore next */\n throw _err;\n }\n\n if (\n manifestResponse?.headers['content-type'] !==\n 'application/vnd.docker.distribution.manifest.v2+json' &&\n manifestResponse?.headers['content-type'] !==\n 'application/vnd.oci.image.manifest.v1+json'\n ) {\n return null;\n }\n\n const configDigest = await this.getConfigDigest(\n registryHost,\n dockerRepository,\n currentDigest,\n );\n if (!configDigest) {\n return null;\n }\n\n const configResponse = await this.getImageConfig(\n registryHost,\n dockerRepository,\n configDigest,\n );\n\n // TODO: fix me, architecture is required in spec\n if (\n configResponse &&\n ('config' in configResponse.body ||\n 'architecture' in configResponse.body)\n ) {\n const architecture = configResponse.body.architecture ?? null;\n logger.debug(\n `Current digest ${currentDigest} relates to architecture ${\n architecture ?? 'null'\n }`,\n );\n\n return architecture;\n }\n } catch (err) /* istanbul ignore next */ {\n if (err.statusCode !== 404 || err.message === PAGE_NOT_FOUND_ERROR) {\n throw err;\n }\n logger.debug(\n { registryHost, dockerRepository, currentDigest, err },\n 'Unknown error getting image architecture',\n );\n }\n\n return undefined;\n }\n\n getImageArchitecture(\n registryHost: string,\n dockerRepository: string,\n currentDigest: string,\n ): Promise<string | null | undefined> {\n return withCache(\n {\n namespace: 'datasource-docker-architecture',\n key: `${registryHost}:${dockerRepository}@${currentDigest}`,\n ttlMinutes: 1440 * 28,\n shouldCacheResult: isNonEmptyString,\n },\n () =>\n this._getImageArchitecture(\n registryHost,\n dockerRepository,\n currentDigest,\n ),\n );\n }\n\n /*\n * docker.getLabels\n *\n * This function will:\n * - Return the labels for the requested image\n */\n private async _getLabels(\n registryHost: string,\n dockerRepository: string,\n tag: string,\n ): Promise<Record<string, string> | undefined> {\n logger.debug(`getLabels(${registryHost}, ${dockerRepository}, ${tag})`);\n // Skip Docker Hub image if RENOVATE_X_DOCKER_HUB_DISABLE_LABEL_LOOKUP is set\n if (\n getEnv().RENOVATE_X_DOCKER_HUB_DISABLE_LABEL_LOOKUP &&\n registryHost === DOCKER_HUB\n ) {\n logger.debug(\n 'Docker Hub image - skipping label lookup due to RENOVATE_X_DOCKER_HUB_DISABLE_LABEL_LOOKUP',\n );\n return {};\n }\n try {\n let labels: Record<string, string> | undefined = {};\n const manifestDocument = await this.getManifestDocument(\n registryHost,\n dockerRepository,\n tag,\n );\n\n if (!manifestDocument) {\n logger.debug(\n { registryHost, dockerRepository, tag },\n 'No manifest found',\n );\n return undefined;\n }\n\n if ('annotations' in manifestDocument && manifestDocument.annotations) {\n labels = manifestDocument.annotations;\n }\n\n if ('manifests' in manifestDocument) {\n const descriptorAnnotations = manifestDocument.manifests\n .map((descriptor) => descriptor.annotations)\n .find(\n (annotations) =>\n isNonEmptyString(annotations?.[sourceLabel]) &&\n isNonEmptyString(annotations?.[gitRefLabel]),\n );\n if (descriptorAnnotations) {\n labels = { ...labels, ...descriptorAnnotations };\n }\n }\n\n if (\n 'manifests' in manifestDocument &&\n labels[sourceLabel] &&\n labels[gitRefLabel]\n ) {\n return labels;\n }\n\n const manifest = await this.resolveImageManifest(\n registryHost,\n dockerRepository,\n tag,\n manifestDocument,\n );\n if (!manifest) {\n return undefined;\n }\n\n if ('annotations' in manifest && manifest.annotations) {\n labels = { ...labels, ...manifest.annotations };\n }\n\n switch (manifest.config.mediaType) {\n case 'application/vnd.cncf.helm.config.v1+json': {\n if (labels[sourceLabel]) {\n // we already have the source url, so no need to pull the config\n return labels;\n }\n const configResponse = await this.getHelmConfig(\n registryHost,\n dockerRepository,\n manifest.config.digest,\n );\n\n if (configResponse) {\n // Helm chart\n const url = findHelmSourceUrl(configResponse.body);\n if (url) {\n labels[sourceLabel] = url;\n }\n }\n break;\n }\n case 'application/vnd.oci.image.config.v1+json':\n case 'application/vnd.docker.container.image.v1+json': {\n if (labels[sourceLabel] && labels[gitRefLabel]) {\n // we already have the source url, so no need to pull the config\n return labels;\n }\n const configResponse = await this.getImageConfig(\n registryHost,\n dockerRepository,\n manifest.config.digest,\n );\n\n /* v8 ignore next 3 -- should never happen */\n if (!configResponse) {\n return labels;\n }\n\n const body = configResponse.body;\n if (body.config) {\n labels = { ...labels, ...body.config.Labels };\n } else {\n logger.debug(\n { headers: configResponse.headers, body },\n `manifest blob response body missing the \"config\" property`,\n );\n }\n break;\n }\n }\n\n if (labels) {\n logger.debug(\n {\n labels,\n },\n 'found labels in manifest',\n );\n }\n return labels;\n } catch (err) /* istanbul ignore next: should be tested in future */ {\n if (err instanceof ExternalHostError) {\n throw err;\n }\n if (err.statusCode === 400 || err.statusCode === 401) {\n logger.debug(\n { registryHost, dockerRepository, err },\n 'Unauthorized docker lookup',\n );\n } else if (err.statusCode === 404) {\n logger.warn(\n {\n err,\n registryHost,\n dockerRepository,\n tag,\n },\n 'Config Manifest is unknown',\n );\n } else if (err.statusCode === 429 && isDockerHost(registryHost)) {\n logger.warn({ err }, 'docker registry failure: too many requests');\n } else if (err.statusCode >= 500 && err.statusCode < 600) {\n logger.debug(\n {\n err,\n registryHost,\n dockerRepository,\n tag,\n },\n 'docker registry failure: internal error',\n );\n } else if (\n err.code === 'ERR_TLS_CERT_ALTNAME_INVALID' ||\n err.code === 'ETIMEDOUT'\n ) {\n logger.debug(\n { registryHost, err },\n 'Error connecting to docker registry',\n );\n } else if (registryHost === 'https://quay.io') {\n // istanbul ignore next\n logger.debug(\n 'Ignoring quay.io errors until they fully support v2 schema',\n );\n } else {\n logger.info(\n { registryHost, dockerRepository, tag, err },\n 'Unknown error getting Docker labels',\n );\n }\n return {};\n }\n }\n\n getLabels(\n registryHost: string,\n dockerRepository: string,\n tag: string,\n ): Promise<Record<string, string> | undefined> {\n return withCache(\n {\n namespace: 'datasource-docker-labels',\n key: `${registryHost}:${dockerRepository}:${tag}`,\n ttlMinutes: 24 * 60,\n },\n () => this._getLabels(registryHost, dockerRepository, tag),\n );\n }\n\n private async getTagsQuayRegistry(\n registry: string,\n repository: string,\n ): Promise<string[]> {\n let tags: string[] = [];\n const limit = 100;\n\n function pageUrl(page: number): string {\n return `${registry}/api/v1/repository/${repository}/tag/?limit=${limit}&page=${page}&onlyActiveTags=true`;\n }\n\n let page = 1;\n let url: string | null = pageUrl(page);\n while (url && page <= 20) {\n const res: HttpResponse<QuayTagsResponse> = await this.http.getJson(\n url,\n QuayTagsResponse,\n );\n const pageTags = res.body.tags.map((tag) => tag.name);\n tags = tags.concat(pageTags);\n page += 1;\n url = res.body.has_additional ? pageUrl(page) : null;\n }\n return tags;\n }\n\n private async getDockerApiTags(\n registryHost: string,\n dockerRepository: string,\n ): Promise<string[] | null> {\n let tags: string[] = [];\n // AWS ECR limits the maximum number of results to 1000\n // See https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_DescribeRepositories.html#ECR-DescribeRepositories-request-maxResults\n // See https://docs.aws.amazon.com/AmazonECRPublic/latest/APIReference/API_DescribeRepositories.html#ecrpublic-DescribeRepositories-request-maxResults\n const limit =\n ecrRegex.test(registryHost) || ecrPublicRegex.test(registryHost)\n ? 1000\n : 10000;\n let url: string | null =\n `${registryHost}/${dockerRepository}/tags/list?n=${limit}`;\n url = ensurePathPrefix(url, '/v2');\n const headers = await getAuthHeaders(\n this.http,\n registryHost,\n dockerRepository,\n url,\n );\n if (!headers) {\n logger.debug('Failed to get authHeaders for getTags lookup');\n return null;\n }\n let page = 0;\n const hostsNeedingAllPages = [\n 'https://ghcr.io', // GHCR sorts from oldest to newest, so we need to get all pages\n 'https://quay.io', // Quay sorts from oldest to newest, so we need to get all pages\n ];\n const pages = hostsNeedingAllPages.includes(registryHost)\n ? 1000\n : GlobalConfig.get('dockerMaxPages');\n logger.trace({ registryHost, dockerRepository, pages }, 'docker.getTags');\n let foundMaxResultsError = false;\n do {\n let res: HttpResponse<RegistryTagsList>;\n try {\n res = await this.http.getJson(\n url,\n { headers, noAuth: true },\n RegistryTagsList,\n );\n } catch (err) {\n if (\n !foundMaxResultsError &&\n err instanceof HttpError &&\n isECRMaxResultsError(err)\n ) {\n const maxResults = 1000;\n url = `${registryHost}/${dockerRepository}/tags/list?n=${maxResults}`;\n url = ensurePathPrefix(url, '/v2');\n foundMaxResultsError = true;\n continue;\n }\n throw err;\n }\n tags = tags.concat(res.body.tags);\n const linkHeader = parseLinkHeader(res.headers.link);\n if (isArtifactoryServer(res)) {\n // Artifactory bug: next link comes back without virtual-repo prefix (RTFACT-18971)\n if (linkHeader?.next?.last) {\n // parse the current URL, strip any old \"last\" param, then set the new one\n const parsed = parseUrl(url);\n // v8 ignore if: url is always a valid HTTP URL as `ensurePathPrefix`\n if (!parsed) {\n url = null;\n break;\n }\n parsed.searchParams.delete('last');\n parsed.searchParams.set('last', linkHeader.next.last);\n url = parsed.href;\n } else {\n url = null;\n }\n } else if (linkHeader?.next?.url) {\n // for the normal case we can still use URL to resolve relative-next\n url = new URL(linkHeader.next.url, url).href;\n } else {\n url = null;\n }\n page += 1;\n } while (url && page < pages);\n return tags;\n }\n\n private async _getTags(\n registryHost: string,\n dockerRepository: string,\n ): Promise<string[] | null> {\n try {\n const isQuay = registryHost === 'https://quay.io';\n let tags: string[] | null;\n if (isQuay) {\n try {\n // Due to pagination and sorting limits on Quay Docker v2 API implementation we try the Quay v1 API first\n tags = await this.getTagsQuayRegistry(registryHost, dockerRepository);\n } catch (err) {\n // If we get a 401 Unauthorized error (v1 API requires separate auth for private images), fall back to Docker v2 API\n if (err.statusCode === 401) {\n logger.debug(\n { registryHost, dockerRepository },\n 'Quay v1 API unauthorized, falling back to Docker v2 API',\n );\n tags = await this.getDockerApiTags(registryHost, dockerRepository);\n } else {\n throw err;\n }\n }\n } else {\n tags = await this.getDockerApiTags(registryHost, dockerRepository);\n }\n return tags;\n } catch (_err) /* istanbul ignore next */ {\n const err = _err instanceof ExternalHostError ? _err.err : _err;\n\n if (\n (err.statusCode === 404 || err.message === PAGE_NOT_FOUND_ERROR) &&\n !dockerRepository.includes('/')\n ) {\n logger.debug(\n `Retrying Tags for ${registryHost}/${dockerRepository} using library/ prefix`,\n );\n return this.getTags(registryHost, `library/${dockerRepository}`);\n }\n // JFrog Artifactory - Retry handling when resolving Docker Official Images\n // These follow the format of {{registryHost}}{{jFrogRepository}}/library/{{dockerRepository}}\n if (\n (err.statusCode === 404 || err.message === PAGE_NOT_FOUND_ERROR) &&\n isArtifactoryServer(err.response) &&\n dockerRepository.split('/').length === 2\n ) {\n logger.debug(\n `JFrog Artifactory: Retrying Tags for ${registryHost}/${dockerRepository} using library/ path between JFrog virtual repository and image`,\n );\n\n const dockerRepositoryParts = dockerRepository.split('/');\n const jfrogRepository = dockerRepositoryParts[0];\n const dockerImage = dockerRepositoryParts[1];\n\n return this.getTags(\n registryHost,\n `${jfrogRepository}/library/${dockerImage}`,\n );\n }\n if (err.statusCode === 429 && isDockerHost(registryHost)) {\n logger.warn(\n { registryHost, dockerRepository, err },\n 'docker registry failure: too many requests',\n );\n throw new ExternalHostError(err);\n }\n if (err.statusCode >= 500 && err.statusCode < 600) {\n logger.warn(\n { registryHost, dockerRepository, err },\n 'docker registry failure: internal error',\n );\n throw new ExternalHostError(err);\n }\n const errorCodes = ['ECONNRESET', 'ETIMEDOUT'];\n if (errorCodes.includes(err.code)) {\n logger.warn(\n { registryHost, dockerRepository, err },\n 'docker registry connection failure',\n );\n throw new ExternalHostError(err);\n }\n if (isDockerHost(registryHost)) {\n logger.info({ err }, 'Docker Hub lookup failure');\n }\n throw _err;\n }\n }\n\n getTags(\n registryHost: string,\n dockerRepository: string,\n ): Promise<string[] | null> {\n return withCache(\n {\n namespace: 'datasource-docker-tags',\n key: `${registryHost}:${dockerRepository}`,\n },\n () => this._getTags(registryHost, dockerRepository),\n );\n }\n\n /**\n * docker.getDigest\n *\n * The `newValue` supplied here should be a valid tag for the docker image.\n *\n * This function will:\n * - Look up a sha256 digest for a tag on its registry\n * - Return the digest as a string\n */\n private async _getDigest(\n { registryUrl, lookupName, packageName, currentDigest }: DigestConfig,\n newValue?: string,\n ): Promise<string | null> {\n let registryHost: string;\n let dockerRepository: string;\n if (registryUrl && lookupName) {\n // Reuse the resolved values from getReleases()\n registryHost = registryUrl;\n dockerRepository = lookupName;\n } else {\n // Resolve values independently\n ({ registryHost, dockerRepository } = getRegistryRepository(\n packageName,\n registryUrl!,\n ));\n }\n logger.debug(\n // TODO: types (#22198)\n `getDigest(${registryHost}, ${dockerRepository}, ${newValue})`,\n );\n const newTag = isNonEmptyString(newValue) ? newValue : 'latest';\n let digest: string | null = null;\n try {\n let architecture: string | null | undefined = null;\n if (currentDigest && isDockerDigest(currentDigest)) {\n architecture = await this.getImageArchitecture(\n registryHost,\n dockerRepository,\n currentDigest,\n );\n }\n\n let manifestResponse: HttpResponse | null = null;\n if (!architecture) {\n // Reuse the digest cached from the Docker Hub tag API\n if (registryHost === DOCKER_HUB) {\n const cache = await DockerHubCache.init(dockerRepository);\n const cachedDigest = cache.getDigestForTag(newTag);\n if (cachedDigest) {\n return cachedDigest;\n }\n }\n\n manifestResponse = await this.getManifestResponse(\n registryHost,\n dockerRepository,\n newTag,\n 'head',\n );\n\n if (\n manifestResponse &&\n hasKey('docker-content-digest', manifestResponse.headers)\n ) {\n digest =\n (manifestResponse.headers['docker-content-digest'] as string) ||\n null;\n }\n }\n\n if (\n isNonEmptyString(architecture) ||\n (manifestResponse &&\n !hasKey('docker-content-digest', manifestResponse.headers))\n ) {\n // Reuse the per-arch digest cached from the Docker Hub tag API\n if (isNonEmptyString(architecture) && registryHost === DOCKER_HUB) {\n const cache = await DockerHubCache.init(dockerRepository);\n const cachedDigest = cache.getArchDigestForTag(newTag, architecture);\n if (cachedDigest) {\n return cachedDigest;\n }\n }\n\n logger.debug(\n { registryHost, dockerRepository },\n 'Architecture-specific digest or missing docker-content-digest header - pulling full manifest',\n );\n manifestResponse = await this.getManifestResponse(\n registryHost,\n dockerRepository,\n newTag,\n );\n\n if (architecture && manifestResponse) {\n const parsed = ManifestJson.safeParse(manifestResponse.body);\n /* istanbul ignore else: hard to test */\n if (parsed.success) {\n const manifestList = parsed.data;\n if (\n manifestList.mediaType ===\n 'application/vnd.docker.distribution.manifest.list.v2+json' ||\n manifestList.mediaType ===\n 'application/vnd.oci.image.index.v1+json'\n ) {\n for (const manifest of manifestList.manifests) {\n if (manifest.platform?.architecture === architecture) {\n digest = manifest.digest;\n break;\n }\n }\n // TODO: return null if no matching architecture digest found\n // https://github.com/renovatebot/renovate/discussions/22639\n } else if (\n hasKey('docker-content-digest', manifestResponse.headers)\n ) {\n // TODO: return null if no matching architecture, requires to fetch the config manifest\n // https://github.com/renovatebot/renovate/discussions/22639\n digest = manifestResponse.headers[\n 'docker-content-digest'\n ] as string;\n }\n } else {\n logger.debug(\n {\n registryHost,\n dockerRepository,\n newTag,\n body: manifestResponse.body,\n headers: manifestResponse.headers,\n err: parsed.error,\n },\n 'Failed to parse manifest response',\n );\n }\n }\n\n if (!digest) {\n logger.debug(\n { registryHost, dockerRepository, newTag },\n 'Extraction digest from manifest response body is deprecated',\n );\n digest = extractDigestFromResponseBody(manifestResponse!);\n }\n }\n\n if (\n !manifestResponse &&\n !dockerRepository.includes('/') &&\n !packageName.includes('/')\n ) {\n logger.debug(\n `Retrying Digest for ${registryHost}/${dockerRepository} using library/ prefix`,\n );\n return this.getDigest(\n {\n registryUrl,\n packageName: `library/${packageName}`,\n currentDigest,\n },\n newValue,\n );\n }\n\n if (manifestResponse) {\n // TODO: fix types (#22198)\n logger.debug(`Got docker digest ${digest!}`);\n }\n } catch (err) /* istanbul ignore next */ {\n if (err instanceof ExternalHostError) {\n throw err;\n }\n logger.debug(\n {\n err,\n packageName,\n newTag,\n },\n 'Unknown Error looking up docker image digest',\n );\n }\n return digest;\n }\n\n override getDigest(\n config: DigestConfig,\n newValue?: string,\n ): Promise<string | null> {\n const newTag = newValue ?? 'latest';\n const { registryHost, dockerRepository } = getRegistryRepository(\n config.packageName,\n config.registryUrl!,\n );\n const digest = config.currentDigest ? `@${config.currentDigest}` : '';\n return withCache(\n {\n namespace: 'datasource-docker-digest',\n key: `${registryHost}:${dockerRepository}:${newTag}${digest}`,\n fallback: true,\n shouldCacheResult: isNonEmptyString,\n },\n () => this._getDigest(config, newValue),\n );\n }\n\n private async _getDockerHubTags(\n dockerRepository: string,\n ): Promise<Release[] | null> {\n let url = `https://hub.docker.com/v2/repositories/${dockerRepository}/tags?page_size=1000&ordering=last_updated`;\n\n const cache = await DockerHubCache.init(dockerRepository);\n const maxPages = GlobalConfig.get('dockerMaxPages');\n let page = 0,\n needNextPage = true;\n while (needNextPage && page < maxPages) {\n const { val, err } = await this.http\n .getJsonSafe(url, DockerHubTagsPage)\n .unwrap();\n\n if (err) {\n logger.debug({ err }, `Docker: error fetching data from DockerHub`);\n return null;\n }\n page++;\n const { results, next, count } = val;\n\n needNextPage = cache.reconcile(results, count);\n\n if (!next) {\n break;\n }\n\n url = next;\n }\n\n await cache.save();\n\n const items = cache.getItems();\n return items.map(({ name: version, tag_last_pushed }) => {\n const release: Release = { version };\n\n const releaseTimestamp = asTimestamp(tag_last_pushed);\n if (releaseTimestamp) {\n release.releaseTimestamp = releaseTimestamp;\n }\n\n // Digest is intentionally not propagated — the Docker Hub tag API\n // returns the manifest-list digest, which would bypass arch-aware\n // resolution in `getDigest()`. `getDigest()` consults the same cache\n // as a shortcut when no arch resolution is needed.\n return release;\n });\n }\n\n getDockerHubTags(dockerRepository: string): Promise<Release[] | null> {\n return withCache(\n {\n namespace: 'datasource-docker-hub-tags',\n key: `${dockerRepository}`,\n },\n () => this._getDockerHubTags(dockerRepository),\n );\n }\n\n /**\n * docker.getReleases\n *\n * A docker image usually looks something like this: somehost.io/owner/repo:8.1.0-alpine\n * In the above:\n * - 'somehost.io' is the registry\n * - 'owner/repo' is the package name\n * - '8.1.0-alpine' is the tag\n *\n * This function will filter only tags that contain a semver version\n */\n private async _getReleases({\n packageName,\n registryUrl,\n }: GetReleasesConfig): Promise<ReleaseResult | null> {\n const { registryHost, dockerRepository } = getRegistryRepository(\n packageName,\n registryUrl!,\n );\n\n type TagsResultType = AsyncResult<\n Release[],\n NonNullable<Error | 'tags-error' | 'dockerhub-error'>\n >;\n\n const getTags = (): TagsResultType =>\n Result.wrapNullable(\n this.getTags(registryHost, dockerRepository),\n 'tags-error' as const,\n ).transform((tags) => tags.map((version) => ({ version })));\n\n const getDockerHubTags = (): TagsResultType =>\n Result.wrapNullable(\n this.getDockerHubTags(dockerRepository),\n 'dockerhub-error' as const,\n ).catch(getTags);\n\n const tagsResult =\n registryHost === DOCKER_HUB &&\n !getEnv().RENOVATE_X_DOCKER_HUB_TAGS_DISABLE\n ? getDockerHubTags()\n : getTags();\n\n const { val: releases, err } = await tagsResult.unwrap();\n if (err instanceof Error) {\n throw err;\n } else if (err) {\n return null;\n }\n\n const ret: ReleaseResult = {\n registryUrl: registryHost,\n releases,\n };\n if (dockerRepository !== packageName) {\n // This will be reused later if a getDigest() call is made\n ret.lookupName = dockerRepository;\n }\n\n const tags = releases.map((release) => release.version);\n const latestTag = tags.includes('latest')\n ? 'latest'\n : (findLatestStable(tags) ?? tags.at(-1));\n\n /* v8 ignore next 3 -- TODO: add test */\n if (!latestTag) {\n return ret;\n }\n const labels = await this.getLabels(\n registryHost,\n dockerRepository,\n latestTag,\n );\n if (labels) {\n if (isNonEmptyString(labels[gitRefLabel])) {\n ret.gitRef = labels[gitRefLabel];\n }\n for (const label of sourceLabels) {\n if (isNonEmptyString(labels[label])) {\n ret.sourceUrl = labels[label];\n break;\n }\n }\n if (isNonEmptyString(labels[imageUrlLabel])) {\n ret.homepage = labels[imageUrlLabel];\n }\n }\n return ret;\n }\n\n getReleases(config: GetReleasesConfig): Promise<ReleaseResult | null> {\n const { registryHost, dockerRepository } = getRegistryRepository(\n config.packageName,\n config.registryUrl!,\n );\n return withCache(\n {\n namespace: 'datasource-docker-releases-v2',\n key: `${registryHost}:${dockerRepository}`,\n cacheable: registryHost === DOCKER_HUB,\n fallback: true,\n },\n () => this._getReleases(config),\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["dockerVersioningId","HttpError"],"sources":["../../../../lib/modules/datasource/docker/index.ts"],"sourcesContent":["import { isNonEmptyString } from '@sindresorhus/is';\nimport { GlobalConfig } from '../../../config/global.ts';\nimport { PAGE_NOT_FOUND_ERROR } from '../../../constants/error-messages.ts';\nimport { logger } from '../../../logger/index.ts';\nimport { ExternalHostError } from '../../../types/errors/external-host-error.ts';\nimport { withCache } from '../../../util/cache/package/with-cache.ts';\nimport { getEnv } from '../../../util/env.ts';\nimport { memCacheProvider } from '../../../util/http/cache/memory-http-cache-provider.ts';\nimport { HttpError } from '../../../util/http/index.ts';\nimport type { HttpResponse } from '../../../util/http/types.ts';\nimport { hasKey } from '../../../util/object.ts';\nimport { type AsyncResult, Result } from '../../../util/result.ts';\nimport { isDockerDigest } from '../../../util/string-match.ts';\nimport { asTimestamp } from '../../../util/timestamp.ts';\nimport {\n ensurePathPrefix,\n joinUrlParts,\n parseLinkHeader,\n parseUrl,\n} from '../../../util/url.ts';\nimport { id as dockerVersioningId } from '../../versioning/docker/index.ts';\nimport { Datasource } from '../datasource.ts';\nimport type {\n DigestConfig,\n GetReleasesConfig,\n Release,\n ReleaseResult,\n} from '../types.ts';\nimport {\n isArtifactoryServer,\n isCrossOriginPaginationAllowed,\n resolvePaginationUrl,\n} from '../util.ts';\nimport {\n DOCKER_HUB,\n dockerDatasourceId,\n extractDigestFromResponseBody,\n findHelmSourceUrl,\n findLatestStable,\n getAuthHeaders,\n getRegistryRepository,\n gitRefLabel,\n imageUrlLabel,\n isDockerHost,\n sourceLabel,\n sourceLabels,\n} from './common.ts';\nimport { DockerHubCache } from './dockerhub-cache.ts';\nimport { ecrPublicRegex, ecrRegex, isECRMaxResultsError } from './ecr.ts';\nimport type {\n DistributionManifest,\n Manifest,\n OciImageManifest,\n} from './schema.ts';\nimport {\n DockerHubTagsPage,\n ManifestJson,\n OciHelmConfig,\n OciImageConfig,\n QuayTagsResponse,\n RegistryTagsList,\n} from './schema.ts';\n\nconst defaultConfig = {\n commitMessageTopic: '{{{depName}}} Docker tag',\n commitMessageExtra:\n 'to {{#if isPinDigest}}{{{newDigestShort}}}{{else}}{{#if isMajor}}{{{prettyNewMajor}}}{{else}}{{{prettyNewVersion}}}{{/if}}{{/if}}',\n digest: {\n branchTopic: '{{{depNameSanitized}}}-{{{currentValue}}}',\n commitMessageExtra: 'to {{newDigestShort}}',\n commitMessageTopic:\n '{{{depName}}}{{#if currentValue}}:{{{currentValue}}}{{/if}} Docker digest',\n group: {\n commitMessageTopic: '{{{groupName}}}',\n commitMessageExtra: '',\n },\n },\n pin: {\n commitMessageExtra: '',\n groupName: 'Docker digests',\n group: {\n commitMessageTopic: '{{{groupName}}}',\n branchTopic: 'digests-pin',\n },\n },\n};\n\nexport class DockerDatasource extends Datasource {\n static readonly id = dockerDatasourceId;\n\n override readonly defaultVersioning = dockerVersioningId;\n\n override readonly defaultRegistryUrls = [DOCKER_HUB];\n\n override readonly defaultConfig = defaultConfig;\n\n override readonly releaseTimestampSupport = true;\n override readonly releaseTimestampNote =\n 'Only supported on Docker Hub: The release timestamp is determined from the `tag_last_pushed` field in the results. **NOTE**: Currently, digests will receive the same release timestamp as the `tag_last_pushed`, which means that digests may appear newer than they are - see https://github.com/renovatebot/renovate/issues/38659';\n override readonly sourceUrlSupport = 'package';\n override readonly sourceUrlNote =\n 'The source URL is determined from the `org.opencontainers.image.source` and `org.label-schema.vcs-url` labels present in the metadata of the **latest stable** image found on the Docker registry.';\n\n constructor() {\n super(DockerDatasource.id);\n }\n\n // TODO: debug why quay throws errors (#9612)\n private async getManifestResponse(\n registryHost: string,\n dockerRepository: string,\n tag: string,\n mode: 'head' | 'getText' = 'getText',\n ): Promise<HttpResponse | null> {\n logger.debug(\n `getManifestResponse(${registryHost}, ${dockerRepository}, ${tag}, ${mode})`,\n );\n try {\n const headers = await getAuthHeaders(\n this.http,\n registryHost,\n dockerRepository,\n );\n if (!headers) {\n logger.warn('No docker auth found - returning');\n return null;\n }\n headers.accept = [\n 'application/vnd.docker.distribution.manifest.list.v2+json',\n 'application/vnd.docker.distribution.manifest.v2+json',\n 'application/vnd.oci.image.manifest.v1+json',\n 'application/vnd.oci.image.index.v1+json',\n ].join(', ');\n const url = `${registryHost}/v2/${dockerRepository}/manifests/${tag}`;\n const manifestResponse = await this.http[mode](url, {\n headers,\n noAuth: true,\n cacheProvider: memCacheProvider,\n });\n return manifestResponse;\n } catch (err) /* istanbul ignore next */ {\n if (err instanceof ExternalHostError) {\n throw err;\n }\n if (err.statusCode === 401) {\n logger.debug(\n { registryHost, dockerRepository },\n 'Unauthorized docker lookup',\n );\n logger.debug({ err });\n return null;\n }\n if (err.statusCode === 404) {\n logger.debug(\n {\n err,\n registryHost,\n dockerRepository,\n tag,\n },\n 'Docker Manifest is unknown',\n );\n return null;\n }\n if (err.statusCode === 429 && isDockerHost(registryHost)) {\n throw new ExternalHostError(err);\n }\n if (err.statusCode >= 500 && err.statusCode < 600) {\n throw new ExternalHostError(err);\n }\n if (err.code === 'ETIMEDOUT') {\n logger.debug(\n { registryHost },\n 'Timeout when attempting to connect to docker registry',\n );\n logger.debug({ err });\n return null;\n }\n logger.debug(\n {\n err,\n registryHost,\n dockerRepository,\n tag,\n },\n 'Unknown Error looking up docker manifest',\n );\n return null;\n }\n }\n\n private async _getImageConfig(\n registryHost: string,\n dockerRepository: string,\n configDigest: string,\n ): Promise<HttpResponse<OciImageConfig> | undefined> {\n logger.trace(\n `getImageConfig(${registryHost}, ${dockerRepository}, ${configDigest})`,\n );\n\n const headers = await getAuthHeaders(\n this.http,\n registryHost,\n dockerRepository,\n );\n /* v8 ignore next 4 -- should never happen */\n if (!headers) {\n logger.warn('No docker auth found - returning');\n return undefined;\n }\n const url = joinUrlParts(\n registryHost,\n 'v2',\n dockerRepository,\n 'blobs',\n configDigest,\n );\n return await this.http.getJson(\n url,\n {\n headers,\n noAuth: true,\n },\n OciImageConfig,\n );\n }\n\n getImageConfig(\n registryHost: string,\n dockerRepository: string,\n configDigest: string,\n ): Promise<HttpResponse<OciImageConfig> | undefined> {\n return withCache(\n {\n namespace: 'datasource-docker-imageconfig',\n key: `${registryHost}:${dockerRepository}@${configDigest}`,\n ttlMinutes: 1440 * 28,\n },\n () => this._getImageConfig(registryHost, dockerRepository, configDigest),\n );\n }\n\n private async _getHelmConfig(\n registryHost: string,\n dockerRepository: string,\n configDigest: string,\n ): Promise<HttpResponse<OciHelmConfig> | undefined> {\n logger.trace(\n `getImageConfig(${registryHost}, ${dockerRepository}, ${configDigest})`,\n );\n\n const headers = await getAuthHeaders(\n this.http,\n registryHost,\n dockerRepository,\n );\n /* v8 ignore next 4 -- should never happen */\n if (!headers) {\n logger.warn('No docker auth found - returning');\n return undefined;\n }\n const url = joinUrlParts(\n registryHost,\n 'v2',\n dockerRepository,\n 'blobs',\n configDigest,\n );\n return await this.http.getJson(\n url,\n {\n headers,\n noAuth: true,\n },\n OciHelmConfig,\n );\n }\n\n getHelmConfig(\n registryHost: string,\n dockerRepository: string,\n configDigest: string,\n ): Promise<HttpResponse<OciHelmConfig> | undefined> {\n return withCache(\n {\n namespace: 'datasource-docker-imageconfig',\n key: `${registryHost}:${dockerRepository}@${configDigest}`,\n ttlMinutes: 1440 * 28,\n },\n () => this._getHelmConfig(registryHost, dockerRepository, configDigest),\n );\n }\n\n private async getConfigDigest(\n registry: string,\n dockerRepository: string,\n tag: string,\n ): Promise<string | null> {\n return (\n (await this.getManifest(registry, dockerRepository, tag))?.config\n ?.digest ?? null\n );\n }\n\n private async getManifestDocument(\n registry: string,\n dockerRepository: string,\n tag: string,\n ): Promise<Manifest | null> {\n const manifestResponse = await this.getManifestResponse(\n registry,\n dockerRepository,\n tag,\n );\n\n // If getting the manifest fails here, then abort\n // This means that the latest tag doesn't have a manifest, which shouldn't\n // be possible\n /* v8 ignore next 3 -- should never happen */\n if (!manifestResponse) {\n return null;\n }\n\n // Softfail on invalid manifests\n const parsed = ManifestJson.safeParse(manifestResponse.body);\n if (!parsed.success) {\n logger.debug(\n {\n registry,\n dockerRepository,\n tag,\n body: manifestResponse.body,\n headers: manifestResponse.headers,\n err: parsed.error,\n },\n 'Invalid manifest response',\n );\n return null;\n }\n\n return parsed.data;\n }\n\n private async resolveImageManifest(\n registry: string,\n dockerRepository: string,\n tag: string,\n manifest: Manifest,\n ): Promise<OciImageManifest | DistributionManifest | null> {\n switch (manifest.mediaType) {\n case 'application/vnd.docker.distribution.manifest.v2+json':\n case 'application/vnd.oci.image.manifest.v1+json':\n return manifest;\n case 'application/vnd.docker.distribution.manifest.list.v2+json':\n case 'application/vnd.oci.image.index.v1+json':\n if (!manifest.manifests.length) {\n logger.debug(\n { manifest },\n 'Invalid manifest list with no manifests - returning',\n );\n return null;\n }\n logger.trace(\n { registry, dockerRepository, tag },\n 'Found manifest list, using first image',\n );\n return this.getManifest(\n registry,\n dockerRepository,\n manifest.manifests[0].digest,\n );\n // istanbul ignore next: can't happen\n default:\n return null;\n }\n }\n\n private async getManifest(\n registry: string,\n dockerRepository: string,\n tag: string,\n ): Promise<OciImageManifest | DistributionManifest | null> {\n const manifest = await this.getManifestDocument(\n registry,\n dockerRepository,\n tag,\n );\n if (!manifest) {\n return null;\n }\n return this.resolveImageManifest(registry, dockerRepository, tag, manifest);\n }\n\n private async _getImageArchitecture(\n registryHost: string,\n dockerRepository: string,\n currentDigest: string,\n ): Promise<string | null | undefined> {\n try {\n let manifestResponse: HttpResponse | null;\n\n try {\n manifestResponse = await this.getManifestResponse(\n registryHost,\n dockerRepository,\n currentDigest,\n 'head',\n );\n } catch (_err) {\n const err =\n _err instanceof ExternalHostError\n ? _err.err\n : /* istanbul ignore next: can never happen */ _err;\n\n if (\n typeof err.statusCode === 'number' &&\n err.statusCode >= 500 &&\n err.statusCode < 600\n ) {\n // querying the digest manifest for a non existent image leads to a 500 statusCode\n return null;\n }\n\n /* istanbul ignore next */\n throw _err;\n }\n\n if (\n manifestResponse?.headers['content-type'] !==\n 'application/vnd.docker.distribution.manifest.v2+json' &&\n manifestResponse?.headers['content-type'] !==\n 'application/vnd.oci.image.manifest.v1+json'\n ) {\n return null;\n }\n\n const configDigest = await this.getConfigDigest(\n registryHost,\n dockerRepository,\n currentDigest,\n );\n if (!configDigest) {\n return null;\n }\n\n const configResponse = await this.getImageConfig(\n registryHost,\n dockerRepository,\n configDigest,\n );\n\n // TODO: fix me, architecture is required in spec\n if (\n configResponse &&\n ('config' in configResponse.body ||\n 'architecture' in configResponse.body)\n ) {\n const architecture = configResponse.body.architecture ?? null;\n logger.debug(\n `Current digest ${currentDigest} relates to architecture ${\n architecture ?? 'null'\n }`,\n );\n\n return architecture;\n }\n } catch (err) /* istanbul ignore next */ {\n if (err.statusCode !== 404 || err.message === PAGE_NOT_FOUND_ERROR) {\n throw err;\n }\n logger.debug(\n { registryHost, dockerRepository, currentDigest, err },\n 'Unknown error getting image architecture',\n );\n }\n\n return undefined;\n }\n\n getImageArchitecture(\n registryHost: string,\n dockerRepository: string,\n currentDigest: string,\n ): Promise<string | null | undefined> {\n return withCache(\n {\n namespace: 'datasource-docker-architecture',\n key: `${registryHost}:${dockerRepository}@${currentDigest}`,\n ttlMinutes: 1440 * 28,\n shouldCacheResult: isNonEmptyString,\n },\n () =>\n this._getImageArchitecture(\n registryHost,\n dockerRepository,\n currentDigest,\n ),\n );\n }\n\n /*\n * docker.getLabels\n *\n * This function will:\n * - Return the labels for the requested image\n */\n private async _getLabels(\n registryHost: string,\n dockerRepository: string,\n tag: string,\n ): Promise<Record<string, string> | undefined> {\n logger.debug(`getLabels(${registryHost}, ${dockerRepository}, ${tag})`);\n // Skip Docker Hub image if RENOVATE_X_DOCKER_HUB_DISABLE_LABEL_LOOKUP is set\n if (\n getEnv().RENOVATE_X_DOCKER_HUB_DISABLE_LABEL_LOOKUP &&\n registryHost === DOCKER_HUB\n ) {\n logger.debug(\n 'Docker Hub image - skipping label lookup due to RENOVATE_X_DOCKER_HUB_DISABLE_LABEL_LOOKUP',\n );\n return {};\n }\n try {\n let labels: Record<string, string> | undefined = {};\n const manifestDocument = await this.getManifestDocument(\n registryHost,\n dockerRepository,\n tag,\n );\n\n if (!manifestDocument) {\n logger.debug(\n { registryHost, dockerRepository, tag },\n 'No manifest found',\n );\n return undefined;\n }\n\n if ('annotations' in manifestDocument && manifestDocument.annotations) {\n labels = manifestDocument.annotations;\n }\n\n if ('manifests' in manifestDocument) {\n const descriptorAnnotations = manifestDocument.manifests\n .map((descriptor) => descriptor.annotations)\n .find(\n (annotations) =>\n isNonEmptyString(annotations?.[sourceLabel]) &&\n isNonEmptyString(annotations?.[gitRefLabel]),\n );\n if (descriptorAnnotations) {\n labels = { ...labels, ...descriptorAnnotations };\n }\n }\n\n if (\n 'manifests' in manifestDocument &&\n labels[sourceLabel] &&\n labels[gitRefLabel]\n ) {\n return labels;\n }\n\n const manifest = await this.resolveImageManifest(\n registryHost,\n dockerRepository,\n tag,\n manifestDocument,\n );\n if (!manifest) {\n return undefined;\n }\n\n if ('annotations' in manifest && manifest.annotations) {\n labels = { ...labels, ...manifest.annotations };\n }\n\n switch (manifest.config.mediaType) {\n case 'application/vnd.cncf.helm.config.v1+json': {\n if (labels[sourceLabel]) {\n // we already have the source url, so no need to pull the config\n return labels;\n }\n const configResponse = await this.getHelmConfig(\n registryHost,\n dockerRepository,\n manifest.config.digest,\n );\n\n if (configResponse) {\n // Helm chart\n const url = findHelmSourceUrl(configResponse.body);\n if (url) {\n labels[sourceLabel] = url;\n }\n }\n break;\n }\n case 'application/vnd.oci.image.config.v1+json':\n case 'application/vnd.docker.container.image.v1+json': {\n if (labels[sourceLabel] && labels[gitRefLabel]) {\n // we already have the source url, so no need to pull the config\n return labels;\n }\n const configResponse = await this.getImageConfig(\n registryHost,\n dockerRepository,\n manifest.config.digest,\n );\n\n /* v8 ignore next 3 -- should never happen */\n if (!configResponse) {\n return labels;\n }\n\n const body = configResponse.body;\n if (body.config) {\n labels = { ...labels, ...body.config.Labels };\n } else {\n logger.debug(\n { headers: configResponse.headers, body },\n `manifest blob response body missing the \"config\" property`,\n );\n }\n break;\n }\n }\n\n if (labels) {\n logger.debug(\n {\n labels,\n },\n 'found labels in manifest',\n );\n }\n return labels;\n } catch (err) /* istanbul ignore next: should be tested in future */ {\n if (err instanceof ExternalHostError) {\n throw err;\n }\n if (err.statusCode === 400 || err.statusCode === 401) {\n logger.debug(\n { registryHost, dockerRepository, err },\n 'Unauthorized docker lookup',\n );\n } else if (err.statusCode === 404) {\n logger.warn(\n {\n err,\n registryHost,\n dockerRepository,\n tag,\n },\n 'Config Manifest is unknown',\n );\n } else if (err.statusCode === 429 && isDockerHost(registryHost)) {\n logger.warn({ err }, 'docker registry failure: too many requests');\n } else if (err.statusCode >= 500 && err.statusCode < 600) {\n logger.debug(\n {\n err,\n registryHost,\n dockerRepository,\n tag,\n },\n 'docker registry failure: internal error',\n );\n } else if (\n err.code === 'ERR_TLS_CERT_ALTNAME_INVALID' ||\n err.code === 'ETIMEDOUT'\n ) {\n logger.debug(\n { registryHost, err },\n 'Error connecting to docker registry',\n );\n } else if (registryHost === 'https://quay.io') {\n // istanbul ignore next\n logger.debug(\n 'Ignoring quay.io errors until they fully support v2 schema',\n );\n } else {\n logger.info(\n { registryHost, dockerRepository, tag, err },\n 'Unknown error getting Docker labels',\n );\n }\n return {};\n }\n }\n\n getLabels(\n registryHost: string,\n dockerRepository: string,\n tag: string,\n ): Promise<Record<string, string> | undefined> {\n return withCache(\n {\n namespace: 'datasource-docker-labels',\n key: `${registryHost}:${dockerRepository}:${tag}`,\n ttlMinutes: 24 * 60,\n },\n () => this._getLabels(registryHost, dockerRepository, tag),\n );\n }\n\n private async getTagsQuayRegistry(\n registry: string,\n repository: string,\n ): Promise<string[]> {\n let tags: string[] = [];\n const limit = 100;\n\n function pageUrl(page: number): string {\n return `${registry}/api/v1/repository/${repository}/tag/?limit=${limit}&page=${page}&onlyActiveTags=true`;\n }\n\n let page = 1;\n let url: string | null = pageUrl(page);\n while (url && page <= 20) {\n const res: HttpResponse<QuayTagsResponse> = await this.http.getJson(\n url,\n QuayTagsResponse,\n );\n const pageTags = res.body.tags.map((tag) => tag.name);\n tags = tags.concat(pageTags);\n page += 1;\n url = res.body.has_additional ? pageUrl(page) : null;\n }\n return tags;\n }\n\n private async getDockerApiTags(\n registryHost: string,\n dockerRepository: string,\n ): Promise<string[] | null> {\n let tags: string[] = [];\n // AWS ECR limits the maximum number of results to 1000\n // See https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_DescribeRepositories.html#ECR-DescribeRepositories-request-maxResults\n // See https://docs.aws.amazon.com/AmazonECRPublic/latest/APIReference/API_DescribeRepositories.html#ecrpublic-DescribeRepositories-request-maxResults\n const limit =\n ecrRegex.test(registryHost) || ecrPublicRegex.test(registryHost)\n ? 1000\n : 10000;\n let url: string | null =\n `${registryHost}/${dockerRepository}/tags/list?n=${limit}`;\n url = ensurePathPrefix(url, '/v2');\n const headers = await getAuthHeaders(\n this.http,\n registryHost,\n dockerRepository,\n url,\n );\n if (!headers) {\n logger.debug('Failed to get authHeaders for getTags lookup');\n return null;\n }\n let page = 0;\n const hostsNeedingAllPages = [\n 'https://ghcr.io', // GHCR sorts from oldest to newest, so we need to get all pages\n 'https://quay.io', // Quay sorts from oldest to newest, so we need to get all pages\n ];\n const pages = hostsNeedingAllPages.includes(registryHost)\n ? 1000\n : GlobalConfig.get('dockerMaxPages');\n logger.trace({ registryHost, dockerRepository, pages }, 'docker.getTags');\n const allowCrossOrigin = isCrossOriginPaginationAllowed(dockerDatasourceId);\n let foundMaxResultsError = false;\n do {\n let res: HttpResponse<RegistryTagsList>;\n try {\n res = await this.http.getJson(\n url,\n { headers, noAuth: true },\n RegistryTagsList,\n );\n } catch (err) {\n if (\n !foundMaxResultsError &&\n err instanceof HttpError &&\n isECRMaxResultsError(err)\n ) {\n const maxResults = 1000;\n url = `${registryHost}/${dockerRepository}/tags/list?n=${maxResults}`;\n url = ensurePathPrefix(url, '/v2');\n foundMaxResultsError = true;\n continue;\n }\n throw err;\n }\n tags = tags.concat(res.body.tags);\n const linkHeader = parseLinkHeader(res.headers.link);\n if (isArtifactoryServer(res)) {\n // Artifactory bug: next link comes back without virtual-repo prefix (RTFACT-18971)\n if (linkHeader?.next?.last) {\n // parse the current URL, strip any old \"last\" param, then set the new one\n const parsed = parseUrl(url);\n // v8 ignore if: url is always a valid HTTP URL as `ensurePathPrefix`\n if (!parsed) {\n url = null;\n break;\n }\n parsed.searchParams.delete('last');\n parsed.searchParams.set('last', linkHeader.next.last);\n url = parsed.href;\n } else {\n url = null;\n }\n } else if (linkHeader?.next?.url) {\n // Resolve the relative-or-absolute next link, not following cross-origin requests unless explicitly opted in\n const nextUrl = resolvePaginationUrl(\n url,\n linkHeader.next.url,\n allowCrossOrigin,\n );\n if (!nextUrl) {\n // make sure that users are aware if there are any (potentially malicious, or misconfigured) pagination links being returned\n logger.once.warn(\n { registryHost, nextUrl: linkHeader.next.url },\n 'Ignoring cross-origin or invalid Docker registry tags pagination link',\n );\n }\n url = nextUrl;\n } else {\n url = null;\n }\n page += 1;\n } while (url && page < pages);\n return tags;\n }\n\n private async _getTags(\n registryHost: string,\n dockerRepository: string,\n ): Promise<string[] | null> {\n try {\n const isQuay = registryHost === 'https://quay.io';\n let tags: string[] | null;\n if (isQuay) {\n try {\n // Due to pagination and sorting limits on Quay Docker v2 API implementation we try the Quay v1 API first\n tags = await this.getTagsQuayRegistry(registryHost, dockerRepository);\n } catch (err) {\n // If we get a 401 Unauthorized error (v1 API requires separate auth for private images), fall back to Docker v2 API\n if (err.statusCode === 401) {\n logger.debug(\n { registryHost, dockerRepository },\n 'Quay v1 API unauthorized, falling back to Docker v2 API',\n );\n tags = await this.getDockerApiTags(registryHost, dockerRepository);\n } else {\n throw err;\n }\n }\n } else {\n tags = await this.getDockerApiTags(registryHost, dockerRepository);\n }\n return tags;\n } catch (_err) /* istanbul ignore next */ {\n const err = _err instanceof ExternalHostError ? _err.err : _err;\n\n if (\n (err.statusCode === 404 || err.message === PAGE_NOT_FOUND_ERROR) &&\n !dockerRepository.includes('/')\n ) {\n logger.debug(\n `Retrying Tags for ${registryHost}/${dockerRepository} using library/ prefix`,\n );\n return this.getTags(registryHost, `library/${dockerRepository}`);\n }\n // JFrog Artifactory - Retry handling when resolving Docker Official Images\n // These follow the format of {{registryHost}}{{jFrogRepository}}/library/{{dockerRepository}}\n if (\n (err.statusCode === 404 || err.message === PAGE_NOT_FOUND_ERROR) &&\n isArtifactoryServer(err.response) &&\n dockerRepository.split('/').length === 2\n ) {\n logger.debug(\n `JFrog Artifactory: Retrying Tags for ${registryHost}/${dockerRepository} using library/ path between JFrog virtual repository and image`,\n );\n\n const dockerRepositoryParts = dockerRepository.split('/');\n const jfrogRepository = dockerRepositoryParts[0];\n const dockerImage = dockerRepositoryParts[1];\n\n return this.getTags(\n registryHost,\n `${jfrogRepository}/library/${dockerImage}`,\n );\n }\n if (err.statusCode === 429 && isDockerHost(registryHost)) {\n logger.warn(\n { registryHost, dockerRepository, err },\n 'docker registry failure: too many requests',\n );\n throw new ExternalHostError(err);\n }\n if (err.statusCode >= 500 && err.statusCode < 600) {\n logger.warn(\n { registryHost, dockerRepository, err },\n 'docker registry failure: internal error',\n );\n throw new ExternalHostError(err);\n }\n const errorCodes = ['ECONNRESET', 'ETIMEDOUT'];\n if (errorCodes.includes(err.code)) {\n logger.warn(\n { registryHost, dockerRepository, err },\n 'docker registry connection failure',\n );\n throw new ExternalHostError(err);\n }\n if (isDockerHost(registryHost)) {\n logger.info({ err }, 'Docker Hub lookup failure');\n }\n throw _err;\n }\n }\n\n getTags(\n registryHost: string,\n dockerRepository: string,\n ): Promise<string[] | null> {\n return withCache(\n {\n namespace: 'datasource-docker-tags',\n key: `${registryHost}:${dockerRepository}`,\n },\n () => this._getTags(registryHost, dockerRepository),\n );\n }\n\n /**\n * docker.getDigest\n *\n * The `newValue` supplied here should be a valid tag for the docker image.\n *\n * This function will:\n * - Look up a sha256 digest for a tag on its registry\n * - Return the digest as a string\n */\n private async _getDigest(\n { registryUrl, lookupName, packageName, currentDigest }: DigestConfig,\n newValue?: string,\n ): Promise<string | null> {\n let registryHost: string;\n let dockerRepository: string;\n if (registryUrl && lookupName) {\n // Reuse the resolved values from getReleases()\n registryHost = registryUrl;\n dockerRepository = lookupName;\n } else {\n // Resolve values independently\n ({ registryHost, dockerRepository } = getRegistryRepository(\n packageName,\n registryUrl!,\n ));\n }\n logger.debug(\n // TODO: types (#22198)\n `getDigest(${registryHost}, ${dockerRepository}, ${newValue})`,\n );\n const newTag = isNonEmptyString(newValue) ? newValue : 'latest';\n let digest: string | null = null;\n try {\n let architecture: string | null | undefined = null;\n if (currentDigest && isDockerDigest(currentDigest)) {\n architecture = await this.getImageArchitecture(\n registryHost,\n dockerRepository,\n currentDigest,\n );\n }\n\n let manifestResponse: HttpResponse | null = null;\n if (!architecture) {\n // Reuse the digest cached from the Docker Hub tag API\n if (registryHost === DOCKER_HUB) {\n const cache = await DockerHubCache.init(dockerRepository);\n const cachedDigest = cache.getDigestForTag(newTag);\n if (cachedDigest) {\n return cachedDigest;\n }\n }\n\n manifestResponse = await this.getManifestResponse(\n registryHost,\n dockerRepository,\n newTag,\n 'head',\n );\n\n if (\n manifestResponse &&\n hasKey('docker-content-digest', manifestResponse.headers)\n ) {\n digest =\n (manifestResponse.headers['docker-content-digest'] as string) ||\n null;\n }\n }\n\n if (\n isNonEmptyString(architecture) ||\n (manifestResponse &&\n !hasKey('docker-content-digest', manifestResponse.headers))\n ) {\n // Reuse the per-arch digest cached from the Docker Hub tag API\n if (isNonEmptyString(architecture) && registryHost === DOCKER_HUB) {\n const cache = await DockerHubCache.init(dockerRepository);\n const cachedDigest = cache.getArchDigestForTag(newTag, architecture);\n if (cachedDigest) {\n return cachedDigest;\n }\n }\n\n logger.debug(\n { registryHost, dockerRepository },\n 'Architecture-specific digest or missing docker-content-digest header - pulling full manifest',\n );\n manifestResponse = await this.getManifestResponse(\n registryHost,\n dockerRepository,\n newTag,\n );\n\n if (architecture && manifestResponse) {\n const parsed = ManifestJson.safeParse(manifestResponse.body);\n /* istanbul ignore else: hard to test */\n if (parsed.success) {\n const manifestList = parsed.data;\n if (\n manifestList.mediaType ===\n 'application/vnd.docker.distribution.manifest.list.v2+json' ||\n manifestList.mediaType ===\n 'application/vnd.oci.image.index.v1+json'\n ) {\n for (const manifest of manifestList.manifests) {\n if (manifest.platform?.architecture === architecture) {\n digest = manifest.digest;\n break;\n }\n }\n // TODO: return null if no matching architecture digest found\n // https://github.com/renovatebot/renovate/discussions/22639\n } else if (\n hasKey('docker-content-digest', manifestResponse.headers)\n ) {\n // TODO: return null if no matching architecture, requires to fetch the config manifest\n // https://github.com/renovatebot/renovate/discussions/22639\n digest = manifestResponse.headers[\n 'docker-content-digest'\n ] as string;\n }\n } else {\n logger.debug(\n {\n registryHost,\n dockerRepository,\n newTag,\n body: manifestResponse.body,\n headers: manifestResponse.headers,\n err: parsed.error,\n },\n 'Failed to parse manifest response',\n );\n }\n }\n\n if (!digest) {\n logger.debug(\n { registryHost, dockerRepository, newTag },\n 'Extraction digest from manifest response body is deprecated',\n );\n digest = extractDigestFromResponseBody(manifestResponse!);\n }\n }\n\n if (\n !manifestResponse &&\n !dockerRepository.includes('/') &&\n !packageName.includes('/')\n ) {\n logger.debug(\n `Retrying Digest for ${registryHost}/${dockerRepository} using library/ prefix`,\n );\n return this.getDigest(\n {\n registryUrl,\n packageName: `library/${packageName}`,\n currentDigest,\n },\n newValue,\n );\n }\n\n if (manifestResponse) {\n // TODO: fix types (#22198)\n logger.debug(`Got docker digest ${digest!}`);\n }\n } catch (err) /* istanbul ignore next */ {\n if (err instanceof ExternalHostError) {\n throw err;\n }\n logger.debug(\n {\n err,\n packageName,\n newTag,\n },\n 'Unknown Error looking up docker image digest',\n );\n }\n return digest;\n }\n\n override getDigest(\n config: DigestConfig,\n newValue?: string,\n ): Promise<string | null> {\n const newTag = newValue ?? 'latest';\n const { registryHost, dockerRepository } = getRegistryRepository(\n config.packageName,\n config.registryUrl!,\n );\n const digest = config.currentDigest ? `@${config.currentDigest}` : '';\n return withCache(\n {\n namespace: 'datasource-docker-digest',\n key: `${registryHost}:${dockerRepository}:${newTag}${digest}`,\n fallback: true,\n shouldCacheResult: isNonEmptyString,\n },\n () => this._getDigest(config, newValue),\n );\n }\n\n private async _getDockerHubTags(\n dockerRepository: string,\n ): Promise<Release[] | null> {\n let url = `https://hub.docker.com/v2/repositories/${dockerRepository}/tags?page_size=1000&ordering=last_updated`;\n\n const cache = await DockerHubCache.init(dockerRepository);\n const maxPages = GlobalConfig.get('dockerMaxPages');\n const allowCrossOrigin = isCrossOriginPaginationAllowed(dockerDatasourceId);\n let page = 0,\n needNextPage = true;\n while (needNextPage && page < maxPages) {\n const { val, err } = await this.http\n .getJsonSafe(url, DockerHubTagsPage)\n .unwrap();\n\n if (err) {\n logger.debug({ err }, `Docker: error fetching data from DockerHub`);\n return null;\n }\n page++;\n const { results, next, count } = val;\n\n needNextPage = cache.reconcile(results, count);\n\n if (!next) {\n break;\n }\n\n // Only follow the `next` link when it's on the same origin, unless explicitly opted in\n const nextUrl = resolvePaginationUrl(url, next, allowCrossOrigin);\n if (!nextUrl) {\n logger.once.warn(\n { dockerRepository, nextUrl: next },\n 'Ignoring cross-origin or invalid Docker Hub tags pagination link',\n );\n break;\n }\n\n url = nextUrl;\n }\n\n await cache.save();\n\n const items = cache.getItems();\n return items.map(({ name: version, tag_last_pushed }) => {\n const release: Release = { version };\n\n const releaseTimestamp = asTimestamp(tag_last_pushed);\n if (releaseTimestamp) {\n release.releaseTimestamp = releaseTimestamp;\n }\n\n // Digest is intentionally not propagated — the Docker Hub tag API\n // returns the manifest-list digest, which would bypass arch-aware\n // resolution in `getDigest()`. `getDigest()` consults the same cache\n // as a shortcut when no arch resolution is needed.\n return release;\n });\n }\n\n getDockerHubTags(dockerRepository: string): Promise<Release[] | null> {\n return withCache(\n {\n namespace: 'datasource-docker-hub-tags',\n key: `${dockerRepository}`,\n },\n () => this._getDockerHubTags(dockerRepository),\n );\n }\n\n /**\n * docker.getReleases\n *\n * A docker image usually looks something like this: somehost.io/owner/repo:8.1.0-alpine\n * In the above:\n * - 'somehost.io' is the registry\n * - 'owner/repo' is the package name\n * - '8.1.0-alpine' is the tag\n *\n * This function will filter only tags that contain a semver version\n */\n private async _getReleases({\n packageName,\n registryUrl,\n }: GetReleasesConfig): Promise<ReleaseResult | null> {\n const { registryHost, dockerRepository } = getRegistryRepository(\n packageName,\n registryUrl!,\n );\n\n type TagsResultType = AsyncResult<\n Release[],\n NonNullable<Error | 'tags-error' | 'dockerhub-error'>\n >;\n\n const getTags = (): TagsResultType =>\n Result.wrapNullable(\n this.getTags(registryHost, dockerRepository),\n 'tags-error' as const,\n ).transform((tags) => tags.map((version) => ({ version })));\n\n const getDockerHubTags = (): TagsResultType =>\n Result.wrapNullable(\n this.getDockerHubTags(dockerRepository),\n 'dockerhub-error' as const,\n ).catch(getTags);\n\n const tagsResult =\n registryHost === DOCKER_HUB &&\n !getEnv().RENOVATE_X_DOCKER_HUB_TAGS_DISABLE\n ? getDockerHubTags()\n : getTags();\n\n const { val: releases, err } = await tagsResult.unwrap();\n if (err instanceof Error) {\n throw err;\n } else if (err) {\n return null;\n }\n\n const ret: ReleaseResult = {\n registryUrl: registryHost,\n releases,\n };\n if (dockerRepository !== packageName) {\n // This will be reused later if a getDigest() call is made\n ret.lookupName = dockerRepository;\n }\n\n const tags = releases.map((release) => release.version);\n const latestTag = tags.includes('latest')\n ? 'latest'\n : (findLatestStable(tags) ?? tags.at(-1));\n\n /* v8 ignore next 3 -- TODO: add test */\n if (!latestTag) {\n return ret;\n }\n const labels = await this.getLabels(\n registryHost,\n dockerRepository,\n latestTag,\n );\n if (labels) {\n if (isNonEmptyString(labels[gitRefLabel])) {\n ret.gitRef = labels[gitRefLabel];\n }\n for (const label of sourceLabels) {\n if (isNonEmptyString(labels[label])) {\n ret.sourceUrl = labels[label];\n break;\n }\n }\n if (isNonEmptyString(labels[imageUrlLabel])) {\n ret.homepage = labels[imageUrlLabel];\n }\n }\n return ret;\n }\n\n getReleases(config: GetReleasesConfig): Promise<ReleaseResult | null> {\n const { registryHost, dockerRepository } = getRegistryRepository(\n config.packageName,\n config.registryUrl!,\n );\n return withCache(\n {\n namespace: 'datasource-docker-releases-v2',\n key: `${registryHost}:${dockerRepository}`,\n cacheable: registryHost === DOCKER_HUB,\n fallback: true,\n },\n () => this._getReleases(config),\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { logger } from "../../../logger/index.js";
|
|
2
2
|
import { id } from "../../versioning/nuget/index.js";
|
|
3
3
|
import { Datasource } from "../datasource.js";
|
|
4
|
+
import { isCrossOriginPaginationAllowed } from "../util.js";
|
|
4
5
|
import { parseRegistryUrl } from "./common.js";
|
|
5
6
|
import { NugetV2Api } from "./v2.js";
|
|
6
7
|
import { NugetV3Api } from "./v3.js";
|
|
@@ -25,7 +26,7 @@ var NugetDatasource = class NugetDatasource extends Datasource {
|
|
|
25
26
|
/* v8 ignore next 3 -- should never happen */
|
|
26
27
|
if (!registryUrl) return null;
|
|
27
28
|
const { feedUrl, protocolVersion } = parseRegistryUrl(registryUrl);
|
|
28
|
-
if (protocolVersion === 2) return this.v2Api.getReleases(this.http, feedUrl, packageName);
|
|
29
|
+
if (protocolVersion === 2) return this.v2Api.getReleases(this.http, feedUrl, packageName, isCrossOriginPaginationAllowed(NugetDatasource.id));
|
|
29
30
|
if (protocolVersion === 3) {
|
|
30
31
|
const queryUrl = await this.v3Api.getResourceUrl(this.http, feedUrl);
|
|
31
32
|
if (queryUrl) return this.v3Api.getReleases(this.http, feedUrl, queryUrl, packageName);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["nugetVersioning.id"],"sources":["../../../../lib/modules/datasource/nuget/index.ts"],"sourcesContent":["import { logger } from '../../../logger/index.ts';\nimport * as nugetVersioning from '../../versioning/nuget/index.ts';\nimport { Datasource } from '../datasource.ts';\nimport type { GetReleasesConfig, ReleaseResult } from '../types.ts';\nimport { parseRegistryUrl } from './common.ts';\nimport { NugetV2Api } from './v2.ts';\nimport { NugetV3Api } from './v3.ts';\n\n// https://api.nuget.org/v3/index.json is a default official nuget feed\nexport const nugetOrg = 'https://api.nuget.org/v3/index.json';\n\nexport class NugetDatasource extends Datasource {\n static readonly id = 'nuget';\n\n override readonly defaultRegistryUrls = [nugetOrg];\n\n override readonly defaultVersioning = nugetVersioning.id;\n\n override readonly registryStrategy = 'merge';\n\n override readonly releaseTimestampSupport = true;\n override readonly releaseTimestampNote =\n 'For the v2 API, the release timestamp is determined from the `Published` tag. And, for the v3 API, the release timestamp is determined from the `published` field in the results.';\n override readonly sourceUrlSupport = 'package';\n override readonly sourceUrlNote =\n 'For the v2 API, the source URL is determined from the `ProjectUrl` tag. And, for the v3 API, the source URL is determined from the `metadata.repository@url` field in the results.';\n\n readonly v2Api = new NugetV2Api();\n\n readonly v3Api = new NugetV3Api();\n\n constructor() {\n super(NugetDatasource.id);\n }\n\n async getReleases({\n packageName,\n registryUrl,\n }: GetReleasesConfig): Promise<ReleaseResult | null> {\n logger.trace(`nuget.getReleases(${packageName})`);\n /* v8 ignore next 3 -- should never happen */\n if (!registryUrl) {\n return null;\n }\n const { feedUrl, protocolVersion } = parseRegistryUrl(registryUrl);\n if (protocolVersion === 2) {\n return this.v2Api.getReleases(this.http
|
|
1
|
+
{"version":3,"file":"index.js","names":["nugetVersioning.id"],"sources":["../../../../lib/modules/datasource/nuget/index.ts"],"sourcesContent":["import { logger } from '../../../logger/index.ts';\nimport * as nugetVersioning from '../../versioning/nuget/index.ts';\nimport { Datasource } from '../datasource.ts';\nimport type { GetReleasesConfig, ReleaseResult } from '../types.ts';\nimport { isCrossOriginPaginationAllowed } from '../util.ts';\nimport { parseRegistryUrl } from './common.ts';\nimport { NugetV2Api } from './v2.ts';\nimport { NugetV3Api } from './v3.ts';\n\n// https://api.nuget.org/v3/index.json is a default official nuget feed\nexport const nugetOrg = 'https://api.nuget.org/v3/index.json';\n\nexport class NugetDatasource extends Datasource {\n static readonly id = 'nuget';\n\n override readonly defaultRegistryUrls = [nugetOrg];\n\n override readonly defaultVersioning = nugetVersioning.id;\n\n override readonly registryStrategy = 'merge';\n\n override readonly releaseTimestampSupport = true;\n override readonly releaseTimestampNote =\n 'For the v2 API, the release timestamp is determined from the `Published` tag. And, for the v3 API, the release timestamp is determined from the `published` field in the results.';\n override readonly sourceUrlSupport = 'package';\n override readonly sourceUrlNote =\n 'For the v2 API, the source URL is determined from the `ProjectUrl` tag. And, for the v3 API, the source URL is determined from the `metadata.repository@url` field in the results.';\n\n readonly v2Api = new NugetV2Api();\n\n readonly v3Api = new NugetV3Api();\n\n constructor() {\n super(NugetDatasource.id);\n }\n\n async getReleases({\n packageName,\n registryUrl,\n }: GetReleasesConfig): Promise<ReleaseResult | null> {\n logger.trace(`nuget.getReleases(${packageName})`);\n /* v8 ignore next 3 -- should never happen */\n if (!registryUrl) {\n return null;\n }\n const { feedUrl, protocolVersion } = parseRegistryUrl(registryUrl);\n if (protocolVersion === 2) {\n return this.v2Api.getReleases(\n this.http,\n feedUrl,\n packageName,\n isCrossOriginPaginationAllowed(NugetDatasource.id),\n );\n }\n if (protocolVersion === 3) {\n const queryUrl = await this.v3Api.getResourceUrl(this.http, feedUrl);\n if (queryUrl) {\n return this.v3Api.getReleases(\n this.http,\n feedUrl,\n queryUrl,\n packageName,\n );\n }\n }\n return null;\n }\n}\n"],"mappings":";;;;;;;;AAUA,MAAa,WAAW;AAExB,IAAa,kBAAb,MAAa,wBAAwB,WAAW;CAC9C,OAAgB,KAAK;CAErB,sBAAwC,CAAC,QAAQ;CAEjD,oBAAsCA;CAEtC,mBAAqC;CAErC,0BAA4C;CAC5C,uBACE;CACF,mBAAqC;CACrC,gBACE;CAEF,QAAiB,IAAI,WAAW;CAEhC,QAAiB,IAAI,WAAW;CAEhC,cAAc;EACZ,MAAM,gBAAgB,EAAE;CAC1B;CAEA,MAAM,YAAY,EAChB,aACA,eACmD;EACnD,OAAO,MAAM,qBAAqB,YAAY,EAAE;;EAEhD,IAAI,CAAC,aACH,OAAO;EAET,MAAM,EAAE,SAAS,oBAAoB,iBAAiB,WAAW;EACjE,IAAI,oBAAoB,GACtB,OAAO,KAAK,MAAM,YAChB,KAAK,MACL,SACA,aACA,+BAA+B,gBAAgB,EAAE,CACnD;EAEF,IAAI,oBAAoB,GAAG;GACzB,MAAM,WAAW,MAAM,KAAK,MAAM,eAAe,KAAK,MAAM,OAAO;GACnE,IAAI,UACF,OAAO,KAAK,MAAM,YAChB,KAAK,MACL,SACA,UACA,WACF;EAEJ;EACA,OAAO;CACT;AACF"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { regEx } from "../../../util/regex.js";
|
|
2
2
|
import { logger } from "../../../logger/index.js";
|
|
3
3
|
import { asTimestamp } from "../../../util/timestamp.js";
|
|
4
|
+
import { resolvePaginationUrl } from "../util.js";
|
|
4
5
|
import { massageUrl, removeBuildMeta } from "./common.js";
|
|
5
6
|
import { XmlDocument } from "xmldoc";
|
|
6
7
|
//#region lib/modules/datasource/nuget/v2.ts
|
|
@@ -8,7 +9,7 @@ var NugetV2Api = class {
|
|
|
8
9
|
getPkgProp(pkgInfo, propName) {
|
|
9
10
|
return pkgInfo.childNamed("m:properties")?.childNamed(`d:${propName}`)?.val;
|
|
10
11
|
}
|
|
11
|
-
async getReleases(http, feedUrl, pkgName) {
|
|
12
|
+
async getReleases(http, feedUrl, pkgName, allowCrossOrigin) {
|
|
12
13
|
const dep = { releases: [] };
|
|
13
14
|
let pkgUrlList = `${feedUrl.replace(regEx(/\/+$/), "")}/FindPackagesById()?id=%27${pkgName}%27&$select=Version,IsLatestVersion,ProjectUrl,Published`;
|
|
14
15
|
while (pkgUrlList !== null) {
|
|
@@ -35,8 +36,13 @@ var NugetV2Api = class {
|
|
|
35
36
|
}, `nuget registry failure: can't parse pkg info for project url`);
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
|
-
const
|
|
39
|
-
|
|
39
|
+
const nextHref = pkgVersionsListDoc.childrenNamed("link").find((node) => node.attr.rel === "next")?.attr.href;
|
|
40
|
+
const nextUrl = nextHref ? resolvePaginationUrl(pkgUrlList, nextHref, allowCrossOrigin) : null;
|
|
41
|
+
if (nextHref && !nextUrl) logger.once.warn({
|
|
42
|
+
feedUrl,
|
|
43
|
+
nextUrl: nextHref
|
|
44
|
+
}, "Ignoring cross-origin or invalid NuGet feed pagination link");
|
|
45
|
+
pkgUrlList = nextUrl;
|
|
40
46
|
}
|
|
41
47
|
if (dep.releases.length === 0) return null;
|
|
42
48
|
return dep;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v2.js","names":[],"sources":["../../../../lib/modules/datasource/nuget/v2.ts"],"sourcesContent":["import type { XmlElement } from 'xmldoc';\nimport { XmlDocument } from 'xmldoc';\nimport { logger } from '../../../logger/index.ts';\nimport type { Http } from '../../../util/http/index.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport { asTimestamp } from '../../../util/timestamp.ts';\nimport type { ReleaseResult } from '../types.ts';\nimport { massageUrl, removeBuildMeta } from './common.ts';\n\nexport class NugetV2Api {\n getPkgProp(pkgInfo: XmlElement, propName: string): string | undefined {\n return pkgInfo.childNamed('m:properties')?.childNamed(`d:${propName}`)?.val;\n }\n\n async getReleases(\n http: Http,\n feedUrl: string,\n pkgName: string,\n ): Promise<ReleaseResult | null> {\n const dep: ReleaseResult = {\n releases: [],\n };\n let pkgUrlList: string | null = `${feedUrl.replace(\n regEx(/\\/+$/),\n '',\n )}/FindPackagesById()?id=%27${pkgName}%27&$select=Version,IsLatestVersion,ProjectUrl,Published`;\n while (pkgUrlList !== null) {\n // typescript issue\n const pkgVersionsListRaw = await http.getText(pkgUrlList);\n const pkgVersionsListDoc = new XmlDocument(pkgVersionsListRaw.body);\n\n const pkgInfoList = pkgVersionsListDoc.childrenNamed('entry');\n\n for (const pkgInfo of pkgInfoList) {\n const version = this.getPkgProp(pkgInfo, 'Version');\n const releaseTimestamp = asTimestamp(\n this.getPkgProp(pkgInfo, 'Published'),\n );\n dep.releases.push({\n // TODO: types (#22198)\n version: removeBuildMeta(`${version}`),\n releaseTimestamp,\n });\n try {\n const pkgIsLatestVersion = this.getPkgProp(\n pkgInfo,\n 'IsLatestVersion',\n );\n if (pkgIsLatestVersion === 'true') {\n dep.tags = { latest: removeBuildMeta(`${version}`) };\n const projectUrl = this.getPkgProp(pkgInfo, 'ProjectUrl');\n if (projectUrl) {\n dep.sourceUrl = massageUrl(projectUrl);\n }\n }\n } catch (err) /* istanbul ignore next */ {\n logger.debug(\n { err, pkgName, feedUrl },\n `nuget registry failure: can't parse pkg info for project url`,\n );\n }\n }\n\n const nextPkgUrlListLink = pkgVersionsListDoc\n .childrenNamed('link')\n .find((node) => node.attr.rel === 'next');\n\n
|
|
1
|
+
{"version":3,"file":"v2.js","names":[],"sources":["../../../../lib/modules/datasource/nuget/v2.ts"],"sourcesContent":["import type { XmlElement } from 'xmldoc';\nimport { XmlDocument } from 'xmldoc';\nimport { logger } from '../../../logger/index.ts';\nimport type { Http } from '../../../util/http/index.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport { asTimestamp } from '../../../util/timestamp.ts';\nimport type { ReleaseResult } from '../types.ts';\nimport { resolvePaginationUrl } from '../util.ts';\nimport { massageUrl, removeBuildMeta } from './common.ts';\n\nexport class NugetV2Api {\n getPkgProp(pkgInfo: XmlElement, propName: string): string | undefined {\n return pkgInfo.childNamed('m:properties')?.childNamed(`d:${propName}`)?.val;\n }\n\n async getReleases(\n http: Http,\n feedUrl: string,\n pkgName: string,\n allowCrossOrigin: boolean,\n ): Promise<ReleaseResult | null> {\n const dep: ReleaseResult = {\n releases: [],\n };\n let pkgUrlList: string | null = `${feedUrl.replace(\n regEx(/\\/+$/),\n '',\n )}/FindPackagesById()?id=%27${pkgName}%27&$select=Version,IsLatestVersion,ProjectUrl,Published`;\n while (pkgUrlList !== null) {\n // typescript issue\n const pkgVersionsListRaw = await http.getText(pkgUrlList);\n const pkgVersionsListDoc = new XmlDocument(pkgVersionsListRaw.body);\n\n const pkgInfoList = pkgVersionsListDoc.childrenNamed('entry');\n\n for (const pkgInfo of pkgInfoList) {\n const version = this.getPkgProp(pkgInfo, 'Version');\n const releaseTimestamp = asTimestamp(\n this.getPkgProp(pkgInfo, 'Published'),\n );\n dep.releases.push({\n // TODO: types (#22198)\n version: removeBuildMeta(`${version}`),\n releaseTimestamp,\n });\n try {\n const pkgIsLatestVersion = this.getPkgProp(\n pkgInfo,\n 'IsLatestVersion',\n );\n if (pkgIsLatestVersion === 'true') {\n dep.tags = { latest: removeBuildMeta(`${version}`) };\n const projectUrl = this.getPkgProp(pkgInfo, 'ProjectUrl');\n if (projectUrl) {\n dep.sourceUrl = massageUrl(projectUrl);\n }\n }\n } catch (err) /* istanbul ignore next */ {\n logger.debug(\n { err, pkgName, feedUrl },\n `nuget registry failure: can't parse pkg info for project url`,\n );\n }\n }\n\n const nextPkgUrlListLink = pkgVersionsListDoc\n .childrenNamed('link')\n .find((node) => node.attr.rel === 'next');\n\n // Resolve the relative-or-absolute next link, but only follow it when it stays on the same origin, or if the user has opted in via `RENOVATE_X_NUGET_PAGINATION_ALLOW_CROSS_ORIGIN`\n const nextHref = nextPkgUrlListLink?.attr.href;\n const nextUrl: string | null = nextHref\n ? resolvePaginationUrl(pkgUrlList, nextHref, allowCrossOrigin)\n : null;\n if (nextHref && !nextUrl) {\n // make sure that users are aware if there are any (potentially malicious, or misconfigured) pagination links being returned\n logger.once.warn(\n { feedUrl, nextUrl: nextHref },\n 'Ignoring cross-origin or invalid NuGet feed pagination link',\n );\n }\n pkgUrlList = nextUrl;\n }\n\n // dep not found if no release, so we can try next registry\n if (dep.releases.length === 0) {\n return null;\n }\n\n return dep;\n }\n}\n"],"mappings":";;;;;;;AAUA,IAAa,aAAb,MAAwB;CACtB,WAAW,SAAqB,UAAsC;EACpE,OAAO,QAAQ,WAAW,cAAc,CAAC,EAAE,WAAW,KAAK,UAAU,CAAC,EAAE;CAC1E;CAEA,MAAM,YACJ,MACA,SACA,SACA,kBAC+B;EAC/B,MAAM,MAAqB,EACzB,UAAU,CAAC,EACb;EACA,IAAI,aAA4B,GAAG,QAAQ,QACzC,MAAM,MAAM,GACZ,EACF,EAAE,4BAA4B,QAAQ;EACtC,OAAO,eAAe,MAAM;GAG1B,MAAM,qBAAqB,IAAI,aAAY,MADV,KAAK,QAAQ,UAAU,EAAA,CACM,IAAI;GAElE,MAAM,cAAc,mBAAmB,cAAc,OAAO;GAE5D,KAAK,MAAM,WAAW,aAAa;IACjC,MAAM,UAAU,KAAK,WAAW,SAAS,SAAS;IAClD,MAAM,mBAAmB,YACvB,KAAK,WAAW,SAAS,WAAW,CACtC;IACA,IAAI,SAAS,KAAK;KAEhB,SAAS,gBAAgB,GAAG,SAAS;KACrC;IACF,CAAC;IACD,IAAI;KAKF,IAJ2B,KAAK,WAC9B,SACA,iBAEmB,MAAM,QAAQ;MACjC,IAAI,OAAO,EAAE,QAAQ,gBAAgB,GAAG,SAAS,EAAE;MACnD,MAAM,aAAa,KAAK,WAAW,SAAS,YAAY;MACxD,IAAI,YACF,IAAI,YAAY,WAAW,UAAU;KAEzC;IACF,SAAS,oCAAgC;KACvC,OAAO,MACL;MAAE;MAAK;MAAS;KAAQ,GACxB,8DACF;IACF;GACF;GAOA,MAAM,WALqB,mBACxB,cAAc,MAAM,CAAC,CACrB,MAAM,SAAS,KAAK,KAAK,QAAQ,MAGF,CAAC,EAAE,KAAK;GAC1C,MAAM,UAAyB,WAC3B,qBAAqB,YAAY,UAAU,gBAAgB,IAC3D;GACJ,IAAI,YAAY,CAAC,SAEf,OAAO,KAAK,KACV;IAAE;IAAS,SAAS;GAAS,GAC7B,6DACF;GAEF,aAAa;EACf;EAGA,IAAI,IAAI,SAAS,WAAW,GAC1B,OAAO;EAGT,OAAO;CACT;AACF"}
|
|
@@ -1,9 +1,53 @@
|
|
|
1
|
+
import { getEnv } from "../../util/env.js";
|
|
1
2
|
import { addSecretForSanitizing } from "../../util/sanitize.js";
|
|
2
3
|
import { logger } from "../../logger/index.js";
|
|
4
|
+
import { parseUrl, resolveSameOriginUrl } from "../../util/url.js";
|
|
3
5
|
import { isString } from "@sindresorhus/is";
|
|
4
6
|
import { GoogleAuth } from "google-auth-library";
|
|
5
7
|
//#region lib/modules/datasource/util.ts
|
|
6
8
|
const JFROG_ARTIFACTORY_RES_HEADER = "x-jfrog-version";
|
|
9
|
+
/**
|
|
10
|
+
* Experimental, per-datasource env var that opts a datasource into following
|
|
11
|
+
* server-provided pagination links across origins. Each datasource has its own
|
|
12
|
+
* flag so relaxing the restriction for one registry cannot weaken the others.
|
|
13
|
+
*/
|
|
14
|
+
const crossOriginPaginationEnv = {
|
|
15
|
+
docker: "RENOVATE_X_DOCKER_PAGINATION_ALLOW_CROSS_ORIGIN",
|
|
16
|
+
nuget: "RENOVATE_X_NUGET_PAGINATION_ALLOW_CROSS_ORIGIN"
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Whether a datasource is opted in to following server-provided pagination
|
|
20
|
+
* links across origins, via its experimental `RENOVATE_X_<DATASOURCE>_PAGINATION_ALLOW_CROSS_ORIGIN`
|
|
21
|
+
* env var.
|
|
22
|
+
*
|
|
23
|
+
* By default such links are restricted to the same origin as the request so a
|
|
24
|
+
* malicious or compromised registry cannot attempt to redirect our requests in an attempt for Server Side Request Forgery.
|
|
25
|
+
*/
|
|
26
|
+
function isCrossOriginPaginationAllowed(datasource) {
|
|
27
|
+
const envVar = crossOriginPaginationEnv[datasource];
|
|
28
|
+
return envVar ? !!getEnv()[envVar] : false;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolves a server-provided pagination `next` URL against `baseUrl`, returning
|
|
32
|
+
* the URL to follow or `null` if it must not be followed.
|
|
33
|
+
*
|
|
34
|
+
* When `allowCrossOrigin` is false (the default; see
|
|
35
|
+
* {@link isCrossOriginPaginationAllowed}) the link is dropped unless it stays on
|
|
36
|
+
* the same origin. When true, the link is followed regardless of origin.
|
|
37
|
+
*/
|
|
38
|
+
function resolvePaginationUrl(baseUrl, nextUrl, allowCrossOrigin) {
|
|
39
|
+
if (!allowCrossOrigin) return resolveSameOriginUrl(baseUrl, nextUrl);
|
|
40
|
+
try {
|
|
41
|
+
const resolved = new URL(nextUrl, baseUrl).href;
|
|
42
|
+
if (parseUrl(baseUrl)?.origin !== parseUrl(resolved)?.origin) logger.once.warn({
|
|
43
|
+
baseUrl,
|
|
44
|
+
nextUrl
|
|
45
|
+
}, "Following cross-origin pagination link");
|
|
46
|
+
return resolved;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
7
51
|
function isArtifactoryServer(res) {
|
|
8
52
|
return isString(res?.headers[JFROG_ARTIFACTORY_RES_HEADER]);
|
|
9
53
|
}
|
|
@@ -34,6 +78,6 @@ async function getGoogleAuthToken() {
|
|
|
34
78
|
return null;
|
|
35
79
|
}
|
|
36
80
|
//#endregion
|
|
37
|
-
export { getGoogleAuthHostRule, getGoogleAuthToken, isArtifactoryServer };
|
|
81
|
+
export { getGoogleAuthHostRule, getGoogleAuthToken, isArtifactoryServer, isCrossOriginPaginationAllowed, resolvePaginationUrl };
|
|
38
82
|
|
|
39
83
|
//# sourceMappingURL=util.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","names":[],"sources":["../../../lib/modules/datasource/util.ts"],"sourcesContent":["import { isString } from '@sindresorhus/is';\nimport { GoogleAuth } from 'google-auth-library';\nimport { logger } from '../../logger/index.ts';\nimport type { HostRule } from '../../types/index.ts';\nimport type { HttpResponse } from '../../util/http/types.ts';\nimport { addSecretForSanitizing } from '../../util/sanitize.ts';\n\nconst JFROG_ARTIFACTORY_RES_HEADER = 'x-jfrog-version';\n\nexport function isArtifactoryServer<T = unknown>(\n res: HttpResponse<T> | undefined,\n): boolean {\n return isString(res?.headers[JFROG_ARTIFACTORY_RES_HEADER]);\n}\n\nexport async function getGoogleAuthHostRule(): Promise<HostRule | null> {\n try {\n const googleAuth: GoogleAuth = new GoogleAuth({\n scopes: 'https://www.googleapis.com/auth/cloud-platform',\n });\n const accessToken = await googleAuth.getAccessToken();\n if (accessToken) {\n // sanitize token\n addSecretForSanitizing(accessToken);\n return {\n username: 'oauth2accesstoken',\n password: accessToken,\n };\n }\n logger.warn(\n 'Could not retrieve access token using google-auth-library getAccessToken',\n );\n } catch (err) {\n if (err.message?.includes('Could not load the default credentials')) {\n return null;\n }\n throw err;\n }\n return null;\n}\n\nexport async function getGoogleAuthToken(): Promise<string | null> {\n const rule = await getGoogleAuthHostRule();\n if (rule) {\n const token = Buffer.from(`${rule.username}:${rule.password}`).toString(\n 'base64',\n );\n addSecretForSanitizing(token);\n return token;\n }\n return null;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"util.js","names":[],"sources":["../../../lib/modules/datasource/util.ts"],"sourcesContent":["import { isString } from '@sindresorhus/is';\nimport { GoogleAuth } from 'google-auth-library';\nimport { logger } from '../../logger/index.ts';\nimport type { HostRule } from '../../types/index.ts';\nimport { getEnv } from '../../util/env.ts';\nimport type { HttpResponse } from '../../util/http/types.ts';\nimport { addSecretForSanitizing } from '../../util/sanitize.ts';\nimport { parseUrl, resolveSameOriginUrl } from '../../util/url.ts';\n\nconst JFROG_ARTIFACTORY_RES_HEADER = 'x-jfrog-version';\n\n/**\n * Experimental, per-datasource env var that opts a datasource into following\n * server-provided pagination links across origins. Each datasource has its own\n * flag so relaxing the restriction for one registry cannot weaken the others.\n */\nconst crossOriginPaginationEnv: Record<string, string> = {\n docker: 'RENOVATE_X_DOCKER_PAGINATION_ALLOW_CROSS_ORIGIN',\n nuget: 'RENOVATE_X_NUGET_PAGINATION_ALLOW_CROSS_ORIGIN',\n};\n\n/**\n * Whether a datasource is opted in to following server-provided pagination\n * links across origins, via its experimental `RENOVATE_X_<DATASOURCE>_PAGINATION_ALLOW_CROSS_ORIGIN`\n * env var.\n *\n * By default such links are restricted to the same origin as the request so a\n * malicious or compromised registry cannot attempt to redirect our requests in an attempt for Server Side Request Forgery.\n */\nexport function isCrossOriginPaginationAllowed(datasource: string): boolean {\n const envVar = crossOriginPaginationEnv[datasource];\n return envVar ? !!getEnv()[envVar] : false;\n}\n\n/**\n * Resolves a server-provided pagination `next` URL against `baseUrl`, returning\n * the URL to follow or `null` if it must not be followed.\n *\n * When `allowCrossOrigin` is false (the default; see\n * {@link isCrossOriginPaginationAllowed}) the link is dropped unless it stays on\n * the same origin. When true, the link is followed regardless of origin.\n */\nexport function resolvePaginationUrl(\n baseUrl: string,\n nextUrl: string,\n allowCrossOrigin: boolean,\n): string | null {\n if (!allowCrossOrigin) {\n return resolveSameOriginUrl(baseUrl, nextUrl);\n }\n try {\n const resolved = new URL(nextUrl, baseUrl).href;\n if (parseUrl(baseUrl)?.origin !== parseUrl(resolved)?.origin) {\n logger.once.warn(\n { baseUrl, nextUrl },\n 'Following cross-origin pagination link',\n );\n }\n return resolved;\n } catch {\n return null;\n }\n}\n\nexport function isArtifactoryServer<T = unknown>(\n res: HttpResponse<T> | undefined,\n): boolean {\n return isString(res?.headers[JFROG_ARTIFACTORY_RES_HEADER]);\n}\n\nexport async function getGoogleAuthHostRule(): Promise<HostRule | null> {\n try {\n const googleAuth: GoogleAuth = new GoogleAuth({\n scopes: 'https://www.googleapis.com/auth/cloud-platform',\n });\n const accessToken = await googleAuth.getAccessToken();\n if (accessToken) {\n // sanitize token\n addSecretForSanitizing(accessToken);\n return {\n username: 'oauth2accesstoken',\n password: accessToken,\n };\n }\n logger.warn(\n 'Could not retrieve access token using google-auth-library getAccessToken',\n );\n } catch (err) {\n if (err.message?.includes('Could not load the default credentials')) {\n return null;\n }\n throw err;\n }\n return null;\n}\n\nexport async function getGoogleAuthToken(): Promise<string | null> {\n const rule = await getGoogleAuthHostRule();\n if (rule) {\n const token = Buffer.from(`${rule.username}:${rule.password}`).toString(\n 'base64',\n );\n addSecretForSanitizing(token);\n return token;\n }\n return null;\n}\n"],"mappings":";;;;;;;AASA,MAAM,+BAA+B;;;;;;AAOrC,MAAM,2BAAmD;CACvD,QAAQ;CACR,OAAO;AACT;;;;;;;;;AAUA,SAAgB,+BAA+B,YAA6B;CAC1E,MAAM,SAAS,yBAAyB;CACxC,OAAO,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU;AACvC;;;;;;;;;AAUA,SAAgB,qBACd,SACA,SACA,kBACe;CACf,IAAI,CAAC,kBACH,OAAO,qBAAqB,SAAS,OAAO;CAE9C,IAAI;EACF,MAAM,WAAW,IAAI,IAAI,SAAS,OAAO,CAAC,CAAC;EAC3C,IAAI,SAAS,OAAO,CAAC,EAAE,WAAW,SAAS,QAAQ,CAAC,EAAE,QACpD,OAAO,KAAK,KACV;GAAE;GAAS;EAAQ,GACnB,wCACF;EAEF,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,oBACd,KACS;CACT,OAAO,SAAS,KAAK,QAAQ,6BAA6B;AAC5D;AAEA,eAAsB,wBAAkD;CACtE,IAAI;EAIF,MAAM,cAAc,MAAM,IAHS,WAAW,EAC5C,QAAQ,iDACV,CACmC,CAAC,CAAC,eAAe;EACpD,IAAI,aAAa;GAEf,uBAAuB,WAAW;GAClC,OAAO;IACL,UAAU;IACV,UAAU;GACZ;EACF;EACA,OAAO,KACL,0EACF;CACF,SAAS,KAAK;EACZ,IAAI,IAAI,SAAS,SAAS,wCAAwC,GAChE,OAAO;EAET,MAAM;CACR;CACA,OAAO;AACT;AAEA,eAAsB,qBAA6C;CACjE,MAAM,OAAO,MAAM,sBAAsB;CACzC,IAAI,MAAM;EACR,MAAM,QAAQ,OAAO,KAAK,GAAG,KAAK,SAAS,GAAG,KAAK,UAAU,CAAC,CAAC,SAC7D,QACF;EACA,uBAAuB,KAAK;EAC5B,OAAO;CACT;CACA,OAAO;AACT"}
|
package/dist/util/http/github.js
CHANGED
|
@@ -234,32 +234,37 @@ var GithubHttp = class extends HttpBase {
|
|
|
234
234
|
const baseUrl = httpOptions.baseUrl ?? this.baseUrl;
|
|
235
235
|
const parsedUrl = new URL(next.url, baseUrl);
|
|
236
236
|
const firstPageUrl = !!baseUrl && !!env.RENOVATE_X_REBASE_PAGINATION_LINKS && parsedUrl.origin !== "https://api.github.com" ? replaceUrlBase(parsedUrl, baseUrl) : parsedUrl;
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (httpOptions.paginationField && isPlainObject(result.body)) {
|
|
247
|
-
const paginatedResult = result.body[httpOptions.paginationField];
|
|
237
|
+
if (firstPageUrl.origin === resolvedUrl.origin) {
|
|
238
|
+
const pages = await all([...range(2, lastPage)].map((pageNumber) => () => {
|
|
239
|
+
const nextUrl = parseUrl(firstPageUrl.toString());
|
|
240
|
+
nextUrl.searchParams.set("page", String(pageNumber));
|
|
241
|
+
return super.requestJsonUnsafe(method, {
|
|
242
|
+
...opts,
|
|
243
|
+
url: nextUrl
|
|
244
|
+
});
|
|
245
|
+
}));
|
|
248
246
|
// v8 ignore else -- TODO: add test #40625
|
|
249
|
-
if (
|
|
250
|
-
|
|
247
|
+
if (httpOptions.paginationField && isPlainObject(result.body)) {
|
|
248
|
+
const paginatedResult = result.body[httpOptions.paginationField];
|
|
249
|
+
// v8 ignore else -- TODO: add test #40625
|
|
250
|
+
if (isArray(paginatedResult)) {
|
|
251
|
+
for (const nextPage of pages)
|
|
251
252
|
// v8 ignore else -- TODO: add test #40625
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
253
|
+
if (isPlainObject(nextPage.body)) {
|
|
254
|
+
const nextPageResults = nextPage.body[httpOptions.paginationField];
|
|
255
|
+
// v8 ignore else -- TODO: add test #40625
|
|
256
|
+
if (isArray(nextPageResults)) paginatedResult.push(...nextPageResults);
|
|
257
|
+
}
|
|
256
258
|
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
for (const nextPage of pages)
|
|
259
|
+
} else if (isArray(result.body)) {
|
|
260
|
+
for (const nextPage of pages)
|
|
260
261
|
// v8 ignore else -- TODO: add test #40625
|
|
261
|
-
|
|
262
|
-
|
|
262
|
+
if (isArray(nextPage.body)) result.body.push(...nextPage.body);
|
|
263
|
+
}
|
|
264
|
+
} else logger.once.warn({
|
|
265
|
+
requestHost: resolvedUrl.host,
|
|
266
|
+
paginationHost: firstPageUrl.host
|
|
267
|
+
}, "Ignoring cross-origin GitHub pagination link. Set RENOVATE_X_REBASE_PAGINATION_LINKS if this is a self-hosted instance that returns a different host in pagination links.");
|
|
263
268
|
}
|
|
264
269
|
}
|
|
265
270
|
return result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github.js","names":["hostRules.find","p.all"],"sources":["../../../lib/util/http/github.ts"],"sourcesContent":["import {\n isArray,\n isNonEmptyObject,\n isNullOrUndefined,\n isPlainObject,\n} from '@sindresorhus/is';\nimport { DateTime } from 'luxon';\nimport { z } from 'zod/v4';\nimport { GlobalConfig } from '../../config/global.ts';\nimport {\n PLATFORM_BAD_CREDENTIALS,\n PLATFORM_INTEGRATION_UNAUTHORIZED,\n PLATFORM_RATE_LIMIT_EXCEEDED,\n REPOSITORY_CHANGED,\n} from '../../constants/error-messages.ts';\nimport { logger } from '../../logger/index.ts';\nimport { ExternalHostError } from '../../types/errors/external-host-error.ts';\nimport { getCache } from '../cache/repository/index.ts';\nimport { getEnv } from '../env.ts';\nimport * as hostRules from '../host-rules.ts';\nimport { maskToken } from '../mask.ts';\nimport * as p from '../promises.ts';\nimport { range } from '../range.ts';\nimport { regEx } from '../regex.ts';\nimport { LooseArray } from '../schema-utils/index.ts';\nimport { joinUrlParts, parseLinkHeader, parseUrl } from '../url.ts';\nimport { findMatchingRule } from './host-rules.ts';\nimport {\n HttpBase,\n type InternalHttpOptions,\n type InternalJsonUnsafeOptions,\n} from './http.ts';\nimport type { GotLegacyError } from './legacy.ts';\nimport type {\n GraphqlOptions,\n HttpMethod,\n HttpOptions,\n HttpResponse,\n} from './types.ts';\n\nconst githubBaseUrl = 'https://api.github.com/';\nlet baseUrl = githubBaseUrl;\nexport function setBaseUrl(url: string): void {\n baseUrl = url;\n}\n\nexport interface GithubBaseHttpOptions extends HttpOptions {\n repository?: string;\n}\n\nexport interface GithubHttpOptions extends GithubBaseHttpOptions {\n paginate?: boolean | string;\n paginationField?: string;\n pageLimit?: number;\n}\n\ninterface GithubGraphqlRepoData<T = unknown> {\n repository?: T;\n}\n\nexport type GithubGraphqlResponse<T = unknown> =\n | {\n data: T;\n errors?: never;\n }\n | {\n data?: never;\n errors: {\n type?: string;\n message: string;\n }[];\n };\n\nconst GithubError = z.object({\n field: z.string().optional(),\n code: z.string().optional(),\n message: z.string().optional(),\n});\n\n/**\n * GitHub usually returns `errors` as an array, but not always - normalize it\n * into an array of error objects, dropping anything that doesn't fit.\n */\nconst GithubErrors = z\n .union([LooseArray(GithubError), GithubError.transform((error) => [error])])\n .catch([]);\n\nfunction handleGotError(\n err: GotLegacyError,\n url: string | URL,\n opts: GithubBaseHttpOptions,\n): Error {\n const path = url.toString();\n let message = err.message || '';\n const body = err.response?.body;\n if (isPlainObject(body) && 'message' in body) {\n message = String(body.message);\n }\n if (\n err.code === 'ERR_HTTP2_STREAM_ERROR' ||\n err.code === 'ENOTFOUND' ||\n err.code === 'ETIMEDOUT' ||\n err.code === 'EAI_AGAIN' ||\n err.code === 'ECONNRESET'\n ) {\n logger.debug({ err }, 'GitHub failure: RequestError');\n return new ExternalHostError(err, 'github');\n }\n if (err.name === 'ParseError') {\n logger.debug({ err }, '');\n return new ExternalHostError(err, 'github');\n }\n if (err.statusCode && err.statusCode >= 500 && err.statusCode < 600) {\n logger.debug({ err }, 'GitHub failure: 5xx');\n return new ExternalHostError(err, 'github');\n }\n if (\n err.statusCode === 403 &&\n message.startsWith('You have triggered an abuse detection mechanism')\n ) {\n logger.debug({ err }, 'GitHub failure: abuse detection');\n return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);\n }\n if (\n err.statusCode === 403 &&\n message.startsWith('You have exceeded a secondary rate limit')\n ) {\n logger.warn({ err }, 'GitHub failure: secondary rate limit');\n return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);\n }\n if (err.statusCode === 403 && message.includes('Upgrade to GitHub Pro')) {\n logger.debug(`Endpoint: ${path}, needs paid GitHub plan`);\n return err;\n }\n if (err.statusCode === 403 && message.includes('rate limit exceeded')) {\n logger.debug({ err }, 'GitHub failure: rate limit');\n\n const parsed = parseUrl(baseUrl);\n\n const rule = hostRules.find({ url: baseUrl });\n if (rule.token || rule.password) {\n logger.once.warn(\n 'Rate limit exceeded for api.github.com, even though we are authenticated',\n );\n } else {\n if (parsed?.hostname === 'api.github.com') {\n logger.once.warn(\n {\n documentationUrl: `${GlobalConfig.get('productLinks').documentation}getting-started/running/#githubcom-token-for-changelogs-and-tools`,\n },\n 'Rate limit exceeded for api.github.com, as no hostRules set for this host. Please set a GITHUB_COM_TOKEN',\n );\n } else {\n logger.once.warn(\n { host: parsed!.host },\n 'Rate limit exceeded, as no hostRules set for this host',\n );\n }\n }\n\n return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);\n }\n if (\n err.statusCode === 403 &&\n message.startsWith('Resource not accessible by integration')\n ) {\n logger.debug(\n { err },\n 'GitHub failure: Resource not accessible by integration',\n );\n return new Error(PLATFORM_INTEGRATION_UNAUTHORIZED);\n }\n if (err.statusCode === 401) {\n // Warn once for github.com token if unauthorized\n const hostname = parseUrl(url)?.hostname;\n // v8 ignore else -- TODO: add test #40625\n if (hostname === 'github.com' || hostname === 'api.github.com') {\n logger.once.warn('github.com token 401 unauthorized');\n }\n if (message.includes('Bad credentials')) {\n const rateLimit = err.headers?.['x-ratelimit-limit'] ?? -1;\n logger.debug(\n {\n token: maskToken(opts.token),\n err,\n },\n 'GitHub failure: Bad credentials',\n );\n if (rateLimit === '60') {\n return new ExternalHostError(err, 'github');\n }\n return new Error(PLATFORM_BAD_CREDENTIALS);\n }\n }\n if (err.statusCode === 422) {\n if (\n message.includes('Review cannot be requested from pull request author')\n ) {\n return err;\n }\n\n const errors = GithubErrors.parse(err.body?.errors);\n if (errors.some((e) => e.field === 'milestone')) {\n return err;\n }\n if (errors.some((e) => e.code === 'invalid')) {\n logger.debug({ err }, 'Received invalid response - aborting');\n return new Error(REPOSITORY_CHANGED);\n }\n if (\n errors.some((e) => e.message?.startsWith('A pull request already exists'))\n ) {\n return err;\n }\n logger.debug({ err }, '422 Error thrown from GitHub');\n return new ExternalHostError(err, 'github');\n }\n if (\n err.statusCode === 410 &&\n err.body?.message === 'Issues are disabled for this repo'\n ) {\n return err;\n }\n return err;\n}\n\ninterface GraphqlPaginatedContent<T = unknown> {\n nodes?: T[];\n edges?: T[];\n pageInfo: { hasNextPage: boolean; endCursor: string };\n}\n\nfunction constructAcceptString(input?: unknown): string {\n const defaultAccept = 'application/vnd.github.v3+json';\n const acceptStrings =\n typeof input === 'string' ? input.split(regEx(/\\s*,\\s*/)) : [];\n\n // TODO: regression of #6736\n // v8 ignore else -- TODO: add test #40625\n if (\n !acceptStrings.some((x) => x === defaultAccept) &&\n (!acceptStrings.some((x) => x.startsWith('application/vnd.github.')) ||\n acceptStrings.length < 2)\n ) {\n acceptStrings.push(defaultAccept);\n }\n return acceptStrings.join(', ');\n}\n\nconst MAX_GRAPHQL_PAGE_SIZE = 100;\n\ninterface GraphqlPageCacheItem {\n pageLastResizedAt: string;\n pageSize: number;\n}\n\nexport type GraphqlPageCache = Record<string, GraphqlPageCacheItem>;\n\nfunction getGraphqlPageSize(\n fieldName: string,\n defaultPageSize = MAX_GRAPHQL_PAGE_SIZE,\n): number {\n const cache = getCache();\n const graphqlPageCache = cache?.platform?.github\n ?.graphqlPageCache as GraphqlPageCache;\n const cachedRecord = graphqlPageCache?.[fieldName];\n\n if (graphqlPageCache && cachedRecord) {\n logger.debug(\n { fieldName, ...cachedRecord },\n 'GraphQL page size: found cached value',\n );\n\n const oldPageSize = cachedRecord.pageSize;\n\n const now = DateTime.local();\n const then = DateTime.fromISO(cachedRecord.pageLastResizedAt);\n const expiry = then.plus({ hours: 24 });\n if (now > expiry) {\n const newPageSize = Math.min(oldPageSize * 2, MAX_GRAPHQL_PAGE_SIZE);\n if (newPageSize < MAX_GRAPHQL_PAGE_SIZE) {\n const timestamp = now.toISO();\n\n logger.debug(\n { fieldName, oldPageSize, newPageSize, timestamp },\n 'GraphQL page size: expanding',\n );\n\n cachedRecord.pageLastResizedAt = timestamp;\n cachedRecord.pageSize = newPageSize;\n } else {\n logger.debug(\n { fieldName, oldPageSize, newPageSize },\n 'GraphQL page size: expanded to default page size',\n );\n\n delete graphqlPageCache[fieldName];\n }\n\n return newPageSize;\n }\n\n return oldPageSize;\n }\n\n return defaultPageSize;\n}\n\nfunction setGraphqlPageSize(fieldName: string, newPageSize: number): void {\n const oldPageSize = getGraphqlPageSize(fieldName);\n if (newPageSize !== oldPageSize) {\n const now = DateTime.local();\n const pageLastResizedAt = now.toISO();\n logger.debug(\n { fieldName, oldPageSize, newPageSize, timestamp: pageLastResizedAt },\n 'GraphQL page size: shrinking',\n );\n const cache = getCache();\n cache.platform ??= {};\n cache.platform.github ??= {};\n cache.platform.github.graphqlPageCache ??= {};\n const graphqlPageCache = cache.platform.github\n .graphqlPageCache as GraphqlPageCache;\n graphqlPageCache[fieldName] = {\n pageLastResizedAt,\n pageSize: newPageSize,\n };\n }\n}\n\nfunction replaceUrlBase(url: URL, baseUrl: string): URL {\n const relativeUrl = `${url.pathname}${url.search}`;\n return new URL(relativeUrl, baseUrl);\n}\n\nexport class GithubHttp extends HttpBase<GithubHttpOptions> {\n protected override get baseUrl(): string | undefined {\n return baseUrl;\n }\n\n constructor(hostType = 'github', options?: HttpOptions) {\n super(hostType, options);\n }\n\n protected override extraOptions(): readonly string[] {\n return super\n .extraOptions()\n .concat([\n 'pageLimit',\n 'paginate',\n 'paginationField',\n 'repository',\n ] as (keyof GithubHttpOptions)[]);\n }\n\n protected override processOptions(\n url: URL,\n opts: InternalHttpOptions & GithubBaseHttpOptions,\n ): void {\n if (!opts.token) {\n // create a mutable copy of `url`\n const authUrl = parseUrl(url.toString())!;\n\n if (opts.repository) {\n // set authUrl to https://api.github.com/repos/org/repo or https://gihub.domain.com/api/v3/repos/org/repo\n authUrl.hash = '';\n authUrl.search = '';\n authUrl.pathname = joinUrlParts(\n authUrl.pathname.startsWith('/api/v3') ? '/api/v3' : '',\n 'repos',\n `${opts.repository}`,\n );\n }\n\n let readOnly = opts.readOnly;\n const { method = 'get' } = opts;\n if (\n readOnly === undefined &&\n ['get', 'head'].includes(method.toLowerCase())\n ) {\n readOnly = true;\n }\n const { token } = findMatchingRule(authUrl.toString(), {\n hostType: this.hostType,\n readOnly,\n });\n opts.token = token;\n }\n\n const accept = constructAcceptString(opts.headers?.accept);\n\n opts.headers = {\n ...opts.headers,\n accept,\n };\n }\n\n protected override handleError(\n url: string | URL,\n opts: HttpOptions,\n err: GotLegacyError,\n ): never {\n throw handleGotError(err, url, opts);\n }\n\n protected override async requestJsonUnsafe<T>(\n method: HttpMethod,\n options: InternalJsonUnsafeOptions<GithubHttpOptions>,\n ): Promise<HttpResponse<T>> {\n const httpOptions = options.httpOptions ?? {};\n const resolvedUrl = this.resolveUrl(options.url, httpOptions);\n const opts = {\n ...options,\n url: resolvedUrl,\n };\n\n const result = await super.requestJsonUnsafe<T>(method, opts);\n if (httpOptions.paginate) {\n delete httpOptions.cacheProvider;\n httpOptions.memCache = false;\n // Check if result is paginated\n const pageLimit = httpOptions.pageLimit ?? 10;\n const linkHeader = parseLinkHeader(result?.headers?.link);\n const next = linkHeader?.next;\n const env = getEnv();\n if (next?.url && linkHeader?.last?.page) {\n let lastPage = parseInt(linkHeader.last.page, 10);\n // v8 ignore else -- TODO: add test #40625\n if (!env.RENOVATE_PAGINATE_ALL && httpOptions.paginate !== 'all') {\n lastPage = Math.min(pageLimit, lastPage);\n }\n const baseUrl = httpOptions.baseUrl ?? this.baseUrl;\n const parsedUrl = new URL(next.url, baseUrl);\n const rebasePagination =\n !!baseUrl &&\n !!env.RENOVATE_X_REBASE_PAGINATION_LINKS &&\n // Preserve github.com URLs for use cases like release notes\n parsedUrl.origin !== 'https://api.github.com';\n const firstPageUrl = rebasePagination\n ? replaceUrlBase(parsedUrl, baseUrl)\n : parsedUrl;\n const queue = [...range(2, lastPage)].map(\n (pageNumber) => (): Promise<HttpResponse<T>> => {\n // copy before modifying searchParams\n const nextUrl = parseUrl(firstPageUrl.toString())!;\n nextUrl.searchParams.set('page', String(pageNumber));\n return super.requestJsonUnsafe<T>(method, {\n ...opts,\n url: nextUrl,\n });\n },\n );\n const pages = await p.all(queue);\n // v8 ignore else -- TODO: add test #40625\n if (httpOptions.paginationField && isPlainObject(result.body)) {\n const paginatedResult = result.body[httpOptions.paginationField];\n // v8 ignore else -- TODO: add test #40625\n if (isArray<T>(paginatedResult)) {\n for (const nextPage of pages) {\n // v8 ignore else -- TODO: add test #40625\n if (isPlainObject(nextPage.body)) {\n const nextPageResults =\n nextPage.body[httpOptions.paginationField];\n // v8 ignore else -- TODO: add test #40625\n if (isArray<T>(nextPageResults)) {\n paginatedResult.push(...nextPageResults);\n }\n }\n }\n }\n } else if (isArray<T>(result.body)) {\n for (const nextPage of pages) {\n // v8 ignore else -- TODO: add test #40625\n if (isArray<T>(nextPage.body)) {\n result.body.push(...nextPage.body);\n }\n }\n }\n }\n }\n return result;\n }\n\n public async requestGraphql<T = unknown>(\n query: string,\n options: GraphqlOptions = {},\n ): Promise<GithubGraphqlResponse<T> | null> {\n const path = 'graphql';\n\n const { paginate, count = MAX_GRAPHQL_PAGE_SIZE, cursor = null } = options;\n let { variables } = options;\n if (paginate) {\n variables = {\n ...variables,\n count,\n cursor,\n };\n }\n const body = variables ? { query, variables } : { query };\n\n const opts: GithubBaseHttpOptions = {\n baseUrl: baseUrl.replace('/v3/', '/'), // GHE uses unversioned graphql path\n body,\n headers: { accept: options?.acceptHeader },\n readOnly: options.readOnly,\n };\n if (options.token) {\n opts.token = options.token;\n }\n logger.trace(`Performing Github GraphQL request`);\n\n try {\n const res = await this.postJson<GithubGraphqlResponse<T>>(path, opts);\n return res?.body;\n } catch (err) {\n logger.debug({ err, query, options }, 'Unexpected GraphQL Error');\n if (err instanceof ExternalHostError && count && count > 10) {\n logger.info('Reducing pagination count to workaround graphql errors');\n return null;\n }\n throw handleGotError(err, path, opts);\n }\n }\n\n async queryRepoField<T = Record<string, unknown>>(\n query: string,\n fieldName: string,\n options: GraphqlOptions = {},\n ): Promise<T[]> {\n const result: T[] = [];\n\n const { paginate = true } = options;\n\n let optimalCount: null | number = null;\n let count = getGraphqlPageSize(\n fieldName,\n options.count ?? MAX_GRAPHQL_PAGE_SIZE,\n );\n let limit = options.limit ?? 1000;\n let cursor: string | null = null;\n\n let isIterating = true;\n while (isIterating) {\n const res = await this.requestGraphql<GithubGraphqlRepoData<T>>(query, {\n ...options,\n count: Math.min(count, limit),\n cursor,\n paginate,\n });\n const repositoryData = res?.data?.repository;\n if (\n isNonEmptyObject(repositoryData) &&\n !isNullOrUndefined(repositoryData[fieldName])\n ) {\n optimalCount = count;\n\n const {\n nodes = [],\n edges = [],\n pageInfo,\n } = repositoryData[fieldName] as GraphqlPaginatedContent<T>;\n result.push(...nodes);\n result.push(...edges);\n\n limit = Math.max(0, limit - nodes.length - edges.length);\n\n if (limit === 0) {\n isIterating = false;\n } else if (paginate && pageInfo) {\n const { hasNextPage, endCursor } = pageInfo;\n if (hasNextPage && endCursor) {\n cursor = endCursor;\n } else {\n isIterating = false;\n }\n }\n } else {\n count = Math.floor(count / 2);\n if (count === 0) {\n logger.warn({ query, options, res }, 'Error fetching GraphQL nodes');\n isIterating = false;\n }\n }\n\n if (!paginate) {\n isIterating = false;\n }\n }\n\n if (optimalCount && optimalCount < MAX_GRAPHQL_PAGE_SIZE) {\n setGraphqlPageSize(fieldName, optimalCount);\n }\n\n return result;\n }\n\n /**\n * Get the raw text file from a URL.\n * Only use this method to fetch text files.\n *\n * @param url Full API URL, contents path or path inside the repository to the file\n * @param options\n *\n * @example url = 'https://api.github.com/repos/renovatebot/renovate/contents/package.json'\n * @example url = 'renovatebot/renovate/contents/package.json'\n * @example url = 'package.json' & options.repository = 'renovatebot/renovate'\n */\n public async getRawTextFile(\n url: string,\n options: InternalHttpOptions & GithubBaseHttpOptions = {},\n ): Promise<HttpResponse> {\n const newOptions: InternalHttpOptions & GithubBaseHttpOptions = {\n ...options,\n headers: {\n accept: 'application/vnd.github.raw+json',\n },\n };\n\n let newURL = url;\n const httpRegex = regEx(/^https?:\\/\\//);\n if (options.repository && !httpRegex.test(options.repository)) {\n newURL = joinUrlParts(options.repository, 'contents', url);\n }\n\n return await this.getText(newURL, newOptions);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"github.js","names":["hostRules.find","p.all"],"sources":["../../../lib/util/http/github.ts"],"sourcesContent":["import {\n isArray,\n isNonEmptyObject,\n isNullOrUndefined,\n isPlainObject,\n} from '@sindresorhus/is';\nimport { DateTime } from 'luxon';\nimport { z } from 'zod/v4';\nimport { GlobalConfig } from '../../config/global.ts';\nimport {\n PLATFORM_BAD_CREDENTIALS,\n PLATFORM_INTEGRATION_UNAUTHORIZED,\n PLATFORM_RATE_LIMIT_EXCEEDED,\n REPOSITORY_CHANGED,\n} from '../../constants/error-messages.ts';\nimport { logger } from '../../logger/index.ts';\nimport { ExternalHostError } from '../../types/errors/external-host-error.ts';\nimport { getCache } from '../cache/repository/index.ts';\nimport { getEnv } from '../env.ts';\nimport * as hostRules from '../host-rules.ts';\nimport { maskToken } from '../mask.ts';\nimport * as p from '../promises.ts';\nimport { range } from '../range.ts';\nimport { regEx } from '../regex.ts';\nimport { LooseArray } from '../schema-utils/index.ts';\nimport { joinUrlParts, parseLinkHeader, parseUrl } from '../url.ts';\nimport { findMatchingRule } from './host-rules.ts';\nimport {\n HttpBase,\n type InternalHttpOptions,\n type InternalJsonUnsafeOptions,\n} from './http.ts';\nimport type { GotLegacyError } from './legacy.ts';\nimport type {\n GraphqlOptions,\n HttpMethod,\n HttpOptions,\n HttpResponse,\n} from './types.ts';\n\nconst githubBaseUrl = 'https://api.github.com/';\nlet baseUrl = githubBaseUrl;\nexport function setBaseUrl(url: string): void {\n baseUrl = url;\n}\n\nexport interface GithubBaseHttpOptions extends HttpOptions {\n repository?: string;\n}\n\nexport interface GithubHttpOptions extends GithubBaseHttpOptions {\n paginate?: boolean | string;\n paginationField?: string;\n pageLimit?: number;\n}\n\ninterface GithubGraphqlRepoData<T = unknown> {\n repository?: T;\n}\n\nexport type GithubGraphqlResponse<T = unknown> =\n | {\n data: T;\n errors?: never;\n }\n | {\n data?: never;\n errors: {\n type?: string;\n message: string;\n }[];\n };\n\nconst GithubError = z.object({\n field: z.string().optional(),\n code: z.string().optional(),\n message: z.string().optional(),\n});\n\n/**\n * GitHub usually returns `errors` as an array, but not always - normalize it\n * into an array of error objects, dropping anything that doesn't fit.\n */\nconst GithubErrors = z\n .union([LooseArray(GithubError), GithubError.transform((error) => [error])])\n .catch([]);\n\nfunction handleGotError(\n err: GotLegacyError,\n url: string | URL,\n opts: GithubBaseHttpOptions,\n): Error {\n const path = url.toString();\n let message = err.message || '';\n const body = err.response?.body;\n if (isPlainObject(body) && 'message' in body) {\n message = String(body.message);\n }\n if (\n err.code === 'ERR_HTTP2_STREAM_ERROR' ||\n err.code === 'ENOTFOUND' ||\n err.code === 'ETIMEDOUT' ||\n err.code === 'EAI_AGAIN' ||\n err.code === 'ECONNRESET'\n ) {\n logger.debug({ err }, 'GitHub failure: RequestError');\n return new ExternalHostError(err, 'github');\n }\n if (err.name === 'ParseError') {\n logger.debug({ err }, '');\n return new ExternalHostError(err, 'github');\n }\n if (err.statusCode && err.statusCode >= 500 && err.statusCode < 600) {\n logger.debug({ err }, 'GitHub failure: 5xx');\n return new ExternalHostError(err, 'github');\n }\n if (\n err.statusCode === 403 &&\n message.startsWith('You have triggered an abuse detection mechanism')\n ) {\n logger.debug({ err }, 'GitHub failure: abuse detection');\n return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);\n }\n if (\n err.statusCode === 403 &&\n message.startsWith('You have exceeded a secondary rate limit')\n ) {\n logger.warn({ err }, 'GitHub failure: secondary rate limit');\n return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);\n }\n if (err.statusCode === 403 && message.includes('Upgrade to GitHub Pro')) {\n logger.debug(`Endpoint: ${path}, needs paid GitHub plan`);\n return err;\n }\n if (err.statusCode === 403 && message.includes('rate limit exceeded')) {\n logger.debug({ err }, 'GitHub failure: rate limit');\n\n const parsed = parseUrl(baseUrl);\n\n const rule = hostRules.find({ url: baseUrl });\n if (rule.token || rule.password) {\n logger.once.warn(\n 'Rate limit exceeded for api.github.com, even though we are authenticated',\n );\n } else {\n if (parsed?.hostname === 'api.github.com') {\n logger.once.warn(\n {\n documentationUrl: `${GlobalConfig.get('productLinks').documentation}getting-started/running/#githubcom-token-for-changelogs-and-tools`,\n },\n 'Rate limit exceeded for api.github.com, as no hostRules set for this host. Please set a GITHUB_COM_TOKEN',\n );\n } else {\n logger.once.warn(\n { host: parsed!.host },\n 'Rate limit exceeded, as no hostRules set for this host',\n );\n }\n }\n\n return new Error(PLATFORM_RATE_LIMIT_EXCEEDED);\n }\n if (\n err.statusCode === 403 &&\n message.startsWith('Resource not accessible by integration')\n ) {\n logger.debug(\n { err },\n 'GitHub failure: Resource not accessible by integration',\n );\n return new Error(PLATFORM_INTEGRATION_UNAUTHORIZED);\n }\n if (err.statusCode === 401) {\n // Warn once for github.com token if unauthorized\n const hostname = parseUrl(url)?.hostname;\n // v8 ignore else -- TODO: add test #40625\n if (hostname === 'github.com' || hostname === 'api.github.com') {\n logger.once.warn('github.com token 401 unauthorized');\n }\n if (message.includes('Bad credentials')) {\n const rateLimit = err.headers?.['x-ratelimit-limit'] ?? -1;\n logger.debug(\n {\n token: maskToken(opts.token),\n err,\n },\n 'GitHub failure: Bad credentials',\n );\n if (rateLimit === '60') {\n return new ExternalHostError(err, 'github');\n }\n return new Error(PLATFORM_BAD_CREDENTIALS);\n }\n }\n if (err.statusCode === 422) {\n if (\n message.includes('Review cannot be requested from pull request author')\n ) {\n return err;\n }\n\n const errors = GithubErrors.parse(err.body?.errors);\n if (errors.some((e) => e.field === 'milestone')) {\n return err;\n }\n if (errors.some((e) => e.code === 'invalid')) {\n logger.debug({ err }, 'Received invalid response - aborting');\n return new Error(REPOSITORY_CHANGED);\n }\n if (\n errors.some((e) => e.message?.startsWith('A pull request already exists'))\n ) {\n return err;\n }\n logger.debug({ err }, '422 Error thrown from GitHub');\n return new ExternalHostError(err, 'github');\n }\n if (\n err.statusCode === 410 &&\n err.body?.message === 'Issues are disabled for this repo'\n ) {\n return err;\n }\n return err;\n}\n\ninterface GraphqlPaginatedContent<T = unknown> {\n nodes?: T[];\n edges?: T[];\n pageInfo: { hasNextPage: boolean; endCursor: string };\n}\n\nfunction constructAcceptString(input?: unknown): string {\n const defaultAccept = 'application/vnd.github.v3+json';\n const acceptStrings =\n typeof input === 'string' ? input.split(regEx(/\\s*,\\s*/)) : [];\n\n // TODO: regression of #6736\n // v8 ignore else -- TODO: add test #40625\n if (\n !acceptStrings.some((x) => x === defaultAccept) &&\n (!acceptStrings.some((x) => x.startsWith('application/vnd.github.')) ||\n acceptStrings.length < 2)\n ) {\n acceptStrings.push(defaultAccept);\n }\n return acceptStrings.join(', ');\n}\n\nconst MAX_GRAPHQL_PAGE_SIZE = 100;\n\ninterface GraphqlPageCacheItem {\n pageLastResizedAt: string;\n pageSize: number;\n}\n\nexport type GraphqlPageCache = Record<string, GraphqlPageCacheItem>;\n\nfunction getGraphqlPageSize(\n fieldName: string,\n defaultPageSize = MAX_GRAPHQL_PAGE_SIZE,\n): number {\n const cache = getCache();\n const graphqlPageCache = cache?.platform?.github\n ?.graphqlPageCache as GraphqlPageCache;\n const cachedRecord = graphqlPageCache?.[fieldName];\n\n if (graphqlPageCache && cachedRecord) {\n logger.debug(\n { fieldName, ...cachedRecord },\n 'GraphQL page size: found cached value',\n );\n\n const oldPageSize = cachedRecord.pageSize;\n\n const now = DateTime.local();\n const then = DateTime.fromISO(cachedRecord.pageLastResizedAt);\n const expiry = then.plus({ hours: 24 });\n if (now > expiry) {\n const newPageSize = Math.min(oldPageSize * 2, MAX_GRAPHQL_PAGE_SIZE);\n if (newPageSize < MAX_GRAPHQL_PAGE_SIZE) {\n const timestamp = now.toISO();\n\n logger.debug(\n { fieldName, oldPageSize, newPageSize, timestamp },\n 'GraphQL page size: expanding',\n );\n\n cachedRecord.pageLastResizedAt = timestamp;\n cachedRecord.pageSize = newPageSize;\n } else {\n logger.debug(\n { fieldName, oldPageSize, newPageSize },\n 'GraphQL page size: expanded to default page size',\n );\n\n delete graphqlPageCache[fieldName];\n }\n\n return newPageSize;\n }\n\n return oldPageSize;\n }\n\n return defaultPageSize;\n}\n\nfunction setGraphqlPageSize(fieldName: string, newPageSize: number): void {\n const oldPageSize = getGraphqlPageSize(fieldName);\n if (newPageSize !== oldPageSize) {\n const now = DateTime.local();\n const pageLastResizedAt = now.toISO();\n logger.debug(\n { fieldName, oldPageSize, newPageSize, timestamp: pageLastResizedAt },\n 'GraphQL page size: shrinking',\n );\n const cache = getCache();\n cache.platform ??= {};\n cache.platform.github ??= {};\n cache.platform.github.graphqlPageCache ??= {};\n const graphqlPageCache = cache.platform.github\n .graphqlPageCache as GraphqlPageCache;\n graphqlPageCache[fieldName] = {\n pageLastResizedAt,\n pageSize: newPageSize,\n };\n }\n}\n\nfunction replaceUrlBase(url: URL, baseUrl: string): URL {\n const relativeUrl = `${url.pathname}${url.search}`;\n return new URL(relativeUrl, baseUrl);\n}\n\nexport class GithubHttp extends HttpBase<GithubHttpOptions> {\n protected override get baseUrl(): string | undefined {\n return baseUrl;\n }\n\n constructor(hostType = 'github', options?: HttpOptions) {\n super(hostType, options);\n }\n\n protected override extraOptions(): readonly string[] {\n return super\n .extraOptions()\n .concat([\n 'pageLimit',\n 'paginate',\n 'paginationField',\n 'repository',\n ] as (keyof GithubHttpOptions)[]);\n }\n\n protected override processOptions(\n url: URL,\n opts: InternalHttpOptions & GithubBaseHttpOptions,\n ): void {\n if (!opts.token) {\n // create a mutable copy of `url`\n const authUrl = parseUrl(url.toString())!;\n\n if (opts.repository) {\n // set authUrl to https://api.github.com/repos/org/repo or https://gihub.domain.com/api/v3/repos/org/repo\n authUrl.hash = '';\n authUrl.search = '';\n authUrl.pathname = joinUrlParts(\n authUrl.pathname.startsWith('/api/v3') ? '/api/v3' : '',\n 'repos',\n `${opts.repository}`,\n );\n }\n\n let readOnly = opts.readOnly;\n const { method = 'get' } = opts;\n if (\n readOnly === undefined &&\n ['get', 'head'].includes(method.toLowerCase())\n ) {\n readOnly = true;\n }\n const { token } = findMatchingRule(authUrl.toString(), {\n hostType: this.hostType,\n readOnly,\n });\n opts.token = token;\n }\n\n const accept = constructAcceptString(opts.headers?.accept);\n\n opts.headers = {\n ...opts.headers,\n accept,\n };\n }\n\n protected override handleError(\n url: string | URL,\n opts: HttpOptions,\n err: GotLegacyError,\n ): never {\n throw handleGotError(err, url, opts);\n }\n\n protected override async requestJsonUnsafe<T>(\n method: HttpMethod,\n options: InternalJsonUnsafeOptions<GithubHttpOptions>,\n ): Promise<HttpResponse<T>> {\n const httpOptions = options.httpOptions ?? {};\n const resolvedUrl = this.resolveUrl(options.url, httpOptions);\n const opts = {\n ...options,\n url: resolvedUrl,\n };\n\n const result = await super.requestJsonUnsafe<T>(method, opts);\n if (httpOptions.paginate) {\n delete httpOptions.cacheProvider;\n httpOptions.memCache = false;\n // Check if result is paginated\n const pageLimit = httpOptions.pageLimit ?? 10;\n const linkHeader = parseLinkHeader(result?.headers?.link);\n const next = linkHeader?.next;\n const env = getEnv();\n if (next?.url && linkHeader?.last?.page) {\n let lastPage = parseInt(linkHeader.last.page, 10);\n // v8 ignore else -- TODO: add test #40625\n if (!env.RENOVATE_PAGINATE_ALL && httpOptions.paginate !== 'all') {\n lastPage = Math.min(pageLimit, lastPage);\n }\n const baseUrl = httpOptions.baseUrl ?? this.baseUrl;\n const parsedUrl = new URL(next.url, baseUrl);\n const rebasePagination =\n !!baseUrl &&\n !!env.RENOVATE_X_REBASE_PAGINATION_LINKS &&\n // Preserve github.com URLs for use cases like release notes\n parsedUrl.origin !== 'https://api.github.com';\n const firstPageUrl = rebasePagination\n ? replaceUrlBase(parsedUrl, baseUrl)\n : parsedUrl;\n // Don't follow a cross-origin request, unless we've been explicitly requested to do so with `RENOVATE_X_REBASE_PAGINATION_LINKS`\n if (firstPageUrl.origin === resolvedUrl.origin) {\n const queue = [...range(2, lastPage)].map(\n (pageNumber) => (): Promise<HttpResponse<T>> => {\n // copy before modifying searchParams\n const nextUrl = parseUrl(firstPageUrl.toString())!;\n nextUrl.searchParams.set('page', String(pageNumber));\n return super.requestJsonUnsafe<T>(method, {\n ...opts,\n url: nextUrl,\n });\n },\n );\n const pages = await p.all(queue);\n // v8 ignore else -- TODO: add test #40625\n if (httpOptions.paginationField && isPlainObject(result.body)) {\n const paginatedResult = result.body[httpOptions.paginationField];\n // v8 ignore else -- TODO: add test #40625\n if (isArray<T>(paginatedResult)) {\n for (const nextPage of pages) {\n // v8 ignore else -- TODO: add test #40625\n if (isPlainObject(nextPage.body)) {\n const nextPageResults =\n nextPage.body[httpOptions.paginationField];\n // v8 ignore else -- TODO: add test #40625\n if (isArray<T>(nextPageResults)) {\n paginatedResult.push(...nextPageResults);\n }\n }\n }\n }\n } else if (isArray<T>(result.body)) {\n for (const nextPage of pages) {\n // v8 ignore else -- TODO: add test #40625\n if (isArray<T>(nextPage.body)) {\n result.body.push(...nextPage.body);\n }\n }\n }\n } else {\n // make sure that users are aware if there are any (potentially malicious, or misconfigured) pagination links being returned\n logger.once.warn(\n {\n requestHost: resolvedUrl.host,\n paginationHost: firstPageUrl.host,\n },\n 'Ignoring cross-origin GitHub pagination link. Set RENOVATE_X_REBASE_PAGINATION_LINKS if this is a self-hosted instance that returns a different host in pagination links.',\n );\n }\n }\n }\n return result;\n }\n\n public async requestGraphql<T = unknown>(\n query: string,\n options: GraphqlOptions = {},\n ): Promise<GithubGraphqlResponse<T> | null> {\n const path = 'graphql';\n\n const { paginate, count = MAX_GRAPHQL_PAGE_SIZE, cursor = null } = options;\n let { variables } = options;\n if (paginate) {\n variables = {\n ...variables,\n count,\n cursor,\n };\n }\n const body = variables ? { query, variables } : { query };\n\n const opts: GithubBaseHttpOptions = {\n baseUrl: baseUrl.replace('/v3/', '/'), // GHE uses unversioned graphql path\n body,\n headers: { accept: options?.acceptHeader },\n readOnly: options.readOnly,\n };\n if (options.token) {\n opts.token = options.token;\n }\n logger.trace(`Performing Github GraphQL request`);\n\n try {\n const res = await this.postJson<GithubGraphqlResponse<T>>(path, opts);\n return res?.body;\n } catch (err) {\n logger.debug({ err, query, options }, 'Unexpected GraphQL Error');\n if (err instanceof ExternalHostError && count && count > 10) {\n logger.info('Reducing pagination count to workaround graphql errors');\n return null;\n }\n throw handleGotError(err, path, opts);\n }\n }\n\n async queryRepoField<T = Record<string, unknown>>(\n query: string,\n fieldName: string,\n options: GraphqlOptions = {},\n ): Promise<T[]> {\n const result: T[] = [];\n\n const { paginate = true } = options;\n\n let optimalCount: null | number = null;\n let count = getGraphqlPageSize(\n fieldName,\n options.count ?? MAX_GRAPHQL_PAGE_SIZE,\n );\n let limit = options.limit ?? 1000;\n let cursor: string | null = null;\n\n let isIterating = true;\n while (isIterating) {\n const res = await this.requestGraphql<GithubGraphqlRepoData<T>>(query, {\n ...options,\n count: Math.min(count, limit),\n cursor,\n paginate,\n });\n const repositoryData = res?.data?.repository;\n if (\n isNonEmptyObject(repositoryData) &&\n !isNullOrUndefined(repositoryData[fieldName])\n ) {\n optimalCount = count;\n\n const {\n nodes = [],\n edges = [],\n pageInfo,\n } = repositoryData[fieldName] as GraphqlPaginatedContent<T>;\n result.push(...nodes);\n result.push(...edges);\n\n limit = Math.max(0, limit - nodes.length - edges.length);\n\n if (limit === 0) {\n isIterating = false;\n } else if (paginate && pageInfo) {\n const { hasNextPage, endCursor } = pageInfo;\n if (hasNextPage && endCursor) {\n cursor = endCursor;\n } else {\n isIterating = false;\n }\n }\n } else {\n count = Math.floor(count / 2);\n if (count === 0) {\n logger.warn({ query, options, res }, 'Error fetching GraphQL nodes');\n isIterating = false;\n }\n }\n\n if (!paginate) {\n isIterating = false;\n }\n }\n\n if (optimalCount && optimalCount < MAX_GRAPHQL_PAGE_SIZE) {\n setGraphqlPageSize(fieldName, optimalCount);\n }\n\n return result;\n }\n\n /**\n * Get the raw text file from a URL.\n * Only use this method to fetch text files.\n *\n * @param url Full API URL, contents path or path inside the repository to the file\n * @param options\n *\n * @example url = 'https://api.github.com/repos/renovatebot/renovate/contents/package.json'\n * @example url = 'renovatebot/renovate/contents/package.json'\n * @example url = 'package.json' & options.repository = 'renovatebot/renovate'\n */\n public async getRawTextFile(\n url: string,\n options: InternalHttpOptions & GithubBaseHttpOptions = {},\n ): Promise<HttpResponse> {\n const newOptions: InternalHttpOptions & GithubBaseHttpOptions = {\n ...options,\n headers: {\n accept: 'application/vnd.github.raw+json',\n },\n };\n\n let newURL = url;\n const httpRegex = regEx(/^https?:\\/\\//);\n if (options.repository && !httpRegex.test(options.repository)) {\n newURL = joinUrlParts(options.repository, 'contents', url);\n }\n\n return await this.getText(newURL, newOptions);\n }\n}\n"],"mappings":"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"}
|
package/dist/util/http/gitlab.js
CHANGED
|
@@ -40,10 +40,15 @@ var GitlabHttp = class extends HttpBase {
|
|
|
40
40
|
nextUrl.protocol = defaultEndpoint.protocol;
|
|
41
41
|
nextUrl.host = defaultEndpoint.host;
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
if (nextUrl.origin === resolvedUrl.origin) {
|
|
44
|
+
opts.url = nextUrl;
|
|
45
|
+
const nextResult = await this.requestJsonUnsafe(method, opts);
|
|
46
|
+
// v8 ignore else -- TODO: add test #40625
|
|
47
|
+
if (isArray(nextResult.body)) result.body.push(...nextResult.body);
|
|
48
|
+
} else logger.once.warn({
|
|
49
|
+
requestHost: resolvedUrl.host,
|
|
50
|
+
paginationHost: nextUrl.host
|
|
51
|
+
}, "Ignoring cross-origin GitLab pagination link. Set GITLAB_IGNORE_REPO_URL if this is a self-hosted instance that returns a different host in pagination links.");
|
|
47
52
|
}
|
|
48
53
|
} catch (err) {
|
|
49
54
|
logger.warn({ err }, "Pagination error");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gitlab.js","names":[],"sources":["../../../lib/util/http/gitlab.ts"],"sourcesContent":["import { isArray, isString } from '@sindresorhus/is';\nimport { RequestError, type RetryObject } from 'got';\nimport { logger } from '../../logger/index.ts';\nimport { ExternalHostError } from '../../types/errors/external-host-error.ts';\nimport { getEnv } from '../env.ts';\nimport { parseLinkHeader, parseUrl } from '../url.ts';\nimport { HttpBase, type InternalJsonUnsafeOptions } from './http.ts';\nimport type { HttpMethod, HttpOptions, HttpResponse } from './types.ts';\n\nlet baseUrl = 'https://gitlab.com/api/v4/';\nexport function setBaseUrl(url: string): void {\n baseUrl = url;\n}\n\nexport interface GitlabHttpOptions extends HttpOptions {\n paginate?: boolean;\n}\n\nexport class GitlabHttp extends HttpBase<GitlabHttpOptions> {\n protected override get baseUrl(): string | undefined {\n return baseUrl;\n }\n\n constructor(type = 'gitlab', options?: GitlabHttpOptions) {\n super(type, options);\n }\n\n protected override extraOptions(): readonly string[] {\n return super\n .extraOptions()\n .concat(['paginate'] as (keyof GitlabHttpOptions)[]);\n }\n\n protected override async requestJsonUnsafe<T = unknown>(\n method: HttpMethod,\n options: InternalJsonUnsafeOptions<GitlabHttpOptions>,\n ): Promise<HttpResponse<T>> {\n const resolvedUrl = this.resolveUrl(options.url, options.httpOptions);\n const opts = {\n ...options,\n url: resolvedUrl,\n };\n opts.httpOptions ??= {};\n opts.httpOptions.throwHttpErrors = true;\n\n const result = await super.requestJsonUnsafe<T>(method, opts);\n if (opts.httpOptions.paginate && isArray(result.body)) {\n opts.httpOptions.memCache = false;\n\n // Check if result is paginated\n try {\n const linkHeader = parseLinkHeader(result.headers.link);\n const nextUrl = linkHeader?.next?.url\n ? parseUrl(linkHeader.next.url)\n : null;\n if (nextUrl) {\n if (getEnv().GITLAB_IGNORE_REPO_URL) {\n const defaultEndpoint = parseUrl(baseUrl)!;\n nextUrl.protocol = defaultEndpoint.protocol;\n nextUrl.host = defaultEndpoint.host;\n }\n\n opts.url = nextUrl;\n\n
|
|
1
|
+
{"version":3,"file":"gitlab.js","names":[],"sources":["../../../lib/util/http/gitlab.ts"],"sourcesContent":["import { isArray, isString } from '@sindresorhus/is';\nimport { RequestError, type RetryObject } from 'got';\nimport { logger } from '../../logger/index.ts';\nimport { ExternalHostError } from '../../types/errors/external-host-error.ts';\nimport { getEnv } from '../env.ts';\nimport { parseLinkHeader, parseUrl } from '../url.ts';\nimport { HttpBase, type InternalJsonUnsafeOptions } from './http.ts';\nimport type { HttpMethod, HttpOptions, HttpResponse } from './types.ts';\n\nlet baseUrl = 'https://gitlab.com/api/v4/';\nexport function setBaseUrl(url: string): void {\n baseUrl = url;\n}\n\nexport interface GitlabHttpOptions extends HttpOptions {\n paginate?: boolean;\n}\n\nexport class GitlabHttp extends HttpBase<GitlabHttpOptions> {\n protected override get baseUrl(): string | undefined {\n return baseUrl;\n }\n\n constructor(type = 'gitlab', options?: GitlabHttpOptions) {\n super(type, options);\n }\n\n protected override extraOptions(): readonly string[] {\n return super\n .extraOptions()\n .concat(['paginate'] as (keyof GitlabHttpOptions)[]);\n }\n\n protected override async requestJsonUnsafe<T = unknown>(\n method: HttpMethod,\n options: InternalJsonUnsafeOptions<GitlabHttpOptions>,\n ): Promise<HttpResponse<T>> {\n const resolvedUrl = this.resolveUrl(options.url, options.httpOptions);\n const opts = {\n ...options,\n url: resolvedUrl,\n };\n opts.httpOptions ??= {};\n opts.httpOptions.throwHttpErrors = true;\n\n const result = await super.requestJsonUnsafe<T>(method, opts);\n if (opts.httpOptions.paginate && isArray(result.body)) {\n opts.httpOptions.memCache = false;\n\n // Check if result is paginated\n try {\n const linkHeader = parseLinkHeader(result.headers.link);\n const nextUrl = linkHeader?.next?.url\n ? parseUrl(linkHeader.next.url)\n : null;\n if (nextUrl) {\n if (getEnv().GITLAB_IGNORE_REPO_URL) {\n const defaultEndpoint = parseUrl(baseUrl)!;\n nextUrl.protocol = defaultEndpoint.protocol;\n nextUrl.host = defaultEndpoint.host;\n }\n\n // Don't follow a cross-origin request, unless we've been explicitly requested to do so with `RENOVATE_X_REBASE_PAGINATION_LINKS`\n if (nextUrl.origin === resolvedUrl.origin) {\n opts.url = nextUrl;\n\n const nextResult = await this.requestJsonUnsafe<T>(method, opts);\n // v8 ignore else -- TODO: add test #40625\n if (isArray(nextResult.body)) {\n result.body.push(...nextResult.body);\n }\n } else {\n // make sure that users are aware if there are any (potentially malicious, or misconfigured) pagination links being returned\n logger.once.warn(\n { requestHost: resolvedUrl.host, paginationHost: nextUrl.host },\n 'Ignoring cross-origin GitLab pagination link. Set GITLAB_IGNORE_REPO_URL if this is a self-hosted instance that returns a different host in pagination links.',\n );\n }\n }\n } catch (err) {\n logger.warn({ err }, 'Pagination error');\n }\n }\n return result;\n }\n\n protected override handleError(\n url: string | URL,\n _httpOptions: HttpOptions,\n err: Error,\n ): never {\n if (err instanceof RequestError && err.response?.statusCode) {\n if (err.response.statusCode === 404) {\n logger.trace({ err }, 'GitLab 404');\n logger.debug({ url }, 'GitLab API 404');\n throw err;\n }\n logger.debug({ err }, 'Gitlab API error');\n if (\n err.response.statusCode === 429 ||\n (err.response.statusCode >= 500 && err.response.statusCode < 600)\n ) {\n throw new ExternalHostError(err, 'gitlab');\n }\n }\n const platformFailureCodes = [\n 'EAI_AGAIN',\n 'ECONNRESET',\n 'ETIMEDOUT',\n 'UNABLE_TO_VERIFY_LEAF_SIGNATURE',\n ];\n // TODO: fix test, should be `RequestError`\n if (\n 'code' in err &&\n isString(err.code) &&\n platformFailureCodes.includes(err.code)\n ) {\n throw new ExternalHostError(err, 'gitlab');\n }\n if (err.name === 'ParseError') {\n throw new ExternalHostError(err, 'gitlab');\n }\n throw err;\n }\n\n protected override calculateRetryDelay(retryObject: RetryObject): number {\n const { error, attemptCount, retryOptions } = retryObject;\n if (\n attemptCount <= retryOptions.limit &&\n error.options.method === 'POST' &&\n isRetryablePostError(error)\n ) {\n const noise = Math.random() * 100;\n return 2 ** (attemptCount - 1) * 1000 + noise;\n }\n\n return super.calculateRetryDelay(retryObject);\n }\n}\n\n/**\n * Detects transient GitLab errors on POST requests that are safe to retry:\n *\n * - `409` with a `Resource lock` message, which happens when concurrent\n * requests conflict.\n * - `400` with a `source_branch does not exist` message, which happens when a\n * freshly pushed branch is not yet visible to the API due to Gitaly eventual\n * consistency.\n */\nfunction isRetryablePostError(error: RequestError): boolean {\n const { response } = error;\n if (response?.statusCode === 409) {\n return response.rawBody.toString().includes('Resource lock');\n }\n\n if (response?.statusCode === 400) {\n const rawBody = response.rawBody.toString();\n return (\n rawBody.includes('source_branch') && rawBody.includes('does not exist')\n );\n }\n\n return false;\n}\n"],"mappings":"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"}
|
package/dist/util/url.d.ts
CHANGED
|
@@ -20,6 +20,22 @@ declare function resolveBaseUrl(baseUrl: string, input: string | URL): string;
|
|
|
20
20
|
* @param path - replacement path (if this is a full URL, it will be returned)
|
|
21
21
|
*/
|
|
22
22
|
declare function replaceUrlPath(baseUrl: string | URL, path: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Resolves a server-provided pagination "next" URL against the current request
|
|
25
|
+
* URL, returning it only when it stays on the same origin.
|
|
26
|
+
*
|
|
27
|
+
* Registries paginate by returning a `Link` header (or Atom `<link rel="next">`)
|
|
28
|
+
* pointing at the next page.
|
|
29
|
+
*
|
|
30
|
+
* However, if we were to follow that URL without validating it, this could lead to us being redirected to a different host, which could lead to Server-Side Request Forgery (SSRF).
|
|
31
|
+
*
|
|
32
|
+
* This guard drops any `next` URL that resolves to a different origin.
|
|
33
|
+
*
|
|
34
|
+
* @param baseUrl - the URL of the request that produced `nextUrl`
|
|
35
|
+
* @param nextUrl - the remote-server-provided pagination target (may be relative, and may be malicious)
|
|
36
|
+
* @returns the resolved absolute URL if same-origin, otherwise `null`
|
|
37
|
+
*/
|
|
38
|
+
declare function resolveSameOriginUrl(baseUrl: string | URL, nextUrl: string | URL): string | null;
|
|
23
39
|
declare function getQueryString(params: Record<string, any>): string;
|
|
24
40
|
declare function isHttpUrl(url: unknown): boolean;
|
|
25
41
|
declare function parseUrl(url: URL | string | undefined | null): URL | null;
|
|
@@ -36,5 +52,5 @@ declare function parseLinkHeader(linkHeader: string | null | undefined): LinkHea
|
|
|
36
52
|
*/
|
|
37
53
|
declare function massageHostUrl(url: string): string;
|
|
38
54
|
//#endregion
|
|
39
|
-
export { LinkHeaderLinks, createURLFromHostOrURL, ensurePathPrefix, ensureTrailingSlash, getQueryString, isHttpUrl, joinUrlParts, massageHostUrl, parseLinkHeader, parseUrl, replaceUrlPath, resolveBaseUrl, trimLeadingSlash, trimSlashes, trimTrailingSlash };
|
|
55
|
+
export { LinkHeaderLinks, createURLFromHostOrURL, ensurePathPrefix, ensureTrailingSlash, getQueryString, isHttpUrl, joinUrlParts, massageHostUrl, parseLinkHeader, parseUrl, replaceUrlPath, resolveBaseUrl, resolveSameOriginUrl, trimLeadingSlash, trimSlashes, trimTrailingSlash };
|
|
40
56
|
//# sourceMappingURL=url.d.ts.map
|
package/dist/util/url.js
CHANGED
|
@@ -53,6 +53,32 @@ function replaceUrlPath(baseUrl, path) {
|
|
|
53
53
|
const { origin } = isString(baseUrl) ? new URL(baseUrl) : baseUrl;
|
|
54
54
|
return urlJoin(origin, path);
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolves a server-provided pagination "next" URL against the current request
|
|
58
|
+
* URL, returning it only when it stays on the same origin.
|
|
59
|
+
*
|
|
60
|
+
* Registries paginate by returning a `Link` header (or Atom `<link rel="next">`)
|
|
61
|
+
* pointing at the next page.
|
|
62
|
+
*
|
|
63
|
+
* However, if we were to follow that URL without validating it, this could lead to us being redirected to a different host, which could lead to Server-Side Request Forgery (SSRF).
|
|
64
|
+
*
|
|
65
|
+
* This guard drops any `next` URL that resolves to a different origin.
|
|
66
|
+
*
|
|
67
|
+
* @param baseUrl - the URL of the request that produced `nextUrl`
|
|
68
|
+
* @param nextUrl - the remote-server-provided pagination target (may be relative, and may be malicious)
|
|
69
|
+
* @returns the resolved absolute URL if same-origin, otherwise `null`
|
|
70
|
+
*/
|
|
71
|
+
function resolveSameOriginUrl(baseUrl, nextUrl) {
|
|
72
|
+
const base = parseUrl(baseUrl);
|
|
73
|
+
if (!base) return null;
|
|
74
|
+
let resolved;
|
|
75
|
+
try {
|
|
76
|
+
resolved = new URL(nextUrl.toString(), base);
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return resolved.origin === base.origin ? resolved.href : null;
|
|
81
|
+
}
|
|
56
82
|
function getQueryString(params) {
|
|
57
83
|
const usp = new URLSearchParams();
|
|
58
84
|
for (const [k, v] of Object.entries(params)) if (isArray(v)) for (const item of v) usp.append(k, item.toString());
|
|
@@ -98,6 +124,6 @@ function massageHostUrl(url) {
|
|
|
98
124
|
return url;
|
|
99
125
|
}
|
|
100
126
|
//#endregion
|
|
101
|
-
export { createURLFromHostOrURL, ensurePathPrefix, ensureTrailingSlash, getQueryString, isHttpUrl, joinUrlParts, massageHostUrl, parseLinkHeader, parseUrl, replaceUrlPath, resolveBaseUrl, trimLeadingSlash, trimSlashes, trimTrailingSlash };
|
|
127
|
+
export { createURLFromHostOrURL, ensurePathPrefix, ensureTrailingSlash, getQueryString, isHttpUrl, joinUrlParts, massageHostUrl, parseLinkHeader, parseUrl, replaceUrlPath, resolveBaseUrl, resolveSameOriginUrl, trimLeadingSlash, trimSlashes, trimTrailingSlash };
|
|
102
128
|
|
|
103
129
|
//# sourceMappingURL=url.js.map
|
package/dist/util/url.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.js","names":[],"sources":["../../lib/util/url.ts"],"sourcesContent":["import {\n isArray,\n isNonEmptyString,\n isString,\n isUrlInstance,\n} from '@sindresorhus/is';\n// oxlint-disable-next-line no-restricted-imports\nimport _parseLinkHeader from 'parse-link-header';\nimport urlJoin from 'url-join';\nimport { logger } from '../logger/index.ts';\nimport { regEx } from './regex.ts';\n\nexport function joinUrlParts(...parts: string[]): string {\n return urlJoin(...parts);\n}\n\nexport function ensurePathPrefix(url: string, prefix: string): string {\n const parsed = new URL(url);\n const fullPath = parsed.pathname + parsed.search;\n if (fullPath.startsWith(prefix)) {\n return url;\n }\n return parsed.origin + prefix + fullPath;\n}\n\nexport function ensureTrailingSlash(url: string): string {\n return url.replace(/\\/?$/, '/'); // TODO #12875 adds slash at the front when re2 is used\n}\n\nexport function trimTrailingSlash(url: string): string {\n return url.replace(regEx(/\\/+$/), '');\n}\n\nexport function trimLeadingSlash(path: string): string {\n return path.replace(/^\\/+/, '');\n}\n\nexport function trimSlashes(path: string): string {\n return trimLeadingSlash(trimTrailingSlash(path));\n}\n\n/**\n * Resolves an input path against a base URL\n *\n * @param baseUrl - base URL to resolve against\n * @param input - input path (if this is a full URL, it will be returned)\n */\nexport function resolveBaseUrl(baseUrl: string, input: string | URL): string {\n const inputString = input.toString();\n\n let host;\n let pathname;\n try {\n ({ host, pathname } = new URL(inputString));\n } catch {\n pathname = inputString;\n }\n\n return host ? inputString : urlJoin(baseUrl, pathname || '');\n}\n\n/**\n * Replaces the path of a URL with a new path\n *\n * @param baseUrl - source URL\n * @param path - replacement path (if this is a full URL, it will be returned)\n */\nexport function replaceUrlPath(baseUrl: string | URL, path: string): string {\n if (parseUrl(path)) {\n return path;\n }\n\n const { origin } = isString(baseUrl) ? new URL(baseUrl) : baseUrl;\n return urlJoin(origin, path);\n}\n\nexport function getQueryString(params: Record<string, any>): string {\n const usp = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (isArray<object>(v)) {\n for (const item of v) {\n // TODO: fix me?\n // oxlint-disable-next-line typescript/no-base-to-string\n usp.append(k, item.toString());\n }\n } else {\n usp.append(k, v.toString());\n }\n }\n return usp.toString();\n}\n\nexport function isHttpUrl(url: unknown): boolean {\n if (!isNonEmptyString(url) && !isUrlInstance(url)) {\n return false;\n }\n const protocol = parseUrl(url)?.protocol;\n return protocol === 'https:' || protocol === 'http:';\n}\n\nexport function parseUrl(url: URL | string | undefined | null): URL | null {\n if (!url) {\n return null;\n }\n\n if (url instanceof URL) {\n return url;\n }\n\n try {\n return new URL(url);\n } catch {\n return null;\n }\n}\n\n/**\n * Tries to create an URL object from either a full URL string or a hostname\n * @param url either the full url or a hostname\n * @returns an URL object or null\n */\nexport function createURLFromHostOrURL(url: string): URL | null {\n return parseUrl(url) ?? parseUrl(`https://${url}`);\n}\n\nexport type LinkHeaderLinks = _parseLinkHeader.Links;\n\nexport function parseLinkHeader(\n linkHeader: string | null | undefined,\n): LinkHeaderLinks | null {\n if (!isNonEmptyString(linkHeader)) {\n return null;\n }\n if (linkHeader.length > 2000) {\n logger.warn({ linkHeader }, 'Link header too long.');\n return null;\n }\n return _parseLinkHeader(linkHeader);\n}\n\n/**\n * prefix https:// to hosts with port or path\n */\nexport function massageHostUrl(url: string): string {\n if (!url.includes('://') && url.includes('/')) {\n return `https://${url}`;\n }\n if (!url.includes('://') && url.includes(':')) {\n return `https://${url}`;\n }\n return url;\n}\n"],"mappings":";;;;;;AAYA,SAAgB,aAAa,GAAG,OAAyB;CACvD,OAAO,QAAQ,GAAG,KAAK;AACzB;AAEA,SAAgB,iBAAiB,KAAa,QAAwB;CACpE,MAAM,SAAS,IAAI,IAAI,GAAG;CAC1B,MAAM,WAAW,OAAO,WAAW,OAAO;CAC1C,IAAI,SAAS,WAAW,MAAM,GAC5B,OAAO;CAET,OAAO,OAAO,SAAS,SAAS;AAClC;AAEA,SAAgB,oBAAoB,KAAqB;CACvD,OAAO,IAAI,QAAQ,QAAQ,GAAG;AAChC;AAEA,SAAgB,kBAAkB,KAAqB;CACrD,OAAO,IAAI,QAAQ,MAAM,MAAM,GAAG,EAAE;AACtC;AAEA,SAAgB,iBAAiB,MAAsB;CACrD,OAAO,KAAK,QAAQ,QAAQ,EAAE;AAChC;AAEA,SAAgB,YAAY,MAAsB;CAChD,OAAO,iBAAiB,kBAAkB,IAAI,CAAC;AACjD;;;;;;;AAQA,SAAgB,eAAe,SAAiB,OAA6B;CAC3E,MAAM,cAAc,MAAM,SAAS;CAEnC,IAAI;CACJ,IAAI;CACJ,IAAI;EACF,CAAC,CAAE,MAAM,YAAa,IAAI,IAAI,WAAW;CAC3C,QAAQ;EACN,WAAW;CACb;CAEA,OAAO,OAAO,cAAc,QAAQ,SAAS,YAAY,EAAE;AAC7D;;;;;;;AAQA,SAAgB,eAAe,SAAuB,MAAsB;CAC1E,IAAI,SAAS,IAAI,GACf,OAAO;CAGT,MAAM,EAAE,WAAW,SAAS,OAAO,IAAI,IAAI,IAAI,OAAO,IAAI;CAC1D,OAAO,QAAQ,QAAQ,IAAI;AAC7B;AAEA,SAAgB,eAAe,QAAqC;CAClE,MAAM,MAAM,IAAI,gBAAgB;CAChC,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,GACxC,IAAI,QAAgB,CAAC,GACnB,KAAK,MAAM,QAAQ,GAGjB,IAAI,OAAO,GAAG,KAAK,SAAS,CAAC;MAG/B,IAAI,OAAO,GAAG,EAAE,SAAS,CAAC;CAG9B,OAAO,IAAI,SAAS;AACtB;AAEA,SAAgB,UAAU,KAAuB;CAC/C,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC,cAAc,GAAG,GAC9C,OAAO;CAET,MAAM,WAAW,SAAS,GAAG,CAAC,EAAE;CAChC,OAAO,aAAa,YAAY,aAAa;AAC/C;AAEA,SAAgB,SAAS,KAAkD;CACzE,IAAI,CAAC,KACH,OAAO;CAGT,IAAI,eAAe,KACjB,OAAO;CAGT,IAAI;EACF,OAAO,IAAI,IAAI,GAAG;CACpB,QAAQ;EACN,OAAO;CACT;AACF;;;;;;AAOA,SAAgB,uBAAuB,KAAyB;CAC9D,OAAO,SAAS,GAAG,KAAK,SAAS,WAAW,KAAK;AACnD;AAIA,SAAgB,gBACd,YACwB;CACxB,IAAI,CAAC,iBAAiB,UAAU,GAC9B,OAAO;CAET,IAAI,WAAW,SAAS,KAAM;EAC5B,OAAO,KAAK,EAAE,WAAW,GAAG,uBAAuB;EACnD,OAAO;CACT;CACA,OAAO,iBAAiB,UAAU;AACpC;;;;AAKA,SAAgB,eAAe,KAAqB;CAClD,IAAI,CAAC,IAAI,SAAS,KAAK,KAAK,IAAI,SAAS,GAAG,GAC1C,OAAO,WAAW;CAEpB,IAAI,CAAC,IAAI,SAAS,KAAK,KAAK,IAAI,SAAS,GAAG,GAC1C,OAAO,WAAW;CAEpB,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"url.js","names":[],"sources":["../../lib/util/url.ts"],"sourcesContent":["import {\n isArray,\n isNonEmptyString,\n isString,\n isUrlInstance,\n} from '@sindresorhus/is';\n// oxlint-disable-next-line no-restricted-imports\nimport _parseLinkHeader from 'parse-link-header';\nimport urlJoin from 'url-join';\nimport { logger } from '../logger/index.ts';\nimport { regEx } from './regex.ts';\n\nexport function joinUrlParts(...parts: string[]): string {\n return urlJoin(...parts);\n}\n\nexport function ensurePathPrefix(url: string, prefix: string): string {\n const parsed = new URL(url);\n const fullPath = parsed.pathname + parsed.search;\n if (fullPath.startsWith(prefix)) {\n return url;\n }\n return parsed.origin + prefix + fullPath;\n}\n\nexport function ensureTrailingSlash(url: string): string {\n return url.replace(/\\/?$/, '/'); // TODO #12875 adds slash at the front when re2 is used\n}\n\nexport function trimTrailingSlash(url: string): string {\n return url.replace(regEx(/\\/+$/), '');\n}\n\nexport function trimLeadingSlash(path: string): string {\n return path.replace(/^\\/+/, '');\n}\n\nexport function trimSlashes(path: string): string {\n return trimLeadingSlash(trimTrailingSlash(path));\n}\n\n/**\n * Resolves an input path against a base URL\n *\n * @param baseUrl - base URL to resolve against\n * @param input - input path (if this is a full URL, it will be returned)\n */\nexport function resolveBaseUrl(baseUrl: string, input: string | URL): string {\n const inputString = input.toString();\n\n let host;\n let pathname;\n try {\n ({ host, pathname } = new URL(inputString));\n } catch {\n pathname = inputString;\n }\n\n return host ? inputString : urlJoin(baseUrl, pathname || '');\n}\n\n/**\n * Replaces the path of a URL with a new path\n *\n * @param baseUrl - source URL\n * @param path - replacement path (if this is a full URL, it will be returned)\n */\nexport function replaceUrlPath(baseUrl: string | URL, path: string): string {\n if (parseUrl(path)) {\n return path;\n }\n\n const { origin } = isString(baseUrl) ? new URL(baseUrl) : baseUrl;\n return urlJoin(origin, path);\n}\n\n/**\n * Resolves a server-provided pagination \"next\" URL against the current request\n * URL, returning it only when it stays on the same origin.\n *\n * Registries paginate by returning a `Link` header (or Atom `<link rel=\"next\">`)\n * pointing at the next page.\n *\n * However, if we were to follow that URL without validating it, this could lead to us being redirected to a different host, which could lead to Server-Side Request Forgery (SSRF).\n *\n * This guard drops any `next` URL that resolves to a different origin.\n *\n * @param baseUrl - the URL of the request that produced `nextUrl`\n * @param nextUrl - the remote-server-provided pagination target (may be relative, and may be malicious)\n * @returns the resolved absolute URL if same-origin, otherwise `null`\n */\nexport function resolveSameOriginUrl(\n baseUrl: string | URL,\n nextUrl: string | URL,\n): string | null {\n const base = parseUrl(baseUrl);\n if (!base) {\n return null;\n }\n let resolved: URL;\n try {\n resolved = new URL(nextUrl.toString(), base);\n } catch {\n return null;\n }\n return resolved.origin === base.origin ? resolved.href : null;\n}\n\nexport function getQueryString(params: Record<string, any>): string {\n const usp = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (isArray<object>(v)) {\n for (const item of v) {\n // TODO: fix me?\n // oxlint-disable-next-line typescript/no-base-to-string\n usp.append(k, item.toString());\n }\n } else {\n usp.append(k, v.toString());\n }\n }\n return usp.toString();\n}\n\nexport function isHttpUrl(url: unknown): boolean {\n if (!isNonEmptyString(url) && !isUrlInstance(url)) {\n return false;\n }\n const protocol = parseUrl(url)?.protocol;\n return protocol === 'https:' || protocol === 'http:';\n}\n\nexport function parseUrl(url: URL | string | undefined | null): URL | null {\n if (!url) {\n return null;\n }\n\n if (url instanceof URL) {\n return url;\n }\n\n try {\n return new URL(url);\n } catch {\n return null;\n }\n}\n\n/**\n * Tries to create an URL object from either a full URL string or a hostname\n * @param url either the full url or a hostname\n * @returns an URL object or null\n */\nexport function createURLFromHostOrURL(url: string): URL | null {\n return parseUrl(url) ?? parseUrl(`https://${url}`);\n}\n\nexport type LinkHeaderLinks = _parseLinkHeader.Links;\n\nexport function parseLinkHeader(\n linkHeader: string | null | undefined,\n): LinkHeaderLinks | null {\n if (!isNonEmptyString(linkHeader)) {\n return null;\n }\n if (linkHeader.length > 2000) {\n logger.warn({ linkHeader }, 'Link header too long.');\n return null;\n }\n return _parseLinkHeader(linkHeader);\n}\n\n/**\n * prefix https:// to hosts with port or path\n */\nexport function massageHostUrl(url: string): string {\n if (!url.includes('://') && url.includes('/')) {\n return `https://${url}`;\n }\n if (!url.includes('://') && url.includes(':')) {\n return `https://${url}`;\n }\n return url;\n}\n"],"mappings":";;;;;;AAYA,SAAgB,aAAa,GAAG,OAAyB;CACvD,OAAO,QAAQ,GAAG,KAAK;AACzB;AAEA,SAAgB,iBAAiB,KAAa,QAAwB;CACpE,MAAM,SAAS,IAAI,IAAI,GAAG;CAC1B,MAAM,WAAW,OAAO,WAAW,OAAO;CAC1C,IAAI,SAAS,WAAW,MAAM,GAC5B,OAAO;CAET,OAAO,OAAO,SAAS,SAAS;AAClC;AAEA,SAAgB,oBAAoB,KAAqB;CACvD,OAAO,IAAI,QAAQ,QAAQ,GAAG;AAChC;AAEA,SAAgB,kBAAkB,KAAqB;CACrD,OAAO,IAAI,QAAQ,MAAM,MAAM,GAAG,EAAE;AACtC;AAEA,SAAgB,iBAAiB,MAAsB;CACrD,OAAO,KAAK,QAAQ,QAAQ,EAAE;AAChC;AAEA,SAAgB,YAAY,MAAsB;CAChD,OAAO,iBAAiB,kBAAkB,IAAI,CAAC;AACjD;;;;;;;AAQA,SAAgB,eAAe,SAAiB,OAA6B;CAC3E,MAAM,cAAc,MAAM,SAAS;CAEnC,IAAI;CACJ,IAAI;CACJ,IAAI;EACF,CAAC,CAAE,MAAM,YAAa,IAAI,IAAI,WAAW;CAC3C,QAAQ;EACN,WAAW;CACb;CAEA,OAAO,OAAO,cAAc,QAAQ,SAAS,YAAY,EAAE;AAC7D;;;;;;;AAQA,SAAgB,eAAe,SAAuB,MAAsB;CAC1E,IAAI,SAAS,IAAI,GACf,OAAO;CAGT,MAAM,EAAE,WAAW,SAAS,OAAO,IAAI,IAAI,IAAI,OAAO,IAAI;CAC1D,OAAO,QAAQ,QAAQ,IAAI;AAC7B;;;;;;;;;;;;;;;;AAiBA,SAAgB,qBACd,SACA,SACe;CACf,MAAM,OAAO,SAAS,OAAO;CAC7B,IAAI,CAAC,MACH,OAAO;CAET,IAAI;CACJ,IAAI;EACF,WAAW,IAAI,IAAI,QAAQ,SAAS,GAAG,IAAI;CAC7C,QAAQ;EACN,OAAO;CACT;CACA,OAAO,SAAS,WAAW,KAAK,SAAS,SAAS,OAAO;AAC3D;AAEA,SAAgB,eAAe,QAAqC;CAClE,MAAM,MAAM,IAAI,gBAAgB;CAChC,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,GACxC,IAAI,QAAgB,CAAC,GACnB,KAAK,MAAM,QAAQ,GAGjB,IAAI,OAAO,GAAG,KAAK,SAAS,CAAC;MAG/B,IAAI,OAAO,GAAG,EAAE,SAAS,CAAC;CAG9B,OAAO,IAAI,SAAS;AACtB;AAEA,SAAgB,UAAU,KAAuB;CAC/C,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC,cAAc,GAAG,GAC9C,OAAO;CAET,MAAM,WAAW,SAAS,GAAG,CAAC,EAAE;CAChC,OAAO,aAAa,YAAY,aAAa;AAC/C;AAEA,SAAgB,SAAS,KAAkD;CACzE,IAAI,CAAC,KACH,OAAO;CAGT,IAAI,eAAe,KACjB,OAAO;CAGT,IAAI;EACF,OAAO,IAAI,IAAI,GAAG;CACpB,QAAQ;EACN,OAAO;CACT;AACF;;;;;;AAOA,SAAgB,uBAAuB,KAAyB;CAC9D,OAAO,SAAS,GAAG,KAAK,SAAS,WAAW,KAAK;AACnD;AAIA,SAAgB,gBACd,YACwB;CACxB,IAAI,CAAC,iBAAiB,UAAU,GAC9B,OAAO;CAET,IAAI,WAAW,SAAS,KAAM;EAC5B,OAAO,KAAK,EAAE,WAAW,GAAG,uBAAuB;EACnD,OAAO;CACT;CACA,OAAO,iBAAiB,UAAU;AACpC;;;;AAKA,SAAgB,eAAe,KAAqB;CAClD,IAAI,CAAC,IAAI,SAAS,KAAK,KAAK,IAAI,SAAS,GAAG,GAC1C,OAAO,WAAW;CAEpB,IAAI,CAAC,IAAI,SAAS,KAAK,KAAK,IAAI,SAAS,GAAG,GAC1C,OAAO,WAAW;CAEpB,OAAO;AACT"}
|
package/package.json
CHANGED
package/renovate-schema.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$id": "https://docs.renovatebot.com/renovate-schema.json",
|
|
3
|
-
"title": "JSON schema for Renovate 44.11.
|
|
3
|
+
"title": "JSON schema for Renovate 44.11.3 config files (https://renovatebot.com/)",
|
|
4
4
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
5
|
-
"x-renovate-version": "44.11.
|
|
5
|
+
"x-renovate-version": "44.11.3",
|
|
6
6
|
"allowComments": true,
|
|
7
7
|
"type": "object",
|
|
8
8
|
"definitions": {
|