Fix unusable strategy schema for custom jobs + add compiler extraction (Option B)#19405
Fix unusable strategy schema for custom jobs + add compiler extraction (Option B)#19405
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Fixes custom job strategy support by making the schema accept real strategy objects and ensuring the compiler extracts/renders strategy so it round-trips into the generated workflow YAML.
Changes:
- Updated the main workflow JSON schema to allow
strategy.matrix,strategy.fail-fast, andstrategy.max-parallelfor custom jobs. - Added
Strategyto theJobmodel and rendered it in job YAML output. - Extracted
strategyfrom custom job frontmatter during compilation and added a compiler test.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| pkg/parser/schemas/main_workflow_schema.json | Fixes the custom jobs strategy schema so non-empty strategy objects validate. |
| pkg/workflow/jobs.go | Adds Job.Strategy and renders it after runs-on. |
| pkg/workflow/compiler_jobs.go | Extracts strategy from custom jobs and formats it into job YAML. |
| pkg/workflow/compiler_jobs_test.go | Adds a test exercising custom job strategy compilation output. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Extract strategy for custom jobs | ||
| if strategy, hasStrategy := configMap["strategy"]; hasStrategy { | ||
| if strategyMap, ok := strategy.(map[string]any); ok { | ||
| // Use goccy/go-yaml to marshal strategy | ||
| yamlBytes, err := yaml.Marshal(strategyMap) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to convert strategy to YAML for job '%s': %w", jobName, err) | ||
| } | ||
| // Indent the YAML properly for job-level strategy | ||
| strategyYAML := string(yamlBytes) | ||
| lines := strings.Split(strings.TrimSpace(strategyYAML), "\n") | ||
| var formattedStrategy strings.Builder | ||
| formattedStrategy.WriteString("strategy:\n") | ||
| for _, line := range lines { | ||
| formattedStrategy.WriteString(" " + line + "\n") | ||
| } | ||
| job.Strategy = formattedStrategy.String() | ||
| } | ||
| } |
There was a problem hiding this comment.
The strategy extraction duplicates the same marshal/indent logic used for permissions just above. This kind of copy/paste tends to diverge over time (formatting, error handling, indentation rules). Consider factoring this into a small helper (e.g., marshalJobSection(name string, v any) (string, error)) and use it for both permissions and strategy to keep behavior consistent.
There was a problem hiding this comment.
@copilot refactor the strategy type in the JSON schema
There was a problem hiding this comment.
Done in 2140137 — extracted the inline strategy object definition into a named $defs/job_strategy type and replaced the inline definition with "$ref": "#/$defs/job_strategy", following the same pattern as other reusable types in the schema.
| // Verify matrix_job has strategy section | ||
| if !strings.Contains(yamlStr, "matrix_job:") { | ||
| t.Error("Expected matrix_job in compiled output") | ||
| } | ||
| if !strings.Contains(yamlStr, "strategy:") { | ||
| t.Error("Expected strategy section in compiled output") | ||
| } | ||
| if !strings.Contains(yamlStr, "matrix:") { | ||
| t.Error("Expected matrix section in compiled output") | ||
| } | ||
| if !strings.Contains(yamlStr, "fail-fast: false") { | ||
| t.Error("Expected fail-fast: false in compiled output") | ||
| } | ||
| if !strings.Contains(yamlStr, "max-parallel: 2") { | ||
| t.Error("Expected max-parallel: 2 in compiled output") | ||
| } | ||
|
|
||
| // Verify simple_job has no strategy | ||
| if !strings.Contains(yamlStr, "simple_job:") { | ||
| t.Error("Expected simple_job in compiled output") | ||
| } | ||
| } |
There was a problem hiding this comment.
This test only checks strings.Contains on the entire lock file, which is brittle and can give false positives (e.g., if "strategy:" appears elsewhere in the workflow) and it doesn't actually verify that simple_job doesn't get a strategy block. A more reliable assertion would be to yaml.Unmarshal the compiled lock YAML and then check that jobs.matrix_job.strategy exists with the expected subfields, and that jobs.simple_job has no strategy key.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@copilot merge main, recompile |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Done in bed224d — merged main, resolved the conflict in Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
@copilot merge main and recompile |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Done in 54ea865 — merged main (4 new commits including Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
strategyschema inpkg/parser/schemas/main_workflow_schema.jsonwith propermatrix,fail-fast,max-parallelpropertiesstrategyinline object into a named$defs/job_strategytype referenced via$refStrategy stringfield toJobstruct inpkg/workflow/jobs.gostrategyinbuildCustomJobs()inpkg/workflow/compiler_jobs.gousing YAML marshal (same pattern aspermissions)StrategyinrenderJob()inpkg/workflow/jobs.goTestBuildCustomJobsWithStrategytest for strategy round-trip compilationmake buildafter schema changesTestBuildCustomJobsWithStrategyandTestBuildCustomJobsRunsOnForms)Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.