# Custom WaitRule for Resources ## Today waitRules can be defined like this: ``` waitRules: - supportsObservedGeneration: true conditionMatchers: - type: Failed status: "True" failure: true - type: Deployed status: "True" success: true resourceMatchers: - apiVersionKindMatcher: {apiVersion: corp.com/v1, kind: DatabaseInstance} ``` We are lacking the waitRules to define on custom objects and transitive resources. I propose below for the waitRules on custom Resources: ## Path Approach ### Scenario 1: I want to define the waitRule w.r.t. `currentState` value of `status`. e.g. ``` ... status: currentState: Running ... ``` Then waitRule can be: ``` waitRules: customMatchers: paths: - path/Key: [status, currentState] value: "Failed" failure: true - path/Key: [status, currentState] value: "Running" success: true resourceMatchers: - apiVersionKindMatcher: {apiVersion: corp.com/v1, kind: DatabaseInstance} ``` ### Scenario 2: I want to define the waitRule w.r.t. condition Type and status value. e.g. ``` status: conditions: - type: Ready status: "False" ... - type: Failed status: "True" ... ``` Then waitRule can be: ``` waitRules: customMatchers: paths: - path/Key: [status, conditions, {type:Failed}, status] value: "True" failure: true - path/Key: [status, conditions, {type:Ready}, status] value: "True" success: true resourceMatchers: - apiVersionKindMatcher: {apiVersion: v1, kind: Pod} ``` ## YTT integration Approach: ``` waitRules: yttMatchers: - ytt: assertContractV1: assertion.yml: | ... success/failure: true resourceMatchers: - apiVersionKindMatcher: {apiVersion: corp.com/v1, kind: DatabaseInstance} ``` ### Scenario 1: waitRules with ytt: ``` ... status: currentState: Running ... ``` WaitRule defined with starlark (.star) file instead of yaml - ``` waitRules: yttMatchers: - ytt: waitRuleContractV1: rules.star: | load("@ytt:data", "data") def DoneApplyState(state): if state == "Failed": return {"Done":True, "Successful": False, "Message": ""} elif state == "Running": return {"Done":True, "Successful": True, "Message": ""} else: return {"Done":False, "Successful": False, "Message": "Not in Failed or Running state"} end end DoneApplyState(data.values.status.currentState)) resourceMatchers: - apiVersionKindMatcher: {apiVersion: corp.com/v1, kind: DatabaseInstance} ``` using yaml file - ``` waitRules: yttMatchers: - ytt: assertContractV1: assertion.yml: | #@ load("@ytt:data", "data") val = data.values.status.currentState #@ val if val == "Failed" else assert.fail("Not in the failed state") failure: true - ytt: assertContractV1: assertion.yml: | #@ load("@ytt:data", "data") #@ load("@ytt:assertion", "assertion") #@ val = data.values.status.currentState #@ val if val == "Running" else assert.fail("Not in the running state") success: true resourceMatchers: - apiVersionKindMatcher: {apiVersion: corp.com/v1, kind: DatabaseInstance} ``` ### Scenario 2: I want to define the waitRule w.r.t. condition Type and status value. e.g. ``` status: conditions: - type: Ready status: "False" ... - type: Failed status: "True" ... ``` WaitRule defined with starlark (.star) file - ``` waitRules: yttMatchers: - ytt: assertContractV1: rules.star: | load("@ytt:data", "data") def DoneApplyState(conditions): for k in conditions: if k.type == "Ready" and k.status == "True": return {"Done":True, "Successful": True, "Message": ""} elif k.type == "Failed" and k.status == "True": return {"Done":True, "Successful": False, "Message": ""} # is type Failed and status False will be considered as success ? # should we have a check for status to be either true or false ? end end end DoneApplyState(data.values.status.conditions) resourceMatchers: - apiVersionKindMatcher: {apiVersion: corp.com/v1, kind: DatabaseInstance} ``` With yaml file - ``` waitRules: yttMatchers: - ytt: assertContractV1: assertion.yml: | #@ load("@ytt:data", "data") #@ load("@ytt:assert", "assert") #@ secrets = [] #@ conditions = data.values.status.conditions #@ status = None #@ for k in conditions: #@ if k.type=="Ready": #@ status = k.status #@ end #@ end #@ status if status == "True" else assert.fail("status is not True") success: True - ytt: assertContractV1: assertion.yml: | #@ load("@ytt:data", "data") #@ load("@ytt:assert", "assert") #@ secrets = [] #@ conditions = data.values.status.conditions #@ status = None #@ for k in conditions: #@ if k.type=="Failed": #@ status = k.status #@ end #@ end #@ status if status == "True" else assert.fail("status is not True") failure: True resourceMatchers: - apiVersionKindMatcher: {apiVersion: corp.com/v1, kind: DatabaseInstance} ``` ### Pros/Cons of each Approach #### Path Approach ##### Pros 1. No need to learn anything new. ##### Cons 1. Difficult to handle scenario which are not string match e.g. less than or greater than. #### yttMatcher Approach ##### Pros 1. All the scenarios such as string match, less than or greater than etc. can be taken care by ytt itself. ##### Cons 1. User needs to have knowledge of ytt.