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') }