templates: add PV_LIFECYCLE_GOAL, render PV_STATUS_GOAL=STAGED as MOUNTED + lifecycle_goal, drop PV_STATUS_MOUNTED
Why
Pantavisor's STAGED status goal (full container, mounted with drivers
loaded, not started until requested via pv-ctrl) is being reworked in
pantavisor#827 (open) as a
dual-goal design instead of the PV_STATUS_MOUNTED substate from !504 (merged)
(merged as f1c440a1, not yet in any release tag):
{ "status_goal": "MOUNTED", "lifecycle_goal": "STAGED" }status_goalis the fallback for older Pantavisor, which ignoreslifecycle_goaland treats the container as plainMOUNTED: volumes mounted, never started. The revision still installs. Older Pantavisor rejects the whole revision for anystatus_goalvalue other thanMOUNTED/STARTED/READY, so it must never be set toSTAGEDdirectly.lifecycle_goalis the preferred goal on newer Pantavisor, which gives the fullSTAGEDbehaviour. Unknown values fall back tostatus_goal.
This MR replaces !504 (merged)'s PV_STATUS_MOUNTED template argument with this
simpler design, decided with the pantavisor reviewers, and adds an explicit
PV_LIFECYCLE_GOAL template argument so goals other than STAGED (future
ones included) can use the same dual-goal mechanism without a template
change.
What changed
templates/builtin-lxc-docker.go (templates/compile-template_test.go for
tests, docs/PVR_TEMPLATES.md for docs):
PV_STATUS_GOAL |
PV_LIFECYCLE_GOAL |
Rendering |
|---|---|---|
STAGED |
unset | Shorthand for PV_LIFECYCLE_GOAL=STAGED. Full container (type, config, lxc.container.conf -- exactly as STARTED) plus "status_goal": "MOUNTED" and "lifecycle_goal": "STAGED". |
| any / unset | set (any value) | Full container whenever the value is not MOUNTED; data-only when it is MOUNTED. lifecycle_goal is always rendered verbatim (no validation -- future goal names pass through). status_goal is PV_STATUS_GOAL if it is MOUNTED/STARTED/READY, else MOUNTED (covers unset and STAGED). |
MOUNTED |
unset | Data-only, byte-identical to pre-!504 rendering (no type/config/lxc.container.conf/roles/exports/logs). |
STARTED / READY |
unset | Unchanged: full container, plain status_goal, no lifecycle_goal. |
PV_STATUS_MOUNTED is removed entirely (it was never released -- see
Compatibility below): its LXC_CONTAINER_CONF/RUN_JSON template branches,
the "status_substate" output, the pvr_warn template func (added in !504 (merged)
and used nowhere else), its tests and its docs row.
cmd/app/appadd.go: the --status-goal/PVR_STATUS_GOAL flag on
pvr app add writes c.String("status-goal") straight into
PV_STATUS_GOAL with no validation of allowed values anywhere in
cmd/app or libpvr -- so --status-goal STAGED already renders the same
pair with no code change needed. Its usage string was updated to list
STAGED as a valid goal, and a matching --lifecycle-goal/
PVR_LIFECYCLE_GOAL flag was added (same pass-through pattern, into
PV_LIFECYCLE_GOAL). pvr app update has no --status-goal flag to
mirror, so it gets no --lifecycle-goal either.
PV_LIFECYCLE_GOAL
PV_LIFECYCLE_GOAL generalizes the STAGED shorthand to any goal name:
pvr app add myapp --from=nginx --lifecycle-goal STAGED
pvr app add myapp --from=nginx --lifecycle-goal STAGED --status-goal STARTED
pvr app add myapp --from=nginx --arg PV_LIFECYCLE_GOAL=MOUNTED- Set (non-empty) ->
"lifecycle_goal"is rendered verbatim, no validation, so future goals pass through unchanged. status_goalis thenPV_STATUS_GOALif it is one ofMOUNTED/STARTED/READY, elseMOUNTEDas the safe fallback -- this also applies to thePV_STATUS_GOAL=STAGEDshorthand, sinceSTAGEDitself is not one of the three allowed fallback values.- The container renders in full (
type,config,lxc.container.conf,roles/exports/logs) whenever the preferred goal (PV_LIFECYCLE_GOALif set, elsePV_STATUS_GOAL) is notMOUNTED, even whenstatus_goalitself falls back toMOUNTED. Data-only rendering is reserved for when that preferred goal isMOUNTED(the existingdatarunlevel rule is unchanged). - Neither set, or
PV_STATUS_GOALone ofMOUNTED/STARTED/READYwithoutPV_LIFECYCLE_GOAL, renders byte-identical to master before this branch: nolifecycle_goalkey.
Compatibility
git tag --contains f1c440a1 is empty: !504 (merged)'s PV_STATUS_MOUNTED never
shipped in a release, so removing it is not a breaking change for anyone
consuming a tagged pvr.
Why lifecycle_goal, not status_goal_v2
The key is named lifecycle_goal, a name that shares no prefix with
status_goal, rather than a status_goal-prefixed variant.
Pantavisor versions before
pantavisor#828 look up
run.json keys by prefix match: parser/parser_system1.c's
do_lookup_json_key uses strncmp(curr, key, strlen(key)). Any rendered
key that starts with status_goal -- status_goal_v2 included -- can
therefore be matched by an old pantavisor's lookup for status_goal itself,
which would hand it STAGED as if it were a status_goal value. Naming the
new key lifecycle_goal removes that prefix relationship entirely, so an
old pantavisor's status_goal lookup can only ever match the real
status_goal key. This also means the rendered key order no longer matters
(the earlier design required status_goal to render before status_goal_v2
in the raw template text to win the prefix match; that constraint is gone).
Rendered examples (throwaway go test, not committed)
PV_STATUS_GOAL=STAGED (shorthand, no PV_LIFECYCLE_GOAL):
{
"#spec": "service-manifest-run@1",
"name":"myapp",
"config": "lxc.container.conf",
"drivers": {
"manual":[],
"required":[],
"optional":[]
},
"services": {
"manual":[],
"required":[],
"optional":[]
},
"status_goal": "MOUNTED",
"lifecycle_goal": "STAGED",
"storage":{
"lxc-overlay" : {
"persistence": "boot"
}
},
"type":"lxc",
"root-volume": "root.squashfs",
"volumes":[]
}PV_LIFECYCLE_GOAL=STAGED + PV_STATUS_GOAL=STARTED (status_goal keeps
the explicit STARTED fallback instead of defaulting to MOUNTED):
{
"#spec": "service-manifest-run@1",
"name":"cont",
"config": "lxc.container.conf",
"drivers": {
"manual":[],
"required":[],
"optional":[]
},
"services": {
"manual":[],
"required":[],
"optional":[]
},
"status_goal": "STARTED",
"lifecycle_goal": "STAGED",
"storage":{
"lxc-overlay" : {
"persistence": "boot"
}
},
"type":"lxc",
"root-volume": "root.squashfs",
"volumes":[]
}PV_LIFECYCLE_GOAL=STAGED alone (PV_STATUS_GOAL unset, falls back to
MOUNTED -- same output as the shorthand above):
{
"#spec": "service-manifest-run@1",
"name":"cont",
"config": "lxc.container.conf",
"drivers": {
"manual":[],
"required":[],
"optional":[]
},
"services": {
"manual":[],
"required":[],
"optional":[]
},
"status_goal": "MOUNTED",
"lifecycle_goal": "STAGED",
"storage":{
"lxc-overlay" : {
"persistence": "boot"
}
},
"type":"lxc",
"root-volume": "root.squashfs",
"volumes":[]
}PV_STATUS_GOAL=MOUNTED (data-only, unchanged):
{
"#spec": "service-manifest-run@1",
"name":"myapp",
"drivers": {
"manual":[],
"required":[],
"optional":[]
},
"services": {
"manual":[],
"required":[],
"optional":[]
},
"status_goal": "MOUNTED",
"storage":{
"lxc-overlay" : {
"persistence": "boot"
}
},
"root-volume": "root.squashfs",
"volumes":[]
}Test plan
-
go build ./... -
go vet ./... -
go test ./...(all packages pass, incl. the table-drivenTestCompileTemplate/PV_STATUS_GOAL_/_PV_LIFECYCLE_GOAL_combinations, covering theSTAGEDshorthand, everyPV_LIFECYCLE_GOAL+PV_STATUS_GOALcombination above, and unchanged plainMOUNTED/STARTED) -
gofmt -l .clean