package lua import ( "bytes" "fmt" "testing" "github.com/argoproj/argo-cd/gitops-engine/pkg/health" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" lua "github.com/yuin/gopher-lua" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/yaml" applicationpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/application" appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v3/util/grpc" ) const objJSON = ` apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: labels: app.kubernetes.io/instance: helm-guestbook name: helm-guestbook namespace: default resourceVersion: "123" ` const objWithNoScriptJSON = ` apiVersion: not-an-endpoint.io/v1alpha1 kind: Test metadata: labels: app.kubernetes.io/instance: helm-guestbook name: helm-guestbook namespace: default resourceVersion: "123" ` const ec2AWSCrossplaneObjJSON = ` apiVersion: ec2.aws.crossplane.io/v1alpha1 kind: Instance metadata: name: sample-crosspalne-ec2-instance spec: forProvider: region: us-west-2 instanceType: t2.micro keyName: my-crossplane-key-pair providerConfigRef: name: awsconfig ` const newHealthStatusFunction = `a = {} a.status = "Healthy" a.message ="NeedsToBeChanged" if obj.metadata.name == "helm-guestbook" then a.message = "testMessage" end return a` const newWildcardHealthStatusFunction = `a = {} a.status = "Healthy" a.message ="NeedsToBeChanged" if obj.metadata.name == "sample-crosspalne-ec2-instance" then a.message = "testWildcardMessage" end return a` func StrToUnstructured(jsonStr string) *unstructured.Unstructured { obj := make(map[string]any) err := yaml.Unmarshal([]byte(jsonStr), &obj) if err != nil { panic(err) } return &unstructured.Unstructured{Object: obj} } func TestExecuteNewHealthStatusFunction(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} status, err := vm.ExecuteHealthLua(testObj, newHealthStatusFunction) require.NoError(t, err) expectedHealthStatus := &health.HealthStatus{ Status: "Healthy", Message: "testMessage", } assert.Equal(t, expectedHealthStatus, status) } func TestExecuteWildcardHealthStatusFunction(t *testing.T) { testObj := StrToUnstructured(ec2AWSCrossplaneObjJSON) vm := VM{} status, err := vm.ExecuteHealthLua(testObj, newWildcardHealthStatusFunction) require.NoError(t, err) expectedHealthStatus := &health.HealthStatus{ Status: "Healthy", Message: "testWildcardMessage", } assert.Equal(t, expectedHealthStatus, status) } const osLuaScript = `os.getenv("HOME")` func TestFailExternalLibCall(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} _, err := vm.ExecuteHealthLua(testObj, osLuaScript) require.Error(t, err) var target *lua.ApiError assert.ErrorAs(t, err, &target) } const returnInt = `return 1` func TestFailLuaReturnNonTable(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} _, err := vm.ExecuteHealthLua(testObj, returnInt) assert.Equal(t, fmt.Errorf(incorrectReturnType, "table", "number"), err) } const invalidHealthStatusStatus = `local healthStatus = {} healthStatus.status = "test" return healthStatus ` func TestInvalidHealthStatusStatus(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} status, err := vm.ExecuteHealthLua(testObj, invalidHealthStatusStatus) require.NoError(t, err) expectedStatus := &health.HealthStatus{ Status: health.HealthStatusUnknown, Message: invalidHealthStatus, } assert.Equal(t, expectedStatus, status) } const validReturnNothingHealthStatusStatus = `local healthStatus = {} return ` func TestNoReturnHealthStatusStatus(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} status, err := vm.ExecuteHealthLua(testObj, validReturnNothingHealthStatusStatus) require.NoError(t, err) expectedStatus := &health.HealthStatus{} assert.Equal(t, expectedStatus, status) } const validNilHealthStatusStatus = `local healthStatus = {} return nil ` func TestNilHealthStatusStatus(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} status, err := vm.ExecuteHealthLua(testObj, validNilHealthStatusStatus) require.NoError(t, err) expectedStatus := &health.HealthStatus{} assert.Equal(t, expectedStatus, status) } const validEmptyArrayHealthStatusStatus = `local healthStatus = {} return healthStatus ` func TestEmptyHealthStatusStatus(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} status, err := vm.ExecuteHealthLua(testObj, validEmptyArrayHealthStatusStatus) require.NoError(t, err) expectedStatus := &health.HealthStatus{} assert.Equal(t, expectedStatus, status) } const infiniteLoop = `while true do ; end` func TestHandleInfiniteLoop(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} _, err := vm.ExecuteHealthLua(testObj, infiniteLoop) var target *lua.ApiError require.ErrorAs(t, err, &target) } func TestGetHealthScriptWithOverride(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{ ResourceOverrides: map[string]appv1.ResourceOverride{ "argoproj.io/Rollout": { HealthLua: newHealthStatusFunction, UseOpenLibs: false, }, }, } script, useOpenLibs, err := vm.GetHealthScript(testObj) require.NoError(t, err) assert.False(t, useOpenLibs) assert.Equal(t, newHealthStatusFunction, script) } func TestGetHealthScriptWithKindWildcardOverride(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{ ResourceOverrides: map[string]appv1.ResourceOverride{ "argoproj.io/*": { HealthLua: newHealthStatusFunction, UseOpenLibs: false, }, }, } script, useOpenLibs, err := vm.GetHealthScript(testObj) require.NoError(t, err) assert.False(t, useOpenLibs) assert.Equal(t, newHealthStatusFunction, script) } func TestGetHealthScriptWithGroupWildcardOverride(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{ ResourceOverrides: map[string]appv1.ResourceOverride{ "*.io/Rollout": { HealthLua: newHealthStatusFunction, UseOpenLibs: false, }, }, } script, useOpenLibs, err := vm.GetHealthScript(testObj) require.NoError(t, err) assert.False(t, useOpenLibs) assert.Equal(t, newHealthStatusFunction, script) } func TestGetHealthScriptWithGroupAndKindWildcardOverride(t *testing.T) { testObj := StrToUnstructured(ec2AWSCrossplaneObjJSON) vm := VM{ ResourceOverrides: map[string]appv1.ResourceOverride{ "*.aws.crossplane.io/*": { HealthLua: newHealthStatusFunction, UseOpenLibs: false, }, }, } script, useOpenLibs, err := vm.GetHealthScript(testObj) require.NoError(t, err) assert.False(t, useOpenLibs) assert.Equal(t, newHealthStatusFunction, script) } func TestGetHealthScriptPredefined(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} script, useOpenLibs, err := vm.GetHealthScript(testObj) require.NoError(t, err) assert.True(t, useOpenLibs) assert.NotEmpty(t, script) } func TestGetHealthScriptNoPredefined(t *testing.T) { testObj := StrToUnstructured(objWithNoScriptJSON) vm := VM{} script, useOpenLibs, err := vm.GetHealthScript(testObj) require.NoError(t, err) assert.False(t, useOpenLibs) assert.Empty(t, script) } func TestGetResourceActionPredefined(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} action, err := vm.GetResourceAction(testObj, "resume") require.NoError(t, err) assert.NotEmpty(t, action) } func TestGetResourceActionNoPredefined(t *testing.T) { testObj := StrToUnstructured(objWithNoScriptJSON) vm := VM{} action, err := vm.GetResourceAction(testObj, "test") require.ErrorIs(t, err, errScriptDoesNotExist) assert.Empty(t, action.ActionLua) } func TestGetResourceActionWithOverride(t *testing.T) { testObj := StrToUnstructured(objJSON) test := appv1.ResourceActionDefinition{ Name: "test", ActionLua: "return obj", } vm := VM{ ResourceOverrides: map[string]appv1.ResourceOverride{ "argoproj.io/Rollout": { Actions: string(grpc.MustMarshal(appv1.ResourceActions{ Definitions: []appv1.ResourceActionDefinition{ test, }, })), }, }, } action, err := vm.GetResourceAction(testObj, "test") require.NoError(t, err) assert.Equal(t, test, action) } func TestGetResourceActionDiscoveryPredefined(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} discoveryLua, err := vm.GetResourceActionDiscovery(testObj) require.NoError(t, err) assert.NotEmpty(t, discoveryLua) } func TestGetResourceActionDiscoveryNoPredefined(t *testing.T) { testObj := StrToUnstructured(objWithNoScriptJSON) vm := VM{} discoveryLua, err := vm.GetResourceActionDiscovery(testObj) require.NoError(t, err) assert.Empty(t, discoveryLua) } func TestGetResourceActionDiscoveryWithOverride(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{ ResourceOverrides: map[string]appv1.ResourceOverride{ "argoproj.io/Rollout": { Actions: string(grpc.MustMarshal(appv1.ResourceActions{ ActionDiscoveryLua: validDiscoveryLua, })), }, }, } discoveryLua, err := vm.GetResourceActionDiscovery(testObj) require.NoError(t, err) assert.Equal(t, validDiscoveryLua, discoveryLua[0]) } func TestGetResourceActionsWithBuiltInActionsFlag(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{ ResourceOverrides: map[string]appv1.ResourceOverride{ "argoproj.io/Rollout": { Actions: string(grpc.MustMarshal(appv1.ResourceActions{ ActionDiscoveryLua: validDiscoveryLua, MergeBuiltinActions: true, })), }, }, } discoveryLua, err := vm.GetResourceActionDiscovery(testObj) require.NoError(t, err) assert.Equal(t, validDiscoveryLua, discoveryLua[0]) } const validDiscoveryLua = ` scaleParams = { {name = "replicas", type = "number"} } scale = {name = 'scale', params = scaleParams} resume = {name = 'resume'} a = {scale = scale, resume = resume} return a ` const additionalValidDiscoveryLua = ` scaleParams = { {name = "override", type = "number"} } scale = {name = 'scale', params = scaleParams} prebuilt = {prebuilt = 'prebuilt', type = 'number'} a = {scale = scale, prebuilt = prebuilt} return a ` func TestExecuteResourceActionDiscovery(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} actions, err := vm.ExecuteResourceActionDiscovery(testObj, []string{validDiscoveryLua}) require.NoError(t, err) expectedActions := []appv1.ResourceAction{ { Name: "resume", }, { Name: "scale", Params: []appv1.ResourceActionParam{{ Name: "replicas", }}, }, } for _, expectedAction := range expectedActions { assert.Contains(t, actions, expectedAction) } } func TestExecuteResourceActionDiscoveryWithDuplicationActions(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} actions, err := vm.ExecuteResourceActionDiscovery(testObj, []string{validDiscoveryLua, additionalValidDiscoveryLua}) require.NoError(t, err) expectedActions := []appv1.ResourceAction{ { Name: "resume", }, { Name: "scale", Params: []appv1.ResourceActionParam{{ Name: "replicas", }}, }, { Name: "prebuilt", }, } for _, expectedAction := range expectedActions { assert.Contains(t, actions, expectedAction) } } const discoveryLuaWithInvalidResourceAction = ` resume = {name = 'resume', invalidField: "test""} a = {resume = resume} return a` func TestExecuteResourceActionDiscoveryInvalidResourceAction(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} actions, err := vm.ExecuteResourceActionDiscovery(testObj, []string{discoveryLuaWithInvalidResourceAction}) require.Error(t, err) assert.Nil(t, actions) } const invalidDiscoveryLua = ` a = 1 return a ` func TestExecuteResourceActionDiscoveryInvalidReturn(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} actions, err := vm.ExecuteResourceActionDiscovery(testObj, []string{invalidDiscoveryLua}) assert.Nil(t, actions) require.Error(t, err) } const validActionLua = ` obj.metadata.labels["test"] = "test" return obj ` const expectedLuaUpdatedResult = ` apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: labels: app.kubernetes.io/instance: helm-guestbook test: test name: helm-guestbook namespace: default resourceVersion: "123" ` // Test an action that returns a single k8s resource json func TestExecuteOldStyleResourceAction(t *testing.T) { testObj := StrToUnstructured(objJSON) expectedLuaUpdatedObj := StrToUnstructured(expectedLuaUpdatedResult) vm := VM{} newObjects, err := vm.ExecuteResourceAction(testObj, validActionLua, nil) require.NoError(t, err) assert.Len(t, newObjects, 1) assert.Equal(t, newObjects[0].K8SOperation, K8SOperation("patch")) assert.Equal(t, expectedLuaUpdatedObj, newObjects[0].UnstructuredObj) } const cronJobObjYaml = ` apiVersion: batch/v1 kind: CronJob metadata: name: hello namespace: test-ns ` const expectedCreatedJobObjList = ` - operation: create resource: apiVersion: batch/v1 kind: Job metadata: name: hello-1 namespace: test-ns ` const expectedCreatedMultipleJobsObjList = ` - operation: create resource: apiVersion: batch/v1 kind: Job metadata: name: hello-1 namespace: test-ns - operation: create resource: apiVersion: batch/v1 kind: Job metadata: name: hello-2 namespace: test-ns ` const expectedActionMixedOperationObjList = ` - operation: create resource: apiVersion: batch/v1 kind: Job metadata: name: hello-1 namespace: test-ns - operation: patch resource: apiVersion: batch/v1 kind: CronJob metadata: name: hello namespace: test-ns labels: test: test ` const createJobActionLua = ` job = {} job.apiVersion = "batch/v1" job.kind = "Job" job.metadata = {} job.metadata.name = "hello-1" job.metadata.namespace = "test-ns" impactedResource = {} impactedResource.operation = "create" impactedResource.resource = job result = {} result[1] = impactedResource return result ` const createMultipleJobsActionLua = ` job1 = {} job1.apiVersion = "batch/v1" job1.kind = "Job" job1.metadata = {} job1.metadata.name = "hello-1" job1.metadata.namespace = "test-ns" impactedResource1 = {} impactedResource1.operation = "create" impactedResource1.resource = job1 result = {} result[1] = impactedResource1 job2 = {} job2.apiVersion = "batch/v1" job2.kind = "Job" job2.metadata = {} job2.metadata.name = "hello-2" job2.metadata.namespace = "test-ns" impactedResource2 = {} impactedResource2.operation = "create" impactedResource2.resource = job2 result[2] = impactedResource2 return result ` const mixedOperationActionLuaOk = ` job1 = {} job1.apiVersion = "batch/v1" job1.kind = "Job" job1.metadata = {} job1.metadata.name = "hello-1" job1.metadata.namespace = obj.metadata.namespace impactedResource1 = {} impactedResource1.operation = "create" impactedResource1.resource = job1 result = {} result[1] = impactedResource1 obj.metadata.labels = {} obj.metadata.labels["test"] = "test" impactedResource2 = {} impactedResource2.operation = "patch" impactedResource2.resource = obj result[2] = impactedResource2 return result ` const createMixedOperationActionLuaFailing = ` job1 = {} job1.apiVersion = "batch/v1" job1.kind = "Job" job1.metadata = {} job1.metadata.name = "hello-1" job1.metadata.namespace = obj.metadata.namespace impactedResource1 = {} impactedResource1.operation = "create" impactedResource1.resource = job1 result = {} result[1] = impactedResource1 obj.metadata.labels = {} obj.metadata.labels["test"] = "test" impactedResource2 = {} impactedResource2.operation = "thisShouldFail" impactedResource2.resource = obj result[2] = impactedResource2 return result ` func TestExecuteNewStyleCreateActionSingleResource(t *testing.T) { testObj := StrToUnstructured(cronJobObjYaml) jsonBytes, err := yaml.YAMLToJSON([]byte(expectedCreatedJobObjList)) require.NoError(t, err) t.Log(bytes.NewBuffer(jsonBytes).String()) expectedObjects, err := UnmarshalToImpactedResources(bytes.NewBuffer(jsonBytes).String()) require.NoError(t, err) vm := VM{} newObjects, err := vm.ExecuteResourceAction(testObj, createJobActionLua, nil) require.NoError(t, err) assert.Equal(t, expectedObjects, newObjects) } func TestExecuteNewStyleCreateActionMultipleResources(t *testing.T) { testObj := StrToUnstructured(cronJobObjYaml) jsonBytes, err := yaml.YAMLToJSON([]byte(expectedCreatedMultipleJobsObjList)) require.NoError(t, err) // t.Log(bytes.NewBuffer(jsonBytes).String()) expectedObjects, err := UnmarshalToImpactedResources(bytes.NewBuffer(jsonBytes).String()) require.NoError(t, err) vm := VM{} newObjects, err := vm.ExecuteResourceAction(testObj, createMultipleJobsActionLua, nil) require.NoError(t, err) assert.Equal(t, expectedObjects, newObjects) } func TestExecuteNewStyleActionMixedOperationsOk(t *testing.T) { testObj := StrToUnstructured(cronJobObjYaml) jsonBytes, err := yaml.YAMLToJSON([]byte(expectedActionMixedOperationObjList)) require.NoError(t, err) // t.Log(bytes.NewBuffer(jsonBytes).String()) expectedObjects, err := UnmarshalToImpactedResources(bytes.NewBuffer(jsonBytes).String()) require.NoError(t, err) vm := VM{} newObjects, err := vm.ExecuteResourceAction(testObj, mixedOperationActionLuaOk, nil) require.NoError(t, err) assert.Equal(t, expectedObjects, newObjects) } func TestExecuteNewStyleActionMixedOperationsFailure(t *testing.T) { testObj := StrToUnstructured(cronJobObjYaml) vm := VM{} _, err := vm.ExecuteResourceAction(testObj, createMixedOperationActionLuaFailing, nil) assert.ErrorContains(t, err, "unsupported operation") } func TestExecuteResourceActionNonTableReturn(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} _, err := vm.ExecuteResourceAction(testObj, returnInt, nil) assert.Errorf(t, err, incorrectReturnType, "table", "number") } const invalidTableReturn = `newObj = {} newObj["test"] = "test" return newObj ` func TestExecuteResourceActionInvalidUnstructured(t *testing.T) { testObj := StrToUnstructured(objJSON) vm := VM{} _, err := vm.ExecuteResourceAction(testObj, invalidTableReturn, nil) require.Error(t, err) } func TestCleanPatch(t *testing.T) { t.Run("Empty Struct preserved", func(t *testing.T) { const obj = ` apiVersion: argoproj.io/v1alpha1 kind: Test metadata: labels: app.kubernetes.io/instance: helm-guestbook test: test name: helm-guestbook namespace: default resourceVersion: "123" spec: resources: {} updated: something: true containers: - name: name1 test: {} anotherList: - name: name2 test2: {} ` const expected = ` apiVersion: argoproj.io/v1alpha1 kind: Test metadata: labels: app.kubernetes.io/instance: helm-guestbook test: test name: helm-guestbook namespace: default resourceVersion: "123" spec: resources: {} updated: {} containers: - name: name1 test: {} anotherList: - name: name2 test2: {} ` const luaAction = ` obj.spec.updated = {} return obj ` testObj := StrToUnstructured(obj) expectedObj := StrToUnstructured(expected) vm := VM{} newObjects, err := vm.ExecuteResourceAction(testObj, luaAction, nil) require.NoError(t, err) assert.Len(t, newObjects, 1) assert.Equal(t, newObjects[0].K8SOperation, K8SOperation("patch")) assert.Equal(t, expectedObj, newObjects[0].UnstructuredObj) }) t.Run("New item added to array", func(t *testing.T) { const obj = ` apiVersion: argoproj.io/v1alpha1 kind: Test metadata: labels: app.kubernetes.io/instance: helm-guestbook test: test name: helm-guestbook namespace: default resourceVersion: "123" spec: containers: - name: name1 test: {} anotherList: - name: name2 test2: {} ` const expected = ` apiVersion: argoproj.io/v1alpha1 kind: Test metadata: labels: app.kubernetes.io/instance: helm-guestbook test: test name: helm-guestbook namespace: default resourceVersion: "123" spec: containers: - name: name1 test: {} anotherList: - name: name2 test2: {} - name: added #test: {} ### would be decoded as an empty array and is not supported. The type is unknown testArray: [] ### works since it is decoded in the correct type another: supported: true ` // `test: {}` in new container would be decoded as an empty array and is not supported. The type is unknown // `testArray: []` works since it is decoded in the correct type const luaAction = ` table.insert(obj.spec.containers, {name = "added", testArray = {}, another = {supported = true}}) return obj ` testObj := StrToUnstructured(obj) expectedObj := StrToUnstructured(expected) vm := VM{} newObjects, err := vm.ExecuteResourceAction(testObj, luaAction, nil) require.NoError(t, err) assert.Len(t, newObjects, 1) assert.Equal(t, newObjects[0].K8SOperation, K8SOperation("patch")) assert.Equal(t, expectedObj, newObjects[0].UnstructuredObj) }) t.Run("Last item removed from array", func(t *testing.T) { const obj = ` apiVersion: argoproj.io/v1alpha1 kind: Test metadata: labels: app.kubernetes.io/instance: helm-guestbook test: test name: helm-guestbook namespace: default resourceVersion: "123" spec: containers: - name: name1 test: {} anotherList: - name: name2 test2: {} - name: name3 test: {} anotherList: - name: name4 test2: {} ` const expected = ` apiVersion: argoproj.io/v1alpha1 kind: Test metadata: labels: app.kubernetes.io/instance: helm-guestbook test: test name: helm-guestbook namespace: default resourceVersion: "123" spec: containers: - name: name1 test: {} anotherList: - name: name2 test2: {} ` const luaAction = ` table.remove(obj.spec.containers) return obj ` testObj := StrToUnstructured(obj) expectedObj := StrToUnstructured(expected) vm := VM{} newObjects, err := vm.ExecuteResourceAction(testObj, luaAction, nil) require.NoError(t, err) assert.Len(t, newObjects, 1) assert.Equal(t, newObjects[0].K8SOperation, K8SOperation("patch")) assert.Equal(t, expectedObj, newObjects[0].UnstructuredObj) }) } func TestGetResourceHealth(t *testing.T) { const testSA = ` apiVersion: v1 kind: ServiceAccount metadata: name: test namespace: test` const script = ` hs = {} str = "Using lua standard library" if string.find(str, "standard") then hs.message = "Standard lib was used" else hs.message = "Standard lib was not used" end hs.status = "Healthy" return hs` const healthWildcardOverrideScript = ` hs = {} hs.status = "Healthy" return hs` const healthWildcardOverrideScriptUnhealthy = ` hs = {} hs.status = "UnHealthy" return hs` getHealthOverride := func(openLibs bool) ResourceHealthOverrides { return ResourceHealthOverrides{ "ServiceAccount": appv1.ResourceOverride{ HealthLua: script, UseOpenLibs: openLibs, }, } } getWildcardHealthOverride := ResourceHealthOverrides{ "*.aws.crossplane.io/*": appv1.ResourceOverride{ HealthLua: healthWildcardOverrideScript, }, } getMultipleWildcardHealthOverrides := ResourceHealthOverrides{ "*.aws.crossplane.io/*": appv1.ResourceOverride{ HealthLua: "", }, "*.aws*": appv1.ResourceOverride{ HealthLua: healthWildcardOverrideScriptUnhealthy, }, } getBaseWildcardHealthOverrides := ResourceHealthOverrides{ "*/*": appv1.ResourceOverride{ HealthLua: "", }, } t.Run("Enable Lua standard lib", func(t *testing.T) { testObj := StrToUnstructured(testSA) overrides := getHealthOverride(true) status, err := overrides.GetResourceHealth(testObj) require.NoError(t, err) expectedStatus := &health.HealthStatus{ Status: health.HealthStatusHealthy, Message: "Standard lib was used", } assert.Equal(t, expectedStatus, status) }) t.Run("Disable Lua standard lib", func(t *testing.T) { testObj := StrToUnstructured(testSA) overrides := getHealthOverride(false) status, err := overrides.GetResourceHealth(testObj) var target *lua.ApiError require.ErrorAs(t, err, &target) expectedErr := ":4: attempt to index a non-table object(nil) with key 'find'" require.EqualError(t, err, expectedErr) assert.Nil(t, status) }) t.Run("Get resource health for wildcard override", func(t *testing.T) { testObj := StrToUnstructured(ec2AWSCrossplaneObjJSON) overrides := getWildcardHealthOverride status, err := overrides.GetResourceHealth(testObj) require.NoError(t, err) expectedStatus := &health.HealthStatus{ Status: health.HealthStatusHealthy, } assert.Equal(t, expectedStatus, status) }) t.Run("Get resource health for wildcard override with non-empty health.lua", func(t *testing.T) { testObj := StrToUnstructured(ec2AWSCrossplaneObjJSON) overrides := getMultipleWildcardHealthOverrides status, err := overrides.GetResourceHealth(testObj) require.NoError(t, err) expectedStatus := &health.HealthStatus{Status: "Unknown", Message: "Lua returned an invalid health status"} assert.Equal(t, expectedStatus, status) }) t.Run("Get resource health for */* override with empty health.lua", func(t *testing.T) { testObj := StrToUnstructured(objWithNoScriptJSON) overrides := getBaseWildcardHealthOverrides status, err := overrides.GetResourceHealth(testObj) require.NoError(t, err) assert.Nil(t, status) }) t.Run("Resource health for wildcard override not found", func(t *testing.T) { testObj := StrToUnstructured(testSA) overrides := getWildcardHealthOverride status, err := overrides.GetResourceHealth(testObj) require.NoError(t, err) assert.Nil(t, status) }) } func TestExecuteResourceActionWithParams(t *testing.T) { deploymentObj := createMockResource("Deployment", "test-deployment", 1) statefulSetObj := createMockResource("StatefulSet", "test-statefulset", 1) actionLua := ` obj.spec.replicas = tonumber(actionParams["replicas"]) return obj ` params := []*applicationpkg.ResourceActionParameters{ { Name: func() *string { s := "replicas"; return &s }(), Value: func() *string { s := "3"; return &s }(), }, } vm := VM{} // Test with Deployment t.Run("Test with Deployment", func(t *testing.T) { impactedResources, err := vm.ExecuteResourceAction(deploymentObj, actionLua, params) require.NoError(t, err) for _, impactedResource := range impactedResources { modifiedObj := impactedResource.UnstructuredObj // Check the replicas in the modified object actualReplicas, found, err := unstructured.NestedInt64(modifiedObj.Object, "spec", "replicas") require.NoError(t, err) assert.True(t, found, "spec.replicas should be found in the modified object") assert.Equal(t, int64(3), actualReplicas, "replicas should be updated to 3") } }) // Test with StatefulSet t.Run("Test with StatefulSet", func(t *testing.T) { impactedResources, err := vm.ExecuteResourceAction(statefulSetObj, actionLua, params) require.NoError(t, err) for _, impactedResource := range impactedResources { modifiedObj := impactedResource.UnstructuredObj // Check the replicas in the modified object actualReplicas, found, err := unstructured.NestedInt64(modifiedObj.Object, "spec", "replicas") require.NoError(t, err) assert.True(t, found, "spec.replicas should be found in the modified object") assert.Equal(t, int64(3), actualReplicas, "replicas should be updated to 3") } }) } func createMockResource(kind string, name string, replicas int) *unstructured.Unstructured { return StrToUnstructured(fmt.Sprintf(` apiVersion: apps/v1 kind: %s metadata: name: %s namespace: default spec: replicas: %d template: metadata: labels: app: test spec: containers: - name: test-container image: nginx `, kind, name, replicas)) } func Test_getHealthScriptPaths(t *testing.T) { paths, err := getGlobHealthScriptPaths() require.NoError(t, err) // This test will fail any time a glob pattern is added to the health script paths. We don't expect that to happen // often. assert.Equal(t, []string{ "_.cnrm.cloud.google.com/_", "_.crossplane.io/_", "_.upbound.io/_", "grafana-org-operator.kubitus-project.gitlab.io/_", }, paths) }