Compare commits

..

36 Commits

Author SHA1 Message Date
5b25704cf8 1.3.2 2025-08-18 21:52:41 +00:00
00e6033d8b fix(package.json): Fix test script timeout typo, update dependency versions, and add typings & project configs 2025-08-18 21:52:41 +00:00
453040983d 1.3.1 2025-08-18 21:47:31 +00:00
456858bc36 fix(test): Update test setup and devDependencies; adjust test import and add package metadata 2025-08-18 21:47:31 +00:00
606c82dafa 1.3.0 2024-12-23 00:30:20 +01:00
9fc4afe4b8 feat(core): Initial release of Docker client with TypeScript support 2024-12-23 00:30:20 +01:00
90689c2645 1.2.8 2024-12-23 00:30:00 +01:00
4a1d649e5e fix(core): Improved the image creation process from tar stream in DockerImage class. 2024-12-23 00:30:00 +01:00
66bd36dc4f 1.2.7 2024-10-13 13:29:19 +02:00
349d711cc5 fix(core): Prepare patch release with minor fixes and improvements 2024-10-13 13:29:18 +02:00
c74a4bcd5b 1.2.6 2024-10-13 13:23:41 +02:00
ff835c4160 fix(core): Minor refactoring and code quality improvements. 2024-10-13 13:23:40 +02:00
05eceeb056 1.2.5 2024-10-13 13:19:44 +02:00
de55beda08 fix(dependencies): Update dependencies for stability improvements 2024-10-13 13:19:43 +02:00
9aa2b0c7be 1.2.4 2024-10-13 13:14:36 +02:00
a283bbfba0 fix(core): Refactored DockerImageStore constructor to remove DockerHost dependency 2024-10-13 13:14:35 +02:00
8a4e300581 1.2.3 2024-08-21 16:04:43 +02:00
6b0d96b745 fix(dependencies): Update dependencies to the latest versions and fix image export test 2024-08-21 16:04:42 +02:00
a08c11838f 1.2.2 2024-06-10 00:15:10 +02:00
7c5225125c fix(core): update 2024-06-10 00:15:10 +02:00
bc4778f7db 1.2.1 2024-06-10 00:15:02 +02:00
2e7e8ae5cf fix(core): update 2024-06-10 00:15:01 +02:00
054585c7f5 1.2.0 2024-06-08 15:03:20 +02:00
c0cebbe614 feat(imagestore): now processing images with extraction, retagging, repackaging and long term storage 2024-06-08 15:03:19 +02:00
740f83114c 1.1.4 2024-06-06 00:32:51 +02:00
e48023d490 fix(core): update 2024-06-06 00:32:50 +02:00
eaaf313442 1.1.3 2024-06-06 00:25:40 +02:00
68b2baadae fix(core): update 2024-06-06 00:25:39 +02:00
6743dc35e7 1.1.2 2024-06-06 00:14:31 +02:00
bbf265716d fix(core): update 2024-06-06 00:14:30 +02:00
3a705534fe 1.1.1 2024-06-05 23:56:03 +02:00
cbdbd32dd1 fix(core): update 2024-06-05 23:56:02 +02:00
224004217c 1.1.0 2024-06-05 14:10:45 +02:00
e06ef454a6 feat(images): can now import and export images, start work on local 100% JS oci imageregistry 2024-06-05 14:10:44 +02:00
a5f4d93f50 1.0.112 2024-05-08 20:35:17 +02:00
9f5d2cacf1 fix(core): update 2024-05-08 20:35:16 +02:00
24 changed files with 8607 additions and 3803 deletions

Binary file not shown.

68
.serena/project.yml Normal file
View 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"

182
changelog.md Normal file
View File

@@ -0,0 +1,182 @@
# Changelog
## 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

View File

