feat(actions): support configured hydrate fields

This commit is contained in:
Vincent Koc 2026-05-01 03:39:57 -07:00
parent 94b15c8e2e
commit c30e7221a8
No known key found for this signature in database
6 changed files with 51 additions and 1 deletions

View File

@ -315,6 +315,8 @@ class: beast
actions:
workflow: .github/workflows/crabbox.yml
ref: main
fields:
- crabbox_docker_cache=true
runnerLabels:
- crabbox
sync:

View File

@ -40,6 +40,8 @@ actions:
workflow: .github/workflows/crabbox.yml
job: hydrate
ref: main
fields:
- crabbox_docker_cache=true
runnerLabels:
- crabbox
runnerVersion: latest
@ -48,6 +50,7 @@ actions:
Workflow jobs should target the dynamic label printed by registration, for example `crabbox-cbx-123`, plus any static labels configured for the project.
When `actions.job` is set and the workflow declares `crabbox_job`, Crabbox sends it and verifies that the ready marker came from that job. Older workflows can omit both.
Use `actions.fields` for repository-specific workflow inputs that should be sent on every hydration. CLI `-f key=value` values override matching configured fields for that dispatch.
## Hydration Flow

View File

@ -110,7 +110,8 @@ func (a App) actionsHydrate(ctx context.Context, args []string) error {
return err
}
ref := actionsRef(cfg, repo)
fields := actionsHydrateFields(leaseID, label, cfg.Actions.Job, *keepAliveMinutes, fieldFlags)
extraFields := mergeWorkflowInputFields(cfg.Actions.Fields, fieldFlags)
fields := actionsHydrateFields(leaseID, label, cfg.Actions.Job, *keepAliveMinutes, extraFields)
if inputs, ok, err := githubWorkflowDispatchInputs(ctx, repo.Root, ghRepo, cfg.Actions.Workflow, ref); err != nil {
fmt.Fprintf(a.Stderr, "warning: inspect workflow inputs failed: %v\n", err)
} else if ok {
@ -327,6 +328,28 @@ func actionsHydrateFields(leaseID, label, job string, keepAliveMinutes int, extr
return fields
}
func mergeWorkflowInputFields(base, override []string) []string {
fields := append([]string{}, base...)
index := map[string]int{}
for i, field := range fields {
if name := fieldName(field); name != "" {
index[name] = i
}
}
for _, field := range override {
name := fieldName(field)
if name != "" {
if existing, ok := index[name]; ok {
fields[existing] = field
continue
}
index[name] = len(fields)
}
fields = append(fields, field)
}
return fields
}
func githubWorkflowDispatchInputs(ctx context.Context, dir string, repo GitHubRepo, workflow, ref string) (map[string]bool, bool, error) {
workflow = strings.TrimPrefix(workflow, "/")
if !strings.HasPrefix(workflow, ".github/workflows/") {

View File

@ -46,6 +46,17 @@ func TestActionsHydrateFieldsOmitsEmptyJobForOldWorkflows(t *testing.T) {
}
}
func TestMergeWorkflowInputFieldsLetsFlagsOverrideConfig(t *testing.T) {
got := mergeWorkflowInputFields(
[]string{"crabbox_docker_cache=false", "crabbox_prepare_images=1"},
[]string{"crabbox_docker_cache=true", "custom=value"},
)
want := []string{"crabbox_docker_cache=true", "crabbox_prepare_images=1", "custom=value"}
if strings.Join(got, "\n") != strings.Join(want, "\n") {
t.Fatalf("fields=%#v want %#v", got, want)
}
}
func TestFilterWorkflowInputsDropsUndeclaredOptionalInputs(t *testing.T) {
fields := actionsHydrateFields("cbx_123", "crabbox-cbx-123", "hydrate", 90, []string{"custom=value"})
filtered, dropped := filterWorkflowInputs(fields, map[string]bool{

View File

@ -76,6 +76,7 @@ type ActionsConfig struct {
Workflow string
Job string
Ref string
Fields []string
RunnerLabels []string
RunnerVersion string
Ephemeral bool
@ -282,6 +283,7 @@ type fileActionsConfig struct {
Workflow string `yaml:"workflow,omitempty"`
Job string `yaml:"job,omitempty"`
Ref string `yaml:"ref,omitempty"`
Fields []string `yaml:"fields,omitempty"`
RunnerLabels []string `yaml:"runnerLabels,omitempty"`
RunnerVersion string `yaml:"runnerVersion,omitempty"`
Ephemeral *bool `yaml:"ephemeral,omitempty"`
@ -583,6 +585,9 @@ func applyFileConfig(cfg *Config, file fileConfig) {
if file.Actions.Ref != "" {
cfg.Actions.Ref = file.Actions.Ref
}
if len(file.Actions.Fields) > 0 {
cfg.Actions.Fields = appendUniqueStrings(nil, file.Actions.Fields...)
}
if len(file.Actions.RunnerLabels) > 0 {
cfg.Actions.RunnerLabels = appendUniqueStrings(nil, file.Actions.RunnerLabels...)
}

View File

@ -84,6 +84,9 @@ actions:
workflow: .github/workflows/crabbox.yml
job: hydrate
ref: main
fields:
- crabbox_docker_cache=true
- crabbox_prepare_images=1
runnerLabels:
- crabbox
- linux-large
@ -164,6 +167,9 @@ ssh:
if cfg.Actions.Repo != "openclaw/crabbox" || cfg.Actions.Workflow != ".github/workflows/crabbox.yml" || cfg.Actions.Job != "hydrate" || cfg.Actions.Ref != "main" {
t.Fatalf("actions config not loaded: %#v", cfg.Actions)
}
if len(cfg.Actions.Fields) != 2 || cfg.Actions.Fields[0] != "crabbox_docker_cache=true" || cfg.Actions.Fields[1] != "crabbox_prepare_images=1" {
t.Fatalf("actions fields config not loaded: %#v", cfg.Actions.Fields)
}
if cfg.Actions.Ephemeral || len(cfg.Actions.RunnerLabels) != 2 || cfg.Actions.RunnerLabels[1] != "linux-large" {
t.Fatalf("actions runner config not loaded: %#v", cfg.Actions)
}