mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-31 01:37:24 +00:00
cca6227dfa
* fix(git): preserve approved runtime config with authentication * test(git): remove redundant authentication cases * test(git): update post-upgrade environment assertions * test(hermit): cover uninstall setup failure * refactor(git): clarify authentication host types * refactor(git): coerce exec options * refactor(git): coerce docker env vars * refactor(exec): coerce docker env vars
304 lines
7.8 KiB
TypeScript
304 lines
7.8 KiB
TypeScript
import { quote } from 'shlex';
|
|
import upath from 'upath';
|
|
import { logger } from '../../../logger/index.ts';
|
|
import { ExecError } from '../../../util/exec/exec-error.ts';
|
|
import type { ExecOptions } from '../../../util/exec/types.ts';
|
|
import {
|
|
localPathIsSymbolicLink,
|
|
readLocalSymlink,
|
|
} from '../../../util/fs/index.ts';
|
|
import { withGitEnvironment } from '../../../util/git/exec.ts';
|
|
import { getRepoStatus } from '../../../util/git/index.ts';
|
|
import * as p from '../../../util/promises.ts';
|
|
import type { UpdateArtifact, UpdateArtifactsResult } from '../types.ts';
|
|
import type { ReadContentResult } from './types.ts';
|
|
|
|
const gitExec = withGitEnvironment(['hermit']);
|
|
|
|
/**
|
|
* updateArtifacts runs hermit install for each updated dependencies
|
|
*/
|
|
export async function updateArtifacts(
|
|
update: UpdateArtifact,
|
|
): Promise<UpdateArtifactsResult[] | null> {
|
|
const { packageFileName } = update;
|
|
try {
|
|
await updateHermitPackage(update);
|
|
} catch (err) {
|
|
logger.debug({ err }, `error updating hermit packages.`);
|
|
if (err instanceof UpdateHermitError) {
|
|
return [
|
|
{
|
|
artifactError: {
|
|
fileName: `from: ${err.from}, to: ${err.to}`,
|
|
stderr: err.stderr,
|
|
},
|
|
},
|
|
];
|
|
}
|
|
return [
|
|
{
|
|
artifactError: {
|
|
stderr: err instanceof Error ? err.message : String(err),
|
|
},
|
|
},
|
|
];
|
|
}
|
|
|
|
logger.debug(`scanning the changes after update`);
|
|
|
|
let updateResult: UpdateArtifactsResult[] | null = null;
|
|
|
|
try {
|
|
updateResult = await getUpdateResult(packageFileName);
|
|
logger.debug({ updateResult }, `update result for hermit`);
|
|
} catch (err) {
|
|
logger.debug({ err }, 'Error getting hermet update results');
|
|
return [
|
|
{
|
|
artifactError: {
|
|
stderr: err.message,
|
|
},
|
|
},
|
|
];
|
|
}
|
|
|
|
return updateResult;
|
|
}
|
|
|
|
/**
|
|
* getContent returns the content of either link or a normal file
|
|
*/
|
|
async function getContent(file: string): Promise<ReadContentResult> {
|
|
let contents: string | null = '';
|
|
const isSymlink = await localPathIsSymbolicLink(file);
|
|
|
|
if (isSymlink) {
|
|
contents = await readLocalSymlink(file);
|
|
}
|
|
|
|
if (contents === null) {
|
|
throw new Error(`error getting content for ${file}`);
|
|
}
|
|
|
|
return {
|
|
isSymlink,
|
|
contents,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* getAddResult returns the UpdateArtifactsResult for the added files
|
|
*/
|
|
function getAddResult(
|
|
path: string,
|
|
contentRes: ReadContentResult,
|
|
): UpdateArtifactsResult {
|
|
return {
|
|
file: {
|
|
type: 'addition',
|
|
path,
|
|
contents: contentRes.contents,
|
|
isSymlink: contentRes.isSymlink,
|
|
isExecutable: contentRes.isExecutable,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* getDeleteResult returns the UpdateArtifactsResult for deleted files
|
|
*/
|
|
function getDeleteResult(path: string): UpdateArtifactsResult {
|
|
return {
|
|
file: {
|
|
type: 'deletion',
|
|
path,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* getUpdateResult will return the update result after `hermit install`
|
|
* has been performed for all packages
|
|
*/
|
|
async function getUpdateResult(
|
|
packageFileName: string,
|
|
): Promise<UpdateArtifactsResult[]> {
|
|
const hermitFolder = `${upath.dirname(packageFileName)}/`;
|
|
const hermitChanges = await getRepoStatus(hermitFolder);
|
|
logger.debug(
|
|
{ hermitChanges, hermitFolder },
|
|
`hermit changes after package update`,
|
|
);
|
|
|
|
// handle added files
|
|
const added = await p.map(
|
|
[...hermitChanges.created, ...hermitChanges.not_added],
|
|
async (path: string): Promise<UpdateArtifactsResult> => {
|
|
const contents = await getContent(path);
|
|
|
|
return getAddResult(path, contents);
|
|
},
|
|
);
|
|
|
|
const deleted = hermitChanges.deleted.map(getDeleteResult);
|
|
|
|
const modified = await p.map(
|
|
hermitChanges.modified,
|
|
async (path: string): Promise<UpdateArtifactsResult[]> => {
|
|
const contents = await getContent(path);
|
|
return [
|
|
getDeleteResult(path), // delete existing link
|
|
getAddResult(path, contents), // add a new link
|
|
];
|
|
},
|
|
);
|
|
|
|
const renamed = await p.map(
|
|
hermitChanges.renamed,
|
|
async (renamed): Promise<UpdateArtifactsResult[]> => {
|
|
const from = renamed.from;
|
|
const to = renamed.to;
|
|
const toContents = await getContent(to);
|
|
|
|
return [getDeleteResult(from), getAddResult(to, toContents)];
|
|
},
|
|
);
|
|
|
|
return [
|
|
// rename will need to go first, because
|
|
// it needs to create the new link for the new version
|
|
// for the modified links to use
|
|
...renamed.flat(),
|
|
...modified.flat(),
|
|
...added,
|
|
...deleted,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* getHermitPackage returns the hermit package for running the hermit install
|
|
*/
|
|
function getHermitPackage(name: string, version: string): string {
|
|
return `${name}-${version}`;
|
|
}
|
|
|
|
/**
|
|
* updateHermitPackage runs hermit install for the given package
|
|
*/
|
|
async function updateHermitPackage(update: UpdateArtifact): Promise<void> {
|
|
logger.trace({ update }, `hermit.updateHermitPackage()`);
|
|
|
|
const toInstall = [];
|
|
const from = [];
|
|
// storing the old package for replacement
|
|
const toUninstall = [];
|
|
|
|
for (const pkg of update.updatedDeps) {
|
|
if (!pkg.depName || !pkg.currentVersion || !pkg.newValue) {
|
|
logger.debug(
|
|
{
|
|
depName: pkg.depName,
|
|
currentVersion: pkg.currentVersion,
|
|
newValue: pkg.newValue,
|
|
},
|
|
'missing package update information',
|
|
);
|
|
|
|
throw new UpdateHermitError(
|
|
getHermitPackage(pkg.depName ?? '', pkg.currentVersion ?? ''),
|
|
getHermitPackage(pkg.depName ?? '', pkg.newValue ?? ''),
|
|
'invalid package to update',
|
|
);
|
|
}
|
|
|
|
const depName = pkg.depName;
|
|
const newName = pkg.newName;
|
|
const currentVersion = pkg.currentVersion;
|
|
const newValue = pkg.newValue;
|
|
const fromPackage = getHermitPackage(depName, currentVersion);
|
|
// newName will be available for replacement
|
|
const toPackage = getHermitPackage(newName ?? depName, newValue);
|
|
toInstall.push(toPackage);
|
|
from.push(fromPackage);
|
|
// skips uninstall for version only replacement
|
|
if (pkg.updateType === 'replacement' && newName !== depName) {
|
|
toUninstall.push(depName);
|
|
}
|
|
}
|
|
|
|
const execOptions: ExecOptions = {
|
|
docker: {},
|
|
cwdFile: update.packageFileName,
|
|
};
|
|
|
|
const fromPackages = from.map(quote).join(' ');
|
|
|
|
// when a name replacement happens, need to uninstall the old package
|
|
if (toUninstall.length > 0) {
|
|
const packagesToUninstall = toUninstall.map(quote).join(' ');
|
|
const uninstallCommands = `./hermit uninstall ${packagesToUninstall}`;
|
|
|
|
try {
|
|
const result = await gitExec(uninstallCommands, execOptions);
|
|
logger.trace(
|
|
{ stdout: result.stdout },
|
|
`hermit uninstall command stdout`,
|
|
);
|
|
} catch (e) {
|
|
logger.warn({ err: e }, `error uninstall hermit package for replacement`);
|
|
if (!(e instanceof ExecError)) {
|
|
throw e;
|
|
}
|
|
throw new UpdateHermitError(
|
|
fromPackages,
|
|
packagesToUninstall,
|
|
e.stderr,
|
|
e.stdout,
|
|
);
|
|
}
|
|
}
|
|
|
|
const packagesToInstall = toInstall.map(quote).join(' ');
|
|
|
|
const execCommands = `./hermit install ${packagesToInstall}`;
|
|
logger.debug(
|
|
{
|
|
packageFile: update.packageFileName,
|
|
packagesToInstall,
|
|
},
|
|
`performing updates`,
|
|
);
|
|
|
|
try {
|
|
const result = await gitExec(execCommands, execOptions);
|
|
logger.trace({ stdout: result.stdout }, `hermit command stdout`);
|
|
} catch (e) {
|
|
logger.warn({ err: e }, `error updating hermit package`);
|
|
if (!(e instanceof ExecError)) {
|
|
throw e;
|
|
}
|
|
throw new UpdateHermitError(
|
|
fromPackages,
|
|
packagesToInstall,
|
|
e.stderr,
|
|
e.stdout,
|
|
);
|
|
}
|
|
}
|
|
|
|
export class UpdateHermitError extends Error {
|
|
stdout: string;
|
|
stderr: string;
|
|
from: string;
|
|
to: string;
|
|
|
|
constructor(from: string, to: string, stderr: string, stdout = '') {
|
|
super();
|
|
this.stdout = stdout;
|
|
this.stderr = stderr;
|
|
this.from = from;
|
|
this.to = to;
|
|
}
|
|
}
|