@git.zone/tsdocker 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dockerfile.d.ts +85 -0
- package/dist_ts/classes.dockerfile.js +366 -0
- package/dist_ts/classes.dockerregistry.d.ts +29 -0
- package/dist_ts/classes.dockerregistry.js +83 -0
- package/dist_ts/classes.registrystorage.d.ts +35 -0
- package/dist_ts/classes.registrystorage.js +76 -0
- package/dist_ts/classes.tsdockermanager.d.ts +53 -0
- package/dist_ts/classes.tsdockermanager.js +222 -0
- package/dist_ts/interfaces/index.d.ts +68 -0
- package/dist_ts/interfaces/index.js +2 -0
- package/dist_ts/tsdocker.cli.js +115 -8
- package/dist_ts/tsdocker.config.d.ts +3 -8
- package/dist_ts/tsdocker.config.js +10 -2
- package/dist_ts/tsdocker.plugins.d.ts +2 -1
- package/dist_ts/tsdocker.plugins.js +3 -2
- package/package.json +2 -2
- package/readme.hints.md +95 -26
- package/readme.md +0 -8
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dockerfile.ts +462 -0
- package/ts/classes.dockerregistry.ts +91 -0
- package/ts/classes.registrystorage.ts +83 -0
- package/ts/classes.tsdockermanager.ts +254 -0
- package/ts/interfaces/index.ts +70 -0
- package/ts/tsdocker.cli.ts +123 -10
- package/ts/tsdocker.config.ts +14 -7
- package/ts/tsdocker.plugins.ts +2 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as plugins from './tsdocker.plugins.js';
|
|
2
|
+
import { logger } from './tsdocker.logging.js';
|
|
3
|
+
import type { IDockerRegistryOptions } from './interfaces/index.js';
|
|
4
|
+
|
|
5
|
+
const smartshellInstance = new plugins.smartshell.Smartshell({
|
|
6
|
+
executor: 'bash',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Represents a Docker registry with authentication capabilities
|
|
11
|
+
*/
|
|
12
|
+
export class DockerRegistry {
|
|
13
|
+
public registryUrl: string;
|
|
14
|
+
public username: string;
|
|
15
|
+
public password: string;
|
|
16
|
+
|
|
17
|
+
constructor(optionsArg: IDockerRegistryOptions) {
|
|
18
|
+
this.registryUrl = optionsArg.registryUrl;
|
|
19
|
+
this.username = optionsArg.username;
|
|
20
|
+
this.password = optionsArg.password;
|
|
21
|
+
logger.log('info', `created DockerRegistry for ${this.registryUrl}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates a DockerRegistry instance from a pipe-delimited environment string
|
|
26
|
+
* Format: "registryUrl|username|password"
|
|
27
|
+
*/
|
|
28
|
+
public static fromEnvString(envString: string): DockerRegistry {
|
|
29
|
+
const dockerRegexResultArray = envString.split('|');
|
|
30
|
+
if (dockerRegexResultArray.length !== 3) {
|
|
31
|
+
logger.log('error', 'malformed docker env var...');
|
|
32
|
+
throw new Error('malformed docker env var, expected format: registryUrl|username|password');
|
|
33
|
+
}
|
|
34
|
+
const registryUrl = dockerRegexResultArray[0].replace('https://', '').replace('http://', '');
|
|
35
|
+
const username = dockerRegexResultArray[1];
|
|
36
|
+
const password = dockerRegexResultArray[2];
|
|
37
|
+
return new DockerRegistry({
|
|
38
|
+
registryUrl: registryUrl,
|
|
39
|
+
username: username,
|
|
40
|
+
password: password,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a DockerRegistry from environment variables
|
|
46
|
+
* Looks for DOCKER_REGISTRY, DOCKER_REGISTRY_USER, DOCKER_REGISTRY_PASSWORD
|
|
47
|
+
* Or for a specific registry: DOCKER_REGISTRY_<NAME>, etc.
|
|
48
|
+
*/
|
|
49
|
+
public static fromEnv(registryName?: string): DockerRegistry | null {
|
|
50
|
+
const prefix = registryName ? `DOCKER_REGISTRY_${registryName.toUpperCase()}_` : 'DOCKER_REGISTRY_';
|
|
51
|
+
|
|
52
|
+
const registryUrl = process.env[`${prefix}URL`] || process.env['DOCKER_REGISTRY'];
|
|
53
|
+
const username = process.env[`${prefix}USER`] || process.env['DOCKER_REGISTRY_USER'];
|
|
54
|
+
const password = process.env[`${prefix}PASSWORD`] || process.env['DOCKER_REGISTRY_PASSWORD'];
|
|
55
|
+
|
|
56
|
+
if (!registryUrl || !username || !password) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return new DockerRegistry({
|
|
61
|
+
registryUrl: registryUrl.replace('https://', '').replace('http://', ''),
|
|
62
|
+
username,
|
|
63
|
+
password,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Logs in to the Docker registry
|
|
69
|
+
*/
|
|
70
|
+
public async login(): Promise<void> {
|
|
71
|
+
if (this.registryUrl === 'docker.io') {
|
|
72
|
+
await smartshellInstance.exec(`docker login -u ${this.username} -p ${this.password}`);
|
|
73
|
+
logger.log('info', 'Logged in to standard docker hub');
|
|
74
|
+
} else {
|
|
75
|
+
await smartshellInstance.exec(`docker login -u ${this.username} -p ${this.password} ${this.registryUrl}`);
|
|
76
|
+
}
|
|
77
|
+
logger.log('ok', `docker authenticated for ${this.registryUrl}!`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Logs out from the Docker registry
|
|
82
|
+
*/
|
|
83
|
+
public async logout(): Promise<void> {
|
|
84
|
+
if (this.registryUrl === 'docker.io') {
|
|
85
|
+
await smartshellInstance.exec('docker logout');
|
|
86
|
+
} else {
|
|
87
|
+
await smartshellInstance.exec(`docker logout ${this.registryUrl}`);
|
|
88
|
+
}
|
|
89
|
+
logger.log('info', `logged out from ${this.registryUrl}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as plugins from './tsdocker.plugins.js';
|
|
2
|
+
import { logger } from './tsdocker.logging.js';
|
|
3
|
+
import { DockerRegistry } from './classes.dockerregistry.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Storage class for managing multiple Docker registries
|
|
7
|
+
*/
|
|
8
|
+
export class RegistryStorage {
|
|
9
|
+
public objectMap = new plugins.lik.ObjectMap<DockerRegistry>();
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
// Nothing here
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Adds a registry to the storage
|
|
17
|
+
*/
|
|
18
|
+
public addRegistry(registryArg: DockerRegistry): void {
|
|
19
|
+
this.objectMap.add(registryArg);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Gets a registry by its URL
|
|
24
|
+
*/
|
|
25
|
+
public getRegistryByUrl(registryUrlArg: string): DockerRegistry | undefined {
|
|
26
|
+
return this.objectMap.findSync((registryArg) => {
|
|
27
|
+
return registryArg.registryUrl === registryUrlArg;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Gets all registries
|
|
33
|
+
*/
|
|
34
|
+
public getAllRegistries(): DockerRegistry[] {
|
|
35
|
+
return this.objectMap.getArray();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Logs in to all registries
|
|
40
|
+
*/
|
|
41
|
+
public async loginAll(): Promise<void> {
|
|
42
|
+
await this.objectMap.forEach(async (registryArg) => {
|
|
43
|
+
await registryArg.login();
|
|
44
|
+
});
|
|
45
|
+
logger.log('success', 'logged in successfully into all available DockerRegistries!');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Logs out from all registries
|
|
50
|
+
*/
|
|
51
|
+
public async logoutAll(): Promise<void> {
|
|
52
|
+
await this.objectMap.forEach(async (registryArg) => {
|
|
53
|
+
await registryArg.logout();
|
|
54
|
+
});
|
|
55
|
+
logger.log('info', 'logged out from all DockerRegistries');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Loads registries from environment variables
|
|
60
|
+
* Looks for DOCKER_REGISTRY_1, DOCKER_REGISTRY_2, etc. (pipe-delimited format)
|
|
61
|
+
* Or individual registries like DOCKER_REGISTRY_GITLAB_URL, etc.
|
|
62
|
+
*/
|
|
63
|
+
public loadFromEnv(): void {
|
|
64
|
+
// Check for numbered registry env vars (pipe-delimited format)
|
|
65
|
+
for (let i = 1; i <= 10; i++) {
|
|
66
|
+
const envVar = process.env[`DOCKER_REGISTRY_${i}`];
|
|
67
|
+
if (envVar) {
|
|
68
|
+
try {
|
|
69
|
+
const registry = DockerRegistry.fromEnvString(envVar);
|
|
70
|
+
this.addRegistry(registry);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
logger.log('warn', `Failed to parse DOCKER_REGISTRY_${i}: ${(err as Error).message}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check for default registry
|
|
78
|
+
const defaultRegistry = DockerRegistry.fromEnv();
|
|
79
|
+
if (defaultRegistry) {
|
|
80
|
+
this.addRegistry(defaultRegistry);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import * as plugins from './tsdocker.plugins.js';
|
|
2
|
+
import * as paths from './tsdocker.paths.js';
|
|
3
|
+
import { logger } from './tsdocker.logging.js';
|
|
4
|
+
import { Dockerfile } from './classes.dockerfile.js';
|
|
5
|
+
import { DockerRegistry } from './classes.dockerregistry.js';
|
|
6
|
+
import { RegistryStorage } from './classes.registrystorage.js';
|
|
7
|
+
import type { ITsDockerConfig } from './interfaces/index.js';
|
|
8
|
+
|
|
9
|
+
const smartshellInstance = new plugins.smartshell.Smartshell({
|
|
10
|
+
executor: 'bash',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Main orchestrator class for Docker operations
|
|
15
|
+
*/
|
|
16
|
+
export class TsDockerManager {
|
|
17
|
+
public registryStorage: RegistryStorage;
|
|
18
|
+
public config: ITsDockerConfig;
|
|
19
|
+
public projectInfo: any;
|
|
20
|
+
private dockerfiles: Dockerfile[] = [];
|
|
21
|
+
|
|
22
|
+
constructor(config: ITsDockerConfig) {
|
|
23
|
+
this.config = config;
|
|
24
|
+
this.registryStorage = new RegistryStorage();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Prepares the manager by loading project info and registries
|
|
29
|
+
*/
|
|
30
|
+
public async prepare(): Promise<void> {
|
|
31
|
+
// Load project info
|
|
32
|
+
try {
|
|
33
|
+
const projectinfoInstance = new plugins.projectinfo.ProjectInfo(paths.cwd);
|
|
34
|
+
this.projectInfo = {
|
|
35
|
+
npm: {
|
|
36
|
+
name: projectinfoInstance.npm.name,
|
|
37
|
+
version: projectinfoInstance.npm.version,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
} catch (err) {
|
|
41
|
+
logger.log('warn', 'Could not load project info');
|
|
42
|
+
this.projectInfo = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Load registries from environment
|
|
46
|
+
this.registryStorage.loadFromEnv();
|
|
47
|
+
|
|
48
|
+
// Add registries from config if specified
|
|
49
|
+
if (this.config.registries) {
|
|
50
|
+
for (const registryUrl of this.config.registries) {
|
|
51
|
+
// Check if already loaded from env
|
|
52
|
+
if (!this.registryStorage.getRegistryByUrl(registryUrl)) {
|
|
53
|
+
// Try to load credentials for this registry from env
|
|
54
|
+
const envVarName = registryUrl.replace(/\./g, '_').toUpperCase();
|
|
55
|
+
const envString = process.env[`DOCKER_REGISTRY_${envVarName}`];
|
|
56
|
+
if (envString) {
|
|
57
|
+
try {
|
|
58
|
+
const registry = DockerRegistry.fromEnvString(envString);
|
|
59
|
+
this.registryStorage.addRegistry(registry);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
logger.log('warn', `Could not load credentials for registry ${registryUrl}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
logger.log('info', `Prepared TsDockerManager with ${this.registryStorage.getAllRegistries().length} registries`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Logs in to all configured registries
|
|
73
|
+
*/
|
|
74
|
+
public async login(): Promise<void> {
|
|
75
|
+
if (this.registryStorage.getAllRegistries().length === 0) {
|
|
76
|
+
logger.log('warn', 'No registries configured');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
await this.registryStorage.loginAll();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Discovers and sorts Dockerfiles in the current directory
|
|
84
|
+
*/
|
|
85
|
+
public async discoverDockerfiles(): Promise<Dockerfile[]> {
|
|
86
|
+
this.dockerfiles = await Dockerfile.readDockerfiles(this);
|
|
87
|
+
this.dockerfiles = await Dockerfile.sortDockerfiles(this.dockerfiles);
|
|
88
|
+
this.dockerfiles = await Dockerfile.mapDockerfiles(this.dockerfiles);
|
|
89
|
+
return this.dockerfiles;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Builds all discovered Dockerfiles in dependency order
|
|
94
|
+
*/
|
|
95
|
+
public async build(): Promise<Dockerfile[]> {
|
|
96
|
+
if (this.dockerfiles.length === 0) {
|
|
97
|
+
await this.discoverDockerfiles();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (this.dockerfiles.length === 0) {
|
|
101
|
+
logger.log('warn', 'No Dockerfiles found');
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check if buildx is needed
|
|
106
|
+
if (this.config.platforms && this.config.platforms.length > 1) {
|
|
107
|
+
await this.ensureBuildx();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
logger.log('info', `Building ${this.dockerfiles.length} Dockerfiles...`);
|
|
111
|
+
await Dockerfile.buildDockerfiles(this.dockerfiles);
|
|
112
|
+
logger.log('success', 'All Dockerfiles built successfully');
|
|
113
|
+
|
|
114
|
+
return this.dockerfiles;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Ensures Docker buildx is set up for multi-architecture builds
|
|
119
|
+
*/
|
|
120
|
+
private async ensureBuildx(): Promise<void> {
|
|
121
|
+
logger.log('info', 'Setting up Docker buildx for multi-platform builds...');
|
|
122
|
+
|
|
123
|
+
// Check if a buildx builder exists
|
|
124
|
+
const inspectResult = await smartshellInstance.exec('docker buildx inspect tsdocker-builder 2>/dev/null');
|
|
125
|
+
|
|
126
|
+
if (inspectResult.exitCode !== 0) {
|
|
127
|
+
// Create a new buildx builder
|
|
128
|
+
logger.log('info', 'Creating new buildx builder...');
|
|
129
|
+
await smartshellInstance.exec('docker buildx create --name tsdocker-builder --use');
|
|
130
|
+
await smartshellInstance.exec('docker buildx inspect --bootstrap');
|
|
131
|
+
} else {
|
|
132
|
+
// Use existing builder
|
|
133
|
+
await smartshellInstance.exec('docker buildx use tsdocker-builder');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
logger.log('ok', 'Docker buildx ready');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Pushes all built images to specified registries
|
|
141
|
+
*/
|
|
142
|
+
public async push(registryUrls?: string[]): Promise<void> {
|
|
143
|
+
if (this.dockerfiles.length === 0) {
|
|
144
|
+
await this.discoverDockerfiles();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (this.dockerfiles.length === 0) {
|
|
148
|
+
logger.log('warn', 'No Dockerfiles found to push');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Determine which registries to push to
|
|
153
|
+
let registriesToPush: DockerRegistry[] = [];
|
|
154
|
+
|
|
155
|
+
if (registryUrls && registryUrls.length > 0) {
|
|
156
|
+
// Push to specified registries
|
|
157
|
+
for (const url of registryUrls) {
|
|
158
|
+
const registry = this.registryStorage.getRegistryByUrl(url);
|
|
159
|
+
if (registry) {
|
|
160
|
+
registriesToPush.push(registry);
|
|
161
|
+
} else {
|
|
162
|
+
logger.log('warn', `Registry ${url} not found in storage`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
// Push to all configured registries
|
|
167
|
+
registriesToPush = this.registryStorage.getAllRegistries();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (registriesToPush.length === 0) {
|
|
171
|
+
logger.log('warn', 'No registries available to push to');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Push each Dockerfile to each registry
|
|
176
|
+
for (const dockerfile of this.dockerfiles) {
|
|
177
|
+
for (const registry of registriesToPush) {
|
|
178
|
+
await dockerfile.push(registry);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
logger.log('success', 'All images pushed successfully');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Pulls images from a specified registry
|
|
187
|
+
*/
|
|
188
|
+
public async pull(registryUrl: string): Promise<void> {
|
|
189
|
+
if (this.dockerfiles.length === 0) {
|
|
190
|
+
await this.discoverDockerfiles();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const registry = this.registryStorage.getRegistryByUrl(registryUrl);
|
|
194
|
+
if (!registry) {
|
|
195
|
+
throw new Error(`Registry ${registryUrl} not found`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
for (const dockerfile of this.dockerfiles) {
|
|
199
|
+
await dockerfile.pull(registry);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
logger.log('success', 'All images pulled successfully');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Runs tests for all Dockerfiles
|
|
207
|
+
*/
|
|
208
|
+
public async test(): Promise<void> {
|
|
209
|
+
if (this.dockerfiles.length === 0) {
|
|
210
|
+
await this.discoverDockerfiles();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (this.dockerfiles.length === 0) {
|
|
214
|
+
logger.log('warn', 'No Dockerfiles found to test');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
await Dockerfile.testDockerfiles(this.dockerfiles);
|
|
219
|
+
logger.log('success', 'All tests completed');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Lists all discovered Dockerfiles and their info
|
|
224
|
+
*/
|
|
225
|
+
public async list(): Promise<Dockerfile[]> {
|
|
226
|
+
if (this.dockerfiles.length === 0) {
|
|
227
|
+
await this.discoverDockerfiles();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
console.log('\nDiscovered Dockerfiles:');
|
|
231
|
+
console.log('========================\n');
|
|
232
|
+
|
|
233
|
+
for (let i = 0; i < this.dockerfiles.length; i++) {
|
|
234
|
+
const df = this.dockerfiles[i];
|
|
235
|
+
console.log(`${i + 1}. ${df.filePath}`);
|
|
236
|
+
console.log(` Tag: ${df.cleanTag}`);
|
|
237
|
+
console.log(` Base Image: ${df.baseImage}`);
|
|
238
|
+
console.log(` Version: ${df.version}`);
|
|
239
|
+
if (df.localBaseImageDependent) {
|
|
240
|
+
console.log(` Depends on: ${df.localBaseDockerfile?.cleanTag}`);
|
|
241
|
+
}
|
|
242
|
+
console.log('');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return this.dockerfiles;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Gets the cached Dockerfiles (after discovery)
|
|
250
|
+
*/
|
|
251
|
+
public getDockerfiles(): Dockerfile[] {
|
|
252
|
+
return this.dockerfiles;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration interface for tsdocker
|
|
3
|
+
* Extends legacy config with new Docker build capabilities
|
|
4
|
+
*/
|
|
5
|
+
export interface ITsDockerConfig {
|
|
6
|
+
// Legacy (backward compatible)
|
|
7
|
+
baseImage: string;
|
|
8
|
+
command: string;
|
|
9
|
+
dockerSock: boolean;
|
|
10
|
+
keyValueObject: { [key: string]: any };
|
|
11
|
+
|
|
12
|
+
// New Docker build config
|
|
13
|
+
registries?: string[];
|
|
14
|
+
registryRepoMap?: { [registry: string]: string };
|
|
15
|
+
buildArgEnvMap?: { [dockerArg: string]: string };
|
|
16
|
+
platforms?: string[]; // ['linux/amd64', 'linux/arm64']
|
|
17
|
+
push?: boolean;
|
|
18
|
+
testDir?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Options for constructing a DockerRegistry
|
|
23
|
+
*/
|
|
24
|
+
export interface IDockerRegistryOptions {
|
|
25
|
+
registryUrl: string;
|
|
26
|
+
username: string;
|
|
27
|
+
password: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Information about a discovered Dockerfile
|
|
32
|
+
*/
|
|
33
|
+
export interface IDockerfileInfo {
|
|
34
|
+
filePath: string;
|
|
35
|
+
fileName: string;
|
|
36
|
+
version: string;
|
|
37
|
+
baseImage: string;
|
|
38
|
+
buildTag: string;
|
|
39
|
+
localBaseImageDependent: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Options for creating a Dockerfile instance
|
|
44
|
+
*/
|
|
45
|
+
export interface IDockerfileOptions {
|
|
46
|
+
filePath?: string;
|
|
47
|
+
fileContents?: string;
|
|
48
|
+
read?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Result from a Docker build operation
|
|
53
|
+
*/
|
|
54
|
+
export interface IBuildResult {
|
|
55
|
+
success: boolean;
|
|
56
|
+
tag: string;
|
|
57
|
+
duration?: number;
|
|
58
|
+
error?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Result from a Docker push operation
|
|
63
|
+
*/
|
|
64
|
+
export interface IPushResult {
|
|
65
|
+
success: boolean;
|
|
66
|
+
registry: string;
|
|
67
|
+
tag: string;
|
|
68
|
+
digest?: string;
|
|
69
|
+
error?: string;
|
|
70
|
+
}
|
package/ts/tsdocker.cli.ts
CHANGED
|
@@ -6,10 +6,12 @@ import * as ConfigModule from './tsdocker.config.js';
|
|
|
6
6
|
import * as DockerModule from './tsdocker.docker.js';
|
|
7
7
|
|
|
8
8
|
import { logger, ora } from './tsdocker.logging.js';
|
|
9
|
+
import { TsDockerManager } from './classes.tsdockermanager.js';
|
|
9
10
|
|
|
10
11
|
const tsdockerCli = new plugins.smartcli.Smartcli();
|
|
11
12
|
|
|
12
13
|
export let run = () => {
|
|
14
|
+
// Default command: run tests in container (legacy behavior)
|
|
13
15
|
tsdockerCli.standardCommand().subscribe(async argvArg => {
|
|
14
16
|
const configArg = await ConfigModule.run().then(DockerModule.run);
|
|
15
17
|
if (configArg.exitCode === 0) {
|
|
@@ -20,6 +22,127 @@ export let run = () => {
|
|
|
20
22
|
}
|
|
21
23
|
});
|
|
22
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Build all Dockerfiles in dependency order
|
|
27
|
+
*/
|
|
28
|
+
tsdockerCli.addCommand('build').subscribe(async argvArg => {
|
|
29
|
+
try {
|
|
30
|
+
const config = await ConfigModule.run();
|
|
31
|
+
const manager = new TsDockerManager(config);
|
|
32
|
+
await manager.prepare();
|
|
33
|
+
await manager.build();
|
|
34
|
+
logger.log('success', 'Build completed successfully');
|
|
35
|
+
} catch (err) {
|
|
36
|
+
logger.log('error', `Build failed: ${(err as Error).message}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Push built images to configured registries
|
|
43
|
+
*/
|
|
44
|
+
tsdockerCli.addCommand('push').subscribe(async argvArg => {
|
|
45
|
+
try {
|
|
46
|
+
const config = await ConfigModule.run();
|
|
47
|
+
const manager = new TsDockerManager(config);
|
|
48
|
+
await manager.prepare();
|
|
49
|
+
|
|
50
|
+
// Login first
|
|
51
|
+
await manager.login();
|
|
52
|
+
|
|
53
|
+
// Build images first (if not already built)
|
|
54
|
+
await manager.build();
|
|
55
|
+
|
|
56
|
+
// Get registry from arguments if specified
|
|
57
|
+
const registryArg = argvArg._[1]; // e.g., tsdocker push registry.gitlab.com
|
|
58
|
+
const registries = registryArg ? [registryArg] : undefined;
|
|
59
|
+
|
|
60
|
+
await manager.push(registries);
|
|
61
|
+
logger.log('success', 'Push completed successfully');
|
|
62
|
+
} catch (err) {
|
|
63
|
+
logger.log('error', `Push failed: ${(err as Error).message}`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Pull images from a specified registry
|
|
70
|
+
*/
|
|
71
|
+
tsdockerCli.addCommand('pull').subscribe(async argvArg => {
|
|
72
|
+
try {
|
|
73
|
+
const registryArg = argvArg._[1]; // e.g., tsdocker pull registry.gitlab.com
|
|
74
|
+
if (!registryArg) {
|
|
75
|
+
logger.log('error', 'Registry URL required. Usage: tsdocker pull <registry-url>');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const config = await ConfigModule.run();
|
|
80
|
+
const manager = new TsDockerManager(config);
|
|
81
|
+
await manager.prepare();
|
|
82
|
+
|
|
83
|
+
// Login first
|
|
84
|
+
await manager.login();
|
|
85
|
+
|
|
86
|
+
await manager.pull(registryArg);
|
|
87
|
+
logger.log('success', 'Pull completed successfully');
|
|
88
|
+
} catch (err) {
|
|
89
|
+
logger.log('error', `Pull failed: ${(err as Error).message}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Run container tests for all Dockerfiles
|
|
96
|
+
*/
|
|
97
|
+
tsdockerCli.addCommand('test').subscribe(async argvArg => {
|
|
98
|
+
try {
|
|
99
|
+
const config = await ConfigModule.run();
|
|
100
|
+
const manager = new TsDockerManager(config);
|
|
101
|
+
await manager.prepare();
|
|
102
|
+
|
|
103
|
+
// Build images first
|
|
104
|
+
await manager.build();
|
|
105
|
+
|
|
106
|
+
// Run tests
|
|
107
|
+
await manager.test();
|
|
108
|
+
logger.log('success', 'Tests completed successfully');
|
|
109
|
+
} catch (err) {
|
|
110
|
+
logger.log('error', `Tests failed: ${(err as Error).message}`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Login to configured registries
|
|
117
|
+
*/
|
|
118
|
+
tsdockerCli.addCommand('login').subscribe(async argvArg => {
|
|
119
|
+
try {
|
|
120
|
+
const config = await ConfigModule.run();
|
|
121
|
+
const manager = new TsDockerManager(config);
|
|
122
|
+
await manager.prepare();
|
|
123
|
+
await manager.login();
|
|
124
|
+
logger.log('success', 'Login completed successfully');
|
|
125
|
+
} catch (err) {
|
|
126
|
+
logger.log('error', `Login failed: ${(err as Error).message}`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* List discovered Dockerfiles and their dependencies
|
|
133
|
+
*/
|
|
134
|
+
tsdockerCli.addCommand('list').subscribe(async argvArg => {
|
|
135
|
+
try {
|
|
136
|
+
const config = await ConfigModule.run();
|
|
137
|
+
const manager = new TsDockerManager(config);
|
|
138
|
+
await manager.prepare();
|
|
139
|
+
await manager.list();
|
|
140
|
+
} catch (err) {
|
|
141
|
+
logger.log('error', `List failed: ${(err as Error).message}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
23
146
|
/**
|
|
24
147
|
* this command is executed inside docker and meant for use from outside docker
|
|
25
148
|
*/
|
|
@@ -62,16 +185,6 @@ export let run = () => {
|
|
|
62
185
|
ora.finishSuccess('docker environment now is clean!');
|
|
63
186
|
});
|
|
64
187
|
|
|
65
|
-
tsdockerCli.addCommand('speedtest').subscribe(async argvArg => {
|
|
66
|
-
const smartshellInstance = new plugins.smartshell.Smartshell({
|
|
67
|
-
executor: 'bash'
|
|
68
|
-
});
|
|
69
|
-
logger.log('ok', 'Starting speedtest');
|
|
70
|
-
await smartshellInstance.exec(
|
|
71
|
-
`docker pull tianon/speedtest && docker run --rm tianon/speedtest --accept-license --accept-gdpr`
|
|
72
|
-
);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
188
|
tsdockerCli.addCommand('vscode').subscribe(async argvArg => {
|
|
76
189
|
const smartshellInstance = new plugins.smartshell.Smartshell({
|
|
77
190
|
executor: 'bash'
|
package/ts/tsdocker.config.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import * as plugins from './tsdocker.plugins.js';
|
|
2
2
|
import * as paths from './tsdocker.paths.js';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
|
+
import type { ITsDockerConfig } from './interfaces/index.js';
|
|
4
5
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
command: string;
|
|
8
|
-
dockerSock: boolean;
|
|
6
|
+
// Re-export ITsDockerConfig as IConfig for backward compatibility
|
|
7
|
+
export type IConfig = ITsDockerConfig & {
|
|
9
8
|
exitCode?: number;
|
|
10
|
-
|
|
11
|
-
}
|
|
9
|
+
};
|
|
12
10
|
|
|
13
11
|
const getQenvKeyValueObject = async () => {
|
|
14
12
|
let qenvKeyValueObjectArray: { [key: string]: string | number };
|
|
@@ -23,11 +21,20 @@ const getQenvKeyValueObject = async () => {
|
|
|
23
21
|
const buildConfig = async (qenvKeyValueObjectArg: { [key: string]: string | number }) => {
|
|
24
22
|
const npmextra = new plugins.npmextra.Npmextra(paths.cwd);
|
|
25
23
|
const config = npmextra.dataFor<IConfig>('@git.zone/tsdocker', {
|
|
24
|
+
// Legacy options (backward compatible)
|
|
26
25
|
baseImage: 'hosttoday/ht-docker-node:npmdocker',
|
|
27
26
|
init: 'rm -rf node_nodules/ && yarn install',
|
|
28
27
|
command: 'npmci npm test',
|
|
29
28
|
dockerSock: false,
|
|
30
|
-
keyValueObject: qenvKeyValueObjectArg
|
|
29
|
+
keyValueObject: qenvKeyValueObjectArg,
|
|
30
|
+
|
|
31
|
+
// New Docker build options
|
|
32
|
+
registries: [],
|
|
33
|
+
registryRepoMap: {},
|
|
34
|
+
buildArgEnvMap: {},
|
|
35
|
+
platforms: ['linux/amd64'],
|
|
36
|
+
push: false,
|
|
37
|
+
testDir: undefined,
|
|
31
38
|
});
|
|
32
39
|
return config;
|
|
33
40
|
};
|