From 9a109645e15945d65b39ea216c317916e73c3471 Mon Sep 17 00:00:00 2001 From: Piotr Konopka Date: Fri, 15 Mar 2024 16:39:45 +0100 Subject: [PATCH] [core] support query parameters in apricot URIs in JIT --- configuration/componentcfg/query.go | 49 ++++++++++++++++++++++- configuration/componentcfg/query_test.go | 51 ++++++++++++++++++++++++ configuration/template/dplutil.go | 23 +++++++++-- 3 files changed, 117 insertions(+), 6 deletions(-) diff --git a/configuration/componentcfg/query.go b/configuration/componentcfg/query.go index e8cc25963..173660a60 100644 --- a/configuration/componentcfg/query.go +++ b/configuration/componentcfg/query.go @@ -26,7 +26,9 @@ package componentcfg import ( "errors" + "net/url" "regexp" + "strconv" "strings" apricotpb "github.com/AliceO2Group/Control/apricot/protos" @@ -42,8 +44,9 @@ var ( // component /RUNTYPE /rolename /entry @timestamp inputFullRegex = regexp.MustCompile(`^([a-zA-Z0-9-_]+)(\/[A-Z0-9-_]+){1}(\/[a-z-A-Z0-9-_]+){1}(\/[a-z-A-Z0-9-_]+){1}(\@[0-9]+)?$`) // component /RUNTYPE /rolename - inputEntriesRegex = regexp.MustCompile(`^([a-zA-Z0-9-_]+)(\/[A-Z0-9-_]+){1}(\/[a-z-A-Z0-9-_]+){1}$`) - E_BAD_KEY = errors.New("bad component configuration key format") + inputEntriesRegex = regexp.MustCompile(`^([a-zA-Z0-9-_]+)(\/[A-Z0-9-_]+){1}(\/[a-z-A-Z0-9-_]+){1}$`) + inputParametersRegex = regexp.MustCompile(`^([a-zA-Z0-9-_]+=[a-zA-Z0-9-_,]+)(&[a-zA-Z0-9-_]+=[a-zA-Z0-9-_,]+)*$`) + E_BAD_KEY = errors.New("bad component configuration key format") ) func IsStringValidQueryPathWithOptionalTimestamp(input string) bool { @@ -52,6 +55,9 @@ func IsStringValidQueryPathWithOptionalTimestamp(input string) bool { func IsStringValidEntriesQueryPath(input string) bool { return inputEntriesRegex.MatchString(input) } +func IsStringValidQueryParameters(input string) bool { + return inputParametersRegex.MatchString(input) +} type EntriesQuery struct { Component string @@ -192,3 +198,42 @@ func (p *Query) AbsoluteRaw() string { func (p *Query) AbsoluteWithoutTimestamp() string { return ConfigComponentsPath + p.WithoutTimestamp() } + +type QueryParameters struct { + ProcessTemplates bool + VarStack map[string]string +} + +func NewQueryParameters(parameters string) (p *QueryParameters, err error) { + p = &QueryParameters{ + ProcessTemplates: false, + VarStack: make(map[string]string), + } + parameters = strings.TrimSpace(parameters) + + if !IsStringValidQueryParameters(parameters) { + err = E_BAD_KEY + return + } + keyValues, err := url.ParseQuery(parameters) + if err != nil { + return + } + // in our case, we support just one value per key, thus we map the returned keyValues accordingly + for key, values := range keyValues { + if len(values) != 1 { + err = E_BAD_KEY + return + } + if key == "process" { + p.ProcessTemplates, err = strconv.ParseBool(values[0]) + if err != nil { + return + } + } else { + p.VarStack[key] = values[0] + } + } + + return p, nil +} diff --git a/configuration/componentcfg/query_test.go b/configuration/componentcfg/query_test.go index 65c4c4416..d273f60cd 100644 --- a/configuration/componentcfg/query_test.go +++ b/configuration/componentcfg/query_test.go @@ -194,6 +194,57 @@ var _ = Describe("query", func() { }) }) }) + + Describe("Component configuration query parameters", func() { + var ( + q *componentcfg.QueryParameters + err error + ) + + When("creating new valid query parameters", func() { + BeforeEach(func() { + q, err = componentcfg.NewQueryParameters("process=true&a=aaa&b=123&C_D3=C,C,C") + }) + It("should be parsed without reporting errors", func() { + Expect(err).To(BeNil()) + }) + It("should have parsed the process variable correctly", func() { + Expect(q.ProcessTemplates).To(BeTrue()) + }) + It("should have parsed the var stack correctly", func() { + Expect(q.VarStack["a"]).To(Equal("aaa")) + Expect(q.VarStack["b"]).To(Equal("123")) + Expect(q.VarStack["C_D3"]).To(Equal("C,C,C")) + }) + }) + + Describe("dealing with incorrectly formatted query parameters", func() { + When("query parameters are empty", func() { + BeforeEach(func() { + q, err = componentcfg.NewQueryParameters("") + }) + It("should return an error", func() { + Expect(err).To(MatchError(componentcfg.E_BAD_KEY)) + }) + }) + When("a key has empty value", func() { + BeforeEach(func() { + q, err = componentcfg.NewQueryParameters("process=") + }) + It("should return an error", func() { + Expect(err).To(MatchError(componentcfg.E_BAD_KEY)) + }) + }) + When("there are two identical keys", func() { + BeforeEach(func() { + q, err = componentcfg.NewQueryParameters("a=33&a=34") + }) + It("should return an error", func() { + Expect(err).To(MatchError(componentcfg.E_BAD_KEY)) + }) + }) + }) + }) }) func TestQuery(t *testing.T) { diff --git a/configuration/template/dplutil.go b/configuration/template/dplutil.go index e3ca62943..1943bfa0f 100644 --- a/configuration/template/dplutil.go +++ b/configuration/template/dplutil.go @@ -47,7 +47,7 @@ func jitDplGenerate(confSvc ConfigurationService, varStack map[string]string, wo var payloads []string // Match any consul URL - re := regexp.MustCompile(`(consul-json|apricot)://[^ |\n]*`) + re := regexp.MustCompile(`(consul-json|apricot)://[^ |"\n]*`) matches := re.FindAllStringSubmatch(dplCommand, nMaxExpectedQcPayloads) matches = append(matches) @@ -57,13 +57,28 @@ func jitDplGenerate(confSvc ConfigurationService, varStack map[string]string, wo keyRe := regexp.MustCompile(`components/[^']*`) consulKeyMatch := keyRe.FindAllStringSubmatch(match[0], 1) consulKey := strings.SplitAfter(consulKeyMatch[0][0], "components/") + // split between the query and its parameters if there are any + consulKeyTokens := strings.Split(consulKey[1], "?") - // And query Apricot for the configuration payload - newQ, err := componentcfg.NewQuery(consulKey[1]) + // Query Apricot for the configuration payload + query, err := componentcfg.NewQuery(consulKeyTokens[0]) if err != nil { return "", fmt.Errorf("JIT could not create a query out of path '%s'. error: %w", consulKey[1], err) } - payload, err := confSvc.GetComponentConfiguration(newQ) + // parse parameters if they are present + queryParams := &componentcfg.QueryParameters{ProcessTemplates: false, VarStack: nil} + if len(consulKeyTokens) == 2 { + queryParams, err = componentcfg.NewQueryParameters(consulKeyTokens[1]) + if err != nil { + return "", fmt.Errorf("JIT could not parse query parameters of path '%s', error: %w", consulKey[1], err) + } + } + var payload string + if queryParams.ProcessTemplates { + payload, err = confSvc.GetAndProcessComponentConfiguration(query, queryParams.VarStack) + } else { + payload, err = confSvc.GetComponentConfiguration(query) + } if err != nil { return "", fmt.Errorf("JIT failed trying to query QC payload '%s', error: %w", match, err)