schema/v1/short_reference.go (88 lines of code) (raw):
package schema
import (
"fmt"
"regexp"
"slices"
"strings"
"gitlab.com/gitlab-org/step-runner/proto"
)
const distPrefix = "dist://"
var containsExpressionRe = regexp.MustCompile(`\${{.*}}`)
func CompileShortRef(value string) (*proto.Step_Reference, error) {
return shortReference(value).compile()
}
type shortReference string
func (sr shortReference) compile() (*proto.Step_Reference, error) {
value := strings.TrimSpace(string(sr))
if containsExpressionRe.MatchString(string(sr)) {
return &proto.Step_Reference{
Protocol: proto.StepReferenceProtocol_dynamic,
Url: string(sr),
}, nil
}
if strings.HasPrefix(value, ".") || strings.HasPrefix(value, "/") {
return sr.compileLocal()
}
if strings.HasPrefix(string(sr), distPrefix) {
return sr.compileDist()
}
return sr.compileRemote()
}
func (sr shortReference) compileDist() (*proto.Step_Reference, error) {
stepDir := strings.TrimPrefix(string(sr), distPrefix)
return &proto.Step_Reference{
Protocol: proto.StepReferenceProtocol_dist,
Path: strings.Split(stepDir, "/"),
Filename: "step.yml",
}, nil
}
func (sr shortReference) compileLocal() (*proto.Step_Reference, error) {
path, filename := pathFilename(true, string(sr))
return &proto.Step_Reference{
Protocol: proto.StepReferenceProtocol_local,
Path: path,
Filename: filename,
}, nil
}
func (sr shortReference) compileRemote() (*proto.Step_Reference, error) {
parts := strings.Split(string(sr), "@")
if len(parts) < 2 {
return nil, fmt.Errorf("expecting url@rev. got %q", sr)
}
rest := strings.Join(parts[0:len(parts)-1], "@")
rev := parts[len(parts)-1]
url, rest, _ := strings.Cut(rest, "/-/")
url = defaultHTTPS(url)
path, filename := pathFilename(false, rest)
path = append([]string{"steps"}, path...)
return &proto.Step_Reference{
Protocol: proto.StepReferenceProtocol_git,
Url: url,
Version: rev,
Path: path,
Filename: filename,
}, nil
}
func defaultHTTPS(stepUrl string) string {
if strings.HasPrefix(stepUrl, "http://") || strings.HasPrefix(stepUrl, "https://") {
return stepUrl
}
return "https://" + stepUrl
}
func pathFilename(allowAbsolute bool, pathStr string) ([]string, string) {
filename := "step.yml"
if pathStr == "" {
return nil, filename
}
path := strings.Split(pathStr, "/")
lastItemIndex := len(path) - 1
if strings.HasSuffix(path[lastItemIndex], ".yml") {
filename = path[lastItemIndex]
path = path[:lastItemIndex]
}
if allowAbsolute && len(path) > 0 && strings.HasPrefix(pathStr, "/") {
// for absolute paths, strings.Split results in the first element of path being empty string
path[0] = "/"
}
path = slices.DeleteFunc(path, func(value string) bool { return value == "" })
return path, filename
}