@phystack/cli 4.3.40-dev
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/LICENSE.md +19 -0
- package/README.md +24 -0
- package/bin/index.js +2 -0
- package/dist/commands/app/build-apps.js +66 -0
- package/dist/commands/app/build-apps.js.map +1 -0
- package/dist/commands/app/build-container-remote.js +171 -0
- package/dist/commands/app/build-container-remote.js.map +1 -0
- package/dist/commands/app/build-container.js +330 -0
- package/dist/commands/app/build-container.js.map +1 -0
- package/dist/commands/app/build.js +393 -0
- package/dist/commands/app/build.js.map +1 -0
- package/dist/commands/app/create.js +451 -0
- package/dist/commands/app/create.js.map +1 -0
- package/dist/commands/app/deploy.js +176 -0
- package/dist/commands/app/deploy.js.map +1 -0
- package/dist/commands/app/device-picker.js +150 -0
- package/dist/commands/app/device-picker.js.map +1 -0
- package/dist/commands/app/import.js +303 -0
- package/dist/commands/app/import.js.map +1 -0
- package/dist/commands/app/list.js +26 -0
- package/dist/commands/app/list.js.map +1 -0
- package/dist/commands/app/publish.js +327 -0
- package/dist/commands/app/publish.js.map +1 -0
- package/dist/commands/app/settings.js +129 -0
- package/dist/commands/app/settings.js.map +1 -0
- package/dist/commands/app/types.js +13 -0
- package/dist/commands/app/types.js.map +1 -0
- package/dist/commands/app/upload-description.js +139 -0
- package/dist/commands/app/upload-description.js.map +1 -0
- package/dist/commands/app/upload-settings.js +29 -0
- package/dist/commands/app/upload-settings.js.map +1 -0
- package/dist/commands/app/utils.js +86 -0
- package/dist/commands/app/utils.js.map +1 -0
- package/dist/commands/auth/login.js +111 -0
- package/dist/commands/auth/login.js.map +1 -0
- package/dist/commands/auth/logout.js +19 -0
- package/dist/commands/auth/logout.js.map +1 -0
- package/dist/commands/descriptor/create.js +143 -0
- package/dist/commands/descriptor/create.js.map +1 -0
- package/dist/commands/descriptor/index.js +36 -0
- package/dist/commands/descriptor/index.js.map +1 -0
- package/dist/commands/descriptor/publish.js +163 -0
- package/dist/commands/descriptor/publish.js.map +1 -0
- package/dist/commands/descriptor/show.js +68 -0
- package/dist/commands/descriptor/show.js.map +1 -0
- package/dist/commands/dev/develop.js +175 -0
- package/dist/commands/dev/develop.js.map +1 -0
- package/dist/commands/dev/forward.js +118 -0
- package/dist/commands/dev/forward.js.map +1 -0
- package/dist/commands/dev/index.js +66 -0
- package/dist/commands/dev/index.js.map +1 -0
- package/dist/commands/dev/list.js +96 -0
- package/dist/commands/dev/list.js.map +1 -0
- package/dist/commands/dev/screen-devtools.js +156 -0
- package/dist/commands/dev/screen-devtools.js.map +1 -0
- package/dist/commands/dev/select.js +118 -0
- package/dist/commands/dev/select.js.map +1 -0
- package/dist/commands/dev/shell.js +171 -0
- package/dist/commands/dev/shell.js.map +1 -0
- package/dist/commands/dev/vnc.js +75 -0
- package/dist/commands/dev/vnc.js.map +1 -0
- package/dist/commands/device/select.js +118 -0
- package/dist/commands/device/select.js.map +1 -0
- package/dist/commands/flash.js +1120 -0
- package/dist/commands/flash.js.map +1 -0
- package/dist/commands/inst/create.js +55 -0
- package/dist/commands/inst/create.js.map +1 -0
- package/dist/commands/inst/index.js +15 -0
- package/dist/commands/inst/index.js.map +1 -0
- package/dist/commands/inst/list.js +26 -0
- package/dist/commands/inst/list.js.map +1 -0
- package/dist/commands/legacydev/debug.js +11 -0
- package/dist/commands/legacydev/debug.js.map +1 -0
- package/dist/commands/legacydev/deploy.js +15 -0
- package/dist/commands/legacydev/deploy.js.map +1 -0
- package/dist/commands/legacydev/dumpTwin.js +27 -0
- package/dist/commands/legacydev/dumpTwin.js.map +1 -0
- package/dist/commands/legacydev/forward.js +104 -0
- package/dist/commands/legacydev/forward.js.map +1 -0
- package/dist/commands/legacydev/index.js +188 -0
- package/dist/commands/legacydev/index.js.map +1 -0
- package/dist/commands/legacydev/invoke.js +29 -0
- package/dist/commands/legacydev/invoke.js.map +1 -0
- package/dist/commands/legacydev/js.js +69 -0
- package/dist/commands/legacydev/js.js.map +1 -0
- package/dist/commands/legacydev/list.js +196 -0
- package/dist/commands/legacydev/list.js.map +1 -0
- package/dist/commands/legacydev/logs.js +60 -0
- package/dist/commands/legacydev/logs.js.map +1 -0
- package/dist/commands/legacydev/modules.js +50 -0
- package/dist/commands/legacydev/modules.js.map +1 -0
- package/dist/commands/legacydev/move.js +23 -0
- package/dist/commands/legacydev/move.js.map +1 -0
- package/dist/commands/legacydev/ota.js +88 -0
- package/dist/commands/legacydev/ota.js.map +1 -0
- package/dist/commands/legacydev/patchTwin.js +21 -0
- package/dist/commands/legacydev/patchTwin.js.map +1 -0
- package/dist/commands/legacydev/pin.js +23 -0
- package/dist/commands/legacydev/pin.js.map +1 -0
- package/dist/commands/legacydev/pub.js +25 -0
- package/dist/commands/legacydev/pub.js.map +1 -0
- package/dist/commands/legacydev/rdp.js +64 -0
- package/dist/commands/legacydev/rdp.js.map +1 -0
- package/dist/commands/legacydev/screen-devtools.js +142 -0
- package/dist/commands/legacydev/screen-devtools.js.map +1 -0
- package/dist/commands/legacydev/settingsShow.js +89 -0
- package/dist/commands/legacydev/settingsShow.js.map +1 -0
- package/dist/commands/legacydev/settingsUpdate.js +114 -0
- package/dist/commands/legacydev/settingsUpdate.js.map +1 -0
- package/dist/commands/legacydev/shell.js +167 -0
- package/dist/commands/legacydev/shell.js.map +1 -0
- package/dist/commands/legacydev/showTwin.js +9 -0
- package/dist/commands/legacydev/showTwin.js.map +1 -0
- package/dist/commands/legacydev/statusLog.js +56 -0
- package/dist/commands/legacydev/statusLog.js.map +1 -0
- package/dist/commands/legacydev/sub.js +39 -0
- package/dist/commands/legacydev/sub.js.map +1 -0
- package/dist/commands/legacydev/vnc.js +61 -0
- package/dist/commands/legacydev/vnc.js.map +1 -0
- package/dist/commands/tenant/index.js +21 -0
- package/dist/commands/tenant/index.js.map +1 -0
- package/dist/commands/tenant/list.js +14 -0
- package/dist/commands/tenant/list.js.map +1 -0
- package/dist/commands/tenant/select.js +87 -0
- package/dist/commands/tenant/select.js.map +1 -0
- package/dist/commands/vm/create.js +718 -0
- package/dist/commands/vm/create.js.map +1 -0
- package/dist/commands/vm/index.js +130 -0
- package/dist/commands/vm/index.js.map +1 -0
- package/dist/commands/vm/list.js +124 -0
- package/dist/commands/vm/list.js.map +1 -0
- package/dist/commands/vm/logs.js +66 -0
- package/dist/commands/vm/logs.js.map +1 -0
- package/dist/commands/vm/remove.js +180 -0
- package/dist/commands/vm/remove.js.map +1 -0
- package/dist/commands/vm/shell.js +400 -0
- package/dist/commands/vm/shell.js.map +1 -0
- package/dist/commands/vm/start.js +861 -0
- package/dist/commands/vm/start.js.map +1 -0
- package/dist/commands/vm/stop.js +232 -0
- package/dist/commands/vm/stop.js.map +1 -0
- package/dist/index.js +158 -0
- package/dist/index.js.map +1 -0
- package/dist/services/admin-api/admin-api.types.js +3 -0
- package/dist/services/admin-api/admin-api.types.js.map +1 -0
- package/dist/services/admin-api/device-modules.admin-api.service.js +58 -0
- package/dist/services/admin-api/device-modules.admin-api.service.js.map +1 -0
- package/dist/services/admin-api/devices-admin-api.service.js +213 -0
- package/dist/services/admin-api/devices-admin-api.service.js.map +1 -0
- package/dist/services/admin-api/gridapps-admin-api.service.js +59 -0
- package/dist/services/admin-api/gridapps-admin-api.service.js.map +1 -0
- package/dist/services/admin-api/index.js +157 -0
- package/dist/services/admin-api/index.js.map +1 -0
- package/dist/services/admin-api/installations-admin-api.service.js +29 -0
- package/dist/services/admin-api/installations-admin-api.service.js.map +1 -0
- package/dist/services/admin-api/organizations-admin-api.service.js +53 -0
- package/dist/services/admin-api/organizations-admin-api.service.js.map +1 -0
- package/dist/services/auth/device-grant-auth.service.js +224 -0
- package/dist/services/auth/device-grant-auth.service.js.map +1 -0
- package/dist/services/phyhub/index.js +200 -0
- package/dist/services/phyhub/index.js.map +1 -0
- package/dist/services/phyhub/phyhub.types.js +3 -0
- package/dist/services/phyhub/phyhub.types.js.map +1 -0
- package/dist/utils/device-fetcher.js +92 -0
- package/dist/utils/device-fetcher.js.map +1 -0
- package/dist/utils/devices.js +41 -0
- package/dist/utils/devices.js.map +1 -0
- package/dist/utils/docker-credentials.js +720 -0
- package/dist/utils/docker-credentials.js.map +1 -0
- package/dist/utils/emulated-device.js +91 -0
- package/dist/utils/emulated-device.js.map +1 -0
- package/dist/utils/index.js +180 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/modules.js +36 -0
- package/dist/utils/modules.js.map +1 -0
- package/dist/utils/org-selector.js +108 -0
- package/dist/utils/org-selector.js.map +1 -0
- package/dist/utils/proxy.js +31 -0
- package/dist/utils/proxy.js.map +1 -0
- package/dist/utils/registry-credentials.js +113 -0
- package/dist/utils/registry-credentials.js.map +1 -0
- package/dist/utils/statuses.js +124 -0
- package/dist/utils/statuses.js.map +1 -0
- package/dist/utils/templates.js +197 -0
- package/dist/utils/templates.js.map +1 -0
- package/dist/utils/tenant-storage.js +88 -0
- package/dist/utils/tenant-storage.js.map +1 -0
- package/dist/utils/vm.js +434 -0
- package/dist/utils/vm.js.map +1 -0
- package/dist/utils/with-spinner.js +20 -0
- package/dist/utils/with-spinner.js.map +1 -0
- package/package.json +103 -0
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.setupDockerCredentials = exports.verifyDockerCredentials = exports.createDockerHubRepository = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const child_process_1 = __importDefault(require("child_process"));
|
|
9
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
+
const uuid_1 = require("uuid");
|
|
11
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
|
+
const registry_credentials_1 = require("./registry-credentials");
|
|
13
|
+
/**
|
|
14
|
+
* Authenticate with Docker Hub API and return a token
|
|
15
|
+
*/
|
|
16
|
+
const getDockerHubToken = async (username, password) => {
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch('https://hub.docker.com/v2/users/login', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
body: JSON.stringify({ username, password }),
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(`Failed to authenticate with Docker Hub: ${response.statusText}`);
|
|
25
|
+
}
|
|
26
|
+
const data = await response.json();
|
|
27
|
+
if (!data.token) {
|
|
28
|
+
throw new Error('No token returned from Docker Hub authentication');
|
|
29
|
+
}
|
|
30
|
+
return data.token;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new Error(`Docker Hub API authentication failed: ${error.message}`);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Delete a repository from Docker Hub
|
|
38
|
+
* @param username - The username for authentication
|
|
39
|
+
* @param password - The password for authentication
|
|
40
|
+
* @param namespace - The namespace (organization or username)
|
|
41
|
+
* @param repoName - The repository name
|
|
42
|
+
*/
|
|
43
|
+
const deleteDockerHubRepo = async (username, password, namespace, repoName) => {
|
|
44
|
+
try {
|
|
45
|
+
console.log(chalk_1.default.dim(`Deleting test repository ${namespace}/${repoName} from Docker Hub...`));
|
|
46
|
+
const token = await getDockerHubToken(username, password);
|
|
47
|
+
const deleteResponse = await fetch(`https://hub.docker.com/v2/repositories/${namespace}/${repoName}/`, {
|
|
48
|
+
method: 'DELETE',
|
|
49
|
+
headers: {
|
|
50
|
+
'Authorization': `Bearer ${token}`,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
if (deleteResponse.ok || deleteResponse.status === 204) {
|
|
54
|
+
console.log(chalk_1.default.green(`✓ Test repository ${namespace}/${repoName} deleted successfully`));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log(chalk_1.default.dim(`Note: Could not delete test repository (status ${deleteResponse.status})`));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.log(chalk_1.default.dim(`Note: Could not delete test repository: ${error.message}`));
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Create a repository on Docker Hub if it doesn't exist
|
|
66
|
+
* @param username - The username for authentication (personal account)
|
|
67
|
+
* @param password - The password for authentication
|
|
68
|
+
* @param repoName - The repository name (without organization prefix)
|
|
69
|
+
* @param namespace - The namespace (organization or username) where the repo should be created
|
|
70
|
+
* @param isPrivate - Whether the repository should be private (default: false)
|
|
71
|
+
*/
|
|
72
|
+
const createDockerHubRepo = async (username, password, repoName, namespace, isPrivate = false) => {
|
|
73
|
+
console.log(chalk_1.default.dim(`Checking if repository ${namespace}/${repoName} exists on Docker Hub...`));
|
|
74
|
+
// Get authentication token using personal credentials
|
|
75
|
+
const token = await getDockerHubToken(username, password);
|
|
76
|
+
// Check if repository already exists in the namespace
|
|
77
|
+
const checkResponse = await fetch(`https://hub.docker.com/v2/repositories/${namespace}/${repoName}/`, {
|
|
78
|
+
method: 'GET',
|
|
79
|
+
headers: {
|
|
80
|
+
'Authorization': `Bearer ${token}`,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
if (checkResponse.ok) {
|
|
84
|
+
console.log(chalk_1.default.green(`✓ Repository ${namespace}/${repoName} already exists`));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Repository doesn't exist, create it in the namespace
|
|
88
|
+
const visibility = isPrivate ? 'private' : 'public';
|
|
89
|
+
console.log(chalk_1.default.dim(`Repository does not exist. Creating ${visibility} repository ${namespace}/${repoName} on Docker Hub...`));
|
|
90
|
+
const createResponse = await fetch(`https://hub.docker.com/v2/repositories/`, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers: {
|
|
93
|
+
'Authorization': `JWT ${token}`,
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
},
|
|
96
|
+
body: JSON.stringify({
|
|
97
|
+
name: repoName,
|
|
98
|
+
namespace: namespace,
|
|
99
|
+
is_private: isPrivate,
|
|
100
|
+
description: 'Auto-created by phygrid-cli for container deployment',
|
|
101
|
+
}),
|
|
102
|
+
});
|
|
103
|
+
if (!createResponse.ok) {
|
|
104
|
+
const errorText = await createResponse.text();
|
|
105
|
+
// Check for specific error conditions
|
|
106
|
+
if (errorText.includes('private repositories') || errorText.includes('exceeds the limit')) {
|
|
107
|
+
console.log(chalk_1.default.red(`Failed to create repository: ${createResponse.status} ${createResponse.statusText}`));
|
|
108
|
+
console.log(chalk_1.default.red(`Error: The number of private repositories in your account exceeds the limit of your current Docker Hub subscription.`));
|
|
109
|
+
console.log(chalk_1.default.yellow(`Solutions:`));
|
|
110
|
+
console.log(chalk_1.default.yellow(` 1. Create the repository as public instead`));
|
|
111
|
+
console.log(chalk_1.default.yellow(` 2. Upgrade your Docker Hub subscription at https://hub.docker.com/billing`));
|
|
112
|
+
console.log(chalk_1.default.yellow(` 3. Delete unused private repositories`));
|
|
113
|
+
console.log(chalk_1.default.yellow(` 4. Manually create the repository at https://hub.docker.com/repository/create`));
|
|
114
|
+
throw new Error(`Docker Hub private repository limit exceeded`);
|
|
115
|
+
}
|
|
116
|
+
console.log(chalk_1.default.red(`Failed to create repository: ${createResponse.status} ${createResponse.statusText}`));
|
|
117
|
+
console.log(chalk_1.default.dim(`Response: ${errorText}`));
|
|
118
|
+
throw new Error(`Failed to create repository ${namespace}/${repoName}: ${createResponse.statusText} - ${errorText}`);
|
|
119
|
+
}
|
|
120
|
+
console.log(chalk_1.default.green(`✓ Repository ${namespace}/${repoName} created successfully on Docker Hub as ${visibility}`));
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Create a Docker Hub repository for the given image if it doesn't exist
|
|
124
|
+
* This is a public export that extracts the repo name from a full image string
|
|
125
|
+
* Only works for Docker Hub (docker.io) - silently does nothing for other registries
|
|
126
|
+
*/
|
|
127
|
+
const createDockerHubRepository = async (registry, imageName, username, password, isPrivate = false) => {
|
|
128
|
+
// Only create repositories for Docker Hub
|
|
129
|
+
if (registry !== 'docker.io') {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
// Extract repository name and namespace from full image name
|
|
134
|
+
// Examples:
|
|
135
|
+
// docker.io/hassellof/superted82:latest -> namespace=hassellof, repo=superted82
|
|
136
|
+
// docker.io/myorg/myapp -> namespace=myorg, repo=myapp
|
|
137
|
+
// hassellof/superted82:latest -> namespace=hassellof, repo=superted82
|
|
138
|
+
let fullPath = imageName;
|
|
139
|
+
// Remove registry prefix if present
|
|
140
|
+
if (fullPath.startsWith('docker.io/')) {
|
|
141
|
+
fullPath = fullPath.substring('docker.io/'.length);
|
|
142
|
+
}
|
|
143
|
+
// Remove tag if present (do this before splitting)
|
|
144
|
+
fullPath = fullPath.split(':')[0];
|
|
145
|
+
// Split into parts to extract namespace and repo
|
|
146
|
+
const parts = fullPath.split('/');
|
|
147
|
+
let namespace;
|
|
148
|
+
let repoName;
|
|
149
|
+
if (parts.length >= 2) {
|
|
150
|
+
// Format: namespace/repo or namespace/sub/repo
|
|
151
|
+
namespace = parts[0]; // The organization or username
|
|
152
|
+
repoName = parts[parts.length - 1]; // The repo name (last part)
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// Format: just repo name, use username as namespace
|
|
156
|
+
namespace = username;
|
|
157
|
+
repoName = parts[0];
|
|
158
|
+
}
|
|
159
|
+
console.log(chalk_1.default.dim(`Parsed image: namespace=${namespace}, repo=${repoName}`));
|
|
160
|
+
await createDockerHubRepo(username, password, repoName, namespace, isPrivate);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
// Log the error but don't throw - let the build attempt proceed
|
|
164
|
+
// The push will fail with a clear error if the repo doesn't exist
|
|
165
|
+
console.log(chalk_1.default.yellow(`Warning: Could not auto-create repository: ${error.message}`));
|
|
166
|
+
console.log(chalk_1.default.yellow(`You may need to create the repository manually at https://hub.docker.com/repository/create`));
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
exports.createDockerHubRepository = createDockerHubRepository;
|
|
170
|
+
const verifyDockerCredentials = async (registry, pullUsername, pullPassword, pushUsername, pushPassword, customImageName, nonInteractive = false, isPushCredsFromEnv = false) => {
|
|
171
|
+
// Check if we can skip validation using cached hash
|
|
172
|
+
if (pullUsername && pullPassword && pushUsername && pushPassword) {
|
|
173
|
+
const isValid = await (0, registry_credentials_1.isCredentialHashValid)(registry, pullUsername, pullPassword, pushUsername, pushPassword);
|
|
174
|
+
if (isValid) {
|
|
175
|
+
const envHash = process.env.PHYGRID_REGISTRY_VALIDATION_HASH;
|
|
176
|
+
if (envHash) {
|
|
177
|
+
console.log(chalk_1.default.green('✓ Skipping credential validation - using PHYGRID_REGISTRY_VALIDATION_HASH from environment'));
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
console.log(chalk_1.default.green('✓ Skipping credential validation - using cached validation from previous successful build'));
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
pullUsername,
|
|
184
|
+
pullPassword,
|
|
185
|
+
pushUsername,
|
|
186
|
+
pushPassword,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
let testPushTag;
|
|
191
|
+
// Use a consistent image name for easier cleanup
|
|
192
|
+
const testImageName = 'phygrid-test-image';
|
|
193
|
+
// Extract organization from custom image name if available
|
|
194
|
+
let orgName = null;
|
|
195
|
+
if (customImageName && customImageName.includes('/')) {
|
|
196
|
+
if (customImageName.startsWith('docker.io/')) {
|
|
197
|
+
// For docker.io images, organization comes after the docker.io/ prefix
|
|
198
|
+
const parts = customImageName.split('/');
|
|
199
|
+
if (parts.length >= 2) {
|
|
200
|
+
orgName = parts[1];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// For other registries, organization is the first part before the slash
|
|
205
|
+
orgName = customImageName.split('/')[0];
|
|
206
|
+
}
|
|
207
|
+
console.log(chalk_1.default.dim(`Using organization from image: ${orgName}`));
|
|
208
|
+
}
|
|
209
|
+
// Pull test image from Docker Hub first
|
|
210
|
+
console.log(chalk_1.default.dim('Pulling test image...'));
|
|
211
|
+
try {
|
|
212
|
+
console.log(chalk_1.default.dim('Running: docker pull hello-world:latest'));
|
|
213
|
+
child_process_1.default.execSync('docker pull hello-world:latest', {
|
|
214
|
+
stdio: 'pipe',
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
throw new Error('Failed to pull test image from Docker Hub. Verify that Docker is running and check your internet connection.');
|
|
219
|
+
}
|
|
220
|
+
// Validate PUSH credentials first (must be able to push)
|
|
221
|
+
console.log(chalk_1.default.dim('Validating push credentials...'));
|
|
222
|
+
let pushCredsValid = false;
|
|
223
|
+
let finalPushUsername = pushUsername;
|
|
224
|
+
let finalPushPassword = pushPassword;
|
|
225
|
+
// Explicitly indicate when environment variables are being used
|
|
226
|
+
if (isPushCredsFromEnv) {
|
|
227
|
+
console.log(chalk_1.default.green('Using push credentials from PHYGRID_REGISTRY_PUSH_* environment variables'));
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
console.log(chalk_1.default.dim('hint: set the following environment variables:'));
|
|
231
|
+
console.log(chalk_1.default.dim(' PHYGRID_REGISTRY_PULL_USERNAME - for read-only access'));
|
|
232
|
+
console.log(chalk_1.default.dim(' PHYGRID_REGISTRY_PULL_PASSWORD - for read-only access'));
|
|
233
|
+
console.log(chalk_1.default.dim(' PHYGRID_REGISTRY_PUSH_USERNAME - for write access'));
|
|
234
|
+
console.log(chalk_1.default.dim(' PHYGRID_REGISTRY_PUSH_PASSWORD - for write access'));
|
|
235
|
+
console.log(chalk_1.default.dim(' PHYGRID_REGISTRY_VALIDATION_HASH - to skip validation in CI (provided after first successful validation)'));
|
|
236
|
+
}
|
|
237
|
+
while (!pushCredsValid) {
|
|
238
|
+
try {
|
|
239
|
+
// Login with push credentials
|
|
240
|
+
console.log(chalk_1.default.dim(`Logging in to registry ${registry} with username ${finalPushUsername}`));
|
|
241
|
+
try {
|
|
242
|
+
// Don't log the actual command with password to avoid exposing credentials
|
|
243
|
+
// We're also using stdio: 'pipe' instead of 'inherit' to have better control over the output
|
|
244
|
+
child_process_1.default.execSync(`docker login ${registry} -u ${finalPushUsername} -p ${finalPushPassword}`, {
|
|
245
|
+
stdio: 'pipe',
|
|
246
|
+
});
|
|
247
|
+
console.log(chalk_1.default.green('✓ Login successful'));
|
|
248
|
+
}
|
|
249
|
+
catch (loginError) {
|
|
250
|
+
// Create a cleaned error message that doesn't include credentials
|
|
251
|
+
console.log(chalk_1.default.red(`Error: Docker login to ${registry} with user ${finalPushUsername} failed.`));
|
|
252
|
+
// Extract just the error response from Docker, not the command with credentials
|
|
253
|
+
let errorMsg = loginError.message || '';
|
|
254
|
+
// Look for the pattern of Docker error messages
|
|
255
|
+
const errorMatch = errorMsg.match(/Error response from daemon: (.*)/);
|
|
256
|
+
if (errorMatch && errorMatch[1]) {
|
|
257
|
+
console.log(chalk_1.default.yellow(`Docker error: ${errorMatch[1]}`));
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
console.log(chalk_1.default.yellow('Docker authentication failed. Check credentials.'));
|
|
261
|
+
}
|
|
262
|
+
throw new Error(`Docker login to ${registry} failed. Authentication error.`);
|
|
263
|
+
}
|
|
264
|
+
// Tag the hello-world image for our registry with a unique tag
|
|
265
|
+
const pushTestTag = `test-${(0, uuid_1.v4)().substring(0, 8)}`;
|
|
266
|
+
const testImage = registry === 'docker.io'
|
|
267
|
+
? `${registry}/${orgName ||
|
|
268
|
+
finalPushUsername}/${testImageName}:${pushTestTag}`
|
|
269
|
+
: `${registry}/${testImageName}:${pushTestTag}`;
|
|
270
|
+
console.log(chalk_1.default.dim(`Generated test image tag: ${testImage}`));
|
|
271
|
+
console.log(chalk_1.default.dim(`Registry: ${chalk_1.default.blue(registry)}`));
|
|
272
|
+
console.log(chalk_1.default.dim(`Username: ${chalk_1.default.blue(finalPushUsername)}`));
|
|
273
|
+
console.log(chalk_1.default.dim(`Testing with image: ${chalk_1.default.blue(testImage)}`));
|
|
274
|
+
console.log(chalk_1.default.dim(`Running: docker tag hello-world:latest ${testImage}`));
|
|
275
|
+
child_process_1.default.execSync(`docker tag hello-world:latest ${testImage}`, {
|
|
276
|
+
stdio: 'pipe',
|
|
277
|
+
});
|
|
278
|
+
// For Docker Hub, ensure the repository exists before pushing
|
|
279
|
+
if (registry === 'docker.io') {
|
|
280
|
+
// Use orgName if available, otherwise use username as namespace
|
|
281
|
+
const namespace = orgName || finalPushUsername;
|
|
282
|
+
// Test image is always public to avoid wasting private repo slots
|
|
283
|
+
await createDockerHubRepo(finalPushUsername, finalPushPassword, testImageName, namespace, false);
|
|
284
|
+
}
|
|
285
|
+
// Try pushing hello-world to our registry
|
|
286
|
+
try {
|
|
287
|
+
console.log(chalk_1.default.dim(`Attempting to push test image to registry: ${chalk_1.default.blue(registry)}`));
|
|
288
|
+
console.log(chalk_1.default.dim(`Using push credentials: username=${chalk_1.default.blue(finalPushUsername)}`));
|
|
289
|
+
console.log(chalk_1.default.dim(`Running: docker push ${testImage}`));
|
|
290
|
+
child_process_1.default.execSync(`docker push ${testImage}`, {
|
|
291
|
+
stdio: 'pipe',
|
|
292
|
+
});
|
|
293
|
+
console.log(chalk_1.default.green('✓ Push credentials validated'));
|
|
294
|
+
pushCredsValid = true;
|
|
295
|
+
// Store the test tag for pull validation
|
|
296
|
+
testPushTag = pushTestTag;
|
|
297
|
+
}
|
|
298
|
+
catch (pushError) {
|
|
299
|
+
const errorMessage = pushError.message || '';
|
|
300
|
+
// Check if the error is specifically about repository not existing or access denied
|
|
301
|
+
if (errorMessage.includes('denied') || errorMessage.includes('requested access to the resource is denied')) {
|
|
302
|
+
// For Docker Hub, this might mean the repo doesn't exist
|
|
303
|
+
if (registry === 'docker.io') {
|
|
304
|
+
console.log(chalk_1.default.yellow('Push failed - this may indicate the repository does not exist or credentials are invalid'));
|
|
305
|
+
console.log(chalk_1.default.yellow(`Please verify the repository exists at: https://hub.docker.com/repository/docker/${finalPushUsername}/${testImageName}`));
|
|
306
|
+
console.log(chalk_1.default.yellow(`Or create it manually at: https://hub.docker.com/repository/create`));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
console.log(chalk_1.default.red(`Failed to push test image: ${errorMessage}`));
|
|
310
|
+
throw new Error(`Failed to push test image: ${errorMessage}`);
|
|
311
|
+
}
|
|
312
|
+
finally {
|
|
313
|
+
// Clean up local tag
|
|
314
|
+
try {
|
|
315
|
+
child_process_1.default.execSync(`docker rmi ${testImage}`, {
|
|
316
|
+
stdio: 'pipe',
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
console.log(chalk_1.default.yellow(`Warning: Failed to remove test image: ${error.message}`));
|
|
321
|
+
// Ignore cleanup errors
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
// Create a sanitized error message
|
|
327
|
+
let safeErrorMessage = 'Unknown error';
|
|
328
|
+
if (error.message) {
|
|
329
|
+
// If it's a login failure we created earlier, use it directly
|
|
330
|
+
if (error.message.includes('Docker login to') && error.message.includes('failed')) {
|
|
331
|
+
safeErrorMessage = error.message;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
// For other errors, sanitize any command that might contain credentials
|
|
335
|
+
safeErrorMessage = error.message.replace(/docker login[^;]*;?/g, 'docker login [credentials hidden];');
|
|
336
|
+
// Extract Docker daemon errors if present
|
|
337
|
+
const errorMatch = error.message.match(/Error response from daemon: (.*)/);
|
|
338
|
+
if (errorMatch && errorMatch[1]) {
|
|
339
|
+
safeErrorMessage = `Docker error: ${errorMatch[1]}`;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
console.log(chalk_1.default.red('Error: Push credentials are invalid.'));
|
|
344
|
+
console.log(chalk_1.default.red(`Error details: ${safeErrorMessage}`));
|
|
345
|
+
// If non-interactive or creds came from ENV, throw error instead of prompting
|
|
346
|
+
if (nonInteractive || isPushCredsFromEnv) {
|
|
347
|
+
throw new Error(`Push credentials for registry ${registry} are invalid. Check environment variables or configuration.`);
|
|
348
|
+
}
|
|
349
|
+
// Force re-prompt for push credentials (only in interactive mode and if not from ENV)
|
|
350
|
+
const { inputUsername, inputPassword } = await inquirer_1.default.prompt([
|
|
351
|
+
{
|
|
352
|
+
type: 'input',
|
|
353
|
+
name: 'inputUsername',
|
|
354
|
+
message: `Please input push username for registry ${registry}:`,
|
|
355
|
+
validate: (input) => (input ? true : 'Username cannot be empty.'),
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
type: 'password',
|
|
359
|
+
name: 'inputPassword',
|
|
360
|
+
message: `Please input push password for registry ${registry}:`,
|
|
361
|
+
mask: '*',
|
|
362
|
+
validate: (input) => (input ? true : 'Password cannot be empty.'),
|
|
363
|
+
},
|
|
364
|
+
]);
|
|
365
|
+
finalPushUsername = inputUsername;
|
|
366
|
+
finalPushPassword = inputPassword;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Then validate PULL credentials
|
|
370
|
+
console.log(chalk_1.default.dim('Validating pull credentials...'));
|
|
371
|
+
let pullCredsValid = false;
|
|
372
|
+
let finalPullUsername = pullUsername;
|
|
373
|
+
let finalPullPassword = pullPassword;
|
|
374
|
+
// Explicitly indicate when environment variables are being used for pull credentials
|
|
375
|
+
if (isPushCredsFromEnv) {
|
|
376
|
+
console.log(chalk_1.default.green('Using pull credentials from PHYGRID_REGISTRY_PULL_* environment variables'));
|
|
377
|
+
}
|
|
378
|
+
// If pull credentials are not provided AND we are in interactive mode AND creds are not from ENV, prompt for them
|
|
379
|
+
if (!finalPullUsername || !finalPullPassword) {
|
|
380
|
+
if (!nonInteractive && !isPushCredsFromEnv) {
|
|
381
|
+
console.log(chalk_1.default.yellow('Registry pull credentials not found'));
|
|
382
|
+
const { inputUsername, inputPassword } = await inquirer_1.default.prompt([
|
|
383
|
+
{
|
|
384
|
+
type: 'input',
|
|
385
|
+
name: 'inputUsername',
|
|
386
|
+
message: `Please input pull username for registry ${registry} (read-only):`,
|
|
387
|
+
validate: (input) => (input ? true : 'Username cannot be empty.'),
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
type: 'password',
|
|
391
|
+
name: 'inputPassword',
|
|
392
|
+
message: `Please input pull password for registry ${registry} (read-only):`,
|
|
393
|
+
mask: '*',
|
|
394
|
+
validate: (input) => (input ? true : 'Password cannot be empty.'),
|
|
395
|
+
},
|
|
396
|
+
]);
|
|
397
|
+
finalPullUsername = inputUsername;
|
|
398
|
+
finalPullPassword = inputPassword;
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
// In non-interactive mode or if creds from ENV, throw error if pull creds are missing
|
|
402
|
+
throw new Error(`Pull credentials for registry ${registry} are missing. Check environment variables or configuration.`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
while (!pullCredsValid) {
|
|
406
|
+
try {
|
|
407
|
+
// Login with pull credentials
|
|
408
|
+
console.log(chalk_1.default.dim(`Logging in to registry ${registry} with pull username ${finalPullUsername}`));
|
|
409
|
+
try {
|
|
410
|
+
child_process_1.default.execSync(`docker login ${registry} -u ${finalPullUsername} -p ${finalPullPassword}`, {
|
|
411
|
+
stdio: 'pipe',
|
|
412
|
+
});
|
|
413
|
+
console.log(chalk_1.default.green('✓ Login successful with pull credentials'));
|
|
414
|
+
}
|
|
415
|
+
catch (loginError) {
|
|
416
|
+
// Create a cleaned error message that doesn't include credentials
|
|
417
|
+
console.log(chalk_1.default.red(`Error: Docker login to ${registry} with pull user ${finalPullUsername} failed.`));
|
|
418
|
+
// Extract just the error response from Docker, not the command with credentials
|
|
419
|
+
let errorMsg = loginError.message || '';
|
|
420
|
+
// Look for the pattern of Docker error messages
|
|
421
|
+
const errorMatch = errorMsg.match(/Error response from daemon: (.*)/);
|
|
422
|
+
if (errorMatch && errorMatch[1]) {
|
|
423
|
+
console.log(chalk_1.default.yellow(`Docker error: ${errorMatch[1]}`));
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
console.log(chalk_1.default.yellow('Docker authentication failed. Check pull credentials.'));
|
|
427
|
+
}
|
|
428
|
+
throw new Error(`Docker login to ${registry} with pull credentials failed. Authentication error.`);
|
|
429
|
+
}
|
|
430
|
+
// First verify they can pull
|
|
431
|
+
try {
|
|
432
|
+
// Try to pull the test image we just pushed
|
|
433
|
+
const pullTestImage = registry === 'docker.io'
|
|
434
|
+
? `${registry}/${orgName ||
|
|
435
|
+
finalPushUsername}/${testImageName}:${testPushTag}`
|
|
436
|
+
: `${registry}/${testImageName}:${testPushTag}`;
|
|
437
|
+
// Remove the image locally first to ensure we're actually testing pull
|
|
438
|
+
try {
|
|
439
|
+
child_process_1.default.execSync(`docker rmi ${pullTestImage}`, {
|
|
440
|
+
stdio: 'pipe',
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
// Ignore errors if image doesn't exist locally
|
|
445
|
+
}
|
|
446
|
+
child_process_1.default.execSync(`docker pull ${pullTestImage}`, {
|
|
447
|
+
stdio: 'pipe',
|
|
448
|
+
});
|
|
449
|
+
// Now verify if they can push (they shouldn't be able to)
|
|
450
|
+
try {
|
|
451
|
+
// Try to push a different tag to properly test push permissions
|
|
452
|
+
const pullTestTag = `test-${(0, uuid_1.v4)().substring(0, 8)}`;
|
|
453
|
+
const pushTestImage = registry === 'docker.io'
|
|
454
|
+
? `${registry}/${orgName ||
|
|
455
|
+
finalPushUsername}/${testImageName}:${pullTestTag}`
|
|
456
|
+
: `${registry}/${testImageName}:${pullTestTag}`;
|
|
457
|
+
child_process_1.default.execSync(`docker tag ${pullTestImage} ${pushTestImage}`, {
|
|
458
|
+
stdio: 'pipe',
|
|
459
|
+
});
|
|
460
|
+
child_process_1.default.execSync(`docker push ${pushTestImage}`, {
|
|
461
|
+
stdio: 'pipe',
|
|
462
|
+
});
|
|
463
|
+
// If push succeeds, credentials have too many permissions
|
|
464
|
+
console.log(chalk_1.default.red('Error: Pull credentials have push permissions. This is a security risk.'));
|
|
465
|
+
console.log(chalk_1.default.dim('Please enter different read-only credentials.'));
|
|
466
|
+
// If non-interactive or creds from ENV, throw error instead of prompting
|
|
467
|
+
if (nonInteractive || isPushCredsFromEnv) {
|
|
468
|
+
throw new Error(`Pull credentials for registry ${registry} have push permissions. This is a security risk. Provide read-only credentials.`);
|
|
469
|
+
}
|
|
470
|
+
// Force re-prompt for pull credentials (only in interactive mode)
|
|
471
|
+
const { inputUsername, inputPassword } = await inquirer_1.default.prompt([
|
|
472
|
+
{
|
|
473
|
+
type: 'input',
|
|
474
|
+
name: 'inputUsername',
|
|
475
|
+
message: `Please input pull username for registry ${registry} (read-only):`,
|
|
476
|
+
validate: (input) => (input ? true : 'Username cannot be empty.'),
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
type: 'password',
|
|
480
|
+
name: 'inputPassword',
|
|
481
|
+
message: `Please input pull password for registry ${registry} (read-only):`,
|
|
482
|
+
mask: '*',
|
|
483
|
+
validate: (input) => (input ? true : 'Password cannot be empty.'),
|
|
484
|
+
},
|
|
485
|
+
]);
|
|
486
|
+
finalPullUsername = inputUsername;
|
|
487
|
+
finalPullPassword = inputPassword;
|
|
488
|
+
// Clean up test images
|
|
489
|
+
try {
|
|
490
|
+
child_process_1.default.execSync(`docker rmi ${pullTestImage} ${pushTestImage}`, {
|
|
491
|
+
stdio: 'pipe',
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
catch (error) {
|
|
495
|
+
// Ignore cleanup errors
|
|
496
|
+
}
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
catch (pushError) {
|
|
500
|
+
// Push failed as expected for pull-only credentials
|
|
501
|
+
console.log(chalk_1.default.green('✓ Pull credentials validated (read-only access confirmed)'));
|
|
502
|
+
pullCredsValid = true;
|
|
503
|
+
}
|
|
504
|
+
// Clean up the test image
|
|
505
|
+
try {
|
|
506
|
+
child_process_1.default.execSync(`docker rmi ${pullTestImage}`, {
|
|
507
|
+
stdio: 'pipe',
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
catch (error) {
|
|
511
|
+
// Ignore cleanup errors
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
catch (pullError) {
|
|
515
|
+
console.log(chalk_1.default.red('Error: Pull credentials cannot pull from registry.'));
|
|
516
|
+
throw new Error('Pull credentials cannot pull');
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
if (error.message === 'Pull credentials cannot pull') {
|
|
521
|
+
// If non-interactive or creds from ENV, throw error instead of prompting
|
|
522
|
+
if (nonInteractive || isPushCredsFromEnv) {
|
|
523
|
+
throw new Error(`Pull credentials for registry ${registry} are invalid or cannot pull. Check environment variables or configuration.`);
|
|
524
|
+
}
|
|
525
|
+
// Force re-prompt for pull credentials (only in interactive mode)
|
|
526
|
+
const { inputUsername, inputPassword } = await inquirer_1.default.prompt([
|
|
527
|
+
{
|
|
528
|
+
type: 'input',
|
|
529
|
+
name: 'inputUsername',
|
|
530
|
+
message: `Please input pull username for registry ${registry} (read-only):`,
|
|
531
|
+
validate: (input) => (input ? true : 'Username cannot be empty.'),
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
type: 'password',
|
|
535
|
+
name: 'inputPassword',
|
|
536
|
+
message: `Please input pull password for registry ${registry} (read-only):`,
|
|
537
|
+
mask: '*',
|
|
538
|
+
validate: (input) => (input ? true : 'Password cannot be empty.'),
|
|
539
|
+
},
|
|
540
|
+
]);
|
|
541
|
+
finalPullUsername = inputUsername;
|
|
542
|
+
finalPullPassword = inputPassword;
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
throw error;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
// Final cleanup - remove the test repository from Docker Hub
|
|
550
|
+
try {
|
|
551
|
+
const cleanupImage = registry === 'docker.io'
|
|
552
|
+
? `${registry}/${orgName ||
|
|
553
|
+
finalPushUsername}/${testImageName}:${testPushTag}`
|
|
554
|
+
: `${registry}/${testImageName}:${testPushTag}`;
|
|
555
|
+
// Try to remove the local image
|
|
556
|
+
try {
|
|
557
|
+
child_process_1.default.execSync(`docker rmi ${cleanupImage}`, {
|
|
558
|
+
stdio: 'pipe',
|
|
559
|
+
});
|
|
560
|
+
console.log(chalk_1.default.dim(`Local test image removed successfully.`));
|
|
561
|
+
}
|
|
562
|
+
catch (rmiError) {
|
|
563
|
+
// Fail gracefully if the image can't be removed locally
|
|
564
|
+
console.log(chalk_1.default.dim(`Note: Could not remove local test image.`));
|
|
565
|
+
}
|
|
566
|
+
// For Docker Hub, delete the entire test repository to avoid wasting private repo slots
|
|
567
|
+
if (registry === 'docker.io') {
|
|
568
|
+
const namespace = orgName || finalPushUsername;
|
|
569
|
+
await deleteDockerHubRepo(finalPushUsername, finalPushPassword, namespace, testImageName);
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
console.log(chalk_1.default.dim(`Note: Test image '${testImageName}' may remain in your registry. You can manually remove it if needed.`));
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
// Ignore cleanup errors
|
|
577
|
+
console.log(chalk_1.default.dim(`Note: Could not perform final cleanup. Test image '${testImageName}' may remain in your registry.`));
|
|
578
|
+
}
|
|
579
|
+
// Save the validation hash after successful validation
|
|
580
|
+
const validationHash = (0, registry_credentials_1.generateCredentialHash)(registry, finalPullUsername, finalPullPassword, finalPushUsername, finalPushPassword);
|
|
581
|
+
await (0, registry_credentials_1.saveValidationCache)(registry, validationHash, finalPullUsername, finalPushUsername);
|
|
582
|
+
// Log the hash that can be used for CI
|
|
583
|
+
console.log(chalk_1.default.green('✓ Credentials validated successfully'));
|
|
584
|
+
console.log(chalk_1.default.dim(`To skip validation in CI pipelines, set the following environment variable:`));
|
|
585
|
+
console.log(chalk_1.default.cyan(`PHYGRID_REGISTRY_VALIDATION_HASH=${validationHash}`));
|
|
586
|
+
console.log(chalk_1.default.dim('This hash verifies that pull credentials are read-only and push credentials have write access.'));
|
|
587
|
+
return {
|
|
588
|
+
pullUsername: finalPullUsername,
|
|
589
|
+
pullPassword: finalPullPassword,
|
|
590
|
+
pushUsername: finalPushUsername,
|
|
591
|
+
pushPassword: finalPushPassword,
|
|
592
|
+
};
|
|
593
|
+
};
|
|
594
|
+
exports.verifyDockerCredentials = verifyDockerCredentials;
|
|
595
|
+
/**
|
|
596
|
+
* Verify and set up Docker credentials from various sources
|
|
597
|
+
* Used by both app create and build-container commands
|
|
598
|
+
*/
|
|
599
|
+
const setupDockerCredentials = async (registry, name, packageFile, customImageName, nonInteractive = false) => {
|
|
600
|
+
console.log(chalk_1.default.dim('Verifying Docker registry credentials'));
|
|
601
|
+
let pullUsername = null;
|
|
602
|
+
let pullPassword = null;
|
|
603
|
+
let pushUsername = null;
|
|
604
|
+
let pushPassword = null;
|
|
605
|
+
let isPushCredsFromEnv = false;
|
|
606
|
+
let isPullCredsFromEnv = false;
|
|
607
|
+
// 1. First, attempt to resolve pull credentials from package.json
|
|
608
|
+
if (fs_extra_1.default.existsSync(packageFile)) {
|
|
609
|
+
try {
|
|
610
|
+
const packageData = JSON.parse(fs_extra_1.default.readFileSync(packageFile).toString());
|
|
611
|
+
const dockerCredentials = packageData['docker-credentials'];
|
|
612
|
+
if (dockerCredentials?.pull?.username && dockerCredentials?.pull?.password) {
|
|
613
|
+
pullUsername = dockerCredentials.pull.username;
|
|
614
|
+
pullPassword = dockerCredentials.pull.password;
|
|
615
|
+
console.log(chalk_1.default.dim('Using pull credentials from package.json'));
|
|
616
|
+
}
|
|
617
|
+
// If push credentials are present in package.json, we do NOT use them at this stage
|
|
618
|
+
}
|
|
619
|
+
catch (error) {
|
|
620
|
+
console.warn(chalk_1.default.yellow(`Warning: Could not read credentials from ${packageFile}: ${error.message}`));
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
// 2. Next, look for push credentials in environment variables
|
|
624
|
+
const envPullUser = process.env.PHYGRID_REGISTRY_PULL_USERNAME;
|
|
625
|
+
const envPullPass = process.env.PHYGRID_REGISTRY_PULL_PASSWORD;
|
|
626
|
+
const envPushUser = process.env.PHYGRID_REGISTRY_PUSH_USERNAME;
|
|
627
|
+
const envPushPass = process.env.PHYGRID_REGISTRY_PUSH_PASSWORD;
|
|
628
|
+
if (envPushUser && envPushPass) {
|
|
629
|
+
pushUsername = envPushUser;
|
|
630
|
+
pushPassword = envPushPass;
|
|
631
|
+
isPushCredsFromEnv = true;
|
|
632
|
+
console.log(chalk_1.default.dim('Using push credentials from environment variables'));
|
|
633
|
+
}
|
|
634
|
+
// For pull credentials, only assign from env if any pull credential is not set
|
|
635
|
+
if (envPullUser && envPullPass) {
|
|
636
|
+
const assignedFromEnv = !pullUsername || !pullPassword;
|
|
637
|
+
pullUsername = pullUsername || envPullUser;
|
|
638
|
+
pullPassword = pullPassword || envPullPass;
|
|
639
|
+
if (assignedFromEnv) {
|
|
640
|
+
isPullCredsFromEnv = true;
|
|
641
|
+
console.log(chalk_1.default.dim('Using pull credentials from environment variables (fallback)'));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
// 3. Last, use credentials file for any unset pull or push credentials
|
|
645
|
+
const storedCredentials = await (0, registry_credentials_1.getRegistryCredentials)(registry);
|
|
646
|
+
if (storedCredentials) {
|
|
647
|
+
if (!pullUsername && storedCredentials.pull?.username) {
|
|
648
|
+
pullUsername = storedCredentials.pull.username;
|
|
649
|
+
console.log(chalk_1.default.dim('Using pull username from stored credentials'));
|
|
650
|
+
}
|
|
651
|
+
if (!pullPassword && storedCredentials.pull?.password) {
|
|
652
|
+
pullPassword = storedCredentials.pull.password;
|
|
653
|
+
console.log(chalk_1.default.dim('Using pull password from stored credentials'));
|
|
654
|
+
}
|
|
655
|
+
if (!pushUsername && storedCredentials.push?.username) {
|
|
656
|
+
pushUsername = storedCredentials.push.username;
|
|
657
|
+
console.log(chalk_1.default.dim('Using push username from stored credentials'));
|
|
658
|
+
}
|
|
659
|
+
if (!pushPassword && storedCredentials.push?.password) {
|
|
660
|
+
pushPassword = storedCredentials.push.password;
|
|
661
|
+
console.log(chalk_1.default.dim('Using push password from stored credentials'));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
// If running non-interactively (e.g., CI) and no credentials found from any source, throw error.
|
|
665
|
+
if (nonInteractive && (!pullUsername || !pullPassword || !pushUsername || !pushPassword)) {
|
|
666
|
+
throw new Error('Missing required Docker credentials in non-interactive mode. Please set PHYGRID_REGISTRY_PULL/PUSH_USERNAME/PASSWORD environment variables.');
|
|
667
|
+
}
|
|
668
|
+
// Pass the nonInteractive flag and isPushCredsFromEnv flag to verifyDockerCredentials
|
|
669
|
+
const verifiedCredentials = await (0, exports.verifyDockerCredentials)(registry, pullUsername, pullPassword, pushUsername, pushPassword, customImageName, nonInteractive, // Pass the flag
|
|
670
|
+
isPushCredsFromEnv);
|
|
671
|
+
// Store the verified credentials in home directory (only if not from env vars or non-interactive?)
|
|
672
|
+
// For CI, we might not want to write back to the home dir. Let's skip saving if push creds came from ENV.
|
|
673
|
+
if (!isPushCredsFromEnv) {
|
|
674
|
+
await (0, registry_credentials_1.saveRegistryCredentials)(registry, {
|
|
675
|
+
pull: {
|
|
676
|
+
username: verifiedCredentials.pullUsername,
|
|
677
|
+
password: verifiedCredentials.pullPassword,
|
|
678
|
+
},
|
|
679
|
+
push: {
|
|
680
|
+
username: verifiedCredentials.pushUsername,
|
|
681
|
+
password: verifiedCredentials.pushPassword,
|
|
682
|
+
},
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
// Update package.json with only pull credentials (only if pull creds didn't come from env)
|
|
686
|
+
// We only want to update package.json if the source wasn't env vars, to avoid overwriting things unintentionally.
|
|
687
|
+
if (!isPullCredsFromEnv && fs_extra_1.default.existsSync(packageFile)) {
|
|
688
|
+
try {
|
|
689
|
+
let packageData = JSON.parse(fs_extra_1.default.readFileSync(packageFile).toString());
|
|
690
|
+
// Make sure docker-credentials exists
|
|
691
|
+
if (!packageData['docker-credentials']) {
|
|
692
|
+
packageData['docker-credentials'] = {};
|
|
693
|
+
}
|
|
694
|
+
// Update pull credentials
|
|
695
|
+
packageData['docker-credentials'].pull = {
|
|
696
|
+
username: verifiedCredentials.pullUsername,
|
|
697
|
+
password: verifiedCredentials.pullPassword, // Note: Storing password in package.json is generally discouraged
|
|
698
|
+
};
|
|
699
|
+
// Make sure registry is set in docker-credentials
|
|
700
|
+
if (registry && !packageData['docker-credentials'].registry) {
|
|
701
|
+
packageData['docker-credentials'].registry = registry;
|
|
702
|
+
}
|
|
703
|
+
// If customImageName was provided, make sure it's set in docker-credentials
|
|
704
|
+
if (customImageName && !packageData['docker-credentials'].image) {
|
|
705
|
+
packageData['docker-credentials'].image = customImageName;
|
|
706
|
+
}
|
|
707
|
+
// Remove container-registry from root if it exists
|
|
708
|
+
if (packageData['container-registry']) {
|
|
709
|
+
delete packageData['container-registry'];
|
|
710
|
+
}
|
|
711
|
+
fs_extra_1.default.writeFileSync(packageFile, JSON.stringify(packageData, null, 2));
|
|
712
|
+
}
|
|
713
|
+
catch (error) {
|
|
714
|
+
console.warn(chalk_1.default.yellow(`Warning: Could not update credentials in ${packageFile}: ${error.message}`));
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return verifiedCredentials;
|
|
718
|
+
};
|
|
719
|
+
exports.setupDockerCredentials = setupDockerCredentials;
|
|
720
|
+
//# sourceMappingURL=docker-credentials.js.map
|