@@ -1,13 +1,13 @@
{
"name": "@apiclient.xyz/docker",
"version": "1.0.111",
"version": "1.3.2",
"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 120)",
"build": "(tsbuild --web --allowimplicitany)",
"buildDocs": "tsdoc"
},
@@ -33,25 +33,29 @@
},
"homepage": "https://gitlab.com/mojoio/docker#readme",
"dependencies": {
"@push.rocks/lik": "^6.0.15",
"@push.rocks/smartfile": "^11.0.14",
"@push.rocks/smartjson": "^5.0.19",
"@push.rocks/smartlog": "^3.0.1",
"@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.1.8",
"@push.rocks/smartnetwork": "^3.0.0",
"@push.rocks/smartpath": "^5.0.18",
"@push.rocks/smartpromise": "^4.0.3",
"@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrequest": "^2.0.22",
"@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": "^4.1.2",
"rxjs": "^7.8.2"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.1.25",
"@git.zone/tsrun": "^1.2.12",
"@git.zone/tstest": "^1.0.90",
"@push.rocks/tapbundle": "^5.0.23",
"@types/node": "^20.12.11"
"@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/**/*",
@@ -67,5 +71,6 @@
],
"browserslist": [
"last 1 chrome versions"
]
],
"packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748"
}

10719
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

6
qenv.yml Normal file
View File

@@ -0,0 +1,6 @@
required:
- S3_ENDPOINT
- S3_ACCESSKEY
- S3_ACCESSSECRET
- S3_BUCKET

565
readme.md
View File

