feat(deploy): externalRouting toggle to keep apps off Traefik
Adds a boolean `externalRouting` flag (default `true`) on
ResolvedContainerConfig. When `false`, TraefikLabelBuilder emits only
the identity labels (`managed-by`, `cameleer.*`) and skips every
`traefik.*` label, so the container is not published by Traefik.
Sibling containers on `cameleer-traefik` / `cameleer-env-{tenant}-{env}`
can still reach it via Docker DNS on whatever port the app listens on.
TDD: new TraefikLabelBuilderTest covers enabled (default labels present),
disabled (zero traefik.* labels), and disabled (identity labels retained)
cases. Full module unit suite: 208/0/0.
Plumbed through ConfigMerger read, DeploymentExecutor snapshot, UI form
state, Resources tab toggle, POST payload, and snapshot-to-form mapping.
Rule files updated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -67,7 +67,7 @@ describe('ConfigPanel', () => {
|
||||
resources: {
|
||||
memoryLimit: '256', memoryReserve: '', cpuRequest: '500', cpuLimit: '',
|
||||
appPort: '8080', replicas: '1', deployStrategy: 'blue-green',
|
||||
stripPrefix: true, sslOffloading: true, runtimeType: 'auto', customArgs: '',
|
||||
stripPrefix: true, sslOffloading: true, externalRouting: true, runtimeType: 'auto', customArgs: '',
|
||||
extraNetworks: [],
|
||||
},
|
||||
variables: { envVars: [] },
|
||||
|
||||
@@ -41,6 +41,7 @@ export function snapshotToForm(
|
||||
deployStrategy: (c.deploymentStrategy as string) ?? defaults.resources.deployStrategy,
|
||||
stripPrefix: c.stripPathPrefix !== undefined ? (c.stripPathPrefix as boolean) : defaults.resources.stripPrefix,
|
||||
sslOffloading: c.sslOffloading !== undefined ? (c.sslOffloading as boolean) : defaults.resources.sslOffloading,
|
||||
externalRouting: c.externalRouting !== undefined ? (c.externalRouting as boolean) : defaults.resources.externalRouting,
|
||||
runtimeType: (c.runtimeType as string) ?? defaults.resources.runtimeType,
|
||||
customArgs: c.customArgs !== undefined ? String(c.customArgs ?? '') : defaults.resources.customArgs,
|
||||
extraNetworks: Array.isArray(c.extraNetworks) ? (c.extraNetworks as string[]) : defaults.resources.extraNetworks,
|
||||
|
||||
@@ -171,6 +171,25 @@ export function ResourcesTab({ value, onChange, disabled, isProd = false }: Prop
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span className={styles.configLabel}>External Routing</span>
|
||||
<div>
|
||||
<div className={styles.configInline}>
|
||||
<Toggle
|
||||
checked={value.externalRouting}
|
||||
onChange={() => !disabled && update('externalRouting', !value.externalRouting)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<span className={value.externalRouting ? styles.toggleEnabled : styles.toggleDisabled}>
|
||||
{value.externalRouting ? 'Enabled' : 'Disabled'}
|
||||
</span>
|
||||
</div>
|
||||
<span className={styles.configHint}>
|
||||
{value.externalRouting
|
||||
? 'Traefik publishes the app at the configured path/subdomain.'
|
||||
: 'No Traefik labels emitted — the app is reachable only by sibling containers via Docker DNS.'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span className={styles.configLabel}>Extra Networks</span>
|
||||
<div>
|
||||
<div className={styles.portPills}>
|
||||
|
||||
@@ -52,6 +52,7 @@ const defaultResources: ResourcesFormState = {
|
||||
deployStrategy: 'blue-green',
|
||||
stripPrefix: true,
|
||||
sslOffloading: true,
|
||||
externalRouting: true,
|
||||
runtimeType: 'auto',
|
||||
customArgs: '',
|
||||
extraNetworks: [],
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface ResourcesFormState {
|
||||
deployStrategy: string;
|
||||
stripPrefix: boolean;
|
||||
sslOffloading: boolean;
|
||||
externalRouting: boolean;
|
||||
runtimeType: string;
|
||||
customArgs: string;
|
||||
extraNetworks: string[];
|
||||
@@ -66,7 +67,7 @@ export const defaultForm: DeploymentPageFormState = {
|
||||
resources: {
|
||||
memoryLimit: '512', memoryReserve: '', cpuRequest: '500', cpuLimit: '',
|
||||
appPort: '8080', replicas: '1', deployStrategy: 'blue-green',
|
||||
stripPrefix: true, sslOffloading: true, runtimeType: 'auto', customArgs: '',
|
||||
stripPrefix: true, sslOffloading: true, externalRouting: true, runtimeType: 'auto', customArgs: '',
|
||||
extraNetworks: [],
|
||||
},
|
||||
variables: { envVars: [] },
|
||||
@@ -112,6 +113,7 @@ export function useDeploymentPageState(
|
||||
deployStrategy: String(merged.deploymentStrategy ?? defaultForm.resources.deployStrategy),
|
||||
stripPrefix: merged.stripPathPrefix !== false,
|
||||
sslOffloading: merged.sslOffloading !== false,
|
||||
externalRouting: merged.externalRouting !== false,
|
||||
runtimeType: String(merged.runtimeType ?? defaultForm.resources.runtimeType),
|
||||
customArgs: String(merged.customArgs ?? defaultForm.resources.customArgs),
|
||||
extraNetworks: Array.isArray(merged.extraNetworks) ? (merged.extraNetworks as string[]) : defaultForm.resources.extraNetworks,
|
||||
|
||||
@@ -208,6 +208,7 @@ export default function AppDeploymentPage() {
|
||||
deploymentStrategy: r.deployStrategy,
|
||||
stripPathPrefix: r.stripPrefix,
|
||||
sslOffloading: r.sslOffloading,
|
||||
externalRouting: r.externalRouting,
|
||||
runtimeType: r.runtimeType,
|
||||
customArgs: r.customArgs || null,
|
||||
extraNetworks: r.extraNetworks,
|
||||
|
||||
Reference in New Issue
Block a user