in appservice/src/deploy/waitForDeploymentToComplete.ts [18:105]
export async function waitForDeploymentToComplete(context: IActionContext & Partial<IDeployContext>, site: ParsedSite, expectedId?: string, token?: CancellationToken, pollingInterval: number = 5000): Promise<void> {
let fullLog: string = '';
let lastLogTime: Date = new Date(0);
let lastErrorLine: string = '';
let initialStartTime: Date | undefined;
let deployment: KuduModels.DeployResult | undefined;
let permanentId: string | undefined;
// a 60 second timeout period to let Kudu initialize the deployment
const maxTimeToWaitForExpectedId: number = Date.now() + 60 * 1000;
const kuduClient = await createKuduClient(context, site);
while (!token?.isCancellationRequested) {
[deployment, permanentId, initialStartTime] = await tryGetLatestDeployment(context, kuduClient, permanentId, initialStartTime, expectedId);
if ((deployment === undefined || !deployment.id)) {
if (expectedId && Date.now() < maxTimeToWaitForExpectedId) {
await delay(pollingInterval);
continue;
}
throw new Error(localize('failedToFindDeployment', 'Failed to get status of deployment.'));
}
const deploymentId: string = deployment.id;
let logEntries: KuduModels.LogEntry[] = [];
await retryKuduCall(context, 'getLogEntry', async () => {
await ignore404Error(context, async () => {
logEntries = (await kuduClient.deployment.getLogEntry(deploymentId)).reverse();
});
});
let lastLogTimeForThisPoll: Date | undefined;
// eslint-disable-next-line no-constant-condition
while (true) {
const newEntry: KuduModels.LogEntry | undefined = logEntries.pop();
if (!newEntry) {
break;
}
if (newEntry.message && newEntry.logTime && newEntry.logTime > lastLogTime) {
fullLog = fullLog.concat(newEntry.message);
ext.outputChannel.appendLog(newEntry.message, { date: newEntry.logTime, resourceName: site.fullName });
lastLogTimeForThisPoll = newEntry.logTime;
if (/error/i.test(newEntry.message)) {
lastErrorLine = newEntry.message;
}
}
await retryKuduCall(context, 'getLogEntryDetails', async () => {
await ignore404Error(context, async () => {
if (newEntry.id && newEntry.detailsUrl) {
const details: KuduModels.LogEntry[] = await kuduClient.deployment.getLogEntryDetails(deploymentId, newEntry.id);
logEntries.push(...cleanDetails(details));
}
});
});
}
if (lastLogTimeForThisPoll) {
lastLogTime = lastLogTimeForThisPoll;
}
if (deployment.complete) {
if (deployment.status === 3 /* Failed */ || deployment.isTemp) { // If the deployment completed without making it to the "permanent" phase, it must have failed
const message: string = localize('deploymentFailed', 'Deployment to "{0}" failed.', site.fullName);
const viewOutput: string = localize('viewOutput', 'View Output');
// don't wait
void window.showErrorMessage(message, viewOutput).then(result => {
if (result === viewOutput) {
ext.outputChannel.show();
}
});
const messageWithoutName: string = localize('deploymentFailedWithoutName', 'Deployment failed.');
ext.outputChannel.appendLog(messageWithoutName, { resourceName: site.fullName });
context.errorHandling.suppressDisplay = true;
// Hopefully the last line is enough to give us an idea why deployments are failing without excessively tracking everything
context.telemetry.properties.deployErrorLastLine = lastErrorLine;
throw new Error(messageWithoutName);
} else {
context.syncTriggersPostDeploy = site.isFunctionApp && !/syncing/i.test(fullLog) && !site.isKubernetesApp && !site.isWorkflowApp;
return;
}
} else {
await delay(pollingInterval);
}
}
}