@@ -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();
```
#### Create and Remove a Container
```typescript
import { IContainerCreationDescriptor } from '@apiclient.xyz/docker';
async function createAndRemoveContainer() {
const containerDescriptor: IContainerCreationDescriptor = {
Hostname: 'test-container',
Domainname: '',
// Additional settings here
};
// Create container
const container = await DockerContainer.create(localDockerHost, containerDescriptor);
console.log(`Container Created: ${container.Id}`);
// Remove container
await container.remove();
console.log(`Container Removed: ${container.Id}`);
}
createAndRemoveContainer();
```
### Working with Docker Services
#### Create a Docker Service
```typescript
import { IServiceCreationDescriptor } from '@apiclient.xyz/docker';
async function createDockerService() {
const serviceDescriptor: IServiceCreationDescriptor = {
name: 'my-service',
image: 'nginx:latest', // Docker Image
// Additional settings
};
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
// 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}`);
});
console.log(`Network Created: ${network.Id}`);
}
listAndCreateNetwork();
```
### Advanced Usage
#### Create and Manage Containers
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.
```typescript
import { DockerContainer } from '@apiclient.xyz/docker';
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.
// 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'
}
});
### Conclusion
console.log(`Container created: ${container.Id}`);
`@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.
// Container operations (these would need to be implemented)
// await container.start();
// await container.stop();
// await container.remove();
```
#### Get Container by ID
```typescript
const container = await DockerContainer.getContainerById(docker, 'container-id-here');
if (container) {
console.log(`Found container: ${container.Names[0]}`);
}
```
### 🖼️ 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();
// Create network
const network = await DockerNetwork.createNetwork(docker, {
Name: 'app-network',
Driver: 'overlay' // for swarm mode
});
// 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!');
}
```
## 🔍 TypeScript Support
This package provides comprehensive TypeScript definitions for all Docker API entities:
```typescript
import type {
IContainerCreationDescriptor,
IServiceCreationDescriptor,
INetworkCreationDescriptor,
IImageCreationDescriptor,
ISecretCreationDescriptor
} from '@apiclient.xyz/docker';
// Full IntelliSense support for all configuration options
const containerConfig: IContainerCreationDescriptor = {
Image: 'node:18',
// Your IDE will provide full autocomplete here
};
```
## 🤝 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

View File

@@ -1,10 +1,18 @@
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';
import * as docker from '../ts/index.js';
let testDockerHost: docker.DockerHost;
tap.test('should create a new Dockersock instance', async () => {
testDockerHost = new docker.DockerHost();
testDockerHost = new docker.DockerHost({});
await testDockerHost.start();
return expect(testDockerHost).toBeInstanceOf(docker.DockerHost);
});
@@ -40,8 +48,10 @@ tap.test('should remove a network', async () => {
// Images
tap.test('should pull an image from imagetag', async () => {
const image = await docker.DockerImage.createFromRegistry(testDockerHost, {
creationObject: {
imageUrl: 'hosttoday/ht-docker-node',
imageTag: 'alpine',
},
});
expect(image).toBeInstanceOf(docker.DockerImage);
console.log(image);
@@ -93,7 +103,9 @@ tap.test('should create a service', async () => {
contentArg: '{"hi": "wow"}',
});
const testImage = await docker.DockerImage.createFromRegistry(testDockerHost, {
imageUrl: 'registry.gitlab.com/hosttoday/ht-docker-static',
creationObject: {
imageUrl: 'code.foss.global/host.today/ht-docker-node:latest',
}
});
const testService = await docker.DockerService.createService(testDockerHost, {
image: testImage,
@@ -110,4 +122,48 @@ tap.test('should create a service', async () => {
await testSecret.remove();
});
tap.start();
tap.test('should export images', async (toolsArg) => {
const done = toolsArg.defer();
const testImage = await docker.DockerImage.createFromRegistry(testDockerHost, {
creationObject: {
imageUrl: 'code.foss.global/host.today/ht-docker-node:latest',
}
});
const fsWriteStream = plugins.smartfile.fsStream.createWriteStream(
plugins.path.join(paths.nogitDir, 'testimage.tar')
);
const exportStream = await testImage.exportToTarStream();
exportStream.pipe(fsWriteStream).on('finish', () => {
done.resolve();
});
await done.promise;
});
tap.test('should import images', async (toolsArg) => {
const done = toolsArg.defer();
const fsReadStream = plugins.smartfile.fsStream.createReadStream(
plugins.path.join(paths.nogitDir, 'testimage.tar')
);
await docker.DockerImage.createFromTarStream(testDockerHost, {
tarStream: fsReadStream,
creationObject: {
imageUrl: 'code.foss.global/host.today/ht-docker-node:latest',
}
})
});
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('hello', plugins.smartfile.fsStream.createReadStream(plugins.path.join(paths.nogitDir, 'testimage.tar')));
})
export default tap.start();

View File

@@ -1,8 +1,8 @@
/**
* autocreated commitinfo by @pushrocks/commitinfo
* autocreated commitinfo by @push.rocks/commitinfo
*/
export const commitinfo = {
name: '@apiclient.xyz/docker',
version: '1.0.111',
version: '1.3.2',
description: 'Provides easy communication with Docker remote API from Node.js, with TypeScript support.'
}

View File

@@ -1,8 +1,8 @@
import * as plugins from './docker.plugins.js';
import * as plugins from './plugins.js';
import * as interfaces from './interfaces/index.js';
import { DockerHost } from './docker.classes.host.js';
import { logger } from './docker.logging.js';
import { DockerHost } from './classes.host.js';
import { logger } from './logger.js';
export class DockerContainer {
// STATIC

View File

@@ -1,9 +1,12 @@
import * as plugins from './docker.plugins.js';
import { DockerContainer } from './docker.classes.container.js';
import { DockerNetwork } from './docker.classes.network.js';
import { DockerService } from './docker.classes.service.js';
import { logger } from './docker.logging.js';
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 './logger.js';
import path from 'path';
import { DockerImageStore } from './classes.imagestore.js';
import { DockerImage } from './classes.image.js';
export interface IAuthData {
serveraddress: string;
@@ -11,21 +14,36 @@ export interface IAuthData {
password: string;
}
export interface IDockerHostConstructorOptions {
dockerSockPath?: string;
imageStoreDir?: string;
}
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(pathArg?: string) {
constructor(optionsArg: IDockerHostConstructorOptions) {
this.options = {
...{
imageStoreDir: plugins.path.join(paths.nogitDir, 'temp-docker-image-store'),
},
...optionsArg,
}
let pathToUse: string;
if (pathArg) {
pathToUse = pathArg;
if (optionsArg.dockerSockPath) {
pathToUse = optionsArg.dockerSockPath;
} else if (process.env.DOCKER_HOST) {
pathToUse = process.env.DOCKER_HOST;
} else if (process.env.CI) {
@@ -41,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();
}
/**
@@ -70,10 +99,13 @@ export class DockerHost {
await this.auth({
username: gitlabAuthArray[0],
password: gitlabAuthArray[1],
serveraddress: 'registry.gitlab.com',
serveraddress: registryUrlArg,
});
}
// ==============
// NETWORKS
// ==============
/**
* gets all networks
*/
@@ -82,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
*/
@@ -93,6 +139,10 @@ export class DockerHost {
return containerArray;
}
// ==============
// SERVICES
// ==============
/**
* gets all services
*/
@@ -101,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);
}
/**
*
*/
@@ -174,7 +242,7 @@ export class DockerHost {
return response;
}
public async requestStreaming(methodArg: string, routeArg: string, dataArg = {}) {
public async requestStreaming(methodArg: string, routeArg: string, readStream?: plugins.smartstream.stream.Readable) {
const requestUrl = `${this.socketPath}${routeArg}`;
const response = await plugins.smartrequest.request(
requestUrl,
@@ -188,10 +256,40 @@ export class DockerHost {
requestBody: null,
keepAlive: false,
},
true
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;
}
});
readStream.pipe(smartduplex).pipe(reqArg);
} : null),
);
console.log(response.statusCode);
console.log(response.body);
return response;
}
/**
* 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;
}
}

275
ts/classes.image.ts Normal file
View File

@@ -0,0 +1,275 @@
import * as plugins from './plugins.js';
import * as interfaces from './interfaces/index.js';
import { DockerHost } from './classes.host.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) {
const images: DockerImage[] = [];
const response = await dockerHost.request('GET', '/images/json');
for (const imageObject of response.body) {
images.push(new DockerImage(dockerHost, imageObject));
}
return images;
}
public static async getImageByName(dockerHost: DockerHost, imageNameArg: string) {
const images = await this.getImages(dockerHost);
const result = images.find((image) => {
if (image.RepoTags) {
return image.RepoTags.includes(imageNameArg);
} else {
return false;
}
});
return result;
}
public static async createFromRegistry(
dockerHostArg: DockerHost,
optionsArg: {
creationObject: interfaces.IImageCreationDescriptor
}
): Promise<DockerImage> {
// lets create a sanatized imageUrlObject
const imageUrlObject: {
imageUrl: string;
imageTag: string;
imageOriginTag: string;
} = {
imageUrl: optionsArg.creationObject.imageUrl,
imageTag: optionsArg.creationObject.imageTag,
imageOriginTag: null,
};
if (imageUrlObject.imageUrl.includes(':')) {
const imageUrl = imageUrlObject.imageUrl.split(':')[0];
const imageTag = imageUrlObject.imageUrl.split(':')[1];
if (imageUrlObject.imageTag) {
throw new Error(
`imageUrl ${imageUrlObject.imageUrl} can't be tagged with ${imageUrlObject.imageTag} because it is already tagged with ${imageTag}`
);
} else {
imageUrlObject.imageUrl = imageUrl;
imageUrlObject.imageTag = imageTag;
}
} else if (!imageUrlObject.imageTag) {
imageUrlObject.imageTag = 'latest';
}
imageUrlObject.imageOriginTag = `${imageUrlObject.imageUrl}:${imageUrlObject.imageTag}`;
// lets actually create the image
const response = await dockerHostArg.request(
'POST',
`/images/create?fromImage=${encodeURIComponent(
imageUrlObject.imageUrl
)}&tag=${encodeURIComponent(imageUrlObject.imageTag)}`
);
if (response.statusCode < 300) {
logger.log('info', `Successfully pulled image ${imageUrlObject.imageUrl} from the registry`);
const image = await DockerImage.getImageByName(dockerHostArg, imageUrlObject.imageOriginTag);
return image;
} else {
logger.log('error', `Failed at the attempt of creating a new image`);
}
}
/**
*
* @param dockerHostArg
* @param tarStreamArg
*/
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 Dockers 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,
newTagArg: string
) {
const response = await dockerHost.request(
'POST',
`/images/${encodeURIComponent(idOrNameArg)}/${encodeURIComponent(newTagArg)}`
);
}
public static async buildImage(dockerHostArg: DockerHost, dockerImageTag) {
// TODO: implement building an image
}
// INSTANCE
// references
public dockerHost: DockerHost;
// properties
/**
* the tags for an image
*/
public Containers: number;
public Created: number;
public Id: string;
public Labels: interfaces.TLabels;
public ParentId: string;
public RepoDigests: string[];
public RepoTags: string[];
public SharedSize: number;
public Size: number;
public VirtualSize: number;
constructor(dockerHostArg, dockerImageObjectArg: any) {
this.dockerHost = dockerHostArg;
Object.keys(dockerImageObjectArg).forEach((keyArg) => {
this[keyArg] = dockerImageObjectArg[keyArg];
});
}
/**
* tag an image
* @param newTag
*/
public async tagImage(newTag) {
throw new Error('.tagImage is not yet implemented');
}
/**
* pulls the latest version from the registry
*/
public async pullLatestImageFromRegistry(): Promise<boolean> {
const updatedImage = await DockerImage.createFromRegistry(this.dockerHost, {
creationObject: {
imageUrl: this.RepoTags[0],
},
});
Object.assign(this, updatedImage);
// TODO: Compare image digists before and after
return true;
}
// get stuff
public async getVersion() {
if (this.Labels && this.Labels.version) {
return this.Labels.version;
} else {
return '0.0.0';
}
}
/**
* exports an image to a tar ball
*/
public async exportToTarStream(): Promise<plugins.smartstream.stream.Readable> {
logger.log('info', `Exporting image ${this.RepoTags[0]} to tar stream.`);
const response = await this.dockerHost.requestStreaming('GET', `/images/${encodeURIComponent(this.RepoTags[0])}/get`);
let counter = 0;
const webduplexStream = new plugins.smartstream.SmartDuplex({
writeFunction: async (chunk, tools) => {
if (counter % 1000 === 0)
console.log(`Got chunk: ${counter}`);
counter++;
return chunk;
}
});
response.on('data', (chunk) => {
if (!webduplexStream.write(chunk)) {
response.pause();
webduplexStream.once('drain', () => {
response.resume();
})
};
});
response.on('end', () => {
webduplexStream.end();
})
return webduplexStream;
}
}

