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 } type DistributorBatchTemplateValues struct { LocationID string Batch string BatchRunID string BatchStartedDate 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 var distributorBatchTemplateVariables = map[string]struct{}{ "location_id": {}, "batch": {}, "batch_run_id": {}, "batch_started_date": {}, } var distributorBatchIdempotencyTemplateVariables = map[string]struct{}{ "location_id": {}, "batch": {}, "batch_run_id": {}, "batch_started_date": {}, "bundle_id": {}, } var distributorBatchPipelineTemplateVariables = distributorBatchTemplateVariables func RenderDistributorBundleID(template string, values DistributorTemplateValues) (string, error) { rendered, err := renderDistributorTemplate("notify.distributor.bundle_id_template", template, newDistributorTemplateResolver(values, distributorTemplateVariables)) if err != nil { return "", err } if strings.TrimSpace(rendered) == "" { return "", fmt.Errorf("notify.distributor.bundle_id_template renders an empty bundle id") } return rendered, nil } func RenderDistributorPipelineID(template string, values DistributorTemplateValues) (string, error) { rendered, err := renderDistributorTemplate("notify.distributor.pipeline_id_template", template, newDistributorTemplateResolver(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) { rendered, err := renderDistributorTemplate("notify.distributor.idempotency_key_template", template, newDistributorTemplateResolver(values, distributorIdempotencyTemplateVariables)) if err != nil { return "", err } if strings.TrimSpace(rendered) == "" { return "", fmt.Errorf("notify.distributor.idempotency_key_template renders an empty idempotency key") } return rendered, nil } func RenderDistributorBatchBundleID(template string, values DistributorBatchTemplateValues) (string, error) { rendered, err := renderDistributorTemplate("notify.distributor.batch.bundle_id_template", template, newDistributorBatchTemplateResolver(values, distributorBatchTemplateVariables)) if err != nil { return "", err } if strings.TrimSpace(rendered) == "" { return "", fmt.Errorf("notify.distributor.batch.bundle_id_template renders an empty bundle id") } return rendered, nil } func RenderDistributorBatchPipelineID(template string, values DistributorBatchTemplateValues) (string, error) { rendered, err := renderDistributorTemplate("notify.distributor.batch.pipeline_id_template", template, newDistributorBatchTemplateResolver(values, distributorBatchPipelineTemplateVariables)) if err != nil { return "", err } if strings.TrimSpace(rendered) == "" { return "", fmt.Errorf("notify.distributor.batch.pipeline_id_template renders an empty pipeline id") } return rendered, nil } func RenderDistributorBatchIdempotencyKey(template string, values DistributorBatchTemplateValues) (string, error) { rendered, err := renderDistributorTemplate("notify.distributor.batch.idempotency_key_template", template, newDistributorBatchTemplateResolver(values, distributorBatchIdempotencyTemplateVariables)) if err != nil { return "", err } if strings.TrimSpace(rendered) == "" { return "", fmt.Errorf("notify.distributor.batch.idempotency_key_template renders an empty idempotency key") } return rendered, nil } func RenderDistributorReportPaths(name string, templates []string, values DistributorTemplateValues) ([]string, error) { if len(templates) == 0 { return nil, fmt.Errorf("%s must contain at least one entry", name) } paths := make([]string, 0, len(templates)) seen := make(map[string]struct{}, len(templates)) for i, template := range templates { itemName := fmt.Sprintf("%s[%d]", name, i) rendered, err := renderDistributorTemplate(itemName, template, newDistributorTemplateResolver(values, distributorTemplateVariables)) if err != nil { return nil, err } if err := ValidateDistributorReportPath(itemName, rendered); err != nil { return nil, err } if _, ok := seen[rendered]; ok { return nil, fmt.Errorf("%s renders duplicate path %q", name, 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, newDistributorTemplateResolver(DistributorTemplateValues{}, allowed)) return err } func validateDistributorBatchTemplate(name, template string, allowed map[string]struct{}) error { _, err := renderDistributorTemplate(name, template, newDistributorBatchTemplateResolver(DistributorBatchTemplateValues{}, allowed)) return err } type distributorTemplateResolver func(string) (string, bool) func renderDistributorTemplate(name, template string, resolve distributorTemplateResolver) (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) } value, ok := resolve(variable) if !ok { return "", fmt.Errorf("%s contains unknown template variable %q", name, variable) } rendered.WriteString(value) 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 newDistributorTemplateResolver(values DistributorTemplateValues, allowed map[string]struct{}) distributorTemplateResolver { return func(variable string) (string, bool) { if _, ok := allowed[variable]; !ok { return "", false } return distributorTemplateValue(variable, values), true } } func newDistributorBatchTemplateResolver(values DistributorBatchTemplateValues, allowed map[string]struct{}) distributorTemplateResolver { return func(variable string) (string, bool) { if _, ok := allowed[variable]; !ok { return "", false } return distributorBatchTemplateValue(variable, values), true } } 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 distributorBatchTemplateValue(variable string, values DistributorBatchTemplateValues) string { switch variable { case "location_id": return values.LocationID case "batch": return values.Batch case "batch_run_id": return values.BatchRunID case "batch_started_date": return values.BatchStartedDate 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 == distributorSidecarBasename() { return fmt.Errorf("%s must not render reserved path segment %q", name, segment) } } return nil } func distributorSidecarBasename() string { return "." + "distributor.json" } 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') }