Merge pull request #32599 from mikedanese/kubectl-selector

Automatic merge from submit-queue

allow kubectl -f to filter by selector

cc @kubernetes/kubectl

Fixes #32544
pull/6/head
Kubernetes Submit Queue 2016-10-01 01:13:09 -07:00 committed by GitHub
commit 6f69293240
6 changed files with 112 additions and 21 deletions

View File

@ -992,6 +992,21 @@ __EOF__
# Clean up # Clean up
kubectl delete pods test-pod "${kube_flags[@]}" kubectl delete pods test-pod "${kube_flags[@]}"
## kubectl apply -f with label selector should only apply matching objects
# Pre-Condition: no POD exists
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
# apply
kubectl apply -l unique-label=bingbang -f hack/testdata/filter "${kube_flags[@]}"
# check right pod exists
kube::test::get_object_assert 'pods selector-test-pod' "{{${labels_field}.name}}" 'selector-test-pod'
# check wrong pod doesn't exist
output_message=$(! kubectl get pods selector-test-pod-dont-apply 2>&1 "${kube_flags[@]}")
kube::test::if_has_string "${output_message}" 'pods "selector-test-pod-dont-apply" not found'
# cleanup
kubectl delete pods selector-test-pod
## kubectl run should create deployments or jobs ## kubectl run should create deployments or jobs
# Pre-Condition: no Job exists # Pre-Condition: no Job exists
kube::test::get_object_assert jobs "{{range.items}}{{$id_field}}:{{end}}" '' kube::test::get_object_assert jobs "{{range.items}}{{$id_field}}:{{end}}" ''

View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: Pod
metadata:
name: selector-test-pod
labels:
name: selector-test-pod
unique-label: bingbang
spec:
containers:
- name: kubernetes-pause
image: gcr.io/google-containers/pause:2.0

View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: Pod
metadata:
name: selector-test-pod-dont-apply
labels:
name: selector-test-pod-dont-apply
unique-label: biz
spec:
containers:
- name: kubernetes-pause
image: gcr.io/google-containers/pause:2.0

View File

