github.com/argoproj/argo-cd@v1.8.7/util/lua/lua_test.go (about) 1 package lua 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/argoproj/gitops-engine/pkg/health" 8 "github.com/ghodss/yaml" 9 "github.com/stretchr/testify/assert" 10 lua "github.com/yuin/gopher-lua" 11 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 12 13 appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" 14 "github.com/argoproj/argo-cd/util/grpc" 15 ) 16 17 const objJSON = ` 18 apiVersion: argoproj.io/v1alpha1 19 kind: Rollout 20 metadata: 21 labels: 22 app.kubernetes.io/instance: helm-guestbook 23 name: helm-guestbook 24 namespace: default 25 resourceVersion: "123" 26 ` 27 const objWithNoScriptJSON = ` 28 apiVersion: not-an-endpoint.io/v1alpha1 29 kind: Test 30 metadata: 31 labels: 32 app.kubernetes.io/instance: helm-guestbook 33 name: helm-guestbook 34 namespace: default 35 resourceVersion: "123" 36 ` 37 38 const newHealthStatusFunction = `a = {} 39 a.status = "Healthy" 40 a.message ="NeedsToBeChanged" 41 if obj.metadata.name == "helm-guestbook" then 42 a.message = "testMessage" 43 end 44 return a` 45 46 func StrToUnstructured(jsonStr string) *unstructured.Unstructured { 47 obj := make(map[string]interface{}) 48 err := yaml.Unmarshal([]byte(jsonStr), &obj) 49 if err != nil { 50 panic(err) 51 } 52 return &unstructured.Unstructured{Object: obj} 53 } 54 55 func TestExecuteNewHealthStatusFunction(t *testing.T) { 56 testObj := StrToUnstructured(objJSON) 57 vm := VM{} 58 status, err := vm.ExecuteHealthLua(testObj, newHealthStatusFunction) 59 assert.Nil(t, err) 60 expectedHealthStatus := &health.HealthStatus{ 61 Status: "Healthy", 62 Message: "testMessage", 63 } 64 assert.Equal(t, expectedHealthStatus, status) 65 66 } 67 68 const osLuaScript = `os.getenv("HOME")` 69 70 func TestFailExternalLibCall(t *testing.T) { 71 testObj := StrToUnstructured(objJSON) 72 vm := VM{} 73 _, err := vm.ExecuteHealthLua(testObj, osLuaScript) 74 assert.Error(t, err, "") 75 assert.IsType(t, &lua.ApiError{}, err) 76 } 77 78 const returnInt = `return 1` 79 80 func TestFailLuaReturnNonTable(t *testing.T) { 81 testObj := StrToUnstructured(objJSON) 82 vm := VM{} 83 _, err := vm.ExecuteHealthLua(testObj, returnInt) 84 assert.Equal(t, fmt.Errorf(incorrectReturnType, "table", "number"), err) 85 } 86 87 const invalidHealthStatusStatus = `local healthStatus = {} 88 healthStatus.status = "test" 89 return healthStatus 90 ` 91 92 func TestInvalidHealthStatusStatus(t *testing.T) { 93 testObj := StrToUnstructured(objJSON) 94 vm := VM{} 95 status, err := vm.ExecuteHealthLua(testObj, invalidHealthStatusStatus) 96 assert.Nil(t, err) 97 expectedStatus := &health.HealthStatus{ 98 Status: health.HealthStatusUnknown, 99 Message: invalidHealthStatus, 100 } 101 assert.Equal(t, expectedStatus, status) 102 } 103 104 const infiniteLoop = `while true do ; end` 105 106 func TestHandleInfiniteLoop(t *testing.T) { 107 testObj := StrToUnstructured(objJSON) 108 vm := VM{} 109 _, err := vm.ExecuteHealthLua(testObj, infiniteLoop) 110 assert.IsType(t, &lua.ApiError{}, err) 111 } 112 113 func TestGetHealthScriptWithOverride(t *testing.T) { 114 testObj := StrToUnstructured(objJSON) 115 vm := VM{ 116 ResourceOverrides: map[string]appv1.ResourceOverride{ 117 "argoproj.io/Rollout": { 118 HealthLua: newHealthStatusFunction, 119 }, 120 }, 121 } 122 script, err := vm.GetHealthScript(testObj) 123 assert.Nil(t, err) 124 assert.Equal(t, newHealthStatusFunction, script) 125 } 126 127 func TestGetHealthScriptPredefined(t *testing.T) { 128 testObj := StrToUnstructured(objJSON) 129 vm := VM{} 130 script, err := vm.GetHealthScript(testObj) 131 assert.Nil(t, err) 132 assert.NotEmpty(t, script) 133 } 134 135 func TestGetHealthScriptNoPredefined(t *testing.T) { 136 testObj := StrToUnstructured(objWithNoScriptJSON) 137 vm := VM{} 138 script, err := vm.GetHealthScript(testObj) 139 assert.Nil(t, err) 140 assert.Equal(t, "", script) 141 } 142 143 func TestGetResourceActionPredefined(t *testing.T) { 144 testObj := StrToUnstructured(objJSON) 145 vm := VM{} 146 147 action, err := vm.GetResourceAction(testObj, "resume") 148 assert.Nil(t, err) 149 assert.NotEmpty(t, action) 150 } 151 152 func TestGetResourceActionNoPredefined(t *testing.T) { 153 testObj := StrToUnstructured(objWithNoScriptJSON) 154 vm := VM{} 155 action, err := vm.GetResourceAction(testObj, "test") 156 assert.Nil(t, err) 157 assert.Empty(t, action.ActionLua) 158 } 159 160 func TestGetResourceActionWithOverride(t *testing.T) { 161 testObj := StrToUnstructured(objJSON) 162 test := appv1.ResourceActionDefinition{ 163 Name: "test", 164 ActionLua: "return obj", 165 } 166 167 vm := VM{ 168 ResourceOverrides: map[string]appv1.ResourceOverride{ 169 "argoproj.io/Rollout": { 170 Actions: string(grpc.MustMarshal(appv1.ResourceActions{ 171 Definitions: []appv1.ResourceActionDefinition{ 172 test, 173 }, 174 })), 175 }, 176 }, 177 } 178 action, err := vm.GetResourceAction(testObj, "test") 179 assert.Nil(t, err) 180 assert.Equal(t, test, action) 181 } 182 183 func TestGetResourceActionDiscoveryPredefined(t *testing.T) { 184 testObj := StrToUnstructured(objJSON) 185 vm := VM{} 186 187 discoveryLua, err := vm.GetResourceActionDiscovery(testObj) 188 assert.Nil(t, err) 189 assert.NotEmpty(t, discoveryLua) 190 } 191 192 func TestGetResourceActionDiscoveryNoPredefined(t *testing.T) { 193 testObj := StrToUnstructured(objWithNoScriptJSON) 194 vm := VM{} 195 discoveryLua, err := vm.GetResourceActionDiscovery(testObj) 196 assert.Nil(t, err) 197 assert.Empty(t, discoveryLua) 198 } 199 200 func TestGetResourceActionDiscoveryWithOverride(t *testing.T) { 201 testObj := StrToUnstructured(objJSON) 202 vm := VM{ 203 ResourceOverrides: map[string]appv1.ResourceOverride{ 204 "argoproj.io/Rollout": { 205 Actions: string(grpc.MustMarshal(appv1.ResourceActions{ 206 ActionDiscoveryLua: validDiscoveryLua, 207 })), 208 }, 209 }, 210 } 211 discoveryLua, err := vm.GetResourceActionDiscovery(testObj) 212 assert.Nil(t, err) 213 assert.Equal(t, validDiscoveryLua, discoveryLua) 214 } 215 216 const validDiscoveryLua = ` 217 scaleParams = { {name = "replicas", type = "number"} } 218 scale = {name = 'scale', params = scaleParams} 219 220 resume = {name = 'resume'} 221 222 test = {} 223 a = {scale = scale, resume = resume, test = test} 224 225 return a 226 ` 227 228 func TestExecuteResourceActionDiscovery(t *testing.T) { 229 testObj := StrToUnstructured(objJSON) 230 vm := VM{} 231 actions, err := vm.ExecuteResourceActionDiscovery(testObj, validDiscoveryLua) 232 assert.Nil(t, err) 233 expectedActions := []appv1.ResourceAction{ 234 { 235 Name: "resume", 236 }, { 237 Name: "scale", 238 Params: []appv1.ResourceActionParam{{ 239 Name: "replicas", 240 Type: "number", 241 }}, 242 }, { 243 Name: "test", 244 }, 245 } 246 for _, expectedAction := range expectedActions { 247 assert.Contains(t, actions, expectedAction) 248 } 249 } 250 251 const discoveryLuaWithInvalidResourceAction = ` 252 resume = {name = 'resume', invalidField: "test""} 253 a = {resume = resume} 254 return a` 255 256 func TestExecuteResourceActionDiscoveryInvalidResourceAction(t *testing.T) { 257 testObj := StrToUnstructured(objJSON) 258 vm := VM{} 259 actions, err := vm.ExecuteResourceActionDiscovery(testObj, discoveryLuaWithInvalidResourceAction) 260 assert.Error(t, err) 261 assert.Nil(t, actions) 262 } 263 264 const invalidDiscoveryLua = ` 265 a = 1 266 return a 267 ` 268 269 func TestExecuteResourceActionDiscoveryInvalidReturn(t *testing.T) { 270 testObj := StrToUnstructured(objJSON) 271 vm := VM{} 272 actions, err := vm.ExecuteResourceActionDiscovery(testObj, invalidDiscoveryLua) 273 assert.Nil(t, actions) 274 assert.Error(t, err) 275 276 } 277 278 const validActionLua = ` 279 obj.metadata.labels["test"] = "test" 280 return obj 281 ` 282 283 const expectedUpdatedObj = ` 284 apiVersion: argoproj.io/v1alpha1 285 kind: Rollout 286 metadata: 287 labels: 288 app.kubernetes.io/instance: helm-guestbook 289 test: test 290 name: helm-guestbook 291 namespace: default 292 resourceVersion: "123" 293 ` 294 295 func TestExecuteResourceAction(t *testing.T) { 296 testObj := StrToUnstructured(objJSON) 297 expectedObj := StrToUnstructured(expectedUpdatedObj) 298 vm := VM{} 299 newObj, err := vm.ExecuteResourceAction(testObj, validActionLua) 300 assert.Nil(t, err) 301 assert.Equal(t, expectedObj, newObj) 302 } 303 304 func TestExecuteResourceActionNonTableReturn(t *testing.T) { 305 testObj := StrToUnstructured(objJSON) 306 vm := VM{} 307 _, err := vm.ExecuteResourceAction(testObj, returnInt) 308 assert.Errorf(t, err, incorrectReturnType, "table", "number") 309 } 310 311 const invalidTableReturn = `newObj = {} 312 newObj["test"] = "test" 313 return newObj 314 ` 315 316 func TestExecuteResourceActionInvalidUnstructured(t *testing.T) { 317 testObj := StrToUnstructured(objJSON) 318 vm := VM{} 319 _, err := vm.ExecuteResourceAction(testObj, invalidTableReturn) 320 assert.Error(t, err) 321 } 322 323 const objWithEmptyStruct = ` 324 apiVersion: argoproj.io/v1alpha1 325 kind: Test 326 metadata: 327 labels: 328 app.kubernetes.io/instance: helm-guestbook 329 test: test 330 name: helm-guestbook 331 namespace: default 332 resourceVersion: "123" 333 spec: 334 resources: {} 335 paused: true 336 containers: 337 - name: name1 338 test: {} 339 anotherList: 340 - name: name2 341 test2: {} 342 ` 343 344 const expectedUpdatedObjWithEmptyStruct = ` 345 apiVersion: argoproj.io/v1alpha1 346 kind: Test 347 metadata: 348 labels: 349 app.kubernetes.io/instance: helm-guestbook 350 test: test 351 name: helm-guestbook 352 namespace: default 353 resourceVersion: "123" 354 spec: 355 resources: {} 356 paused: false 357 containers: 358 - name: name1 359 test: {} 360 anotherList: 361 - name: name2 362 test2: {} 363 ` 364 365 const pausedToFalseLua = ` 366 obj.spec.paused = false 367 return obj 368 ` 369 370 func TestCleanPatch(t *testing.T) { 371 testObj := StrToUnstructured(objWithEmptyStruct) 372 expectedObj := StrToUnstructured(expectedUpdatedObjWithEmptyStruct) 373 vm := VM{} 374 newObj, err := vm.ExecuteResourceAction(testObj, pausedToFalseLua) 375 assert.Nil(t, err) 376 assert.Equal(t, expectedObj, newObj) 377 378 }