Merge pull request #118748 from andreaskaris/kubectl-wait-for
Kubectl: Improve conditionFuncFor expression parsing for wait --for jsonpath Kubernetes-commit: d486180eb050c756d9add30377980eced146ffa1
This commit is contained in:
commit
0266cec8bc
14
go.mod
14
go.mod
|
@ -30,10 +30,10 @@ require (
|
|||
github.com/stretchr/testify v1.8.2
|
||||
golang.org/x/sys v0.10.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/api v0.0.0-20230807202504-d11dea4516ea
|
||||
k8s.io/apimachinery v0.0.0-20230807201405-8071e5f05ff1
|
||||
k8s.io/api v0.0.0-20230810042731-2f6eec10c476
|
||||
k8s.io/apimachinery v0.0.0-20230815235016-14436eb53afd
|
||||
k8s.io/cli-runtime v0.0.0-20230807221238-0daafa128c61
|
||||
k8s.io/client-go v0.0.0-20230807204204-49410bfbbcf9
|
||||
k8s.io/client-go v0.0.0-20230816000758-856e847bb7cb
|
||||
k8s.io/component-base v0.0.0-20230807211050-31137ad9f7f2
|
||||
k8s.io/component-helpers v0.0.0-20230807211335-91a729046f19
|
||||
k8s.io/klog/v2 v2.100.1
|
||||
|
@ -95,11 +95,11 @@ require (
|
|||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.0.0-20230807202504-d11dea4516ea
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20230807201405-8071e5f05ff1
|
||||
k8s.io/api => k8s.io/api v0.0.0-20230810042731-2f6eec10c476
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20230815235016-14436eb53afd
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20230807221238-0daafa128c61
|
||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20230807204204-49410bfbbcf9
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20230807201020-acb52e329a7f
|
||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20230816000758-856e847bb7cb
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20230815234323-164b07cd93ea
|
||||
k8s.io/component-base => k8s.io/component-base v0.0.0-20230807211050-31137ad9f7f2
|
||||
k8s.io/component-helpers => k8s.io/component-helpers v0.0.0-20230807211335-91a729046f19
|
||||
k8s.io/metrics => k8s.io/metrics v0.0.0-20230807220806-4c5f05109520
|
||||
|
|
12
go.sum
12
go.sum
|
@ -273,14 +273,14 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.0.0-20230807202504-d11dea4516ea h1:1IjoJXclbOUWuIkoxE1+p/7hQvNqXiTnAikqG+h+B54=
|
||||
k8s.io/api v0.0.0-20230807202504-d11dea4516ea/go.mod h1:RFi7MZgMNqcWc0azfutkpPR/OdHWZjnTAwdFHTKjAUQ=
|
||||
k8s.io/apimachinery v0.0.0-20230807201405-8071e5f05ff1 h1:NElIwKgvUTGJ2/PZEctXR9JmNzUbb8q38ojfC5guWeU=
|
||||
k8s.io/apimachinery v0.0.0-20230807201405-8071e5f05ff1/go.mod h1:X0xh/chESs2hP9koe+SdIAcXWcQ+RM5hy0ZynB+yEvw=
|
||||
k8s.io/api v0.0.0-20230810042731-2f6eec10c476 h1:1LpPoqkYurARQ/TQ0U3DRAclyCkM7hMCMCUVymCR3jM=
|
||||
k8s.io/api v0.0.0-20230810042731-2f6eec10c476/go.mod h1:RFi7MZgMNqcWc0azfutkpPR/OdHWZjnTAwdFHTKjAUQ=
|
||||
k8s.io/apimachinery v0.0.0-20230815235016-14436eb53afd h1:x//MctFnLnU7WIEUEorfJtI5RkFptZADuNKPFxZBdbg=
|
||||
k8s.io/apimachinery v0.0.0-20230815235016-14436eb53afd/go.mod h1:X0xh/chESs2hP9koe+SdIAcXWcQ+RM5hy0ZynB+yEvw=
|
||||
k8s.io/cli-runtime v0.0.0-20230807221238-0daafa128c61 h1:rucHwIDQTB+CBn5R+vYaAaLWUK+KKXPPPexdFng0PHY=
|
||||
k8s.io/cli-runtime v0.0.0-20230807221238-0daafa128c61/go.mod h1:2TfvpatNccVWjkFyiHv65ekELznAVhjJ34PaFcSb/Vc=
|
||||
k8s.io/client-go v0.0.0-20230807204204-49410bfbbcf9 h1:/wXosf3IlbuvLQQBDkBY+lSOmJGaTB5931MVjn3Zq5A=
|
||||
k8s.io/client-go v0.0.0-20230807204204-49410bfbbcf9/go.mod h1:NbnWu9sTxFULr211okLOk4jiLYAyWn+kFzXmCLu2pK4=
|
||||
k8s.io/client-go v0.0.0-20230816000758-856e847bb7cb h1:QLY5cHaZwawHP6394w6miAQAaJ2fwlA/TfRHXmG4U3M=
|
||||
k8s.io/client-go v0.0.0-20230816000758-856e847bb7cb/go.mod h1:xt/XQN6z9voSDnQ/uCIJ3a5n5sk2lhhnwzWGeuDhsvE=
|
||||
k8s.io/component-base v0.0.0-20230807211050-31137ad9f7f2 h1:bgkLpsQhIRm8Rd6h9V/n50sN63k6sEzX+Q8nCpZCCX4=
|
||||
k8s.io/component-base v0.0.0-20230807211050-31137ad9f7f2/go.mod h1:wjy+fowSTnR9NfN23CZuwDq+yF+viZTN5nbGbXcOYBM=
|
||||
k8s.io/component-helpers v0.0.0-20230807211335-91a729046f19 h1:WTPiAU5fRLmytj73dy6Ew2voeWwN/O9yfGHzdmSds1s=
|
||||
|
|
|
@ -74,6 +74,9 @@ var (
|
|||
# Wait for the pod "busybox1" to contain the status phase to be "Running"
|
||||
kubectl wait --for=jsonpath='{.status.phase}'=Running pod/busybox1
|
||||
|
||||
# Wait for pod "busybox1" to be Ready
|
||||
kubectl wait --for='jsonpath={.status.conditions[?(@.type=="Ready")].status}=True' pod/busybox1
|
||||
|
||||
# Wait for the service "loadbalancer" to have ingress.
|
||||
kubectl wait --for=jsonpath='{.status.loadBalancer.ingress}' service/loadbalancer
|
||||
|
||||
|
@ -209,8 +212,8 @@ func conditionFuncFor(condition string, errOut io.Writer) (ConditionFunc, error)
|
|||
}.IsConditionMet, nil
|
||||
}
|
||||
if strings.HasPrefix(condition, "jsonpath=") {
|
||||
splitStr := strings.Split(condition, "=")
|
||||
jsonPathExp, jsonPathValue, err := processJSONPathInput(splitStr[1:])
|
||||
jsonPathInput := strings.TrimPrefix(condition, "jsonpath=")
|
||||
jsonPathExp, jsonPathValue, err := processJSONPathInput(jsonPathInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -241,8 +244,10 @@ func newJSONPathParser(jsonPathExpression string) (*jsonpath.JSONPath, error) {
|
|||
return j, nil
|
||||
}
|
||||
|
||||
// processJSONPathInput will parses the user's JSONPath input containing JSON expression and, optionally, JSON value for matching condition and process it
|
||||
func processJSONPathInput(jsonPathInput []string) (string, string, error) {
|
||||
// processJSONPathInput will parse and process the provided JSONPath input containing a JSON expression and optionally
|
||||
// a value for the matching condition.
|
||||
func processJSONPathInput(input string) (string, string, error) {
|
||||
jsonPathInput := splitJSONPathInput(input)
|
||||
if numOfArgs := len(jsonPathInput); numOfArgs < 1 || numOfArgs > 2 {
|
||||
return "", "", fmt.Errorf("jsonpath wait format must be --for=jsonpath='{.status.readyReplicas}'=3 or --for=jsonpath='{.status.readyReplicas}'")
|
||||
}
|
||||
|
@ -260,6 +265,27 @@ func processJSONPathInput(jsonPathInput []string) (string, string, error) {
|
|||
return relaxedJSONPathExp, jsonPathValue, nil
|
||||
}
|
||||
|
||||
// splitJSONPathInput splits the provided input string on single '='. Double '==' will not cause the string to be
|
||||
// split. E.g., "a.b.c====d.e.f===g.h.i===" will split to ["a.b.c====d.e.f==","g.h.i==",""].
|
||||
func splitJSONPathInput(input string) []string {
|
||||
var output []string
|
||||
var element strings.Builder
|
||||
for i := 0; i < len(input); i++ {
|
||||
if input[i] == '=' {
|
||||
if i < len(input)-1 && input[i+1] == '=' {
|
||||
element.WriteString("==")
|
||||
i++
|
||||
continue
|
||||
}
|
||||
output = append(output, element.String())
|
||||
element.Reset()
|
||||
continue
|
||||
}
|
||||
element.WriteByte(input[i])
|
||||
}
|
||||
return append(output, element.String())
|
||||
}
|
||||
|
||||
// ResourceLocation holds the location of a resource
|
||||
type ResourceLocation struct {
|
||||
GroupResource schema.GroupResource
|
||||
|
|
|
@ -1534,39 +1534,112 @@ func TestWaitForJSONPathCondition(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestWaitForJSONPathBadConditionParsing will test errors in parsing JSONPath bad condition expressions
|
||||
// except for parsing JSONPath expression itself (i.e. call to cmdget.RelaxedJSONPathExpression())
|
||||
func TestWaitForJSONPathBadConditionParsing(t *testing.T) {
|
||||
// TestConditionFuncFor tests that the condition string can be properly parsed into a ConditionFunc.
|
||||
func TestConditionFuncFor(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
condition string
|
||||
expectedResult JSONPathWait
|
||||
expectedErr string
|
||||
name string
|
||||
condition string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "missing JSONPath expression",
|
||||
name: "jsonpath missing JSONPath expression",
|
||||
condition: "jsonpath=",
|
||||
expectedErr: "jsonpath expression cannot be empty",
|
||||
},
|
||||
{
|
||||
name: "value in JSONPath expression has equal sign",
|
||||
name: "jsonpath check for condition without value",
|
||||
condition: "jsonpath={.metadata.name}",
|
||||
expectedErr: None,
|
||||
},
|
||||
{
|
||||
name: "jsonpath check for condition without value relaxed parsing",
|
||||
condition: "jsonpath=abc",
|
||||
expectedErr: None,
|
||||
},
|
||||
{
|
||||
name: "jsonpath check for expression and value",
|
||||
condition: "jsonpath={.metadata.name}=foo-b6699dcfb-rnv7t",
|
||||
expectedErr: None,
|
||||
},
|
||||
{
|
||||
name: "jsonpath check for expression and value relaxed parsing",
|
||||
condition: "jsonpath=.metadata.name=foo-b6699dcfb-rnv7t",
|
||||
expectedErr: None,
|
||||
},
|
||||
{
|
||||
name: "jsonpath selecting based on condition",
|
||||
condition: `jsonpath={.status.containerStatuses[?(@.name=="foo")].ready}=True`,
|
||||
expectedErr: None,
|
||||
},
|
||||
{
|
||||
name: "jsonpath selecting based on condition relaxed parsing",
|
||||
condition: "jsonpath=status.conditions[?(@.type==\"Available\")].status=True",
|
||||
expectedErr: None,
|
||||
},
|
||||
{
|
||||
name: "jsonpath selecting based on condition without value",
|
||||
condition: `jsonpath={.status.containerStatuses[?(@.name=="foo")].ready}`,
|
||||
expectedErr: None,
|
||||
},
|
||||
{
|
||||
name: "jsonpath selecting based on condition without value relaxed parsing",
|
||||
condition: `jsonpath=.status.containerStatuses[?(@.name=="foo")].ready`,
|
||||
expectedErr: None,
|
||||
},
|
||||
{
|
||||
name: "jsonpath invalid expression with repeated '='",
|
||||
condition: "jsonpath={.metadata.name}='test=wrong'",
|
||||
expectedErr: "jsonpath wait format must be --for=jsonpath='{.status.readyReplicas}'=3 or --for=jsonpath='{.status.readyReplicas}'",
|
||||
},
|
||||
{
|
||||
name: "undefined value",
|
||||
name: "jsonpath undefined value after '='",
|
||||
condition: "jsonpath={.metadata.name}=",
|
||||
expectedErr: "jsonpath wait has to have a value after equal sign, like --for=jsonpath='{.status.readyReplicas}'=3",
|
||||
expectedErr: "jsonpath wait has to have a value after equal sign",
|
||||
},
|
||||
{
|
||||
name: "jsonpath complex expressions not supported",
|
||||
condition: "jsonpath={.status.conditions[?(@.type==\"Failed\"||@.type==\"Complete\")].status}=True",
|
||||
expectedErr: "unrecognized character in action: U+007C '|'",
|
||||
},
|
||||
{
|
||||
name: "jsonpath invalid expression",
|
||||
condition: "jsonpath={=True",
|
||||
expectedErr: "unexpected path string, expected a 'name1.name2' or '.name1.name2' or '{name1.name2}' or " +
|
||||
"'{.name1.name2}'",
|
||||
},
|
||||
{
|
||||
name: "condition delete",
|
||||
condition: "delete",
|
||||
expectedErr: None,
|
||||
},
|
||||
{
|
||||
name: "condition true",
|
||||
condition: "condition=hello",
|
||||
expectedErr: None,
|
||||
},
|
||||
{
|
||||
name: "condition with value",
|
||||
condition: "condition=hello=world",
|
||||
expectedErr: None,
|
||||
},
|
||||
{
|
||||
name: "unrecognized condition",
|
||||
condition: "cond=invalid",
|
||||
expectedErr: "unrecognized condition: \"cond=invalid\"",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
_, err := conditionFuncFor(test.condition, io.Discard)
|
||||
if err == nil && test.expectedErr != "" {
|
||||
t.Fatalf("expected %q, got empty", test.expectedErr)
|
||||
}
|
||||
if !strings.Contains(err.Error(), test.expectedErr) {
|
||||
t.Fatalf("expected %q, got %q", test.expectedErr, err.Error())
|
||||
switch {
|
||||
case err == nil && test.expectedErr != None:
|
||||
t.Fatalf("expected error %q, got nil", test.expectedErr)
|
||||
case err != nil && test.expectedErr == None:
|
||||
t.Fatalf("expected no error, got %q", err)
|
||||
case err != nil && test.expectedErr != None:
|
||||
if !strings.Contains(err.Error(), test.expectedErr) {
|
||||
t.Fatalf("expected error %q, got %q", test.expectedErr, err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue