diff --git a/README.md b/README.md index 6ec93f9b..d82fa750 100644 --- a/README.md +++ b/README.md @@ -33,26 +33,26 @@ The Hedera Local Node project allows developers to set up their own local networ - Ensure the **VirtioFS** file sharing implementation is enabled in the docker settings **Note**: The image may look different if you are on a different version -![docker-compose-settings.png](.github/docker-compose-settings.png) +![docker-compose-settings.png](https://raw.githubusercontent.com/hashgraph/hedera-local-node/refs/heads/main/.github/docker-compose-settings.png) - Ensure the following configurations are set at minimum in Docker **Settings -> Resources** and are available for use. - **CPUs:** 6 - - **Memory:** 8GB + - **Memory:** 8 GB - **Swap:** 1 GB - **Disk Image Size:** 64 GB **Note**: The image may look different if you are on a different version -![settings.png](.github/settings.png) +![settings.png](https://raw.githubusercontent.com/hashgraph/hedera-local-node/refs/heads/main/.github/settings.png) - Ensure the hedera-local-node folder is added to Docker File Sharing **Settings -> Resources -> File Sharing**. **Note**: The image may look different if you are on a different version -![docker-file-sharing-settings.png](.github/docker-file-sharing-settings.png) +![docker-file-sharing-settings.png](https://raw.githubusercontent.com/hashgraph/hedera-local-node/refs/heads/main/.github/docker-file-sharing-settings.png) - Ensure the *Allow the default Docker sockets to be used (requires password)* is enabled in Docker **Settings -> Advanced**. **Note**: The image may look different if you are on a different version -![docker-socket-setting](.github/docker-socket-settings.png) +![docker-socket-setting.png](https://raw.githubusercontent.com/hashgraph/hedera-local-node/refs/heads/main/.github/docker-socket-settings.png) # CLI Tool - @hashgraph/hedera-local diff --git a/src/constants.ts b/src/constants.ts index 24305b66..a2512dcc 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -101,7 +101,8 @@ export const STOP_STATE_STOPPED_MESSAGE = `${CHECK_SUCCESS} Hedera Local Node wa // Docker commands export const DOCKER_STOPPING_CONTAINERS_MESSAGE = `${LOADING} Stopping the docker containers...`; -export const DOCKER_CLEANING_VALUMES_MESSAGE = `${LOADING} Cleaning the volumes and temp files...`; +export const DOCKER_PULLING_IMAGES_MESSAGE = `${LOADING} Pulling docker images...`; +export const DOCKER_CLEANING_VOLUMES_MESSAGE = `${LOADING} Cleaning the volumes and temp files...`; // Recovery state export const RECOVERY_STATE_INIT_MESSAGE = `${CHECK_SUCCESS} Recovery State Initialized!`; diff --git a/src/services/DockerService.ts b/src/services/DockerService.ts index 605a9baa..48308a85 100644 --- a/src/services/DockerService.ts +++ b/src/services/DockerService.ts @@ -26,7 +26,7 @@ import { IS_WINDOWS, NECESSARY_PORTS, UNKNOWN_VERSION, OPTIONAL_PORTS, MIN_CPUS, MIN_MEMORY_MULTI_MODE, MIN_MEMORY_SINGLE_MODE, RECOMMENDED_CPUS, RECOMMENDED_MEMORY_SINGLE_MODE, CHECK_SUCCESS, CHECK_FAIL, LOADING, - SHARED_PATHS_ERROR + SHARED_PATHS_ERROR, DOCKER_PULLING_IMAGES_MESSAGE, } from '../constants'; import { IService } from './IService'; import { LoggerService } from './LoggerService'; @@ -36,6 +36,7 @@ import * as dotenv from 'dotenv'; import { CLIOptions } from '../types/CLIOptions'; import path from 'path'; import { SafeDockerNetworkRemover } from '../utils/SafeDockerNetworkRemover'; +import yaml from 'js-yaml'; dotenv.config(); @@ -223,6 +224,26 @@ export class DockerService implements IService{ this.checkCPUResources(dockerCPUs); } + public checkDockerImages() { + const dockerComposeYml = yaml.load(shell.exec("docker compose config", { silent: true }).stdout) as any; + const dockerComposeImages = Object.values(dockerComposeYml.services).map((s: any) => { + const parsed = s.image.split(":"); + return `${parsed[0]}:${parsed[1] ?? "latest"}`; + }); + const dockerComposeImagesUnique = [...new Set(dockerComposeImages.sort())]; + + const dockerImagesString = shell.exec("docker images", { silent: true }).stdout.split(/\r?\n/).slice(1, -1); + const dockerImages = dockerImagesString.map(line => { + const parsed = line.replace(/\s\s+/g, " ").split(" "); + return `${parsed[0]}:${parsed[1]}`; + }); + const dockerImagesUnique = [...new Set(dockerImages.sort())]; + + if (!dockerImages.length || dockerComposeImagesUnique.toString() != dockerImagesUnique.toString()) { + this.logger.info(DOCKER_PULLING_IMAGES_MESSAGE, this.serviceName); + } + } + private checkMemoryResources(dockerMemory: number, isMultiNodeMode: boolean) { if ((dockerMemory >= MIN_MEMORY_SINGLE_MODE && dockerMemory < RECOMMENDED_MEMORY_SINGLE_MODE && !isMultiNodeMode) || (dockerMemory < MIN_MEMORY_SINGLE_MODE && !isMultiNodeMode) || diff --git a/src/state/StartState.ts b/src/state/StartState.ts index 9bc7b3cb..bcc5318c 100644 --- a/src/state/StartState.ts +++ b/src/state/StartState.ts @@ -99,6 +99,8 @@ export class StartState implements IState{ shell.cd(__dirname); shell.cd('../../'); + + this.dockerService.checkDockerImages(); const output = await this.dockerService.dockerComposeUp(this.cliOptions); if (output.code === 1) { diff --git a/src/state/StopState.ts b/src/state/StopState.ts index 16f2a1de..96ea14aa 100644 --- a/src/state/StopState.ts +++ b/src/state/StopState.ts @@ -26,7 +26,7 @@ import { ServiceLocator } from '../services/ServiceLocator'; import { IState } from './IState'; import { EventType } from '../types/EventType'; import { - DOCKER_CLEANING_VALUMES_MESSAGE, + DOCKER_CLEANING_VOLUMES_MESSAGE, DOCKER_STOPPING_CONTAINERS_MESSAGE, IS_WINDOWS, STOP_STATE_INIT_MESSAGE, @@ -98,7 +98,7 @@ export class StopState implements IState { this.logger.trace(DOCKER_STOPPING_CONTAINERS_MESSAGE, this.stateName); shell.exec(`docker compose kill --remove-orphans 2>${nullOutput}`); shell.exec(`docker compose down -v --remove-orphans 2>${nullOutput}`); - this.logger.trace(DOCKER_CLEANING_VALUMES_MESSAGE, this.stateName); + this.logger.trace(DOCKER_CLEANING_VOLUMES_MESSAGE, this.stateName); shell.exec(`rm -rf network-logs/* >${nullOutput} 2>&1`); this.logger.trace(`Working dir is ${this.cliOptions.workDir}`, this.stateName); shell.exec(`rm -rf "${join(this.cliOptions.workDir, 'network-logs')}" >${nullOutput} 2>&1`); diff --git a/test/unit/states/StopState.spec.ts b/test/unit/states/StopState.spec.ts index 4fa9ac80..5687e03e 100644 --- a/test/unit/states/StopState.spec.ts +++ b/test/unit/states/StopState.spec.ts @@ -22,7 +22,7 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { StopState } from '../../../src/state/StopState'; import { - DOCKER_CLEANING_VALUMES_MESSAGE, + DOCKER_CLEANING_VOLUMES_MESSAGE, DOCKER_STOPPING_CONTAINERS_MESSAGE, IS_WINDOWS, NETWORK_PREFIX, @@ -92,7 +92,7 @@ describe('StopState tests', () => { testSandbox.assert.calledWith(loggerService.info, STOP_STATE_ON_START_MESSAGE, StopState.name); testSandbox.assert.calledWith(loggerService.info, STOP_STATE_STOPPING_MESSAGE, StopState.name); testSandbox.assert.calledWith(loggerService.trace, DOCKER_STOPPING_CONTAINERS_MESSAGE, StopState.name); - testSandbox.assert.calledWith(loggerService.trace, DOCKER_CLEANING_VALUMES_MESSAGE, StopState.name); + testSandbox.assert.calledWith(loggerService.trace, DOCKER_CLEANING_VOLUMES_MESSAGE, StopState.name); testSandbox.assert.calledWith(loggerService.trace, TEST_DIR_MESSAGE, StopState.name); testSandbox.assert.calledWith(loggerService.info, STOP_STATE_STOPPED_MESSAGE, StopState.name);