Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(dev-env): verify the container is running before URL scan #2061

Merged
merged 2 commits into from
Nov 4, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 48 additions & 35 deletions src/lib/dev-environment/dev-environment-lando.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import chalk from 'chalk';
import debugLib from 'debug';
import App, { type ScanResult } from 'lando/lib/app';
import { buildConfig } from 'lando/lib/bootstrap';
import Lando, { LandoConfig } from 'lando/lib/lando';
import landoUtils, { AppInfo } from 'lando/plugins/lando-core/lib/utils';
import Lando, { type LandoConfig } from 'lando/lib/lando';
import landoUtils, { type AppInfo } from 'lando/plugins/lando-core/lib/utils';
import landoBuildTask from 'lando/plugins/lando-tooling/lib/build';
import { lookup } from 'node:dns/promises';
import { FileHandle, mkdir, rename } from 'node:fs/promises';
Expand All @@ -23,6 +23,7 @@ import { DEV_ENVIRONMENT_NOT_FOUND } from '../constants/dev-environment';
import UserError from '../user-error';

import type { NetworkInspectInfo } from 'dockerode';
import type Landerode from 'lando/lib/docker';

export interface LandoExecOptions {
stdio?: string | [ FileHandle, string, string ];
Expand Down Expand Up @@ -374,7 +375,7 @@ export async function landoInfo(
const reachableServices = app.info.filter( service => service.urls.length );
reachableServices.forEach( service => ( info[ `${ service.service } urls` ] = service.urls ) );

const health = await checkEnvHealth( lando, instancePath );
const health = await checkEnvHealth( lando, app );
const frontEndUrl = app.info.find( service => 'nginx' === service.service )?.urls[ 0 ] ?? '';

const extraService = await getExtraServicesConnections( lando, app );
Expand Down Expand Up @@ -545,37 +546,65 @@ async function tryResolveDomains( urls: string[] ): Promise< void > {
}
}

async function getRunningServicesForProject(
docker: Landerode,
project: string
): Promise< string[] > {
const containers = await docker.listContainers( {
filters: {
label: [ `com.docker.compose.project=${ project }` ],
},
} );

return containers
.filter( container => container.State === 'running' )
.map( container => container.Labels[ 'com.docker.compose.service' ] );
}

export async function checkEnvHealth(
lando: Lando,
instancePath: string
app: App
): Promise< Record< string, boolean > > {
const urls: Record< string, string > = {};

const now = new Date();

const app = await getLandoApplication( lando, instancePath );
app.urls ??= [];
app.info
.filter( service => service.urls.length )
.forEach( service => {
service.urls.forEach( url => {
urls[ url ] = service.service;
if ( ! /^https?:\/\/(localhost|127\.0\.0\.1):/.exec( url ) ) {
urls[ url ] = service.service;
}
} );
} );

const urlsToScan = Object.keys( urls ).filter( url => ! url.includes( '*' ) );
await tryResolveDomains( urlsToScan );
let scanResults: ScanResult[] = [];
if ( Array.isArray( app.urls ) ) {
scanResults = app.urls;
app.urls.forEach( entry => {
// We use different status codes to see if the service is up.
// We may consider the service is up when Lando considers it is down.
if ( entry.color !== 'red' ) {
urlsToScan.splice( urlsToScan.indexOf( entry.url ), 1 );
}
} );
}
app.urls.forEach( entry => {
// We use different status codes to see if the service is up.
// We may consider the service is up when Lando considers it is down.
if ( entry.color !== 'red' ) {
urlsToScan.splice( urlsToScan.indexOf( entry.url ), 1 );
}
} );

const runningServices = await getRunningServicesForProject( lando.engine.docker, app.project );
Object.entries( urls ).forEach( ( [ url, service ] ) => {
if ( ! runningServices.includes( service ) ) {
( app.urls as ScanResult[] ).push( {
url,
color: 'red',
status: false,
} );

debug( 'Service %s is not running, removing %s from the scan queue', service, url );
urlsToScan.splice( urlsToScan.indexOf( url ), 1 );
}
} );

let scanResults = app.urls;
if ( urlsToScan.length ) {
scanResults = scanResults.concat(
await app.scanUrls( urlsToScan, { max: 1, waitCodes: [ 502, 504 ] } )
Expand All @@ -594,25 +623,9 @@ export async function checkEnvHealth(
}

export async function isEnvUp( lando: Lando, instancePath: string ): Promise< boolean > {
const now = new Date();
const app = await getLandoApplication( lando, instancePath );

const reachableServices = app.info.filter( service => service.urls.length );
const webUrls = reachableServices
.map( service => service.urls )
.flat()
.filter( url => ! /^https?:\/\/(localhost|127\.0\.0\.1):/.exec( url ) );

await tryResolveDomains( webUrls );
const scanResult = await app.scanUrls( webUrls, { max: 1, waitCodes: [ 502, 504 ] } );
const duration = new Date().getTime() - now.getTime();
debug( 'isEnvUp took %d ms', duration );

// If all the URLs are reachable then the app is considered 'up'
return (
scanResult.length > 0 &&
scanResult.filter( result => result.status ).length === scanResult.length
);
const healthResults = await checkEnvHealth( lando, app );
return Object.keys( healthResults ).length > 0 && Object.values( healthResults ).every( Boolean );
}

export async function landoExec(
Expand Down