https://github.com/kubernetes/enhancements/blob/master/keps/sig-storage/177-volume-snapshot/tighten-validation-webhook-crd.md 1. Ratcheting validation webhook server image 2. Controller labels invalid objects 3. Unit tests for webhook 4. Deployment README and example deployment method with certs 5. Update top-level README Racheting validation: 1. webhook is strict on create 2. webhook is strict on updates where the existing object passes strict validation 3. webhook is relaxed on updates where the existing object fails strict validation (allows finalizer removal, status update, deletion, etc) Additionally the validating wehook server will perform immutability checks on scenario 2 above.
337 lines
9.6 KiB
Go
337 lines
9.6 KiB
Go
package cobra
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/spf13/pflag"
|
|
)
|
|
|
|
const (
|
|
zshCompArgumentAnnotation = "cobra_annotations_zsh_completion_argument_annotation"
|
|
zshCompArgumentFilenameComp = "cobra_annotations_zsh_completion_argument_file_completion"
|
|
zshCompArgumentWordComp = "cobra_annotations_zsh_completion_argument_word_completion"
|
|
zshCompDirname = "cobra_annotations_zsh_dirname"
|
|
)
|
|
|
|
var (
|
|
zshCompFuncMap = template.FuncMap{
|
|
"genZshFuncName": zshCompGenFuncName,
|
|
"extractFlags": zshCompExtractFlag,
|
|
"genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments,
|
|
"extractArgsCompletions": zshCompExtractArgumentCompletionHintsForRendering,
|
|
}
|
|
zshCompletionText = `
|
|
{{/* should accept Command (that contains subcommands) as parameter */}}
|
|
{{define "argumentsC" -}}
|
|
{{ $cmdPath := genZshFuncName .}}
|
|
function {{$cmdPath}} {
|
|
local -a commands
|
|
|
|
_arguments -C \{{- range extractFlags .}}
|
|
{{genFlagEntryForZshArguments .}} \{{- end}}
|
|
"1: :->cmnds" \
|
|
"*::arg:->args"
|
|
|
|
case $state in
|
|
cmnds)
|
|
commands=({{range .Commands}}{{if not .Hidden}}
|
|
"{{.Name}}:{{.Short}}"{{end}}{{end}}
|
|
)
|
|
_describe "command" commands
|
|
;;
|
|
esac
|
|
|
|
case "$words[1]" in {{- range .Commands}}{{if not .Hidden}}
|
|
{{.Name}})
|
|
{{$cmdPath}}_{{.Name}}
|
|
;;{{end}}{{end}}
|
|
esac
|
|
}
|
|
{{range .Commands}}{{if not .Hidden}}
|
|
{{template "selectCmdTemplate" .}}
|
|
{{- end}}{{end}}
|
|
{{- end}}
|
|
|
|
{{/* should accept Command without subcommands as parameter */}}
|
|
{{define "arguments" -}}
|
|
function {{genZshFuncName .}} {
|
|
{{" _arguments"}}{{range extractFlags .}} \
|
|
{{genFlagEntryForZshArguments . -}}
|
|
{{end}}{{range extractArgsCompletions .}} \
|
|
{{.}}{{end}}
|
|
}
|
|
{{end}}
|
|
|
|
{{/* dispatcher for commands with or without subcommands */}}
|
|
{{define "selectCmdTemplate" -}}
|
|
{{if .Hidden}}{{/* ignore hidden*/}}{{else -}}
|
|
{{if .Commands}}{{template "argumentsC" .}}{{else}}{{template "arguments" .}}{{end}}
|
|
{{- end}}
|
|
{{- end}}
|
|
|
|
{{/* template entry point */}}
|
|
{{define "Main" -}}
|
|
#compdef _{{.Name}} {{.Name}}
|
|
|
|
{{template "selectCmdTemplate" .}}
|
|
{{end}}
|
|
`
|
|
)
|
|
|
|
// zshCompArgsAnnotation is used to encode/decode zsh completion for
|
|
// arguments to/from Command.Annotations.
|
|
type zshCompArgsAnnotation map[int]zshCompArgHint
|
|
|
|
type zshCompArgHint struct {
|
|
// Indicates the type of the completion to use. One of:
|
|
// zshCompArgumentFilenameComp or zshCompArgumentWordComp
|
|
Tipe string `json:"type"`
|
|
|
|
// A value for the type above (globs for file completion or words)
|
|
Options []string `json:"options"`
|
|
}
|
|
|
|
// GenZshCompletionFile generates zsh completion file.
|
|
func (c *Command) GenZshCompletionFile(filename string) error {
|
|
outFile, err := os.Create(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer outFile.Close()
|
|
|
|
return c.GenZshCompletion(outFile)
|
|
}
|
|
|
|
// GenZshCompletion generates a zsh completion file and writes to the passed
|
|
// writer. The completion always run on the root command regardless of the
|
|
// command it was called from.
|
|
func (c *Command) GenZshCompletion(w io.Writer) error {
|
|
tmpl, err := template.New("Main").Funcs(zshCompFuncMap).Parse(zshCompletionText)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating zsh completion template: %v", err)
|
|
}
|
|
return tmpl.Execute(w, c.Root())
|
|
}
|
|
|
|
// MarkZshCompPositionalArgumentFile marks the specified argument (first
|
|
// argument is 1) as completed by file selection. patterns (e.g. "*.txt") are
|
|
// optional - if not provided the completion will search for all files.
|
|
func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error {
|
|
if argPosition < 1 {
|
|
return fmt.Errorf("Invalid argument position (%d)", argPosition)
|
|
}
|
|
annotation, err := c.zshCompGetArgsAnnotations()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) {
|
|
return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition)
|
|
}
|
|
annotation[argPosition] = zshCompArgHint{
|
|
Tipe: zshCompArgumentFilenameComp,
|
|
Options: patterns,
|
|
}
|
|
return c.zshCompSetArgsAnnotations(annotation)
|
|
}
|
|
|
|
// MarkZshCompPositionalArgumentWords marks the specified positional argument
|
|
// (first argument is 1) as completed by the provided words. At east one word
|
|
// must be provided, spaces within words will be offered completion with
|
|
// "word\ word".
|
|
func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error {
|
|
if argPosition < 1 {
|
|
return fmt.Errorf("Invalid argument position (%d)", argPosition)
|
|
}
|
|
if len(words) == 0 {
|
|
return fmt.Errorf("Trying to set empty word list for positional argument %d", argPosition)
|
|
}
|
|
annotation, err := c.zshCompGetArgsAnnotations()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) {
|
|
return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition)
|
|
}
|
|
annotation[argPosition] = zshCompArgHint{
|
|
Tipe: zshCompArgumentWordComp,
|
|
Options: words,
|
|
}
|
|
return c.zshCompSetArgsAnnotations(annotation)
|
|
}
|
|
|
|
func zshCompExtractArgumentCompletionHintsForRendering(c *Command) ([]string, error) {
|
|
var result []string
|
|
annotation, err := c.zshCompGetArgsAnnotations()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for k, v := range annotation {
|
|
s, err := zshCompRenderZshCompArgHint(k, v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, s)
|
|
}
|
|
if len(c.ValidArgs) > 0 {
|
|
if _, positionOneExists := annotation[1]; !positionOneExists {
|
|
s, err := zshCompRenderZshCompArgHint(1, zshCompArgHint{
|
|
Tipe: zshCompArgumentWordComp,
|
|
Options: c.ValidArgs,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, s)
|
|
}
|
|
}
|
|
sort.Strings(result)
|
|
return result, nil
|
|
}
|
|
|
|
func zshCompRenderZshCompArgHint(i int, z zshCompArgHint) (string, error) {
|
|
switch t := z.Tipe; t {
|
|
case zshCompArgumentFilenameComp:
|
|
var globs []string
|
|
for _, g := range z.Options {
|
|
globs = append(globs, fmt.Sprintf(`-g "%s"`, g))
|
|
}
|
|
return fmt.Sprintf(`'%d: :_files %s'`, i, strings.Join(globs, " ")), nil
|
|
case zshCompArgumentWordComp:
|
|
var words []string
|
|
for _, w := range z.Options {
|
|
words = append(words, fmt.Sprintf("%q", w))
|
|
}
|
|
return fmt.Sprintf(`'%d: :(%s)'`, i, strings.Join(words, " ")), nil
|
|
default:
|
|
return "", fmt.Errorf("Invalid zsh argument completion annotation: %s", t)
|
|
}
|
|
}
|
|
|
|
func (c *Command) zshcompArgsAnnotationnIsDuplicatePosition(annotation zshCompArgsAnnotation, position int) bool {
|
|
_, dup := annotation[position]
|
|
return dup
|
|
}
|
|
|
|
func (c *Command) zshCompGetArgsAnnotations() (zshCompArgsAnnotation, error) {
|
|
annotation := make(zshCompArgsAnnotation)
|
|
annotationString, ok := c.Annotations[zshCompArgumentAnnotation]
|
|
if !ok {
|
|
return annotation, nil
|
|
}
|
|
err := json.Unmarshal([]byte(annotationString), &annotation)
|
|
if err != nil {
|
|
return annotation, fmt.Errorf("Error unmarshaling zsh argument annotation: %v", err)
|
|
}
|
|
return annotation, nil
|
|
}
|
|
|
|
func (c *Command) zshCompSetArgsAnnotations(annotation zshCompArgsAnnotation) error {
|
|
jsn, err := json.Marshal(annotation)
|
|
if err != nil {
|
|
return fmt.Errorf("Error marshaling zsh argument annotation: %v", err)
|
|
}
|
|
if c.Annotations == nil {
|
|
c.Annotations = make(map[string]string)
|
|
}
|
|
c.Annotations[zshCompArgumentAnnotation] = string(jsn)
|
|
return nil
|
|
}
|
|
|
|
func zshCompGenFuncName(c *Command) string {
|
|
if c.HasParent() {
|
|
return zshCompGenFuncName(c.Parent()) + "_" + c.Name()
|
|
}
|
|
return "_" + c.Name()
|
|
}
|
|
|
|
func zshCompExtractFlag(c *Command) []*pflag.Flag {
|
|
var flags []*pflag.Flag
|
|
c.LocalFlags().VisitAll(func(f *pflag.Flag) {
|
|
if !f.Hidden {
|
|
flags = append(flags, f)
|
|
}
|
|
})
|
|
c.InheritedFlags().VisitAll(func(f *pflag.Flag) {
|
|
if !f.Hidden {
|
|
flags = append(flags, f)
|
|
}
|
|
})
|
|
return flags
|
|
}
|
|
|
|
// zshCompGenFlagEntryForArguments returns an entry that matches _arguments
|
|
// zsh-completion parameters. It's too complicated to generate in a template.
|
|
func zshCompGenFlagEntryForArguments(f *pflag.Flag) string {
|
|
if f.Name == "" || f.Shorthand == "" {
|
|
return zshCompGenFlagEntryForSingleOptionFlag(f)
|
|
}
|
|
return zshCompGenFlagEntryForMultiOptionFlag(f)
|
|
}
|
|
|
|
func zshCompGenFlagEntryForSingleOptionFlag(f *pflag.Flag) string {
|
|
var option, multiMark, extras string
|
|
|
|
if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) {
|
|
multiMark = "*"
|
|
}
|
|
|
|
option = "--" + f.Name
|
|
if option == "--" {
|
|
option = "-" + f.Shorthand
|
|
}
|
|
extras = zshCompGenFlagEntryExtras(f)
|
|
|
|
return fmt.Sprintf(`'%s%s[%s]%s'`, multiMark, option, zshCompQuoteFlagDescription(f.Usage), extras)
|
|
}
|
|
|
|
func zshCompGenFlagEntryForMultiOptionFlag(f *pflag.Flag) string {
|
|
var options, parenMultiMark, curlyMultiMark, extras string
|
|
|
|
if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) {
|
|
parenMultiMark = "*"
|
|
curlyMultiMark = "\\*"
|
|
}
|
|
|
|
options = fmt.Sprintf(`'(%s-%s %s--%s)'{%s-%s,%s--%s}`,
|
|
parenMultiMark, f.Shorthand, parenMultiMark, f.Name, curlyMultiMark, f.Shorthand, curlyMultiMark, f.Name)
|
|
extras = zshCompGenFlagEntryExtras(f)
|
|
|
|
return fmt.Sprintf(`%s'[%s]%s'`, options, zshCompQuoteFlagDescription(f.Usage), extras)
|
|
}
|
|
|
|
func zshCompGenFlagEntryExtras(f *pflag.Flag) string {
|
|
if f.NoOptDefVal != "" {
|
|
return ""
|
|
}
|
|
|
|
extras := ":" // allow options for flag (even without assistance)
|
|
for key, values := range f.Annotations {
|
|
switch key {
|
|
case zshCompDirname:
|
|
extras = fmt.Sprintf(":filename:_files -g %q", values[0])
|
|
case BashCompFilenameExt:
|
|
extras = ":filename:_files"
|
|
for _, pattern := range values {
|
|
extras = extras + fmt.Sprintf(` -g "%s"`, pattern)
|
|
}
|
|
}
|
|
}
|
|
|
|
return extras
|
|
}
|
|
|
|
func zshCompFlagCouldBeSpecifiedMoreThenOnce(f *pflag.Flag) bool {
|
|
return strings.Contains(f.Value.Type(), "Slice") ||
|
|
strings.Contains(f.Value.Type(), "Array")
|
|
}
|
|
|
|
func zshCompQuoteFlagDescription(s string) string {
|
|
return strings.Replace(s, "'", `'\''`, -1)
|
|
}
|