diff --git a/core/server/api_container/server/startosis_engine/interpretation_time_value_store/interpretation_time_value_store.go b/core/server/api_container/server/startosis_engine/interpretation_time_value_store/interpretation_time_value_store.go index af89cd79cc..a6bb42bb28 100644 --- a/core/server/api_container/server/startosis_engine/interpretation_time_value_store/interpretation_time_value_store.go +++ b/core/server/api_container/server/startosis_engine/interpretation_time_value_store/interpretation_time_value_store.go @@ -34,3 +34,19 @@ func (itvs *InterpretationTimeValueStore) GetService(name service.ServiceName) ( } return serviceStarlark, nil } + +func (itvs *InterpretationTimeValueStore) GetServices() ([]*kurtosis_types.Service, error) { + servicesStarlark, err := itvs.serviceValues.GetServices() + if err != nil { + return nil, stacktrace.Propagate(err, "An error occurred fetching interpretation time service objects from db") + } + return servicesStarlark, nil +} + +func (itvs *InterpretationTimeValueStore) RemoveService(name service.ServiceName) error { + err := itvs.serviceValues.RemoveService(name) + if err != nil { + return stacktrace.Propagate(err, "An error occurred removing interpretation time service object for service '%v'", name) + } + return nil +} diff --git a/core/server/api_container/server/startosis_engine/interpretation_time_value_store/service_value_repository.go b/core/server/api_container/server/startosis_engine/interpretation_time_value_store/service_value_repository.go index f3c44bbe54..84be9f7046 100644 --- a/core/server/api_container/server/startosis_engine/interpretation_time_value_store/service_value_repository.go +++ b/core/server/api_container/server/startosis_engine/interpretation_time_value_store/service_value_repository.go @@ -105,7 +105,49 @@ func (repository *serviceInterpretationValueRepository) GetService(name service. } logrus.Debugf("Successfully got value for '%v'", name) return value, nil +} + +func (repository *serviceInterpretationValueRepository) GetServices() ([]*kurtosis_types.Service, error) { + logrus.Debug("Getting all known interpretation time service values.") + var services []*kurtosis_types.Service + + if err := repository.enclaveDb.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket(serviceInterpretationValueBucketName) + return bucket.ForEach(func(serviceName, serializedValue []byte) error { + deserializedValue, interpretationErr := repository.starlarkValueSerde.Deserialize(string(serializedValue)) + if interpretationErr != nil { + return stacktrace.Propagate(interpretationErr, "an error occurred while deserializing object associated with service '%v' in repository", serviceName) + } + + kurtosisServiceValue, ok := deserializedValue.(*kurtosis_types.Service) + if !ok { + return stacktrace.NewError("an error occurred casting repository service value to kurtosis service value for service: %v", serviceName) + } + + services = append(services, kurtosisServiceValue) + return nil + }) + }); err != nil { + return nil, stacktrace.Propagate(err, "An error occurred while getting services values from repository.") + } + logrus.Debugf("Successfully retrieved interpretation time service values.") + return services, nil +} + +func (repository *serviceInterpretationValueRepository) RemoveService(name service.ServiceName) error { + logrus.Debugf("Removing service value for '%v' from service value repository...", name) + if err := repository.enclaveDb.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket(serviceInterpretationValueBucketName) + + serviceNameKey := getKey(name) + + return bucket.Delete(serviceNameKey) + }); err != nil { + return stacktrace.Propagate(err, "An error occurred while removing service '%v' from service value repository", name) + } + logrus.Debugf("Successfully removed service value for '%v'", name) + return nil } func getKey(name service.ServiceName) []byte { diff --git a/core/server/api_container/server/startosis_engine/interpretation_time_value_store/service_value_repository_test.go b/core/server/api_container/server/startosis_engine/interpretation_time_value_store/service_value_repository_test.go index 9b7db03a01..1ddb4777b3 100644 --- a/core/server/api_container/server/startosis_engine/interpretation_time_value_store/service_value_repository_test.go +++ b/core/server/api_container/server/startosis_engine/interpretation_time_value_store/service_value_repository_test.go @@ -1,6 +1,7 @@ package interpretation_time_value_store import ( + "fmt" port_spec_core "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/database_accessors/enclave_db" @@ -16,7 +17,7 @@ import ( const ( starlarkThreadName = "thread-for-db-test" - serviceName = service.ServiceName("datastore-1") + serviceName = service.ServiceName("datastore") serviceNameStarlarkStr = starlark.String(serviceName) hostName = serviceNameStarlarkStr ipAddress = starlark.String("172.23.34.44") @@ -62,6 +63,84 @@ func TestPutGetFail_ForMissingServiceName(t *testing.T) { require.Nil(t, actualService) } +func TestGetServices(t *testing.T) { + repository := getServiceInterpretationTimeValueRepository(t) + require.NotNil(t, repository) + + applicationProtocol := "" + maybeUrl := "" + + port, interpretationErr := port_spec.CreatePortSpecUsingGoValues( + string(serviceName), + uint16(443), + port_spec_core.TransportProtocol_TCP, + &applicationProtocol, + "10s", + &maybeUrl, + ) + require.Nil(t, interpretationErr) + ports := starlark.NewDict(1) + require.NoError(t, ports.SetKey(starlark.String("http"), port)) + + serviceOneName := fmt.Sprintf("%v-%v", serviceName, "1") + serviceOne, interpretationErr := kurtosis_types.CreateService(starlark.String(serviceOneName), hostName, ipAddress, ports) + require.Nil(t, interpretationErr) + + serviceTwoName := fmt.Sprintf("%v-%v", serviceName, "2") + serviceTwo, interpretationErr := kurtosis_types.CreateService(starlark.String(serviceTwoName), hostName, ipAddress, ports) + require.Nil(t, interpretationErr) + + err := repository.PutService(service.ServiceName(serviceOneName), serviceOne) + require.Nil(t, err) + err = repository.PutService(service.ServiceName(serviceTwoName), serviceTwo) + require.Nil(t, err) + + actualServices, err := repository.GetServices() + require.NoError(t, err) + require.Len(t, actualServices, 2) + + actualServiceOneName, err := actualServices[0].GetName() + require.Nil(t, err) + require.Equal(t, service.ServiceName(serviceOneName), actualServiceOneName) + + actualServiceTwoName, err := actualServices[1].GetName() + require.Nil(t, err) + require.Equal(t, service.ServiceName(serviceTwoName), actualServiceTwoName) +} + +func TestRemoveService(t *testing.T) { + repository := getServiceInterpretationTimeValueRepository(t) + require.NotNil(t, repository) + + applicationProtocol := "" + maybeUrl := "" + + port, interpretationErr := port_spec.CreatePortSpecUsingGoValues( + string(serviceName), + uint16(443), + port_spec_core.TransportProtocol_TCP, + &applicationProtocol, + "10s", + &maybeUrl, + ) + require.Nil(t, interpretationErr) + ports := starlark.NewDict(1) + require.NoError(t, ports.SetKey(starlark.String("http"), port)) + + serviceOneName := fmt.Sprintf("%v-%v", serviceName, "1") + serviceOne, interpretationErr := kurtosis_types.CreateService(starlark.String(serviceOneName), hostName, ipAddress, ports) + require.Nil(t, interpretationErr) + + err := repository.PutService(service.ServiceName(serviceOneName), serviceOne) + require.Nil(t, err) + + err = repository.RemoveService(service.ServiceName(serviceOneName)) + require.Nil(t, err) + + _, err = repository.GetService(service.ServiceName(serviceOneName)) + require.Error(t, err) +} + func getServiceInterpretationTimeValueRepository(t *testing.T) *serviceInterpretationValueRepository { file, err := os.CreateTemp("/tmp", "*.db") defer func() { diff --git a/core/server/api_container/server/startosis_engine/kurtosis_builtins.go b/core/server/api_container/server/startosis_engine/kurtosis_builtins.go index 4224f5e5bc..9a318b22c6 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_builtins.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_builtins.go @@ -12,6 +12,7 @@ import ( "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/exec" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/get_files_artifact" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/get_service" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/get_services" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/kurtosis_print" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/remove_service" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/render_templates" @@ -71,11 +72,12 @@ func KurtosisPlanInstructions( add_service.NewAddService(serviceNetwork, runtimeValueStore, packageId, packageContentProvider, packageReplaceOptions, interpretationTimeValueStore, imageDownloadMode), add_service.NewAddServices(serviceNetwork, runtimeValueStore, packageId, packageContentProvider, packageReplaceOptions, interpretationTimeValueStore, imageDownloadMode), get_service.NewGetService(interpretationTimeValueStore), + get_services.NewGetServices(interpretationTimeValueStore), get_files_artifact.NewGetFilesArtifact(), verify.NewVerify(runtimeValueStore), exec.NewExec(serviceNetwork, runtimeValueStore), kurtosis_print.NewPrint(serviceNetwork, runtimeValueStore), - remove_service.NewRemoveService(serviceNetwork), + remove_service.NewRemoveService(serviceNetwork, interpretationTimeValueStore), render_templates.NewRenderTemplatesInstruction(serviceNetwork, runtimeValueStore), request.NewRequest(serviceNetwork, runtimeValueStore), start_service.NewStartService(serviceNetwork), diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/get_services/get_services.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/get_services/get_services.go new file mode 100644 index 0000000000..403f7e1771 --- /dev/null +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/get_services/get_services.go @@ -0,0 +1,101 @@ +package get_services + +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/startosis_engine/enclave_plan_persistence" + "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/interpretation_time_value_store" + "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" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/plan_yaml" + "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" + "go.starlark.net/starlark" +) + +const ( + GetServicesBuiltinName = "get_services" + descriptionStr = "Fetching services" +) + +func NewGetServices(interpretationTimeStore *interpretation_time_value_store.InterpretationTimeValueStore) *kurtosis_plan_instruction.KurtosisPlanInstruction { + return &kurtosis_plan_instruction.KurtosisPlanInstruction{ + KurtosisBaseBuiltin: &kurtosis_starlark_framework.KurtosisBaseBuiltin{ + Name: GetServicesBuiltinName, + Arguments: []*builtin_argument.BuiltinArgument{}, + Deprecation: nil, + }, + Capabilities: func() kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities { + return &GetServicesCapabilities{ + interpretationTimeStore: interpretationTimeStore, + serviceNames: []service.ServiceName{}, // populated at interpretation time + description: "", // populated at interpretation time + } + }, + DefaultDisplayArguments: map[string]bool{}, + } +} + +type GetServicesCapabilities struct { + interpretationTimeStore *interpretation_time_value_store.InterpretationTimeValueStore + serviceNames []service.ServiceName + description string +} + +func (builtin *GetServicesCapabilities) Interpret(_ string, arguments *builtin_argument.ArgumentValuesSet) (starlark.Value, *startosis_errors.InterpretationError) { + builtin.description = builtin_argument.GetDescriptionOrFallBack(arguments, descriptionStr) + + services, err := builtin.interpretationTimeStore.GetServices() + if err != nil { + return nil, startosis_errors.WrapWithInterpretationError(err, "An error occurred while fetching service.") + } + servicesList := &starlark.List{} + for _, serviceVal := range services { + name, err := serviceVal.GetName() + if err != nil { + return nil, startosis_errors.WrapWithInterpretationError(err, "An error occurred getting name of service: %v", serviceVal) + } + builtin.serviceNames = append(builtin.serviceNames, name) + + _ = servicesList.Append(serviceVal) + } + + return servicesList, nil +} + +func (builtin *GetServicesCapabilities) Validate(_ *builtin_argument.ArgumentValuesSet, validatorEnvironment *startosis_validator.ValidatorEnvironment) *startosis_errors.ValidationError { + // validate if all services exist in the validation environment + for _, serviceName := range builtin.serviceNames { + if exists := validatorEnvironment.DoesServiceNameExist(serviceName); exists == startosis_validator.ComponentNotFound { + return startosis_errors.NewValidationError("Service '%v' required by '%v' instruction doesn't exist", serviceName, GetServicesBuiltinName) + } + } + return nil +} + +func (builtin *GetServicesCapabilities) Execute(_ context.Context, _ *builtin_argument.ArgumentValuesSet) (string, error) { + // note: this is a no op + return descriptionStr, nil +} + +func (builtin *GetServicesCapabilities) TryResolveWith(instructionsAreEqual bool, _ *enclave_plan_persistence.EnclavePlanInstruction, enclaveComponents *enclave_structure.EnclaveComponents) enclave_structure.InstructionResolutionStatus { + if instructionsAreEqual { + return enclave_structure.InstructionIsEqual + } + return enclave_structure.InstructionIsUnknown +} + +func (builtin *GetServicesCapabilities) FillPersistableAttributes(builder *enclave_plan_persistence.EnclavePlanInstructionBuilder) { + builder.SetType(GetServicesBuiltinName) +} + +func (builtin *GetServicesCapabilities) UpdatePlan(planYaml *plan_yaml.PlanYaml) error { + // get services does not affect the plan + return nil +} + +func (builtin *GetServicesCapabilities) Description() string { + return builtin.description +} 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 e7dc571fda..d905da7e81 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 @@ -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/enclave_plan_persistence" "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/interpretation_time_value_store" "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" @@ -24,7 +25,7 @@ const ( descriptionFormatStr = "Removing service '%v'" ) -func NewRemoveService(serviceNetwork service_network.ServiceNetwork) *kurtosis_plan_instruction.KurtosisPlanInstruction { +func NewRemoveService(serviceNetwork service_network.ServiceNetwork, interpretationTimeStore *interpretation_time_value_store.InterpretationTimeValueStore) *kurtosis_plan_instruction.KurtosisPlanInstruction { return &kurtosis_plan_instruction.KurtosisPlanInstruction{ KurtosisBaseBuiltin: &kurtosis_starlark_framework.KurtosisBaseBuiltin{ Name: RemoveServiceBuiltinName, @@ -44,7 +45,8 @@ func NewRemoveService(serviceNetwork service_network.ServiceNetwork) *kurtosis_p Capabilities: func() kurtosis_plan_instruction.KurtosisPlanInstructionCapabilities { return &RemoveServiceCapabilities{ - serviceNetwork: serviceNetwork, + serviceNetwork: serviceNetwork, + interpretationTimeStore: interpretationTimeStore, serviceName: "", // populated at interpretation time description: "", // populated at interpretation time @@ -58,7 +60,8 @@ func NewRemoveService(serviceNetwork service_network.ServiceNetwork) *kurtosis_p } type RemoveServiceCapabilities struct { - serviceNetwork service_network.ServiceNetwork + serviceNetwork service_network.ServiceNetwork + interpretationTimeStore *interpretation_time_value_store.InterpretationTimeValueStore serviceName service.ServiceName description string @@ -71,6 +74,10 @@ func (builtin *RemoveServiceCapabilities) Interpret(_ string, arguments *builtin } builtin.serviceName = service.ServiceName(serviceName.GoString()) + err = builtin.interpretationTimeStore.RemoveService(builtin.serviceName) + if err != nil { + return nil, startosis_errors.WrapWithInterpretationError(err, "An error occurred removing '%v' from interpretation time store", builtin.serviceName) + } builtin.description = builtin_argument.GetDescriptionOrFallBack(arguments, fmt.Sprintf(descriptionFormatStr, builtin.serviceName)) return starlark.None, nil } diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/remove_service_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/remove_service_framework_test.go index cacd604278..5751c635d4 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/remove_service_framework_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/remove_service_framework_test.go @@ -3,6 +3,7 @@ package test_engine import ( "fmt" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/interpretation_time_value_store" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/remove_service" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_plan_instruction" "github.com/stretchr/testify/mock" @@ -13,7 +14,8 @@ import ( type removeServiceTestCase struct { *testing.T - serviceNetwork *service_network.MockServiceNetwork + serviceNetwork *service_network.MockServiceNetwork + interpretationTimeStore *interpretation_time_value_store.InterpretationTimeValueStore } func (suite *KurtosisPlanInstructionTestSuite) TestRemoveService() { @@ -26,13 +28,14 @@ func (suite *KurtosisPlanInstructionTestSuite) TestRemoveService() { ) suite.run(&removeServiceTestCase{ - T: suite.T(), - serviceNetwork: suite.serviceNetwork, + T: suite.T(), + serviceNetwork: suite.serviceNetwork, + interpretationTimeStore: suite.interpretationTimeValueStore, }) } func (t *removeServiceTestCase) GetInstruction() *kurtosis_plan_instruction.KurtosisPlanInstruction { - return remove_service.NewRemoveService(t.serviceNetwork) + return remove_service.NewRemoveService(t.serviceNetwork, t.interpretationTimeStore) } func (t *removeServiceTestCase) GetStarlarkCode() string {