Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 16 additions & 0 deletions schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down
18 changes: 17 additions & 1 deletion schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
Expand All @@ -903,6 +905,7 @@ func TestLoadPluginTypesFromDir_RegistersTypes(t *testing.T) {
}
manifest := `{
"moduleTypes": ["` + customModuleType + `"],
"resourceTypes": ["` + customResourceType + `"],
"stepTypes": [],
"triggerTypes": ["` + customTriggerType + `"],
"workflowTypes": ["` + customWorkflowType + `"]
Expand All @@ -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{},
Expand Down Expand Up @@ -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)
})

Expand All @@ -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)
}
Expand All @@ -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 {
Expand All @@ -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)
Expand Down
Loading