Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add starlark.Value serializer/deserializer for enclave persistence #1229

Merged
merged 1 commit into from Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -19,16 +19,29 @@ import (
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/upload_files"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/wait"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/directory"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/port_spec"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/service_config"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/recipe"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/runtime_value_store"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_errors"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages"
starlarkjson "go.starlark.net/lib/json"
"go.starlark.net/lib/time"
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
)

func Predeclared() starlark.StringDict {
return starlark.StringDict{
// go-starlark add-ons
starlarkjson.Module.Name: starlarkjson.Module,
starlarkstruct.Default.GoString(): starlark.NewBuiltin(starlarkstruct.Default.GoString(), starlarkstruct.Make), // extension to build struct in starlark
time.Module.Name: time.Module,
}
}

// KurtosisPlanInstructions returns the entire list of KurtosisPlanInstruction.
//
// A KurtosisPlanInstruction is a builtin that adds an operation to the plan.
Expand Down Expand Up @@ -80,6 +93,7 @@ func KurtosisHelpers(recursiveInterpret func(moduleId string, scriptContent stri
// Example: ServiceConfig, PortSpec, etc.
func KurtosisTypeConstructors() []*starlark.Builtin {
return []*starlark.Builtin{
starlark.NewBuiltin(kurtosis_types.ServiceTypeName, kurtosis_types.NewServiceType().CreateBuiltin()),
starlark.NewBuiltin(directory.DirectoryTypeName, directory.NewDirectoryType().CreateBuiltin()),
starlark.NewBuiltin(recipe.ExecRecipeTypeName, recipe.NewExecRecipeType().CreateBuiltin()),
starlark.NewBuiltin(recipe.GetHttpRecipeTypeName, recipe.NewGetHttpRequestRecipeType().CreateBuiltin()),
Expand Down
Expand Up @@ -58,7 +58,10 @@ func makeAddServiceInterpretationReturnValue(serviceName starlark.String, servic
}
ipAddress := starlark.String(fmt.Sprintf(magic_string_helper.RuntimeValueReplacementPlaceholderFormat, resultUuid, ipAddressRuntimeValue))
hostname := starlark.String(fmt.Sprintf(magic_string_helper.RuntimeValueReplacementPlaceholderFormat, resultUuid, hostnameRuntimeValue))
returnValue := kurtosis_types.NewService(serviceName, hostname, ipAddress, portSpecsDict)
returnValue, interpretationErr := kurtosis_types.CreateService(serviceName, hostname, ipAddress, portSpecsDict)
if interpretationErr != nil {
return nil, interpretationErr
}
return returnValue, nil
}

Expand Down
Expand Up @@ -87,7 +87,7 @@ func (t *addServiceTestCase) Assert(interpretationResult starlark.Value, executi
serviceObj, ok := interpretationResult.(*kurtosis_types.Service)
require.True(t, ok, "interpretation result should be a dictionary")
require.NotNil(t, serviceObj)
expectedServiceObj := fmt.Sprintf(`Service\(hostname = "{{kurtosis:[0-9a-f]{32}:hostname.runtime_value}}", ip_address = "{{kurtosis:[0-9a-f]{32}:ip_address.runtime_value}}", name = "%v", ports = {}\)`, TestServiceName)
expectedServiceObj := fmt.Sprintf(`Service\(name="%v", hostname="{{kurtosis:[0-9a-f]{32}:hostname.runtime_value}}", ip_address="{{kurtosis:[0-9a-f]{32}:ip_address.runtime_value}}", ports={}\)`, TestServiceName)
require.Regexp(t, expectedServiceObj, serviceObj.String())

expectedExecutionResult := fmt.Sprintf("Service '%s' added with service UUID '%s'", TestServiceName, TestServiceUuid)
Expand Down
@@ -0,0 +1,59 @@
package test_engine

import (
"fmt"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types"
"github.com/stretchr/testify/require"
"testing"
)

const (
testServiceIpAddress = "192.168.0.43"
testServiceHostname = "test-service-hostname"
testServicePorts = "{}"
)

type serviceTestCase struct {
*testing.T
}

func newServiceTestCase(t *testing.T) *serviceTestCase {
return &serviceTestCase{
T: t,
}
}

func (t serviceTestCase) GetId() string {
return kurtosis_types.ServiceTypeName
}

func (t serviceTestCase) GetStarlarkCode() string {
return fmt.Sprintf("%s(%s=%q, %s=%q, %s=%q, %s=%s)",
kurtosis_types.ServiceTypeName,
kurtosis_types.ServiceNameAttr, TestServiceName,
kurtosis_types.HostnameAttr, testServiceHostname,
kurtosis_types.IpAddressAttr, testServiceIpAddress,
kurtosis_types.PortsAttr, testServicePorts)
}

func (t serviceTestCase) Assert(typeValue builtin_argument.KurtosisValueType) {
serviceStarlark, ok := typeValue.(*kurtosis_types.Service)
require.True(t, ok)

resultServiceName, interpretationErr := serviceStarlark.GetName()
require.Nil(t, interpretationErr)
require.Equal(t, TestServiceName, resultServiceName)

resultServiceHostname, interpretationErr := serviceStarlark.GetHostname()
require.Nil(t, interpretationErr)
require.Equal(t, testServiceHostname, resultServiceHostname)

resultServiceIpAddress, interpretationErr := serviceStarlark.GetIpAddress()
require.Nil(t, interpretationErr)
require.Equal(t, testServiceIpAddress, resultServiceIpAddress)

resultPorts, interpretationErr := serviceStarlark.GetPorts()
require.Nil(t, interpretationErr)
require.Equal(t, 0, resultPorts.Len())
}
Expand Up @@ -4,18 +4,14 @@ import (
"context"
"fmt"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/builtins"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/enclave_structure"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/instructions_plan"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/instructions_plan/resolver"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_constants"
"github.com/stretchr/testify/require"
starlarkjson "go.starlark.net/lib/json"
"go.starlark.net/lib/time"
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
"reflect"
"testing"
)
Expand Down Expand Up @@ -61,6 +57,7 @@ func TestAllRegisteredBuiltins(t *testing.T) {
testKurtosisTypeConstructor(t, newPostHttpRequestRecipeMinimalTestCase(t))
testKurtosisTypeConstructor(t, newServiceConfigMinimalTestCase(t))
testKurtosisTypeConstructor(t, newServiceConfigFullTestCase(t))
testKurtosisTypeConstructor(t, newServiceTestCase(t))
testKurtosisTypeConstructor(t, newServiceConfigFullTestCaseBackwardCompatible(t))
testKurtosisTypeConstructor(t, newReadyConditionsHttpRecipeTestCase(t))
testKurtosisTypeConstructor(t, newReadyConditionsExecRecipeTestCase(t))
Expand Down Expand Up @@ -150,18 +147,8 @@ func testKurtosisTypeConstructor(t *testing.T, builtin KurtosisTypeConstructorBa
}

func getBasePredeclaredDict(t *testing.T, thread *starlark.Thread) starlark.StringDict {
kurtosisModule, err := builtins.KurtosisModule(thread, "", "")
require.Nil(t, err)
// TODO: refactor this with the one we have in the interpreter
predeclared := starlark.StringDict{
// go-starlark add-ons
starlarkjson.Module.Name: starlarkjson.Module,
starlarkstruct.Default.GoString(): starlark.NewBuiltin(starlarkstruct.Default.GoString(), starlarkstruct.Make), // extension to build struct in starlark
time.Module.Name: time.Module,

// Kurtosis pre-built module containing Kurtosis constant types
builtins.KurtosisModuleName: kurtosisModule,
}
predeclared := startosis_engine.Predeclared()
gbouv marked this conversation as resolved.
Show resolved Hide resolved

// Add all Kurtosis types
for _, kurtosisTypeConstructor := range startosis_engine.KurtosisTypeConstructors() {
predeclared[kurtosisTypeConstructor.Name()] = kurtosisTypeConstructor
Expand Down
@@ -1,50 +1,143 @@
package kurtosis_types

import (
"fmt"
"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_type_constructor"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_errors"
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
"strings"
)

const (
serviceTypeName = "Service"
ServiceTypeName = "Service"

hostnameAttr = "hostname"
ipAddressAttr = "ip_address"
portsAttr = "ports"
serviceNameAttr = "name"
HostnameAttr = "hostname"
IpAddressAttr = "ip_address"
PortsAttr = "ports"
ServiceNameAttr = "name"
)

// Service is just a wrapper around a regular starlarkstruct.Struct
// It naturally inherits all its function making it a valid starlark.Value
func NewServiceType() *kurtosis_type_constructor.KurtosisTypeConstructor {
return &kurtosis_type_constructor.KurtosisTypeConstructor{
KurtosisBaseBuiltin: &kurtosis_starlark_framework.KurtosisBaseBuiltin{
Name: ServiceTypeName,

Arguments: []*builtin_argument.BuiltinArgument{
{
Name: ServiceNameAttr,
IsOptional: false,
ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.String],
Validator: func(value starlark.Value) *startosis_errors.InterpretationError {
return builtin_argument.NonEmptyString(value, ServiceNameAttr)
},
},
{
Name: HostnameAttr,
IsOptional: false,
ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.String],
Validator: func(value starlark.Value) *startosis_errors.InterpretationError {
return builtin_argument.NonEmptyString(value, HostnameAttr)
},
},
{
Name: IpAddressAttr,
IsOptional: false,
ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.String],
Validator: func(value starlark.Value) *startosis_errors.InterpretationError {
return builtin_argument.NonEmptyString(value, IpAddressAttr)
},
},
{
Name: PortsAttr,
IsOptional: false,
ZeroValueProvider: builtin_argument.ZeroValueProvider[*starlark.Dict],
Validator: func(value starlark.Value) *startosis_errors.InterpretationError {
return nil
},
},
},
},

Instantiate: instantiate,
}
}

func instantiate(arguments *builtin_argument.ArgumentValuesSet) (builtin_argument.KurtosisValueType, *startosis_errors.InterpretationError) {
kurtosisValueType, interpretationErr := kurtosis_type_constructor.CreateKurtosisStarlarkTypeDefault(ServiceTypeName, arguments)
if interpretationErr != nil {
return nil, interpretationErr
}
return &Service{
KurtosisValueTypeDefault: kurtosisValueType,
}, nil
}

type Service struct {
*starlarkstruct.Struct
*kurtosis_type_constructor.KurtosisValueTypeDefault
}

func CreateService(serviceName starlark.String, hostname starlark.String, ipAddress starlark.String, ports *starlark.Dict) (*Service, *startosis_errors.InterpretationError) {
args := []starlark.Value{
serviceName,
hostname,
ipAddress,
ports,
}

argumentDefinitions := NewServiceType().KurtosisBaseBuiltin.Arguments
argumentValuesSet := builtin_argument.NewArgumentValuesSet(argumentDefinitions, args)
kurtosisDefaultValue, interpretationErr := kurtosis_type_constructor.CreateKurtosisStarlarkTypeDefault(ServiceTypeName, argumentValuesSet)
if interpretationErr != nil {
return nil, interpretationErr
}
return &Service{
KurtosisValueTypeDefault: kurtosisDefaultValue,
}, nil
}

func NewService(serviceName starlark.String, hostname starlark.String, ipAddress starlark.String, ports *starlark.Dict) *Service {
structDict := starlark.StringDict{
serviceNameAttr: serviceName,
hostnameAttr: hostname,
ipAddressAttr: ipAddress,
portsAttr: ports,
func (serviceObj *Service) Copy() (builtin_argument.KurtosisValueType, error) {
copiedValueType, err := serviceObj.KurtosisValueTypeDefault.Copy()
if err != nil {
return nil, err
}
return &Service{
Struct: starlarkstruct.FromStringDict(starlark.String(serviceTypeName), structDict),
KurtosisValueTypeDefault: copiedValueType,
}, nil
}

func (serviceObj *Service) GetName() (service.ServiceName, *startosis_errors.InterpretationError) {
serviceName, _, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.String](
serviceObj.KurtosisValueTypeDefault, ServiceNameAttr)
if interpretationErr != nil {
return "", interpretationErr
}
return service.ServiceName(serviceName.GoString()), nil
}

// String manually overrides the default starlarkstruct.Struct String() function because it is wrong when
// we provide a custom constructor, which we do here
//
// See https://github.com/google/starlark-go/issues/448 for more details
func (service *Service) String() string {
oldInvalid := fmt.Sprintf("\"%s\"(", serviceTypeName)
newValid := fmt.Sprintf("%s(", serviceTypeName)
return strings.Replace(service.Struct.String(), oldInvalid, newValid, 1)
func (serviceObj *Service) GetHostname() (string, *startosis_errors.InterpretationError) {
hostname, _, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.String](
serviceObj.KurtosisValueTypeDefault, HostnameAttr)
if interpretationErr != nil {
return "", interpretationErr
}
return hostname.GoString(), nil
}

// Type Needs to be overridden as the default for starlarkstruct.Struct always return "struct", which is dumb
func (service *Service) Type() string {
return serviceTypeName
func (serviceObj *Service) GetIpAddress() (string, *startosis_errors.InterpretationError) {
ipAddress, _, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.String](
serviceObj.KurtosisValueTypeDefault, IpAddressAttr)
if interpretationErr != nil {
return "", interpretationErr
}
return ipAddress.GoString(), nil
}

func (serviceObj *Service) GetPorts() (*starlark.Dict, *startosis_errors.InterpretationError) {
ports, _, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[*starlark.Dict](
serviceObj.KurtosisValueTypeDefault, PortsAttr)
if interpretationErr != nil {
return nil, interpretationErr
}
return ports, nil
}