@ -35,6 +35,11 @@ import (
"k8s.io/kubernetes/pkg/util/strategicpatch" "k8s.io/kubernetes/pkg/util/strategicpatch"
) )
type ApplyOptions struct {
FilenameOptions resource.FilenameOptions
Selector string
}
const ( const (
// maxPatchRetry is the maximum number of conflicts retry for during a patch operation before returning failure // maxPatchRetry is the maximum number of conflicts retry for during a patch operation before returning failure
maxPatchRetry = 5 maxPatchRetry = 5
@ -61,7 +66,7 @@ var (
) )
func NewCmdApply(f *cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdApply(f *cmdutil.Factory, out io.Writer) *cobra.Command {
options := &resource.FilenameOptions{} var options ApplyOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "apply -f FILENAME", Use: "apply -f FILENAME",
@ -71,15 +76,16 @@ func NewCmdApply(f *cmdutil.Factory, out io.Writer) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(validateArgs(cmd, args)) cmdutil.CheckErr(validateArgs(cmd, args))
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
cmdutil.CheckErr(RunApply(f, cmd, out, options)) cmdutil.CheckErr(RunApply(f, cmd, out, &options))
}, },
} }
usage := "that contains the configuration to apply" usage := "that contains the configuration to apply"
cmdutil.AddFilenameOptionFlags(cmd, options, usage) cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.MarkFlagRequired("filename") cmd.MarkFlagRequired("filename")
cmd.Flags().Bool("overwrite", true, "Automatically resolve conflicts between the modified and live configuration by using values from the modified configuration") cmd.Flags().Bool("overwrite", true, "Automatically resolve conflicts between the modified and live configuration by using values from the modified configuration")
cmdutil.AddValidateFlags(cmd) cmdutil.AddValidateFlags(cmd)
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on")
cmdutil.AddOutputFlagsForMutation(cmd) cmdutil.AddOutputFlagsForMutation(cmd)
cmdutil.AddRecordFlag(cmd) cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd) cmdutil.AddInclude3rdPartyFlags(cmd)
@ -94,7 +100,7 @@ func validateArgs(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *resource.FilenameOptions) error { func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *ApplyOptions) error {
shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" shortOutput := cmdutil.GetFlagString(cmd, "output") == "name"
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir")) schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
if err != nil { if err != nil {
@ -111,7 +117,8 @@ func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *re
Schema(schema). Schema(schema).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, options). FilenameParam(enforceNamespace, &options.FilenameOptions).
SelectorParam(options.Selector).
Flatten(). Flatten().
Do() Do()
err = r.Err() err = r.Err()

View File

@ -538,6 +538,11 @@ func (b *Builder) visitorResult() *Result {
b.selector = labels.Everything() b.selector = labels.Everything()
} }
// visit items specified by paths
if len(b.paths) != 0 {
return b.visitByPaths()
}
// visit selectors // visit selectors
if b.selector != nil { if b.selector != nil {
return b.visitBySelector() return b.visitBySelector()
@ -553,11 +558,6 @@ func (b *Builder) visitorResult() *Result {
return b.visitByName() return b.visitByName()
} }
// visit items specified by paths
if len(b.paths) != 0 {
return b.visitByPaths()
}
if len(b.resources) != 0 { if len(b.resources) != 0 {
return &Result{err: fmt.Errorf("resource(s) were provided, but no name, label selector, or --all flag specified")} return &Result{err: fmt.Errorf("resource(s) were provided, but no name, label selector, or --all flag specified")}
} }
@ -574,14 +574,6 @@ func (b *Builder) visitBySelector() *Result {
if len(b.resources) == 0 { if len(b.resources) == 0 {
return &Result{err: fmt.Errorf("at least one resource must be specified to use a selector")} return &Result{err: fmt.Errorf("at least one resource must be specified to use a selector")}
} }
// empty selector has different error message for paths being provided
if len(b.paths) != 0 {
if b.selector.Empty() {
return &Result{err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well")}
} else {
return &Result{err: fmt.Errorf("a selector may not be specified when path, URL, or stdin is provided as input")}
}
}
mappings, err := b.resourceMappings() mappings, err := b.resourceMappings()
if err != nil { if err != nil {
return &Result{err: err} return &Result{err: err}
@ -613,9 +605,6 @@ func (b *Builder) visitByResource() *Result {
isSingular = len(b.resourceTuples) == 1 isSingular = len(b.resourceTuples) == 1
} }
if len(b.paths) != 0 {
return &Result{singular: isSingular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well")}
}
if len(b.resources) != 0 { if len(b.resources) != 0 {
return &Result{singular: isSingular, err: fmt.Errorf("you may not specify individual resources and bulk resources in the same call")} return &Result{singular: isSingular, err: fmt.Errorf("you may not specify individual resources and bulk resources in the same call")}
} }
@ -718,6 +707,12 @@ func (b *Builder) visitByPaths() *Result {
if len(b.resources) != 0 { if len(b.resources) != 0 {
return &Result{singular: singular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify resource arguments as well")} return &Result{singular: singular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify resource arguments as well")}
} }
if len(b.names) != 0 {
return &Result{err: fmt.Errorf("name cannot be provided when a path is specified")}
}
if len(b.resourceTuples) != 0 {
return &Result{err: fmt.Errorf("resource/name arguments cannot be provided when a path is specified")}
}
var visitors Visitor var visitors Visitor
if b.continueOnError { if b.continueOnError {
@ -738,6 +733,9 @@ func (b *Builder) visitByPaths() *Result {
} }
visitors = NewDecoratedVisitor(visitors, RetrieveLatest) visitors = NewDecoratedVisitor(visitors, RetrieveLatest)
} }
if b.selector != nil {
visitors = NewFilteredVisitor(visitors, FilterBySelector(b.selector))
}
return &Result{singular: singular, visitor: visitors, sources: b.paths} return &Result{singular: singular, visitor: visitors, sources: b.paths}
} }

View File

@ -31,6 +31,7 @@ import (
"k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
utilerrors "k8s.io/kubernetes/pkg/util/errors" utilerrors "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/yaml" "k8s.io/kubernetes/pkg/util/yaml"
@ -639,3 +640,51 @@ func RetrieveLazy(info *Info, err error) error {
} }
return nil return nil
} }
type FilterFunc func(info *Info, err error) (bool, error)
type FilteredVisitor struct {
visitor Visitor
filters []FilterFunc
}
func NewFilteredVisitor(v Visitor, fn ...FilterFunc) Visitor {
if len(fn) == 0 {
return v
}
return FilteredVisitor{v, fn}
}
func (v FilteredVisitor) Visit(fn VisitorFunc) error {
return v.visitor.Visit(func(info *Info, err error) error {
if err != nil {
return err
}
for _, filter := range v.filters {
ok, err := filter(info, nil)
if err != nil {
return err
}
if !ok {
return nil
}
}
return fn(info, nil)
})
}
func FilterBySelector(s labels.Selector) FilterFunc {
return func(info *Info, err error) (bool, error) {
if err != nil {
return false, err
}
a, err := meta.Accessor(info.Object)
if err != nil {
return false, err
}
if !s.Matches(labels.Set(a.GetLabels())) {
return false, nil
}
return true, nil
}
}