114
ts/classes.imagestore.ts Normal file
View File

@@ -0,0 +1,114 @@
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 {
/**
* 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(optionsArg: IDockerImageStoreConstructorOptions) {
this.options = optionsArg;
}
// Method to store tar stream
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(initialTarDownloadPath);
// 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.localDirPath, `${imageName}.tar`);
if (!(await plugins.smartfile.fs.fileExists(imagePath))) {
throw new Error(`Image ${imageName} does not exist.`);
}
return plugins.smartfile.fsStream.createReadStream(imagePath);
}
}

View File

@@ -1,9 +1,9 @@
import * as plugins from './docker.plugins.js';
import * as plugins from './plugins.js';
import * as interfaces from './interfaces/index.js';
import { DockerHost } from './docker.classes.host.js';
import { DockerService } from './docker.classes.service.js';
import { logger } from './docker.logging.js';
import { DockerHost } from './classes.host.js';
import { DockerService } from './classes.service.js';
import { logger } from './logger.js';
export class DockerNetwork {
public static async getNetworks(dockerHost: DockerHost): Promise<DockerNetwork[]> {

View File

@@ -1,5 +1,5 @@
import * as plugins from './docker.plugins.js';
import { DockerHost } from './docker.classes.host.js';
import * as plugins from './plugins.js';
import { DockerHost } from './classes.host.js';
// interfaces
import * as interfaces from './interfaces/index.js';

View File

@@ -1,10 +1,10 @@
import * as plugins from './docker.plugins.js';
import * as plugins from './plugins.js';
import * as interfaces from './interfaces/index.js';
import { DockerHost } from './docker.classes.host.js';
import { DockerImage } from './docker.classes.image.js';
import { DockerSecret } from './docker.classes.secret.js';
import { logger } from './docker.logging.js';
import { DockerHost } from './classes.host.js';
import { DockerImage } from './classes.image.js';
import { DockerSecret } from './classes.secret.js';
import { logger } from './logger.js';
export class DockerService {
// STATIC
@@ -232,7 +232,9 @@ export class DockerService {
await this.reReadFromDockerEngine();
const dockerImage = await DockerImage.createFromRegistry(this.dockerHostRef, {
creationObject: {
imageUrl: this.Spec.TaskTemplate.ContainerSpec.Image,
}
});
const imageVersion = new plugins.smartversion.SmartVersion(dockerImage.Labels.version);

View File

@@ -1,144 +0,0 @@
import * as plugins from './docker.plugins.js';
import * as interfaces from './interfaces/index.js';
import { DockerHost } from './docker.classes.host.js';
import { logger } from './docker.logging.js';
export class DockerImage {
// STATIC
public static async getImages(dockerHost: DockerHost) {
const images: DockerImage[] = [];
const response = await dockerHost.request('GET', '/images/json');
for (const imageObject of response.body) {
images.push(new DockerImage(dockerHost, imageObject));
}
return images;
}
public static async findImageByName(dockerHost: DockerHost, imageNameArg: string) {
const images = await this.getImages(dockerHost);
const result = images.find((image) => {
if (image.RepoTags) {
return image.RepoTags.includes(imageNameArg);
} else {
return false;
}
});
return result;
}
public static async createFromRegistry(
dockerHostArg: DockerHost,
creationObject: interfaces.IImageCreationDescriptor
): Promise<DockerImage> {
// lets create a sanatized imageUrlObject
const imageUrlObject: {
imageUrl: string;
imageTag: string;
imageOriginTag: string;
} = {
imageUrl: creationObject.imageUrl,
imageTag: creationObject.imageTag,
imageOriginTag: null,
};
if (imageUrlObject.imageUrl.includes(':')) {
const imageUrl = imageUrlObject.imageUrl.split(':')[0];
const imageTag = imageUrlObject.imageUrl.split(':')[1];
if (imageUrlObject.imageTag) {
throw new Error(
`imageUrl ${imageUrlObject.imageUrl} can't be tagged with ${imageUrlObject.imageTag} because it is already tagged with ${imageTag}`
);
} else {
imageUrlObject.imageUrl = imageUrl;
imageUrlObject.imageTag = imageTag;
}
} else if (!imageUrlObject.imageTag) {
imageUrlObject.imageTag = 'latest';
}
imageUrlObject.imageOriginTag = `${imageUrlObject.imageUrl}:${imageUrlObject.imageTag}`;
// lets actually create the image
const response = await dockerHostArg.request(
'POST',
`/images/create?fromImage=${encodeURIComponent(
imageUrlObject.imageUrl
)}&tag=${encodeURIComponent(imageUrlObject.imageTag)}`
);
if (response.statusCode < 300) {
logger.log('info', `Successfully pulled image ${imageUrlObject.imageUrl} from the registry`);
const image = await DockerImage.findImageByName(dockerHostArg, imageUrlObject.imageOriginTag);
return image;
} else {
logger.log('error', `Failed at the attempt of creating a new image`);
}
}
public static async tagImageByIdOrName(
dockerHost: DockerHost,
idOrNameArg: string,
newTagArg: string
) {
const response = await dockerHost.request(
'POST',
`/images/${encodeURIComponent(idOrNameArg)}/${encodeURIComponent(newTagArg)}`
);
}
public static async buildImage(dockerHostArg: DockerHost, dockerImageTag) {
// TODO: implement building an image
}
// INSTANCE
// references
public dockerHost: DockerHost;
// properties
/**
* the tags for an image
*/
public Containers: number;
public Created: number;
public Id: string;
public Labels: interfaces.TLabels;
public ParentId: string;
public RepoDigests: string[];
public RepoTags: string[];
public SharedSize: number;
public Size: number;
public VirtualSize: number;
constructor(dockerHostArg, dockerImageObjectArg: any) {
this.dockerHost = dockerHostArg;
Object.keys(dockerImageObjectArg).forEach((keyArg) => {
this[keyArg] = dockerImageObjectArg[keyArg];
});
}
/**
* tag an image
* @param newTag
*/
public async tagImage(newTag) {
throw new Error('.tagImage is not yet implemented');
}
/**
* pulls the latest version from the registry
*/
public async pullLatestImageFromRegistry(): Promise<boolean> {
const updatedImage = await DockerImage.createFromRegistry(this.dockerHost, {
imageUrl: this.RepoTags[0],
});
Object.assign(this, updatedImage);
// TODO: Compare image digists before and after
return true;
}
// get stuff
public async getVersion() {
if (this.Labels && this.Labels.version) {
return this.Labels.version;
} else {
return '0.0.0';
}
}
}

