diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 566489be3a..de51ddc124 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -166,13 +166,13 @@ jobs: ./bin/flux create image repository podinfo \ --image=ghcr.io/stefanprodan/podinfo \ --interval=1m - - name: flux create image policy select-semver + - name: flux create image policy run: | - ./bin/flux create image policy podinfo-semver \ + ./bin/flux create image policy podinfo \ --image-ref=podinfo \ --interval=1m \ --select-semver=5.0.x - - name: flux create image policy select-alpha + - name: flux create image policy podinfo-select-alpha run: | ./bin/flux create image policy podinfo-alpha \ --image-ref=podinfo \ diff --git a/cmd/flux/create_image_policy.go b/cmd/flux/create_image_policy.go index 211edca322..777fcdadd8 100644 --- a/cmd/flux/create_image_policy.go +++ b/cmd/flux/create_image_policy.go @@ -18,6 +18,10 @@ package main import ( "fmt" + "regexp/syntax" + "strings" + "unicode" + "unicode/utf8" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -94,6 +98,8 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error { } switch { + case imagePolicyArgs.semver != "" && imagePolicyArgs.alpha != "": + return fmt.Errorf("policy cannot be specified with both --select-semver and --select-alpha") case imagePolicyArgs.semver != "": policy.Spec.Policy.SemVer = &imagev1.SemVerPolicy{ Range: imagePolicyArgs.semver, @@ -110,11 +116,18 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error { } if imagePolicyArgs.filterRegex != "" { + exp, err := syntax.Parse(imagePolicyArgs.filterRegex, syntax.Perl) + if err != nil { + return fmt.Errorf("--filter-regex is an invalid regex pattern") + } policy.Spec.FilterTags = &imagev1.TagFilter{ Pattern: imagePolicyArgs.filterRegex, } if imagePolicyArgs.filterExtract != "" { + if err := validateExtractStr(imagePolicyArgs.filterExtract, exp.CapNames()); err != nil { + return err + } policy.Spec.FilterTags.Extract = imagePolicyArgs.filterExtract } } else if imagePolicyArgs.filterExtract != "" { @@ -134,3 +147,94 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error { }) return err } + +// Performs a dry-run of the extract function in Regexp to validate the template +func validateExtractStr(template string, capNames []string) error { + for len(template) > 0 { + i := strings.Index(template, "$") + if i < 0 { + return nil + } + template = template[i:] + if len(template) > 1 && template[1] == '$' { + template = template[2:] + continue + } + name, num, rest, ok := extract(template) + if !ok { + // Malformed extract string, assume user didn't want this + template = template[1:] + return fmt.Errorf("--filter-extract is malformed") + } + template = rest + if num >= 0 { + // we won't worry about numbers as we can't validate these + continue + } else { + found := false + for _, capName := range capNames { + if name == capName { + found = true + } + } + if !found { + return fmt.Errorf("capture group $%s used in --filter-extract not found in --filter-regex", name) + } + } + + } + return nil +} + +// extract method from the regexp package +// returns the name or number of the value prepended by $ +func extract(str string) (name string, num int, rest string, ok bool) { + if len(str) < 2 || str[0] != '$' { + return + } + brace := false + if str[1] == '{' { + brace = true + str = str[2:] + } else { + str = str[1:] + } + i := 0 + for i < len(str) { + rune, size := utf8.DecodeRuneInString(str[i:]) + if !unicode.IsLetter(rune) && !unicode.IsDigit(rune) && rune != '_' { + break + } + i += size + } + if i == 0 { + // empty name is not okay + return + } + name = str[:i] + if brace { + if i >= len(str) || str[i] != '}' { + // missing closing brace + return + } + i++ + } + + // Parse number. + num = 0 + for i := 0; i < len(name); i++ { + if name[i] < '0' || '9' < name[i] || num >= 1e8 { + num = -1 + break + } + num = num*10 + int(name[i]) - '0' + } + // Disallow leading zeros. + if name[0] == '0' && len(name) > 1 { + num = -1 + } + + rest = str[i:] + ok = true + return +}