renovate/lib/modules/manager/azure-pipelines/extract.ts

220 lines
6.0 KiB
TypeScript

import { GlobalConfig } from '../../../config/global';
import { logger } from '../../../logger';
import { coerceArray } from '../../../util/array';
import { regEx } from '../../../util/regex';
import { joinUrlParts } from '../../../util/url';
import { AzurePipelinesTasksDatasource } from '../../datasource/azure-pipelines-tasks';
import { GitTagsDatasource } from '../../datasource/git-tags';
import { getDep } from '../dockerfile/extract';
import type {
ExtractConfig,
PackageDependency,
PackageFileContent,
} from '../types';
import type {
AzurePipelines,
Container,
Deploy,
Deployment,
Job,
Jobs,
Repository,
Step,
} from './schema';
import { AzurePipelinesYaml } from './schema';
const AzurePipelinesTaskRegex = regEx(/^(?<name>[^@]+)@(?<version>.*)$/);
export function extractRepository(
repository: Repository,
currentRepository?: string,
): PackageDependency | null {
let repositoryUrl = null;
let depName = repository.name;
if (repository.type === 'github') {
repositoryUrl = `https://github.com/${repository.name}.git`;
} else if (repository.type === 'git') {
const platform = GlobalConfig.get('platform');
const endpoint = GlobalConfig.get('endpoint');
if (platform === 'azure' && endpoint) {
// extract the project name if the repository from which the pipline is referencing templates contains the Azure DevOps project name
if (repository.name.includes('/')) {
const [projectName, repoName] = repository.name.split('/');
repositoryUrl = joinUrlParts(
endpoint,
encodeURIComponent(projectName),
'_git',
encodeURIComponent(repoName),
);
// if the repository from which the pipline is referencing templates does not contain the Azure DevOps project name, get the project name from the repository containing the pipeline file being process
} else if (currentRepository?.includes('/')) {
const projectName = currentRepository.split('/')[0];
depName = `${projectName}/${repository.name}`;
repositoryUrl = joinUrlParts(
endpoint,
encodeURIComponent(projectName),
'_git',
encodeURIComponent(repository.name),
);
} else {
logger.debug(
'Renovate cannot update Azure pipelines in git repositories when neither the current repository nor the target repository contains the Azure DevOps project name.',
);
}
}
}
if (repositoryUrl === null) {
return null;
}
if (!repository.ref?.startsWith('refs/tags/')) {
return null;
}
return {
autoReplaceStringTemplate: 'refs/tags/{{newValue}}',
currentValue: repository.ref.replace('refs/tags/', ''),
datasource: GitTagsDatasource.id,
depName,
depType: 'gitTags',
packageName: repositoryUrl,
replaceString: repository.ref,
};
}
export function extractContainer(
container: Container,
): PackageDependency | null {
const dep = getDep(container.image);
logger.debug(
{
depName: dep.depName,
currentValue: dep.currentValue,
currentDigest: dep.currentDigest,
},
'Azure pipelines docker image',
);
dep.depType = 'docker';
return dep;
}
export function extractAzurePipelinesTasks(
task: string,
): PackageDependency | null {
const match = AzurePipelinesTaskRegex.exec(task);
if (match?.groups) {
return {
depName: match.groups.name,
currentValue: match.groups.version,
datasource: AzurePipelinesTasksDatasource.id,
};
}
return null;
}
export function parseAzurePipelines(
content: string,
packageFile: string,
): AzurePipelines | null {
const res = AzurePipelinesYaml.safeParse(content);
if (res.success) {
return res.data;
} else {
logger.debug(
{ err: res.error, packageFile },
'Error parsing pubspec lockfile.',
);
}
return null;
}
function extractSteps(
steps: Step[] | undefined,
): PackageDependency<Record<string, any>>[] {
const deps = [];
for (const step of coerceArray(steps)) {
const task = extractAzurePipelinesTasks(step.task);
if (task) {
deps.push(task);
}
}
return deps;
}
function extractJob(job: Job | undefined): PackageDependency[] {
return extractSteps(job?.steps);
}
function extractDeploy(deploy: Deploy | undefined): PackageDependency[] {
const deps = extractJob(deploy?.deploy);
deps.push(...extractJob(deploy?.postRouteTraffic));
deps.push(...extractJob(deploy?.preDeploy));
deps.push(...extractJob(deploy?.routeTraffic));
deps.push(...extractJob(deploy?.on?.failure));
deps.push(...extractJob(deploy?.on?.success));
return deps;
}
function extractJobs(jobs: Jobs | undefined): PackageDependency[] {
const deps: PackageDependency[] = [];
for (const jobOrDeployment of coerceArray(jobs)) {
const deployment = jobOrDeployment as Deployment;
if (deployment.strategy) {
deps.push(...extractDeploy(deployment.strategy.canary));
deps.push(...extractDeploy(deployment.strategy.rolling));
deps.push(...extractDeploy(deployment.strategy.runOnce));
continue;
}
const job = jobOrDeployment as Job;
deps.push(...extractJob(job));
}
return deps;
}
export function extractPackageFile(
content: string,
packageFile: string,
config: ExtractConfig,
): PackageFileContent | null {
logger.trace(`azurePipelines.extractPackageFile(${packageFile})`);
const deps: PackageDependency[] = [];
const pkg = parseAzurePipelines(content, packageFile);
if (!pkg) {
return null;
}
for (const repository of coerceArray(pkg.resources?.repositories)) {
const dep = extractRepository(repository, config.repository);
if (dep) {
deps.push(dep);
}
}
for (const container of coerceArray(pkg.resources?.containers)) {
const dep = extractContainer(container);
if (dep) {
deps.push(dep);
}
}
for (const { jobs } of coerceArray(pkg.stages)) {
deps.push(...extractJobs(jobs));
}
deps.push(...extractJobs(pkg.jobs));
deps.push(...extractSteps(pkg.steps));
if (!deps.length) {
return null;
}
return { deps };
}