Skip to content

Commit 8c00b15

Browse files
authored
refactor(circleci): Simplify CircleCI extraction (#35233)
1 parent d4da4a1 commit 8c00b15

File tree

3 files changed

+101
-90
lines changed

3 files changed

+101
-90
lines changed

lib/modules/manager/circleci/extract.spec.ts

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,20 @@ describe('modules/manager/circleci/extract', () => {
1414
});
1515

1616
it('handles registry alias', () => {
17-
const res = extractPackageFile(
18-
'executors:\n my-executor:\n docker:\n - image: quay.io/myName/myPackage:0.6.2',
19-
'',
20-
{
21-
registryAliases: {
22-
'quay.io': 'my-quay-mirror.registry.com',
23-
'index.docker.io': 'my-docker-mirror.registry.com',
24-
},
17+
const src = codeBlock`
18+
executors:
19+
my-executor:
20+
docker:
21+
- image: quay.io/myName/myPackage:0.6.2
22+
`;
23+
24+
const res = extractPackageFile(src, '.circleci/config.yml', {
25+
registryAliases: {
26+
'quay.io': 'my-quay-mirror.registry.com',
27+
'index.docker.io': 'my-docker-mirror.registry.com',
2528
},
26-
);
29+
});
30+
2731
expect(res).toEqual({
2832
deps: [
2933
{
@@ -222,10 +226,10 @@ describe('modules/manager/circleci/extract', () => {
222226
it('extracts and exclude android images', () => {
223227
expect(
224228
extractPackageFile(codeBlock`
225-
jobs:
226-
build:
227-
machine:
228-
image: android:202102-01
229+
jobs:
230+
build:
231+
machine:
232+
image: android:202102-01
229233
`),
230234
).toBeNull();
231235
});
@@ -246,10 +250,12 @@ describe('modules/manager/circleci/extract', () => {
246250

247251
it('extracts executors', () => {
248252
const res = extractPackageFile(codeBlock`
249-
executors:
250-
my-executor:
251-
docker:
252-
- image: cimg/ruby:3.0.3-browsers`);
253+
executors:
254+
my-executor:
255+
docker:
256+
- image: cimg/ruby:3.0.3-browsers
257+
`);
258+
253259
expect(res?.deps).toEqual([
254260
{
255261
autoReplaceStringTemplate:
@@ -266,29 +272,30 @@ describe('modules/manager/circleci/extract', () => {
266272

267273
it('extracts orb definitions', () => {
268274
const res = extractPackageFile(codeBlock`
269-
version: 2.1
275+
version: 2.1
270276
271-
orbs:
272-
myorb:
273-
orbs:
274-
python: circleci/[email protected]
277+
orbs:
278+
myorb:
279+
orbs:
280+
python: circleci/[email protected]
275281
276-
executors:
277-
python:
278-
docker:
279-
- image: cimg/python:3.9
282+
executors:
283+
python:
284+
docker:
285+
- image: cimg/python:3.9
280286
281-
jobs:
282-
test_image:
283-
docker:
284-
- image: cimg/python:3.7
285-
steps:
286-
- checkout
287+
jobs:
288+
test_image:
289+
docker:
290+
- image: cimg/python:3.7
291+
steps:
292+
- checkout
287293
288-
workflows:
289-
Test:
290-
jobs:
291-
- myorb/test_image`);
294+
workflows:
295+
Test:
296+
jobs:
297+
- myorb/test_image
298+
`);
292299

293300
expect(res).toEqual({
294301
deps: [
Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { logger } from '../../../logger';
2-
import { coerceArray } from '../../../util/array';
2+
import { Result } from '../../../util/result';
33
import { parseSingleYaml } from '../../../util/yaml';
44
import { OrbDatasource } from '../../datasource/orb';
55
import * as npmVersioning from '../../versioning/npm';
@@ -9,15 +9,14 @@ import type {
99
PackageDependency,
1010
PackageFileContent,
1111
} from '../types';
12-
import { CircleCiFile, type CircleCiJob, type CircleCiOrb } from './schema';
12+
import { CircleCiFile, type CircleCiOrb } from './schema';
1313

1414
function extractDefinition(
15+
deps: PackageDependency[],
1516
definition: CircleCiOrb | CircleCiFile,
16-
config?: ExtractConfig,
17-
): PackageDependency[] {
18-
const deps: PackageDependency[] = [];
19-
20-
for (const [key, orb] of Object.entries(definition.orbs ?? {})) {
17+
registryAliases: Record<string, string>,
18+
): void {
19+
for (const [key, orb] of Object.entries(definition.orbs)) {
2120
if (typeof orb === 'string') {
2221
const [packageName, currentValue] = orb.split('@');
2322

@@ -30,49 +29,48 @@ function extractDefinition(
3029
datasource: OrbDatasource.id,
3130
});
3231
} else {
33-
deps.push(...extractDefinition(orb, config));
32+
extractDefinition(deps, orb, registryAliases);
3433
}
3534
}
3635

3736
// extract environments
38-
const environments: CircleCiJob[] = [
39-
Object.values(definition.executors ?? {}),
40-
Object.values(definition.jobs ?? {}),
41-
].flat();
42-
for (const job of environments) {
43-
for (const dockerElement of coerceArray(job.docker)) {
44-
deps.push({
45-
...getDep(dockerElement.image, true, config?.registryAliases),
46-
depType: 'docker',
47-
});
48-
}
37+
const environments = [...definition.executors, ...definition.jobs];
38+
for (const dockerImage of environments) {
39+
deps.push({
40+
...getDep(dockerImage, true, registryAliases),
41+
depType: 'docker',
42+
});
4943
}
50-
51-
return deps;
5244
}
5345

5446
export function extractPackageFile(
5547
content: string,
5648
packageFile?: string,
5749
config?: ExtractConfig,
5850
): PackageFileContent | null {
59-
const deps: PackageDependency[] = [];
60-
try {
61-
const parsed = CircleCiFile.parse(parseSingleYaml(content));
51+
const { val: parsed, err } = Result.wrap(() =>
52+
CircleCiFile.parse(parseSingleYaml(content)),
53+
).unwrap();
6254

63-
deps.push(...extractDefinition(parsed, config));
64-
65-
for (const alias of parsed.aliases) {
66-
deps.push({
67-
...getDep(alias.image, true, config?.registryAliases),
68-
depType: 'docker',
69-
});
70-
}
71-
} catch (err) /* istanbul ignore next */ {
55+
if (err) {
7256
logger.debug({ err, packageFile }, 'Error extracting circleci images');
57+
return null;
7358
}
59+
60+
const registryAliases = config?.registryAliases ?? {};
61+
const deps: PackageDependency[] = [];
62+
extractDefinition(deps, parsed, registryAliases);
63+
64+
for (const alias of parsed.aliases) {
65+
deps.push({
66+
...getDep(alias, true, registryAliases),
67+
depType: 'docker',
68+
});
69+
}
70+
7471
if (!deps.length) {
7572
return null;
7673
}
74+
7775
return { deps };
7876
}
Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,43 @@
11
import { z } from 'zod';
2-
import { LooseArray, NotCircular } from '../../../util/schema-utils';
2+
import {
3+
LooseArray,
4+
LooseRecord,
5+
NotCircular,
6+
} from '../../../util/schema-utils';
37

4-
export const CircleCiDocker = z.object({
5-
image: z.string(),
6-
});
8+
export const CircleCiDocker = z
9+
.object({ image: z.string() })
10+
.transform(({ image }) => image);
711

8-
export const CircleCiJob = z.object({
9-
docker: z.array(CircleCiDocker).optional(),
10-
});
12+
export const CircleCiJob = z
13+
.object({
14+
docker: LooseArray(CircleCiDocker).catch([]),
15+
})
16+
.transform(({ docker }) => docker);
1117
export type CircleCiJob = z.infer<typeof CircleCiJob>;
1218

13-
const baseOrb = z.object({
14-
executors: z.record(z.string(), CircleCiJob).optional(),
15-
jobs: z.record(z.string(), CircleCiJob).optional(),
19+
const CircleCiJobList = LooseRecord(CircleCiJob)
20+
.transform((x) => Object.values(x).flat())
21+
.catch([]);
22+
23+
const BaseOrb = z.object({
24+
executors: CircleCiJobList,
25+
jobs: CircleCiJobList,
1626
});
1727

18-
type Orb = z.infer<typeof baseOrb> & {
19-
orbs?: Record<string, string | Orb>;
28+
type Orb = z.infer<typeof BaseOrb> & {
29+
orbs: Record<string, string | Orb>;
2030
};
2131

22-
export const CircleCiOrb: z.ZodType<Orb> = baseOrb.extend({
23-
orbs: z.lazy(() =>
24-
z.record(z.string(), z.union([z.string(), CircleCiOrb])).optional(),
25-
),
26-
});
32+
export const CircleCiOrb: z.ZodType<Orb> = BaseOrb.extend({
33+
orbs: LooseRecord(z.union([z.string(), z.lazy(() => CircleCiOrb)])).catch({}),
34+
}) as never;
2735
export type CircleCiOrb = z.infer<typeof CircleCiOrb>;
2836

2937
export const CircleCiFile = NotCircular.pipe(
30-
z.object({
38+
BaseOrb.extend({
3139
aliases: LooseArray(CircleCiDocker).catch([]),
32-
executors: z.record(z.string(), CircleCiJob).optional(),
33-
jobs: z.record(z.string(), CircleCiJob).optional(),
34-
orbs: z.record(z.string(), z.union([z.string(), CircleCiOrb])).optional(),
40+
orbs: LooseRecord(z.union([z.string(), CircleCiOrb])).catch({}),
3541
}),
3642
);
3743
export type CircleCiFile = z.infer<typeof CircleCiFile>;

0 commit comments

Comments
 (0)