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

Move The 'wait' Argument Handling Logic To Its Exec Function #1630

Merged
merged 9 commits into from
Jan 9, 2023

Conversation

ihcsim
Copy link
Contributor

@ihcsim ihcsim commented Sep 9, 2022

Change Overview

This PR moves the handling of the wait function's arguments to its Exec() function, to give it more control over the rendering of the conditions arguments, which are made up of a mixed of Go templates with nested jsonpath variables. This change isolates the rendering of these conditions to the wait function code, allowing the removal of the jsonpath check from the general param.render() function.

The previous implementation of rendering Go templates with nested jsonpath variables doesn't work with templates contains control structure like conditionals and loops. These constructs may refer to root-scoped variables using the $ reference, leading to template validation errors, when these root-scoped variables are being misinterpreted as jsonpath variables by the existing regex. Since Go regex doesn't support negative lookbehind, there isn't an easy way to update the regex to match "only one { and } but not before or after".

The handling of all other arguments remain unchanged.

Pull request type

Please check the type of change your PR introduces:

  • 🚧 Work in Progress
  • 🌈 Refactoring (no functional changes, no api changes)
  • 🐹 Trivial/Minor
  • 🐛 Bugfix
  • 🌻 Feature
  • 🗺️ Documentation
  • 🤖 Test

Issues

Test Plan

Deploy this Nginx statefulset workload to your cluster:

cat <<EOF apiVersion: apps/v1 | kubectl apply -f -
kind: StatefulSet
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  serviceName: nginx
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: local-path      # update this to match your setup
      resources:
        requests:
          storage: 1Gi
EOF

Deploy this blueprint to test the KubeExecAll and Wait functions:

cat<<EOF | kubectl apply -f -
apiVersion: cr.kanister.io/v1alpha1
kind: Blueprint
metadata:
  name: exec-all
  namespace: kanister
actions:
  execAll:
    phases:
    - func: KubeExecAll
      name: execAll
      args:
        namespace: "{{ .Object.metadata.namespace }}"
        pods: "{{range until (int .Object.spec.replicas)}} {{ $.Object.metadata.name }}-{{.}}{{end}}"   # should work with range loop with root-scoped variable
        containers: nginx
        command:
        - sh
        - -c
        - |
          echo "hello world"
  execAllErr:
    phases:
    - func: KubeExecAll
      name: execAllErr
      args:
        namespace: "{{ .Object.metadata.namespace }}"
        pods: "{{range until (int .Object.spec.replicas)}} {{ $.Foo.metadata.name }}-{{.}}{{end}}"   # should catch invalid Foo field
        containers: nginx
        command:
        - sh
        - -c
        - |
          echo "hello world"
  checkReady:
    phases:
    - func: Wait
      name: checkReady
      args:
        timeout: 60s
        conditions:
          allOf:
          - condition: '{{ if (eq "{ $.status.readyReplicas }" "3")}}true{{ else }}false{{ end }}'  # should still work with jsonpath variables in wait conditions
            objectReference:
              apiVersion: v1
              group: apps
              resource: statefulsets
              name: "{{ .Object.metadata.name }}"
              namespace: "{{ .Object.metadata.namespace }}"
EOF

Deploy this actionset to trigger the execAll action and expect the actionset to complete successfully:

cat <<EOF | kubectl create -f -
apiVersion: cr.kanister.io/v1alpha1
kind: ActionSet
metadata:
  generateName: exec-all-
  namespace: kanister
spec:
  actions:
  - name: execAll
    blueprint: exec-all
    object:
      kind: StatefulSet
      name: nginx
      namespace: default
EOF

Deploy this actionset to trigger the execAllErr action and expect the actionset to fail and report the unidenfitied Foo field:

cat <<EOF | kubectl create -f -
apiVersion: cr.kanister.io/v1alpha1
kind: ActionSet
metadata:
  generateName: exec-all-err-
  namespace: kanister
spec:
  actions:
  - name: execAll
    blueprint: exec-all
    object:
      kind: StatefulSet
      name: nginx
      namespace: default
EOF

Deploy this actionset to trigger the wait action and expect the actionset to complete successfully:

cat<<EOF | kubectl apply -f -
apiVersion: cr.kanister.io/v1alpha1
kind: ActionSet
metadata:
  generateName: wait-
  namespace: kanister
spec:
  actions:
  - name: checkReady
    blueprint: exec-all
    object:
      kind: StatefulSet
      name: nginx
      namespace: default
EOF
  • 💪 Manual
  • ⚡ Unit test
  • 💚 E2E

This ensures that 'wait' has more control over the rendering of the
conditions arguments, which are made up of a mixed of Go template
actions and jsonpath variables.

The handling of all other arguments remain unchanged.

Signed-off-by: Ivan Sim <ivan.sim@kasten.io>
@github-actions
Copy link
Contributor

github-actions bot commented Sep 9, 2022

Thanks for submitting this pull request 🎉. The team will review it soon and get back to you.

If you haven't already, please take a moment to review our project contributing guideline and Code of Conduct document.