View File

@@ -1,3 +0,0 @@
import * as plugins from './docker.plugins.js';
export const logger = new plugins.smartlog.ConsoleLog();

View File

@@ -1,6 +1,7 @@
export * from './docker.classes.host.js';
export * from './docker.classes.container.js';
export * from './docker.classes.image.js';
export * from './docker.classes.network.js';
export * from './docker.classes.secret.js';
export * from './docker.classes.service.js';
export * from './classes.host.js';
export * from './classes.container.js';
export * from './classes.image.js';
export * from './classes.imagestore.js';
export * from './classes.network.js';
export * from './classes.secret.js';
export * from './classes.service.js';

View File

@@ -1,4 +1,4 @@
import { DockerNetwork } from '../docker.classes.network.js';
import { DockerNetwork } from '../classes.network.js';
export interface IContainerCreationDescriptor {
Hostname: string;

View File

@@ -1,9 +1,9 @@
import * as plugins from '../docker.plugins.js';
import * as plugins from '../plugins.js';
import * as interfaces from './index.js';
import { DockerNetwork } from '../docker.classes.network.js';
import { DockerSecret } from '../docker.classes.secret.js';
import { DockerImage } from '../docker.classes.image.js';
import { DockerNetwork } from '../classes.network.js';
import { DockerSecret } from '../classes.secret.js';
import { DockerImage } from '../classes.image.js';
export interface IServiceCreationDescriptor {
name: string;

5
ts/logger.ts Normal file
View 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();

9
ts/paths.ts Normal file
View File

@@ -0,0 +1,9 @@
import * as plugins from './plugins.js';
export const packageDir = plugins.path.resolve(
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
'../'
);
export const nogitDir = plugins.path.resolve(packageDir, '.nogit/');
plugins.smartfile.fs.ensureDir(nogitDir);

View File

@@ -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';
@@ -13,10 +15,14 @@ import * as smartpath from '@push.rocks/smartpath';
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,
@@ -25,6 +31,8 @@ export {
smartpromise,
smartrequest,
smartstring,
smartstream,
smartunique,
smartversion,
};