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  }