@ -18,6 +18,10 @@ package main
import (
import (
"fmt"
"fmt"
"regexp/syntax"
"strings"
"unicode"
"unicode/utf8"
"github.com/spf13/cobra"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -41,7 +45,9 @@ the status of the object.`,
type imagePolicyFlags struct {
type imagePolicyFlags struct {
imageRef string
imageRef string
semver string
semver string
alpha string
filterRegex string
filterRegex string
filterExtract string
}
}
var imagePolicyArgs = imagePolicyFlags { }
var imagePolicyArgs = imagePolicyFlags { }
@ -49,8 +55,10 @@ var imagePolicyArgs = imagePolicyFlags{}
func init ( ) {
func init ( ) {
flags := createImagePolicyCmd . Flags ( )
flags := createImagePolicyCmd . Flags ( )
flags . StringVar ( & imagePolicyArgs . imageRef , "image-ref" , "" , "the name of an image repository object" )
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 . semver , "select-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 . 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 )
createImageCmd . AddCommand ( createImagePolicyCmd )
}
}
@ -90,18 +98,40 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error {
}
}
switch {
switch {
case imagePolicyArgs . semver != "" && imagePolicyArgs . alpha != "" :
return fmt . Errorf ( "policy cannot be specified with both --select-semver and --select-alpha" )
case imagePolicyArgs . semver != "" :
case imagePolicyArgs . semver != "" :
policy . Spec . Policy . SemVer = & imagev1 . SemVerPolicy {
policy . Spec . Policy . SemVer = & imagev1 . SemVerPolicy {
Range : imagePolicyArgs . semver ,
Range : imagePolicyArgs . semver ,
}
}
case imagePolicyArgs . alpha != "" :
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 :
default :
return fmt . Errorf ( "a policy must be provided with --semver" )
return fmt . Errorf ( "a policy must be provided with either --select-se mver or --select-alpha ")
}
}
if imagePolicyArgs . filterRegex != "" {
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 {
policy . Spec . FilterTags = & imagev1 . TagFilter {
Pattern : imagePolicyArgs . filterRegex ,
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 != "" {
return fmt . Errorf ( "cannot specify --filter-extract without specifying --filter-regex" )
}
}
if createArgs . export {
if createArgs . export {
@ -117,3 +147,94 @@ func createImagePolicyRun(cmd *cobra.Command, args []string) error {
} )
} )
return err
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
}