2019-01-30 19:03:41 +00:00
package lua
import (
2023-06-23 18:45:53 +00:00
"bytes"
2019-01-30 19:03:41 +00:00
"context"
"encoding/json"
2024-06-18 02:41:51 +00:00
"errors"
2019-01-30 19:03:41 +00:00
"fmt"
2025-05-22 12:38:40 +00:00
"io/fs"
2019-01-30 19:03:41 +00:00
"os"
"path/filepath"
2024-06-18 02:41:51 +00:00
"reflect"
2025-05-22 12:38:40 +00:00
"slices"
"strings"
"sync"
2019-01-30 19:03:41 +00:00
"time"
2026-02-12 14:29:40 +00:00
"github.com/argoproj/argo-cd/gitops-engine/pkg/health"
2025-05-22 12:38:40 +00:00
glob "github.com/bmatcuk/doublestar/v4"
2019-02-25 18:51:24 +00:00
lua "github.com/yuin/gopher-lua"
2019-01-30 19:03:41 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2021-06-10 20:29:22 +00:00
"k8s.io/apimachinery/pkg/runtime/schema"
2019-01-30 19:03:41 +00:00
luajson "layeh.com/gopher-json"
2025-05-08 08:19:04 +00:00
applicationpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/application"
2025-01-10 21:14:00 +00:00
appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v3/resource_customizations"
2025-05-22 12:38:40 +00:00
argoglob "github.com/argoproj/argo-cd/v3/util/glob"
2019-01-30 19:03:41 +00:00
)
const (
2021-05-19 03:46:17 +00:00
incorrectReturnType = "expect %s output from Lua script, not %s"
invalidHealthStatus = "Lua returned an invalid health status"
healthScriptFile = "health.lua"
actionScriptFile = "action.lua"
actionDiscoveryScriptFile = "discovery.lua"
2019-01-30 19:03:41 +00:00
)
2025-05-22 12:38:40 +00:00
// errScriptDoesNotExist is an error type for when a built-in script does not exist.
var errScriptDoesNotExist = errors . New ( "built-in script does not exist" )
2024-10-17 13:28:41 +00:00
2020-05-15 17:01:18 +00:00
type ResourceHealthOverrides map [ string ] appv1 . ResourceOverride
func ( overrides ResourceHealthOverrides ) GetResourceHealth ( obj * unstructured . Unstructured ) ( * health . HealthStatus , error ) {
luaVM := VM {
ResourceOverrides : overrides ,
}
2021-05-04 06:25:51 +00:00
script , useOpenLibs , err := luaVM . GetHealthScript ( obj )
2020-05-15 17:01:18 +00:00
if err != nil {
return nil , err
}
if script == "" {
return nil , nil
}
2021-05-04 06:25:51 +00:00
// enable/disable the usage of lua standard library
luaVM . UseOpenLibs = useOpenLibs
2020-05-15 17:01:18 +00:00
result , err := luaVM . ExecuteHealthLua ( obj , script )
if err != nil {
return nil , err
}
return result , nil
}
2019-01-30 19:03:41 +00:00
// VM Defines a struct that implements the luaVM
type VM struct {
2019-04-02 20:59:55 +00:00
ResourceOverrides map [ string ] appv1 . ResourceOverride
2021-05-04 06:25:51 +00:00
// UseOpenLibs flag to enable open libraries. Libraries are disabled by default while running, but enabled during testing to allow the use of print statements
2019-01-30 19:03:41 +00:00
UseOpenLibs bool
}
func ( vm VM ) runLua ( obj * unstructured . Unstructured , script string ) ( * lua . LState , error ) {
2025-05-08 08:19:04 +00:00
return vm . runLuaWithResourceActionParameters ( obj , script , nil )
}
func ( vm VM ) runLuaWithResourceActionParameters ( obj * unstructured . Unstructured , script string , resourceActionParameters [ ] * applicationpkg . ResourceActionParameters ) ( * lua . LState , error ) {
2019-01-30 19:03:41 +00:00
l := lua . NewState ( lua . Options {
SkipOpenLibs : ! vm . UseOpenLibs ,
} )
defer l . Close ( )
2019-09-13 09:50:57 +00:00
// Opens table library to allow access to functions to manipulate tables
2019-01-30 19:03:41 +00:00
for _ , pair := range [ ] struct {
n string
f lua . LGFunction
} {
{ lua . LoadLibName , lua . OpenPackage } ,
{ lua . BaseLibName , lua . OpenBase } ,
{ lua . TabLibName , lua . OpenTable } ,
2021-07-13 17:02:03 +00:00
// load our 'safe' version of the OS library
2019-09-13 09:50:57 +00:00
{ lua . OsLibName , OpenSafeOs } ,
2019-01-30 19:03:41 +00:00
} {
if err := l . CallByParam ( lua . P {
Fn : l . NewFunction ( pair . f ) ,
NRet : 0 ,
Protect : true ,
} , lua . LString ( pair . n ) ) ; err != nil {
panic ( err )
}
}
2021-07-13 17:02:03 +00:00
// preload our 'safe' version of the OS library. Allows the 'local os = require("os")' to work
2019-09-13 09:50:57 +00:00
l . PreloadModule ( lua . OsLibName , SafeOsLoader )
2019-01-30 19:03:41 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , 1 * time . Second )
defer cancel ( )
l . SetContext ( ctx )
2025-05-08 08:19:04 +00:00
// Inject action parameters as a hash table global variable
actionParams := l . CreateTable ( 0 , len ( resourceActionParameters ) )
for _ , resourceActionParameter := range resourceActionParameters {
value := decodeValue ( l , resourceActionParameter . GetValue ( ) )
actionParams . RawSetH ( lua . LString ( resourceActionParameter . GetName ( ) ) , value )
}
l . SetGlobal ( "actionParams" , actionParams ) // Set the actionParams table as a global variable
2019-01-30 19:03:41 +00:00
objectValue := decodeValue ( l , obj . Object )
l . SetGlobal ( "obj" , objectValue )
err := l . DoString ( script )
2025-05-08 08:19:04 +00:00
// Remove the default lua stack trace from execution errors since these
// errors will make it back to the user
var apiErr * lua . ApiError
if errors . As ( err , & apiErr ) {
if apiErr . Type == lua . ApiErrorRun {
apiErr . StackTrace = ""
err = apiErr
}
}
2019-01-30 19:03:41 +00:00
return l , err
}
// ExecuteHealthLua runs the lua script to generate the health status of a resource
2020-05-15 17:01:18 +00:00
func ( vm VM ) ExecuteHealthLua ( obj * unstructured . Unstructured , script string ) ( * health . HealthStatus , error ) {
2019-01-30 19:03:41 +00:00
l , err := vm . runLua ( obj , script )
if err != nil {
return nil , err
}
returnValue := l . Get ( - 1 )
if returnValue . Type ( ) == lua . LTTable {
jsonBytes , err := luajson . Encode ( returnValue )
if err != nil {
return nil , err
}
2020-05-15 17:01:18 +00:00
healthStatus := & health . HealthStatus { }
2019-01-30 19:03:41 +00:00
err = json . Unmarshal ( jsonBytes , healthStatus )
if err != nil {
2024-06-18 02:41:51 +00:00
// Validate if the error is caused by an empty object
2026-02-09 10:44:05 +00:00
typeError := & json . UnmarshalTypeError { Value : "array" , Type : reflect . TypeFor [ * health . HealthStatus ] ( ) }
2024-06-18 02:41:51 +00:00
if errors . As ( err , & typeError ) {
return & health . HealthStatus { } , nil
}
2019-01-30 19:03:41 +00:00
return nil , err
}
if ! isValidHealthStatusCode ( healthStatus . Status ) {
2020-05-15 17:01:18 +00:00
return & health . HealthStatus {
Status : health . HealthStatusUnknown ,
2019-01-30 19:03:41 +00:00
Message : invalidHealthStatus ,
} , nil
}
return healthStatus , nil
2024-06-18 02:41:51 +00:00
} else if returnValue . Type ( ) == lua . LTNil {
return & health . HealthStatus { } , nil
2019-01-30 19:03:41 +00:00
}
2019-04-16 21:50:44 +00:00
return nil , fmt . Errorf ( incorrectReturnType , "table" , returnValue . Type ( ) . String ( ) )
2019-01-30 19:03:41 +00:00
}
2024-10-17 13:28:41 +00:00
// GetHealthScript attempts to read lua script from config and then filesystem for that resource. If none exists, return
// an empty string.
func ( vm VM ) GetHealthScript ( obj * unstructured . Unstructured ) ( script string , useOpenLibs bool , err error ) {
2022-11-08 13:42:08 +00:00
// first, search the gvk as is in the ResourceOverrides
2021-06-10 20:29:22 +00:00
key := GetConfigMapKey ( obj . GroupVersionKind ( ) )
2022-11-08 13:42:08 +00:00
2019-01-30 19:03:41 +00:00
if script , ok := vm . ResourceOverrides [ key ] ; ok && script . HealthLua != "" {
2021-05-04 06:25:51 +00:00
return script . HealthLua , script . UseOpenLibs , nil
2019-01-30 19:03:41 +00:00
}
2022-11-08 13:42:08 +00:00
2024-10-04 19:34:58 +00:00
// if not found as is, perhaps it matches a wildcard entry in the configmap
getWildcardHealthOverride , useOpenLibs := getWildcardHealthOverrideLua ( vm . ResourceOverrides , obj . GroupVersionKind ( ) )
2022-11-08 13:42:08 +00:00
2024-10-04 19:34:58 +00:00
if getWildcardHealthOverride != "" {
return getWildcardHealthOverride , useOpenLibs , nil
2022-11-08 13:42:08 +00:00
}
// if not found in the ResourceOverrides at all, search it as is in the built-in scripts
// (as built-in scripts are files in folders, named after the GVK, currently there is no wildcard support for them)
2021-05-04 06:25:51 +00:00
builtInScript , err := vm . getPredefinedLuaScripts ( key , healthScriptFile )
2024-10-17 13:28:41 +00:00
if err != nil {
2025-05-22 12:38:40 +00:00
if errors . Is ( err , errScriptDoesNotExist ) {
// Try to find a wildcard built-in health script
builtInScript , err = getWildcardBuiltInHealthOverrideLua ( key )
if err != nil {
return "" , false , fmt . Errorf ( "error while fetching built-in health script: %w" , err )
}
if builtInScript != "" {
return builtInScript , true , nil
}
2024-10-17 13:28:41 +00:00
// It's okay if no built-in health script exists. Just return an empty string and let the caller handle it.
return "" , false , nil
}
return "" , false , err
}
2021-05-04 06:25:51 +00:00
// standard libraries will be enabled for all built-in scripts
return builtInScript , true , err
2019-04-16 21:50:44 +00:00
}
2025-05-08 08:19:04 +00:00
func ( vm VM ) ExecuteResourceAction ( obj * unstructured . Unstructured , script string , resourceActionParameters [ ] * applicationpkg . ResourceActionParameters ) ( [ ] ImpactedResource , error ) {
l , err := vm . runLuaWithResourceActionParameters ( obj , script , resourceActionParameters )
2019-04-16 21:50:44 +00:00
if err != nil {
return nil , err
}
returnValue := l . Get ( - 1 )
if returnValue . Type ( ) == lua . LTTable {
jsonBytes , err := luajson . Encode ( returnValue )
if err != nil {
return nil , err
}
2023-06-23 18:45:53 +00:00
var impactedResources [ ] ImpactedResource
jsonString := bytes . NewBuffer ( jsonBytes ) . String ( )
2025-03-27 16:37:52 +00:00
// nolint:staticcheck // Lua is fine to be capitalized.
2023-06-23 18:45:53 +00:00
if len ( jsonString ) < 2 {
2024-12-30 08:56:41 +00:00
return nil , errors . New ( "Lua output was not a valid json object or array" )
2023-06-23 18:45:53 +00:00
}
// The output from Lua is either an object (old-style action output) or an array (new-style action output).
// Check whether the string starts with an opening square bracket and ends with a closing square bracket,
// avoiding programming by exception.
if jsonString [ 0 ] == '[' && jsonString [ len ( jsonString ) - 1 ] == ']' {
// The string represents a new-style action array output
impactedResources , err = UnmarshalToImpactedResources ( string ( jsonBytes ) )
if err != nil {
return nil , err
}
} else {
// The string represents an old-style action object output
newObj , err := appv1 . UnmarshalToUnstructured ( string ( jsonBytes ) )
if err != nil {
return nil , err
}
// Wrap the old-style action output with a single-member array.
// The default definition of the old-style action is a "patch" one.
impactedResources = append ( impactedResources , ImpactedResource { newObj , PatchOperation } )
}
for _ , impactedResource := range impactedResources {
// Cleaning the resource is only relevant to "patch"
if impactedResource . K8SOperation == PatchOperation {
impactedResource . UnstructuredObj . Object = cleanReturnedObj ( impactedResource . UnstructuredObj . Object , obj . Object )
}
2019-04-16 21:50:44 +00:00
}
2023-06-23 18:45:53 +00:00
return impactedResources , nil
2019-04-16 21:50:44 +00:00
}
return nil , fmt . Errorf ( incorrectReturnType , "table" , returnValue . Type ( ) . String ( ) )
}
2023-06-23 18:45:53 +00:00
// UnmarshalToImpactedResources unmarshals an ImpactedResource array representation in JSON to ImpactedResource array
func UnmarshalToImpactedResources ( resources string ) ( [ ] ImpactedResource , error ) {
if resources == "" || resources == "null" {
return nil , nil
}
var impactedResources [ ] ImpactedResource
err := json . Unmarshal ( [ ] byte ( resources ) , & impactedResources )
if err != nil {
return nil , err
}
return impactedResources , nil
}
2019-04-16 21:50:44 +00:00
// cleanReturnedObj Lua cannot distinguish an empty table as an array or map, and the library we are using choose to
// decoded an empty table into an empty array. This function prevents the lua scripts from unintentionally changing an
// empty struct into empty arrays
2025-01-02 23:26:59 +00:00
func cleanReturnedObj ( newObj , obj map [ string ] any ) map [ string ] any {
2019-04-16 21:50:44 +00:00
mapToReturn := newObj
for key := range obj {
if newValueInterface , ok := newObj [ key ] ; ok {
oldValueInterface , ok := obj [ key ]
if ! ok {
continue
}
switch newValue := newValueInterface . ( type ) {
2025-01-02 23:26:59 +00:00
case map [ string ] any :
if oldValue , ok := oldValueInterface . ( map [ string ] any ) ; ok {
2019-04-16 21:50:44 +00:00
convertedMap := cleanReturnedObj ( newValue , oldValue )
mapToReturn [ key ] = convertedMap
}
2025-01-02 23:26:59 +00:00
case [ ] any :
2019-04-16 21:50:44 +00:00
switch oldValue := oldValueInterface . ( type ) {
2025-01-02 23:26:59 +00:00
case map [ string ] any :
2019-04-16 21:50:44 +00:00
if len ( newValue ) == 0 {
2025-08-13 20:08:24 +00:00
// Lua incorrectly decoded the empty object as an empty array, so set it to an empty object
mapToReturn [ key ] = map [ string ] any { }
2019-04-16 21:50:44 +00:00
}
2025-01-02 23:26:59 +00:00
case [ ] any :
2019-04-16 21:50:44 +00:00
newArray := cleanReturnedArray ( newValue , oldValue )
mapToReturn [ key ] = newArray
}
}
}
}
return mapToReturn
}
// cleanReturnedArray allows Argo CD to recurse into nested arrays when checking for unintentional empty struct to
// empty array conversions.
2025-01-02 23:26:59 +00:00
func cleanReturnedArray ( newObj , obj [ ] any ) [ ] any {
2019-04-16 21:50:44 +00:00
arrayToReturn := newObj
for i := range newObj {
2025-08-13 20:08:24 +00:00
if i >= len ( obj ) {
// If the new object is longer than the old one, we added an item to the array
break
}
2019-04-16 21:50:44 +00:00
switch newValue := newObj [ i ] . ( type ) {
2025-01-02 23:26:59 +00:00
case map [ string ] any :
if oldValue , ok := obj [ i ] . ( map [ string ] any ) ; ok {
2019-04-16 21:50:44 +00:00
convertedMap := cleanReturnedObj ( newValue , oldValue )
arrayToReturn [ i ] = convertedMap
}
2025-01-02 23:26:59 +00:00
case [ ] any :
if oldValue , ok := obj [ i ] . ( [ ] any ) ; ok {
2019-04-16 21:50:44 +00:00
convertedMap := cleanReturnedArray ( newValue , oldValue )
arrayToReturn [ i ] = convertedMap
}
}
}
return arrayToReturn
}
2024-09-10 18:57:29 +00:00
func ( vm VM ) ExecuteResourceActionDiscovery ( obj * unstructured . Unstructured , scripts [ ] string ) ( [ ] appv1 . ResourceAction , error ) {
if len ( scripts ) == 0 {
2024-12-30 08:56:41 +00:00
return nil , errors . New ( "no action discovery script provided" )
2019-04-16 21:50:44 +00:00
}
2024-09-10 18:57:29 +00:00
availableActionsMap := make ( map [ string ] appv1 . ResourceAction )
for _ , script := range scripts {
l , err := vm . runLua ( obj , script )
2019-04-16 21:50:44 +00:00
if err != nil {
return nil , err
}
2024-09-10 18:57:29 +00:00
returnValue := l . Get ( - 1 )
2025-01-08 20:26:02 +00:00
if returnValue . Type ( ) != lua . LTTable {
return nil , fmt . Errorf ( incorrectReturnType , "table" , returnValue . Type ( ) . String ( ) )
}
jsonBytes , err := luajson . Encode ( returnValue )
if err != nil {
return nil , fmt . Errorf ( "error in converting to lua table: %w" , err )
}
if noAvailableActions ( jsonBytes ) {
continue
}
actionsMap := make ( map [ string ] any )
err = json . Unmarshal ( jsonBytes , & actionsMap )
if err != nil {
return nil , fmt . Errorf ( "error unmarshaling action table: %w" , err )
}
for key , value := range actionsMap {
resourceAction := appv1 . ResourceAction { Name : key , Disabled : isActionDisabled ( value ) }
if _ , exist := availableActionsMap [ key ] ; exist {
continue
2024-09-10 18:57:29 +00:00
}
2025-01-08 20:26:02 +00:00
if emptyResourceActionFromLua ( value ) {
availableActionsMap [ key ] = resourceAction
2019-04-16 21:50:44 +00:00
continue
}
2025-01-08 20:26:02 +00:00
resourceActionBytes , err := json . Marshal ( value )
2019-04-16 21:50:44 +00:00
if err != nil {
2025-01-08 20:26:02 +00:00
return nil , fmt . Errorf ( "error marshaling resource action: %w" , err )
2019-04-16 21:50:44 +00:00
}
2025-01-08 20:26:02 +00:00
err = json . Unmarshal ( resourceActionBytes , & resourceAction )
if err != nil {
return nil , fmt . Errorf ( "error unmarshaling resource action: %w" , err )
2019-04-16 21:50:44 +00:00
}
2025-01-08 20:26:02 +00:00
availableActionsMap [ key ] = resourceAction
2019-04-16 21:50:44 +00:00
}
}
2024-09-10 18:57:29 +00:00
availableActions := make ( [ ] appv1 . ResourceAction , 0 , len ( availableActionsMap ) )
for _ , action := range availableActionsMap {
availableActions = append ( availableActions , action )
}
return availableActions , nil
2019-04-16 21:50:44 +00:00
}
2019-10-11 03:34:40 +00:00
// Actions are enabled by default
2025-01-02 23:26:59 +00:00
func isActionDisabled ( actionsMap any ) bool {
actions , ok := actionsMap . ( map [ string ] any )
2019-10-04 00:11:42 +00:00
if ! ok {
return false
}
for key , val := range actions {
2025-01-07 14:56:38 +00:00
if vv , ok := val . ( bool ) ; ok {
2019-10-11 03:34:40 +00:00
if key == "disabled" {
2019-10-04 00:11:42 +00:00
return vv
}
}
}
return false
}
2025-01-02 23:26:59 +00:00
func emptyResourceActionFromLua ( i any ) bool {
_ , ok := i . ( [ ] any )
2019-04-16 21:50:44 +00:00
return ok
}
func noAvailableActions ( jsonBytes [ ] byte ) bool {
// When the Lua script returns an empty table, it is decoded as a empty array.
return string ( jsonBytes ) == "[]"
}
2024-09-10 18:57:29 +00:00
func ( vm VM ) GetResourceActionDiscovery ( obj * unstructured . Unstructured ) ( [ ] string , error ) {
2021-06-10 20:29:22 +00:00
key := GetConfigMapKey ( obj . GroupVersionKind ( ) )
2024-09-10 18:57:29 +00:00
var discoveryScripts [ ] string
// Check if there are resource overrides for the given key
2019-04-16 21:50:44 +00:00
override , ok := vm . ResourceOverrides [ key ]
2019-04-19 17:27:12 +00:00
if ok && override . Actions != "" {
actions , err := override . GetActions ( )
if err != nil {
2024-09-10 18:57:29 +00:00
return nil , err
}
// Append the action discovery Lua script if built-in actions are to be included
2025-01-08 20:26:02 +00:00
if ! actions . MergeBuiltinActions {
2024-09-10 18:57:29 +00:00
return [ ] string { actions . ActionDiscoveryLua } , nil
2019-04-19 17:27:12 +00:00
}
2025-01-08 20:26:02 +00:00
discoveryScripts = append ( discoveryScripts , actions . ActionDiscoveryLua )
2019-04-16 21:50:44 +00:00
}
2024-09-10 18:57:29 +00:00
// Fetch predefined Lua scripts
2024-12-20 16:22:28 +00:00
discoveryKey := key + "/actions/"
2019-04-16 21:50:44 +00:00
discoveryScript , err := vm . getPredefinedLuaScripts ( discoveryKey , actionDiscoveryScriptFile )
2025-03-04 19:25:36 +00:00
if err != nil {
2025-05-22 12:38:40 +00:00
if errors . Is ( err , errScriptDoesNotExist ) {
2025-03-04 19:25:36 +00:00
// No worries, just return what we have.
return discoveryScripts , nil
}
2024-09-10 18:57:29 +00:00
return nil , fmt . Errorf ( "error while fetching predefined lua scripts: %w" , err )
2019-04-16 21:50:44 +00:00
}
2024-09-10 18:57:29 +00:00
discoveryScripts = append ( discoveryScripts , discoveryScript )
return discoveryScripts , nil
2019-04-16 21:50:44 +00:00
}
// GetResourceAction attempts to read lua script from config and then filesystem for that resource
func ( vm VM ) GetResourceAction ( obj * unstructured . Unstructured , actionName string ) ( appv1 . ResourceActionDefinition , error ) {
2021-06-10 20:29:22 +00:00
key := GetConfigMapKey ( obj . GroupVersionKind ( ) )
2019-04-16 21:50:44 +00:00
override , ok := vm . ResourceOverrides [ key ]
2019-04-19 17:27:12 +00:00
if ok && override . Actions != "" {
actions , err := override . GetActions ( )
if err != nil {
return appv1 . ResourceActionDefinition { } , err
}
for _ , action := range actions . Definitions {
2019-04-16 21:50:44 +00:00
if action . Name == actionName {
return action , nil
}
}
}
actionKey := fmt . Sprintf ( "%s/actions/%s" , key , actionName )
actionScript , err := vm . getPredefinedLuaScripts ( actionKey , actionScriptFile )
if err != nil {
return appv1 . ResourceActionDefinition { } , err
}
return appv1 . ResourceActionDefinition {
Name : actionName ,
ActionLua : actionScript ,
} , nil
2019-01-30 19:03:41 +00:00
}
2021-06-10 20:29:22 +00:00
func GetConfigMapKey ( gvk schema . GroupVersionKind ) string {
2019-01-30 19:03:41 +00:00
if gvk . Group == "" {
return gvk . Kind
}
return fmt . Sprintf ( "%s/%s" , gvk . Group , gvk . Kind )
}
2024-10-04 19:34:58 +00:00
// getWildcardHealthOverrideLua returns the first encountered resource override which matches the wildcard and has a
// non-empty health script. Having multiple wildcards with non-empty health checks that can match the GVK is
// non-deterministic.
func getWildcardHealthOverrideLua ( overrides map [ string ] appv1 . ResourceOverride , gvk schema . GroupVersionKind ) ( string , bool ) {
2022-11-08 13:42:08 +00:00
gvkKeyToMatch := GetConfigMapKey ( gvk )
2024-10-04 19:34:58 +00:00
for key , override := range overrides {
2025-05-22 12:38:40 +00:00
if argoglob . Match ( key , gvkKeyToMatch ) && override . HealthLua != "" {
2024-10-04 19:34:58 +00:00
return override . HealthLua , override . UseOpenLibs
2022-11-08 13:42:08 +00:00
}
}
2024-10-04 19:34:58 +00:00
return "" , false
2022-11-08 13:42:08 +00:00
}
2019-04-16 21:50:44 +00:00
func ( vm VM ) getPredefinedLuaScripts ( objKey string , scriptFile string ) ( string , error ) {
2021-05-19 03:46:17 +00:00
data , err := resource_customizations . Embedded . ReadFile ( filepath . Join ( objKey , scriptFile ) )
2019-01-30 19:03:41 +00:00
if err != nil {
if os . IsNotExist ( err ) {
2025-05-22 12:38:40 +00:00
return "" , errScriptDoesNotExist
2019-01-30 19:03:41 +00:00
}
return "" , err
}
return string ( data ) , nil
}
2025-05-22 12:38:40 +00:00
// globHealthScriptPathsOnce is a sync.Once instance to ensure that the globHealthScriptPaths are only initialized once.
// The globs come from an embedded filesystem, so it won't change at runtime.
var globHealthScriptPathsOnce sync . Once
// globHealthScriptPaths is a cache for the glob patterns of directories containing health.lua files. Don't use this
// directly, use getGlobHealthScriptPaths() instead.
var globHealthScriptPaths [ ] string
// getGlobHealthScriptPaths returns the paths of the directories containing health.lua files where the path contains a
// glob pattern. It uses a sync.Once to ensure that the paths are only initialized once.
func getGlobHealthScriptPaths ( ) ( [ ] string , error ) {
var err error
globHealthScriptPathsOnce . Do ( func ( ) {
// Walk through the embedded filesystem and get the directory names of all directories containing a health.lua.
var patterns [ ] string
err = fs . WalkDir ( resource_customizations . Embedded , "." , func ( path string , d fs . DirEntry , err error ) error {
if err != nil {
return fmt . Errorf ( "error walking path %q: %w" , path , err )
}
// Skip non-directories at the top level
if d . IsDir ( ) && filepath . Dir ( path ) == "." {
return nil
}
// Check if the directory contains a health.lua file
if filepath . Base ( path ) != healthScriptFile {
return nil
}
groupKindPath := filepath . Dir ( path )
// Check if the path contains a wildcard. If it doesn't, skip it.
if ! strings . Contains ( groupKindPath , "_" ) {
return nil
}
pattern := strings . ReplaceAll ( groupKindPath , "_" , "*" )
// Check that the pattern is valid.
if ! glob . ValidatePattern ( pattern ) {
return fmt . Errorf ( "invalid glob pattern %q: %w" , pattern , err )
}
patterns = append ( patterns , groupKindPath )
return nil
} )
if err != nil {
return
}
// Sort the patterns to ensure deterministic choice of wildcard directory for a given GK.
slices . Sort ( patterns )
globHealthScriptPaths = patterns
} )
if err != nil {
return nil , fmt . Errorf ( "error getting health script glob directories: %w" , err )
}
return globHealthScriptPaths , nil
}
func getWildcardBuiltInHealthOverrideLua ( objKey string ) ( string , error ) {
// Check if the GVK matches any of the wildcard directories
globs , err := getGlobHealthScriptPaths ( )
if err != nil {
return "" , fmt . Errorf ( "error getting health script globs: %w" , err )
}
for _ , g := range globs {
pattern := strings . ReplaceAll ( g , "_" , "*" )
if ! glob . PathMatchUnvalidated ( pattern , objKey ) {
continue
}
var script [ ] byte
script , err = resource_customizations . Embedded . ReadFile ( filepath . Join ( g , healthScriptFile ) )
if err != nil {
return "" , fmt . Errorf ( "error reading %q file in embedded filesystem: %w" , filepath . Join ( objKey , healthScriptFile ) , err )
}
return string ( script ) , nil
}
return "" , nil
}
2020-05-15 17:01:18 +00:00
func isValidHealthStatusCode ( statusCode health . HealthStatusCode ) bool {
2019-01-30 19:03:41 +00:00
switch statusCode {
2020-05-15 17:01:18 +00:00
case health . HealthStatusUnknown , health . HealthStatusProgressing , health . HealthStatusSuspended , health . HealthStatusHealthy , health . HealthStatusDegraded , health . HealthStatusMissing :
2019-01-30 19:03:41 +00:00
return true
}
return false
}
// Took logic from the link below and added the int, int32, and int64 types since the value would have type int64
// while actually running in the controller and it was not reproducible through testing.
// https://github.com/layeh/gopher-json/blob/97fed8db84274c421dbfffbb28ec859901556b97/json.go#L154
2025-01-02 23:26:59 +00:00
func decodeValue ( l * lua . LState , value any ) lua . LValue {
2019-01-30 19:03:41 +00:00
switch converted := value . ( type ) {
case bool :
return lua . LBool ( converted )
case float64 :
return lua . LNumber ( converted )
case string :
return lua . LString ( converted )
case json . Number :
return lua . LString ( converted )
case int :
return lua . LNumber ( converted )
case int32 :
return lua . LNumber ( converted )
case int64 :
return lua . LNumber ( converted )
2025-01-02 23:26:59 +00:00
case [ ] any :
2024-06-13 19:10:00 +00:00
arr := l . CreateTable ( len ( converted ) , 0 )
2019-01-30 19:03:41 +00:00
for _ , item := range converted {
2024-06-13 19:10:00 +00:00
arr . Append ( decodeValue ( l , item ) )
2019-01-30 19:03:41 +00:00
}
return arr
2025-01-02 23:26:59 +00:00
case map [ string ] any :
2024-06-13 19:10:00 +00:00
tbl := l . CreateTable ( 0 , len ( converted ) )
2019-01-30 19:03:41 +00:00
for key , item := range converted {
2024-06-13 19:10:00 +00:00
tbl . RawSetH ( lua . LString ( key ) , decodeValue ( l , item ) )
2019-01-30 19:03:41 +00:00
}
return tbl
case nil :
return lua . LNil
}
return lua . LNil
}