mirror of https://github.com/k3s-io/k3s
Merge pull request #50140 from dixudx/kubectl_add_fieldSelector
Automatic merge from submit-queue (batch tested with PRs 53273, 55058, 55237, 50140). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. add field selector for kubectl get **What this PR does / why we need it**: When working in #50075, I found current kubectl did not support using `field-selector`. **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #14129 **Special notes for your reviewer**: /cc @janetkuo @kargakis /assign @derekwaynecarr @smarterclayton @bgrant0607 **Release note**: ```release-note add field selector for kubectl get ```pull/6/head
commit
45bdf707f0
|
@ -430,6 +430,13 @@ run_pod_tests() {
|
|||
kubectl create -f test/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml "${kube_flags[@]}"
|
||||
# Post-condition: valid-pod POD is created
|
||||
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'valid-pod:'
|
||||
# Command
|
||||
output_message=$(kubectl get pods --field-selector metadata.name=valid-pod "${kube_flags[@]}")
|
||||
kube::test::if_has_string "${output_message}" "valid-pod"
|
||||
# Command
|
||||
phase=$(kubectl get "${kube_flags[@]}" pod valid-pod -o go-template='{{ .status.phase }}')
|
||||
output_message=$(kubectl get pods --field-selector status.phase="${phase}" "${kube_flags[@]}")
|
||||
kube::test::if_has_string "${output_message}" "valid-pod"
|
||||
|
||||
### Delete PODs with no parameter mustn't kill everything
|
||||
# Pre-condition: valid-pod POD exists
|
||||
|
|
|
@ -205,7 +205,7 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
|
|||
return err
|
||||
}
|
||||
|
||||
b = b.SelectorParam(o.selector).
|
||||
b = b.LabelSelectorParam(o.selector).
|
||||
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
|
||||
ResourceTypeOrNameArgs(o.all, o.resources...).
|
||||
Latest()
|
||||
|
|
|
@ -219,7 +219,7 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti
|
|||
ContinueOnError().
|
||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||
FilenameParam(enforceNamespace, &options.FilenameOptions).
|
||||
SelectorParam(options.Selector).
|
||||
LabelSelectorParam(options.Selector).
|
||||
IncludeUninitialized(includeUninitialized).
|
||||
Flatten().
|
||||
Do()
|
||||
|
@ -367,8 +367,8 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti
|
|||
clientFunc: f.UnstructuredClientForMapping,
|
||||
clientsetFunc: f.ClientSet,
|
||||
|
||||
selector: options.Selector,
|
||||
visitedUids: visitedUids,
|
||||
labelSelector: options.Selector,
|
||||
visitedUids: visitedUids,
|
||||
|
||||
cascade: options.Cascade,
|
||||
dryRun: dryRun,
|
||||
|
@ -453,8 +453,9 @@ type pruner struct {
|
|||
clientFunc resource.ClientMapperFunc
|
||||
clientsetFunc func() (internalclientset.Interface, error)
|
||||
|
||||
visitedUids sets.String
|
||||
selector string
|
||||
visitedUids sets.String
|
||||
labelSelector string
|
||||
fieldSelector string
|
||||
|
||||
cascade bool
|
||||
dryRun bool
|
||||
|
@ -474,7 +475,8 @@ func (p *pruner) prune(namespace string, mapping *meta.RESTMapping, shortOutput,
|
|||
mapping.GroupVersionKind.Version,
|
||||
false,
|
||||
&metav1.ListOptions{
|
||||
LabelSelector: p.selector,
|
||||
LabelSelector: p.labelSelector,
|
||||
FieldSelector: p.fieldSelector,
|
||||
IncludeUninitialized: includeUninitialized,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -98,7 +98,7 @@ func (o *ViewLastAppliedOptions) Complete(f cmdutil.Factory, args []string) erro
|
|||
FilenameParam(enforceNamespace, &o.FilenameOptions).
|
||||
ResourceTypeOrNameArgs(enforceNamespace, args...).
|
||||
SelectAllParam(o.All).
|
||||
SelectorParam(o.Selector).
|
||||
LabelSelectorParam(o.Selector).
|
||||
Latest().
|
||||
Flatten().
|
||||
Do()
|
||||
|
|
|
@ -74,7 +74,7 @@ func RunClusterInfo(f cmdutil.Factory, out io.Writer, cmd *cobra.Command) error
|
|||
// TODO use generalized labels once they are implemented (#341)
|
||||
b := f.NewBuilder().
|
||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||
SelectorParam("kubernetes.io/cluster-service=true").
|
||||
LabelSelectorParam("kubernetes.io/cluster-service=true").
|
||||
ResourceTypeOrNameArgs(false, []string{"services"}...).
|
||||
Latest()
|
||||
err = b.Do().Visit(func(r *resource.Info, err error) error {
|
||||
|
|
|
@ -194,7 +194,7 @@ func RunCreate(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opt
|
|||
ContinueOnError().
|
||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||
FilenameParam(enforceNamespace, &options.FilenameOptions).
|
||||
SelectorParam(options.Selector).
|
||||
LabelSelectorParam(options.Selector).
|
||||
Flatten().
|
||||
Do()
|
||||
err = r.Err()
|
||||
|
|
|
@ -182,7 +182,7 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args
|
|||
ContinueOnError().
|
||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||
FilenameParam(enforceNamespace, &o.FilenameOptions).
|
||||
SelectorParam(o.Selector).
|
||||
LabelSelectorParam(o.Selector).
|
||||
IncludeUninitialized(includeUninitialized).
|
||||
SelectAllParam(o.DeleteAll).
|
||||
ResourceTypeOrNameArgs(false, args...).RequireObject(false).
|
||||
|
|
|
@ -129,7 +129,7 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a
|
|||
ContinueOnError().
|
||||
NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces).
|
||||
FilenameParam(enforceNamespace, options).
|
||||
SelectorParam(selector).
|
||||
LabelSelectorParam(selector).
|
||||
IncludeUninitialized(includeUninitialized).
|
||||
ResourceTypeOrNameArgs(true, args...).
|
||||
Flatten().
|
||||
|
|
|
@ -243,7 +243,7 @@ func (o *DrainOptions) SetupDrain(cmd *cobra.Command, args []string) error {
|
|||
Flatten()
|
||||
|
||||
if len(o.Selector) > 0 {
|
||||
builder = builder.SelectorParam(o.Selector).
|
||||
builder = builder.LabelSelectorParam(o.Selector).
|
||||
ResourceTypes("nodes")
|
||||
}
|
||||
|
||||
|
|
|
@ -206,7 +206,7 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
|
|||
return err
|
||||
}
|
||||
|
||||
b = b.SelectorParam(o.selector).
|
||||
b = b.LabelSelectorParam(o.selector).
|
||||
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
|
||||
ResourceTypeOrNameArgs(o.all, o.resources...).
|
||||
Latest()
|
||||
|
|
|
@ -199,7 +199,7 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Comm
|
|||
builder.ResourceNames("pods", o.ResourceArg)
|
||||
}
|
||||
if selector != "" {
|
||||
builder.ResourceTypes("pods").SelectorParam(selector)
|
||||
builder.ResourceTypes("pods").LabelSelectorParam(selector)
|
||||
}
|
||||
infos, err := builder.Do().Infos()
|
||||
if err != nil {
|
||||
|
|
|
@ -56,6 +56,7 @@ type GetOptions struct {
|
|||
ChunkSize int64
|
||||
|
||||
LabelSelector string
|
||||
FieldSelector string
|
||||
AllNamespaces bool
|
||||
Namespace string
|
||||
ExplicitNamespace bool
|
||||
|
@ -158,6 +159,7 @@ func NewCmdGet(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Comman
|
|||
cmd.Flags().Int64Var(&options.ChunkSize, "chunk-size", 500, "Return large lists in chunks rather than all at once. Pass 0 to disable. This flag is beta and may change in the future.")
|
||||
cmd.Flags().BoolVar(&options.IgnoreNotFound, "ignore-not-found", options.IgnoreNotFound, "If the requested object does not exist the command will return exit code 0.")
|
||||
cmd.Flags().StringVarP(&options.LabelSelector, "selector", "l", options.LabelSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
|
||||
cmd.Flags().StringVar(&options.FieldSelector, "field-selector", options.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
|
||||
cmd.Flags().BoolVar(&options.AllNamespaces, "all-namespaces", options.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
|
||||
cmdutil.AddIncludeUninitializedFlag(cmd)
|
||||
cmdutil.AddPrinterFlags(cmd)
|
||||
|
@ -233,7 +235,8 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str
|
|||
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
|
||||
NamespaceParam(options.Namespace).DefaultNamespace().AllNamespaces(options.AllNamespaces).
|
||||
FilenameParam(options.ExplicitNamespace, &options.FilenameOptions).
|
||||
SelectorParam(options.LabelSelector).
|
||||
LabelSelectorParam(options.LabelSelector).
|
||||
FieldSelectorParam(options.FieldSelector).
|
||||
ExportParam(options.Export).
|
||||
RequestChunksOf(options.ChunkSize).
|
||||
IncludeUninitialized(cmdutil.ShouldIncludeUninitialized(cmd, false)). // TODO: this needs to be better factored
|
||||
|
@ -450,7 +453,8 @@ func (options *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []s
|
|||
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
|
||||
NamespaceParam(options.Namespace).DefaultNamespace().AllNamespaces(options.AllNamespaces).
|
||||
FilenameParam(options.ExplicitNamespace, &options.FilenameOptions).
|
||||
SelectorParam(options.LabelSelector).
|
||||
LabelSelectorParam(options.LabelSelector).
|
||||
FieldSelectorParam(options.FieldSelector).
|
||||
ExportParam(options.Export).
|
||||
RequestChunksOf(options.ChunkSize).
|
||||
IncludeUninitialized(includeUninitialized).
|
||||
|
|
|
@ -825,7 +825,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetMultipleTypeObjectsWithSelector(t *testing.T) {
|
||||
func TestGetMultipleTypeObjectsWithLabelSelector(t *testing.T) {
|
||||
pods, svc, _ := testData()
|
||||
|
||||
f, tf, codec, _ := cmdtesting.NewAPIFactory()
|
||||
|
@ -868,6 +868,49 @@ func TestGetMultipleTypeObjectsWithSelector(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetMultipleTypeObjectsWithFieldSelector(t *testing.T) {
|
||||
pods, svc, _ := testData()
|
||||
|
||||
f, tf, codec, _ := cmdtesting.NewAPIFactory()
|
||||
tf.Printer = &testPrinter{}
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Query().Get(metav1.FieldSelectorQueryParam("v1")) != "a=b" {
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
}
|
||||
switch req.URL.Path {
|
||||
case "/namespaces/test/pods":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil
|
||||
case "/namespaces/test/services":
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, svc)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
tf.Namespace = "test"
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
errBuf := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd := NewCmdGet(f, buf, errBuf)
|
||||
cmd.SetOutput(buf)
|
||||
|
||||
cmd.Flags().Set("field-selector", "a=b")
|
||||
cmd.Run(cmd, []string{"pods,services"})
|
||||
|
||||
expected, err := extractResourceList([]runtime.Object{pods, svc})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
|
||||
|
||||
if len(buf.String()) == 0 {
|
||||
t.Errorf("unexpected empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) {
|
||||
_, svc, _ := testData()
|
||||
node := &api.Node{
|
||||
|
@ -1006,7 +1049,7 @@ func watchTestData() ([]api.Pod, []watch.Event) {
|
|||
return pods, events
|
||||
}
|
||||
|
||||
func TestWatchSelector(t *testing.T) {
|
||||
func TestWatchLabelSelector(t *testing.T) {
|
||||
pods, events := watchTestData()
|
||||
|
||||
f, tf, codec, _ := cmdtesting.NewAPIFactory()
|
||||
|
@ -1054,6 +1097,54 @@ func TestWatchSelector(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestWatchFieldSelector(t *testing.T) {
|
||||
pods, events := watchTestData()
|
||||
|
||||
f, tf, codec, _ := cmdtesting.NewAPIFactory()
|
||||
tf.Printer = &testPrinter{}
|
||||
podList := &api.PodList{
|
||||
Items: pods,
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: "10",
|
||||
},
|
||||
}
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: unstructuredSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Query().Get(metav1.FieldSelectorQueryParam("v1")) != "a=b" {
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
}
|
||||
switch req.URL.Path {
|
||||
case "/namespaces/test/pods":
|
||||
if req.URL.Query().Get("watch") == "true" {
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[2:])}, nil
|
||||
}
|
||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, podList)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
tf.Namespace = "test"
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
errBuf := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd := NewCmdGet(f, buf, errBuf)
|
||||
cmd.SetOutput(buf)
|
||||
|
||||
cmd.Flags().Set("watch", "true")
|
||||
cmd.Flags().Set("field-selector", "a=b")
|
||||
cmd.Run(cmd, []string{"pods"})
|
||||
|
||||
expected := []runtime.Object{&pods[0], &pods[1], events[2].Object, events[3].Object}
|
||||
verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects)
|
||||
|
||||
if len(buf.String()) == 0 {
|
||||
t.Errorf("unexpected empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchResource(t *testing.T) {
|
||||
pods, events := watchTestData()
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ func RunScale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
|
|||
FilenameParam(enforceNamespace, options).
|
||||
ResourceTypeOrNameArgs(all, args...).
|
||||
Flatten().
|
||||
SelectorParam(selector).
|
||||
LabelSelectorParam(selector).
|
||||
Do()
|
||||
err = r.Err()
|
||||
if resource.IsUsageError(err) {
|
||||
|
|
|
@ -240,7 +240,7 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
|
|||
|
||||
if !o.Local {
|
||||
b = b.
|
||||
SelectorParam(o.Selector).
|
||||
LabelSelectorParam(o.Selector).
|
||||
ResourceTypeOrNameArgs(o.All, o.From).
|
||||
Latest()
|
||||
} else {
|
||||
|
@ -304,7 +304,7 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
|
|||
|
||||
if !o.Local {
|
||||
b = b.
|
||||
SelectorParam(o.Selector).
|
||||
LabelSelectorParam(o.Selector).
|
||||
ResourceTypeOrNameArgs(o.All, o.Resources...).
|
||||
Latest()
|
||||
} else {
|
||||
|
|
|
@ -149,7 +149,7 @@ func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
|
|||
|
||||
if !o.Local {
|
||||
builder = builder.
|
||||
SelectorParam(o.Selector).
|
||||
LabelSelectorParam(o.Selector).
|
||||
ResourceTypeOrNameArgs(o.All, o.Resources...).
|
||||
Latest()
|
||||
} else {
|
||||
|
|
|
@ -153,7 +153,7 @@ func (o *ResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
|
|||
|
||||
if !o.Local {
|
||||
builder = builder.
|
||||
SelectorParam(o.Selector).
|
||||
LabelSelectorParam(o.Selector).
|
||||
ResourceTypeOrNameArgs(o.All, args...).
|
||||
Latest()
|
||||
} else {
|
||||
|
|
|
@ -135,7 +135,7 @@ func (o *SubjectOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
|
|||
|
||||
if !o.Local {
|
||||
builder = builder.
|
||||
SelectorParam(o.Selector).
|
||||
LabelSelectorParam(o.Selector).
|
||||
ResourceTypeOrNameArgs(o.All, args...).
|
||||
Latest()
|
||||
} else {
|
||||
|
|
|
@ -152,7 +152,7 @@ func (o *TaintOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Com
|
|||
ContinueOnError().
|
||||
NamespaceParam(namespace).DefaultNamespace()
|
||||
if o.selector != "" {
|
||||
o.builder = o.builder.SelectorParam(o.selector).ResourceTypes("node")
|
||||
o.builder = o.builder.LabelSelectorParam(o.selector).ResourceTypes("node")
|
||||
}
|
||||
if o.all {
|
||||
o.builder = o.builder.SelectAllParam(o.all).ResourceTypes("node").Flatten().Latest()
|
||||
|
@ -160,7 +160,7 @@ func (o *TaintOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Com
|
|||
if !o.all && o.selector == "" && len(o.resources) >= 2 {
|
||||
o.builder = o.builder.ResourceNames("node", o.resources[1:]...)
|
||||
}
|
||||
o.builder = o.builder.SelectorParam(o.selector).
|
||||
o.builder = o.builder.LabelSelectorParam(o.selector).
|
||||
Flatten().
|
||||
Latest()
|
||||
o.f = f
|
||||
|
|
|
@ -53,7 +53,8 @@ type Builder struct {
|
|||
stream bool
|
||||
dir bool
|
||||
|
||||
selector *string
|
||||
labelSelector *string
|
||||
fieldSelector *string
|
||||
selectAll bool
|
||||
includeUninitialized bool
|
||||
limitChunks int64
|
||||
|
@ -274,24 +275,41 @@ func (b *Builder) ResourceNames(resource string, names ...string) *Builder {
|
|||
return b
|
||||
}
|
||||
|
||||
// SelectorParam defines a selector that should be applied to the object types to load.
|
||||
// LabelSelectorParam defines a selector that should be applied to the object types to load.
|
||||
// This will not affect files loaded from disk or URL. If the parameter is empty it is
|
||||
// a no-op - to select all resources invoke `b.Selector(labels.Everything.String)`.
|
||||
func (b *Builder) SelectorParam(s string) *Builder {
|
||||
// a no-op - to select all resources invoke `b.LabelSelector(labels.Everything.String)`.
|
||||
func (b *Builder) LabelSelectorParam(s string) *Builder {
|
||||
selector := strings.TrimSpace(s)
|
||||
if len(selector) == 0 {
|
||||
return b
|
||||
}
|
||||
if b.selectAll {
|
||||
b.errs = append(b.errs, fmt.Errorf("found non empty selector %q with previously set 'all' parameter. ", s))
|
||||
b.errs = append(b.errs, fmt.Errorf("found non-empty label selector %q with previously set 'all' parameter. ", s))
|
||||
return b
|
||||
}
|
||||
return b.Selector(selector)
|
||||
return b.LabelSelector(selector)
|
||||
}
|
||||
|
||||
// Selector accepts a selector directly, and if non nil will trigger a list action.
|
||||
func (b *Builder) Selector(selector string) *Builder {
|
||||
b.selector = &selector
|
||||
// LabelSelector accepts a selector directly and will filter the resulting list by that object.
|
||||
// Use LabelSelectorParam instead for user input.
|
||||
func (b *Builder) LabelSelector(selector string) *Builder {
|
||||
b.labelSelector = &selector
|
||||
return b
|
||||
}
|
||||
|
||||
// FieldSelectorParam defines a selector that should be applied to the object types to load.
|
||||
// This will not affect files loaded from disk or URL. If the parameter is empty it is
|
||||
// a no-op - to select all resources.
|
||||
func (b *Builder) FieldSelectorParam(s string) *Builder {
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s) == 0 {
|
||||
return b
|
||||
}
|
||||
if b.selectAll {
|
||||
b.errs = append(b.errs, fmt.Errorf("found non-empty field selector %q with previously set 'all' parameter. ", s))
|
||||
return b
|
||||
}
|
||||
b.fieldSelector = &s
|
||||
return b
|
||||
}
|
||||
|
||||
|
@ -349,7 +367,7 @@ func (b *Builder) RequestChunksOf(chunkSize int64) *Builder {
|
|||
|
||||
// SelectEverythingParam
|
||||
func (b *Builder) SelectAllParam(selectAll bool) *Builder {
|
||||
if selectAll && b.selector != nil {
|
||||
if selectAll && (b.labelSelector != nil || b.fieldSelector != nil) {
|
||||
b.errs = append(b.errs, fmt.Errorf("setting 'all' parameter but found a non empty selector. "))
|
||||
return b
|
||||
}
|
||||
|
@ -394,9 +412,9 @@ func (b *Builder) ResourceTypeOrNameArgs(allowEmptySelector bool, args ...string
|
|||
b.ResourceTypes(SplitResourceArgument(args[0])...)
|
||||
case len(args) == 1:
|
||||
b.ResourceTypes(SplitResourceArgument(args[0])...)
|
||||
if b.selector == nil && allowEmptySelector {
|
||||
if b.labelSelector == nil && allowEmptySelector {
|
||||
selector := labels.Everything().String()
|
||||
b.selector = &selector
|
||||
b.labelSelector = &selector
|
||||
}
|
||||
case len(args) == 0:
|
||||
default:
|
||||
|
@ -585,7 +603,7 @@ func (b *Builder) visitorResult() *Result {
|
|||
|
||||
if b.selectAll {
|
||||
selector := labels.Everything().String()
|
||||
b.selector = &selector
|
||||
b.labelSelector = &selector
|
||||
}
|
||||
|
||||
// visit items specified by paths
|
||||
|
@ -594,7 +612,7 @@ func (b *Builder) visitorResult() *Result {
|
|||
}
|
||||
|
||||
// visit selectors
|
||||
if b.selector != nil {
|
||||
if b.labelSelector != nil || b.fieldSelector != nil {
|
||||
return b.visitBySelector()
|
||||
}
|
||||
|
||||
|
@ -634,6 +652,14 @@ func (b *Builder) visitBySelector() *Result {
|
|||
return result
|
||||
}
|
||||
|
||||
var labelSelector, fieldSelector string
|
||||
if b.labelSelector != nil {
|
||||
labelSelector = *b.labelSelector
|
||||
}
|
||||
if b.fieldSelector != nil {
|
||||
fieldSelector = *b.fieldSelector
|
||||
}
|
||||
|
||||
visitors := []Visitor{}
|
||||
for _, mapping := range mappings {
|
||||
client, err := b.mapper.ClientForMapping(mapping)
|
||||
|
@ -645,7 +671,7 @@ func (b *Builder) visitBySelector() *Result {
|
|||
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
|
||||
selectorNamespace = ""
|
||||
}
|
||||
visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, *b.selector, b.export, b.includeUninitialized, b.limitChunks))
|
||||
visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, labelSelector, fieldSelector, b.export, b.includeUninitialized, b.limitChunks))
|
||||
}
|
||||
if b.continueOnError {
|
||||
result.visitor = EagerVisitorList(visitors)
|
||||
|
@ -820,12 +846,12 @@ func (b *Builder) visitByPaths() *Result {
|
|||
}
|
||||
visitors = NewDecoratedVisitor(visitors, RetrieveLatest)
|
||||
}
|
||||
if b.selector != nil {
|
||||
selector, err := labels.Parse(*b.selector)
|
||||
if b.labelSelector != nil {
|
||||
selector, err := labels.Parse(*b.labelSelector)
|
||||
if err != nil {
|
||||
return result.withError(fmt.Errorf("the provided selector %q is not valid: %v", b.selector, err))
|
||||
return result.withError(fmt.Errorf("the provided selector %q is not valid: %v", b.labelSelector, err))
|
||||
}
|
||||
visitors = NewFilteredVisitor(visitors, FilterBySelector(selector))
|
||||
visitors = NewFilteredVisitor(visitors, FilterByLabelSelector(selector))
|
||||
}
|
||||
result.visitor = visitors
|
||||
result.sources = b.paths
|
||||
|
|
|
@ -697,7 +697,7 @@ func TestResourceByNameAndEmptySelector(t *testing.T) {
|
|||
"/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
|
||||
}), corev1Codec).
|
||||
NamespaceParam("test").
|
||||
SelectorParam("").
|
||||
LabelSelectorParam("").
|
||||
ResourceTypeOrNameArgs(true, "pods", "foo")
|
||||
|
||||
singleItemImplied := false
|
||||
|
@ -718,14 +718,14 @@ func TestResourceByNameAndEmptySelector(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSelector(t *testing.T) {
|
||||
func TestLabelSelector(t *testing.T) {
|
||||
pods, svc := testData()
|
||||
labelKey := metav1.LabelSelectorQueryParam(corev1GV.String())
|
||||
b := NewBuilder(restmapper, LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{
|
||||
"/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods),
|
||||
"/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc),
|
||||
}), corev1Codec).
|
||||
SelectorParam("a=b").
|
||||
LabelSelectorParam("a=b").
|
||||
NamespaceParam("test").
|
||||
Flatten()
|
||||
|
||||
|
@ -751,9 +751,53 @@ func TestSelector(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSelectorRequiresKnownTypes(t *testing.T) {
|
||||
func TestLabelSelectorRequiresKnownTypes(t *testing.T) {
|
||||
b := NewBuilder(restmapper, LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
|
||||
SelectorParam("a=b").
|
||||
LabelSelectorParam("a=b").
|
||||
NamespaceParam("test").
|
||||
ResourceTypes("unknown")
|
||||
|
||||
if b.Do().Err() == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldSelector(t *testing.T) {
|
||||
pods, svc := testData()
|
||||
fieldKey := metav1.FieldSelectorQueryParam(corev1GV.String())
|
||||
b := NewBuilder(restmapper, LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{
|
||||
"/namespaces/test/pods?" + fieldKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods),
|
||||
"/namespaces/test/services?" + fieldKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc),
|
||||
}), corev1Codec).
|
||||
FieldSelectorParam("a=b").
|
||||
NamespaceParam("test").
|
||||
Flatten()
|
||||
|
||||
test := &testVisitor{}
|
||||
singleItemImplied := false
|
||||
|
||||
if b.Do().Err() == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
|
||||
b.ResourceTypeOrNameArgs(true, "pods,service")
|
||||
|
||||
err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
|
||||
if err != nil || singleItemImplied || len(test.Infos) != 3 {
|
||||
t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
|
||||
}
|
||||
if !apiequality.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &pods.Items[1], &svc.Items[0]}, test.Objects()) {
|
||||
t.Errorf("unexpected visited objects: %#v", test.Objects())
|
||||
}
|
||||
|
||||
if _, err := b.Do().ResourceMapping(); err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldSelectorRequiresKnownTypes(t *testing.T) {
|
||||
b := NewBuilder(restmapper, LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
|
||||
FieldSelectorParam("a=b").
|
||||
NamespaceParam("test").
|
||||
ResourceTypes("unknown")
|
||||
|
||||
|
@ -764,7 +808,7 @@ func TestSelectorRequiresKnownTypes(t *testing.T) {
|
|||
|
||||
func TestSingleResourceType(t *testing.T) {
|
||||
b := NewBuilder(restmapper, LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
|
||||
SelectorParam("a=b").
|
||||
LabelSelectorParam("a=b").
|
||||
SingleResourceType().
|
||||
ResourceTypeOrNameArgs(true, "pods,services")
|
||||
|
||||
|
@ -1019,7 +1063,7 @@ func TestListObject(t *testing.T) {
|
|||
b := NewBuilder(restmapper, LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{
|
||||
"/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods),
|
||||
}), corev1Codec).
|
||||
SelectorParam("a=b").
|
||||
LabelSelectorParam("a=b").
|
||||
NamespaceParam("test").
|
||||
ResourceTypeOrNameArgs(true, "pods").
|
||||
Flatten()
|
||||
|
@ -1053,7 +1097,7 @@ func TestListObjectWithDifferentVersions(t *testing.T) {
|
|||
"/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods),
|
||||
"/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc),
|
||||
}), corev1Codec).
|
||||
SelectorParam("a=b").
|
||||
LabelSelectorParam("a=b").
|
||||
NamespaceParam("test").
|
||||
ResourceTypeOrNameArgs(true, "pods,services").
|
||||
Flatten().
|
||||
|
|
|
@ -75,15 +75,12 @@ func (m *Helper) List(namespace, apiVersion string, export bool, options *metav1
|
|||
return req.Do().Get()
|
||||
}
|
||||
|
||||
func (m *Helper) Watch(namespace, resourceVersion, apiVersion string, labelSelector string) (watch.Interface, error) {
|
||||
func (m *Helper) Watch(namespace, apiVersion string, options *metav1.ListOptions) (watch.Interface, error) {
|
||||
options.Watch = true
|
||||
return m.RESTClient.Get().
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
VersionedParams(&metav1.ListOptions{
|
||||
ResourceVersion: resourceVersion,
|
||||
Watch: true,
|
||||
LabelSelector: labelSelector,
|
||||
}, metav1.ParameterCodec).
|
||||
VersionedParams(options, metav1.ParameterCodec).
|
||||
Watch()
|
||||
}
|
||||
|
||||
|
|
|
@ -385,6 +385,72 @@ func TestHelperList(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHelperListSelectorCombination(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
Err bool
|
||||
ErrMsg string
|
||||
FieldSelector string
|
||||
LabelSelector string
|
||||
}{
|
||||
{
|
||||
Name: "No selector",
|
||||
Err: false,
|
||||
},
|
||||
{
|
||||
Name: "Only Label Selector",
|
||||
Err: false,
|
||||
LabelSelector: "foo=baz",
|
||||
},
|
||||
{
|
||||
Name: "Only Field Selector",
|
||||
Err: false,
|
||||
FieldSelector: "xyz=zyx",
|
||||
},
|
||||
{
|
||||
Name: "Both Label and Field Selector",
|
||||
Err: false,
|
||||
LabelSelector: "foo=baz",
|
||||
FieldSelector: "xyz=zyx",
|
||||
},
|
||||
}
|
||||
|
||||
resp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&corev1.PodList{
|
||||
Items: []corev1.Pod{{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
client := &fake.RESTClient{
|
||||
NegotiatedSerializer: scheme.Codecs,
|
||||
Resp: resp,
|
||||
Err: nil,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
_, err := modifier.List("bar",
|
||||
corev1GV.String(),
|
||||
false,
|
||||
&metav1.ListOptions{LabelSelector: test.LabelSelector, FieldSelector: test.FieldSelector})
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Errorf("%q expected error: %q", test.Name, test.ErrMsg)
|
||||
}
|
||||
if err != nil && err.Error() != test.ErrMsg {
|
||||
t.Errorf("%q expected error: %q", test.Name, test.ErrMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperReplace(t *testing.T) {
|
||||
expectPut := func(path string, req *http.Request) bool {
|
||||
if req.Method != "PUT" {
|
||||
|
|
|
@ -30,19 +30,21 @@ type Selector struct {
|
|||
Client RESTClient
|
||||
Mapping *meta.RESTMapping
|
||||
Namespace string
|
||||
Selector string
|
||||
LabelSelector string
|
||||
FieldSelector string
|
||||
Export bool
|
||||
IncludeUninitialized bool
|
||||
LimitChunks int64
|
||||
}
|
||||
|
||||
// NewSelector creates a resource selector which hides details of getting items by their label selector.
|
||||
func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace string, selector string, export, includeUninitialized bool, limitChunks int64) *Selector {
|
||||
func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace, labelSelector, fieldSelector string, export, includeUninitialized bool, limitChunks int64) *Selector {
|
||||
return &Selector{
|
||||
Client: client,
|
||||
Mapping: mapping,
|
||||
Namespace: namespace,
|
||||
Selector: selector,
|
||||
LabelSelector: labelSelector,
|
||||
FieldSelector: fieldSelector,
|
||||
Export: export,
|
||||
IncludeUninitialized: includeUninitialized,
|
||||
LimitChunks: limitChunks,
|
||||
|
@ -58,7 +60,8 @@ func (r *Selector) Visit(fn VisitorFunc) error {
|
|||
r.ResourceMapping().GroupVersionKind.GroupVersion().String(),
|
||||
r.Export,
|
||||
&metav1.ListOptions{
|
||||
LabelSelector: r.Selector,
|
||||
LabelSelector: r.LabelSelector,
|
||||
FieldSelector: r.FieldSelector,
|
||||
IncludeUninitialized: r.IncludeUninitialized,
|
||||
Limit: r.LimitChunks,
|
||||
Continue: continueToken,
|
||||
|
@ -71,17 +74,17 @@ func (r *Selector) Visit(fn VisitorFunc) error {
|
|||
if errors.IsBadRequest(err) || errors.IsNotFound(err) {
|
||||
if se, ok := err.(*errors.StatusError); ok {
|
||||
// modify the message without hiding this is an API error
|
||||
if len(r.Selector) == 0 {
|
||||
if len(r.LabelSelector) == 0 && len(r.FieldSelector) == 0 {
|
||||
se.ErrStatus.Message = fmt.Sprintf("Unable to list %q: %v", r.Mapping.Resource, se.ErrStatus.Message)
|
||||
} else {
|
||||
se.ErrStatus.Message = fmt.Sprintf("Unable to find %q that match the selector %q: %v", r.Mapping.Resource, r.Selector, se.ErrStatus.Message)
|
||||
se.ErrStatus.Message = fmt.Sprintf("Unable to find %q that match label selector %q, field selector %q: %v", r.Mapping.Resource, r.LabelSelector, r.FieldSelector, se.ErrStatus.Message)
|
||||
}
|
||||
return se
|
||||
}
|
||||
if len(r.Selector) == 0 {
|
||||
if len(r.LabelSelector) == 0 && len(r.FieldSelector) == 0 {
|
||||
return fmt.Errorf("Unable to list %q: %v", r.Mapping.Resource, err)
|
||||
}
|
||||
return fmt.Errorf("Unable to find %q that match the selector %q: %v", r.Mapping.Resource, r.Selector, err)
|
||||
return fmt.Errorf("Unable to find %q that match label selector %q, field selector %q: %v", r.Mapping.Resource, r.LabelSelector, r.FieldSelector, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -107,7 +110,8 @@ func (r *Selector) Visit(fn VisitorFunc) error {
|
|||
}
|
||||
|
||||
func (r *Selector) Watch(resourceVersion string) (watch.Interface, error) {
|
||||
return NewHelper(r.Client, r.Mapping).Watch(r.Namespace, resourceVersion, r.ResourceMapping().GroupVersionKind.GroupVersion().String(), r.Selector)
|
||||
return NewHelper(r.Client, r.Mapping).Watch(r.Namespace, r.ResourceMapping().GroupVersionKind.GroupVersion().String(),
|
||||
&metav1.ListOptions{ResourceVersion: resourceVersion, LabelSelector: r.LabelSelector, FieldSelector: r.FieldSelector})
|
||||
}
|
||||
|
||||
// ResourceMapping returns the mapping for this resource and implements ResourceMapping
|
||||
|
|
|
@ -691,7 +691,7 @@ func (v FilteredVisitor) Visit(fn VisitorFunc) error {
|
|||
})
|
||||
}
|
||||
|
||||
func FilterBySelector(s labels.Selector) FilterFunc {
|
||||
func FilterByLabelSelector(s labels.Selector) FilterFunc {
|
||||
return func(info *Info, err error) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
|
Loading…
Reference in New Issue