Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding --select-alpha and --extract to create image policy #860

Merged
merged 4 commits into from
Feb 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,13 @@ jobs:
./bin/flux create image policy podinfo \
--image-ref=podinfo \
--interval=1m \
--semver=5.0.x
--select-semver=5.0.x
- name: flux create image policy podinfo-select-alpha
run: |
./bin/flux create image policy podinfo-alpha \
--image-ref=podinfo \
--interval=1m \
--select-alpha=desc
- name: flux get image policy
run: |
./bin/flux get image policy podinfo | grep '5.0.3'
Expand Down
133 changes: 127 additions & 6 deletions cmd/flux/create_image_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -39,18 +43,22 @@ the status of the object.`,
RunE: createImagePolicyRun}

type imagePolicyFlags struct {
imageRef string
semver string
filterRegex string
imageRef string
semver string
alpha string
filterRegex string
filterExtract string
}

var imagePolicyArgs = imagePolicyFlags{}

func init() {
flags := createImagePolicyCmd.Flags()
flags.StringVar(&imagePolicyArgs.imageRef, "image-ref", "", "the name of an image repository object")
flags.StringVar(&imagePolicyArgs.semver, "semver", "", "a semver range to apply to tags; e.g., '1.x'")
flags.StringVar(&imagePolicyArgs.filterRegex, "filter-regex", "", " regular expression pattern used to filter the image tags")
flags.StringVar(&imagePolicyArgs.semver, "select-semver", "", "a semver range to apply to tags; e.g., '1.x'")
flags.StringVar(&imagePolicyArgs.alpha, "select-alpha", "", "use alphabetical sorting to select image; either \"asc\" meaning select the last, or \"desc\" meaning select the first")
flags.StringVar(&imagePolicyArgs.filterRegex, "filter-regex", "", "regular expression pattern used to filter the image tags")
flags.StringVar(&imagePolicyArgs.filterExtract, "filter-extract", "", "replacement pattern (using capture groups from --filter-regex) to use for sorting")

createImageCmd.AddCommand(createImagePolicyCmd)
}
Expand Down Expand Up @@ -90,18 +98,40 @@ 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,
}
case imagePolicyArgs.alpha != "":
jonathan-innis marked this conversation as resolved.
Show resolved Hide resolved
if imagePolicyArgs.alpha != "desc" && imagePolicyArgs.alpha != "asc" {
return fmt.Errorf("--select-alpha must be one of [\"asc\", \"desc\"]")
}
policy.Spec.Policy.Alphabetical = &imagev1.AlphabeticalPolicy{
Order: imagePolicyArgs.alpha,
}
default:
return fmt.Errorf("a policy must be provided with --semver")
return fmt.Errorf("a policy must be provided with either --select-semver or --select-alpha")
}

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 != "" {
jonathan-innis marked this conversation as resolved.
Show resolved Hide resolved
if err := validateExtractStr(imagePolicyArgs.filterExtract, exp.CapNames()); err != nil {
return err
}
policy.Spec.FilterTags.Extract = imagePolicyArgs.filterExtract
}
} else if imagePolicyArgs.filterExtract != "" {
return fmt.Errorf("cannot specify --filter-extract without specifying --filter-regex")
}

if createArgs.export {
Expand All @@ -117,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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could check it against exp.MaxCap() (exp from the caller).

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
}
10 changes: 6 additions & 4 deletions docs/cmd/flux_create_image_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ flux create image policy <name> [flags]
### Options

```
--filter-regex string regular expression pattern used to filter the image tags
-h, --help help for policy
--image-ref string the name of an image repository object
--semver string a semver range to apply to tags; e.g., '1.x'
--filter-extract string replacement pattern (using capture groups from --filter-regex) to use for sorting
--filter-regex string regular expression pattern used to filter the image tags
-h, --help help for policy
--image-ref string the name of an image repository object
--select-alpha string use alphabetical sorting to select image; either "asc" meaning select the last, or "desc" meaning select the first
--select-semver string a semver range to apply to tags; e.g., '1.x'
```

### Options inherited from parent commands
Expand Down