Skip to content

Commit

Permalink
feat: random function args (#703)
Browse files Browse the repository at this point in the history
## Description:
random function args

## Is this change user facing?
YES


## References (if applicable):
<!-- Add relevant Github Issues, Discord threads, or other helpful
information. -->
  • Loading branch information
leoporoli committed Jun 12, 2023
1 parent 921f277 commit e650a20
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 14 deletions.
2 changes: 1 addition & 1 deletion core/server/api_container/server/api_container_service.go
Expand Up @@ -139,7 +139,7 @@ func (apicService ApiContainerService) RunStarlarkPackage(args *kurtosis_core_rp
packageId := args.GetPackageId()
parallelism := int(args.GetParallelism())
dryRun := shared_utils.GetOrDefaultBool(args.DryRun, defaultStartosisDryRun)
serializedParams := args.SerializedParams
serializedParams := args.GetSerializedParams()
relativePathToMainFile := args.GetRelativePathToMainFile()
mainFuncName := args.GetMainFunctionName()

Expand Down
Expand Up @@ -96,8 +96,13 @@ func (interpreter *StartosisInterpreter) Interpret(

logrus.Debugf("Successfully interpreted Starlark code into instruction queue: \n%s", instructionsQueue)

var isUsingDefaultMainFunction bool
var shouldInjectPlanArg bool
if mainFunctionName == "" {
mainFunctionName = runFunctionName
isUsingDefaultMainFunction = true
} else {
shouldInjectPlanArg = true //TODO now we inject the plan arg by default as the first argument on any random function, we have to make it optional and set by the users
}
if !globalVariables.Has(mainFunctionName) {
return missingMainFunctionReturnValue(packageId, mainFunctionName)
Expand All @@ -111,13 +116,14 @@ func (interpreter *StartosisInterpreter) Interpret(

runFunctionExecutionThread := newStarlarkThread(starlarkGoThreadName)

if mainFunction.NumParams() > maximumParamsAllowedForRunFunction {
if isUsingDefaultMainFunction && mainFunction.NumParams() > maximumParamsAllowedForRunFunction {
return "", nil, startosis_errors.NewInterpretationError("The 'run' entrypoint function can have at most '%v' argument got '%v'", maximumParamsAllowedForRunFunction, mainFunction.NumParams()).ToAPIType()
}

var argsTuple starlark.Tuple
var kwArgs []starlark.Tuple

if mainFunction.NumParams() >= minimumParamsRequiredForPlan {
if shouldInjectPlanArg || (isUsingDefaultMainFunction && mainFunction.NumParams() >= minimumParamsRequiredForPlan) {
if paramName, _ := mainFunction.Param(planParamIndex); paramName != planParamName {
return "", nil, startosis_errors.NewInterpretationError(unexpectedArgNameError, planParamIndex, planParamName, paramName).ToAPIType()
}
Expand All @@ -126,19 +132,33 @@ func (interpreter *StartosisInterpreter) Interpret(
argsTuple = append(argsTuple, planModule)
}

if mainFunction.NumParams() == paramsRequiredForArgs {
if paramName, _ := mainFunction.Param(argsParamIndex); paramName != argsParamName {
return "", nil, startosis_errors.NewInterpretationError(unexpectedArgNameError, argsParamIndex, argsParamName, paramName).ToAPIType()
mainFuncParamsNum := mainFunction.NumParams()

if (isUsingDefaultMainFunction && mainFuncParamsNum == paramsRequiredForArgs) ||
(!isUsingDefaultMainFunction && mainFuncParamsNum > 0) {
if isUsingDefaultMainFunction {
if paramName, _ := mainFunction.Param(argsParamIndex); paramName != argsParamName {
return "", nil, startosis_errors.NewInterpretationError(unexpectedArgNameError, argsParamIndex, argsParamName, paramName).ToAPIType()
}
}
// run function has an argument so we parse input args
inputArgs, interpretationError := interpreter.parseInputArgs(runFunctionExecutionThread, serializedJsonParams)
if interpretationError != nil {
return "", nil, interpretationError.ToAPIType()
}
argsTuple = append(argsTuple, inputArgs)
if isUsingDefaultMainFunction {
argsTuple = append(argsTuple, inputArgs)
kwArgs = noKwargs
} else {
argsDict, ok := inputArgs.(*starlark.Dict)
if !ok {
return "", nil, startosis_errors.NewInterpretationError("An error occurred casting input args '%s' to Starlark Dict", inputArgs).ToAPIType()
}
kwArgs = append(kwArgs, argsDict.Items()...)
}
}

outputObject, err := starlark.Call(runFunctionExecutionThread, mainFunction, argsTuple, noKwargs)
outputObject, err := starlark.Call(runFunctionExecutionThread, mainFunction, argsTuple, kwArgs)
if err != nil {
return "", nil, generateInterpretationError(err).ToAPIType()
}
Expand Down Expand Up @@ -284,7 +304,7 @@ func generateInterpretationError(err error) *startosis_errors.InterpretationErro
func missingMainFunctionReturnValue(packageId string, mainFunctionName string) (string, []kurtosis_instruction.KurtosisInstruction, *kurtosis_core_rpc_api_bindings.StarlarkInterpretationError) {
if packageId == startosis_constants.PackageIdPlaceholderForStandaloneScript {
return "", nil, startosis_errors.NewInterpretationError(
"No '%s' function found in the script; a '%s' entrypoint function with the signature `%s(plan, args)` or `%s()` is required in any Kurtosis script",
"No '%s' function found in the script; a '%s' entrypoint function with the signature `%s(plan, args)` or `%s()` is required in the Kurtosis script",
mainFunctionName,
mainFunctionName,
mainFunctionName,
Expand All @@ -293,7 +313,7 @@ func missingMainFunctionReturnValue(packageId string, mainFunctionName string) (
}

return "", nil, startosis_errors.NewInterpretationError(
"No '%s' function found in the main file of package '%s'; a '%s' entrypoint function with the signature `%s(plan, args)` or `%s()` is required in the main file of any Kurtosis package",
"No '%s' function found in the main file of package '%s'; a '%s' entrypoint function with the signature `%s(plan, args)` or `%s()` is required in the main file of the Kurtosis package",
mainFunctionName,
packageId,
mainFunctionName,
Expand Down
Expand Up @@ -58,6 +58,34 @@ def run(plan):
validateScriptOutputFromPrintInstructions(t, instructions, expectedOutput)
}

func TestStartosisInterpreter_RandomMainFunctionAndParams(t *testing.T) {

packageContentProvider := mock_package_content_provider.NewMockPackageContentProvider()
runtimeValueStore := runtime_value_store.NewRuntimeValueStore()
defer packageContentProvider.RemoveAll()
startosisInterpreter := NewStartosisInterpreter(testServiceNetwork, packageContentProvider, runtimeValueStore)
interpreter := startosisInterpreter
script := `
def deploy_contract(plan,service_name,contract_name,init_message,args):
plan.print("Service name: service_name")
plan.print("Contract name: contract_name")
plan.print("Init message: init_message")
return args["arg1"] + ":" + args["arg2"]
`
mainFunctionName := "deploy_contract"
inputArgs := `{"service_name": "my-service", "contract_name": "my-contract", "init_message": "Init message", "args": {"arg1": "arg1-value", "arg2": "arg2-value"}}`

result, instructions, interpretationError := interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, mainFunctionName, script, inputArgs)
require.Nil(t, interpretationError)
require.Len(t, instructions, 3) // The three print functions
require.NotNil(t, result)
expectedResult := "\"arg1-value:arg2-value\""
require.Equal(t, expectedResult, result)
expectedOutput := "Service name: service_name\nContract name: contract_name\nInit message: init_message\n"
validateScriptOutputFromPrintInstructions(t, instructions, expectedOutput)
}

func TestStartosisInterpreter_Test(t *testing.T) {
packageContentProvider := mock_package_content_provider.NewMockPackageContentProvider()
runtimeValueStore := runtime_value_store.NewRuntimeValueStore()
Expand Down
25 changes: 22 additions & 3 deletions docs/docs/cli-reference/run-starlark.md
Expand Up @@ -41,6 +41,25 @@ kurtosis run /path/to/package/on/your/machine '{"company":"Kurtosis"}'
kurtosis run github.com/package-author/package-repo '{"company":"Kurtosis"}'
```

:::info
If the flag `--main-function-name` is set, the JSON-serialized object will be used for passing the function arguments.

For example, if the main function signature (inside this file github.com/my-org/my-package/src/entry.star) has this shape:
```python
# the plan object is injected always as the first argument
def my_main_function(plan, first_argument, second_argument, their_argument):
# your code
```

It can be called like this:
```bash
# you don't have to pass the plan object as an argument because it will be automatically injected by default at the first position
kurtosis run main.star '{"first_argument": "Foo", "second_argument": "Bar", "their_argument": {"first-key:"first-value", "second-key":"second-value"}}' --main-file src/entry.star --main-function-name my_main_function
```

THIS IS A TEMPORARY OPTION AND IT WILL BE REMOVED SOON!!!
:::

This command has options available to customize its execution:

1. The `--dry-run` flag can be used to print the changes proposed by the script without executing them
Expand All @@ -53,15 +72,15 @@ This command has options available to customize its execution:

Example of using setting the --main-function-name flag

For example, to run the `start-node` function in a `main.star` file, simple use:
For example, to run the `start_node` function in a `main.star` file, simple use:
```bash
kurtosis run main.star --main-function-name start-node
kurtosis run main.star --main-function-name start_node
```

Where start-node is a function defined in `main.star` as so:
```python
# main.star code
def start-node(plan,args):
def start_node(plan,args):
# your code
```

Expand Down
Expand Up @@ -33,7 +33,7 @@ func TestStartosisPackage_NoMainInMainStar(t *testing.T) {

logrus.Infof("Starlark package path: \n%v", packageDirpath)

expectedInterpretationErr := "No 'run' function found in the main file of package 'github.com/sample/sample-kurtosis-package'; a 'run' entrypoint function with the signature `run(plan, args)` or `run()` is required in the main file of any Kurtosis package"
expectedInterpretationErr := "No 'run' function found in the main file of package 'github.com/sample/sample-kurtosis-package'; a 'run' entrypoint function with the signature `run(plan, args)` or `run()` is required in the main file of the Kurtosis package"
runResult, _ := enclaveCtx.RunStarlarkPackageBlocking(ctx, packageDirpath, useDefaultMainFile, useDefaultFunctionName, emptyRunParams, defaultDryRun, defaultParallelism)
require.NotNil(t, runResult.InterpretationError)
require.Contains(t, runResult.InterpretationError.GetErrorMessage(), expectedInterpretationErr)
Expand Down

0 comments on commit e650a20

Please sign in to comment.