Add per-report distributor path overrides
This commit is contained in:
@@ -113,10 +113,16 @@ type RecentChangeConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ReportConfig struct {
|
type ReportConfig struct {
|
||||||
DeterministicModules []ModuleConfigItem `yaml:"deterministic_modules"`
|
DeterministicModules []ModuleConfigItem `yaml:"deterministic_modules"`
|
||||||
|
Distributor ReportDistributorConfig `yaml:"distributor"`
|
||||||
deterministicModulesSet bool
|
deterministicModulesSet bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ReportDistributorConfig struct {
|
||||||
|
PathTemplates []string `yaml:"path_templates"`
|
||||||
|
pathTemplatesSet bool
|
||||||
|
}
|
||||||
|
|
||||||
type ModuleConfigItem struct {
|
type ModuleConfigItem struct {
|
||||||
ID module.ID `yaml:"id"`
|
ID module.ID `yaml:"id"`
|
||||||
Options any `yaml:"options,omitempty"`
|
Options any `yaml:"options,omitempty"`
|
||||||
@@ -135,6 +141,10 @@ func (c *ReportConfig) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.deterministicModulesSet = true
|
c.deterministicModulesSet = true
|
||||||
|
case "distributor":
|
||||||
|
if err := node.Decode(&c.Distributor); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown report entry field %q", key)
|
return fmt.Errorf("unknown report entry field %q", key)
|
||||||
}
|
}
|
||||||
@@ -142,6 +152,30 @@ func (c *ReportConfig) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ReportDistributorConfig) UnmarshalYAML(value *yaml.Node) error {
|
||||||
|
if value.Kind != yaml.MappingNode {
|
||||||
|
return fmt.Errorf("report distributor entry must be a mapping")
|
||||||
|
}
|
||||||
|
for i := 0; i < len(value.Content); i += 2 {
|
||||||
|
key := value.Content[i].Value
|
||||||
|
node := value.Content[i+1]
|
||||||
|
switch key {
|
||||||
|
case "path_templates":
|
||||||
|
if err := node.Decode(&c.PathTemplates); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.pathTemplatesSet = true
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown report distributor field %q", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ReportDistributorConfig) PathTemplatesSet() bool {
|
||||||
|
return c.pathTemplatesSet
|
||||||
|
}
|
||||||
|
|
||||||
func (m *ModuleConfigItem) UnmarshalYAML(value *yaml.Node) error {
|
func (m *ModuleConfigItem) UnmarshalYAML(value *yaml.Node) error {
|
||||||
switch value.Kind {
|
switch value.Kind {
|
||||||
case yaml.ScalarNode:
|
case yaml.ScalarNode:
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaults(t *testing.T) {
|
func TestDefaults(t *testing.T) {
|
||||||
@@ -313,6 +314,102 @@ reports:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadReportDistributorPathOverrides(t *testing.T) {
|
||||||
|
path := writeConfig(t, `
|
||||||
|
reports:
|
||||||
|
daily:
|
||||||
|
distributor:
|
||||||
|
path_templates:
|
||||||
|
- "daily/{valid_start_date}/{run_id}.md"
|
||||||
|
- "daily/{valid_start_date}/index.md"
|
||||||
|
today:
|
||||||
|
deterministic_modules:
|
||||||
|
- metadata
|
||||||
|
`)
|
||||||
|
|
||||||
|
cfg, err := LoadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadFile() error = %v", err)
|
||||||
|
}
|
||||||
|
daily := cfg.Reports["daily"].Distributor
|
||||||
|
if !daily.PathTemplatesSet() {
|
||||||
|
t.Fatal("daily distributor path_templates set = false, want true")
|
||||||
|
}
|
||||||
|
want := []string{
|
||||||
|
"daily/{valid_start_date}/{run_id}.md",
|
||||||
|
"daily/{valid_start_date}/index.md",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(daily.PathTemplates, want) {
|
||||||
|
t.Fatalf("daily path templates = %#v, want %#v", daily.PathTemplates, want)
|
||||||
|
}
|
||||||
|
if cfg.Reports["today"].Distributor.PathTemplatesSet() {
|
||||||
|
t.Fatal("today distributor path_templates set = true, want false")
|
||||||
|
}
|
||||||
|
|
||||||
|
overrides, err := cfg.ReportDistributorPathOverrides()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReportDistributorPathOverrides() error = %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(overrides[report.Daily], want) {
|
||||||
|
t.Fatalf("daily distributor override = %#v, want %#v", overrides[report.Daily], want)
|
||||||
|
}
|
||||||
|
if _, ok := overrides[report.Today]; ok {
|
||||||
|
t.Fatalf("today distributor override = %#v, want omitted override absent", overrides[report.Today])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReportDistributorPathTemplatesSetTracksExplicitEmptyList(t *testing.T) {
|
||||||
|
var cfg Config
|
||||||
|
if err := yaml.Unmarshal([]byte(`
|
||||||
|
reports:
|
||||||
|
daily:
|
||||||
|
distributor:
|
||||||
|
path_templates: []
|
||||||
|
today:
|
||||||
|
distributor: {}
|
||||||
|
`), &cfg); err != nil {
|
||||||
|
t.Fatalf("yaml.Unmarshal() error = %v", err)
|
||||||
|
}
|
||||||
|
if !cfg.Reports["daily"].Distributor.PathTemplatesSet() {
|
||||||
|
t.Fatal("daily distributor path_templates set = false, want true")
|
||||||
|
}
|
||||||
|
if len(cfg.Reports["daily"].Distributor.PathTemplates) != 0 {
|
||||||
|
t.Fatalf("daily path templates = %#v, want empty explicit list", cfg.Reports["daily"].Distributor.PathTemplates)
|
||||||
|
}
|
||||||
|
if cfg.Reports["today"].Distributor.PathTemplatesSet() {
|
||||||
|
t.Fatal("today distributor path_templates set = true, want false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadReportDistributorPathOverrideAliases(t *testing.T) {
|
||||||
|
path := writeConfig(t, `
|
||||||
|
reports:
|
||||||
|
three-day-outlook:
|
||||||
|
distributor:
|
||||||
|
path_templates:
|
||||||
|
- "three-day/{valid_start_date}/index.md"
|
||||||
|
weekend_outlook:
|
||||||
|
distributor:
|
||||||
|
path_templates:
|
||||||
|
- "weekend/{valid_start_date}/index.md"
|
||||||
|
`)
|
||||||
|
|
||||||
|
cfg, err := LoadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadFile() error = %v", err)
|
||||||
|
}
|
||||||
|
overrides, err := cfg.ReportDistributorPathOverrides()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReportDistributorPathOverrides() error = %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(overrides[report.ThreeDay], []string{"three-day/{valid_start_date}/index.md"}) {
|
||||||
|
t.Fatalf("three-day distributor override = %#v, want alias override", overrides[report.ThreeDay])
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(overrides[report.Weekend], []string{"weekend/{valid_start_date}/index.md"}) {
|
||||||
|
t.Fatalf("weekend distributor override = %#v, want alias override", overrides[report.Weekend])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidateReportModuleKeysWithoutMutatingOptions(t *testing.T) {
|
func TestValidateReportModuleKeysWithoutMutatingOptions(t *testing.T) {
|
||||||
cfg := Defaults()
|
cfg := Defaults()
|
||||||
rawOptions := map[string]any{
|
rawOptions := map[string]any{
|
||||||
@@ -565,6 +662,168 @@ reports:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReportDistributorPathOverrideValidation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
yaml string
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "UnknownReportField",
|
||||||
|
yaml: `
|
||||||
|
reports:
|
||||||
|
daily:
|
||||||
|
distributor_paths:
|
||||||
|
- latest.md
|
||||||
|
`,
|
||||||
|
wantErr: `unknown report entry field "distributor_paths"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UnknownDistributorField",
|
||||||
|
yaml: `
|
||||||
|
reports:
|
||||||
|
daily:
|
||||||
|
distributor:
|
||||||
|
paths:
|
||||||
|
- latest.md
|
||||||
|
`,
|
||||||
|
wantErr: `unknown report distributor field "paths"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DuplicateReportAlias",
|
||||||
|
yaml: `
|
||||||
|
reports:
|
||||||
|
three-day:
|
||||||
|
distributor:
|
||||||
|
path_templates:
|
||||||
|
- "three-day/{valid_start_date}/index.md"
|
||||||
|
three_day:
|
||||||
|
distributor:
|
||||||
|
path_templates:
|
||||||
|
- "three-day/latest.md"
|
||||||
|
`,
|
||||||
|
wantErr: "duplicates report override",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UnknownTemplateVariable",
|
||||||
|
yaml: `
|
||||||
|
reports:
|
||||||
|
daily:
|
||||||
|
distributor:
|
||||||
|
path_templates:
|
||||||
|
- "{unknown}.md"
|
||||||
|
`,
|
||||||
|
wantErr: `reports.daily.distributor.path_templates[0] contains unknown template variable "unknown"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AbsolutePath",
|
||||||
|
yaml: `
|
||||||
|
reports:
|
||||||
|
daily:
|
||||||
|
distributor:
|
||||||
|
path_templates:
|
||||||
|
- "/daily.md"
|
||||||
|
`,
|
||||||
|
wantErr: "reports.daily.distributor.path_templates[0] must render a relative path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ParentSegment",
|
||||||
|
yaml: `
|
||||||
|
reports:
|
||||||
|
daily:
|
||||||
|
distributor:
|
||||||
|
path_templates:
|
||||||
|
- "daily/../index.md"
|
||||||
|
`,
|
||||||
|
wantErr: "reports.daily.distributor.path_templates[0] must not render . or .. path segments",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Manifest",
|
||||||
|
yaml: `
|
||||||
|
reports:
|
||||||
|
daily:
|
||||||
|
distributor:
|
||||||
|
path_templates:
|
||||||
|
- "daily/manifest.json"
|
||||||
|
`,
|
||||||
|
wantErr: `reports.daily.distributor.path_templates[0] must not render reserved path segment "manifest.json"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DuplicateRenderedPath",
|
||||||
|
yaml: `
|
||||||
|
reports:
|
||||||
|
daily:
|
||||||
|
distributor:
|
||||||
|
path_templates:
|
||||||
|
- "daily/index.md"
|
||||||
|
- "daily/index.md"
|
||||||
|
`,
|
||||||
|
wantErr: `reports.daily.distributor.path_templates renders duplicate path "daily/index.md"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EmptyOverrideList",
|
||||||
|
yaml: `
|
||||||
|
reports:
|
||||||
|
daily:
|
||||||
|
distributor:
|
||||||
|
path_templates: []
|
||||||
|
`,
|
||||||
|
wantErr: "reports.daily.distributor.path_templates must contain at least one entry",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
_, err := LoadFile(writeConfig(t, tt.yaml))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("LoadFile() error = nil, want validation error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tt.wantErr) {
|
||||||
|
t.Fatalf("error = %q, want %q", err.Error(), tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReportDistributorPathOverridesConsistentForLoadedAndConstructedConfig(t *testing.T) {
|
||||||
|
yaml := `
|
||||||
|
reports:
|
||||||
|
daily:
|
||||||
|
distributor:
|
||||||
|
path_templates:
|
||||||
|
- "daily/{valid_start_date}/index.md"
|
||||||
|
`
|
||||||
|
reports := map[string]ReportConfig{
|
||||||
|
"daily": {
|
||||||
|
Distributor: ReportDistributorConfig{
|
||||||
|
PathTemplates: []string{"daily/{valid_start_date}/index.md"},
|
||||||
|
pathTemplatesSet: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := LoadFile(writeConfig(t, yaml))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadFile() error = %v", err)
|
||||||
|
}
|
||||||
|
loaded, err := cfg.ReportDistributorPathOverrides()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("loaded ReportDistributorPathOverrides() error = %v", err)
|
||||||
|
}
|
||||||
|
cfg = Defaults()
|
||||||
|
cfg.Reports = reports
|
||||||
|
if err := Validate(cfg); err != nil {
|
||||||
|
t.Fatalf("Validate() error = %v", err)
|
||||||
|
}
|
||||||
|
constructed, err := cfg.ReportDistributorPathOverrides()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("constructed ReportDistributorPathOverrides() error = %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(loaded, constructed) {
|
||||||
|
t.Fatalf("loaded overrides = %#v, constructed = %#v", loaded, constructed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReportModuleValidationConsistentForLoadedAndConstructedConfig(t *testing.T) {
|
func TestReportModuleValidationConsistentForLoadedAndConstructedConfig(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ func (cfg Config) ReportModuleOverrides() (map[report.ID][]module.ConfigItem, er
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg Config) ReportDistributorPathOverrides() (map[report.ID][]string, error) {
|
||||||
|
return traverseReportDistributorPathOverrides(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
func normalizeReportModules(cfg *Config) error {
|
func normalizeReportModules(cfg *Config) error {
|
||||||
if cfg.Reports == nil {
|
if cfg.Reports == nil {
|
||||||
cfg.Reports = map[string]ReportConfig{}
|
cfg.Reports = map[string]ReportConfig{}
|
||||||
@@ -35,6 +39,11 @@ func validateReportModules(cfg Config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateReportDistributorPathOverrides(cfg Config) error {
|
||||||
|
_, err := traverseReportDistributorPathOverrides(cfg)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
type reportModuleTraversalOptions struct {
|
type reportModuleTraversalOptions struct {
|
||||||
normalizeOptions bool
|
normalizeOptions bool
|
||||||
updateConfig bool
|
updateConfig bool
|
||||||
@@ -82,6 +91,76 @@ func traverseReportModules(cfg *Config, opts reportModuleTraversalOptions) (map[
|
|||||||
return overrides, nil
|
return overrides, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func traverseReportDistributorPathOverrides(cfg Config) (map[report.ID][]string, error) {
|
||||||
|
overrides := map[report.ID][]string{}
|
||||||
|
if cfg.Reports == nil {
|
||||||
|
return overrides, nil
|
||||||
|
}
|
||||||
|
reportRegistry := report.DefaultRegistry()
|
||||||
|
seenReports := map[report.ID]string{}
|
||||||
|
for key, reportCfg := range cfg.Reports {
|
||||||
|
reportID, err := report.IDForConfigKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reports.%s: %w", key, err)
|
||||||
|
}
|
||||||
|
if previous, ok := seenReports[reportID]; ok {
|
||||||
|
return nil, fmt.Errorf("reports.%s duplicates report override %q", key, previous)
|
||||||
|
}
|
||||||
|
seenReports[reportID] = key
|
||||||
|
if _, err := reportRegistry.Lookup(reportID); err != nil {
|
||||||
|
return nil, fmt.Errorf("reports.%s: %w", key, err)
|
||||||
|
}
|
||||||
|
if !reportCfg.Distributor.pathTemplatesSet {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := validateReportDistributorPathTemplates(key, reportCfg.Distributor.PathTemplates); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
overrides[reportID] = append([]string(nil), reportCfg.Distributor.PathTemplates...)
|
||||||
|
}
|
||||||
|
return overrides, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateReportDistributorPathTemplates(reportKey string, templates []string) error {
|
||||||
|
name := fmt.Sprintf("reports.%s.distributor.path_templates", reportKey)
|
||||||
|
if len(templates) == 0 {
|
||||||
|
return fmt.Errorf("%s must contain at least one entry", name)
|
||||||
|
}
|
||||||
|
values := sampleDistributorTemplateValues()
|
||||||
|
seen := map[string]struct{}{}
|
||||||
|
for i, template := range templates {
|
||||||
|
itemName := fmt.Sprintf("%s[%d]", name, i)
|
||||||
|
rendered, err := renderDistributorTemplate(itemName, template, values, distributorTemplateVariables)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ValidateDistributorReportPath(itemName, rendered); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, ok := seen[rendered]; ok {
|
||||||
|
return fmt.Errorf("%s renders duplicate path %q", name, rendered)
|
||||||
|
}
|
||||||
|
seen[rendered] = struct{}{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sampleDistributorTemplateValues() DistributorTemplateValues {
|
||||||
|
return DistributorTemplateValues{
|
||||||
|
LocationID: "location",
|
||||||
|
ReportID: "report",
|
||||||
|
RunID: "run",
|
||||||
|
ArtifactGroup: "artifact",
|
||||||
|
BatchOutputName: "report.md",
|
||||||
|
ValidStartDate: "2026-05-29",
|
||||||
|
ValidEndDate: "2026-05-30",
|
||||||
|
ValidStartTime: "0000",
|
||||||
|
ValidEndTime: "0000",
|
||||||
|
ValidStartStamp: "2026-05-29T0000",
|
||||||
|
ValidEndStamp: "2026-05-30T0000",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func moduleItemsFromConfig(registry briefing.ModuleRegistry, reportKey string, items []ModuleConfigItem, normalizeOptions bool) ([]module.ConfigItem, []ModuleConfigItem, error) {
|
func moduleItemsFromConfig(registry briefing.ModuleRegistry, reportKey string, items []ModuleConfigItem, normalizeOptions bool) ([]module.ConfigItem, []ModuleConfigItem, error) {
|
||||||
out := make([]module.ConfigItem, 0, len(items))
|
out := make([]module.ConfigItem, 0, len(items))
|
||||||
normalizedItems := append([]ModuleConfigItem(nil), items...)
|
normalizedItems := append([]ModuleConfigItem(nil), items...)
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ func Validate(cfg Config) error {
|
|||||||
if err := validateReportModules(cfg); err != nil {
|
if err := validateReportModules(cfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := validateReportDistributorPathOverrides(cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if cfg.WeatherAPI.BaseURL != "" {
|
if cfg.WeatherAPI.BaseURL != "" {
|
||||||
parsed, err := url.Parse(cfg.WeatherAPI.BaseURL)
|
parsed, err := url.Parse(cfg.WeatherAPI.BaseURL)
|
||||||
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
||||||
@@ -124,19 +127,7 @@ func validateDistributorNotify(cfg DistributorNotifyConfig) error {
|
|||||||
if len(cfg.ReportPathTemplates) == 0 {
|
if len(cfg.ReportPathTemplates) == 0 {
|
||||||
return fmt.Errorf("notify.distributor.report_path_templates must contain at least one entry when enabled")
|
return fmt.Errorf("notify.distributor.report_path_templates must contain at least one entry when enabled")
|
||||||
}
|
}
|
||||||
values := DistributorTemplateValues{
|
values := sampleDistributorTemplateValues()
|
||||||
LocationID: "location",
|
|
||||||
ReportID: "report",
|
|
||||||
RunID: "run",
|
|
||||||
ArtifactGroup: "artifact",
|
|
||||||
BatchOutputName: "report.md",
|
|
||||||
ValidStartDate: "2026-05-29",
|
|
||||||
ValidEndDate: "2026-05-30",
|
|
||||||
ValidStartTime: "0000",
|
|
||||||
ValidEndTime: "0000",
|
|
||||||
ValidStartStamp: "2026-05-29T0000",
|
|
||||||
ValidEndStamp: "2026-05-30T0000",
|
|
||||||
}
|
|
||||||
bundleID, err := RenderDistributorBundleID(cfg.BundleIDTemplate, values)
|
bundleID, err := RenderDistributorBundleID(cfg.BundleIDTemplate, values)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user