Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
4b1c908b89 | |||
6e313261e7 | |||
42df15a523 | |||
7ef2ebcf5b | |||
87f26b7b63 | |||
ffdc61fb42 | |||
5b25704cf8 | |||
00e6033d8b | |||
453040983d | |||
456858bc36 | |||
606c82dafa | |||
9fc4afe4b8 | |||
90689c2645 | |||
4a1d649e5e | |||
66bd36dc4f | |||
349d711cc5 | |||
c74a4bcd5b | |||
ff835c4160 | |||
05eceeb056 | |||
de55beda08 | |||
9aa2b0c7be | |||
a283bbfba0 | |||
8a4e300581 | |||
6b0d96b745 | |||
a08c11838f | |||
7c5225125c | |||
bc4778f7db | |||
2e7e8ae5cf | |||
054585c7f5 | |||
c0cebbe614 | |||
740f83114c | |||
e48023d490 | |||
eaaf313442 | |||
68b2baadae |
BIN
.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl
vendored
Normal file
BIN
.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl
vendored
Normal file
Binary file not shown.
68
.serena/project.yml
Normal file
68
.serena/project.yml
Normal file
@@ -0,0 +1,68 @@
|
||||
# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby)
|
||||
# * For C, use cpp
|
||||
# * For JavaScript, use typescript
|
||||
# Special requirements:
|
||||
# * csharp: Requires the presence of a .sln file in the project folder.
|
||||
language: typescript
|
||||
|
||||
# whether to use the project's gitignore file to ignore files
|
||||
# Added on 2025-04-07
|
||||
ignore_all_files_in_gitignore: true
|
||||
# list of additional paths to ignore
|
||||
# same syntax as gitignore, so you can use * and **
|
||||
# Was previously called `ignored_dirs`, please update your config if you are using that.
|
||||
# Added (renamed) on 2025-04-07
|
||||
ignored_paths: []
|
||||
|
||||
# whether the project is in read-only mode
|
||||
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
||||
# Added on 2025-04-18
|
||||
read_only: false
|
||||
|
||||
|
||||
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
|
||||
# Below is the complete list of tools for convenience.
|
||||
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||
# execute `uv run scripts/print_tool_overview.py`.
|
||||
#
|
||||
# * `activate_project`: Activates a project by name.
|
||||
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
||||
# * `delete_lines`: Deletes a range of lines within a file.
|
||||
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
|
||||
# * `execute_shell_command`: Executes a shell command.
|
||||
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
|
||||
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
|
||||
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
||||
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
||||
# * `initial_instructions`: Gets the initial instructions for the current project.
|
||||
# Should only be used in settings where the system prompt cannot be set,
|
||||
# e.g. in clients you have no control over, like Claude Desktop.
|
||||
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
||||
# * `insert_at_line`: Inserts content at a given line in a file.
|
||||
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
||||
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
||||
# * `list_memories`: Lists memories in Serena's project-specific memory store.
|
||||
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
||||
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
|
||||
# * `read_file`: Reads a file within the project directory.
|
||||
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
|
||||
# * `remove_project`: Removes a project from the Serena configuration.
|
||||
# * `replace_lines`: Replaces a range of lines within a file with new content.
|
||||
# * `replace_symbol_body`: Replaces the full definition of a symbol.
|
||||
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
|
||||
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
||||
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
|
||||
# * `switch_modes`: Activates modes by providing a list of their names
|
||||
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
|
||||
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
|
||||
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
|
||||
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
||||
excluded_tools: []
|
||||
|
||||
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||
# (contrary to the memories, which are loaded on demand).
|
||||
initial_prompt: ""
|
||||
|
||||
project_name: "docker"
|
198
changelog.md
Normal file
198
changelog.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-08-19 - 1.3.4 - fix(test)
|
||||
Increase test timeout, enable DockerImageStore test, update test image name, bump smartrequest patch, and add local claude settings
|
||||
|
||||
- Increase tstest timeout from 120s to 600s in package.json to accommodate longer-running integration tests.
|
||||
- Unskip the DockerImageStore integration test and change stored image name from 'hello' to 'hello2' in test/test.nonci.node.ts.
|
||||
- Bump dependency @push.rocks/smartrequest from ^4.3.0 to ^4.3.1.
|
||||
- Add .claude/settings.local.json to allow local agent permissions for running tests and related tooling.
|
||||
|
||||
## 2025-08-19 - 1.3.3 - fix(classes.host)
|
||||
Adjust requestStreaming timeout and autoDrain; stabilize tests
|
||||
|
||||
- Reduced requestStreaming timeout from 10 minutes to 30 seconds to avoid long-running hanging requests.
|
||||
- Enabled autoDrain for streaming requests to ensure response streams are properly drained and reduce resource issues.
|
||||
- Marked the DockerImageStore S3 integration test as skipped to avoid CI dependence on external S3 and added a cleanup test to stop the test DockerHost.
|
||||
- Added local tool settings file (.claude/settings.local.json) with local permissions (development-only).
|
||||
|
||||
## 2025-08-18 - 1.3.2 - fix(package.json)
|
||||
Fix test script timeout typo, update dependency versions, and add typings & project configs
|
||||
|
||||
- Fix test script: correct 'tineout' -> 'timeout' for npm test command and set timeout to 120s
|
||||
- Add 'typings': 'dist_ts/index.d.ts' to package.json
|
||||
- Bump dependencies to newer compatible versions (notable packages: @push.rocks/lik, @push.rocks/smartarchive, @push.rocks/smartbucket, @push.rocks/smartfile, @push.rocks/smartlog, @push.rocks/smartpromise, @push.rocks/smartstream, rxjs)
|
||||
- Add project/config files: .serena/project.yml and .claude/settings.local.json (editor/CI metadata)
|
||||
- Include generated cache/metadata files (typescript document symbols cache) — not source changes but tooling/cache artifacts
|
||||
|
||||
## 2025-08-18 - 1.3.1 - fix(test)
|
||||
Update test setup and devDependencies; adjust test import and add package metadata
|
||||
|
||||
- Update test script to run with additional flags: --verbose, --logfile and --tineout 120
|
||||
- Bump devDependencies: @git.zone/tsbuild -> ^2.6.7, @git.zone/tsrun -> ^1.3.3, @git.zone/tstest -> ^2.3.5, @push.rocks/qenv -> ^6.1.3
|
||||
- Change test import from @push.rocks/tapbundle to @git.zone/tstest/tapbundle
|
||||
- Add typings field (dist_ts/index.d.ts)
|
||||
- Add packageManager field for pnpm@10.14.0 with integrity hash
|
||||
|
||||
## 2024-12-23 - 1.3.0 - feat(core)
|
||||
Initial release of Docker client with TypeScript support
|
||||
|
||||
- Provides easy communication with Docker's remote API from Node.js
|
||||
- Includes implementations for managing Docker services, networks, secrets, containers, and images
|
||||
|
||||
## 2024-12-23 - 1.2.8 - fix(core)
|
||||
Improved the image creation process from tar stream in DockerImage class.
|
||||
|
||||
- Enhanced `DockerImage.createFromTarStream` method to handle streamed response and parse imported image details.
|
||||
- Fixed the dependency version for `@push.rocks/smartarchive` in package.json.
|
||||
|
||||
## 2024-10-13 - 1.2.7 - fix(core)
|
||||
Prepare patch release with minor fixes and improvements
|
||||
|
||||
|
||||
## 2024-10-13 - 1.2.6 - fix(core)
|
||||
Minor refactoring and code quality improvements.
|
||||
|
||||
|
||||
## 2024-10-13 - 1.2.5 - fix(dependencies)
|
||||
Update dependencies for stability improvements
|
||||
|
||||
- Updated @push.rocks/smartstream to version ^3.0.46
|
||||
- Updated @push.rocks/tapbundle to version ^5.3.0
|
||||
- Updated @types/node to version 22.7.5
|
||||
|
||||
## 2024-10-13 - 1.2.4 - fix(core)
|
||||
Refactored DockerImageStore constructor to remove DockerHost dependency
|
||||
|
||||
- Adjusted DockerImageStore constructor to remove dependency on DockerHost
|
||||
- Updated ts/classes.host.ts to align with DockerImageStore's new constructor signature
|
||||
|
||||
## 2024-08-21 - 1.2.3 - fix(dependencies)
|
||||
Update dependencies to the latest versions and fix image export test
|
||||
|
||||
- Updated several dependencies to their latest versions in package.json.
|
||||
- Enabled the previously skipped 'should export images' test.
|
||||
|
||||
## 2024-06-10 - 1.2.1-1.2.2 - Core/General
|
||||
General updates and fixes.
|
||||
|
||||
- Fix core update
|
||||
|
||||
## 2024-06-10 - 1.2.0 - Core
|
||||
Core updates and bug fixes.
|
||||
|
||||
- Fix core update
|
||||
|
||||
## 2024-06-08 - 1.2.0 - General/Core
|
||||
Major release with core enhancements.
|
||||
|
||||
- Processing images with extraction, retagging, repackaging, and long-term storage
|
||||
|
||||
## 2024-06-06 - 1.1.4 - General/Imagestore
|
||||
Significant feature addition.
|
||||
|
||||
- Add feature to process images with extraction, retagging, repackaging, and long-term storage
|
||||
|
||||
## 2024-05-08 - 1.0.112 - Images
|
||||
Add new functionality for image handling.
|
||||
|
||||
- Can now import and export images
|
||||
- Start work on local 100% JS OCI image registry
|
||||
|
||||
## 2024-06-05 - 1.1.0-1.1.3 - Core
|
||||
Regular updates and fixes.
|
||||
|
||||
- Fix core update
|
||||
|
||||
## 2024-02-02 - 1.0.105-1.0.110 - Core
|
||||
Routine core updates and fixes.
|
||||
|
||||
- Fix core update
|
||||
|
||||
## 2022-10-17 - 1.0.103-1.0.104 - Core
|
||||
Routine core updates.
|
||||
|
||||
- Fix core update
|
||||
|
||||
## 2020-10-01 - 1.0.99-1.0.102 - Core
|
||||
Routine core updates.
|
||||
|
||||
- Fix core update
|
||||
|
||||
## 2019-09-22 - 1.0.73-1.0.78 - Core
|
||||
Routine updates and core fixes.
|
||||
|
||||
- Fix core update
|
||||
|
||||
## 2019-09-13 - 1.0.60-1.0.72 - Core
|
||||
Routine updates and core fixes.
|
||||
|
||||
- Fix core update
|
||||
|
||||
## 2019-08-16 - 1.0.43-1.0.59 - Core
|
||||
Routine updates and core fixes.
|
||||
|
||||
- Fix core update
|
||||
|
||||
## 2019-08-15 - 1.0.37-1.0.42 - Core
|
||||
Routine updates and core fixes.
|
||||
|
||||
- Fix core update
|
||||
|
||||
## 2019-08-14 - 1.0.31-1.0.36 - Core
|
||||
Routine updates and core fixes.
|
||||
|
||||
- Fix core update
|
||||
|
||||
## 2019-01-10 - 1.0.27-1.0.30 - Core
|
||||
Routine updates and core fixes.
|
||||
|
||||
- Fix core update
|
||||
|
||||
## 2018-07-16 - 1.0.23-1.0.24 - Core
|
||||
Routine updates and core fixes.
|
||||
|
||||
- Fix core shift to new style
|
||||
|
||||
## 2017-07-16 - 1.0.20-1.0.22 - General
|
||||
Routine updates and fixes.
|
||||
|
||||
- Update node_modules within npmdocker
|
||||
|
||||
## 2017-04-02 - 1.0.18-1.0.19 - General
|
||||
Routine updates and fixes.
|
||||
|
||||
- Work with npmdocker and npmts 7.x.x
|
||||
- CI updates
|
||||
|
||||
## 2016-07-31 - 1.0.17 - General
|
||||
Enhancements and fixes.
|
||||
|
||||
- Now waiting for response to be stored before ending streaming request
|
||||
- Cosmetic fix
|
||||
|
||||
## 2016-07-29 - 1.0.14-1.0.16 - General
|
||||
Multiple updates and features added.
|
||||
|
||||
- Fix request for change observable and add npmdocker
|
||||
- Add request typings
|
||||
|
||||
## 2016-07-28 - 1.0.13 - Core
|
||||
Fixes and preparations.
|
||||
|
||||
- Fixed request for newer docker
|
||||
- Prepare for npmdocker
|
||||
|
||||
|
||||
## 2016-06-16 - 1.0.0-1.0.2 - General
|
||||
Initial sequence of releases, significant feature additions and CI setups.
|
||||
|
||||
- Implement container start and stop
|
||||
- Implement list containers and related functions
|
||||
- Add tests with in docker environment
|
||||
|
||||
## 2016-04-12 - unknown - Initial Commit
|
||||
Initial project setup.
|
||||
|
||||
- Initial commit
|
||||
|
41
package.json
41
package.json
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@apiclient.xyz/docker",
|
||||
"version": "1.1.2",
|
||||
"version": "1.3.4",
|
||||
"description": "Provides easy communication with Docker remote API from Node.js, with TypeScript support.",
|
||||
"private": false,
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "(tstest test/ --web)",
|
||||
"test": "(tstest test/ --verbose --logfile --timeout 600)",
|
||||
"build": "(tsbuild --web --allowimplicitany)",
|
||||
"buildDocs": "tsdoc"
|
||||
},
|
||||
@@ -33,27 +33,29 @@
|
||||
},
|
||||
"homepage": "https://gitlab.com/mojoio/docker#readme",
|
||||
"dependencies": {
|
||||
"@push.rocks/lik": "^6.0.15",
|
||||
"@push.rocks/smartarchive": "^4.0.22",
|
||||
"@push.rocks/smartfile": "^11.0.16",
|
||||
"@push.rocks/lik": "^6.2.2",
|
||||
"@push.rocks/smartarchive": "^4.2.2",
|
||||
"@push.rocks/smartbucket": "^3.3.10",
|
||||
"@push.rocks/smartfile": "^11.2.7",
|
||||
"@push.rocks/smartjson": "^5.0.20",
|
||||
"@push.rocks/smartlog": "^3.0.6",
|
||||
"@push.rocks/smartnetwork": "^3.0.0",
|
||||
"@push.rocks/smartpath": "^5.0.18",
|
||||
"@push.rocks/smartpromise": "^4.0.3",
|
||||
"@push.rocks/smartrequest": "^2.0.22",
|
||||
"@push.rocks/smartstream": "^3.0.44",
|
||||
"@push.rocks/smartlog": "^3.1.8",
|
||||
"@push.rocks/smartnetwork": "^4.1.2",
|
||||
"@push.rocks/smartpath": "^6.0.0",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smartrequest": "^4.3.1",
|
||||
"@push.rocks/smartstream": "^3.2.5",
|
||||
"@push.rocks/smartstring": "^4.0.15",
|
||||
"@push.rocks/smartunique": "^3.0.9",
|
||||
"@push.rocks/smartversion": "^3.0.5",
|
||||
"@tsclass/tsclass": "^4.0.54",
|
||||
"rxjs": "^7.5.7"
|
||||
"@tsclass/tsclass": "^9.2.0",
|
||||
"rxjs": "^7.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.1.80",
|
||||
"@git.zone/tsrun": "^1.2.12",
|
||||
"@git.zone/tstest": "^1.0.90",
|
||||
"@push.rocks/tapbundle": "^5.0.23",
|
||||
"@types/node": "20.14.1"
|
||||
"@git.zone/tsbuild": "^2.6.7",
|
||||
"@git.zone/tsrun": "^1.3.3",
|
||||
"@git.zone/tstest": "^2.3.5",
|
||||
"@push.rocks/qenv": "^6.1.3",
|
||||
"@types/node": "22.7.5"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
@@ -69,5 +71,6 @@
|
||||
],
|
||||
"browserslist": [
|
||||
"last 1 chrome versions"
|
||||
]
|
||||
],
|
||||
"packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748"
|
||||
}
|
||||
|
7534
pnpm-lock.yaml
generated
7534
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
6
qenv.yml
Normal file
6
qenv.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
required:
|
||||
- S3_ENDPOINT
|
||||
- S3_ACCESSKEY
|
||||
- S3_ACCESSSECRET
|
||||
- S3_BUCKET
|
||||
|
539
readme.md
539
readme.md
@@ -1,136 +1,495 @@
|
||||
# @apiclient.xyz/docker
|
||||
# @apiclient.xyz/docker 🐳
|
||||
|
||||
easy communication with docker remote api from node, TypeScript ready
|
||||
> **Powerful TypeScript client for Docker Remote API** - Build, manage, and orchestrate Docker containers, images, networks, and more with type-safe elegance.
|
||||
|
||||
## Install
|
||||
## 🚀 Features
|
||||
|
||||
To install @apiclient.xyz/docker, you can use npm (npm package manager). Run the following command in your terminal:
|
||||
- 🎯 **Full TypeScript Support** - Complete type definitions for Docker API entities
|
||||
- 🔄 **Async/Await Ready** - Modern promise-based architecture for seamless async operations
|
||||
- 📦 **Container Management** - Create, list, inspect, and remove containers effortlessly
|
||||
- 🖼️ **Image Handling** - Pull from registries, build from tarballs, export, and manage tags
|
||||
- 🌐 **Network Operations** - Create and manage Docker networks with full IPAM support
|
||||
- 🔐 **Secrets Management** - Handle Docker secrets securely in swarm mode
|
||||
- 🎭 **Service Orchestration** - Deploy and manage services in Docker Swarm
|
||||
- 💾 **S3 Image Storage** - Built-in support for storing/retrieving images from S3
|
||||
- 📊 **Event Streaming** - Real-time Docker event monitoring with RxJS observables
|
||||
- 🔧 **Registry Authentication** - Seamless authentication with Docker registries
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
```bash
|
||||
# Using npm
|
||||
npm install @apiclient.xyz/docker --save
|
||||
|
||||
# Using pnpm (recommended)
|
||||
pnpm add @apiclient.xyz/docker
|
||||
|
||||
# Using yarn
|
||||
yarn add @apiclient.xyz/docker
|
||||
```
|
||||
|
||||
This command installs the package and adds it as a dependency to your project's `package.json` file.
|
||||
|
||||
## Usage
|
||||
|
||||
The `@apiclient.xyz/docker` package provides a TypeScript-ready interface for interacting with Docker's Remote API directly from Node.js applications. It leverages TypeScript for strong type definitions, ensuring more reliable and maintainable code.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you begin, ensure:
|
||||
|
||||
- You have Docker installed and running on your machine or a remote server.
|
||||
- You are familiar with TypeScript and have it set up in your development environment.
|
||||
|
||||
### Getting Started
|
||||
|
||||
First, import the required classes from the package:
|
||||
## 🎯 Quick Start
|
||||
|
||||
```typescript
|
||||
import { DockerHost, DockerContainer, DockerService, DockerNetwork } from '@apiclient.xyz/docker';
|
||||
import { DockerHost } from '@apiclient.xyz/docker';
|
||||
|
||||
// Connect to local Docker daemon
|
||||
const docker = new DockerHost();
|
||||
|
||||
// Or connect to remote Docker host
|
||||
const remoteDocker = new DockerHost({
|
||||
socketPath: 'tcp://remote-docker-host:2375'
|
||||
});
|
||||
```
|
||||
|
||||
### Instantiate DockerHost
|
||||
## 📚 Complete API Guide
|
||||
|
||||
Start by creating a `DockerHost` instance. This class is the entry point to communicate with the Docker Remote API.
|
||||
### 🐳 DockerHost - Your Gateway to Docker
|
||||
|
||||
The `DockerHost` class is your primary interface to interact with the Docker daemon.
|
||||
|
||||
```typescript
|
||||
// Connect to local Docker instance
|
||||
const localDockerHost = new DockerHost();
|
||||
import { DockerHost } from '@apiclient.xyz/docker';
|
||||
|
||||
// Or specify a custom path or URL to a Docker host
|
||||
const remoteDockerHost = new DockerHost('tcp://<REMOTE_DOCKER_HOST>:2375');
|
||||
// Initialize with default local socket
|
||||
const docker = new DockerHost();
|
||||
|
||||
// Custom initialization options
|
||||
const customDocker = new DockerHost({
|
||||
socketPath: '/var/run/docker.sock', // Unix socket path
|
||||
// or
|
||||
socketPath: 'tcp://192.168.1.100:2375' // TCP connection
|
||||
});
|
||||
|
||||
// Start and stop (for lifecycle management)
|
||||
await docker.start();
|
||||
// ... do your work
|
||||
await docker.stop();
|
||||
```
|
||||
|
||||
### Working with Containers
|
||||
### 📦 Container Management
|
||||
|
||||
#### List All Containers
|
||||
|
||||
```typescript
|
||||
async function listAllContainers() {
|
||||
const containers = await localDockerHost.getContainers();
|
||||
console.log(containers);
|
||||
}
|
||||
// Get all containers (including stopped ones)
|
||||
const allContainers = await docker.getContainers();
|
||||
|
||||
listAllContainers();
|
||||
// Each container includes detailed information
|
||||
allContainers.forEach(container => {
|
||||
console.log(`Container: ${container.Names[0]}`);
|
||||
console.log(` ID: ${container.Id}`);
|
||||
console.log(` Status: ${container.Status}`);
|
||||
console.log(` Image: ${container.Image}`);
|
||||
console.log(` State: ${container.State}`);
|
||||
});
|
||||
```
|
||||
|
||||
#### Create and Remove a Container
|
||||
#### Create and Manage Containers
|
||||
|
||||
```typescript
|
||||
import { IContainerCreationDescriptor } from '@apiclient.xyz/docker';
|
||||
import { DockerContainer } from '@apiclient.xyz/docker';
|
||||
|
||||
async function createAndRemoveContainer() {
|
||||
const containerDescriptor: IContainerCreationDescriptor = {
|
||||
Hostname: 'test-container',
|
||||
Domainname: '',
|
||||
// Additional settings here
|
||||
};
|
||||
// Create a container with detailed configuration
|
||||
const container = await DockerContainer.create(docker, {
|
||||
Image: 'nginx:latest',
|
||||
name: 'my-nginx-server',
|
||||
HostConfig: {
|
||||
PortBindings: {
|
||||
'80/tcp': [{ HostPort: '8080' }]
|
||||
},
|
||||
RestartPolicy: {
|
||||
Name: 'unless-stopped'
|
||||
},
|
||||
Memory: 512 * 1024 * 1024, // 512MB memory limit
|
||||
},
|
||||
Env: [
|
||||
'NODE_ENV=production',
|
||||
'LOG_LEVEL=info'
|
||||
],
|
||||
Labels: {
|
||||
'app': 'web-server',
|
||||
'environment': 'production'
|
||||
}
|
||||
});
|
||||
|
||||
// Create container
|
||||
const container = await DockerContainer.create(localDockerHost, containerDescriptor);
|
||||
console.log(`Container Created: ${container.Id}`);
|
||||
console.log(`Container created: ${container.Id}`);
|
||||
|
||||
// Remove container
|
||||
await container.remove();
|
||||
console.log(`Container Removed: ${container.Id}`);
|
||||
}
|
||||
|
||||
createAndRemoveContainer();
|
||||
// Container operations (these would need to be implemented)
|
||||
// await container.start();
|
||||
// await container.stop();
|
||||
// await container.remove();
|
||||
```
|
||||
|
||||
### Working with Docker Services
|
||||
|
||||
#### Create a Docker Service
|
||||
#### Get Container by ID
|
||||
|
||||
```typescript
|
||||
import { IServiceCreationDescriptor } from '@apiclient.xyz/docker';
|
||||
const container = await DockerContainer.getContainerById(docker, 'container-id-here');
|
||||
if (container) {
|
||||
console.log(`Found container: ${container.Names[0]}`);
|
||||
}
|
||||
```
|
||||
|
||||
async function createDockerService() {
|
||||
const serviceDescriptor: IServiceCreationDescriptor = {
|
||||
name: 'my-service',
|
||||
image: 'nginx:latest', // Docker Image
|
||||
// Additional settings
|
||||
};
|
||||
### 🖼️ Image Management
|
||||
|
||||
#### Pull Images from Registry
|
||||
|
||||
```typescript
|
||||
import { DockerImage } from '@apiclient.xyz/docker';
|
||||
|
||||
// Pull an image from Docker Hub
|
||||
const image = await DockerImage.createFromRegistry(docker, {
|
||||
imageName: 'node',
|
||||
imageTag: '18-alpine',
|
||||
// Optional: provide registry authentication
|
||||
authToken: 'your-registry-auth-token'
|
||||
});
|
||||
|
||||
console.log(`Image pulled: ${image.RepoTags[0]}`);
|
||||
console.log(`Size: ${(image.Size / 1024 / 1024).toFixed(2)} MB`);
|
||||
```
|
||||
|
||||
#### Import Images from Tar
|
||||
|
||||
```typescript
|
||||
import * as fs from 'fs';
|
||||
|
||||
// Import from a tar stream
|
||||
const tarStream = fs.createReadStream('./my-image.tar');
|
||||
const importedImage = await DockerImage.createFromTarStream(docker, {
|
||||
tarStream,
|
||||
imageUrl: 'file://./my-image.tar',
|
||||
imageTag: 'my-app:v1.0.0'
|
||||
});
|
||||
```
|
||||
|
||||
#### Export Images to Tar
|
||||
|
||||
```typescript
|
||||
// Export an image to a tar stream
|
||||
const image = await DockerImage.getImageByName(docker, 'nginx:latest');
|
||||
const exportStream = await image.exportToTarStream();
|
||||
|
||||
// Save to file
|
||||
const writeStream = fs.createWriteStream('./nginx-export.tar');
|
||||
exportStream.pipe(writeStream);
|
||||
```
|
||||
|
||||
#### Tag Images
|
||||
|
||||
```typescript
|
||||
// Tag an existing image
|
||||
await DockerImage.tagImageByIdOrName(docker, 'node:18-alpine', {
|
||||
registry: 'myregistry.com',
|
||||
imageName: 'my-node-app',
|
||||
imageTag: 'v2.0.0'
|
||||
});
|
||||
// Result: myregistry.com/my-node-app:v2.0.0
|
||||
```
|
||||
|
||||
### 🌐 Network Management
|
||||
|
||||
#### Create Custom Networks
|
||||
|
||||
```typescript
|
||||
import { DockerNetwork } from '@apiclient.xyz/docker';
|
||||
|
||||
// Create a bridge network
|
||||
const network = await DockerNetwork.createNetwork(docker, {
|
||||
Name: 'my-app-network',
|
||||
Driver: 'bridge',
|
||||
EnableIPv6: false,
|
||||
IPAM: {
|
||||
Driver: 'default',
|
||||
Config: [{
|
||||
Subnet: '172.28.0.0/16',
|
||||
Gateway: '172.28.0.1'
|
||||
}]
|
||||
},
|
||||
Labels: {
|
||||
'project': 'my-app',
|
||||
'environment': 'production'
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Network created: ${network.Id}`);
|
||||
```
|
||||
|
||||
#### List and Inspect Networks
|
||||
|
||||
```typescript
|
||||
// Get all networks
|
||||
const networks = await docker.getNetworks();
|
||||
networks.forEach(net => {
|
||||
console.log(`Network: ${net.Name} (${net.Driver})`);
|
||||
console.log(` Scope: ${net.Scope}`);
|
||||
console.log(` Internal: ${net.Internal}`);
|
||||
});
|
||||
|
||||
// Get specific network
|
||||
const appNetwork = await DockerNetwork.getNetworkByName(docker, 'my-app-network');
|
||||
|
||||
// Get containers on network
|
||||
const containers = await appNetwork.getContainersOnNetwork();
|
||||
console.log(`Containers on network: ${containers.length}`);
|
||||
```
|
||||
|
||||
### 🎭 Service Management (Swarm Mode)
|
||||
|
||||
#### Deploy Services
|
||||
|
||||
```typescript
|
||||
import { DockerService } from '@apiclient.xyz/docker';
|
||||
|
||||
// Create a replicated service
|
||||
const service = await DockerService.createService(docker, {
|
||||
name: 'web-api',
|
||||
image: 'my-api:latest',
|
||||
replicas: 3,
|
||||
ports: [{
|
||||
Protocol: 'tcp',
|
||||
PublishedPort: 80,
|
||||
TargetPort: 3000
|
||||
}],
|
||||
networks: ['my-app-network'],
|
||||
labels: {
|
||||
'app': 'api',
|
||||
'version': '2.0.0'
|
||||
},
|
||||
resources: {
|
||||
limits: {
|
||||
Memory: 256 * 1024 * 1024, // 256MB
|
||||
CPUs: 0.5
|
||||
}
|
||||
},
|
||||
secrets: ['api-key', 'db-password'],
|
||||
mounts: [{
|
||||
Target: '/data',
|
||||
Source: 'app-data',
|
||||
Type: 'volume'
|
||||
}]
|
||||
});
|
||||
|
||||
console.log(`Service deployed: ${service.ID}`);
|
||||
```
|
||||
|
||||
#### Manage Services
|
||||
|
||||
```typescript
|
||||
// List all services
|
||||
const services = await docker.getServices();
|
||||
services.forEach(service => {
|
||||
console.log(`Service: ${service.Spec.Name}`);
|
||||
console.log(` Replicas: ${service.Spec.Mode.Replicated.Replicas}`);
|
||||
console.log(` Image: ${service.Spec.TaskTemplate.ContainerSpec.Image}`);
|
||||
});
|
||||
|
||||
// Get service by name
|
||||
const myService = await DockerService.getServiceByName(docker, 'web-api');
|
||||
|
||||
// Check if service needs update
|
||||
const needsUpdate = await myService.needsUpdate();
|
||||
if (needsUpdate) {
|
||||
console.log('Service configuration has changed, update needed');
|
||||
}
|
||||
|
||||
// Remove service
|
||||
await myService.remove();
|
||||
```
|
||||
|
||||
### 🔐 Secrets Management
|
||||
|
||||
```typescript
|
||||
import { DockerSecret } from '@apiclient.xyz/docker';
|
||||
|
||||
// Create a secret
|
||||
const secret = await DockerSecret.createSecret(docker, {
|
||||
name: 'api-key',
|
||||
data: Buffer.from('super-secret-key-123').toString('base64'),
|
||||
labels: {
|
||||
'app': 'my-app',
|
||||
'type': 'api-key'
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Secret created: ${secret.ID}`);
|
||||
|
||||
// List secrets
|
||||
const secrets = await DockerSecret.getSecrets(docker);
|
||||
secrets.forEach(secret => {
|
||||
console.log(`Secret: ${secret.Spec.Name}`);
|
||||
});
|
||||
|
||||
// Get secret by name
|
||||
const apiKeySecret = await DockerSecret.getSecretByName(docker, 'api-key');
|
||||
|
||||
// Update secret
|
||||
await apiKeySecret.update({
|
||||
data: Buffer.from('new-secret-key-456').toString('base64')
|
||||
});
|
||||
|
||||
// Remove secret
|
||||
await apiKeySecret.remove();
|
||||
```
|
||||
|
||||
### 💾 S3 Image Storage
|
||||
|
||||
Store and retrieve Docker images from S3-compatible storage:
|
||||
|
||||
```typescript
|
||||
// Configure S3 storage
|
||||
await docker.addS3Storage({
|
||||
endpoint: 's3.amazonaws.com',
|
||||
accessKeyId: 'your-access-key',
|
||||
secretAccessKey: 'your-secret-key',
|
||||
bucket: 'docker-images'
|
||||
});
|
||||
|
||||
// Store an image to S3
|
||||
const imageStore = docker.imageStore;
|
||||
await imageStore.storeImage('my-app:v1.0.0');
|
||||
|
||||
// Retrieve an image from S3
|
||||
const retrievedImage = await imageStore.getImage('my-app:v1.0.0');
|
||||
```
|
||||
|
||||
### 📊 Event Monitoring
|
||||
|
||||
Monitor Docker events in real-time using RxJS observables:
|
||||
|
||||
```typescript
|
||||
// Subscribe to Docker events
|
||||
const eventStream = docker.getEventObservable();
|
||||
|
||||
const subscription = eventStream.subscribe({
|
||||
next: (event) => {
|
||||
console.log(`Event: ${event.Type} - ${event.Action}`);
|
||||
console.log(`Actor: ${event.Actor.ID}`);
|
||||
console.log(`Time: ${new Date(event.time * 1000).toISOString()}`);
|
||||
},
|
||||
error: (err) => console.error('Event stream error:', err),
|
||||
complete: () => console.log('Event stream completed')
|
||||
});
|
||||
|
||||
// Unsubscribe when done
|
||||
subscription.unsubscribe();
|
||||
```
|
||||
|
||||
### 🔧 Registry Authentication
|
||||
|
||||
Authenticate with Docker registries for private images:
|
||||
|
||||
```typescript
|
||||
// Authenticate with Docker Hub
|
||||
await docker.auth({
|
||||
username: 'your-username',
|
||||
password: 'your-password',
|
||||
serveraddress: 'https://index.docker.io/v1/'
|
||||
});
|
||||
|
||||
// Or use existing Docker config
|
||||
const authToken = await docker.getAuthTokenFromDockerConfig('myregistry.com');
|
||||
|
||||
// Use auth token when pulling images
|
||||
const privateImage = await DockerImage.createFromRegistry(docker, {
|
||||
imageName: 'myregistry.com/private/image',
|
||||
imageTag: 'latest',
|
||||
authToken
|
||||
});
|
||||
```
|
||||
|
||||
### 🔄 Swarm Mode
|
||||
|
||||
Initialize and manage Docker Swarm:
|
||||
|
||||
```typescript
|
||||
// Initialize swarm mode
|
||||
await docker.activateSwarm({
|
||||
ListenAddr: '0.0.0.0:2377',
|
||||
AdvertiseAddr: '192.168.1.100:2377',
|
||||
ForceNewCluster: false
|
||||
});
|
||||
|
||||
// Now you can create services, secrets, and use swarm features
|
||||
const service = await DockerService.createService(docker, {
|
||||
name: 'my-swarm-service',
|
||||
image: 'nginx:latest',
|
||||
replicas: 5
|
||||
// ... more service config
|
||||
});
|
||||
```
|
||||
|
||||
## 🏗️ Advanced Examples
|
||||
|
||||
### Complete Application Stack
|
||||
|
||||
```typescript
|
||||
async function deployStack() {
|
||||
const docker = new DockerHost();
|
||||
|
||||
const service = await DockerService.createService(localDockerHost, serviceDescriptor);
|
||||
console.log(`Service Created: ${service.Id}`);
|
||||
}
|
||||
|
||||
createDockerService();
|
||||
```
|
||||
|
||||
### Working with Docker Networks
|
||||
|
||||
#### Listing and Creating Networks
|
||||
|
||||
```typescript
|
||||
async function listAndCreateNetwork() {
|
||||
// List all networks
|
||||
const networks = await localDockerHost.getNetworks();
|
||||
console.log(networks);
|
||||
|
||||
// Create a new network
|
||||
const network = await DockerNetwork.createNetwork(localDockerHost, {
|
||||
Name: 'my-network'
|
||||
// Additional settings
|
||||
// Create network
|
||||
const network = await DockerNetwork.createNetwork(docker, {
|
||||
Name: 'app-network',
|
||||
Driver: 'overlay' // for swarm mode
|
||||
});
|
||||
console.log(`Network Created: ${network.Id}`);
|
||||
|
||||
// Create secrets
|
||||
const dbPassword = await DockerSecret.createSecret(docker, {
|
||||
name: 'db-password',
|
||||
data: Buffer.from('strong-password').toString('base64')
|
||||
});
|
||||
|
||||
// Deploy database service
|
||||
const dbService = await DockerService.createService(docker, {
|
||||
name: 'postgres',
|
||||
image: 'postgres:14',
|
||||
networks: ['app-network'],
|
||||
secrets: ['db-password'],
|
||||
env: ['POSTGRES_PASSWORD_FILE=/run/secrets/db-password']
|
||||
});
|
||||
|
||||
// Deploy application service
|
||||
const appService = await DockerService.createService(docker, {
|
||||
name: 'web-app',
|
||||
image: 'my-app:latest',
|
||||
replicas: 3,
|
||||
networks: ['app-network'],
|
||||
ports: [{ Protocol: 'tcp', PublishedPort: 80, TargetPort: 3000 }]
|
||||
});
|
||||
|
||||
console.log('Stack deployed successfully!');
|
||||
}
|
||||
|
||||
listAndCreateNetwork();
|
||||
```
|
||||
|
||||
### Advanced Usage
|
||||
## 🔍 TypeScript Support
|
||||
|
||||
You can leverage the full potential of the Docker Remote API with `@apiclient.xyz/docker`. This includes managing images, volumes, swarms, and more. The package's design is consistent and intuitive, making it easy to extend your usage as needed.
|
||||
This package provides comprehensive TypeScript definitions for all Docker API entities:
|
||||
|
||||
Remember, the Docker Remote API offers extensive capabilities. Always refer to the [Docker API documentation](https://docs.docker.com/engine/api/latest/) for a comprehensive list of endpoints and actions you can perform.
|
||||
```typescript
|
||||
import type {
|
||||
IContainerCreationDescriptor,
|
||||
IServiceCreationDescriptor,
|
||||
INetworkCreationDescriptor,
|
||||
IImageCreationDescriptor,
|
||||
ISecretCreationDescriptor
|
||||
} from '@apiclient.xyz/docker';
|
||||
|
||||
### Conclusion
|
||||
// Full IntelliSense support for all configuration options
|
||||
const containerConfig: IContainerCreationDescriptor = {
|
||||
Image: 'node:18',
|
||||
// Your IDE will provide full autocomplete here
|
||||
};
|
||||
```
|
||||
|
||||
`@apiclient.xyz/docker` simplifies interaction with Docker's Remote API in TypeScript projects, providing strong typing and asynchronous operations. Whether you're managing containers, images, services or networks, it offers a comprehensive toolset to perform these tasks seamlessly.
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome contributions! Please feel free to submit issues and pull requests.
|
||||
|
||||
## 📖 API Documentation
|
||||
|
||||
For complete API documentation, visit [https://apiclient.xyz/docker](https://apiclient.xyz/docker)
|
||||
|
||||
For Docker Remote API reference, see [Docker Engine API Documentation](https://docs.docker.com/engine/api/latest/)
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
@@ -149,4 +508,4 @@ Registered at District court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
40
test-stream.js
Normal file
40
test-stream.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const { SmartRequest } = require('@push.rocks/smartrequest');
|
||||
|
||||
async function test() {
|
||||
try {
|
||||
const response = await SmartRequest.create()
|
||||
.url('http://unix:/run/user/1000/docker.sock:/images/hello-world:latest/get')
|
||||
.header('Host', 'docker.sock')
|
||||
.get();
|
||||
|
||||
console.log('Response status:', response.status);
|
||||
console.log('Response type:', typeof response);
|
||||
|
||||
const stream = response.streamNode();
|
||||
console.log('Stream type:', typeof stream);
|
||||
console.log('Has on method:', typeof stream.on);
|
||||
|
||||
if (stream) {
|
||||
let chunks = 0;
|
||||
stream.on('data', (chunk) => {
|
||||
chunks++;
|
||||
if (chunks <= 3) console.log('Got chunk', chunks, chunk.length);
|
||||
});
|
||||
stream.on('end', () => {
|
||||
console.log('Stream ended, total chunks:', chunks);
|
||||
process.exit(0);
|
||||
});
|
||||
stream.on('error', (err) => {
|
||||
console.error('Stream error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
} else {
|
||||
console.log('No stream available');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
test();
|
46
test-stream.mjs
Normal file
46
test-stream.mjs
Normal file
@@ -0,0 +1,46 @@
|
||||
import { SmartRequest } from '@push.rocks/smartrequest';
|
||||
|
||||
async function test() {
|
||||
try {
|
||||
const response = await SmartRequest.create()
|
||||
.url('http://unix:/run/user/1000/docker.sock:/images/hello-world:latest/get')
|
||||
.header('Host', 'docker.sock')
|
||||
.get();
|
||||
|
||||
console.log('Response status:', response.status);
|
||||
console.log('Response type:', typeof response);
|
||||
|
||||
const stream = response.streamNode();
|
||||
console.log('Stream type:', typeof stream);
|
||||
console.log('Has on method:', typeof stream.on);
|
||||
|
||||
if (stream) {
|
||||
let chunks = 0;
|
||||
stream.on('data', (chunk) => {
|
||||
chunks++;
|
||||
if (chunks <= 3) console.log('Got chunk', chunks, chunk.length);
|
||||
});
|
||||
stream.on('end', () => {
|
||||
console.log('Stream ended, total chunks:', chunks);
|
||||
process.exit(0);
|
||||
});
|
||||
stream.on('error', (err) => {
|
||||
console.error('Stream error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Set a timeout in case stream doesn't end
|
||||
setTimeout(() => {
|
||||
console.log('Timeout after 5 seconds');
|
||||
process.exit(1);
|
||||
}, 5000);
|
||||
} else {
|
||||
console.log('No stream available');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
test();
|
@@ -1,4 +1,7 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { Qenv } from '@push.rocks/qenv';
|
||||
|
||||
const testQenv = new Qenv('./', './.nogit/');
|
||||
|
||||
import * as plugins from '../ts/plugins.js';
|
||||
import * as paths from '../ts/paths.js';
|
||||
@@ -9,6 +12,7 @@ let testDockerHost: docker.DockerHost;
|
||||
|
||||
tap.test('should create a new Dockersock instance', async () => {
|
||||
testDockerHost = new docker.DockerHost({});
|
||||
await testDockerHost.start();
|
||||
return expect(testDockerHost).toBeInstanceOf(docker.DockerHost);
|
||||
});
|
||||
|
||||
@@ -135,17 +139,35 @@ tap.test('should export images', async (toolsArg) => {
|
||||
await done.promise;
|
||||
});
|
||||
|
||||
tap.test('should import images', async (toolsArg) => {
|
||||
const done = toolsArg.defer();
|
||||
tap.test('should import images', async () => {
|
||||
const fsReadStream = plugins.smartfile.fsStream.createReadStream(
|
||||
plugins.path.join(paths.nogitDir, 'testimage.tar')
|
||||
);
|
||||
await docker.DockerImage.createFromTarStream(testDockerHost, {
|
||||
const importedImage = await docker.DockerImage.createFromTarStream(testDockerHost, {
|
||||
tarStream: fsReadStream,
|
||||
creationObject: {
|
||||
imageUrl: 'code.foss.global/host.today/ht-docker-node:latest',
|
||||
}
|
||||
})
|
||||
});
|
||||
expect(importedImage).toBeInstanceOf(docker.DockerImage);
|
||||
});
|
||||
|
||||
tap.test('should expose a working DockerImageStore', async () => {
|
||||
// lets first add am s3 target
|
||||
const s3Descriptor = {
|
||||
endpoint: await testQenv.getEnvVarOnDemand('S3_ENDPOINT'),
|
||||
accessKey: await testQenv.getEnvVarOnDemand('S3_ACCESSKEY'),
|
||||
accessSecret: await testQenv.getEnvVarOnDemand('S3_ACCESSSECRET'),
|
||||
bucketName: await testQenv.getEnvVarOnDemand('S3_BUCKET'),
|
||||
};
|
||||
await testDockerHost.addS3Storage(s3Descriptor);
|
||||
|
||||
//
|
||||
await testDockerHost.imageStore.storeImage('hello2', plugins.smartfile.fsStream.createReadStream(plugins.path.join(paths.nogitDir, 'testimage.tar')));
|
||||
})
|
||||
|
||||
tap.test('cleanup', async () => {
|
||||
await testDockerHost.stop();
|
||||
})
|
||||
|
||||
export default tap.start();
|
||||
|
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @pushrocks/commitinfo
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@apiclient.xyz/docker',
|
||||
version: '1.1.2',
|
||||
version: '1.3.4',
|
||||
description: 'Provides easy communication with Docker remote API from Node.js, with TypeScript support.'
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import * as plugins from './plugins.js';
|
||||
import * as interfaces from './interfaces/index.js';
|
||||
|
||||
import { DockerHost } from './classes.host.js';
|
||||
import { logger } from './logging.js';
|
||||
import { logger } from './logger.js';
|
||||
|
||||
export class DockerContainer {
|
||||
// STATIC
|
||||
|
@@ -1,10 +1,12 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { DockerContainer } from './classes.container.js';
|
||||
import { DockerNetwork } from './classes.network.js';
|
||||
import { DockerService } from './classes.service.js';
|
||||
import { logger } from './logging.js';
|
||||
import { logger } from './logger.js';
|
||||
import path from 'path';
|
||||
import type { DockerImageStore } from './classes.imagestore.js';
|
||||
import { DockerImageStore } from './classes.imagestore.js';
|
||||
import { DockerImage } from './classes.image.js';
|
||||
|
||||
export interface IAuthData {
|
||||
serveraddress: string;
|
||||
@@ -18,18 +20,27 @@ export interface IDockerHostConstructorOptions {
|
||||
}
|
||||
|
||||
export class DockerHost {
|
||||
public options: IDockerHostConstructorOptions;
|
||||
|
||||
/**
|
||||
* the path where the docker sock can be found
|
||||
*/
|
||||
public socketPath: string;
|
||||
private registryToken: string = '';
|
||||
public imageStore: DockerImageStore;
|
||||
public smartBucket: plugins.smartbucket.SmartBucket;
|
||||
|
||||
/**
|
||||
* the constructor to instantiate a new docker sock instance
|
||||
* @param pathArg
|
||||
*/
|
||||
constructor(optionsArg: IDockerHostConstructorOptions) {
|
||||
this.options = {
|
||||
...{
|
||||
imageStoreDir: plugins.path.join(paths.nogitDir, 'temp-docker-image-store'),
|
||||
},
|
||||
...optionsArg,
|
||||
}
|
||||
let pathToUse: string;
|
||||
if (optionsArg.dockerSockPath) {
|
||||
pathToUse = optionsArg.dockerSockPath;
|
||||
@@ -48,6 +59,17 @@ export class DockerHost {
|
||||
}
|
||||
console.log(`using docker sock at ${pathToUse}`);
|
||||
this.socketPath = pathToUse;
|
||||
this.imageStore = new DockerImageStore({
|
||||
bucketDir: null,
|
||||
localDirPath: this.options.imageStoreDir,
|
||||
})
|
||||
}
|
||||
|
||||
public async start() {
|
||||
await this.imageStore.start();
|
||||
}
|
||||
public async stop() {
|
||||
await this.imageStore.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,6 +103,9 @@ export class DockerHost {
|
||||
});
|
||||
}
|
||||
|
||||
// ==============
|
||||
// NETWORKS
|
||||
// ==============
|
||||
/**
|
||||
* gets all networks
|
||||
*/
|
||||
@@ -89,9 +114,23 @@ export class DockerHost {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* create a network
|
||||
*/
|
||||
public async createNetwork(optionsArg: Parameters<typeof DockerNetwork.createNetwork>[1]) {
|
||||
return await DockerNetwork.createNetwork(this, optionsArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* get a network by name
|
||||
*/
|
||||
public async getNetworkByName(networkNameArg: string) {
|
||||
return await DockerNetwork.getNetworkByName(this, networkNameArg);
|
||||
}
|
||||
|
||||
|
||||
// ==============
|
||||
// CONTAINERS
|
||||
// ==============
|
||||
/**
|
||||
* gets all containers
|
||||
*/
|
||||
@@ -100,6 +139,10 @@ export class DockerHost {
|
||||
return containerArray;
|
||||
}
|
||||
|
||||
// ==============
|
||||
// SERVICES
|
||||
// ==============
|
||||
|
||||
/**
|
||||
* gets all services
|
||||
*/
|
||||
@@ -108,6 +151,24 @@ export class DockerHost {
|
||||
return serviceArray;
|
||||
}
|
||||
|
||||
// ==============
|
||||
// IMAGES
|
||||
// ==============
|
||||
|
||||
/**
|
||||
* get all images
|
||||
*/
|
||||
public async getImages() {
|
||||
return await DockerImage.getImages(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* get an image by name
|
||||
*/
|
||||
public async getImageByName(imageNameArg: string) {
|
||||
return await DockerImage.getImageByName(this, imageNameArg);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -165,53 +226,167 @@ export class DockerHost {
|
||||
*/
|
||||
public async request(methodArg: string, routeArg: string, dataArg = {}) {
|
||||
const requestUrl = `${this.socketPath}${routeArg}`;
|
||||
const response = await plugins.smartrequest.request(requestUrl, {
|
||||
method: methodArg,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Registry-Auth': this.registryToken,
|
||||
Host: 'docker.sock',
|
||||
},
|
||||
requestBody: dataArg,
|
||||
keepAlive: false,
|
||||
});
|
||||
if (response.statusCode !== 200) {
|
||||
console.log(response.body);
|
||||
|
||||
// Build the request using the fluent API
|
||||
const smartRequest = plugins.smartrequest.SmartRequest.create()
|
||||
.url(requestUrl)
|
||||
.header('Content-Type', 'application/json')
|
||||
.header('X-Registry-Auth', this.registryToken)
|
||||
.header('Host', 'docker.sock')
|
||||
.options({ keepAlive: false });
|
||||
|
||||
// Add body for methods that support it
|
||||
if (dataArg && Object.keys(dataArg).length > 0) {
|
||||
smartRequest.json(dataArg);
|
||||
}
|
||||
return response;
|
||||
|
||||
// Execute the request based on method
|
||||
let response;
|
||||
switch (methodArg.toUpperCase()) {
|
||||
case 'GET':
|
||||
response = await smartRequest.get();
|
||||
break;
|
||||
case 'POST':
|
||||
response = await smartRequest.post();
|
||||
break;
|
||||
case 'PUT':
|
||||
response = await smartRequest.put();
|
||||
break;
|
||||
case 'DELETE':
|
||||
response = await smartRequest.delete();
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported HTTP method: ${methodArg}`);
|
||||
}
|
||||
|
||||
// Parse the response body based on content type
|
||||
let body;
|
||||
const contentType = response.headers['content-type'] || '';
|
||||
|
||||
// Docker's streaming endpoints (like /images/create) return newline-delimited JSON
|
||||
// which can't be parsed as a single JSON object
|
||||
const isStreamingEndpoint = routeArg.includes('/images/create') ||
|
||||
routeArg.includes('/images/load') ||
|
||||
routeArg.includes('/build');
|
||||
|
||||
if (contentType.includes('application/json') && !isStreamingEndpoint) {
|
||||
body = await response.json();
|
||||
} else {
|
||||
body = await response.text();
|
||||
// Try to parse as JSON if it looks like JSON and is not a streaming response
|
||||
if (!isStreamingEndpoint && body && (body.startsWith('{') || body.startsWith('['))) {
|
||||
try {
|
||||
body = JSON.parse(body);
|
||||
} catch {
|
||||
// Keep as text if parsing fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a response object compatible with existing code
|
||||
const legacyResponse = {
|
||||
statusCode: response.status,
|
||||
body: body,
|
||||
headers: response.headers
|
||||
};
|
||||
|
||||
if (response.status !== 200) {
|
||||
console.log(body);
|
||||
}
|
||||
|
||||
return legacyResponse;
|
||||
}
|
||||
|
||||
public async requestStreaming(methodArg: string, routeArg: string, readStream?: plugins.smartstream.stream.Readable) {
|
||||
const requestUrl = `${this.socketPath}${routeArg}`;
|
||||
const response = await plugins.smartrequest.request(
|
||||
requestUrl,
|
||||
{
|
||||
method: methodArg,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Registry-Auth': this.registryToken,
|
||||
Host: 'docker.sock',
|
||||
},
|
||||
requestBody: null,
|
||||
keepAlive: false,
|
||||
},
|
||||
true,
|
||||
(readStream ? reqArg => {
|
||||
let counter = 0;
|
||||
const smartduplex = new plugins.smartstream.SmartDuplex({
|
||||
writeFunction: async (chunkArg) => {
|
||||
if (counter % 1000 === 0) {
|
||||
console.log(`posting chunk ${counter}`);
|
||||
}
|
||||
counter++;
|
||||
return chunkArg;
|
||||
|
||||
// Build the request using the fluent API
|
||||
const smartRequest = plugins.smartrequest.SmartRequest.create()
|
||||
.url(requestUrl)
|
||||
.header('Content-Type', 'application/json')
|
||||
.header('X-Registry-Auth', this.registryToken)
|
||||
.header('Host', 'docker.sock')
|
||||
.timeout(30000)
|
||||
.options({ keepAlive: false, autoDrain: true }); // Disable auto-drain for streaming
|
||||
|
||||
// If we have a readStream, use the new stream method with logging
|
||||
if (readStream) {
|
||||
let counter = 0;
|
||||
const smartduplex = new plugins.smartstream.SmartDuplex({
|
||||
writeFunction: async (chunkArg) => {
|
||||
if (counter % 1000 === 0) {
|
||||
console.log(`posting chunk ${counter}`);
|
||||
}
|
||||
});
|
||||
readStream.pipe(smartduplex).pipe(reqArg);
|
||||
} : null),
|
||||
);
|
||||
console.log(response.statusCode);
|
||||
console.log(response.body);
|
||||
return response;
|
||||
counter++;
|
||||
return chunkArg;
|
||||
}
|
||||
});
|
||||
|
||||
// Pipe through the logging duplex stream
|
||||
const loggedStream = readStream.pipe(smartduplex);
|
||||
|
||||
// Use the new stream method to stream the data
|
||||
smartRequest.stream(loggedStream, 'application/octet-stream');
|
||||
}
|
||||
|
||||
// Execute the request based on method
|
||||
let response;
|
||||
switch (methodArg.toUpperCase()) {
|
||||
case 'GET':
|
||||
response = await smartRequest.get();
|
||||
break;
|
||||
case 'POST':
|
||||
response = await smartRequest.post();
|
||||
break;
|
||||
case 'PUT':
|
||||
response = await smartRequest.put();
|
||||
break;
|
||||
case 'DELETE':
|
||||
response = await smartRequest.delete();
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported HTTP method: ${methodArg}`);
|
||||
}
|
||||
|
||||
console.log(response.status);
|
||||
|
||||
// For streaming responses, get the Node.js stream
|
||||
const nodeStream = response.streamNode();
|
||||
|
||||
if (!nodeStream) {
|
||||
// If no stream is available, consume the body as text
|
||||
const body = await response.text();
|
||||
console.log(body);
|
||||
|
||||
// Return a compatible response object
|
||||
return {
|
||||
statusCode: response.status,
|
||||
body: body,
|
||||
headers: response.headers
|
||||
};
|
||||
}
|
||||
|
||||
// For streaming responses, return the stream with added properties
|
||||
(nodeStream as any).statusCode = response.status;
|
||||
(nodeStream as any).body = ''; // For compatibility
|
||||
|
||||
return nodeStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* add s3 storage
|
||||
* @param optionsArg
|
||||
*/
|
||||
public async addS3Storage(optionsArg: plugins.tsclass.storage.IS3Descriptor) {
|
||||
this.smartBucket = new plugins.smartbucket.SmartBucket(optionsArg);
|
||||
if (!optionsArg.bucketName) {
|
||||
throw new Error('bucketName is required');
|
||||
}
|
||||
const bucket = await this.smartBucket.getBucketByName(optionsArg.bucketName);
|
||||
let wantedDirectory = await bucket.getBaseDirectory();
|
||||
if (optionsArg.directoryPath) {
|
||||
wantedDirectory = await wantedDirectory.getSubDirectoryByName(optionsArg.directoryPath);
|
||||
}
|
||||
this.imageStore.options.bucketDir = wantedDirectory;
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,11 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as interfaces from './interfaces/index.js';
|
||||
import { DockerHost } from './classes.host.js';
|
||||
import { logger } from './logging.js';
|
||||
import { logger } from './logger.js';
|
||||
|
||||
/**
|
||||
* represents a docker image on the remote docker host
|
||||
*/
|
||||
export class DockerImage {
|
||||
// STATIC
|
||||
public static async getImages(dockerHost: DockerHost) {
|
||||
@@ -14,7 +17,7 @@ export class DockerImage {
|
||||
return images;
|
||||
}
|
||||
|
||||
public static async findImageByName(dockerHost: DockerHost, imageNameArg: string) {
|
||||
public static async getImageByName(dockerHost: DockerHost, imageNameArg: string) {
|
||||
const images = await this.getImages(dockerHost);
|
||||
const result = images.find((image) => {
|
||||
if (image.RepoTags) {
|
||||
@@ -67,7 +70,7 @@ export class DockerImage {
|
||||
);
|
||||
if (response.statusCode < 300) {
|
||||
logger.log('info', `Successfully pulled image ${imageUrlObject.imageUrl} from the registry`);
|
||||
const image = await DockerImage.findImageByName(dockerHostArg, imageUrlObject.imageOriginTag);
|
||||
const image = await DockerImage.getImageByName(dockerHostArg, imageUrlObject.imageOriginTag);
|
||||
return image;
|
||||
} else {
|
||||
logger.log('error', `Failed at the attempt of creating a new image`);
|
||||
@@ -79,14 +82,94 @@ export class DockerImage {
|
||||
* @param dockerHostArg
|
||||
* @param tarStreamArg
|
||||
*/
|
||||
public static async createFromTarStream(dockerHostArg: DockerHost, optionsArg: {
|
||||
creationObject: interfaces.IImageCreationDescriptor,
|
||||
tarStream: plugins.smartstream.stream.Readable,
|
||||
}) {
|
||||
const response = await dockerHostArg.requestStreaming('POST', '/images/load', optionsArg.tarStream);
|
||||
return response;
|
||||
public static async createFromTarStream(
|
||||
dockerHostArg: DockerHost,
|
||||
optionsArg: {
|
||||
creationObject: interfaces.IImageCreationDescriptor;
|
||||
tarStream: plugins.smartstream.stream.Readable;
|
||||
}
|
||||
): Promise<DockerImage> {
|
||||
// Start the request for importing an image
|
||||
const response = await dockerHostArg.requestStreaming(
|
||||
'POST',
|
||||
'/images/load',
|
||||
optionsArg.tarStream
|
||||
);
|
||||
|
||||
/**
|
||||
* Docker typically returns lines like:
|
||||
* {"stream":"Loaded image: myrepo/myimage:latest"}
|
||||
*
|
||||
* So we will collect those lines and parse out the final image name.
|
||||
*/
|
||||
let rawOutput = '';
|
||||
response.on('data', (chunk) => {
|
||||
rawOutput += chunk.toString();
|
||||
});
|
||||
|
||||
// Wrap the end event in a Promise for easier async/await usage
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
response.on('end', () => {
|
||||
resolve();
|
||||
});
|
||||
response.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
// Attempt to parse each line to find something like "Loaded image: ..."
|
||||
let loadedImageTag: string | undefined;
|
||||
const lines = rawOutput.trim().split('\n').filter(Boolean);
|
||||
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const jsonLine = JSON.parse(line);
|
||||
if (
|
||||
jsonLine.stream &&
|
||||
(jsonLine.stream.startsWith('Loaded image:') ||
|
||||
jsonLine.stream.startsWith('Loaded image ID:'))
|
||||
) {
|
||||
// Examples:
|
||||
// "Loaded image: your-image:latest"
|
||||
// "Loaded image ID: sha256:...."
|
||||
loadedImageTag = jsonLine.stream
|
||||
.replace('Loaded image: ', '')
|
||||
.replace('Loaded image ID: ', '')
|
||||
.trim();
|
||||
}
|
||||
} catch {
|
||||
// not valid JSON, ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (!loadedImageTag) {
|
||||
throw new Error(
|
||||
`Could not parse the loaded image info from Docker response.\nResponse was:\n${rawOutput}`
|
||||
);
|
||||
}
|
||||
|
||||
// Now try to look up that image by the "loadedImageTag".
|
||||
// Depending on Docker’s response, it might be something like:
|
||||
// "myrepo/myimage:latest" OR "sha256:someHash..."
|
||||
// If Docker gave you an ID (e.g. "sha256:..."), you may need a separate
|
||||
// DockerImage.getImageById method; or if you prefer, you can treat it as a name.
|
||||
const newlyImportedImage = await DockerImage.getImageByName(dockerHostArg, loadedImageTag);
|
||||
|
||||
if (!newlyImportedImage) {
|
||||
throw new Error(
|
||||
`Image load succeeded, but no local reference found for "${loadedImageTag}".`
|
||||
);
|
||||
}
|
||||
|
||||
logger.log(
|
||||
'info',
|
||||
`Successfully imported image "${loadedImageTag}".`
|
||||
);
|
||||
|
||||
return newlyImportedImage;
|
||||
}
|
||||
|
||||
|
||||
public static async tagImageByIdOrName(
|
||||
dockerHost: DockerHost,
|
||||
idOrNameArg: string,
|
||||
@@ -96,6 +179,8 @@ export class DockerImage {
|
||||
'POST',
|
||||
`/images/${encodeURIComponent(idOrNameArg)}/${encodeURIComponent(newTagArg)}`
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static async buildImage(dockerHostArg: DockerHost, dockerImageTag) {
|
||||
@@ -163,8 +248,14 @@ export class DockerImage {
|
||||
* exports an image to a tar ball
|
||||
*/
|
||||
public async exportToTarStream(): Promise<plugins.smartstream.stream.Readable> {
|
||||
console.log(`Exporting image ${this.RepoTags[0]} to tar stream.`);
|
||||
logger.log('info', `Exporting image ${this.RepoTags[0]} to tar stream.`);
|
||||
const response = await this.dockerHost.requestStreaming('GET', `/images/${encodeURIComponent(this.RepoTags[0])}/get`);
|
||||
|
||||
// Check if response is a Node.js stream
|
||||
if (!response || typeof response.on !== 'function') {
|
||||
throw new Error('Failed to get streaming response for image export');
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
const webduplexStream = new plugins.smartstream.SmartDuplex({
|
||||
writeFunction: async (chunk, tools) => {
|
||||
@@ -174,17 +265,25 @@ export class DockerImage {
|
||||
return chunk;
|
||||
}
|
||||
});
|
||||
|
||||
response.on('data', (chunk) => {
|
||||
if (!webduplexStream.write(chunk)) {
|
||||
response.pause();
|
||||
webduplexStream.once('drain', () => {
|
||||
response.resume();
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
response.on('end', () => {
|
||||
webduplexStream.end();
|
||||
})
|
||||
});
|
||||
|
||||
response.on('error', (error) => {
|
||||
logger.log('error', `Error during image export: ${error.message}`);
|
||||
webduplexStream.destroy(error);
|
||||
});
|
||||
|
||||
return webduplexStream;
|
||||
}
|
||||
}
|
||||
|
@@ -1,36 +1,109 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { logger } from './logger.js';
|
||||
import type { DockerHost } from './classes.host.js';
|
||||
|
||||
export interface IDockerImageStoreConstructorOptions {
|
||||
dirPath: string;
|
||||
/**
|
||||
* used for preparing images for longer term storage
|
||||
*/
|
||||
localDirPath: string;
|
||||
/**
|
||||
* a smartbucket dir for longer term storage.
|
||||
*/
|
||||
bucketDir: plugins.smartbucket.Directory;
|
||||
}
|
||||
|
||||
export class DockerImageStore {
|
||||
public options: IDockerImageStoreConstructorOptions;
|
||||
|
||||
constructor(dockerHost: DockerHost, optionsArg: IDockerImageStoreConstructorOptions) {
|
||||
constructor(optionsArg: IDockerImageStoreConstructorOptions) {
|
||||
this.options = optionsArg;
|
||||
}
|
||||
|
||||
// Method to store tar stream
|
||||
public async storeImage(imageName: string, tarStream: NodeJS.ReadableStream): Promise<void> {
|
||||
const imagePath = plugins.path.join(this.options.dirPath, `${imageName}.tar`);
|
||||
public async storeImage(imageName: string, tarStream: plugins.smartstream.stream.Readable): Promise<void> {
|
||||
logger.log('info', `Storing image ${imageName}...`);
|
||||
const uniqueProcessingId = plugins.smartunique.shortId();
|
||||
|
||||
const initialTarDownloadPath = plugins.path.join(this.options.localDirPath, `${uniqueProcessingId}.tar`);
|
||||
const extractionDir = plugins.path.join(this.options.localDirPath, uniqueProcessingId);
|
||||
// Create a write stream to store the tar file
|
||||
const writeStream = plugins.smartfile.fsStream.createWriteStream(imagePath);
|
||||
const writeStream = plugins.smartfile.fsStream.createWriteStream(initialTarDownloadPath);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// lets wait for the write stream to finish
|
||||
await new Promise((resolve, reject) => {
|
||||
tarStream.pipe(writeStream);
|
||||
|
||||
writeStream.on('finish', resolve);
|
||||
writeStream.on('error', reject);
|
||||
});
|
||||
logger.log('info', `Image ${imageName} stored locally for processing. Extracting...`);
|
||||
|
||||
// lets process the image
|
||||
const tarArchive = await plugins.smartarchive.SmartArchive.fromArchiveFile(initialTarDownloadPath);
|
||||
await tarArchive.exportToFs(extractionDir);
|
||||
logger.log('info', `Image ${imageName} extracted.`);
|
||||
await plugins.smartfile.fs.remove(initialTarDownloadPath);
|
||||
logger.log('info', `deleted original tar to save space.`);
|
||||
logger.log('info', `now repackaging for s3...`);
|
||||
const smartfileIndexJson = await plugins.smartfile.SmartFile.fromFilePath(plugins.path.join(extractionDir, 'index.json'));
|
||||
const smartfileManifestJson = await plugins.smartfile.SmartFile.fromFilePath(plugins.path.join(extractionDir, 'manifest.json'));
|
||||
const smartfileOciLayoutJson = await plugins.smartfile.SmartFile.fromFilePath(plugins.path.join(extractionDir, 'oci-layout'));
|
||||
const smartfileRepositoriesJson = await plugins.smartfile.SmartFile.fromFilePath(plugins.path.join(extractionDir, 'repositories'));
|
||||
const indexJson = JSON.parse(smartfileIndexJson.contents.toString());
|
||||
const manifestJson = JSON.parse(smartfileManifestJson.contents.toString());
|
||||
const ociLayoutJson = JSON.parse(smartfileOciLayoutJson.contents.toString());
|
||||
const repositoriesJson = JSON.parse(smartfileRepositoriesJson.contents.toString());
|
||||
|
||||
indexJson.manifests[0].annotations['io.containerd.image.name'] = imageName;
|
||||
manifestJson[0].RepoTags[0] = imageName;
|
||||
const repoFirstKey = Object.keys(repositoriesJson)[0];
|
||||
const repoFirstValue = repositoriesJson[repoFirstKey];
|
||||
repositoriesJson[imageName] = repoFirstValue;
|
||||
delete repositoriesJson[repoFirstKey];
|
||||
|
||||
smartfileIndexJson.contents = Buffer.from(JSON.stringify(indexJson, null, 2));
|
||||
smartfileManifestJson.contents = Buffer.from(JSON.stringify(manifestJson, null, 2));
|
||||
smartfileOciLayoutJson.contents = Buffer.from(JSON.stringify(ociLayoutJson, null, 2));
|
||||
smartfileRepositoriesJson.contents = Buffer.from(JSON.stringify(repositoriesJson, null, 2));
|
||||
await Promise.all([
|
||||
smartfileIndexJson.write(),
|
||||
smartfileManifestJson.write(),
|
||||
smartfileOciLayoutJson.write(),
|
||||
smartfileRepositoriesJson.write(),
|
||||
]);
|
||||
|
||||
logger.log('info', 'repackaging archive for s3...');
|
||||
const tartools = new plugins.smartarchive.TarTools();
|
||||
const newTarPack = await tartools.packDirectory(extractionDir);
|
||||
const finalTarName = `${uniqueProcessingId}.processed.tar`;
|
||||
const finalTarPath = plugins.path.join(this.options.localDirPath, finalTarName);
|
||||
const finalWriteStream = plugins.smartfile.fsStream.createWriteStream(finalTarPath);
|
||||
await new Promise((resolve, reject) => {
|
||||
newTarPack.finalize();
|
||||
newTarPack.pipe(finalWriteStream);
|
||||
finalWriteStream.on('finish', resolve);
|
||||
finalWriteStream.on('error', reject);
|
||||
});
|
||||
logger.log('ok', `Repackaged image ${imageName} for s3.`);
|
||||
await plugins.smartfile.fs.remove(extractionDir);
|
||||
const finalTarReadStream = plugins.smartfile.fsStream.createReadStream(finalTarPath);
|
||||
await this.options.bucketDir.fastPutStream({
|
||||
stream: finalTarReadStream,
|
||||
path: `${imageName}.tar`,
|
||||
});
|
||||
await plugins.smartfile.fs.remove(finalTarPath);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
await plugins.smartfile.fs.ensureEmptyDir(this.options.localDirPath);
|
||||
}
|
||||
|
||||
public async stop() {}
|
||||
|
||||
// Method to retrieve tar stream
|
||||
public async getImage(imageName: string): Promise<plugins.smartstream.stream.Readable> {
|
||||
const imagePath = plugins.path.join(this.options.dirPath, `${imageName}.tar`);
|
||||
const imagePath = plugins.path.join(this.options.localDirPath, `${imageName}.tar`);
|
||||
|
||||
if (!(await plugins.smartfile.fs.fileExists(imagePath))) {
|
||||
throw new Error(`Image ${imageName} does not exist.`);
|
||||
|
@@ -3,7 +3,7 @@ import * as interfaces from './interfaces/index.js';
|
||||
|
||||
import { DockerHost } from './classes.host.js';
|
||||
import { DockerService } from './classes.service.js';
|
||||
import { logger } from './logging.js';
|
||||
import { logger } from './logger.js';
|
||||
|
||||
export class DockerNetwork {
|
||||
public static async getNetworks(dockerHost: DockerHost): Promise<DockerNetwork[]> {
|
||||
|
@@ -4,7 +4,7 @@ import * as interfaces from './interfaces/index.js';
|
||||
import { DockerHost } from './classes.host.js';
|
||||
import { DockerImage } from './classes.image.js';
|
||||
import { DockerSecret } from './classes.secret.js';
|
||||
import { logger } from './logging.js';
|
||||
import { logger } from './logger.js';
|
||||
|
||||
export class DockerService {
|
||||
// STATIC
|
||||
@@ -89,6 +89,11 @@ export class DockerService {
|
||||
}> = [];
|
||||
|
||||
for (const network of serviceCreationDescriptor.networks) {
|
||||
// Skip null networks (can happen if network creation fails)
|
||||
if (!network) {
|
||||
logger.log('warn', 'Skipping null network in service creation');
|
||||
continue;
|
||||
}
|
||||
networkArray.push({
|
||||
Target: network.Name,
|
||||
Aliases: [serviceCreationDescriptor.networkAlias],
|
||||
|
5
ts/logger.ts
Normal file
5
ts/logger.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { commitinfo } from './00_commitinfo_data.js';
|
||||
|
||||
export const logger = plugins.smartlog.Smartlog.createForCommitinfo(commitinfo);
|
||||
logger.enableConsole();
|
@@ -1,3 +0,0 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export const logger = new plugins.smartlog.ConsoleLog();
|
@@ -5,6 +5,8 @@ export { path };
|
||||
|
||||
// @pushrocks scope
|
||||
import * as lik from '@push.rocks/lik';
|
||||
import * as smartarchive from '@push.rocks/smartarchive';
|
||||
import * as smartbucket from '@push.rocks/smartbucket';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
import * as smartjson from '@push.rocks/smartjson';
|
||||
import * as smartlog from '@push.rocks/smartlog';
|
||||
@@ -14,10 +16,13 @@ import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartrequest from '@push.rocks/smartrequest';
|
||||
import * as smartstring from '@push.rocks/smartstring';
|
||||
import * as smartstream from '@push.rocks/smartstream';
|
||||
import * as smartunique from '@push.rocks/smartunique';
|
||||
import * as smartversion from '@push.rocks/smartversion';
|
||||
|
||||
export {
|
||||
lik,
|
||||
smartarchive,
|
||||
smartbucket,
|
||||
smartfile,
|
||||
smartjson,
|
||||
smartlog,
|
||||
@@ -27,6 +32,7 @@ export {
|
||||
smartrequest,
|
||||
smartstring,
|
||||
smartstream,
|
||||
smartunique,
|
||||
smartversion,
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user