@infraq infraq added this to In Progress in Kanister Sep 9, 2022
Signed-off-by: Ivan Sim <ivan.sim@kasten.io>
args map[string]interface{},
tp param.TemplateParams) (map[string]interface{}, error) {
// let wait handle its own go template + jsonpath mixed arguments
if strings.ToLower(funcName) == "wait" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use the declared name from function pkg - function.WaitFuncName

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it will lead to an import loop.

var conditions WaitConditions
var err error
if err = Arg(args, WaitTimeoutArg, &timeout); err != nil {
if err := Arg(rendered, WaitTimeoutArg, &timeout); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need rendered arg to parse timeout? As per my understanding, it's always going to be const value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to retain the original behaviour where we used the rendered argument in Arg(), in this PR. I am curious - do we support Go template everywhere in the blueprint, or only specific fields?

Comment on lines +156 to +164
objRefRaw := map[string]crv1alpha1.ObjectReference{
"objRef": cond.ObjectReference,
}
rendered, err := param.RenderObjectRefs(objRefRaw, tp)
if err != nil {
return false, err
}
objRef := rendered["objRef"]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we move this to a separate helper function?

@michaelcourcy
Copy link
Contributor

michaelcourcy commented Sep 12, 2022

@ihcsim I've been testing on the couchbase cluster example this blueprint

apiVersion: cr.kanister.io/v1alpha1
kind: Blueprint
metadata:
  # test PR 1630 https://github.com/kanisterio/kanister/pull/1630
  name: couchbase-blueprint-test-1630
  namespace: kasten-io
actions:
  backup:
    kind: CustomResource
    phases:
    - func: KubeExecAll
      name: testExecAll      
      args:
        namespace: "{{ .Object.metadata.namespace }}"
        containers: couchbase-server
        pods: "{{range until (int (index .Object.spec.servers 0).size )}} {{ $.Object.metadata.name }}-000{{.}}{{end}}"
        command:
          - bash
          - -o
          - pipefail
          - -o
          - errexit
          - -c
          - |
            touch /tmp/execAll-1630
    - func: Wait
      name: waitCouchbaseClusterReadyAndBalanced
      args:
        timeout: 60s
        conditions:
          allOf:
            - condition: '{{$available := false}}{{ range $condition := $.status.conditions }}{{ if and (eq .type "Available") (eq .status "True")  }}{{$available=true}}{{ end }}{{ end }}{{ $available }}'
              objectReference:
                apiVersion: couchbase.com/v2
                resource: couchbaseclusters
                name: "{{ .Object.Name }}"
                namespace: "{{ .Object.namespace}}"
            - condition: '{{$balanced := false}}{{ range $condition := $.status.conditions }}{{ if and (eq .type "Balanced") (eq .status "True")  }}{{$balanced=true}}{{ end }}{{ end }}{{ $balanced }}'
              objectReference:
                apiVersion: couchbase.com/v2
                resource: couchbaseclusters
                name: "{{ .Object.Name }}"
                namespace: "{{ .Object.namespace}}"

The first func kubeExecAll work as expected but the second has this error

error:
                      message: "template: config:1: bad character U+003D '='"

In your example I can see that you use { and } inside a go template expression

condition: '{{ if (eq "{ $.status.readyReplicas }" "3")}}true{{ else }}false{{ end }}'  # should still work with jsonpath variables in wait conditions

I'm not sure I understand why you are using that, is it to evaluate a jsonpath expression or the contrary avoid the evaluation as a jsonpath ?

My understanding was that now we could use plain go-template in conditions but I may have misunderstood.

@ihcsim
Copy link
Contributor Author

ihcsim commented Sep 12, 2022

@michaelcourcy thanks for trying it out. I thought the proper jsonpath syntax requires the { and } delimiter, i.e. ${.status.conditions}. See original code here.

Signed-off-by: Ivan Sim <ivan.sim@kasten.io>
Signed-off-by: Ivan Sim <ivan.sim@kasten.io>
@PrasadG193
Copy link
Contributor

I am working on switching to go-template syntax for fetching k8s object value in wait condition. Can we hold off on the PR till then?

@michaelcourcy
Copy link
Contributor

I am working on switching to go-template syntax for fetching k8s object value in wait condition. Can we hold off on the PR till then?

I really would prefer that personally, it's a bit ubiquitous to switch from jsonpath to go-template in the same expression.

@PrasadG193
Copy link
Contributor

I have raised - #1635 on top of the current PR

* Switch to go template syntax for Wait conditions

Signed-off-by: Prasad Ghangal <prasad.ghangal@gmail.com>

* Update K8ssandra BP as per the new Wait func syntax

Signed-off-by: Prasad Ghangal <prasad.ghangal@gmail.com>

Signed-off-by: Prasad Ghangal <prasad.ghangal@gmail.com>
@github-actions
Copy link
Contributor

This PR is marked as stale due to inactivity. Add a new comment to reactivate it.

@github-actions github-actions bot added the stale label Dec 31, 2022
@PrasadG193
Copy link
Contributor

Since we decided to implement a new function which supports only go-template syntax. This PR needs to be refactored to remove the changes in wait function implementation to fix only #1470

@github-actions github-actions bot removed the stale label Jan 3, 2023
Kanister automation moved this from In Progress to Reviewer approved Jan 6, 2023
@pavannd1
Copy link
Contributor

pavannd1 commented Jan 6, 2023

@PrasadG193 I have approved this. Do you want to merge this and refactor the required things in your PR?

@mergify mergify bot merged commit 15157fb into master Jan 9, 2023
Kanister automation moved this from Reviewer approved to Done Jan 9, 2023
@mergify mergify bot deleted the fix-go-template-jsonpath branch January 9, 2023 19:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Development

Successfully merging this pull request may close these issues.

Template does not evaluate as expected in the pods field of KubeExecAll
4 participants