diff --git a/core/server/api_container/server/service_network/default_service_network.go b/core/server/api_container/server/service_network/default_service_network.go index aae89f1c55..3b20ac070b 100644 --- a/core/server/api_container/server/service_network/default_service_network.go +++ b/core/server/api_container/server/service_network/default_service_network.go @@ -348,6 +348,10 @@ func (network *DefaultServiceNetwork) AddServices( startedServices := map[service.ServiceName]*service.Service{} failedServices := map[service.ServiceName]error{} + if len(serviceConfigs) == 0 { + return startedServices, failedServices, nil + } + // Save the services currently running in enclave for later currentlyRunningServicesInEnclave := map[service.ServiceName]bool{} for serviceName := range network.registeredServiceInfo { @@ -460,10 +464,9 @@ func (network *DefaultServiceNetwork) AddServices( return startedServices, map[service.ServiceName]error{}, nil } -// UpdateService updates a service currently running inside an enclave. See UpdateServices for more details -func (network *DefaultServiceNetwork) UpdateService(ctx context.Context, serviceName service.ServiceName, serviceConfig *service.ServiceConfig) (*service.Service, error) { +func (network *DefaultServiceNetwork) UpdateService(ctx context.Context, serviceName service.ServiceName, updateServiceConfig *service.ServiceConfig) (*service.Service, error) { serviceConfigMap := map[service.ServiceName]*service.ServiceConfig{ - serviceName: serviceConfig, + serviceName: updateServiceConfig, } startedServices, serviceFailed, err := network.UpdateServices(ctx, serviceConfigMap, singleServiceStartupBatch) @@ -489,6 +492,10 @@ func (network *DefaultServiceNetwork) UpdateServices(ctx context.Context, update failedServicesPool := map[service.ServiceName]error{} successfullyUpdatedService := map[service.ServiceName]*service.Service{} + if len(updateServiceConfigs) == 0 { + return successfullyUpdatedService, failedServicesPool, nil + } + // First, remove the service serviceUuidToNameMap := map[service.ServiceUUID]service.ServiceName{} serviceUuidsToRemove := map[service.ServiceUUID]bool{} @@ -511,7 +518,7 @@ func (network *DefaultServiceNetwork) UpdateServices(ctx context.Context, update if serviceName, found := serviceUuidToNameMap[serviceUuid]; found { failedServicesPool[serviceName] = serviceErr } else { - return nil, nil, stacktrace.NewError("Error mapping service UUID to service name. This is a bug in Kurtosis") + return nil, nil, stacktrace.NewError("Error mapping service UUID to service name. This is a bug in Kurtosis.\nserviceUuidsToRemove=%v\nfailedRemovedServices=%v\nsuccessfullyRemovedServices=%v\nserviceUuidToNameMap=%v", serviceUuidsToRemove, failedRemovedServices, successfullyRemovedServices, serviceUuidToNameMap) } } diff --git a/core/server/api_container/server/service_network/mock_service_network.go b/core/server/api_container/server/service_network/mock_service_network.go index de096899b2..5ce08a3c8e 100644 --- a/core/server/api_container/server/service_network/mock_service_network.go +++ b/core/server/api_container/server/service_network/mock_service_network.go @@ -1174,17 +1174,17 @@ func (_c *MockServiceNetwork_UnsetConnection_Call) RunAndReturn(run func(context return _c } -// UpdateService provides a mock function with given fields: ctx, serviceName, serviceConfig -func (_m *MockServiceNetwork) UpdateService(ctx context.Context, serviceName service.ServiceName, serviceConfig *service.ServiceConfig) (*service.Service, error) { - ret := _m.Called(ctx, serviceName, serviceConfig) +// UpdateService provides a mock function with given fields: ctx, serviceName, updateServiceConfig +func (_m *MockServiceNetwork) UpdateService(ctx context.Context, serviceName service.ServiceName, updateServiceConfig *service.ServiceConfig) (*service.Service, error) { + ret := _m.Called(ctx, serviceName, updateServiceConfig) var r0 *service.Service var r1 error if rf, ok := ret.Get(0).(func(context.Context, service.ServiceName, *service.ServiceConfig) (*service.Service, error)); ok { - return rf(ctx, serviceName, serviceConfig) + return rf(ctx, serviceName, updateServiceConfig) } if rf, ok := ret.Get(0).(func(context.Context, service.ServiceName, *service.ServiceConfig) *service.Service); ok { - r0 = rf(ctx, serviceName, serviceConfig) + r0 = rf(ctx, serviceName, updateServiceConfig) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*service.Service) @@ -1192,7 +1192,7 @@ func (_m *MockServiceNetwork) UpdateService(ctx context.Context, serviceName ser } if rf, ok := ret.Get(1).(func(context.Context, service.ServiceName, *service.ServiceConfig) error); ok { - r1 = rf(ctx, serviceName, serviceConfig) + r1 = rf(ctx, serviceName, updateServiceConfig) } else { r1 = ret.Error(1) } @@ -1208,12 +1208,12 @@ type MockServiceNetwork_UpdateService_Call struct { // UpdateService is a helper method to define mock.On call // - ctx context.Context // - serviceName service.ServiceName -// - serviceConfig *service.ServiceConfig -func (_e *MockServiceNetwork_Expecter) UpdateService(ctx interface{}, serviceName interface{}, serviceConfig interface{}) *MockServiceNetwork_UpdateService_Call { - return &MockServiceNetwork_UpdateService_Call{Call: _e.mock.On("UpdateService", ctx, serviceName, serviceConfig)} +// - updateServiceConfig *service.ServiceConfig +func (_e *MockServiceNetwork_Expecter) UpdateService(ctx interface{}, serviceName interface{}, updateServiceConfig interface{}) *MockServiceNetwork_UpdateService_Call { + return &MockServiceNetwork_UpdateService_Call{Call: _e.mock.On("UpdateService", ctx, serviceName, updateServiceConfig)} } -func (_c *MockServiceNetwork_UpdateService_Call) Run(run func(ctx context.Context, serviceName service.ServiceName, serviceConfig *service.ServiceConfig)) *MockServiceNetwork_UpdateService_Call { +func (_c *MockServiceNetwork_UpdateService_Call) Run(run func(ctx context.Context, serviceName service.ServiceName, updateServiceConfig *service.ServiceConfig)) *MockServiceNetwork_UpdateService_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(context.Context), args[1].(service.ServiceName), args[2].(*service.ServiceConfig)) }) diff --git a/core/server/api_container/server/service_network/service_network.go b/core/server/api_container/server/service_network/service_network.go index 4b076a2280..5a2f6cf4ae 100644 --- a/core/server/api_container/server/service_network/service_network.go +++ b/core/server/api_container/server/service_network/service_network.go @@ -53,7 +53,7 @@ type ServiceNetwork interface { UpdateService( ctx context.Context, serviceName service.ServiceName, - serviceConfig *service.ServiceConfig, + updateServiceConfig *service.ServiceConfig, ) ( *service.Service, error, diff --git a/core/server/api_container/server/startosis_engine/enclave_structure/enclave_component_status.go b/core/server/api_container/server/startosis_engine/enclave_structure/enclave_component_status.go new file mode 100644 index 0000000000..718a1073ca --- /dev/null +++ b/core/server/api_container/server/startosis_engine/enclave_structure/enclave_component_status.go @@ -0,0 +1,17 @@ +package enclave_structure + +//go:generate go run github.com/dmarkham/enumer -trimprefix "EnclaveComponentStatus" -type=EnclaveComponentStatus +type EnclaveComponentStatus uint8 + +const ( + // ComponentIsNew means the component was first created during this run + ComponentIsNew EnclaveComponentStatus = iota + + // ComponentWasLeftIntact means the component was present prior to the beginning of this run and has been left + // intact + ComponentWasLeftIntact + + // ComponentIsUpdated means the component was present prior to the beginning of this run but has been updated + // during this run + ComponentIsUpdated +) diff --git a/core/server/api_container/server/startosis_engine/enclave_structure/enclave_components.go b/core/server/api_container/server/startosis_engine/enclave_structure/enclave_components.go new file mode 100644 index 0000000000..005b298401 --- /dev/null +++ b/core/server/api_container/server/startosis_engine/enclave_structure/enclave_components.go @@ -0,0 +1,26 @@ +package enclave_structure + +import ( + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" +) + +type EnclaveComponents struct { + enclaveServices map[service.ServiceName]EnclaveComponentStatus +} + +func NewEnclaveComponents() *EnclaveComponents { + return &EnclaveComponents{ + enclaveServices: map[service.ServiceName]EnclaveComponentStatus{}, + } +} + +func (components *EnclaveComponents) AddService(serviceName service.ServiceName, componentStatus EnclaveComponentStatus) { + components.enclaveServices[serviceName] = componentStatus +} + +func (components *EnclaveComponents) HasServiceBeenUpdated(serviceName service.ServiceName) bool { + if serviceStatus, found := components.enclaveServices[serviceName]; found { + return serviceStatus == ComponentIsUpdated + } + return false +} diff --git a/core/server/api_container/server/startosis_engine/enclave_structure/enclavecomponentstatus_enumer.go b/core/server/api_container/server/startosis_engine/enclave_structure/enclavecomponentstatus_enumer.go new file mode 100644 index 0000000000..41098f5e2b --- /dev/null +++ b/core/server/api_container/server/startosis_engine/enclave_structure/enclavecomponentstatus_enumer.go @@ -0,0 +1,82 @@ +// Code generated by "enumer -trimprefix EnclaveComponentStatus -type=EnclaveComponentStatus"; DO NOT EDIT. + +package enclave_structure + +import ( + "fmt" + "strings" +) + +const _EnclaveComponentStatusName = "ComponentIsNewComponentWasLeftIntactComponentIsUpdated" + +var _EnclaveComponentStatusIndex = [...]uint8{0, 14, 36, 54} + +const _EnclaveComponentStatusLowerName = "componentisnewcomponentwasleftintactcomponentisupdated" + +func (i EnclaveComponentStatus) String() string { + if i >= EnclaveComponentStatus(len(_EnclaveComponentStatusIndex)-1) { + return fmt.Sprintf("EnclaveComponentStatus(%d)", i) + } + return _EnclaveComponentStatusName[_EnclaveComponentStatusIndex[i]:_EnclaveComponentStatusIndex[i+1]] +} + +// An "invalid array index" compiler error signifies that the constant values have changed. +// Re-run the stringer command to generate them again. +func _EnclaveComponentStatusNoOp() { + var x [1]struct{} + _ = x[ComponentIsNew-(0)] + _ = x[ComponentWasLeftIntact-(1)] + _ = x[ComponentIsUpdated-(2)] +} + +var _EnclaveComponentStatusValues = []EnclaveComponentStatus{ComponentIsNew, ComponentWasLeftIntact, ComponentIsUpdated} + +var _EnclaveComponentStatusNameToValueMap = map[string]EnclaveComponentStatus{ + _EnclaveComponentStatusName[0:14]: ComponentIsNew, + _EnclaveComponentStatusLowerName[0:14]: ComponentIsNew, + _EnclaveComponentStatusName[14:36]: ComponentWasLeftIntact, + _EnclaveComponentStatusLowerName[14:36]: ComponentWasLeftIntact, + _EnclaveComponentStatusName[36:54]: ComponentIsUpdated, + _EnclaveComponentStatusLowerName[36:54]: ComponentIsUpdated, +} + +var _EnclaveComponentStatusNames = []string{ + _EnclaveComponentStatusName[0:14], + _EnclaveComponentStatusName[14:36], + _EnclaveComponentStatusName[36:54], +} + +// EnclaveComponentStatusString retrieves an enum value from the enum constants string name. +// Throws an error if the param is not part of the enum. +func EnclaveComponentStatusString(s string) (EnclaveComponentStatus, error) { + if val, ok := _EnclaveComponentStatusNameToValueMap[s]; ok { + return val, nil + } + + if val, ok := _EnclaveComponentStatusNameToValueMap[strings.ToLower(s)]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to EnclaveComponentStatus values", s) +} + +// EnclaveComponentStatusValues returns all values of the enum +func EnclaveComponentStatusValues() []EnclaveComponentStatus { + return _EnclaveComponentStatusValues +} + +// EnclaveComponentStatusStrings returns a slice of all String values of the enum +func EnclaveComponentStatusStrings() []string { + strs := make([]string, len(_EnclaveComponentStatusNames)) + copy(strs, _EnclaveComponentStatusNames) + return strs +} + +// IsAEnclaveComponentStatus returns "true" if the value is listed in the enum definition. "false" otherwise +func (i EnclaveComponentStatus) IsAEnclaveComponentStatus() bool { + for _, v := range _EnclaveComponentStatusValues { + if i == v { + return true + } + } + return false +} diff --git a/core/server/api_container/server/startosis_engine/enclave_structure/instruction_resolution_status.go b/core/server/api_container/server/startosis_engine/enclave_structure/instruction_resolution_status.go new file mode 100644 index 0000000000..b6c4489082 --- /dev/null +++ b/core/server/api_container/server/startosis_engine/enclave_structure/instruction_resolution_status.go @@ -0,0 +1,21 @@ +package enclave_structure + +//go:generate go run github.com/dmarkham/enumer -trimprefix "InstructionResolutionStatus" -type=InstructionResolutionStatus +type InstructionResolutionStatus uint8 + +const ( + // InstructionIsEqual means this instruction is strictly equal to the one it's being compared to. + InstructionIsEqual InstructionResolutionStatus = iota + + // InstructionIsUpdate means this instruction is the same as the one it's being compared to, but with some + // changes to its parameters. It should be re-run so that the enclave can be updated. + InstructionIsUpdate + + // InstructionIsUnknown means the two instructions are completely different. + InstructionIsUnknown + + // InstructionIsNotResolvableAbort means one of the compared instructions is fundamentally incompatible with the + // concept of idempotency. Kurtosis should stop trying to run it in an idempotent way and fall back to the default + // behaviour + InstructionIsNotResolvableAbort +) diff --git a/core/server/api_container/server/startosis_engine/enclave_structure/instructionresolutionstatus_enumer.go b/core/server/api_container/server/startosis_engine/enclave_structure/instructionresolutionstatus_enumer.go new file mode 100644 index 0000000000..851a78b094 --- /dev/null +++ b/core/server/api_container/server/startosis_engine/enclave_structure/instructionresolutionstatus_enumer.go @@ -0,0 +1,86 @@ +// Code generated by "enumer -trimprefix InstructionResolutionStatus -type=InstructionResolutionStatus"; DO NOT EDIT. + +package enclave_structure + +import ( + "fmt" + "strings" +) + +const _InstructionResolutionStatusName = "InstructionIsEqualInstructionIsUpdateInstructionIsUnknownInstructionIsNotResolvableAbort" + +var _InstructionResolutionStatusIndex = [...]uint8{0, 18, 37, 57, 88} + +const _InstructionResolutionStatusLowerName = "instructionisequalinstructionisupdateinstructionisunknowninstructionisnotresolvableabort" + +func (i InstructionResolutionStatus) String() string { + if i >= InstructionResolutionStatus(len(_InstructionResolutionStatusIndex)-1) { + return fmt.Sprintf("InstructionResolutionStatus(%d)", i) + } + return _InstructionResolutionStatusName[_InstructionResolutionStatusIndex[i]:_InstructionResolutionStatusIndex[i+1]] +} + +// An "invalid array index" compiler error signifies that the constant values have changed. +// Re-run the stringer command to generate them again. +func _InstructionResolutionStatusNoOp() { + var x [1]struct{} + _ = x[InstructionIsEqual-(0)] + _ = x[InstructionIsUpdate-(1)] + _ = x[InstructionIsUnknown-(2)] + _ = x[InstructionIsNotResolvableAbort-(3)] +} + +var _InstructionResolutionStatusValues = []InstructionResolutionStatus{InstructionIsEqual, InstructionIsUpdate, InstructionIsUnknown, InstructionIsNotResolvableAbort} + +var _InstructionResolutionStatusNameToValueMap = map[string]InstructionResolutionStatus{ + _InstructionResolutionStatusName[0:18]: InstructionIsEqual, + _InstructionResolutionStatusLowerName[0:18]: InstructionIsEqual, + _InstructionResolutionStatusName[18:37]: InstructionIsUpdate, + _InstructionResolutionStatusLowerName[18:37]: InstructionIsUpdate, + _InstructionResolutionStatusName[37:57]: InstructionIsUnknown, + _InstructionResolutionStatusLowerName[37:57]: InstructionIsUnknown, + _InstructionResolutionStatusName[57:88]: InstructionIsNotResolvableAbort, + _InstructionResolutionStatusLowerName[57:88]: InstructionIsNotResolvableAbort, +} + +var _InstructionResolutionStatusNames = []string{ + _InstructionResolutionStatusName[0:18], + _InstructionResolutionStatusName[18:37], + _InstructionResolutionStatusName[37:57], + _InstructionResolutionStatusName[57:88], +} + +// InstructionResolutionStatusString retrieves an enum value from the enum constants string name. +// Throws an error if the param is not part of the enum. +func InstructionResolutionStatusString(s string) (InstructionResolutionStatus, error) { + if val, ok := _InstructionResolutionStatusNameToValueMap[s]; ok { + return val, nil + } + + if val, ok := _InstructionResolutionStatusNameToValueMap[strings.ToLower(s)]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to InstructionResolutionStatus values", s) +} + +// InstructionResolutionStatusValues returns all values of the enum +func InstructionResolutionStatusValues() []InstructionResolutionStatus { + return _InstructionResolutionStatusValues +} + +// InstructionResolutionStatusStrings returns a slice of all String values of the enum +func InstructionResolutionStatusStrings() []string { + strs := make([]string, len(_InstructionResolutionStatusNames)) + copy(strs, _InstructionResolutionStatusNames) + return strs +} + +// IsAInstructionResolutionStatus returns "true" if the value is listed in the enum definition. "false" otherwise +func (i InstructionResolutionStatus) IsAInstructionResolutionStatus() bool { + for _, v := range _InstructionResolutionStatusValues { + if i == v { + return true + } + } + return false +} diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service.go index 5493695cff..248787909d 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service.go @@ -5,6 +5,7 @@ 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/service_network" + "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/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_plan_instruction" @@ -103,7 +104,7 @@ func (builtin *AddServiceCapabilities) Interpret(_ string, arguments *builtin_ar builtin.serviceName = service.ServiceName(serviceName.GoString()) builtin.serviceConfig = apiServiceConfig builtin.readyCondition = readyCondition - builtin.resultUuid, err = builtin.runtimeValueStore.CreateValue() + builtin.resultUuid, err = builtin.runtimeValueStore.GetOrCreateValueAssociatedWithService(builtin.serviceName) if err != nil { return nil, startosis_errors.WrapWithInterpretationError(err, "Unable to create runtime value to hold '%v' command return values", AddServiceBuiltinName) } @@ -113,7 +114,6 @@ func (builtin *AddServiceCapabilities) Interpret(_ string, arguments *builtin_ar return nil, interpretationErr } return returnValue, nil - } func (builtin *AddServiceCapabilities) Validate(_ *builtin_argument.ArgumentValuesSet, validatorEnvironment *startosis_validator.ValidatorEnvironment) *startosis_errors.ValidationError { @@ -128,7 +128,12 @@ func (builtin *AddServiceCapabilities) Execute(ctx context.Context, _ *builtin_a if err != nil { return "", stacktrace.Propagate(err, "An error occurred replace a magic string in '%s' instruction arguments for service '%s'. Execution cannot proceed", AddServiceBuiltinName, builtin.serviceName) } - startedService, err := builtin.serviceNetwork.AddService(ctx, replacedServiceName, replacedServiceConfig) + var startedService *service.Service + if _, found := builtin.serviceNetwork.GetServiceRegistration(builtin.serviceName); found { + startedService, err = builtin.serviceNetwork.UpdateService(ctx, replacedServiceName, replacedServiceConfig) + } else { + startedService, err = builtin.serviceNetwork.AddService(ctx, replacedServiceName, replacedServiceConfig) + } if err != nil { return "", stacktrace.Propagate(err, "Unexpected error occurred starting service '%s'", replacedServiceName) } @@ -148,6 +153,28 @@ func (builtin *AddServiceCapabilities) Execute(ctx context.Context, _ *builtin_a return instructionResult, nil } +func (builtin *AddServiceCapabilities) TryResolveWith(instructionsAreEqual bool, other kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, enclaveComponents *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + if instructionsAreEqual { + enclaveComponents.AddService(builtin.serviceName, enclave_structure.ComponentWasLeftIntact) + return enclave_structure.InstructionIsEqual + } + if other == nil { + enclaveComponents.AddService(builtin.serviceName, enclave_structure.ComponentIsNew) + return enclave_structure.InstructionIsUnknown + } + otherAddServiceCapabilities, ok := other.(*AddServiceCapabilities) + if !ok { + enclaveComponents.AddService(builtin.serviceName, enclave_structure.ComponentIsNew) + return enclave_structure.InstructionIsUnknown + } + if otherAddServiceCapabilities.serviceName == builtin.serviceName { + enclaveComponents.AddService(builtin.serviceName, enclave_structure.ComponentIsUpdated) + return enclave_structure.InstructionIsUpdate + } + enclaveComponents.AddService(builtin.serviceName, enclave_structure.ComponentIsNew) + return enclave_structure.InstructionIsUnknown +} + func validateAndConvertConfigAndReadyCondition( serviceNetwork service_network.ServiceNetwork, rawConfig starlark.Value, 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 1784156504..df5a32b211 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 @@ -71,8 +71,8 @@ func validateSingleService(validatorEnvironment *startosis_validator.ValidatorEn return startosis_errors.NewValidationError(invalidServiceNameErrorText(serviceName)) } - if validatorEnvironment.DoesServiceNameExist(serviceName) { - return startosis_errors.NewValidationError("There was an error validating '%s' as service '%s' already exists", AddServiceBuiltinName, serviceName) + if validatorEnvironment.DoesServiceNameExist(serviceName) == startosis_validator.ServiceCreatedOrUpdatedDuringPackageRun { + return startosis_errors.NewValidationError("There was an error validating '%s' as service '%s' was created inside this package. Adding the same service twice in the same package is not allowed", AddServiceBuiltinName, serviceName) } if serviceConfig.GetFilesArtifactsExpansion() != nil { for _, artifactName := range serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers { @@ -83,9 +83,11 @@ func validateSingleService(validatorEnvironment *startosis_validator.ValidatorEn } validatorEnvironment.AddServiceName(serviceName) validatorEnvironment.AppendRequiredContainerImage(serviceConfig.GetContainerImageName()) + var portIds []string for portId := range serviceConfig.GetPrivatePorts() { - validatorEnvironment.AddPrivatePortIDForService(portId, serviceName) + portIds = append(portIds, portId) } + validatorEnvironment.AddPrivatePortIDForService(portIds, serviceName) return nil } diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_services.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_services.go index 6295a7283b..792021bb7c 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_services.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_services.go @@ -5,6 +5,7 @@ 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/service_network" + "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/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_plan_instruction" @@ -123,25 +124,56 @@ func (builtin *AddServicesCapabilities) Execute(ctx context.Context, _ *builtin_ renderedServiceConfigs[renderedServiceName] = renderedServiceConfig } - startedServices, failedServices, err := builtin.serviceNetwork.AddServices(ctx, renderedServiceConfigs, parallelism) + serviceToUpdate := map[service.ServiceName]*service.ServiceConfig{} + serviceToCreate := map[service.ServiceName]*service.ServiceConfig{} + for serviceName, serviceConfig := range renderedServiceConfigs { + if _, found := builtin.serviceNetwork.GetServiceRegistration(serviceName); found { + serviceToUpdate[serviceName] = serviceConfig + } else { + serviceToCreate[serviceName] = serviceConfig + } + } + + updatedServices, failedToBeUpdatedServices, err := builtin.serviceNetwork.UpdateServices(ctx, serviceToUpdate, parallelism) if err != nil { - return "", stacktrace.Propagate(err, "Unexpected error occurred starting a batch of services") + var allServiceNames []string + for serviceName := range serviceToUpdate { + allServiceNames = append(allServiceNames, string(serviceName)) + } + return "", stacktrace.Propagate(err, "Unexpected error occurred updating the following batch of services: %s", strings.Join(allServiceNames, ", ")) } - if len(failedServices) > 0 { - failedServiceNames := make([]service.ServiceName, len(failedServices)) - idx := 0 - for failedServiceName := range failedServices { - failedServiceNames[idx] = failedServiceName - idx++ + + startedServices, failedToBeStartedServices, err := builtin.serviceNetwork.AddServices(ctx, serviceToCreate, parallelism) + if err != nil { + var allServiceNames []string + for serviceName := range serviceToCreate { + allServiceNames = append(allServiceNames, string(serviceName)) } - return "", stacktrace.NewError("Some errors occurred starting the following services: '%v'. The entire batch was rolled back an no service was started. Errors were: \n%v", failedServiceNames, failedServices) + return "", stacktrace.Propagate(err, "Unexpected error occurred starting the following batch of services: %s", strings.Join(allServiceNames, ", ")) + } + if len(failedToBeStartedServices) > 0 || len(failedToBeUpdatedServices) > 0 { + var failedServiceNames []service.ServiceName + for failedServiceName := range failedToBeStartedServices { + failedServiceNames = append(failedServiceNames, failedServiceName) + } + for failedServiceName := range failedToBeUpdatedServices { + failedServiceNames = append(failedServiceNames, failedServiceName) + } + return "", stacktrace.NewError("Some errors occurred starting or updating the following services: '%v'. The entire batch was rolled back an no service was started. Errors were:\nService creations: %v\nService Updates: %v", failedServiceNames, failedToBeStartedServices, failedToBeUpdatedServices) + } + startedAndUpdatedService := map[service.ServiceName]*service.Service{} + for startedServiceName, startedService := range startedServices { + startedAndUpdatedService[startedServiceName] = startedService + } + for updatedServiceName, updatedService := range updatedServices { + startedAndUpdatedService[updatedServiceName] = updatedService } shouldDeleteAllStartedServices := true //TODO we should move the readiness check functionality to the default service network to improve performance ///TODO because we won't have to wait for all services to start for checking readiness, but first we have to //TODO propagate the Recipes to this layer too and probably move the wait instruction also - if failedServicesChecks := builtin.allServicesReadinessCheck(ctx, startedServices, parallelism); len(failedServicesChecks) > 0 { + if failedServicesChecks := builtin.allServicesReadinessCheck(ctx, startedAndUpdatedService, parallelism); len(failedServicesChecks) > 0 { var allServiceChecksErrMsg string for serviceName, serviceErr := range failedServicesChecks { serviceMsg := fmt.Sprintf("Service '%v' error:\n%v\n", serviceName, serviceErr) @@ -151,21 +183,65 @@ func (builtin *AddServicesCapabilities) Execute(ctx context.Context, _ *builtin_ } defer func() { if shouldDeleteAllStartedServices { - builtin.removeAllStartedServices(ctx, startedServices) + builtin.removeAllStartedServices(ctx, startedAndUpdatedService) } }() instructionResult := strings.Builder{} instructionResult.WriteString(fmt.Sprintf("Successfully added the following '%d' services:", len(startedServices))) - for serviceName, serviceObj := range startedServices { + for serviceName, serviceObj := range startedAndUpdatedService { fillAddServiceReturnValueWithRuntimeValues(serviceObj, builtin.resultUuids[serviceName], builtin.runtimeValueStore) instructionResult.WriteString(fmt.Sprintf("\n Service '%s' added with UUID '%s'", serviceName, serviceObj.GetRegistration().GetUUID())) - } shouldDeleteAllStartedServices = false return instructionResult.String(), nil } +func (builtin *AddServicesCapabilities) TryResolveWith(instructionsAreEqual bool, other kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, enclaveComponents *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + if instructionsAreEqual { + for serviceName := range builtin.serviceConfigs { + enclaveComponents.AddService(serviceName, enclave_structure.ComponentWasLeftIntact) + } + return enclave_structure.InstructionIsEqual + } + otherAddServicesCapabilities, ok := other.(*AddServicesCapabilities) + if !ok { + for serviceName := range builtin.serviceConfigs { + enclaveComponents.AddService(serviceName, enclave_structure.ComponentIsNew) + } + return enclave_structure.InstructionIsUnknown + } + + // The instruction can be re-run only if the set of added services is a subset of what was added by the instruction it's being compared to + previouslyAddedService := map[service.ServiceName]bool{} + for serviceName := range otherAddServicesCapabilities.serviceConfigs { + previouslyAddedService[serviceName] = false + } + for serviceName := range builtin.serviceConfigs { + if _, found := previouslyAddedService[serviceName]; found { + previouslyAddedService[serviceName] = true // toggle the boolean to true + } + } + for _, servicePresentInCurrentInstruction := range previouslyAddedService { + if !servicePresentInCurrentInstruction { + // if one service is not present in the current instruction, instruction cannot be re-run + for serviceName := range builtin.serviceConfigs { + enclaveComponents.AddService(serviceName, enclave_structure.ComponentIsNew) + } + return enclave_structure.InstructionIsUnknown + } + } + + for serviceName := range builtin.serviceConfigs { + if _, found := previouslyAddedService[serviceName]; found { + enclaveComponents.AddService(serviceName, enclave_structure.ComponentIsUpdated) + } else { + enclaveComponents.AddService(serviceName, enclave_structure.ComponentIsNew) + } + } + return enclave_structure.InstructionIsUpdate +} + func (builtin *AddServicesCapabilities) removeAllStartedServices( ctx context.Context, startedServices map[service.ServiceName]*service.Service, diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/assert/assert.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/assert/assert.go index 7c992f61a4..da0c954b02 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/assert/assert.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/assert/assert.go @@ -3,6 +3,7 @@ package assert import ( "context" "fmt" + "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/kurtosis_instruction/shared_helpers/magic_string_helper" "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" @@ -142,6 +143,13 @@ func (builtin *AssertCapabilities) Execute(_ context.Context, _ *builtin_argumen return instructionResult, nil } +func (builtin *AssertCapabilities) TryResolveWith(instructionsAreEqual bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, _ *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + if instructionsAreEqual { + return enclave_structure.InstructionIsEqual + } + return enclave_structure.InstructionIsUnknown +} + // Assert verifies whether the currentValue matches the targetValue w.r.t. the assertion operator // TODO: This and ValidateAssertionToken below are used by both assert and wait. Refactor it to a better place func Assert(currentValue starlark.Comparable, assertion string, targetValue starlark.Comparable) error { diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/exec/exec.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/exec/exec.go index faad8d2c17..c1fdd2f39f 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/exec/exec.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/exec/exec.go @@ -5,6 +5,7 @@ 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/service_network" + "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/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_plan_instruction" @@ -172,6 +173,15 @@ func (builtin *ExecCapabilities) Execute(ctx context.Context, _ *builtin_argumen return instructionResult, err } +func (builtin *ExecCapabilities) TryResolveWith(instructionsAreEqual bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, enclaveComponents *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + if instructionsAreEqual && enclaveComponents.HasServiceBeenUpdated(builtin.serviceName) { + return enclave_structure.InstructionIsUpdate + } else if instructionsAreEqual { + return enclave_structure.InstructionIsEqual + } + return enclave_structure.InstructionIsUnknown +} + func (builtin *ExecCapabilities) isAcceptableCode(recipeResult map[string]starlark.Comparable) bool { isAcceptableCode := false for _, acceptableCode := range builtin.acceptableCodes { diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/kurtosis_instruction.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/kurtosis_instruction.go index 1137dd3605..fc71494d8b 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/kurtosis_instruction.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/kurtosis_instruction.go @@ -3,6 +3,7 @@ package kurtosis_instruction import ( "context" "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" + "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/kurtosis_starlark_framework" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_validator" "go.starlark.net/starlark" @@ -31,4 +32,7 @@ type KurtosisInstruction interface { // ValidateAndUpdateEnvironment validates if the instruction can be applied to an environment, and mutates that // environment to reflect how Kurtosis would look like after this instruction is successfully executed. ValidateAndUpdateEnvironment(environment *startosis_validator.ValidatorEnvironment) error + + // TryResolveWith assesses whether the instruction can be resolved with the one passed as an argument. + TryResolveWith(other KurtosisInstruction, enclaveComponents *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus } diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/kurtosis_print/kurtosis_print.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/kurtosis_print/kurtosis_print.go index 4e0c0f7dfb..dd3938341e 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/kurtosis_print/kurtosis_print.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/kurtosis_print/kurtosis_print.go @@ -3,6 +3,7 @@ package kurtosis_print import ( "context" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" + "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/kurtosis_instruction/shared_helpers/magic_string_helper" "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" @@ -86,3 +87,10 @@ func (builtin *PrintCapabilities) Execute(_ context.Context, _ *builtin_argument } return maybeSerializedArgsWithRuntimeValue, nil } + +func (builtin *PrintCapabilities) TryResolveWith(instructionsAreEqual bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, _ *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + if instructionsAreEqual { + return enclave_structure.InstructionIsEqual + } + return enclave_structure.InstructionIsUnknown +} diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/mock_instruction/mock_kurtosis_instruction.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/mock_instruction/mock_kurtosis_instruction.go index e60a3e02c4..68e9ff1198 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/mock_instruction/mock_kurtosis_instruction.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/mock_instruction/mock_kurtosis_instruction.go @@ -4,8 +4,11 @@ package mock_instruction import ( context "context" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction" kurtosis_core_rpc_api_bindings "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" + enclave_structure "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/enclave_structure" + kurtosis_starlark_framework "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework" mock "github.com/stretchr/testify/mock" @@ -208,6 +211,49 @@ func (_c *MockKurtosisInstruction_String_Call) RunAndReturn(run func() string) * return _c } +// TryResolveWith provides a mock function with given fields: other, enclaveComponents +func (_m *MockKurtosisInstruction) TryResolveWith(other kurtosis_instruction.KurtosisInstruction, enclaveComponents *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + ret := _m.Called(other, enclaveComponents) + + var r0 enclave_structure.InstructionResolutionStatus + if rf, ok := ret.Get(0).(func(kurtosis_instruction.KurtosisInstruction, *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus); ok { + r0 = rf(other, enclaveComponents) + } else { + r0 = ret.Get(0).(enclave_structure.InstructionResolutionStatus) + } + + return r0 +} + +// MockKurtosisInstruction_TryResolveWith_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TryResolveWith' +type MockKurtosisInstruction_TryResolveWith_Call struct { + *mock.Call +} + +// TryResolveWith is a helper method to define mock.On call +// - other KurtosisInstruction +// - enclaveComponents *enclave_structure.EnclaveComponents +func (_e *MockKurtosisInstruction_Expecter) TryResolveWith(other interface{}, enclaveComponents interface{}) *MockKurtosisInstruction_TryResolveWith_Call { + return &MockKurtosisInstruction_TryResolveWith_Call{Call: _e.mock.On("TryResolveWith", other, enclaveComponents)} +} + +func (_c *MockKurtosisInstruction_TryResolveWith_Call) Run(run func(other kurtosis_instruction.KurtosisInstruction, enclaveComponents *enclave_structure.EnclaveComponents)) *MockKurtosisInstruction_TryResolveWith_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(kurtosis_instruction.KurtosisInstruction), args[1].(*enclave_structure.EnclaveComponents)) + }) + return _c +} + +func (_c *MockKurtosisInstruction_TryResolveWith_Call) Return(_a0 enclave_structure.InstructionResolutionStatus) *MockKurtosisInstruction_TryResolveWith_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockKurtosisInstruction_TryResolveWith_Call) RunAndReturn(run func(kurtosis_instruction.KurtosisInstruction, *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus) *MockKurtosisInstruction_TryResolveWith_Call { + _c.Call.Return(run) + return _c +} + // ValidateAndUpdateEnvironment provides a mock function with given fields: environment func (_m *MockKurtosisInstruction) ValidateAndUpdateEnvironment(environment *startosis_validator.ValidatorEnvironment) error { ret := _m.Called(environment) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/plan_module/plan_module.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/plan_module/plan_module.go index 007a1d551f..3090b9bd5a 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/plan_module/plan_module.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/plan_module/plan_module.go @@ -1,6 +1,7 @@ package plan_module import ( + "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/kurtosis_plan_instruction" @@ -14,12 +15,13 @@ const ( func PlanModule( instructionsPlan *instructions_plan.InstructionsPlan, + enclaveComponents *enclave_structure.EnclaveComponents, instructionsPlanMask *resolver.InstructionsPlanMask, kurtosisPlanInstructions []*kurtosis_plan_instruction.KurtosisPlanInstruction, ) *starlarkstruct.Module { moduleBuiltins := starlark.StringDict{} for _, planInstruction := range kurtosisPlanInstructions { - wrappedPlanInstruction := kurtosis_plan_instruction.NewKurtosisPlanInstructionWrapper(planInstruction, instructionsPlanMask, instructionsPlan) + wrappedPlanInstruction := kurtosis_plan_instruction.NewKurtosisPlanInstructionWrapper(planInstruction, enclaveComponents, instructionsPlanMask, instructionsPlan) moduleBuiltins[planInstruction.GetName()] = starlark.NewBuiltin(planInstruction.GetName(), wrappedPlanInstruction.CreateBuiltin()) } diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/remove_connection/remove_connection.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/remove_connection/remove_connection.go index b34d188d3e..dfd1d0afc2 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/remove_connection/remove_connection.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/remove_connection/remove_connection.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network/service_network_types" + "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/kurtosis_instruction/shared_helpers" "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" @@ -88,6 +89,10 @@ func (builtin *RemoveConnectionCapabilities) Execute(ctx context.Context, _ *bui return instructionResult, nil } +func (builtin *RemoveConnectionCapabilities) TryResolveWith(_ bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, _ *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + return enclave_structure.InstructionIsNotResolvableAbort +} + func validateSubnetworks(value starlark.Value) *startosis_errors.InterpretationError { subnetworks, ok := value.(starlark.Tuple) if !ok { diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/remove_service/remove_service.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/remove_service/remove_service.go index 46c8bf94d5..2f04e89b45 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/remove_service/remove_service.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/remove_service/remove_service.go @@ -5,6 +5,7 @@ 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/service_network" + "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/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_plan_instruction" @@ -69,7 +70,7 @@ func (builtin *RemoveServiceCapabilities) Interpret(_ string, arguments *builtin } func (builtin *RemoveServiceCapabilities) Validate(_ *builtin_argument.ArgumentValuesSet, validatorEnvironment *startosis_validator.ValidatorEnvironment) *startosis_errors.ValidationError { - if !validatorEnvironment.DoesServiceNameExist(builtin.serviceName) { + if validatorEnvironment.DoesServiceNameExist(builtin.serviceName) == startosis_validator.ServiceNotFound { return startosis_errors.NewValidationError("There was an error validating '%v' as service name '%v' doesn't exist", RemoveServiceBuiltinName, builtin.serviceName) } validatorEnvironment.RemoveServiceName(builtin.serviceName) @@ -85,3 +86,7 @@ func (builtin *RemoveServiceCapabilities) Execute(ctx context.Context, _ *builti instructionResult := fmt.Sprintf("Service '%s' with service UUID '%s' removed", builtin.serviceName, serviceUUID) return instructionResult, nil } + +func (builtin *RemoveServiceCapabilities) TryResolveWith(_ bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, _ *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + return enclave_structure.InstructionIsNotResolvableAbort +} diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/render_templates/render_templates.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/render_templates/render_templates.go index 2fccb01cda..a2c1e946de 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/render_templates/render_templates.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/render_templates/render_templates.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network/render_templates" + "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/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_plan_instruction" @@ -129,6 +130,13 @@ func (builtin *RenderTemplatesCapabilities) Execute(_ context.Context, _ *builti return instructionResult, nil } +func (builtin *RenderTemplatesCapabilities) TryResolveWith(instructionsAreEqual bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, _ *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + if instructionsAreEqual { + return enclave_structure.InstructionIsEqual + } + return enclave_structure.InstructionIsUnknown +} + func parseTemplatesAndData(templatesAndData *starlark.Dict) (map[string]*render_templates.TemplateData, *startosis_errors.InterpretationError) { templateAndDataByDestRelFilepath := make(map[string]*render_templates.TemplateData) for _, relPathInFilesArtifactKey := range templatesAndData.Keys() { diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/request/request.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/request/request.go index f620e3267d..999a406397 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/request/request.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/request/request.go @@ -4,6 +4,7 @@ import ( "context" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" + "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/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_plan_instruction" @@ -161,7 +162,7 @@ func (builtin *RequestCapabilities) Interpret(_ string, arguments *builtin_argum } func (builtin *RequestCapabilities) Validate(_ *builtin_argument.ArgumentValuesSet, validatorEnvironment *startosis_validator.ValidatorEnvironment) *startosis_errors.ValidationError { - if serviceExists := validatorEnvironment.DoesServiceNameExist(builtin.serviceName); !serviceExists { + if validatorEnvironment.DoesServiceNameExist(builtin.serviceName) == startosis_validator.ServiceNotFound { return startosis_errors.NewValidationError("Tried creating a request for service '%s' which doesn't exist", builtin.serviceName) } if validationErr := recipe.ValidateHttpRequestRecipe(builtin.httpRequestRecipe, builtin.serviceName, validatorEnvironment); validationErr != nil { @@ -183,6 +184,15 @@ func (builtin *RequestCapabilities) Execute(ctx context.Context, _ *builtin_argu return instructionResult, err } +func (builtin *RequestCapabilities) TryResolveWith(instructionsAreEqual bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, enclaveComponents *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + if instructionsAreEqual && enclaveComponents.HasServiceBeenUpdated(builtin.serviceName) { + return enclave_structure.InstructionIsUpdate + } else if instructionsAreEqual { + return enclave_structure.InstructionIsEqual + } + return enclave_structure.InstructionIsUnknown +} + func (builtin *RequestCapabilities) isAcceptableCode(recipeResult map[string]starlark.Comparable) bool { isAcceptableCode := false for _, acceptableCode := range builtin.acceptableCodes { diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/set_connection/set_connection.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/set_connection/set_connection.go index e6daa17903..ef22803380 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/set_connection/set_connection.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/set_connection/set_connection.go @@ -6,6 +6,7 @@ import ( "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network/partition_topology" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network/service_network_types" + "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/kurtosis_instruction/shared_helpers" "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" @@ -134,6 +135,10 @@ func (builtin *SetConnectionCapabilities) Execute(ctx context.Context, _ *builti return instructionResult, nil } +func (builtin *SetConnectionCapabilities) TryResolveWith(_ bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, _ *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + return enclave_structure.InstructionIsNotResolvableAbort +} + func validateSubnetworks(value starlark.Value) *startosis_errors.InterpretationError { subnetworks, ok := value.(starlark.Tuple) if !ok { diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/start_service/start_service.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/start_service/start_service.go index 9ea248555b..e385e9cc22 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/start_service/start_service.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/start_service/start_service.go @@ -5,6 +5,7 @@ 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/service_network" + "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/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_plan_instruction" @@ -69,7 +70,7 @@ func (builtin *StartServiceCapabilities) Interpret(_ string, arguments *builtin_ } func (builtin *StartServiceCapabilities) Validate(_ *builtin_argument.ArgumentValuesSet, validatorEnvironment *startosis_validator.ValidatorEnvironment) *startosis_errors.ValidationError { - if !validatorEnvironment.DoesServiceNameExist(builtin.serviceName) { + if validatorEnvironment.DoesServiceNameExist(builtin.serviceName) == startosis_validator.ServiceNotFound { return startosis_errors.NewValidationError("There was an error validating '%v' as service name '%v' doesn't exist", StartServiceBuiltinName, builtin.serviceName) } return nil @@ -83,3 +84,7 @@ func (builtin *StartServiceCapabilities) Execute(ctx context.Context, _ *builtin instructionResult := fmt.Sprintf("Service '%s' started", builtin.serviceName) return instructionResult, nil } + +func (builtin *StartServiceCapabilities) TryResolveWith(_ bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, _ *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + return enclave_structure.InstructionIsNotResolvableAbort +} diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/stop_service/stop_service.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/stop_service/stop_service.go index bb69d8f989..fda2b3db72 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/stop_service/stop_service.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/stop_service/stop_service.go @@ -5,6 +5,7 @@ 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/service_network" + "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/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_plan_instruction" @@ -69,7 +70,7 @@ func (builtin *StopServiceCapabilities) Interpret(_ string, arguments *builtin_a } func (builtin *StopServiceCapabilities) Validate(_ *builtin_argument.ArgumentValuesSet, validatorEnvironment *startosis_validator.ValidatorEnvironment) *startosis_errors.ValidationError { - if !validatorEnvironment.DoesServiceNameExist(builtin.serviceName) { + if validatorEnvironment.DoesServiceNameExist(builtin.serviceName) == startosis_validator.ServiceNotFound { return startosis_errors.NewValidationError("There was an error validating '%v' as service name '%v' doesn't exist", StopServiceBuiltinName, builtin.serviceName) } return nil @@ -83,3 +84,7 @@ func (builtin *StopServiceCapabilities) Execute(ctx context.Context, _ *builtin_ instructionResult := fmt.Sprintf("Service '%s' stopped", builtin.serviceName) return instructionResult, nil } + +func (builtin *StopServiceCapabilities) TryResolveWith(_ bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, _ *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + return enclave_structure.InstructionIsNotResolvableAbort +} diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/store_service_files/store_service_files.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/store_service_files/store_service_files.go index 260aee8bc1..46864acec8 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/store_service_files/store_service_files.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/store_service_files/store_service_files.go @@ -5,6 +5,7 @@ import ( "fmt" kurtosis_backend_service "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" + "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/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_plan_instruction" @@ -106,7 +107,7 @@ func (builtin *StoreServiceFilesCapabilities) Interpret(_ string, arguments *bui } func (builtin *StoreServiceFilesCapabilities) Validate(_ *builtin_argument.ArgumentValuesSet, validatorEnvironment *startosis_validator.ValidatorEnvironment) *startosis_errors.ValidationError { - if !validatorEnvironment.DoesServiceNameExist(builtin.serviceName) { + if validatorEnvironment.DoesServiceNameExist(builtin.serviceName) == startosis_validator.ServiceNotFound { return startosis_errors.NewValidationError("There was an error validating '%v' with service name '%v' that does not exist", StoreServiceFilesBuiltinName, builtin.serviceName) } if validatorEnvironment.DoesArtifactNameExist(builtin.artifactName) { @@ -124,3 +125,10 @@ func (builtin *StoreServiceFilesCapabilities) Execute(ctx context.Context, _ *bu instructionResult := fmt.Sprintf("Files with artifact name '%s' uploaded with artifact UUID '%s'", builtin.artifactName, artifactUuid) return instructionResult, nil } + +func (builtin *StoreServiceFilesCapabilities) TryResolveWith(instructionsAreEqual bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, _ *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + if instructionsAreEqual { + return enclave_structure.InstructionIsEqual + } + return enclave_structure.InstructionIsUnknown +} diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_python.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_python.go index 8efcd3dde5..0e84fbb340 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_python.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_python.go @@ -8,6 +8,7 @@ import ( "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/files_artifacts_expansion" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" + "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/kurtosis_instruction/shared_helpers/magic_string_helper" "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" @@ -325,6 +326,13 @@ func (builtin *RunPythonCapabilities) Execute(ctx context.Context, _ *builtin_ar return instructionResult, err } +func (builtin *RunPythonCapabilities) TryResolveWith(instructionsAreEqual bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, _ *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + if instructionsAreEqual { + return enclave_structure.InstructionIsEqual + } + return enclave_structure.InstructionIsUnknown +} + func setupRequiredPackages(ctx context.Context, builtin *RunPythonCapabilities) (*exec_result.ExecResult, error) { var maybePackagesWithRuntimeValuesReplaced []string for _, pythonPackage := range builtin.packages { diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_sh.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_sh.go index d9d1e492ea..9113caf08c 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_sh.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/run_sh.go @@ -6,6 +6,7 @@ import ( "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/files_artifacts_expansion" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" + "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/kurtosis_instruction/shared_helpers/magic_string_helper" "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" @@ -231,6 +232,13 @@ func (builtin *RunShCapabilities) Execute(ctx context.Context, _ *builtin_argume return instructionResult, err } +func (builtin *RunShCapabilities) TryResolveWith(instructionsAreEqual bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, _ *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + if instructionsAreEqual { + return enclave_structure.InstructionIsEqual + } + return enclave_structure.InstructionIsUnknown +} + func getCommandToRun(builtin *RunShCapabilities) (string, error) { // replace future references to actual strings maybeSubCommandWithRuntimeValues, err := magic_string_helper.ReplaceRuntimeValueInString(builtin.run, builtin.runtimeValueStore) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/update_service/update_service.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/update_service/update_service.go index 9f8ade15fb..10f3ac5e9e 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/update_service/update_service.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/update_service/update_service.go @@ -7,6 +7,7 @@ import ( "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network/partition_topology" + "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/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_plan_instruction" @@ -104,7 +105,7 @@ func (builtin *UpdateServiceCapabilities) Validate(_ *builtin_argument.ArgumentV return startosis_errors.NewValidationError("Service was about to be moved to subnetwork '%s' but the Kurtosis enclave was started with subnetwork capabilities disabled. Make sure to run the Starlark script with subnetwork enabled.", *builtin.updateServiceConfig.Subnetwork) } } - if !validatorEnvironment.DoesServiceNameExist(builtin.serviceName) { + if validatorEnvironment.DoesServiceNameExist(builtin.serviceName) == startosis_validator.ServiceNotFound { return startosis_errors.NewValidationError("There was an error validating '%v' as service name '%v' does not exist", UpdateServiceBuiltinName, builtin.serviceName) } return nil @@ -135,6 +136,10 @@ func (builtin *UpdateServiceCapabilities) Execute(ctx context.Context, _ *builti return instructionResult, nil } +func (builtin *UpdateServiceCapabilities) TryResolveWith(_ bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, _ *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + return enclave_structure.InstructionIsNotResolvableAbort +} + func validateAndConvertConfig(rawConfig starlark.Value) (*kurtosis_core_rpc_api_bindings.UpdateServiceConfig, *startosis_errors.InterpretationError) { config, ok := rawConfig.(*update_service_config.UpdateServiceConfig) if !ok { diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/upload_files/upload_files.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/upload_files/upload_files.go index 0d7a523ecf..c981f257a5 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/upload_files/upload_files.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/upload_files/upload_files.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/shared_utils" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" + "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/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_plan_instruction" @@ -128,3 +129,10 @@ func (builtin *UploadFilesCapabilities) Execute(_ context.Context, _ *builtin_ar instructionResult := fmt.Sprintf("Files with artifact name '%s' uploaded with artifact UUID '%s'", builtin.artifactName, filesArtifactUuid) return instructionResult, nil } + +func (builtin *UploadFilesCapabilities) TryResolveWith(instructionsAreEqual bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, _ *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + if instructionsAreEqual { + return enclave_structure.InstructionIsEqual + } + return enclave_structure.InstructionIsUnknown +} diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/wait/wait.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/wait/wait.go index 7495c7715d..9b86242730 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/wait/wait.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/wait/wait.go @@ -5,6 +5,7 @@ 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/service_network" + "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/kurtosis_instruction/assert" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/shared_helpers" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework" @@ -221,7 +222,7 @@ func (builtin *WaitCapabilities) Interpret(_ string, arguments *builtin_argument } func (builtin *WaitCapabilities) Validate(_ *builtin_argument.ArgumentValuesSet, validatorEnvironment *startosis_validator.ValidatorEnvironment) *startosis_errors.ValidationError { - if serviceExists := validatorEnvironment.DoesServiceNameExist(builtin.serviceName); !serviceExists { + if validatorEnvironment.DoesServiceNameExist(builtin.serviceName) == startosis_validator.ServiceNotFound { return startosis_errors.NewValidationError("Tried creating a wait for service '%s' which doesn't exist", builtin.serviceName) } @@ -271,3 +272,12 @@ func (builtin *WaitCapabilities) Execute(ctx context.Context, _ *builtin_argumen return instructionResult, nil } + +func (builtin *WaitCapabilities) TryResolveWith(instructionsAreEqual bool, _ kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities, enclaveComponents *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + if instructionsAreEqual && enclaveComponents.HasServiceBeenUpdated(builtin.serviceName) { + return enclave_structure.InstructionIsUpdate + } else if instructionsAreEqual { + return enclave_structure.InstructionIsEqual + } + return enclave_structure.InstructionIsUnknown +} diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction/kurtosis_plan_instruction.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction/kurtosis_plan_instruction.go index 4c14bc7c9b..6f8bcd9313 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction/kurtosis_plan_instruction.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction/kurtosis_plan_instruction.go @@ -1,10 +1,12 @@ package kurtosis_plan_instruction import ( + "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" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_errors" + "github.com/kurtosis-tech/stacktrace" "github.com/sirupsen/logrus" "go.starlark.net/starlark" ) @@ -22,15 +24,17 @@ type KurtosisPlanInstruction struct { type KurtosisPlanInstructionWrapper struct { *KurtosisPlanInstruction + enclaveComponents *enclave_structure.EnclaveComponents instructionPlanMask *resolver.InstructionsPlanMask // TODO: This can be changed to KurtosisPlanInstructionInternal when we get rid of KurtosisInstruction instructionsPlan *instructions_plan.InstructionsPlan } -func NewKurtosisPlanInstructionWrapper(instruction *KurtosisPlanInstruction, instructionPlanMask *resolver.InstructionsPlanMask, instructionsPlan *instructions_plan.InstructionsPlan) *KurtosisPlanInstructionWrapper { +func NewKurtosisPlanInstructionWrapper(instruction *KurtosisPlanInstruction, enclaveComponents *enclave_structure.EnclaveComponents, instructionPlanMask *resolver.InstructionsPlanMask, instructionsPlan *instructions_plan.InstructionsPlan) *KurtosisPlanInstructionWrapper { return &KurtosisPlanInstructionWrapper{ KurtosisPlanInstruction: instruction, + enclaveComponents: enclaveComponents, instructionPlanMask: instructionPlanMask, instructionsPlan: instructionsPlan, } @@ -50,41 +54,58 @@ func (builtin *KurtosisPlanInstructionWrapper) CreateBuiltin() func(thread *star return nil, interpretationErr } - var instructionPulledFromMaskIdx int - var instructionPulledFromMaskMaybe *instructions_plan.ScheduledInstruction + var scheduledInstructionPulledFromMaskMaybe *instructions_plan.ScheduledInstruction + var instructionResolutionStatus enclave_structure.InstructionResolutionStatus if builtin.instructionPlanMask.HasNext() { - instructionPulledFromMaskIdx, instructionPulledFromMaskMaybe = builtin.instructionPlanMask.Next() - if instructionPulledFromMaskMaybe != nil && instructionPulledFromMaskMaybe.GetInstruction().String() != instructionWrapper.String() { - // if the instructions differs, then the mask is invalid - builtin.instructionPlanMask.MarkAsInvalid() - logrus.Debugf("The instruction number %d in the plan mask did not match the newly interpreter "+ - "instruction and therefore the plan mask was marked as invalid:\nInstruction from mask - '%s'"+ - "\nInstruction from interpretation: '%s'", - instructionPulledFromMaskIdx, - instructionPulledFromMaskMaybe.GetInstruction().String(), - instructionWrapper.String()) - // TODO: we could interrupt the interpretation here, because with an invalid mask the list of - // instruction generated will be invalid anyway. Though we currently don't have a nive way to - // interrupt an interpretation in progress (other than by throwing an error, which would be - // misleading here) - // To properly solve that, I think we should switch to an interactive interpretation where we - // interpret each instruction one after the other, and evaluating the state after each step + _, scheduledInstructionPulledFromMaskMaybe = builtin.instructionPlanMask.Next() + if scheduledInstructionPulledFromMaskMaybe != nil { + instructionResolutionStatus = instructionWrapper.TryResolveWith(scheduledInstructionPulledFromMaskMaybe.GetInstruction(), builtin.enclaveComponents) + } else { + instructionResolutionStatus = instructionWrapper.TryResolveWith(nil, builtin.enclaveComponents) } + } else { + instructionResolutionStatus = instructionWrapper.TryResolveWith(nil, builtin.enclaveComponents) } - if instructionPulledFromMaskMaybe != nil { - // If there's a mask for this instruction, add the mask the plan and returned the mask's returned value - builtin.instructionsPlan.AddScheduledInstruction(instructionPulledFromMaskMaybe).Executed(true).ImportedFromCurrentEnclavePlan(false) - return instructionPulledFromMaskMaybe.GetReturnedValue(), nil - } else { + switch instructionResolutionStatus { + case enclave_structure.InstructionIsEqual: + // add instruction from the mask and mark it as executed but not imported from the current enclave plan + builtin.instructionsPlan.AddScheduledInstruction(scheduledInstructionPulledFromMaskMaybe).Executed(true).ImportedFromCurrentEnclavePlan(false) + return scheduledInstructionPulledFromMaskMaybe.GetReturnedValue(), nil + case enclave_structure.InstructionIsUpdate: // otherwise add the instruction as a new one to the plan and return its own returned value + if err := builtin.instructionsPlan.AddInstruction(instructionWrapper, returnedFutureValue); err != nil { + return nil, startosis_errors.WrapWithInterpretationError(err, "Unable to add Kurtosis instruction '%s' at position '%s' to the plan currently being assembled. This is a Kurtosis internal bug", + instructionWrapper.String(), + instructionWrapper.GetPositionInOriginalScript().String()) + } + return returnedFutureValue, nil + case enclave_structure.InstructionIsUnknown: + if err := builtin.instructionsPlan.AddInstruction(instructionWrapper, returnedFutureValue); err != nil { + return nil, startosis_errors.WrapWithInterpretationError(err, + "Unable to add Kurtosis instruction '%s' at position '%s' to the plan currently being assembled. This is a Kurtosis internal bug", + instructionWrapper.String(), + instructionWrapper.GetPositionInOriginalScript().String()) + } + if scheduledInstructionPulledFromMaskMaybe != nil { + builtin.instructionPlanMask.MarkAsInvalid() + logrus.Debugf("Marking the plan as invalid as instruction '%s' differs from '%s'", + instructionWrapper.String(), scheduledInstructionPulledFromMaskMaybe.GetInstruction().String()) + } + return returnedFutureValue, nil + case enclave_structure.InstructionIsNotResolvableAbort: + // if the instructions differs, then the mask is invalid + builtin.instructionPlanMask.MarkAsInvalid() + logrus.Debugf("Marking the plan as invalid as instruction '%s' had the following resolution status: '%s'", + instructionWrapper.String(), instructionResolutionStatus) if err := builtin.instructionsPlan.AddInstruction(instructionWrapper, returnedFutureValue); err != nil { return nil, startosis_errors.WrapWithInterpretationError(err, - "Unable to add Kurtosis instruction '%s' at position '%s' to the current plan being assembled. This is a Kurtosis internal bug", + "Unable to add Kurtosis instruction '%s' at position '%s' to the plan currently being assembled. This is a Kurtosis internal bug", instructionWrapper.String(), instructionWrapper.GetPositionInOriginalScript().String()) } return returnedFutureValue, nil } + return nil, stacktrace.NewError("Unexpected error, resolution status of instruction '%s' did not match any of the covered case.", instructionResolutionStatus) } } diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction/kurtosis_plan_instruction_capabilities.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction/kurtosis_plan_instruction_capabilities.go index 039fb66353..5ffda026b5 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction/kurtosis_plan_instruction_capabilities.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction/kurtosis_plan_instruction_capabilities.go @@ -2,6 +2,7 @@ package kurtosis_plan_instruction import ( "context" + "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/kurtosis_starlark_framework/builtin_argument" "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_validator" @@ -14,4 +15,6 @@ type KurtosisPlanInstructionCapabilities interface { Validate(arguments *builtin_argument.ArgumentValuesSet, validatorEnvironment *startosis_validator.ValidatorEnvironment) *startosis_errors.ValidationError Execute(ctx context.Context, arguments *builtin_argument.ArgumentValuesSet) (string, error) + + TryResolveWith(instructionsAreEqual bool, other KurtosisPlanInstructionCapabilities, enclaveComponents *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus } diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction/kurtosis_plan_instruction_internal.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction/kurtosis_plan_instruction_internal.go index f1092e5e24..1f7fa008f3 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction/kurtosis_plan_instruction_internal.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction/kurtosis_plan_instruction_internal.go @@ -4,6 +4,8 @@ import ( "context" "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/binding_constructors" + "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/kurtosis_instruction" "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/startosis_errors" @@ -75,6 +77,25 @@ func (builtin *kurtosisPlanInstructionInternal) Execute(ctx context.Context) (*s return &result, nil } +func (builtin *kurtosisPlanInstructionInternal) TryResolveWith(other kurtosis_instruction.KurtosisInstruction, enclaveComponents *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + isAnAbortAllInstruction := builtin.capabilities.TryResolveWith(false, nil, enclaveComponents) == enclave_structure.InstructionIsNotResolvableAbort + if isAnAbortAllInstruction { + return enclave_structure.InstructionIsNotResolvableAbort + } + + if other == nil { + return enclave_structure.InstructionIsUnknown + } + + otherPlanInstruction, ok := other.(*kurtosisPlanInstructionInternal) + if !ok { + return enclave_structure.InstructionIsUnknown + } + + instructionsAreEqual := builtin.String() == other.String() + return builtin.capabilities.TryResolveWith(instructionsAreEqual, otherPlanInstruction.capabilities, enclaveComponents) +} + func (builtin *kurtosisPlanInstructionInternal) interpret(locatorOfModuleInWhichThisBuiltInIsBeingCalled string) (starlark.Value, *startosis_errors.InterpretationError) { result, interpretationErr := builtin.capabilities.Interpret(locatorOfModuleInWhichThisBuiltInIsBeingCalled, builtin.GetArguments()) if interpretationErr != 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 18e4b939cd..6c8731b2ea 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 @@ -36,6 +36,7 @@ func (t *addServiceTestCase) GetInstruction() *kurtosis_plan_instruction.Kurtosi serviceNetwork := service_network.NewMockServiceNetwork(t) runtimeValueStore := runtime_value_store.NewRuntimeValueStore() + serviceNetwork.EXPECT().GetServiceRegistration(TestServiceName).Times(1).Return(nil, false) serviceNetwork.EXPECT().AddService( mock.Anything, TestServiceName, diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_services_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_services_framework_test.go index 9f8dbe9e1b..4dceb24bbd 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_services_framework_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_services_framework_test.go @@ -39,6 +39,17 @@ func (t *addServicesTestCase) GetInstruction() *kurtosis_plan_instruction.Kurtos serviceNetwork := service_network.NewMockServiceNetwork(t) runtimeValueStore := runtime_value_store.NewRuntimeValueStore() + serviceNetwork.EXPECT().GetServiceRegistration(TestServiceName).Times(1).Return(nil, false) + serviceNetwork.EXPECT().GetServiceRegistration(TestServiceName2).Times(1).Return(nil, false) + serviceNetwork.EXPECT().UpdateServices( + mock.Anything, + map[service.ServiceName]*service.ServiceConfig{}, + mock.Anything, + ).Times(1).Return( + map[service.ServiceName]*service.Service{}, + map[service.ServiceName]error{}, + nil, + ) serviceNetwork.EXPECT().AddServices( mock.Anything, mock.MatchedBy(func(configs map[service.ServiceName]*service.ServiceConfig) bool { 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 5c0e676631..6c5ba16c12 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 @@ -5,6 +5,7 @@ import ( "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" @@ -81,8 +82,9 @@ func testKurtosisPlanInstruction(t *testing.T, builtin KurtosisPlanInstructionBa predeclared := getBasePredeclaredDict(t) // Add the KurtosisPlanInstruction that is being tested instructionFromBuiltin := builtin.GetInstruction() + emptyEnclaveComponents := enclave_structure.NewEnclaveComponents() emptyInstructionsPlanMask := resolver.NewInstructionsPlanMask(0) - instructionWrapper := kurtosis_plan_instruction.NewKurtosisPlanInstructionWrapper(instructionFromBuiltin, emptyInstructionsPlanMask, instructionsPlan) + instructionWrapper := kurtosis_plan_instruction.NewKurtosisPlanInstructionWrapper(instructionFromBuiltin, emptyEnclaveComponents, emptyInstructionsPlanMask, instructionsPlan) predeclared[instructionWrapper.GetName()] = starlark.NewBuiltin(instructionWrapper.GetName(), instructionWrapper.CreateBuiltin()) starlarkCode := builtin.GetStarlarkCode() diff --git a/core/server/api_container/server/startosis_engine/runtime_value_store/runtime_value_store.go b/core/server/api_container/server/startosis_engine/runtime_value_store/runtime_value_store.go index 73e5ff6293..9bd2143512 100644 --- a/core/server/api_container/server/startosis_engine/runtime_value_store/runtime_value_store.go +++ b/core/server/api_container/server/startosis_engine/runtime_value_store/runtime_value_store.go @@ -1,18 +1,21 @@ package runtime_value_store import ( + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/uuid_generator" "github.com/kurtosis-tech/stacktrace" "go.starlark.net/starlark" ) type RuntimeValueStore struct { - recipeResultMap map[string]map[string]starlark.Comparable + recipeResultMap map[string]map[string]starlark.Comparable + serviceAssociatedValues map[service.ServiceName]string } func NewRuntimeValueStore() *RuntimeValueStore { return &RuntimeValueStore{ - recipeResultMap: make(map[string]map[string]starlark.Comparable), + recipeResultMap: make(map[string]map[string]starlark.Comparable), + serviceAssociatedValues: make(map[service.ServiceName]string), } } @@ -25,6 +28,19 @@ func (re *RuntimeValueStore) CreateValue() (string, error) { return uuid, nil } +func (re *RuntimeValueStore) GetOrCreateValueAssociatedWithService(serviceName service.ServiceName) (string, error) { + if uuid, found := re.serviceAssociatedValues[serviceName]; found { + delete(re.recipeResultMap, uuid) // deleting old values so that they do not interfere until that are set again + return uuid, nil + } + uuid, err := re.CreateValue() + if err != nil { + return "", stacktrace.Propagate(err, "An error occurred creating a simple runtime value") + } + re.serviceAssociatedValues[serviceName] = uuid + return uuid, nil +} + func (re *RuntimeValueStore) SetValue(uuid string, value map[string]starlark.Comparable) { re.recipeResultMap[uuid] = value } 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 182be6fb31..28b132116c 100644 --- a/core/server/api_container/server/startosis_engine/startosis_interpreter.go +++ b/core/server/api_container/server/startosis_engine/startosis_interpreter.go @@ -7,6 +7,7 @@ import ( "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" "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/builtins/print_builtin" + "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_instruction/plan_module" @@ -91,8 +92,9 @@ func (interpreter *StartosisInterpreter) InterpretAndOptimizePlan( ) (string, *instructions_plan.InstructionsPlan, *kurtosis_core_rpc_api_bindings.StarlarkInterpretationError) { // run interpretation with no mask at all to generate the list of instructions as if the enclave was empty + enclaveComponents := enclave_structure.NewEnclaveComponents() emptyPlanInstructionsMask := resolver.NewInstructionsPlanMask(0) - naiveInstructionsPlanSerializedScriptOutput, naiveInstructionsPlan, interpretationErrorApi := interpreter.Interpret(ctx, packageId, mainFunctionName, relativePathtoMainFile, serializedStarlark, serializedJsonParams, emptyPlanInstructionsMask) + naiveInstructionsPlanSerializedScriptOutput, naiveInstructionsPlan, interpretationErrorApi := interpreter.Interpret(ctx, packageId, mainFunctionName, relativePathtoMainFile, serializedStarlark, serializedJsonParams, enclaveComponents, emptyPlanInstructionsMask) if interpretationErrorApi != nil { return startosis_constants.NoOutputObject, nil, interpretationErrorApi } @@ -162,7 +164,7 @@ func (interpreter *StartosisInterpreter) InterpretAndOptimizePlan( } // Now that we have a potential plan mask, try running interpretation again using this plan mask - attemptSerializedScriptOutput, attemptInstructionsPlan, interpretationErrorApi := interpreter.Interpret(ctx, packageId, mainFunctionName, relativePathtoMainFile, serializedStarlark, serializedJsonParams, potentialMask) + attemptSerializedScriptOutput, attemptInstructionsPlan, interpretationErrorApi := interpreter.Interpret(ctx, packageId, mainFunctionName, relativePathtoMainFile, serializedStarlark, serializedJsonParams, enclaveComponents, potentialMask) if interpretationErrorApi != nil { // Note: there's no real reason why this interpretation would fail with an error, given that the package // has been interpreted once already (right above). But to be on the safe side, check the error @@ -231,6 +233,7 @@ func (interpreter *StartosisInterpreter) Interpret( relativePathtoMainFile string, serializedStarlark string, serializedJsonParams string, + enclaveComponents *enclave_structure.EnclaveComponents, instructionsPlanMask *resolver.InstructionsPlanMask, ) (string, *instructions_plan.InstructionsPlan, *kurtosis_core_rpc_api_bindings.StarlarkInterpretationError) { interpreter.mutex.Lock() @@ -278,7 +281,7 @@ func (interpreter *StartosisInterpreter) Interpret( firstParamName, _ := mainFunction.Param(planParamIndex) if firstParamName == planParamName { kurtosisPlanInstructions := KurtosisPlanInstructions(interpreter.serviceNetwork, interpreter.recipeExecutor, interpreter.moduleContentProvider) - planModule := plan_module.PlanModule(newInstructionsPlan, instructionsPlanMask, kurtosisPlanInstructions) + planModule := plan_module.PlanModule(newInstructionsPlan, enclaveComponents, instructionsPlanMask, kurtosisPlanInstructions) argsTuple = append(argsTuple, planModule) } @@ -387,7 +390,10 @@ func findFirstEqualInstructionPastIndex(currentEnclaveInstructionsList []*instru return -1 // no result as the naiveInstructionsList is empty } for i := minIndex; i < len(currentEnclaveInstructionsList); i++ { - if currentEnclaveInstructionsList[i].GetInstruction().String() == naiveInstructionsList[0].GetInstruction().String() { + // We just need to compare instructions to see if they match, without needing any enclave specific context here + fakeEnclaveComponent := enclave_structure.NewEnclaveComponents() + instructionResolutionResult := naiveInstructionsList[0].GetInstruction().TryResolveWith(currentEnclaveInstructionsList[i].GetInstruction(), fakeEnclaveComponent) + if instructionResolutionResult == enclave_structure.InstructionIsEqual || instructionResolutionResult == enclave_structure.InstructionIsUpdate { return i } } diff --git a/core/server/api_container/server/startosis_engine/startosis_interpreter_idempotent_test.go b/core/server/api_container/server/startosis_engine/startosis_interpreter_idempotent_test.go index 83323daf26..4ca250166f 100644 --- a/core/server/api_container/server/startosis_engine/startosis_interpreter_idempotent_test.go +++ b/core/server/api_container/server/startosis_engine/startosis_interpreter_idempotent_test.go @@ -3,21 +3,18 @@ package startosis_engine import ( "context" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" - "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/kurtosis_instruction" - "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/mock_instruction" + "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/resolver" "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_constants" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages/mock_package_content_provider" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "go.starlark.net/starlark" "testing" ) const ( noInputParams = "{}" - noReturnValue = starlark.None ) type StartosisInterpreterIdempotentTestSuite struct { @@ -46,31 +43,26 @@ func (suite *StartosisInterpreterIdempotentTestSuite) TearDownTest() { // Package to run -> [`print("instruction1")` `print("instruction2")` `print("instruction3")`] // Check that this results in the entire set of instruction being skipped func (suite *StartosisInterpreterIdempotentTestSuite) TestInterpretAndOptimize_IdenticalPackage() { - packageContentProvider := mock_package_content_provider.NewMockPackageContentProvider() - defer packageContentProvider.RemoveAll() - runtimeValueStore := runtime_value_store.NewRuntimeValueStore() - testServiceNetwork := service_network.NewMockServiceNetwork(suite.T()) - interpreter := NewStartosisInterpreter(testServiceNetwork, packageContentProvider, runtimeValueStore) - script := ` -def run(plan, args): + script := `def run(plan, args): plan.print("instruction1") plan.print("instruction2") plan.print("instruction3") ` + // Interpretation of the initial script to generate the current enclave plan + _, currentEnclavePlan, interpretationApiErr := suite.interpreter.Interpret( + context.Background(), + startosis_constants.PackageIdPlaceholderForStandaloneScript, + useDefaultMainFunctionName, + startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, + script, + noInputParams, + enclave_structure.NewEnclaveComponents(), + resolver.NewInstructionsPlanMask(0)) + require.Nil(suite.T(), interpretationApiErr) + require.Equal(suite.T(), 3, currentEnclavePlan.Size()) - instruction1Str := `print(msg="instruction1")` - instruction1 := suite.newMockInstruction(instruction1Str) - instruction2Str := `print(msg="instruction2")` - instruction2 := suite.newMockInstruction(instruction2Str) - instruction3Str := `print(msg="instruction3")` - instruction3 := suite.newMockInstruction(instruction3Str) - - currentEnclavePlan := instructions_plan.NewInstructionsPlan() - require.Nil(suite.T(), currentEnclavePlan.AddInstruction(instruction1, noReturnValue)) - require.Nil(suite.T(), currentEnclavePlan.AddInstruction(instruction2, noReturnValue)) - require.Nil(suite.T(), currentEnclavePlan.AddInstruction(instruction3, noReturnValue)) - - _, instructionsPlan, interpretationError := interpreter.InterpretAndOptimizePlan( + // Interpret the updated script against the current enclave plan + _, instructionsPlan, interpretationError := suite.interpreter.InterpretAndOptimizePlan( context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, @@ -86,17 +78,17 @@ def run(plan, args): require.Equal(suite.T(), 3, len(instructionSequence)) scheduledInstruction1 := instructionSequence[0] - require.Equal(suite.T(), instruction1Str, scheduledInstruction1.GetInstruction().String()) + require.Equal(suite.T(), `print(msg="instruction1")`, scheduledInstruction1.GetInstruction().String()) require.False(suite.T(), scheduledInstruction1.IsImportedFromCurrentEnclavePlan()) require.True(suite.T(), scheduledInstruction1.IsExecuted()) scheduledInstruction2 := instructionSequence[1] - require.Equal(suite.T(), instruction2Str, scheduledInstruction2.GetInstruction().String()) + require.Equal(suite.T(), `print(msg="instruction2")`, scheduledInstruction2.GetInstruction().String()) require.False(suite.T(), scheduledInstruction2.IsImportedFromCurrentEnclavePlan()) require.True(suite.T(), scheduledInstruction2.IsExecuted()) scheduledInstruction3 := instructionSequence[2] - require.Equal(suite.T(), instruction3Str, scheduledInstruction3.GetInstruction().String()) + require.Equal(suite.T(), `print(msg="instruction3")`, scheduledInstruction3.GetInstruction().String()) require.False(suite.T(), scheduledInstruction3.IsImportedFromCurrentEnclavePlan()) require.True(suite.T(), scheduledInstruction3.IsExecuted()) } @@ -107,28 +99,35 @@ def run(plan, args): // Check that the first two instructions are already executed, and the last one is in the new plan marked as not // executed func (suite *StartosisInterpreterIdempotentTestSuite) TestInterpretAndOptimize_AppendNewInstruction() { - script := ` -def run(plan, args): + initialScript := `def run(plan, args): plan.print("instruction1") plan.print("instruction2") - plan.print("instruction3") ` + // Interpretation of the initial script to generate the current enclave plan + _, currentEnclavePlan, interpretationApiErr := suite.interpreter.Interpret( + context.Background(), + startosis_constants.PackageIdPlaceholderForStandaloneScript, + useDefaultMainFunctionName, + startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, + initialScript, + noInputParams, + enclave_structure.NewEnclaveComponents(), + resolver.NewInstructionsPlanMask(0)) + require.Nil(suite.T(), interpretationApiErr) + require.Equal(suite.T(), 2, currentEnclavePlan.Size()) - instruction1Str := `print(msg="instruction1")` - instruction1 := suite.newMockInstruction(instruction1Str) - instruction2Str := `print(msg="instruction2")` - instruction2 := suite.newMockInstruction(instruction2Str) - - currentEnclavePlan := instructions_plan.NewInstructionsPlan() - require.Nil(suite.T(), currentEnclavePlan.AddInstruction(instruction1, noReturnValue)) - require.Nil(suite.T(), currentEnclavePlan.AddInstruction(instruction2, noReturnValue)) - + updatedScript := `def run(plan, args): + plan.print("instruction1") + plan.print("instruction2") + plan.print("instruction3") +` + // Interpret the updated script against the current enclave plan _, instructionsPlan, interpretationError := suite.interpreter.InterpretAndOptimizePlan( context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, - script, + updatedScript, noInputParams, currentEnclavePlan, ) @@ -139,12 +138,12 @@ def run(plan, args): require.Equal(suite.T(), 3, len(instructionSequence)) scheduledInstruction1 := instructionSequence[0] - require.Equal(suite.T(), instruction1Str, scheduledInstruction1.GetInstruction().String()) + require.Equal(suite.T(), `print(msg="instruction1")`, scheduledInstruction1.GetInstruction().String()) require.False(suite.T(), scheduledInstruction1.IsImportedFromCurrentEnclavePlan()) require.True(suite.T(), scheduledInstruction1.IsExecuted()) scheduledInstruction2 := instructionSequence[1] - require.Equal(suite.T(), instruction2Str, scheduledInstruction2.GetInstruction().String()) + require.Equal(suite.T(), `print(msg="instruction2")`, scheduledInstruction2.GetInstruction().String()) require.False(suite.T(), scheduledInstruction2.IsImportedFromCurrentEnclavePlan()) require.True(suite.T(), scheduledInstruction2.IsExecuted()) @@ -160,26 +159,33 @@ def run(plan, args): // Check that the first two instructions are marked as imported from a previous plan, already executed, and the last // one is in the new plan marked as not executed func (suite *StartosisInterpreterIdempotentTestSuite) TestInterpretAndOptimize_DisjointInstructionSet() { - script := ` -def run(plan, args): - plan.print("instruction3") + initialScript := `def run(plan, args): + plan.print("instruction1") + plan.print("instruction2") ` + // Interpretation of the initial script to generate the current enclave plan + _, currentEnclavePlan, interpretationApiErr := suite.interpreter.Interpret( + context.Background(), + startosis_constants.PackageIdPlaceholderForStandaloneScript, + useDefaultMainFunctionName, + startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, + initialScript, + noInputParams, + enclave_structure.NewEnclaveComponents(), + resolver.NewInstructionsPlanMask(0)) + require.Nil(suite.T(), interpretationApiErr) + require.Equal(suite.T(), 2, currentEnclavePlan.Size()) - instruction1Str := `print(msg="instruction1")` - instruction1 := suite.newMockInstruction(instruction1Str) - instruction2Str := `print(msg="instruction2")` - instruction2 := suite.newMockInstruction(instruction2Str) - - currentEnclavePlan := instructions_plan.NewInstructionsPlan() - require.Nil(suite.T(), currentEnclavePlan.AddInstruction(instruction1, noReturnValue)) - require.Nil(suite.T(), currentEnclavePlan.AddInstruction(instruction2, noReturnValue)) - + updatedScript := `def run(plan, args): + plan.print("instruction3") +` + // Interpret the updated script against the current enclave plan _, instructionsPlan, interpretationError := suite.interpreter.InterpretAndOptimizePlan( context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, - script, + updatedScript, noInputParams, currentEnclavePlan, ) @@ -190,12 +196,12 @@ def run(plan, args): require.Equal(suite.T(), 3, len(instructionSequence)) scheduledInstruction1 := instructionSequence[0] - require.Equal(suite.T(), instruction1Str, scheduledInstruction1.GetInstruction().String()) + require.Equal(suite.T(), `print(msg="instruction1")`, scheduledInstruction1.GetInstruction().String()) require.True(suite.T(), scheduledInstruction1.IsImportedFromCurrentEnclavePlan()) require.True(suite.T(), scheduledInstruction1.IsExecuted()) scheduledInstruction2 := instructionSequence[1] - require.Equal(suite.T(), instruction2Str, scheduledInstruction2.GetInstruction().String()) + require.Equal(suite.T(), `print(msg="instruction2")`, scheduledInstruction2.GetInstruction().String()) require.True(suite.T(), scheduledInstruction2.IsImportedFromCurrentEnclavePlan()) require.True(suite.T(), scheduledInstruction2.IsExecuted()) @@ -211,30 +217,35 @@ def run(plan, args): // Check that instruction 1 and 2 are part of the new plan, already executed, and instruction3 is ALSO part of the // plan, but marked as imported from a previous plan func (suite *StartosisInterpreterIdempotentTestSuite) TestInterpretAndOptimize_ReplacePartOfInstructionWithIdenticalInstruction() { - script := ` -def run(plan, args): + initialScript := `def run(plan, args): plan.print(msg="instruction1") plan.print(msg="instruction2") + plan.print(msg="instruction3") ` + // Interpretation of the initial script to generate the current enclave plan + _, currentEnclavePlan, interpretationApiErr := suite.interpreter.Interpret( + context.Background(), + startosis_constants.PackageIdPlaceholderForStandaloneScript, + useDefaultMainFunctionName, + startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, + initialScript, + noInputParams, + enclave_structure.NewEnclaveComponents(), + resolver.NewInstructionsPlanMask(0)) + require.Nil(suite.T(), interpretationApiErr) + require.Equal(suite.T(), 3, currentEnclavePlan.Size()) - instruction1Str := `print(msg="instruction1")` - instruction1 := suite.newMockInstruction(instruction1Str) - instruction2Str := `print(msg="instruction2")` - instruction2 := suite.newMockInstruction(instruction2Str) - instruction3Str := `print(msg="instruction3")` - instruction3 := suite.newMockInstruction(instruction3Str) - - currentEnclavePlan := instructions_plan.NewInstructionsPlan() - require.Nil(suite.T(), currentEnclavePlan.AddInstruction(instruction1, noReturnValue)) - require.Nil(suite.T(), currentEnclavePlan.AddInstruction(instruction2, noReturnValue)) - require.Nil(suite.T(), currentEnclavePlan.AddInstruction(instruction3, noReturnValue)) - + updatedScript := `def run(plan, args): + plan.print(msg="instruction1") + plan.print(msg="instruction2") +` + // Interpret the updated script against the current enclave plan _, instructionsPlan, interpretationError := suite.interpreter.InterpretAndOptimizePlan( context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, - script, + updatedScript, noInputParams, currentEnclavePlan, ) @@ -245,12 +256,12 @@ def run(plan, args): require.Equal(suite.T(), 3, len(instructionSequence)) scheduledInstruction1 := instructionSequence[0] - require.Equal(suite.T(), instruction1Str, scheduledInstruction1.GetInstruction().String()) + require.Equal(suite.T(), `print(msg="instruction1")`, scheduledInstruction1.GetInstruction().String()) require.False(suite.T(), scheduledInstruction1.IsImportedFromCurrentEnclavePlan()) require.True(suite.T(), scheduledInstruction1.IsExecuted()) scheduledInstruction2 := instructionSequence[1] - require.Equal(suite.T(), instruction2Str, scheduledInstruction2.GetInstruction().String()) + require.Equal(suite.T(), `print(msg="instruction2")`, scheduledInstruction2.GetInstruction().String()) require.False(suite.T(), scheduledInstruction2.IsImportedFromCurrentEnclavePlan()) require.True(suite.T(), scheduledInstruction2.IsExecuted()) @@ -267,31 +278,36 @@ def run(plan, args): // [`print("instruction1")` `print("instruction2")` `print("instruction3")` `print("instruction1")` `print("instruction2_NEW")` `print("instruction3")`] // The first three are imported from a previous plan, already executed, while the last three are from all new func (suite *StartosisInterpreterIdempotentTestSuite) TestInterpretAndOptimize_InvalidNewVersionOfThePackage() { - script := ` -def run(plan, args): + initialScript := `def run(plan, args): plan.print(msg="instruction1") - plan.print(msg="instruction2_NEW") + plan.print(msg="instruction2") plan.print(msg="instruction3") ` + // Interpretation of the initial script to generate the current enclave plan + _, currentEnclavePlan, interpretationApiErr := suite.interpreter.Interpret( + context.Background(), + startosis_constants.PackageIdPlaceholderForStandaloneScript, + useDefaultMainFunctionName, + startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, + initialScript, + noInputParams, + enclave_structure.NewEnclaveComponents(), + resolver.NewInstructionsPlanMask(0)) + require.Nil(suite.T(), interpretationApiErr) + require.Equal(suite.T(), 3, currentEnclavePlan.Size()) - instruction1Str := `print(msg="instruction1")` - instruction1 := suite.newMockInstruction(instruction1Str) - instruction2Str := `print(msg="instruction2")` - instruction2 := suite.newMockInstruction(instruction2Str) - instruction3Str := `print(msg="instruction3")` - instruction3 := suite.newMockInstruction(instruction3Str) - - currentEnclavePlan := instructions_plan.NewInstructionsPlan() - require.Nil(suite.T(), currentEnclavePlan.AddInstruction(instruction1, noReturnValue)) - require.Nil(suite.T(), currentEnclavePlan.AddInstruction(instruction2, noReturnValue)) - require.Nil(suite.T(), currentEnclavePlan.AddInstruction(instruction3, noReturnValue)) - + updatedScript := `def run(plan, args): + plan.print(msg="instruction1") + plan.print(msg="instruction2_NEW") + plan.print(msg="instruction3") +` + // Interpret the updated script against the current enclave plan _, instructionsPlan, interpretationError := suite.interpreter.InterpretAndOptimizePlan( context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, - script, + updatedScript, noInputParams, currentEnclavePlan, ) @@ -302,22 +318,22 @@ def run(plan, args): require.Equal(suite.T(), 6, len(instructionSequence)) scheduledInstruction1 := instructionSequence[0] - require.Equal(suite.T(), instruction1Str, scheduledInstruction1.GetInstruction().String()) + require.Equal(suite.T(), `print(msg="instruction1")`, scheduledInstruction1.GetInstruction().String()) require.True(suite.T(), scheduledInstruction1.IsImportedFromCurrentEnclavePlan()) require.True(suite.T(), scheduledInstruction1.IsExecuted()) scheduledInstruction2 := instructionSequence[1] - require.Equal(suite.T(), instruction2Str, scheduledInstruction2.GetInstruction().String()) + require.Equal(suite.T(), `print(msg="instruction2")`, scheduledInstruction2.GetInstruction().String()) require.True(suite.T(), scheduledInstruction2.IsImportedFromCurrentEnclavePlan()) require.True(suite.T(), scheduledInstruction2.IsExecuted()) scheduledInstruction3 := instructionSequence[2] - require.Equal(suite.T(), instruction3Str, scheduledInstruction3.GetInstruction().String()) + require.Equal(suite.T(), `print(msg="instruction3")`, scheduledInstruction3.GetInstruction().String()) require.True(suite.T(), scheduledInstruction3.IsImportedFromCurrentEnclavePlan()) require.True(suite.T(), scheduledInstruction3.IsExecuted()) scheduledInstruction4 := instructionSequence[3] - require.Equal(suite.T(), instruction1Str, scheduledInstruction4.GetInstruction().String()) + require.Equal(suite.T(), `print(msg="instruction1")`, scheduledInstruction4.GetInstruction().String()) require.False(suite.T(), scheduledInstruction4.IsImportedFromCurrentEnclavePlan()) require.False(suite.T(), scheduledInstruction4.IsExecuted()) @@ -327,13 +343,73 @@ def run(plan, args): require.False(suite.T(), scheduledInstruction5.IsExecuted()) scheduledInstruction6 := instructionSequence[5] - require.Equal(suite.T(), instruction3Str, scheduledInstruction6.GetInstruction().String()) + require.Equal(suite.T(), `print(msg="instruction3")`, scheduledInstruction6.GetInstruction().String()) require.False(suite.T(), scheduledInstruction6.IsImportedFromCurrentEnclavePlan()) require.False(suite.T(), scheduledInstruction6.IsExecuted()) } -func (suite *StartosisInterpreterIdempotentTestSuite) newMockInstruction(instructionName string) kurtosis_instruction.KurtosisInstruction { - mockInstruction := mock_instruction.NewMockKurtosisInstruction(suite.T()) - mockInstruction.EXPECT().String().Maybe().Return(instructionName) - return mockInstruction +// Submit a package with an update to an add_service instruction. add_service instructions is the only one right now +// support being run twice in an enclave, updating "live" the service underneath. Check that the add_service and its +// direct dependencies are scheduled for a re-run but other instructions remains "SKIPPED" +func (suite *StartosisInterpreterIdempotentTestSuite) TestInterpretAndOptimize_AddServiceIdempotency() { + initialScript := `def run(plan): + service_1 = plan.add_service(name="service_1", config=ServiceConfig(image="kurtosistech/image:1.2.3")) + plan.print("Service 1 - IP: {} - Hostname: {}".format(service_1.ip_address, service_1.hostname)) + plan.exec(service_name="service_1", recipe=ExecRecipe(command=["echo", "Hello World!"])) + plan.assert(value=service_1.ip_address, assertion="==", target_value="fake_ip") +` + // Interpretation of the initial script to generate the current enclave plan + _, currentEnclavePlan, interpretationApiErr := suite.interpreter.Interpret( + context.Background(), + startosis_constants.PackageIdPlaceholderForStandaloneScript, + useDefaultMainFunctionName, + startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, + initialScript, + noInputParams, + enclave_structure.NewEnclaveComponents(), + resolver.NewInstructionsPlanMask(0)) + require.Nil(suite.T(), interpretationApiErr) + require.Equal(suite.T(), 4, currentEnclavePlan.Size()) + + updatedScript := `def run(plan): + service_1 = plan.add_service(name="service_1", config=ServiceConfig(image="kurtosistech/image:1.5.0")) # <-- version updated + plan.print("Service 1 - IP: {} - Hostname: {}".format(service_1.ip_address, service_1.hostname)) # <-- identical + plan.exec(service_name="service_1", recipe=ExecRecipe(command=["echo", "Hello World!"])) # <-- identical but should be rerun b/c service_1 updated + plan.assert(value=service_1.ip_address, assertion="==", target_value="fake_ip") # <-- identical b/c we don't track runtime value provenance yet +` + // Interpret the updated script against the current enclave plan + _, instructionsPlan, interpretationError := suite.interpreter.InterpretAndOptimizePlan( + context.Background(), + startosis_constants.PackageIdPlaceholderForStandaloneScript, + useDefaultMainFunctionName, + startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, + updatedScript, + noInputParams, + currentEnclavePlan, + ) + require.Nil(suite.T(), interpretationError) + + instructionSequence, err := instructionsPlan.GeneratePlan() + require.Nil(suite.T(), err) + require.Equal(suite.T(), 4, len(instructionSequence)) + + scheduledInstruction1 := instructionSequence[0] + require.Equal(suite.T(), `add_service(name="service_1", config=ServiceConfig(image="kurtosistech/image:1.5.0"))`, scheduledInstruction1.GetInstruction().String()) + require.False(suite.T(), scheduledInstruction1.IsImportedFromCurrentEnclavePlan()) + require.False(suite.T(), scheduledInstruction1.IsExecuted()) + + scheduledInstruction2 := instructionSequence[1] + require.Regexp(suite.T(), `print\(msg="Service 1 - IP: {{kurtosis:[a-z0-9]{32}:ip_address\.runtime_value}} - Hostname: {{kurtosis:[a-z0-9]{32}:hostname\.runtime_value}}"\)`, scheduledInstruction2.GetInstruction().String()) + require.False(suite.T(), scheduledInstruction2.IsImportedFromCurrentEnclavePlan()) + require.True(suite.T(), scheduledInstruction2.IsExecuted()) + + scheduledInstruction3 := instructionSequence[2] + require.Equal(suite.T(), `exec(service_name="service_1", recipe=ExecRecipe(command=["echo", "Hello World!"]))`, scheduledInstruction3.GetInstruction().String()) + require.False(suite.T(), scheduledInstruction3.IsImportedFromCurrentEnclavePlan()) + require.False(suite.T(), scheduledInstruction3.IsExecuted()) + + scheduledInstruction4 := instructionSequence[3] + require.Regexp(suite.T(), `assert\(value="{{kurtosis:[a-z0-9]{32}:ip_address\.runtime_value}}", assertion="==", target_value="fake_ip"\)`, scheduledInstruction4.GetInstruction().String()) + require.False(suite.T(), scheduledInstruction4.IsImportedFromCurrentEnclavePlan()) + require.True(suite.T(), scheduledInstruction4.IsExecuted()) } diff --git a/core/server/api_container/server/startosis_engine/startosis_interpreter_test.go b/core/server/api_container/server/startosis_engine/startosis_interpreter_test.go index 56904d53a4..ea8cabaf3c 100644 --- a/core/server/api_container/server/startosis_engine/startosis_interpreter_test.go +++ b/core/server/api_container/server/startosis_engine/startosis_interpreter_test.go @@ -8,6 +8,7 @@ import ( "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/builtins/print_builtin" + "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_instruction/add_service" @@ -31,6 +32,7 @@ var ( ) var ( + emptyEnclaveComponents = enclave_structure.NewEnclaveComponents() emptyInstructionsPlanMask = resolver.NewInstructionsPlanMask(0) ) @@ -88,7 +90,7 @@ def run(plan): plan.print("` + testString + `") ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 1, instructionsPlan.Size()) // Only the print statement @@ -109,7 +111,7 @@ def deploy_contract(plan,service_name,contract_name,init_message,args): mainFunctionName := "deploy_contract" inputArgs := `{"service_name": "my-service", "contract_name": "my-contract", "init_message": "Init message", "args": {"arg1": "arg1-value", "arg2": "arg2-value"}}` - result, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, mainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, inputArgs, emptyInstructionsPlanMask) + result, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, mainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, inputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 3, instructionsPlan.Size()) // The three print functions require.NotNil(suite.T(), result) @@ -130,7 +132,7 @@ def my_func(my_arg1, my_arg2, args): mainFunctionName := "my_func" inputArgs := `{"my_arg1": "foo", "my_arg2": "bar", "args": {"arg1": "arg1-value", "arg2": "arg2-value"}}` - result, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, mainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, inputArgs, emptyInstructionsPlanMask) + result, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, mainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, inputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 0, instructionsPlan.Size()) // There are no instructions to execute require.NotNil(suite.T(), result) @@ -147,7 +149,7 @@ def run(plan): plan.print(my_dict) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 2, instructionsPlan.Size()) @@ -165,7 +167,7 @@ def run(plan): unknownInstruction() ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) expectedError := startosis_errors.NewInterpretationErrorWithCustomMsg( []startosis_errors.CallFrame{ @@ -186,7 +188,7 @@ unknownVariable unknownInstruction2() ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) expectedError := startosis_errors.NewInterpretationErrorWithCustomMsg( []startosis_errors.CallFrame{ @@ -208,7 +210,7 @@ def run(): load("otherScript.start") # fails b/c load takes in at least 2 args ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) expectedError := startosis_errors.NewInterpretationErrorFromStacktrace( []startosis_errors.CallFrame{ @@ -240,7 +242,7 @@ def run(plan): plan.print("The grpc transport protocol is " + datastore_service.ports["grpc"].transport_protocol) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, fmt.Sprintf(script, testServiceName), startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, fmt.Sprintf(script, testServiceName), startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 5, instructionsPlan.Size()) @@ -275,7 +277,7 @@ def run(plan): plan.print("The transport protocol is " + datastore_service.ports["grpc"].transport_protocol) plan.print("The application protocol is " + datastore_service.ports["grpc"].application_protocol) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, fmt.Sprintf(script, testServiceName), startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, fmt.Sprintf(script, testServiceName), startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 6, instructionsPlan.Size()) @@ -305,7 +307,7 @@ def run(plan): plan.add_service(name = service_name, config = config) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) expectedError := startosis_errors.NewInterpretationErrorWithCauseAndCustomMsg( errors.New("ServiceConfig: missing argument for image"), @@ -336,7 +338,7 @@ def run(plan): plan.add_service(name = service_name, config = config) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) expectedError := startosis_errors.NewInterpretationErrorWithCauseAndCustomMsg( startosis_errors.NewInterpretationError(`The following argument(s) could not be parsed or did not pass validation: {"transport_protocol":"Invalid argument value for 'transport_protocol': 'TCPK'. Valid values are TCP, SCTP, UDP"}`), []startosis_errors.CallFrame{ @@ -366,7 +368,7 @@ def run(plan): plan.add_service(name = service_name, config = config) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) expectedError := startosis_errors.NewInterpretationErrorWithCauseAndCustomMsg( startosis_errors.NewInterpretationError(`The following argument(s) could not be parsed or did not pass validation: {"number":"Value for 'number' was expected to be an integer between 1 and 65535, but it was 'starlark.String'"}`), []startosis_errors.CallFrame{ @@ -406,7 +408,7 @@ def run(plan): plan.print("Done!") ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 8, instructionsPlan.Size()) @@ -434,7 +436,7 @@ load("` + barModulePath + `", "a") def run(plan): plan.print("Hello " + a) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) expectedError := startosis_errors.NewInterpretationErrorWithCustomMsg( []startosis_errors.CallFrame{ *startosis_errors.NewCallFrame("", startosis_errors.NewScriptPosition(startosis_constants.PackageIdPlaceholderForStandaloneScript, 2, 1)), @@ -457,7 +459,7 @@ my_module = import_module("` + barModulePath + `") def run(plan): plan.print("Hello " + my_module.a) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 1, instructionsPlan.Size()) // Only the print statement @@ -481,7 +483,7 @@ def run(plan): plan.print(module_doo.b) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 1, instructionsPlan.Size()) @@ -505,7 +507,7 @@ def run(plan): plan.print(module_doo.b) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) expectedError := startosis_errors.NewInterpretationErrorWithCustomMsg( []startosis_errors.CallFrame{ *startosis_errors.NewCallFrame("", startosis_errors.NewScriptPosition(moduleBarLoadsModuleDoo, 1, 27)), @@ -526,7 +528,7 @@ def run(plan): plan.print(my_module.b) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) errorMsg := `Evaluation error: An error occurred while loading the module '` + nonExistentModule + `' Caused by: Package '` + nonExistentModule + `' not found` @@ -549,7 +551,7 @@ def run(plan): ` // assert that first load fails - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.NotNil(suite.T(), interpretationError) require.Nil(suite.T(), instructionsPlan) @@ -558,7 +560,7 @@ def run(plan): expectedOutput := `Hello World! ` // assert that second load succeeds - _, instructionsPlan, interpretationError = suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError = suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 1, instructionsPlan.Size()) validateScriptOutputFromPrintInstructions(suite.T(), instructionsPlan, expectedOutput) @@ -585,7 +587,7 @@ def run(plan): plan.add_service(name = module_bar.service_name, config = module_bar.config) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 3, instructionsPlan.Size()) @@ -629,7 +631,7 @@ def run(plan): plan.print("Done!") ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 8, instructionsPlan.Size()) @@ -658,7 +660,7 @@ def run(plan): plan.print("World!") ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 1, instructionsPlan.Size()) @@ -696,7 +698,7 @@ Adding service example-datastore-server Starting Startosis script! ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, scriptA, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, scriptA, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 4, instructionsPlan.Size()) assertInstructionTypeAndPosition(suite.T(), instructionsPlan, 2, add_service.AddServiceBuiltinName, moduleBar, 12, 18) @@ -721,7 +723,7 @@ def run(plan): Adding service example-datastore-server ` - _, instructionsPlan, interpretationError = suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, scriptB, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError = suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, scriptB, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 3, instructionsPlan.Size()) assertInstructionTypeAndPosition(suite.T(), instructionsPlan, 2, add_service.AddServiceBuiltinName, startosis_constants.PackageIdPlaceholderForStandaloneScript, 14, 18) @@ -741,7 +743,7 @@ def run(plan): plan.print(file_contents) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 2, instructionsPlan.Size()) @@ -775,7 +777,7 @@ def run(plan): plan.print(artifact_name) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 3, instructionsPlan.Size()) @@ -814,7 +816,7 @@ def run(plan): plan.print(uuid) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 4, instructionsPlan.Size()) @@ -836,7 +838,7 @@ def run(plan): plan.print("The service example-datastore-server has been removed") ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 3, instructionsPlan.Size()) @@ -854,7 +856,7 @@ func (suite *StartosisInterpreterTestSuite) TestStartosisInterpreter_NoPanicIfUp def run(plan): plan.upload_files("` + filePath + `") ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.NotNil(suite.T(), interpretationError) require.Nil(suite.T(), instructionsPlan) } @@ -865,7 +867,7 @@ def run(plan): plan.print("Hello World!") ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 1, instructionsPlan.Size()) @@ -880,7 +882,7 @@ def run(plan): plan.print("Hello World!") ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, `{"number": 4}`, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, `{"number": 4}`, emptyEnclaveComponents, emptyInstructionsPlanMask) require.NotNil(suite.T(), interpretationError) require.Nil(suite.T(), instructionsPlan) } @@ -891,7 +893,7 @@ def run(plan, args): plan.print("My favorite number is {0}".format(args["number"])) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, `{"number": 4}`, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, `{"number": 4}`, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 1, instructionsPlan.Size()) @@ -909,7 +911,7 @@ def run(plan, args): plan.print("Sorry no args!") ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 1, instructionsPlan.Size()) @@ -924,7 +926,7 @@ def run(plan, args, invalid_arg): plan.print("this wouldn't interpret so the text here doesnt matter") ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.NotNil(suite.T(), interpretationError) expectedError := "Evaluation error: function run missing 2 arguments (args, invalid_arg)" require.Contains(suite.T(), interpretationError.GetErrorMessage(), expectedError) @@ -938,7 +940,7 @@ def run(plan, a, b): ` missingArgumentCount := 1 missingArgument := "b" - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, `{"a": "x"}`, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, `{"a": "x"}`, emptyEnclaveComponents, emptyInstructionsPlanMask) require.NotNil(suite.T(), interpretationError) expectedError := fmt.Sprintf("Evaluation error: function run missing %d argument (%v)", missingArgumentCount, missingArgument) @@ -951,7 +953,7 @@ func (suite *StartosisInterpreterTestSuite) TestStartosisInterpreter_RunWithUnpa def run(plan, a, b=1): plan.print("My favorite number is {0}, but my favorite letter is {1}".format(b, a)) ` - _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, `{"a": "x"}`, emptyInstructionsPlanMask) + _, instructionsPlan, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, `{"a": "x"}`, emptyEnclaveComponents, emptyInstructionsPlanMask) require.Nil(suite.T(), interpretationError) require.Equal(suite.T(), 1, instructionsPlan.Size()) expectedOutput := "My favorite number is 1, but my favorite letter is x\n" @@ -964,7 +966,7 @@ def run(plan): print("this doesnt matter") ` - _, _, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyInstructionsPlanMask) + _, _, interpretationError := suite.interpreter.Interpret(context.Background(), startosis_constants.PackageIdPlaceholderForStandaloneScript, useDefaultMainFunctionName, startosis_constants.PlaceHolderMainFileForPlaceStandAloneScript, script, startosis_constants.EmptyInputArgs, emptyEnclaveComponents, emptyInstructionsPlanMask) require.NotNil(suite.T(), interpretationError) require.Equal(suite.T(), fmt.Sprintf("Evaluation error: %v\n\tat [3:7]: run\n\tat [0:0]: print", print_builtin.UsePlanFromKurtosisInstructionError), interpretationError.GetErrorMessage()) } diff --git a/core/server/api_container/server/startosis_engine/startosis_runner.go b/core/server/api_container/server/startosis_engine/startosis_runner.go index b24cb38a81..73b960db90 100644 --- a/core/server/api_container/server/startosis_engine/startosis_runner.go +++ b/core/server/api_container/server/startosis_engine/startosis_runner.go @@ -4,6 +4,7 @@ import ( "context" "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/binding_constructors" + "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/starlark_warning" @@ -103,6 +104,7 @@ func (runner *StartosisRunner) Run( relativePathToMainFile, serializedStartosis, serializedParams, + enclave_structure.NewEnclaveComponents(), resolver.NewInstructionsPlanMask(0), ) } else { diff --git a/core/server/api_container/server/startosis_engine/startosis_validator/service_existence.go b/core/server/api_container/server/startosis_engine/startosis_validator/service_existence.go new file mode 100644 index 0000000000..ab256a7151 --- /dev/null +++ b/core/server/api_container/server/startosis_engine/startosis_validator/service_existence.go @@ -0,0 +1,15 @@ +package startosis_validator + +//go:generate go run github.com/dmarkham/enumer -trimprefix "ServiceExistence" -type=ServiceExistence +type ServiceExistence uint8 + +const ( + // ServiceNotFound - Service does not exist in the enclave + ServiceNotFound ServiceExistence = iota + + // ServiceExistedBeforePackageRun - The service existed in the enclave at the beginning of the package run. + ServiceExistedBeforePackageRun + + // ServiceCreatedOrUpdatedDUringPackageRun - The service was created or updated during this run. + ServiceCreatedOrUpdatedDuringPackageRun +) diff --git a/core/server/api_container/server/startosis_engine/startosis_validator/serviceexistence_enumer.go b/core/server/api_container/server/startosis_engine/startosis_validator/serviceexistence_enumer.go new file mode 100644 index 0000000000..4677261f3c --- /dev/null +++ b/core/server/api_container/server/startosis_engine/startosis_validator/serviceexistence_enumer.go @@ -0,0 +1,82 @@ +// Code generated by "enumer -trimprefix ServiceExistence -type=ServiceExistence"; DO NOT EDIT. + +package startosis_validator + +import ( + "fmt" + "strings" +) + +const _ServiceExistenceName = "ServiceNotFoundServiceExistedBeforePackageRunServiceCreatedOrUpdatedDuringPackageRun" + +var _ServiceExistenceIndex = [...]uint8{0, 15, 45, 84} + +const _ServiceExistenceLowerName = "servicenotfoundserviceexistedbeforepackagerunservicecreatedorupdatedduringpackagerun" + +func (i ServiceExistence) String() string { + if i >= ServiceExistence(len(_ServiceExistenceIndex)-1) { + return fmt.Sprintf("ServiceExistence(%d)", i) + } + return _ServiceExistenceName[_ServiceExistenceIndex[i]:_ServiceExistenceIndex[i+1]] +} + +// An "invalid array index" compiler error signifies that the constant values have changed. +// Re-run the stringer command to generate them again. +func _ServiceExistenceNoOp() { + var x [1]struct{} + _ = x[ServiceNotFound-(0)] + _ = x[ServiceExistedBeforePackageRun-(1)] + _ = x[ServiceCreatedOrUpdatedDuringPackageRun-(2)] +} + +var _ServiceExistenceValues = []ServiceExistence{ServiceNotFound, ServiceExistedBeforePackageRun, ServiceCreatedOrUpdatedDuringPackageRun} + +var _ServiceExistenceNameToValueMap = map[string]ServiceExistence{ + _ServiceExistenceName[0:15]: ServiceNotFound, + _ServiceExistenceLowerName[0:15]: ServiceNotFound, + _ServiceExistenceName[15:45]: ServiceExistedBeforePackageRun, + _ServiceExistenceLowerName[15:45]: ServiceExistedBeforePackageRun, + _ServiceExistenceName[45:84]: ServiceCreatedOrUpdatedDuringPackageRun, + _ServiceExistenceLowerName[45:84]: ServiceCreatedOrUpdatedDuringPackageRun, +} + +var _ServiceExistenceNames = []string{ + _ServiceExistenceName[0:15], + _ServiceExistenceName[15:45], + _ServiceExistenceName[45:84], +} + +// ServiceExistenceString retrieves an enum value from the enum constants string name. +// Throws an error if the param is not part of the enum. +func ServiceExistenceString(s string) (ServiceExistence, error) { + if val, ok := _ServiceExistenceNameToValueMap[s]; ok { + return val, nil + } + + if val, ok := _ServiceExistenceNameToValueMap[strings.ToLower(s)]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to ServiceExistence values", s) +} + +// ServiceExistenceValues returns all values of the enum +func ServiceExistenceValues() []ServiceExistence { + return _ServiceExistenceValues +} + +// ServiceExistenceStrings returns a slice of all String values of the enum +func ServiceExistenceStrings() []string { + strs := make([]string, len(_ServiceExistenceNames)) + copy(strs, _ServiceExistenceNames) + return strs +} + +// IsAServiceExistence returns "true" if the value is listed in the enum definition. "false" otherwise +func (i ServiceExistence) IsAServiceExistence() bool { + for _, v := range _ServiceExistenceValues { + if i == v { + return true + } + } + return false +} diff --git a/core/server/api_container/server/startosis_engine/startosis_validator/validator_environment.go b/core/server/api_container/server/startosis_engine/startosis_validator/validator_environment.go index 86fb248050..6e489b7fe3 100644 --- a/core/server/api_container/server/startosis_engine/startosis_validator/validator_environment.go +++ b/core/server/api_container/server/startosis_engine/startosis_validator/validator_environment.go @@ -8,16 +8,20 @@ import ( type ValidatorEnvironment struct { isNetworkPartitioningEnabled bool requiredDockerImages map[string]bool - serviceNames map[service.ServiceName]bool + serviceNames map[service.ServiceName]ServiceExistence artifactNames map[string]bool serviceNameToPrivatePortIDs map[service.ServiceName][]string } func NewValidatorEnvironment(isNetworkPartitioningEnabled bool, serviceNames map[service.ServiceName]bool, artifactNames map[string]bool, serviceNameToPrivatePortIds map[service.ServiceName][]string) *ValidatorEnvironment { + serviceNamesWithServiceExistence := map[service.ServiceName]ServiceExistence{} + for serviceName := range serviceNames { + serviceNamesWithServiceExistence[serviceName] = ServiceExistedBeforePackageRun + } return &ValidatorEnvironment{ isNetworkPartitioningEnabled: isNetworkPartitioningEnabled, requiredDockerImages: map[string]bool{}, - serviceNames: serviceNames, + serviceNames: serviceNamesWithServiceExistence, artifactNames: artifactNames, serviceNameToPrivatePortIDs: serviceNameToPrivatePortIds, } @@ -32,20 +36,23 @@ func (environment *ValidatorEnvironment) GetNumberOfContainerImages() uint32 { } func (environment *ValidatorEnvironment) AddServiceName(serviceName service.ServiceName) { - environment.serviceNames[serviceName] = true + environment.serviceNames[serviceName] = ServiceCreatedOrUpdatedDuringPackageRun } func (environment *ValidatorEnvironment) RemoveServiceName(serviceName service.ServiceName) { delete(environment.serviceNames, serviceName) } -func (environment *ValidatorEnvironment) DoesServiceNameExist(serviceName service.ServiceName) bool { - _, ok := environment.serviceNames[serviceName] - return ok +func (environment *ValidatorEnvironment) DoesServiceNameExist(serviceName service.ServiceName) ServiceExistence { + serviceExistence, found := environment.serviceNames[serviceName] + if !found { + return ServiceNotFound + } + return serviceExistence } -func (environment *ValidatorEnvironment) AddPrivatePortIDForService(portID string, serviceName service.ServiceName) { - environment.serviceNameToPrivatePortIDs[serviceName] = append(environment.serviceNameToPrivatePortIDs[serviceName], portID) +func (environment *ValidatorEnvironment) AddPrivatePortIDForService(portIDs []string, serviceName service.ServiceName) { + environment.serviceNameToPrivatePortIDs[serviceName] = portIDs } func (environment *ValidatorEnvironment) DoesPrivatePortIDExistForService(portID string, serviceName service.ServiceName) bool { diff --git a/core/server/api_container/server/startosis_engine/startosis_validator/validator_environment_test.go b/core/server/api_container/server/startosis_engine/startosis_validator/validator_environment_test.go index e598c96f74..b0763d592c 100644 --- a/core/server/api_container/server/startosis_engine/startosis_validator/validator_environment_test.go +++ b/core/server/api_container/server/startosis_engine/startosis_validator/validator_environment_test.go @@ -16,8 +16,11 @@ const ( func TestMultiplePortIdsForValidation(t *testing.T) { emptyInitialMapping := map[service.ServiceName][]string{} validatorEnvironment := NewValidatorEnvironment(false, nil, nil, emptyInitialMapping) - validatorEnvironment.AddPrivatePortIDForService(fooPortId, testBarService) - validatorEnvironment.AddPrivatePortIDForService(fizzPortId, testBarService) + portIds := []string{ + fooPortId, + fizzPortId, + } + validatorEnvironment.AddPrivatePortIDForService(portIds, testBarService) require.True(t, validatorEnvironment.DoesPrivatePortIDExistForService(fooPortId, testBarService)) require.True(t, validatorEnvironment.DoesPrivatePortIDExistForService(fizzPortId, testBarService)) require.False(t, validatorEnvironment.DoesPrivatePortIDExistForService(invalidPortId, testBarService))