Description of feature
The export machinery is the only part of docket that reads current server state back as structured data, and it is close to unusable from Go.
AppExporter.ExportApp(app) and GlobalExporter.ExportGlobal() return populated task structs read off the live server; 60 tasks declare ExportSupported, 8 ExportPartial, and 5 ExportUnsupported. ExportRecipe wraps them with the ordering in appExportOrder / globalExportOrder, warning collection, and the lifting of sensitive values into a vars map. All of that is useful to more than docket export, and three things get in the way.
ExportResult.plays is unexported, so the only ways out of ExportRecipe are MarshalRecipe and MarshalVars. A caller that wants structured data has to marshal to YAML and immediately parse it back. Asserting proto.(AppExporter) and calling ExportApp directly sidesteps that, but then the caller reimplements the ordering, the warnings, and the sensitive-value handling that exportAppPlay already does.
ExportRecipe skips the entire global play whenever opts.Apps is set. That is the right behaviour for docket export --app foo, where the user asked for one app, but it also means there is no narrowed read path for global resources at all - reading back a single dokku_network or dokku_ssh_key requires exporting the whole server.
And building a task to work with requires knowing to call defaults.SetDefaults yourself. The only code that does this correctly is decodeTaskBytes, which is unexported. A caller that writes a struct literal and forgets gets a zero State, which falls into the invalid state: "" branch of DispatchPlan rather than defaulting to present as the field tag says it should. An exported constructor that allocates from the registry prototype and applies defaults would remove a foot-gun that has nothing to do with export specifically.
None of this changes behaviour; it is about making a capability that already exists reachable.
Description of feature
The export machinery is the only part of docket that reads current server state back as structured data, and it is close to unusable from Go.
AppExporter.ExportApp(app)andGlobalExporter.ExportGlobal()return populated task structs read off the live server; 60 tasks declareExportSupported, 8ExportPartial, and 5ExportUnsupported.ExportRecipewraps them with the ordering inappExportOrder/globalExportOrder, warning collection, and the lifting of sensitive values into a vars map. All of that is useful to more thandocket export, and three things get in the way.ExportResult.playsis unexported, so the only ways out ofExportRecipeareMarshalRecipeandMarshalVars. A caller that wants structured data has to marshal to YAML and immediately parse it back. Assertingproto.(AppExporter)and callingExportAppdirectly sidesteps that, but then the caller reimplements the ordering, the warnings, and the sensitive-value handling thatexportAppPlayalready does.ExportRecipeskips the entire global play wheneveropts.Appsis set. That is the right behaviour fordocket export --app foo, where the user asked for one app, but it also means there is no narrowed read path for global resources at all - reading back a singledokku_networkordokku_ssh_keyrequires exporting the whole server.And building a task to work with requires knowing to call
defaults.SetDefaultsyourself. The only code that does this correctly isdecodeTaskBytes, which is unexported. A caller that writes a struct literal and forgets gets a zeroState, which falls into theinvalid state: ""branch ofDispatchPlanrather than defaulting topresentas the field tag says it should. An exported constructor that allocates from the registry prototype and applies defaults would remove a foot-gun that has nothing to do with export specifically.None of this changes behaviour; it is about making a capability that already exists reachable.