export async function waitForDeploymentToComplete()

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);
        }
    }
}