Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1339ab4149 |
@@ -73,6 +73,8 @@ tasks:
|
|||||||
ignore_error: false
|
ignore_error: false
|
||||||
- cmd: ./{{.BINARY}} check --branch feat/x testdata/workflow_vars.yml
|
- cmd: ./{{.BINARY}} check --branch feat/x testdata/workflow_vars.yml
|
||||||
ignore_error: false
|
ignore_error: false
|
||||||
|
- cmd: ./{{.BINARY}} check testdata/workflow_escape.yml
|
||||||
|
ignore_error: false
|
||||||
- cmd: ./{{.BINARY}} check testdata/variable_refs.yml
|
- cmd: ./{{.BINARY}} check testdata/variable_refs.yml
|
||||||
ignore_error: false
|
ignore_error: false
|
||||||
- cmd: ./{{.BINARY}} check testdata/variable_refs_included.yml
|
- cmd: ./{{.BINARY}} check testdata/variable_refs_included.yml
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ func Parse(path string) (*Pipeline, error) {
|
|||||||
|
|
||||||
// ParseBytes parses YAML from an in-memory byte slice.
|
// ParseBytes parses YAML from an in-memory byte slice.
|
||||||
func ParseBytes(data []byte) (*Pipeline, error) {
|
func ParseBytes(data []byte) (*Pipeline, error) {
|
||||||
|
data = sanitizeYAMLEscapes(data)
|
||||||
|
|
||||||
// First pass: parse into a yaml.Node document to extract job keys with
|
// First pass: parse into a yaml.Node document to extract job keys with
|
||||||
// their exact source line numbers (key nodes carry the line, value nodes
|
// their exact source line numbers (key nodes carry the line, value nodes
|
||||||
// carry the body we decode into Job / map[string]any).
|
// carry the body we decode into Job / map[string]any).
|
||||||
@@ -75,3 +77,63 @@ func ParseBytes(data []byte) (*Pipeline, error) {
|
|||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sanitizeYAMLEscapes rewrites double-quoted YAML strings, replacing the \/
|
||||||
|
// escape sequence (unrecognised by gopkg.in/yaml.v3) with \\/ so that the
|
||||||
|
// parser produces a literal backslash+slash — preserving regex patterns like
|
||||||
|
// /^us\// that appear in GitLab CI if: expressions.
|
||||||
|
func sanitizeYAMLEscapes(data []byte) []byte {
|
||||||
|
type state int
|
||||||
|
const (
|
||||||
|
stOutside state = iota
|
||||||
|
stSingleQ
|
||||||
|
stDoubleQ
|
||||||
|
stEscape
|
||||||
|
)
|
||||||
|
|
||||||
|
out := make([]byte, 0, len(data))
|
||||||
|
s := stOutside
|
||||||
|
|
||||||
|
for i := 0; i < len(data); i++ {
|
||||||
|
b := data[i]
|
||||||
|
switch s {
|
||||||
|
case stOutside:
|
||||||
|
out = append(out, b)
|
||||||
|
switch b {
|
||||||
|
case '"':
|
||||||
|
s = stDoubleQ
|
||||||
|
case '\'':
|
||||||
|
s = stSingleQ
|
||||||
|
}
|
||||||
|
case stSingleQ:
|
||||||
|
out = append(out, b)
|
||||||
|
if b == '\'' {
|
||||||
|
if i+1 < len(data) && data[i+1] == '\'' {
|
||||||
|
// '' inside a single-quoted string is an escaped single-quote
|
||||||
|
out = append(out, data[i+1])
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
s = stOutside
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case stDoubleQ:
|
||||||
|
out = append(out, b)
|
||||||
|
switch b {
|
||||||
|
case '\\':
|
||||||
|
s = stEscape
|
||||||
|
case '"':
|
||||||
|
s = stOutside
|
||||||
|
}
|
||||||
|
case stEscape:
|
||||||
|
if b == '/' {
|
||||||
|
// \/ is not recognised by yaml.v3; rewrite as \\/ which
|
||||||
|
// the parser resolves to a literal backslash + slash.
|
||||||
|
out = append(out, '\\', '/')
|
||||||
|
} else {
|
||||||
|
out = append(out, b)
|
||||||
|
}
|
||||||
|
s = stDoubleQ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+57
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
# workflow_vars.yml
|
||||||
|
# Exercises workflow:rules:variables: injection.
|
||||||
|
# The matching workflow rule sets DEPLOY_TARGET; job rules use it.
|
||||||
|
#
|
||||||
|
# Expected behaviour per context:
|
||||||
|
# (no context) → all jobs active (no context evaluation)
|
||||||
|
# --branch main → deploy-prod active, deploy-staging skipped
|
||||||
|
# --branch develop → deploy-prod skipped, deploy-staging active
|
||||||
|
# --branch feat/x → deploy-prod skipped, deploy-staging skipped (manual)
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
variables:
|
||||||
|
DEPLOY_TARGET:
|
||||||
|
value: ""
|
||||||
|
description: "Deployment target — set by workflow rules"
|
||||||
|
|
||||||
|
workflow:
|
||||||
|
rules:
|
||||||
|
- if: "
|
||||||
|
$WORKFLOW = 'gitflow' &&
|
||||||
|
$CI_PIPELINE_SOURCE == /(push|web)/ &&
|
||||||
|
$CI_COMMIT_BRANCH =~ /^us\//
|
||||||
|
"
|
||||||
|
variables:
|
||||||
|
DEPLOY_TARGET: production
|
||||||
|
BUILD: true
|
||||||
|
TEST: true
|
||||||
|
- if: '$CI_COMMIT_BRANCH == "develop"'
|
||||||
|
variables:
|
||||||
|
DEPLOY_TARGET: staging
|
||||||
|
- when: always
|
||||||
|
|
||||||
|
build:
|
||||||
|
stage: build
|
||||||
|
script: make build
|
||||||
|
rules:
|
||||||
|
- when: always
|
||||||
|
|
||||||
|
deploy-prod:
|
||||||
|
stage: deploy
|
||||||
|
script: make deploy ENV=production
|
||||||
|
rules:
|
||||||
|
- if: '$DEPLOY_TARGET == "production"'
|
||||||
|
when: on_success
|
||||||
|
- when: never
|
||||||
|
|
||||||
|
deploy-staging:
|
||||||
|
stage: deploy
|
||||||
|
script: make deploy ENV=staging
|
||||||
|
rules:
|
||||||
|
- if: '$DEPLOY_TARGET == "staging"'
|
||||||
|
when: on_success
|
||||||
|
- when: never
|
||||||
Reference in New Issue
Block a user