diff --git a/core/server/api_container/server/startosis_engine/kurtosis_builtins.go b/core/server/api_container/server/startosis_engine/kurtosis_builtins.go index 0229920ce6..8e2e44c2e3 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_builtins.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_builtins.go @@ -19,6 +19,7 @@ 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" @@ -26,9 +27,21 @@ import ( "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. @@ -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()), diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go index d6fb1510b7..b9497e9235 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go @@ -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 } diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_service_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_service_framework_test.go index c6d38f411b..70ec8d98a5 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_service_framework_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_service_framework_test.go @@ -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) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_framework_test.go new file mode 100644 index 0000000000..181767fbe5 --- /dev/null +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_framework_test.go @@ -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()) +} diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/starlark_framework_engine_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/starlark_framework_engine_test.go index d6bf143912..e1c67cd95c 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/starlark_framework_engine_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/starlark_framework_engine_test.go @@ -4,7 +4,6 @@ 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" @@ -12,10 +11,7 @@ import ( "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" ) @@ -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)) @@ -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() + // Add all Kurtosis types for _, kurtosisTypeConstructor := range startosis_engine.KurtosisTypeConstructors() { predeclared[kurtosisTypeConstructor.Name()] = kurtosisTypeConstructor diff --git a/core/server/api_container/server/startosis_engine/kurtosis_types/service.go b/core/server/api_container/server/startosis_engine/kurtosis_types/service.go index b4b5604e3d..011a6b4ec9 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_types/service.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_types/service.go @@ -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 } diff --git a/core/server/api_container/server/startosis_engine/kurtosis_types/service_test.go b/core/server/api_container/server/startosis_engine/kurtosis_types/service_test.go deleted file mode 100644 index 03bdb75917..0000000000 --- a/core/server/api_container/server/startosis_engine/kurtosis_types/service_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package kurtosis_types - -import ( - port_spec2 "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" - "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/port_spec" - "github.com/stretchr/testify/require" - "go.starlark.net/starlark" - "testing" -) - -const ( - hostnameTestValue = starlark.String("datastore-1") - ipAddressTestValue = starlark.String("{{kurtosis:service_name.ip_address}}") - testInvalidAttr = "invalid-test-attr" - serviceNameTestValue = starlark.String("test-service") - emptyPortSpecWaitTimeout = "" - portSpecWaitTimeout = "2s" - grpcPortId = "grpc" -) - -var ( - httpApplicationProtocol = "http" - emptyApplicationProtocol *string = nil -) - -func TestService_StringRepresentation(t *testing.T) { - service, err := createTestServiceType() - require.Nil(t, err) - expectedStr := `Service(hostname = "datastore-1", ip_address = "{{kurtosis:service_name.ip_address}}", name = "test-service", ports = {"grpc": PortSpec(number=123, transport_protocol="TCP")})` - serviceStr := service.String() - require.Equal(t, expectedStr, serviceStr) -} - -func TestService_StringRepresentationWithApplicationProtocol(t *testing.T) { - service, err := createTestServiceTypeWithApplicationProtocol() - require.Nil(t, err) - expectedStr := `Service(hostname = "datastore-1", ip_address = "{{kurtosis:service_name.ip_address}}", name = "test-service", ports = {"grpc": PortSpec(number=123, transport_protocol="TCP", application_protocol="http")})` - require.Equal(t, expectedStr, service.String()) -} - -func TestService_StringRepresentationWithWait(t *testing.T) { - service, err := createTestServiceTypeWithWait() - require.Nil(t, err) - expectedStr := `Service(hostname = "datastore-1", ip_address = "{{kurtosis:service_name.ip_address}}", name = "test-service", ports = {"grpc": PortSpec(number=123, transport_protocol="TCP", wait="2s")})` - require.Equal(t, expectedStr, service.String()) -} - -func TestService_StringRepresentationWithApplicationProtocolAndWait(t *testing.T) { - service, err := createTestServiceTypeWithApplicationProtocolAndWait() - require.Nil(t, err) - expectedStr := `Service(hostname = "datastore-1", ip_address = "{{kurtosis:service_name.ip_address}}", name = "test-service", ports = {"grpc": PortSpec(number=123, transport_protocol="TCP", application_protocol="http", wait="2s")})` - require.Equal(t, expectedStr, service.String()) -} - -func TestService_ServiceType(t *testing.T) { - service, err := createTestServiceType() - require.Nil(t, err) - require.Equal(t, serviceTypeName, service.Type()) -} - -func TestService_Freeze(t *testing.T) { - service, err := createTestServiceType() - require.Nil(t, err) - // just checking it doesn't panic - require.NotPanics(t, service.Freeze) -} - -func TestService_TruthValidService(t *testing.T) { - service, err := createTestServiceType() - require.Nil(t, err) - // starlarkstruct.Struct Truth() function always return true - require.Equal(t, starlark.Bool(true), service.Truth()) -} - -func TestService_HashThrowsError(t *testing.T) { - service, err := createTestServiceType() - require.Nil(t, err) - hash, err := service.Hash() - require.NotNil(t, err) - require.Equal(t, uint32(0), hash) -} - -func TestService_TestValidAttr(t *testing.T) { - service, err := createTestServiceType() - require.Nil(t, err) - attrValue, err := service.Attr(ipAddressAttr) - require.Nil(t, err) - require.Equal(t, ipAddressTestValue, attrValue) -} - -func TestService_TestInvalidAttr(t *testing.T) { - service, err := createTestServiceType() - require.Nil(t, err) - attrValue, err := service.Attr(testInvalidAttr) - require.NotNil(t, err) - require.Nil(t, attrValue) -} - -func TestService_TestAttrNames(t *testing.T) { - service, err := createTestServiceType() - require.Nil(t, err) - attrNames := service.AttrNames() - require.Equal(t, []string{hostnameAttr, ipAddressAttr, serviceNameAttr, portsAttr}, attrNames) -} - -func createTestServiceType() (*Service, error) { - ports := starlark.NewDict(1) - portSpec, err := port_spec.CreatePortSpec(123, port_spec2.TransportProtocol_TCP, emptyApplicationProtocol, emptyPortSpecWaitTimeout) - if err != nil { - return nil, err - } - if err := ports.SetKey(starlark.String(grpcPortId), portSpec); err != nil { - return nil, err - } - service := NewService(serviceNameTestValue, hostnameTestValue, ipAddressTestValue, ports) - return service, nil -} - -func createTestServiceTypeWithApplicationProtocol() (*Service, error) { - ports := starlark.NewDict(1) - portSpec, err := port_spec.CreatePortSpec(123, port_spec2.TransportProtocol_TCP, &httpApplicationProtocol, emptyPortSpecWaitTimeout) - if err != nil { - return nil, err - } - if err := ports.SetKey(starlark.String(grpcPortId), portSpec); err != nil { - return nil, err - } - service := NewService(serviceNameTestValue, hostnameTestValue, ipAddressTestValue, ports) - return service, nil -} - -func createTestServiceTypeWithWait() (*Service, error) { - ports := starlark.NewDict(1) - portSpec, err := port_spec.CreatePortSpec(123, port_spec2.TransportProtocol_TCP, emptyApplicationProtocol, portSpecWaitTimeout) - if err != nil { - return nil, err - } - if err := ports.SetKey(starlark.String(grpcPortId), portSpec); err != nil { - return nil, err - } - service := NewService(serviceNameTestValue, hostnameTestValue, ipAddressTestValue, ports) - return service, nil -} - -func createTestServiceTypeWithApplicationProtocolAndWait() (*Service, error) { - ports := starlark.NewDict(1) - portSpec, err := port_spec.CreatePortSpec(123, port_spec2.TransportProtocol_TCP, &httpApplicationProtocol, portSpecWaitTimeout) - if err != nil { - return nil, err - } - if err := ports.SetKey(starlark.String(grpcPortId), portSpec); err != nil { - return nil, err - } - service := NewService(serviceNameTestValue, hostnameTestValue, ipAddressTestValue, ports) - return service, nil -} diff --git a/core/server/api_container/server/startosis_engine/starlark_value_serde.go b/core/server/api_container/server/startosis_engine/starlark_value_serde.go new file mode 100644 index 0000000000..0d40c79c9e --- /dev/null +++ b/core/server/api_container/server/startosis_engine/starlark_value_serde.go @@ -0,0 +1,45 @@ +package startosis_engine + +import ( + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_errors" + "go.starlark.net/starlark" +) + +const ( + starlarkThreadName = "starlark-deserializer-thread" +) + +var ( + cachedStarlarkThread *starlark.Thread + + cachedStarlarkEnv starlark.StringDict +) + +func SerializeStarlarkValue(val starlark.Value) string { + return val.String() +} + +func DeserializeStarlarkValue(serializedStarlarkValue string) (starlark.Value, *startosis_errors.InterpretationError) { + if cachedStarlarkThread == nil { + cachedStarlarkThread = &starlark.Thread{ + Name: starlarkThreadName, + Print: nil, + Load: nil, + OnMaxSteps: nil, + Steps: 0, + } + } + if cachedStarlarkEnv == nil { + cachedStarlarkEnv = Predeclared() + builtins := KurtosisTypeConstructors() + for _, builtin := range builtins { + cachedStarlarkEnv[builtin.Name()] = builtin + } + } + + val, err := starlark.Eval(cachedStarlarkThread, "", serializedStarlarkValue, cachedStarlarkEnv) + if err != nil { + return nil, startosis_errors.WrapWithInterpretationError(err, "Unable to deserialize starlark value '%s'", serializedStarlarkValue) + } + return val, nil +} diff --git a/core/server/api_container/server/startosis_engine/starlark_value_serde_test.go b/core/server/api_container/server/startosis_engine/starlark_value_serde_test.go new file mode 100644 index 0000000000..f02d1121ac --- /dev/null +++ b/core/server/api_container/server/startosis_engine/starlark_value_serde_test.go @@ -0,0 +1,77 @@ +package startosis_engine + +import ( + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types" + port_spec2 "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/port_spec" + "github.com/stretchr/testify/require" + "go.starlark.net/starlark" + "go.starlark.net/starlarkstruct" + "testing" +) + +func TestStarlarkValueSerde_Integer(t *testing.T) { + val := starlark.MakeInt(42) + + serializedStarlarkValue := SerializeStarlarkValue(val) + require.Equal(t, "42", serializedStarlarkValue) + + deserializedStarlarkValue, interpretationErr := DeserializeStarlarkValue(serializedStarlarkValue) + require.Nil(t, interpretationErr) + require.Equal(t, val, deserializedStarlarkValue) +} + +func TestStarlarkValueSerde_String(t *testing.T) { + val := starlark.String("Hello world") + + serializedStarlarkValue := SerializeStarlarkValue(val) + require.Equal(t, `"Hello world"`, serializedStarlarkValue) + + deserializedStarlarkValue, interpretationErr := DeserializeStarlarkValue(serializedStarlarkValue) + require.Nil(t, interpretationErr) + require.Equal(t, val, deserializedStarlarkValue) +} + +func TestStarlarkValueSerde_Dict(t *testing.T) { + val := starlark.NewDict(3) + require.NoError(t, val.SetKey(starlark.String("hello"), starlark.String("world"))) + require.NoError(t, val.SetKey(starlark.String("answer"), starlark.MakeInt(42))) + require.NoError(t, val.SetKey(starlark.String("nested"), starlarkstruct.FromStringDict(starlarkstruct.Default, starlark.StringDict{ + "blah": starlark.String("blah"), + }))) + + serializedStarlarkValue := SerializeStarlarkValue(val) + require.Equal(t, `{"hello": "world", "answer": 42, "nested": struct(blah = "blah")}`, serializedStarlarkValue) + + deserializedStarlarkValue, interpretationErr := DeserializeStarlarkValue(serializedStarlarkValue) + require.Nil(t, interpretationErr) + require.Equal(t, val, deserializedStarlarkValue) +} + +func TestStarlarkValueSerde_Service(t *testing.T) { + port, interpretationErr := port_spec2.CreatePortSpec( + uint16(443), + port_spec.TransportProtocol_TCP, + nil, + "10s", + ) + require.Nil(t, interpretationErr) + ports := starlark.NewDict(1) + require.NoError(t, ports.SetKey(starlark.String("http"), port)) + + serviceObj, interpretationErr := kurtosis_types.CreateService( + "test-service", + "test-service-hostname", + "192.168.0.22", + ports, + ) + require.Nil(t, interpretationErr) + + serializedStarlarkValue := SerializeStarlarkValue(serviceObj) + expectedSerializedServiceObj := `Service(name="test-service", hostname="test-service-hostname", ip_address="192.168.0.22", ports={"http": PortSpec(number=443, transport_protocol="TCP", wait="10s")})` + require.Equal(t, expectedSerializedServiceObj, serializedStarlarkValue) + + deserializedStarlarkValue, interpretationErr := DeserializeStarlarkValue(serializedStarlarkValue) + require.Nil(t, interpretationErr) + require.Equal(t, serviceObj.String(), deserializedStarlarkValue.String()) +} diff --git a/core/server/api_container/server/startosis_engine/startosis_interpreter.go b/core/server/api_container/server/startosis_engine/startosis_interpreter.go index a975d923f3..7f87f5ff76 100644 --- a/core/server/api_container/server/startosis_engine/startosis_interpreter.go +++ b/core/server/api_container/server/startosis_engine/startosis_interpreter.go @@ -17,11 +17,8 @@ import ( "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" "github.com/sirupsen/logrus" - starlarkjson "go.starlark.net/lib/json" - "go.starlark.net/lib/time" "go.starlark.net/resolve" "go.starlark.net/starlark" - "go.starlark.net/starlarkstruct" "go.starlark.net/syntax" "path" "strings" @@ -347,15 +344,10 @@ func (interpreter *StartosisInterpreter) buildBindings(thread *starlark.Thread, return nil, interpretationErr } - 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, + predeclared := Predeclared() - // Kurtosis pre-built module containing Kurtosis constant types - builtins.KurtosisModuleName: kurtosisModule, - } + // Add custom Kurtosis module + predeclared[builtins.KurtosisModuleName] = kurtosisModule // Add all Kurtosis helpers for _, kurtosisHelper := range KurtosisHelpers(recursiveInterpretForModuleLoading, interpreter.moduleContentProvider, interpreter.moduleGlobalsCache) {