diff --git a/lib/modules/manager/circleci/__fixtures__/config2.yml b/lib/modules/manager/circleci/__fixtures__/config2.yml index 3f885f3e5c0a1596085b88a729b35ea2d15973ba..df2a528d8a22288ef151dbef0148dfd7902e5649 100644 --- a/lib/modules/manager/circleci/__fixtures__/config2.yml +++ b/lib/modules/manager/circleci/__fixtures__/config2.yml @@ -9,7 +9,7 @@ orbs: no-version: abc/def # Comments help me understand my work. - volatile: zzz/zzz@volatile # Comments help me understand my work. + volatile: "zzz/zzz@volatile" # Comments help me understand my work. test_plan: &test_plan steps: diff --git a/lib/modules/manager/circleci/__fixtures__/config3.yml b/lib/modules/manager/circleci/__fixtures__/config3.yml index 307ade4d7fcf144177b0e425c8e21c9e6c036ec6..5523b2c39ae3aefaf09b04534d96ebb8486afa0b 100644 --- a/lib/modules/manager/circleci/__fixtures__/config3.yml +++ b/lib/modules/manager/circleci/__fixtures__/config3.yml @@ -1,18 +1,16 @@ -aliases: +aliases: - &nodejs image: cimg/node:14.8.0 version: 2 jobs: - checkout: - <<: *defaults + checkout: docker: - *nodejs steps: - run: yarn build:runtime release_docker: - <<: *defaults machine: image: ubuntu-1604:201903-01 docker_layer_caching: true diff --git a/lib/modules/manager/circleci/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/circleci/__snapshots__/extract.spec.ts.snap index 77e4c1db6d89f86dbaf3f2e6d3da0ff0ca3f573d..99741c70d4979161d09d32456cc8b79361eb3b1c 100644 --- a/lib/modules/manager/circleci/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/circleci/__snapshots__/extract.spec.ts.snap @@ -10,7 +10,6 @@ exports[`modules/manager/circleci/extract extractPackageFile() extracts multiple "depName": "node", "depType": "docker", "replaceString": "node", - "versioning": "docker", }, { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", @@ -20,7 +19,6 @@ exports[`modules/manager/circleci/extract extractPackageFile() extracts multiple "depName": "node", "depType": "docker", "replaceString": "node:4", - "versioning": "docker", }, { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", @@ -30,7 +28,6 @@ exports[`modules/manager/circleci/extract extractPackageFile() extracts multiple "depName": "node", "depType": "docker", "replaceString": "node:6", - "versioning": "docker", }, { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", @@ -40,7 +37,6 @@ exports[`modules/manager/circleci/extract extractPackageFile() extracts multiple "depName": "node", "depType": "docker", "replaceString": "node:8.9.0", - "versioning": "docker", }, ] `; diff --git a/lib/modules/manager/circleci/extract.spec.ts b/lib/modules/manager/circleci/extract.spec.ts index 4222a22a5423ec54d339e96195ae18dd3c099fde..827f791248470e1909bddd8af0bc9a7118dcbaa9 100644 --- a/lib/modules/manager/circleci/extract.spec.ts +++ b/lib/modules/manager/circleci/extract.spec.ts @@ -51,6 +51,7 @@ describe('modules/manager/circleci/extract', () => { const res = extractPackageFile(file3); expect(res?.deps).toMatchObject([ { currentValue: '14.8.0', depName: 'cimg/node' }, + { currentValue: '14.8.0', depName: 'cimg/node' }, ]); }); diff --git a/lib/modules/manager/circleci/extract.ts b/lib/modules/manager/circleci/extract.ts index 9d971de87724c03e0045ae6f12b57e640c433721..4de5c6b58e0ea1bf96536af6204e114e73692ba9 100644 --- a/lib/modules/manager/circleci/extract.ts +++ b/lib/modules/manager/circleci/extract.ts @@ -1,9 +1,11 @@ import { logger } from '../../../logger'; -import { newlineRegex, regEx } from '../../../util/regex'; +import { coerceArray } from '../../../util/array'; +import { parseSingleYaml } from '../../../util/yaml'; import { OrbDatasource } from '../../datasource/orb'; import * as npmVersioning from '../../versioning/npm'; import { getDep } from '../dockerfile/extract'; import type { PackageDependency, PackageFileContent } from '../types'; +import { CircleCiFile } from './schema'; export function extractPackageFile( content: string, @@ -11,70 +13,38 @@ export function extractPackageFile( ): PackageFileContent | null { const deps: PackageDependency[] = []; try { - const lines = content.split(newlineRegex); - for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) { - const line = lines[lineNumber]; - const orbs = regEx(/^\s*orbs:\s*$/).exec(line); - if (orbs) { - logger.trace(`Matched orbs on line ${lineNumber}`); - let foundOrbOrNoop: boolean; - do { - foundOrbOrNoop = false; - const orbLine = lines[lineNumber + 1]; - logger.trace(`orbLine: "${orbLine}"`); - const yamlNoop = regEx(/^\s*(#|$)/).exec(orbLine); - if (yamlNoop) { - logger.debug('orbNoop'); - foundOrbOrNoop = true; - lineNumber += 1; - continue; - } - const orbMatch = regEx(/^\s+([^:]+):\s(.+?)(?:\s*#.*)?$/).exec( - orbLine, - ); - if (orbMatch) { - logger.trace('orbMatch'); - foundOrbOrNoop = true; - lineNumber += 1; - const depName = orbMatch[1]; - const [orbName, currentValue] = orbMatch[2].split('@'); - const dep: PackageDependency = { - depType: 'orb', - depName, - currentValue, - datasource: OrbDatasource.id, - packageName: orbName, - commitMessageTopic: '{{{depName}}} orb', - versioning: npmVersioning.id, - }; - deps.push(dep); - } - } while (foundOrbOrNoop); - } - const match = regEx(/^\s*-? image:\s*'?"?([^\s'"]+)'?"?\s*$/).exec(line); - if (match) { - const currentFrom = match[1]; - const dep = getDep(currentFrom); - logger.debug( - { - depName: dep.depName, - currentValue: dep.currentValue, - currentDigest: dep.currentDigest, - }, - 'CircleCI docker image', - ); - dep.depType = 'docker'; - dep.versioning = 'docker'; - if ( - !dep.depName?.startsWith('ubuntu-') && - !dep.depName?.startsWith('windows-server-') && - !dep.depName?.startsWith('android-') && - dep.depName !== 'android' - ) { - deps.push(dep); - } + const parsed = parseSingleYaml(content, { + customSchema: CircleCiFile, + }); + + for (const [key, orb] of Object.entries(parsed.orbs ?? {})) { + const [packageName, currentValue] = orb.split('@'); + + deps.push({ + depName: key, + packageName, + depType: 'orb', + currentValue, + versioning: npmVersioning.id, + datasource: OrbDatasource.id, + }); + } + + for (const job of Object.values(parsed.jobs)) { + for (const dockerElement of coerceArray(job.docker)) { + deps.push({ + ...getDep(dockerElement.image), + depType: 'docker', + }); } } + + for (const alias of coerceArray(parsed.aliases)) { + deps.push({ + ...getDep(alias.image), + depType: 'docker', + }); + } } catch (err) /* istanbul ignore next */ { logger.debug({ err, packageFile }, 'Error extracting circleci images'); } diff --git a/lib/modules/manager/circleci/schema.ts b/lib/modules/manager/circleci/schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..09c945bd117ee496ffbc62110720ae700ccef8b4 --- /dev/null +++ b/lib/modules/manager/circleci/schema.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; + +export const CircleCiDocker = z.object({ + image: z.string(), +}); + +export const CircleCiJob = z.object({ + docker: z.array(CircleCiDocker).optional(), +}); + +export const CircleCiFile = z.object({ + aliases: z.array(CircleCiDocker).optional(), + jobs: z.record(z.string(), CircleCiJob), + orbs: z.record(z.string()).optional(), +});