feat: CPU/RAM limits per stage, NPM access list (global + per-project)

Resource limits:
- Add cpu_limit (cores) and memory_limit (MB) fields to Stage model
- Pass limits to Docker container via NanoCPUs and Memory in HostConfig
- Add CPU/Memory fields to stage creation form in project detail
- 0 = unlimited (default)

NPM access list:
- Add npm_access_list_id to Settings (global default) and Project (per-project override)
- Per-project overrides global when > 0
- NPM provider passes access_list_id when configuring proxy hosts
- Add GET /api/settings/npm-access-lists endpoint to list NPM access lists
- Add access list picker on NPM settings page (global)
- Add access list ID field on project edit form (per-project)
- DB migrations for all new columns
This commit is contained in:
2026-04-05 12:44:26 +03:00
parent c6d20ca26e
commit 7550fe9e32
12 changed files with 217 additions and 38 deletions
+20
View File
@@ -49,6 +49,12 @@ type ContainerConfig struct {
// Mounts is a list of bind mounts to attach to the container.
Mounts []mount.Mount
// CpuLimit is the CPU limit in cores (e.g., 0.5, 1, 2). 0 = unlimited.
CpuLimit float64
// MemoryLimit is the memory limit in megabytes. 0 = unlimited.
MemoryLimit int
}
// sanitizeTag replaces characters that are invalid in Docker container names
@@ -101,6 +107,7 @@ func (c *Client) CreateContainer(ctx context.Context, cfg ContainerConfig) (stri
PortBindings: portBindings,
RestartPolicy: container.RestartPolicy{Name: container.RestartPolicyDisabled},
Mounts: cfg.Mounts,
Resources: containerResources(cfg.CpuLimit, cfg.MemoryLimit),
}
// Attach to network at creation time if specified.
@@ -128,6 +135,19 @@ func (c *Client) CreateContainer(ctx context.Context, cfg ContainerConfig) (stri
return resp.ID, nil
}
// containerResources builds Docker resource constraints from CPU cores and memory MB.
func containerResources(cpuLimit float64, memoryLimitMB int) container.Resources {
r := container.Resources{}
if cpuLimit > 0 {
// NanoCPUs is in units of 1e-9 CPUs. 1 core = 1e9 nanoCPUs.
r.NanoCPUs = int64(cpuLimit * 1e9)
}
if memoryLimitMB > 0 {
r.Memory = int64(memoryLimitMB) * 1024 * 1024
}
return r
}
// StartContainer starts a stopped container.
func (c *Client) StartContainer(ctx context.Context, containerID string) error {
if _, err := c.api.ContainerStart(ctx, containerID, client.ContainerStartOptions{}); err != nil {