202 lines
5.9 KiB
Go
202 lines
5.9 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
type DistributorTemplateValues struct {
|
|
LocationID string
|
|
ReportID string
|
|
RunID string
|
|
ArtifactGroup string
|
|
BatchOutputName string
|
|
ValidStartDate string
|
|
ValidEndDate string
|
|
ValidStartTime string
|
|
ValidEndTime string
|
|
ValidStartStamp string
|
|
ValidEndStamp string
|
|
BundleID string
|
|
}
|
|
|
|
var distributorTemplateVariables = map[string]struct{}{
|
|
"location_id": {},
|
|
"report_id": {},
|
|
"run_id": {},
|
|
"artifact_group": {},
|
|
"batch_output_name": {},
|
|
"valid_start_date": {},
|
|
"valid_end_date": {},
|
|
"valid_start_time": {},
|
|
"valid_end_time": {},
|
|
"valid_start_stamp": {},
|
|
"valid_end_stamp": {},
|
|
}
|
|
|
|
var distributorIdempotencyTemplateVariables = map[string]struct{}{
|
|
"location_id": {},
|
|
"report_id": {},
|
|
"run_id": {},
|
|
"artifact_group": {},
|
|
"batch_output_name": {},
|
|
"valid_start_date": {},
|
|
"valid_end_date": {},
|
|
"valid_start_time": {},
|
|
"valid_end_time": {},
|
|
"valid_start_stamp": {},
|
|
"valid_end_stamp": {},
|
|
"bundle_id": {},
|
|
}
|
|
|
|
var distributorPipelineTemplateVariables = distributorIdempotencyTemplateVariables
|
|
|
|
func RenderDistributorBundleID(template string, values DistributorTemplateValues) (string, error) {
|
|
return renderDistributorTemplate("notify.distributor.bundle_id_template", template, values, distributorTemplateVariables)
|
|
}
|
|
|
|
func RenderDistributorPipelineID(template string, values DistributorTemplateValues) (string, error) {
|
|
rendered, err := renderDistributorTemplate("notify.distributor.pipeline_id_template", template, values, distributorPipelineTemplateVariables)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if strings.TrimSpace(rendered) == "" {
|
|
return "", fmt.Errorf("notify.distributor.pipeline_id_template renders an empty pipeline id")
|
|
}
|
|
return rendered, nil
|
|
}
|
|
|
|
func RenderDistributorIdempotencyKey(template string, values DistributorTemplateValues) (string, error) {
|
|
return renderDistributorTemplate("notify.distributor.idempotency_key_template", template, values, distributorIdempotencyTemplateVariables)
|
|
}
|
|
|
|
func RenderDistributorReportPaths(templates []string, values DistributorTemplateValues) ([]string, error) {
|
|
if len(templates) == 0 {
|
|
return nil, fmt.Errorf("notify.distributor.report_path_templates must contain at least one entry")
|
|
}
|
|
paths := make([]string, 0, len(templates))
|
|
seen := make(map[string]struct{}, len(templates))
|
|
for i, template := range templates {
|
|
name := fmt.Sprintf("notify.distributor.report_path_templates[%d]", i)
|
|
rendered, err := renderDistributorTemplate(name, template, values, distributorTemplateVariables)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := ValidateDistributorReportPath(name, rendered); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, ok := seen[rendered]; ok {
|
|
return nil, fmt.Errorf("notify.distributor.report_path_templates renders duplicate path %q", rendered)
|
|
}
|
|
seen[rendered] = struct{}{}
|
|
paths = append(paths, rendered)
|
|
}
|
|
return paths, nil
|
|
}
|
|
|
|
func validateDistributorTemplate(name, template string, allowed map[string]struct{}) error {
|
|
_, err := renderDistributorTemplate(name, template, DistributorTemplateValues{}, allowed)
|
|
return err
|
|
}
|
|
|
|
func renderDistributorTemplate(name, template string, values DistributorTemplateValues, allowed map[string]struct{}) (string, error) {
|
|
var rendered strings.Builder
|
|
for i := 0; i < len(template); {
|
|
switch template[i] {
|
|
case '{':
|
|
end := strings.IndexByte(template[i+1:], '}')
|
|
if end < 0 {
|
|
return "", fmt.Errorf("%s contains an unclosed template variable", name)
|
|
}
|
|
variable := template[i+1 : i+1+end]
|
|
if variable == "" {
|
|
return "", fmt.Errorf("%s contains an empty template variable", name)
|
|
}
|
|
if _, ok := allowed[variable]; !ok {
|
|
return "", fmt.Errorf("%s contains unknown template variable %q", name, variable)
|
|
}
|
|
rendered.WriteString(distributorTemplateValue(variable, values))
|
|
i += end + 2
|
|
case '}':
|
|
return "", fmt.Errorf("%s contains an unopened template variable", name)
|
|
default:
|
|
rendered.WriteByte(template[i])
|
|
i++
|
|
}
|
|
}
|
|
return rendered.String(), nil
|
|
}
|
|
|
|
func distributorTemplateValue(variable string, values DistributorTemplateValues) string {
|
|
switch variable {
|
|
case "location_id":
|
|
return values.LocationID
|
|
case "report_id":
|
|
return values.ReportID
|
|
case "run_id":
|
|
return values.RunID
|
|
case "artifact_group":
|
|
return values.ArtifactGroup
|
|
case "batch_output_name":
|
|
return values.BatchOutputName
|
|
case "valid_start_date":
|
|
return values.ValidStartDate
|
|
case "valid_end_date":
|
|
return values.ValidEndDate
|
|
case "valid_start_time":
|
|
return values.ValidStartTime
|
|
case "valid_end_time":
|
|
return values.ValidEndTime
|
|
case "valid_start_stamp":
|
|
return values.ValidStartStamp
|
|
case "valid_end_stamp":
|
|
return values.ValidEndStamp
|
|
case "bundle_id":
|
|
return values.BundleID
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func ValidateDistributorReportPath(name, path string) error {
|
|
if path == "" {
|
|
return fmt.Errorf("%s renders an empty path", name)
|
|
}
|
|
if isDistributorAbsolutePath(path) {
|
|
return fmt.Errorf("%s must render a relative path", name)
|
|
}
|
|
if strings.Contains(path, "\\") {
|
|
return fmt.Errorf("%s must not render backslashes", name)
|
|
}
|
|
|
|
segments := strings.Split(path, "/")
|
|
for _, segment := range segments {
|
|
if segment == "" {
|
|
return fmt.Errorf("%s must not render empty path segments", name)
|
|
}
|
|
if segment == "." || segment == ".." {
|
|
return fmt.Errorf("%s must not render . or .. path segments", name)
|
|
}
|
|
if segment == "manifest.json" || segment == ".distributor.json" {
|
|
return fmt.Errorf("%s must not render reserved path segment %q", name, segment)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func isDistributorAbsolutePath(path string) bool {
|
|
if filepath.IsAbs(path) || strings.HasPrefix(path, "/") {
|
|
return true
|
|
}
|
|
if len(path) >= 3 && isASCIIAlpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\') {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isASCIIAlpha(ch byte) bool {
|
|
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
|
|
}
|