after_start deploy fields: asked after the start by this controller, still required for older ones (R-1028/R-1045, security review of catalog 0188430)
gates / gates (push) Successful in 47s
gates / gates (push) Successful in 47s
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -390,8 +390,8 @@ func (m *Manager) DeployStack(req DeployRequest) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if field.Required && value == "" {
|
||||
// Validate required fields (an after_start field is asked after the start — R-1028)
|
||||
if field.Required && value == "" && !field.AfterStart {
|
||||
clearDeploying()
|
||||
return "", util.MsgErrorf(ErrRequiredField, "err.stacks.field_required", field.Label, field.EnvVar)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -169,3 +171,19 @@ func TestR1031_MediaAppIsNotAskedForADrive(t *testing.T) {
|
||||
t.Fatal("an app without the marker must still be asked for its drive")
|
||||
}
|
||||
}
|
||||
|
||||
// R-1028/R-1045: an `after_start` field (Plex's claim code) is not in this controller's wizard and may be empty here;
|
||||
// the same template stays REQUIRED for an older controller (it ignores the key). COMPANION RED-PROOF: drop the
|
||||
// AfterStart skip in UserFacingFields → this test fails. Restored.
|
||||
func TestAfterStartField_NotAskedAtInstall(t *testing.T) {
|
||||
m := Metadata{DeployFields: []DeployField{{EnvVar: "PLEX_CLAIM", Type: "text", Required: true, AfterStart: true}, {EnvVar: "SUBDOMAIN", Type: "subdomain"}}}
|
||||
for _, f := range m.UserFacingFields() {
|
||||
if f.EnvVar == "PLEX_CLAIM" {
|
||||
t.Fatal("the claim code is asked at install — the 4-minute race is back (R-1028)")
|
||||
}
|
||||
}
|
||||
var meta Metadata
|
||||
if err := yaml.Unmarshal([]byte("deploy_fields:\n - env_var: PLEX_CLAIM\n type: text\n required: true\n after_start: true\n"), &meta); err != nil || !meta.DeployFields[0].AfterStart || !meta.DeployFields[0].Required {
|
||||
t.Fatalf("the template shape must parse as required + after_start: %+v %v", meta.DeployFields, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,16 +195,21 @@ type ResourceHints struct {
|
||||
|
||||
// DeployField defines one configuration field shown during first deployment.
|
||||
type DeployField struct {
|
||||
EnvVar string `yaml:"env_var" json:"env_var"`
|
||||
Label string `yaml:"label" json:"label"`
|
||||
Type string `yaml:"type" json:"type"` // domain, subdomain, secret, password, path, text, select, boolean
|
||||
Generate string `yaml:"generate" json:"generate"` // e.g., "password:24", "hex:32", "static:admin"
|
||||
Default string `yaml:"default" json:"default"`
|
||||
Required bool `yaml:"required" json:"required"`
|
||||
Placeholder string `yaml:"placeholder" json:"placeholder"`
|
||||
Description string `yaml:"description" json:"description"`
|
||||
LockedAfterDeploy bool `yaml:"locked_after_deploy" json:"locked_after_deploy"`
|
||||
Options []SelectOption `yaml:"options" json:"options,omitempty"`
|
||||
EnvVar string `yaml:"env_var" json:"env_var"`
|
||||
Label string `yaml:"label" json:"label"`
|
||||
Type string `yaml:"type" json:"type"` // domain, subdomain, secret, password, path, text, select, boolean
|
||||
Generate string `yaml:"generate" json:"generate"` // e.g., "password:24", "hex:32", "static:admin"
|
||||
Default string `yaml:"default" json:"default"`
|
||||
Required bool `yaml:"required" json:"required"`
|
||||
Placeholder string `yaml:"placeholder" json:"placeholder"`
|
||||
Description string `yaml:"description" json:"description"`
|
||||
LockedAfterDeploy bool `yaml:"locked_after_deploy" json:"locked_after_deploy"`
|
||||
// AfterStart (v0.313.0, R-1028/R-1045): this controller asks for the value AFTER the app started (Plex's claim code on
|
||||
// the Plex page), so the wizard does not show the field and an empty value is allowed. An OLDER controller ignores the
|
||||
// key and keeps the field — required — so a template can stay safe for both (a code made optional for everyone would
|
||||
// let an old controller's „Kész" open an unclaimed Plex). Pinned by TestAfterStartField_*.
|
||||
AfterStart bool `yaml:"after_start,omitempty" json:"after_start,omitempty"`
|
||||
Options []SelectOption `yaml:"options" json:"options,omitempty"`
|
||||
// DataKey marks a field as a DATA-ENCRYPTING key (e.g. AdventureLog's "Titkosítási kulcs"):
|
||||
// the app encrypts stored data with it, so regenerating it would render restored data
|
||||
// unreadable. It is a fail-closed annotation only — the recovery unit never stores secrets;
|
||||
@@ -539,6 +544,9 @@ func (m *Metadata) UserFacingFields() []DeployField {
|
||||
if m.MediaLibrary != nil && f.EnvVar == "HDD_PATH" {
|
||||
continue
|
||||
}
|
||||
if f.AfterStart {
|
||||
continue
|
||||
}
|
||||
if f.Type != "domain" && f.Type != "secret" {
|
||||
fields = append(fields, f)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user