registry config: create table and migrate existing setting

This commit is contained in:
Girish Ramakrishnan
2025-05-07 14:09:10 +02:00
parent 91fd93c724
commit 1bbdabc42f
15 changed files with 441 additions and 123 deletions

View File

@@ -1,10 +1,6 @@
'use strict';
exports = module.exports = {
removePrivateFields,
getRegistryConfig,
setRegistryConfig,
ping,
info,
@@ -41,42 +37,19 @@ const apps = require('./apps.js'),
dashboard = require('./dashboard.js'),
debug = require('debug')('box:docker'),
Docker = require('dockerode'),
dockerRegistries = require('./dockerregistries.js'),
fs = require('fs'),
mailServer = require('./mailserver.js'),
os = require('os'),
paths = require('./paths.js'),
promiseRetry = require('./promise-retry.js'),
services = require('./services.js'),
settings = require('./settings.js'),
shell = require('./shell.js')('docker'),
safe = require('safetydance'),
timers = require('timers/promises'),
tld = require('tldjs'),
volumes = require('./volumes.js');
const DOCKER_SOCKET_PATH = '/var/run/docker.sock';
const gConnection = new Docker({ socketPath: DOCKER_SOCKET_PATH });
async function testRegistryConfig(config) {
assert.strictEqual(typeof config, 'object');
if (config.provider === 'noop') return;
const [error] = await safe(gConnection.checkAuth(config)); // this returns a 500 even for auth errors
if (error) throw new BoxError(BoxError.BAD_FIELD, `Invalid serverAddress: ${error.message}`);
}
function injectPrivateFields(newConfig, currentConfig) {
if (newConfig.password === constants.SECRET_PLACEHOLDER) newConfig.password = currentConfig.password;
}
function removePrivateFields(registryConfig) {
assert.strictEqual(typeof registryConfig, 'object');
if (registryConfig.password) registryConfig.password = constants.SECRET_PLACEHOLDER;
return registryConfig;
}
const gConnection = new Docker({ socketPath: paths.DOCKER_SOCKET_PATH });
function parseImageRef(imageRef) {
assert.strictEqual(typeof imageRef, 'string');
@@ -101,7 +74,7 @@ function parseImageRef(imageRef) {
async function ping() {
// do not let the request linger
const connection = new Docker({ socketPath: DOCKER_SOCKET_PATH, timeout: 1000 });
const connection = new Docker({ socketPath: paths.DOCKER_SOCKET_PATH, timeout: 1000 });
const [error, result] = await safe(connection.ping());
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
@@ -119,24 +92,27 @@ async function getAuthConfig(imageRef) {
// images in our cloudron namespace are always unauthenticated to not interfere with any user limits
if (parsedRef.registry === null && parsedRef.fullRepositoryName.startsWith('cloudron/')) return null;
const registryConfig = await getRegistryConfig();
if (registryConfig.provider === 'noop') return null;
const registries = await dockerRegistries.list();
if (registryConfig.serverAddress !== parsedRef.registry) { // ideally they match but there's too many docker registry domains!
if (!registryConfig.serverAddress.includes('.docker.')) return null;
if (parsedRef.registry !== null && !parsedRef.includes('.docker.')) return null;
for (const registry of registries) {
if (registry.serverAddress !== parsedRef.registry) { // ideally they match but there's too many docker registry domains!
if (!registry.serverAddress.includes('.docker.')) continue;
if (parsedRef.registry !== null && !parsedRef.includes('.docker.')) continue;
}
// https://github.com/apocas/dockerode#pull-from-private-repos
const authConfig = {
username: registry.username,
password: registry.password,
auth: registry.auth || '', // the auth token at login time
email: registry.email || '',
serveraddress: registry.serverAddress
};
return authConfig;
}
// https://github.com/apocas/dockerode#pull-from-private-repos
const autoConfig = {
username: registryConfig.username,
password: registryConfig.password,
auth: registryConfig.auth || '', // the auth token at login time
email: registryConfig.email || '',
serveraddress: registryConfig.serverAddress
};
return autoConfig;
return null;
}
async function pullImage(imageRef) {
@@ -722,36 +698,3 @@ async function update(name, memory) {
throw new BoxError(BoxError.DOCKER_ERROR, 'Unable to update container');
}
async function getRegistryConfig() {
const value = await settings.getJson(settings.REGISTRY_CONFIG_KEY);
return value || { provider: 'noop' };
}
function validateServerAddress(serverAddress) {
assert.strictEqual(typeof serverAddress, 'string');
// workaround https://github.com/oncletom/tld.js/issues/73
const tmp = serverAddress.replace('_', '-');
if (!tld.isValid(tmp)) return new BoxError(BoxError.BAD_FIELD, 'Hostname is not a valid domain name');
if (tmp.length > 253) return new BoxError(BoxError.BAD_FIELD, 'Hostname length exceeds 253 characters');
return null;
}
async function setRegistryConfig(registryConfig) {
assert.strictEqual(typeof registryConfig, 'object');
if (registryConfig.provider !== 'noop') {
const error = validateServerAddress(registryConfig.serverAddress);
if (error) throw error;
}
const currentConfig = await getRegistryConfig();
injectPrivateFields(registryConfig, currentConfig);
await testRegistryConfig(registryConfig);
await settings.setJson(settings.REGISTRY_CONFIG_KEY, registryConfig);
}