From 734eb0635f646d153a1cb37ad5fa92b1ba1c078d Mon Sep 17 00:00:00 2001 From: Aleh Yablonski Date: Tue, 10 Feb 2026 14:42:31 +0300 Subject: [PATCH 1/3] feat(instance): add length of instance hash field --- src/server/controllers/generate.ts | 4 ++++ src/server/utils/common.ts | 10 +++++++++- src/server/utils/instance/index.ts | 19 ++++++++++--------- src/shared/api/generate.ts | 1 + src/shared/common.ts | 1 + src/ui/containers/Create/Create.tsx | 1 + .../CreateFormContent/CreateFormContent.tsx | 4 ++++ .../Create/CreateFormContent/i18n/en.json | 1 + .../Create/CreateFormContent/i18n/ru.json | 1 + src/ui/containers/Create/types.ts | 5 ++++- src/ui/containers/Create/validate.ts | 1 + 11 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/server/controllers/generate.ts b/src/server/controllers/generate.ts index 69596c2..9612cd6 100644 --- a/src/server/controllers/generate.ts +++ b/src/server/controllers/generate.ts @@ -18,6 +18,7 @@ const schema = z.object({ instanceConfigName: z.string().optional(), labels: z.record(z.string(), z.string()).optional(), stopTimeout: z.number().optional(), + instanceHashLength: z.number().positive().min(4).optional(), }); const generate = async (req: Request, res: Response) => { @@ -38,6 +39,7 @@ const generate = async (req: Request, res: Response) => { instanceConfigName = '', labels, stopTimeout, + instanceHashLength, ...restParameters } = parsed.data; @@ -94,6 +96,7 @@ const generate = async (req: Request, res: Response) => { vcs, additionalEnvVariables: finalEnvVariables, additionalRunEnvVariables: finalRunEnvVariables, + instanceHashLength, }); let generateErrorMessage: string | null = null; @@ -110,6 +113,7 @@ const generate = async (req: Request, res: Response) => { instanceConfigName, labels: finalLabels, stopTimeout, + hash, }) .catch((e: Error) => { req.ctx.logError('GENERATE ERROR:', wrapInternalError(e)); diff --git a/src/server/utils/common.ts b/src/server/utils/common.ts index 3bcb69b..2d6258b 100644 --- a/src/server/utils/common.ts +++ b/src/server/utils/common.ts @@ -22,6 +22,8 @@ export function generateInstanceHash( additionalEnvVariables: Instance['envVariables']; /** @description additionalRunEnvVariables - only for defined on generation request, and not from already defined configurations */ additionalRunEnvVariables: Instance['runEnvVariables']; + /** @description max length of instance hash; */ + instanceHashLength?: number; }, ) { let { @@ -47,7 +49,13 @@ export function generateInstanceHash( runEnvVariables: additionalRunEnvVariables, }); // replace first number with alphabet symbol for support most domain's standard implementation - return hash.replace(/^[0-9]{1}/, 'x'); + let result = hash.replace(/^[0-9]{1}/, 'x'); + + if (info.instanceHashLength && info.instanceHashLength > 0) { + result = result.slice(0, info.instanceHashLength); + } + + return result; } /** @deprecated */ diff --git a/src/server/utils/instance/index.ts b/src/server/utils/instance/index.ts index 897268f..2c9a0ab 100644 --- a/src/server/utils/instance/index.ts +++ b/src/server/utils/instance/index.ts @@ -13,14 +13,16 @@ import {RunningLimitsErrorMessage, isGenerateAllowedByLimits} from './limiter'; * @throws {Error} if instance is not allowed to generate */ async function addInstanceToGenerateQueue(instanceInfo: InstanceInfo) { - const hash = generateInstanceHash({ - branch: instanceInfo.branch, - instanceConfigName: instanceInfo.instanceConfigName, - project: instanceInfo.project, - vcs: instanceInfo.vcs, - additionalEnvVariables: instanceInfo.envVariables ?? {}, - additionalRunEnvVariables: instanceInfo.runEnvVariables ?? {}, - }); + const hash = + instanceInfo.hash ?? + generateInstanceHash({ + branch: instanceInfo.branch, + instanceConfigName: instanceInfo.instanceConfigName, + project: instanceInfo.project, + vcs: instanceInfo.vcs, + additionalEnvVariables: instanceInfo.envVariables ?? {}, + additionalRunEnvVariables: instanceInfo.runEnvVariables ?? {}, + }); const isGenerateAllowed = await isGenerateAllowedByLimits(instanceInfo.project, hash); if (!isGenerateAllowed) { @@ -87,7 +89,6 @@ const restartInstance = async (instance: Instance) => { /** * Get all projects with instances - * * @returns Array of projects with instances */ const getProjectsWithInstances = async () => { diff --git a/src/shared/api/generate.ts b/src/shared/api/generate.ts index bd28913..9606092 100644 --- a/src/shared/api/generate.ts +++ b/src/shared/api/generate.ts @@ -7,6 +7,7 @@ export type GenerateInstanceRequest = { instanceConfigName?: string; labels?: Record; stopTimeout?: number; + instanceHashLength?: number; [key: string]: unknown; }; diff --git a/src/shared/common.ts b/src/shared/common.ts index c84a811..c3d9cec 100644 --- a/src/shared/common.ts +++ b/src/shared/common.ts @@ -24,6 +24,7 @@ export interface Instance { description?: string; instanceConfigName: string; stopTimeout?: number; + instanceHashLength?: number; } export interface InstanceWithProviderStatus extends Instance { diff --git a/src/ui/containers/Create/Create.tsx b/src/ui/containers/Create/Create.tsx index d1a0d40..6862797 100644 --- a/src/ui/containers/Create/Create.tsx +++ b/src/ui/containers/Create/Create.tsx @@ -85,6 +85,7 @@ export const Create = () => { vcs: fv.vcs, instanceConfigName: fv.instanceConfigName, labels: {}, + instanceHashLength: Number(fv.instanceHashLength), }; if (Array.isArray(fv.variables)) { diff --git a/src/ui/containers/Create/CreateFormContent/CreateFormContent.tsx b/src/ui/containers/Create/CreateFormContent/CreateFormContent.tsx index 2f8588d..ee33ddf 100644 --- a/src/ui/containers/Create/CreateFormContent/CreateFormContent.tsx +++ b/src/ui/containers/Create/CreateFormContent/CreateFormContent.tsx @@ -193,6 +193,10 @@ export const CreateFormContent = ({ )} + + + + diff --git a/src/ui/containers/Create/CreateFormContent/i18n/en.json b/src/ui/containers/Create/CreateFormContent/i18n/en.json index 439fd21..452f677 100644 --- a/src/ui/containers/Create/CreateFormContent/i18n/en.json +++ b/src/ui/containers/Create/CreateFormContent/i18n/en.json @@ -4,6 +4,7 @@ "description": "Description", "url-template": "URL template", "instance-config": "Instance config", + "instance-hash-length": "Instance hash length", "variables": "Environment variables", "run-variables": "Runtime environment variables", "labels": "Labels" diff --git a/src/ui/containers/Create/CreateFormContent/i18n/ru.json b/src/ui/containers/Create/CreateFormContent/i18n/ru.json index 84c6c82..8012875 100644 --- a/src/ui/containers/Create/CreateFormContent/i18n/ru.json +++ b/src/ui/containers/Create/CreateFormContent/i18n/ru.json @@ -4,6 +4,7 @@ "description": "Описание", "url-template": "Шаблон URL", "instance-config": "Конфиг инстанса", + "instance-hash-length": "Длина хэша инстанса", "variables": "Переменные окружения", "run-variables": "Переменные окружения рантайма", "labels": "Метки" diff --git a/src/ui/containers/Create/types.ts b/src/ui/containers/Create/types.ts index 8da16f8..31fd1ce 100644 --- a/src/ui/containers/Create/types.ts +++ b/src/ui/containers/Create/types.ts @@ -14,4 +14,7 @@ export type FormValue = Required< runVariables: QSVariables[]; labels: QSVariables[]; } ->; +> & { + // Form treats number input as string, so we need to convert it to number when submitting. + instanceHashLength?: string; +}; diff --git a/src/ui/containers/Create/validate.ts b/src/ui/containers/Create/validate.ts index 65635fc..ecda457 100644 --- a/src/ui/containers/Create/validate.ts +++ b/src/ui/containers/Create/validate.ts @@ -9,6 +9,7 @@ const schema = z project: z.string(), vcs: z.string(), branch: z.string(), + instanceHashLength: z.number().min(4).positive().optional(), }) .required(); From 4814eae9ad3e1e15cf9218ced1efc89cce24afe5 Mon Sep 17 00:00:00 2001 From: Aleh Yablonski Date: Tue, 10 Feb 2026 15:31:00 +0300 Subject: [PATCH 2/3] fix(instance): move instance hash length set up to farm config --- docs/en/farm-config-json.md | 5 +++++ docs/ru/farm-config-json.md | 5 +++++ src/server/controllers/generate.ts | 3 --- src/server/utils/common.ts | 8 ++++---- src/server/utils/queue/constants.ts | 2 ++ src/shared/api/generate.ts | 1 - src/shared/common.ts | 2 +- src/ui/containers/Create/Create.tsx | 1 - .../Create/CreateFormContent/CreateFormContent.tsx | 4 ---- src/ui/containers/Create/CreateFormContent/i18n/en.json | 1 - src/ui/containers/Create/CreateFormContent/i18n/ru.json | 1 - src/ui/containers/Create/types.ts | 5 +---- src/ui/containers/Create/validate.ts | 1 - 13 files changed, 18 insertions(+), 21 deletions(-) diff --git a/docs/en/farm-config-json.md b/docs/en/farm-config-json.md index 6b1afa2..71d1d6e 100644 --- a/docs/en/farm-config-json.md +++ b/docs/en/farm-config-json.md @@ -51,6 +51,11 @@ Git repository format: `https://@github.com/.git`. - *Optional* > Maximum number of running instances. No limit by default. When limit is reached, new builds will be rejected. +### `instanceHashLength` +- *Number* +- *Optional* +> Maximum length of instance hash . If set, the hash is truncated to this length. + ### `defaultBranch` - *String* - *Required* diff --git a/docs/ru/farm-config-json.md b/docs/ru/farm-config-json.md index 0f623b0..ae787fb 100644 --- a/docs/ru/farm-config-json.md +++ b/docs/ru/farm-config-json.md @@ -51,6 +51,11 @@ - *Optional* > Максимальное количество запущенных инстансов. По умолчанию без ограничения. Когда лимит достигнут, новые сборки будут отклонены. +### `instanceHashLength` +- *Number* +- *Optional* +> Максимальная длина хэша инстанса. Если задано, хэш обрезается до этой длины. + ### `defaultBranch` - *String* - *Required* diff --git a/src/server/controllers/generate.ts b/src/server/controllers/generate.ts index 9612cd6..c8109e3 100644 --- a/src/server/controllers/generate.ts +++ b/src/server/controllers/generate.ts @@ -18,7 +18,6 @@ const schema = z.object({ instanceConfigName: z.string().optional(), labels: z.record(z.string(), z.string()).optional(), stopTimeout: z.number().optional(), - instanceHashLength: z.number().positive().min(4).optional(), }); const generate = async (req: Request, res: Response) => { @@ -39,7 +38,6 @@ const generate = async (req: Request, res: Response) => { instanceConfigName = '', labels, stopTimeout, - instanceHashLength, ...restParameters } = parsed.data; @@ -96,7 +94,6 @@ const generate = async (req: Request, res: Response) => { vcs, additionalEnvVariables: finalEnvVariables, additionalRunEnvVariables: finalRunEnvVariables, - instanceHashLength, }); let generateErrorMessage: string | null = null; diff --git a/src/server/utils/common.ts b/src/server/utils/common.ts index 2d6258b..a6e6cbe 100644 --- a/src/server/utils/common.ts +++ b/src/server/utils/common.ts @@ -10,6 +10,8 @@ import {envConfig} from '../configs/env'; import type {InstanceInfo} from '../models/common'; import type {FarmConfig} from '../models/farmConfig'; +import {INSTANCE_HASH_LENGTH} from './queue/constants'; + export function getProviderConfig() { // expected directly using return envConfig.farmProvider; @@ -22,8 +24,6 @@ export function generateInstanceHash( additionalEnvVariables: Instance['envVariables']; /** @description additionalRunEnvVariables - only for defined on generation request, and not from already defined configurations */ additionalRunEnvVariables: Instance['runEnvVariables']; - /** @description max length of instance hash; */ - instanceHashLength?: number; }, ) { let { @@ -51,8 +51,8 @@ export function generateInstanceHash( // replace first number with alphabet symbol for support most domain's standard implementation let result = hash.replace(/^[0-9]{1}/, 'x'); - if (info.instanceHashLength && info.instanceHashLength > 0) { - result = result.slice(0, info.instanceHashLength); + if (INSTANCE_HASH_LENGTH) { + result = result.slice(0, Math.max(INSTANCE_HASH_LENGTH, 4)); } return result; diff --git a/src/server/utils/queue/constants.ts b/src/server/utils/queue/constants.ts index e94e112..e3e766b 100644 --- a/src/server/utils/queue/constants.ts +++ b/src/server/utils/queue/constants.ts @@ -1,3 +1,5 @@ import {getGlobalFarmConfig} from '../common'; export const BUILDS_LIMIT = getGlobalFarmConfig().maxConcurrentBuilds || 3; + +export const INSTANCE_HASH_LENGTH = getGlobalFarmConfig().instanceHashLength; diff --git a/src/shared/api/generate.ts b/src/shared/api/generate.ts index 9606092..bd28913 100644 --- a/src/shared/api/generate.ts +++ b/src/shared/api/generate.ts @@ -7,7 +7,6 @@ export type GenerateInstanceRequest = { instanceConfigName?: string; labels?: Record; stopTimeout?: number; - instanceHashLength?: number; [key: string]: unknown; }; diff --git a/src/shared/common.ts b/src/shared/common.ts index c3d9cec..90b572e 100644 --- a/src/shared/common.ts +++ b/src/shared/common.ts @@ -24,7 +24,6 @@ export interface Instance { description?: string; instanceConfigName: string; stopTimeout?: number; - instanceHashLength?: number; } export interface InstanceWithProviderStatus extends Instance { @@ -77,6 +76,7 @@ export interface FarmConfigBase { urlTemplate?: string; defaultBranch?: string; vcs?: string; + instanceHashLength?: number; vcsCredentials?: { [key: string]: VcsCredentialsConfig | undefined; diff --git a/src/ui/containers/Create/Create.tsx b/src/ui/containers/Create/Create.tsx index 6862797..d1a0d40 100644 --- a/src/ui/containers/Create/Create.tsx +++ b/src/ui/containers/Create/Create.tsx @@ -85,7 +85,6 @@ export const Create = () => { vcs: fv.vcs, instanceConfigName: fv.instanceConfigName, labels: {}, - instanceHashLength: Number(fv.instanceHashLength), }; if (Array.isArray(fv.variables)) { diff --git a/src/ui/containers/Create/CreateFormContent/CreateFormContent.tsx b/src/ui/containers/Create/CreateFormContent/CreateFormContent.tsx index ee33ddf..2f8588d 100644 --- a/src/ui/containers/Create/CreateFormContent/CreateFormContent.tsx +++ b/src/ui/containers/Create/CreateFormContent/CreateFormContent.tsx @@ -193,10 +193,6 @@ export const CreateFormContent = ({ )} - - - - diff --git a/src/ui/containers/Create/CreateFormContent/i18n/en.json b/src/ui/containers/Create/CreateFormContent/i18n/en.json index 452f677..439fd21 100644 --- a/src/ui/containers/Create/CreateFormContent/i18n/en.json +++ b/src/ui/containers/Create/CreateFormContent/i18n/en.json @@ -4,7 +4,6 @@ "description": "Description", "url-template": "URL template", "instance-config": "Instance config", - "instance-hash-length": "Instance hash length", "variables": "Environment variables", "run-variables": "Runtime environment variables", "labels": "Labels" diff --git a/src/ui/containers/Create/CreateFormContent/i18n/ru.json b/src/ui/containers/Create/CreateFormContent/i18n/ru.json index 8012875..84c6c82 100644 --- a/src/ui/containers/Create/CreateFormContent/i18n/ru.json +++ b/src/ui/containers/Create/CreateFormContent/i18n/ru.json @@ -4,7 +4,6 @@ "description": "Описание", "url-template": "Шаблон URL", "instance-config": "Конфиг инстанса", - "instance-hash-length": "Длина хэша инстанса", "variables": "Переменные окружения", "run-variables": "Переменные окружения рантайма", "labels": "Метки" diff --git a/src/ui/containers/Create/types.ts b/src/ui/containers/Create/types.ts index 31fd1ce..8da16f8 100644 --- a/src/ui/containers/Create/types.ts +++ b/src/ui/containers/Create/types.ts @@ -14,7 +14,4 @@ export type FormValue = Required< runVariables: QSVariables[]; labels: QSVariables[]; } -> & { - // Form treats number input as string, so we need to convert it to number when submitting. - instanceHashLength?: string; -}; +>; diff --git a/src/ui/containers/Create/validate.ts b/src/ui/containers/Create/validate.ts index ecda457..65635fc 100644 --- a/src/ui/containers/Create/validate.ts +++ b/src/ui/containers/Create/validate.ts @@ -9,7 +9,6 @@ const schema = z project: z.string(), vcs: z.string(), branch: z.string(), - instanceHashLength: z.number().min(4).positive().optional(), }) .required(); From eae0b0c38b8c416cbf9a77671bb38cc878a847d9 Mon Sep 17 00:00:00 2001 From: Aleh Yablonski Date: Wed, 11 Feb 2026 12:46:04 +0300 Subject: [PATCH 3/3] fix(instance): make hash required for generating --- docs/en/farm-config-json.md | 2 +- docs/ru/farm-config-json.md | 2 +- src/server/controllers/generate.ts | 1 - src/server/utils/common.ts | 4 ++-- src/server/utils/instance/index.ts | 18 ++++++++---------- src/server/utils/queue/constants.ts | 2 -- 6 files changed, 12 insertions(+), 17 deletions(-) diff --git a/docs/en/farm-config-json.md b/docs/en/farm-config-json.md index 71d1d6e..090bb73 100644 --- a/docs/en/farm-config-json.md +++ b/docs/en/farm-config-json.md @@ -54,7 +54,7 @@ Git repository format: `https://@github.com/.git`. ### `instanceHashLength` - *Number* - *Optional* -> Maximum length of instance hash . If set, the hash is truncated to this length. +> Maximum length of instance hash. If set, the hash is truncated to this length. Minimum value is 4; when not set, no truncation is applied. ### `defaultBranch` - *String* diff --git a/docs/ru/farm-config-json.md b/docs/ru/farm-config-json.md index ae787fb..ddb30db 100644 --- a/docs/ru/farm-config-json.md +++ b/docs/ru/farm-config-json.md @@ -54,7 +54,7 @@ ### `instanceHashLength` - *Number* - *Optional* -> Максимальная длина хэша инстанса. Если задано, хэш обрезается до этой длины. +> Максимальная длина хэша инстанса. Если задано, хэш обрезается до этой длины. Минимальное значение — 4; если не задано, обрезка не применяется. ### `defaultBranch` - *String* diff --git a/src/server/controllers/generate.ts b/src/server/controllers/generate.ts index c8109e3..69596c2 100644 --- a/src/server/controllers/generate.ts +++ b/src/server/controllers/generate.ts @@ -110,7 +110,6 @@ const generate = async (req: Request, res: Response) => { instanceConfigName, labels: finalLabels, stopTimeout, - hash, }) .catch((e: Error) => { req.ctx.logError('GENERATE ERROR:', wrapInternalError(e)); diff --git a/src/server/utils/common.ts b/src/server/utils/common.ts index a6e6cbe..33de362 100644 --- a/src/server/utils/common.ts +++ b/src/server/utils/common.ts @@ -10,13 +10,13 @@ import {envConfig} from '../configs/env'; import type {InstanceInfo} from '../models/common'; import type {FarmConfig} from '../models/farmConfig'; -import {INSTANCE_HASH_LENGTH} from './queue/constants'; - export function getProviderConfig() { // expected directly using return envConfig.farmProvider; } +const INSTANCE_HASH_LENGTH = getGlobalFarmConfig().instanceHashLength; + export function generateInstanceHash( // required to ensure passing all props info: Required> & { diff --git a/src/server/utils/instance/index.ts b/src/server/utils/instance/index.ts index 2c9a0ab..03c4c70 100644 --- a/src/server/utils/instance/index.ts +++ b/src/server/utils/instance/index.ts @@ -13,16 +13,14 @@ import {RunningLimitsErrorMessage, isGenerateAllowedByLimits} from './limiter'; * @throws {Error} if instance is not allowed to generate */ async function addInstanceToGenerateQueue(instanceInfo: InstanceInfo) { - const hash = - instanceInfo.hash ?? - generateInstanceHash({ - branch: instanceInfo.branch, - instanceConfigName: instanceInfo.instanceConfigName, - project: instanceInfo.project, - vcs: instanceInfo.vcs, - additionalEnvVariables: instanceInfo.envVariables ?? {}, - additionalRunEnvVariables: instanceInfo.runEnvVariables ?? {}, - }); + const hash = generateInstanceHash({ + branch: instanceInfo.branch, + instanceConfigName: instanceInfo.instanceConfigName, + project: instanceInfo.project, + vcs: instanceInfo.vcs, + additionalEnvVariables: instanceInfo.envVariables ?? {}, + additionalRunEnvVariables: instanceInfo.runEnvVariables ?? {}, + }); const isGenerateAllowed = await isGenerateAllowedByLimits(instanceInfo.project, hash); if (!isGenerateAllowed) { diff --git a/src/server/utils/queue/constants.ts b/src/server/utils/queue/constants.ts index e3e766b..e94e112 100644 --- a/src/server/utils/queue/constants.ts +++ b/src/server/utils/queue/constants.ts @@ -1,5 +1,3 @@ import {getGlobalFarmConfig} from '../common'; export const BUILDS_LIMIT = getGlobalFarmConfig().maxConcurrentBuilds || 3; - -export const INSTANCE_HASH_LENGTH = getGlobalFarmConfig().instanceHashLength;