1
0
mirror of synced 2026-06-26 21:50:48 +00:00

Merge pull request #5952 from 3uzbcqje/status-selector-negation

cmd: support `type!=status` in get --status-selector
This commit is contained in:
Matheus Pimenta
2026-06-25 08:41:21 +01:00
committed by GitHub
6 changed files with 140 additions and 6 deletions
+17 -6
View File
@@ -78,7 +78,7 @@ func init() {
getCmd.PersistentFlags().BoolVarP(&getArgs.noHeader, "no-header", "", false, "skip the header when printing the results")
getCmd.PersistentFlags().BoolVarP(&getArgs.watch, "watch", "w", false, "After listing/getting the requested object, watch for changes.")
getCmd.PersistentFlags().StringVar(&getArgs.statusSelector, "status-selector", "",
"specify the status condition name and the desired state to filter the get result, e.g. ready=false")
"specify the status condition name and the desired state to filter the get result, e.g. ready=false or ready!=true")
getCmd.PersistentFlags().StringVarP(&getArgs.labelSelector, "label-selector", "l", "",
"filter objects by label selector")
rootCmd.AddCommand(getCmd)
@@ -228,20 +228,31 @@ func namespaceNameOrAny(allNamespaces bool, namespaceName string) string {
}
func getRowsToPrint(getAll bool, list summarisable) ([][]string, error) {
noFilter := true
filter := func(i int) bool { return true }
var conditionType, conditionStatus string
if getArgs.statusSelector != "" {
parts := strings.SplitN(getArgs.statusSelector, "=", 2)
// Support both type=status (match) and type!=status (negated match).
// "!=" must be checked first since it also contains "=".
separator := "="
filter = func(i int) bool {
return list.statusSelectorMatches(i, conditionType, conditionStatus)
}
if strings.Contains(getArgs.statusSelector, "!=") {
separator = "!="
filter = func(i int) bool {
return !list.statusSelectorMatches(i, conditionType, conditionStatus)
}
}
parts := strings.SplitN(getArgs.statusSelector, separator, 2)
if len(parts) != 2 {
return nil, fmt.Errorf("expected status selector in type=status format, but found: %s", getArgs.statusSelector)
return nil, fmt.Errorf("expected status selector in type=status or type!=status format, but found: %s", getArgs.statusSelector)
}
conditionType = parts[0]
conditionStatus = parts[1]
noFilter = false
}
var rows [][]string
for i := 0; i < list.len(); i++ {
if noFilter || list.statusSelectorMatches(i, conditionType, conditionStatus) {
if filter(i) {
row := list.summariseItem(i, getArgs.allNamespaces, getAll)
rows = append(rows, row)
}
+45
View File
@@ -63,6 +63,46 @@ func Test_GetCmd(t *testing.T) {
}
}
func Test_GetCmdStatusSelector(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
testEnv.CreateObjectFile("./testdata/get/status_objects.yaml", tmpl, t)
tests := []struct {
name string
args string
expected string
}{
{
name: "equal status selector matches one",
args: "--status-selector Ready=True",
expected: "testdata/get/get_status_ready_true.golden",
},
{
name: "equal status selector matches false",
args: "--status-selector Ready=False",
expected: "testdata/get/get_status_ready_false.golden",
},
{
name: "not-equal status selector matches all not-true",
args: "--status-selector Ready!=True",
expected: "testdata/get/get_status_ready_not_true.golden",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: "get sources git " + tt.args + " -n " + tmpl["fluxns"],
assert: assertGoldenTemplateFile(tt.expected, nil),
}
cmd.runTestCmd(t)
})
}
}
func Test_GetCmdErrors(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
@@ -84,6 +124,11 @@ func Test_GetCmdErrors(t *testing.T) {
args: "get helmrelease -n " + tmpl["fluxns"],
assert: assertError(fmt.Sprintf("no HelmRelease objects found in \"%s\" namespace", tmpl["fluxns"])),
},
{
name: "malformed status selector",
args: "get sources git --status-selector Ready -n " + tmpl["fluxns"],
assert: assertError("expected status selector in type=status or type!=status format, but found: Ready"),
},
}
for _, tt := range tests {
+2
View File
@@ -0,0 +1,2 @@
NAME REVISION SUSPENDED READY MESSAGE
gr-failed False False failed to checkout and determine revision
@@ -0,0 +1,3 @@
NAME REVISION SUSPENDED READY MESSAGE
gr-failed False False failed to checkout and determine revision
gr-unknown False Unknown reconciliation in progress
+2
View File
@@ -0,0 +1,2 @@
NAME REVISION SUSPENDED READY MESSAGE
gr-ready main@sha1:696f056d False True Fetched revision: main@sha1:696f056d
+71
View File
@@ -0,0 +1,71 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: {{ .fluxns }}
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: gr-failed
namespace: {{ .fluxns }}
spec:
ref:
branch: main
secretRef:
name: flux-system
url: ssh://git@github.com/example/repo
interval: 5m
status:
conditions:
- lastTransitionTime: "2021-07-20T00:48:16Z"
message: 'failed to checkout and determine revision'
reason: GitOperationFailed
status: "False"
type: Ready
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: gr-ready
namespace: {{ .fluxns }}
spec:
ref:
branch: main
secretRef:
name: flux-system
url: ssh://git@github.com/example/repo
interval: 5m
status:
artifact:
lastUpdateTime: "2021-08-01T04:28:42Z"
revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f
path: "example"
url: "example"
digest: sha1:696f056df216eea4f9401adbee0ff744d4df390f
conditions:
- lastTransitionTime: "2021-07-20T00:48:16Z"
message: 'Fetched revision: main@sha1:696f056df216eea4f9401adbee0ff744d4df390f'
reason: GitOperationSucceed
status: "True"
type: Ready
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: gr-unknown
namespace: {{ .fluxns }}
spec:
ref:
branch: main
secretRef:
name: flux-system
url: ssh://git@github.com/example/repo
interval: 5m
status:
conditions:
- lastTransitionTime: "2021-07-20T00:48:16Z"
message: 'reconciliation in progress'
reason: Progressing
status: "Unknown"
type: Ready