Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add converge to field step which improves upon converge to selector #168

Merged
merged 7 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions internal/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -133,3 +134,30 @@
s, _ := json.MarshalIndent(st, "", "\t")
return string(s)
}

func ExtractField(data any, path []string) (any, error) {
if len(path) == 0 || data == nil {
return data, nil
}

currKey := path[0]

maybeArr := strings.Split(currKey, "[")
if len(maybeArr) >= 2 {
indexStr := strings.TrimSuffix(maybeArr[1], "]")
i, err := strconv.Atoi(indexStr)
if err != nil {
return nil, err
}

Check warning on line 151 in internal/util/util.go

View check run for this annotation

Codecov / codecov/patch

internal/util/util.go#L150-L151

Added lines #L150 - L151 were not covered by tests
arr := data.(map[string]any)[maybeArr[0]].([]any)
return ExtractField(arr[i], path[1:])
}

for key, val := range data.(map[string]any) {
if key == currKey {
return ExtractField(val, path[1:])
}
}

return nil, nil

Check warning on line 162 in internal/util/util.go

View check run for this annotation

Codecov / codecov/patch

internal/util/util.go#L162

Added line #L162 was not covered by tests
}
1 change: 1 addition & 0 deletions kubedog.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
kdt.scenario.Step(`^(?:I )?(create|submit|delete|update|upsert) (?:the )?resource (\S+) in (?:the )?([^"]*) namespace, the operation should (succeed|fail)$`, kdt.KubeClientSet.ResourceOperationWithResultInNamespace)
kdt.scenario.Step(`^(?:the )?resource ([^"]*) should be (created|deleted)$`, kdt.KubeClientSet.ResourceShouldBe)
kdt.scenario.Step(`^(?:the )?resource (\S+) (?:should )?converge to selector (\S+)$`, kdt.KubeClientSet.ResourceShouldConvergeToSelector)
kdt.scenario.Step(`^(?:the )?resource (\S+) (?:should )?converge to field (\S+)$`, kdt.KubeClientSet.ResourceShouldConvergeToField)

Check warning on line 56 in kubedog.go

View check run for this annotation

Codecov / codecov/patch

kubedog.go#L56

Added line #L56 was not covered by tests
kdt.scenario.Step(`^(?:the )?resource ([^"]*) condition ([^"]*) should be ([^"]*)$`, kdt.KubeClientSet.ResourceConditionShouldBe)
kdt.scenario.Step(`^(?:I )?update (?:the )?resource ([^"]*) with ([^"]*) set to ([^"]*)$`, kdt.KubeClientSet.UpdateResourceWithField)
kdt.scenario.Step(`^(?:I )?verify InstanceGroups (?:are )?in "ready" state$`, kdt.KubeClientSet.VerifyInstanceGroups)
Expand Down
8 changes: 8 additions & 0 deletions pkg/kube/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,14 @@
return unstruct.ResourceShouldConvergeToSelector(kc.DynamicInterface, resource, kc.getWaiterConfig(), selector)
}

func (kc *ClientSet) ResourceShouldConvergeToField(resourceFileName, selector string) error {
resource, err := unstruct.GetResource(kc.getDiscoveryClient(), kc.config.templateArguments, kc.getResourcePath(resourceFileName))
if err != nil {
return err
}
return unstruct.ResourceShouldConvergeToField(kc.DynamicInterface, resource, kc.getWaiterConfig(), selector)

Check warning on line 198 in pkg/kube/kube.go

View check run for this annotation

Codecov / codecov/patch

pkg/kube/kube.go#L193-L198

Added lines #L193 - L198 were not covered by tests
}

func (kc *ClientSet) ResourceConditionShouldBe(resourceFileName, conditionType, conditionValue string) error {
resource, err := unstruct.GetResource(kc.getDiscoveryClient(), kc.config.templateArguments, kc.getResourcePath(resourceFileName))
if err != nil {
Expand Down
9 changes: 8 additions & 1 deletion pkg/kube/unstructured/test/files/resource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,11 @@ metadata:
status:
conditions:
- type: someConditionType
status: "True"
status: "True"
replicaCount: 2
spec:
template:
containers:
- name: someContainer
image: someImage
version: 1.0.0
59 changes: 59 additions & 0 deletions pkg/kube/unstructured/unstructured.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"fmt"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -195,6 +196,64 @@
}
}

