diff --git a/CHANGELOG.md b/CHANGELOG.md index 3002e1d7..d7a83435 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ Configs that still reference the legacy types now fail to load with an actionabl ## [Unreleased] +### Fixed + +- `wfctl validate --plugin-dir` now registers plugin `resourceTypes` from both flat manifests and `capabilities.iacProvider.resourceTypes`, so plugin-backed `infra.*` resource modules validate the same way `wfctl infra plan/apply` consume them. + ### Breaking changes (workflow#699 — IaCProvider.Apply hard-removal) - `interfaces.IaCProvider.Apply` removed. Plugins must implement v2 dispatch (declare `CapabilitiesResponse.compute_plan_version="v2"` via the typed RPC) and drop their `Apply` Go method. diff --git a/schema/schema.go b/schema/schema.go index 436739eb..e0a6acfe 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -517,6 +517,7 @@ type pluginManifestTypes struct { StepTypes []string `json:"stepTypes"` TriggerTypes []string `json:"triggerTypes"` WorkflowTypes []string `json:"workflowTypes"` + ResourceTypes []string `json:"resourceTypes"` // Capabilities is stored as raw JSON to safely handle both the registry-manifest // format (object with moduleTypes/stepTypes/etc.) and the engine-internal format // (array of CapabilityDecl). A non-object value is silently ignored. @@ -531,6 +532,10 @@ type pluginManifestCapabilities struct { StepTypes []string `json:"stepTypes"` TriggerTypes []string `json:"triggerTypes"` WorkflowHandlers []string `json:"workflowHandlers"` + ResourceTypes []string `json:"resourceTypes"` + IaCProvider *struct { + ResourceTypes []string `json:"resourceTypes"` + } `json:"iacProvider"` } // LoadPluginTypesFromDir scans pluginDir for subdirectories containing a @@ -563,6 +568,9 @@ func LoadPluginTypesFromDir(pluginDir string) error { // Step types share the module type registry (identified by "step." prefix). RegisterModuleType(t) } + for _, t := range m.ResourceTypes { + RegisterModuleType(t) + } for _, t := range m.TriggerTypes { RegisterTriggerType(t) } @@ -581,6 +589,14 @@ func LoadPluginTypesFromDir(pluginDir string) error { for _, t := range cap.StepTypes { RegisterModuleType(t) } + for _, t := range cap.ResourceTypes { + RegisterModuleType(t) + } + if cap.IaCProvider != nil { + for _, t := range cap.IaCProvider.ResourceTypes { + RegisterModuleType(t) + } + } for _, t := range cap.TriggerTypes { RegisterTriggerType(t) } diff --git a/schema/schema_test.go b/schema/schema_test.go index 56be2ef5..9297f7c0 100644 --- a/schema/schema_test.go +++ b/schema/schema_test.go @@ -887,10 +887,12 @@ func TestLoadPluginTypesFromDir_RegistersTypes(t *testing.T) { const customModuleType = "external.plugin.module.testonly" const customTriggerType = "external.trigger.testonly" const customWorkflowType = "external.workflow.testonly" + const customResourceType = "infra.plugin_resource_testonly" // Cleanup after test t.Cleanup(func() { UnregisterModuleType(customModuleType) + UnregisterModuleType(customResourceType) UnregisterTriggerType(customTriggerType) UnregisterWorkflowType(customWorkflowType) }) @@ -903,6 +905,7 @@ func TestLoadPluginTypesFromDir_RegistersTypes(t *testing.T) { } manifest := `{ "moduleTypes": ["` + customModuleType + `"], + "resourceTypes": ["` + customResourceType + `"], "stepTypes": [], "triggerTypes": ["` + customTriggerType + `"], "workflowTypes": ["` + customWorkflowType + `"] @@ -919,6 +922,7 @@ func TestLoadPluginTypesFromDir_RegistersTypes(t *testing.T) { cfg := &config.WorkflowConfig{ Modules: []config.ModuleConfig{ {Name: "ext", Type: customModuleType}, + {Name: "res", Type: customResourceType}, }, Triggers: map[string]any{ customTriggerType: map[string]any{}, @@ -950,10 +954,14 @@ func TestLoadPluginTypesFromDir_CapabilitiesFormat(t *testing.T) { const customModuleType = "external.caps.module.testonly" const customStepType = "step.caps_step_testonly" const customTriggerType = "external.caps.trigger.testonly" + const customResourceType = "infra.caps_resource_testonly" + const customIaCResourceType = "infra.caps_iac_resource_testonly" t.Cleanup(func() { UnregisterModuleType(customModuleType) UnregisterModuleType(customStepType) + UnregisterModuleType(customResourceType) + UnregisterModuleType(customIaCResourceType) UnregisterTriggerType(customTriggerType) }) @@ -962,7 +970,7 @@ func TestLoadPluginTypesFromDir_CapabilitiesFormat(t *testing.T) { if err := makeDir(pluginDir); err != nil { t.Fatal(err) } - manifest := `{"name":"caps-plugin","version":"1.0.0","type":"external","capabilities":{"configProvider":false,"moduleTypes":["` + customModuleType + `"],"stepTypes":["` + customStepType + `"],"triggerTypes":["` + customTriggerType + `"]}}` + manifest := `{"name":"caps-plugin","version":"1.0.0","type":"external","capabilities":{"configProvider":false,"moduleTypes":["` + customModuleType + `"],"stepTypes":["` + customStepType + `"],"triggerTypes":["` + customTriggerType + `"],"resourceTypes":["` + customResourceType + `"],"iacProvider":{"name":"test","resourceTypes":["` + customIaCResourceType + `"]}}}` if err := writeFile(pluginDir+"/plugin.json", []byte(manifest)); err != nil { t.Fatal(err) } @@ -975,6 +983,8 @@ func TestLoadPluginTypesFromDir_CapabilitiesFormat(t *testing.T) { cfg := &config.WorkflowConfig{ Modules: []config.ModuleConfig{ {Name: "ext", Type: customModuleType}, + {Name: "res", Type: customResourceType}, + {Name: "iac-res", Type: customIaCResourceType}, }, } if err := ValidateConfig(cfg, WithAllowNoEntryPoints()); err != nil { @@ -988,6 +998,12 @@ func TestLoadPluginTypesFromDir_CapabilitiesFormat(t *testing.T) { if !sliceContains(knownModules, customStepType) { t.Errorf("expected %q in KnownModuleTypes (step), got: %v", customStepType, knownModules) } + if !sliceContains(knownModules, customResourceType) { + t.Errorf("expected %q in KnownModuleTypes (resource), got: %v", customResourceType, knownModules) + } + if !sliceContains(knownModules, customIaCResourceType) { + t.Errorf("expected %q in KnownModuleTypes (iac provider resource), got: %v", customIaCResourceType, knownModules) + } knownTriggers := KnownTriggerTypes() if !sliceContains(knownTriggers, customTriggerType) { t.Errorf("expected %q in KnownTriggerTypes, got: %v", customTriggerType, knownTriggers)