321 lines
9.9 KiB
Go
321 lines
9.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
|
|
}
|
|
|
|
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')
|
|
}
|