func ResourceShouldConvergeToField(dynamicClient dynamic.Interface, resource unstructuredResource, w common.WaiterConfig, selector string) error {
var counter int

if err := validateDynamicClient(dynamicClient); err != nil {
return err
}

Check warning on line 204 in pkg/kube/unstructured/unstructured.go

View check run for this annotation

Codecov / codecov/patch

pkg/kube/unstructured/unstructured.go#L203-L204

Added lines #L203 - L204 were not covered by tests

split := util.DeleteEmpty(strings.Split(selector, "="))
if len(split) != 2 {
return errors.Errorf("Selector '%s' should meet format '<key>=<value>'", selector)
}

key := split[0]
value := split[1]

keySlice := util.DeleteEmpty(strings.Split(key, "."))
if len(keySlice) < 1 {
return errors.Errorf("Found empty 'key' in selector '%s' of form '<key>=<value>'", selector)
}

gvr, unstruct := resource.GVR, resource.Resource

for {
if counter >= w.GetTries() {
return errors.New("waiter timed out waiting for resource")
}

Check warning on line 224 in pkg/kube/unstructured/unstructured.go

View check run for this annotation

Codecov / codecov/patch

pkg/kube/unstructured/unstructured.go#L223-L224

Added lines #L223 - L224 were not covered by tests
log.Infof("waiting for resource %v/%v to converge to %v=%v", unstruct.GetNamespace(), unstruct.GetName(), key, value)
retResource, err := dynamicClient.Resource(gvr.Resource).Namespace(unstruct.GetNamespace()).Get(context.Background(), unstruct.GetName(), metav1.GetOptions{})
if err != nil {
return err
}

Check warning on line 229 in pkg/kube/unstructured/unstructured.go

View check run for this annotation

Codecov / codecov/patch

pkg/kube/unstructured/unstructured.go#L228-L229

Added lines #L228 - L229 were not covered by tests

val, err := util.ExtractField(retResource.UnstructuredContent(), keySlice)
if err != nil {
return err
}

Check warning on line 234 in pkg/kube/unstructured/unstructured.go

View check run for this annotation

Codecov / codecov/patch

pkg/kube/unstructured/unstructured.go#L233-L234

Added lines #L233 - L234 were not covered by tests
var convertedValue any
switch val.(type) {
case int, int64:
convertedValue, err = strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}

Check warning on line 241 in pkg/kube/unstructured/unstructured.go

View check run for this annotation

Codecov / codecov/patch

pkg/kube/unstructured/unstructured.go#L240-L241

Added lines #L240 - L241 were not covered by tests
case string:
convertedValue = value
default:
return errors.New("unknown type")

Check warning on line 245 in pkg/kube/unstructured/unstructured.go

View check run for this annotation

Codecov / codecov/patch

pkg/kube/unstructured/unstructured.go#L244-L245

Added lines #L244 - L245 were not covered by tests
}
if reflect.DeepEqual(val, convertedValue) {
break
}
counter++
time.Sleep(w.GetInterval())

Check warning on line 251 in pkg/kube/unstructured/unstructured.go

View check run for this annotation

Codecov / codecov/patch

pkg/kube/unstructured/unstructured.go#L250-L251

Added lines #L250 - L251 were not covered by tests
}

return nil
}

func ResourceShouldConvergeToSelector(dynamicClient dynamic.Interface, resource unstructuredResource, w common.WaiterConfig, selector string) error {
var counter int

Expand Down
75 changes: 75 additions & 0 deletions pkg/kube/unstructured/unstructured_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,81 @@ func TestResourceShouldConvergeToSelector(t *testing.T) {
}
}

func TestResourceShouldConvergeToField(t *testing.T) {
type args struct {
dynamicClient dynamic.Interface
resource unstructuredResource
w common.WaiterConfig
selector string
}
resource := getResourceFromYaml(t, getFilePath("resource.yaml"))
labelKey, labelValue := getOneLabel(t, *resource.Resource)
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "Positive Test",
args: args{
dynamicClient: newFakeDynamicClientWithResource(resource),
resource: resource,
selector: ".metadata.labels." + labelKey + "=" + labelValue,
},
},
{
name: "Positive Test: array",
args: args{
dynamicClient: newFakeDynamicClientWithResource(resource),
resource: resource,
selector: ".spec.template.containers[0].image=someImage",
},
},
{
name: "Positive Test: non-string value",
args: args{
dynamicClient: newFakeDynamicClientWithResource(resource),
resource: resource,
selector: ".status.replicaCount=2",
},
},
{
name: "Positive Test: array and non-string value",
args: args{
dynamicClient: newFakeDynamicClientWithResource(resource),
resource: resource,
selector: ".spec.template.containers[0].version=1.0.0",
},
},
{
name: "Negative Test: invalid selector",
args: args{
dynamicClient: newFakeDynamicClientWithResource(resource),
resource: resource,
selector: ".invalid.selector.",
},
wantErr: true,
},
{
name: "Negative Test: invalid key",
args: args{
dynamicClient: newFakeDynamicClientWithResource(resource),
resource: resource,
selector: ".=invalid-key",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.w = common.NewWaiterConfig(1, time.Second)
if err := ResourceShouldConvergeToField(tt.args.dynamicClient, tt.args.resource, tt.args.w, tt.args.selector); (err != nil) != tt.wantErr {
t.Errorf("ResourceShouldConvergeToSelector() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func TestResourceConditionShouldBe(t *testing.T) {
type args struct {
dynamicClient dynamic.Interface
Expand Down
Loading