Skip to content

Commit

Permalink
feat: set service v0 (#2372)
Browse files Browse the repository at this point in the history
## Description

Adds `set_service` instruction which can be used to override the service
config of a service added earlier in the plan. This can be really useful
for two cases:
1. A user wants to set a specific image (or other config) for a service
in a package but the package author has not made the image configurable.
This can be done by appending `set_service` to the plan.
ex.
```
postgres = import_module("github.com/kurtosis-tech/postgres-package/main.star")

def run(plan, args):
    postgres.run(plan)
    plan.set_service(name="postgres", config=ServiceConfig(image="postgres:bullseye"))
```
2. A user wants to swap out the image (or other config) of a service
running in an enclave. This can be done by appending `set_service` to
the plan and enclave editing.
ex. 
https://www.loom.com/share/f88d0f5555dd49b0aeabc55c5ed41d43

This instruction has some functionality missing such as overriding
ports, env vars, and cmd/entrypoint as these require special handling of
future references and handling consecutive set service instructions
properly but functionality can be added iteratively in future PRs.

## Is this change user facing?
YES

## References:
#2057
  • Loading branch information
tedim52 authored Apr 25, 2024
1 parent 136ec4c commit 7056164
Show file tree
Hide file tree
Showing 14 changed files with 841 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ type privateServiceConfig struct {

// Configuration for container engine to pull an in a private registry behind authentication
// If nil, we will use the ContainerImageName and not use any auth
// Mutually exclusive from ImageBuildSpec, ContainerImageName
ImagerRegistrySpec *image_registry_spec.ImageRegistrySpec
// Mutually exclusive from ImageBuildSpec, ContainerImageName, NixBuildSpec
ImageRegistrySpec *image_registry_spec.ImageRegistrySpec

// Configuration for container engine to using Nix
// If nil, we will use the ContainerImageName and not use any Nix
// Mutually exclusive from ImageBuildSpec, ContainerImageName, ImageRegistrySpec
NixBuildSpec *nix_build_spec.NixBuildSpec

PrivatePorts map[string]*port_spec.PortSpec
Expand Down Expand Up @@ -88,7 +91,7 @@ func CreateServiceConfig(
cpuAllocationMillicpus uint64,
memoryAllocationMegabytes uint64,
privateIPAddrPlaceholder string,
minCpuMilliCores uint64,
minCpuMilliCpus uint64,
minMemoryMegaBytes uint64,
labels map[string]string,
user *service_user.ServiceUser,
Expand All @@ -104,7 +107,7 @@ func CreateServiceConfig(
internalServiceConfig := &privateServiceConfig{
ContainerImageName: containerImageName,
ImageBuildSpec: imageBuildSpec,
ImagerRegistrySpec: imageRegistrySpec,
ImageRegistrySpec: imageRegistrySpec,
NixBuildSpec: nixBuildSpec,
PrivatePorts: privatePorts,
PublicPorts: publicPorts,
Expand All @@ -117,7 +120,7 @@ func CreateServiceConfig(
MemoryAllocationMegabytes: memoryAllocationMegabytes,
PrivateIPAddrPlaceholder: privateIPAddrPlaceholder,
// The minimum resources specification is only available for kubernetes
MinCpuAllocationMilliCpus: minCpuMilliCores,
MinCpuAllocationMilliCpus: minCpuMilliCpus,
MinMemoryAllocationMegabytes: minMemoryMegaBytes,
Labels: labels,
User: user,
Expand All @@ -133,18 +136,34 @@ func (serviceConfig *ServiceConfig) GetContainerImageName() string {
return serviceConfig.privateServiceConfig.ContainerImageName
}

func (serviceConfig *ServiceConfig) SetContainerImageName(containerImage string) {
serviceConfig.privateServiceConfig.ContainerImageName = containerImage
}

func (serviceConfig *ServiceConfig) GetImageBuildSpec() *image_build_spec.ImageBuildSpec {
return serviceConfig.privateServiceConfig.ImageBuildSpec
}

func (serviceConfig *ServiceConfig) SetImageBuildSpec(imageBuildSpec *image_build_spec.ImageBuildSpec) {
serviceConfig.privateServiceConfig.ImageBuildSpec = imageBuildSpec
}

func (serviceConfig *ServiceConfig) GetImageRegistrySpec() *image_registry_spec.ImageRegistrySpec {
return serviceConfig.privateServiceConfig.ImagerRegistrySpec
return serviceConfig.privateServiceConfig.ImageRegistrySpec
}

func (serviceConfig *ServiceConfig) SetImageRegistrySpec(imageRegistrySpec *image_registry_spec.ImageRegistrySpec) {
serviceConfig.privateServiceConfig.ImageRegistrySpec = imageRegistrySpec
}

func (serviceConfig *ServiceConfig) GetNixBuildSpec() *nix_build_spec.NixBuildSpec {
return serviceConfig.privateServiceConfig.NixBuildSpec
}

func (serviceConfig *ServiceConfig) SetNixBuildSpec(nixBuildSpec *nix_build_spec.NixBuildSpec) {
serviceConfig.privateServiceConfig.NixBuildSpec = nixBuildSpec
}

func (serviceConfig *ServiceConfig) GetPrivatePorts() map[string]*port_spec.PortSpec {
return serviceConfig.privateServiceConfig.PrivatePorts
}
Expand Down Expand Up @@ -177,10 +196,18 @@ func (serviceConfig *ServiceConfig) GetCPUAllocationMillicpus() uint64 {
return serviceConfig.privateServiceConfig.CpuAllocationMillicpus
}

func (serviceConfig *ServiceConfig) SetCPUAllocationMillicpus(cpuAllocation uint64) {
serviceConfig.privateServiceConfig.CpuAllocationMillicpus = cpuAllocation
}

func (serviceConfig *ServiceConfig) GetMemoryAllocationMegabytes() uint64 {
return serviceConfig.privateServiceConfig.MemoryAllocationMegabytes
}

func (serviceConfig *ServiceConfig) SetMemoryAllocationMegabytes(memoryAllocation uint64) {
serviceConfig.privateServiceConfig.MemoryAllocationMegabytes = memoryAllocation
}

func (serviceConfig *ServiceConfig) GetPrivateIPAddrPlaceholder() string {
return serviceConfig.privateServiceConfig.PrivateIPAddrPlaceholder
}
Expand All @@ -190,27 +217,51 @@ func (serviceConfig *ServiceConfig) GetMinCPUAllocationMillicpus() uint64 {
return serviceConfig.privateServiceConfig.MinCpuAllocationMilliCpus
}

func (serviceConfig *ServiceConfig) SetMinCPUAllocationMillicpus(cpuAllocation uint64) {
serviceConfig.privateServiceConfig.MinCpuAllocationMilliCpus = cpuAllocation
}

// only available for Kubernetes
func (serviceConfig *ServiceConfig) GetMinMemoryAllocationMegabytes() uint64 {
return serviceConfig.privateServiceConfig.MinMemoryAllocationMegabytes
}

func (serviceConfig *ServiceConfig) SetMinMemoryAllocationMegabytes(memoryAllocation uint64) {
serviceConfig.privateServiceConfig.MemoryAllocationMegabytes = memoryAllocation
}

func (serviceConfig *ServiceConfig) GetUser() *service_user.ServiceUser {
return serviceConfig.privateServiceConfig.User
}

func (serviceConfig *ServiceConfig) SetUser(user *service_user.ServiceUser) {
serviceConfig.privateServiceConfig.User = user
}

func (serviceConfig *ServiceConfig) GetLabels() map[string]string {
return serviceConfig.privateServiceConfig.Labels
}

func (serviceConfig *ServiceConfig) SetLabels(labels map[string]string) {
serviceConfig.privateServiceConfig.Labels = labels
}

func (serviceConfig *ServiceConfig) GetTolerations() []v1.Toleration {
return serviceConfig.privateServiceConfig.Tolerations
}

func (serviceConfig *ServiceConfig) SetTolerations(tolerations []v1.Toleration) {
serviceConfig.privateServiceConfig.Tolerations = tolerations
}

func (serviceConfig *ServiceConfig) GetImageDownloadMode() image_download_mode.ImageDownloadMode {
return serviceConfig.privateServiceConfig.ImageDownloadMode
}

func (serviceConfig *ServiceConfig) SetImageDownloadMode(mode image_download_mode.ImageDownloadMode) {
serviceConfig.privateServiceConfig.ImageDownloadMode = mode
}

func (serviceConfig *ServiceConfig) MarshalJSON() ([]byte, error) {
return json.Marshal(serviceConfig.privateServiceConfig)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@ import (
)

type InterpretationTimeValueStore struct {
serviceValues *serviceInterpretationValueRepository
serde *kurtosis_types.StarlarkValueSerde
serviceConfigValues map[service.ServiceName]*service.ServiceConfig
setServiceConfigValues map[service.ServiceName]*service.ServiceConfig
serviceValues *serviceInterpretationValueRepository
serde *kurtosis_types.StarlarkValueSerde
}

func CreateInterpretationTimeValueStore(enclaveDb *enclave_db.EnclaveDB, serde *kurtosis_types.StarlarkValueSerde) (*InterpretationTimeValueStore, error) {
serviceValuesRepository, err := getOrCreateNewServiceInterpretationTimeValueRepository(enclaveDb, serde)
if err != nil {
return nil, stacktrace.Propagate(err, "An error occurred creating interpretation time value store")
}
return &InterpretationTimeValueStore{serviceValues: serviceValuesRepository, serde: serde}, nil
return &InterpretationTimeValueStore{
serviceConfigValues: map[service.ServiceName]*service.ServiceConfig{},
setServiceConfigValues: map[service.ServiceName]*service.ServiceConfig{},
serviceValues: serviceValuesRepository,
serde: serde}, nil
}

func (itvs *InterpretationTimeValueStore) PutService(name service.ServiceName, service *kurtosis_types.Service) error {
Expand Down Expand Up @@ -50,3 +56,32 @@ func (itvs *InterpretationTimeValueStore) RemoveService(name service.ServiceName
}
return nil
}

func (itvs *InterpretationTimeValueStore) PutServiceConfig(name service.ServiceName, serviceConfig *service.ServiceConfig) {
itvs.serviceConfigValues[name] = serviceConfig
}

func (itvs *InterpretationTimeValueStore) GetServiceConfig(name service.ServiceName) (*service.ServiceConfig, error) {
serviceConfig, ok := itvs.serviceConfigValues[name]
if !ok {
return nil, stacktrace.NewError("Did not find new service config for '%v' in interpretation time value store.", name)
}
return serviceConfig, nil
}

func (itvs *InterpretationTimeValueStore) SetServiceConfig(name service.ServiceName, serviceConfig *service.ServiceConfig) {
itvs.setServiceConfigValues[name] = serviceConfig
}

func (itvs *InterpretationTimeValueStore) ExistsNewServiceConfigForService(name service.ServiceName) bool {
_, doesConfigFromSetServiceInstructionExists := itvs.setServiceConfigValues[name]
return doesConfigFromSetServiceInstructionExists
}

func (itvs *InterpretationTimeValueStore) GetNewServiceConfig(name service.ServiceName) (*service.ServiceConfig, error) {
newServiceConfig, ok := itvs.setServiceConfigValues[name]
if !ok {
return nil, stacktrace.NewError("Did not find new service config for '%v' in interpretation time value store.", name)
}
return newServiceConfig, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package interpretation_time_value_store

import (
"fmt"
"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_download_mode"
"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"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/shared_helpers"
"github.com/stretchr/testify/require"
bolt "go.etcd.io/bbolt"
"os"
"testing"
)

const (
testServiceName = service.ServiceName("datastore-service")
testContainerImageName = "datastore-image"
enclaveDbFilePerm = 0666
)

func TestGetServiceConfigReturnsError(t *testing.T) {
enclaveDb := getEnclaveDBForTest(t)
dummySerde := shared_helpers.NewDummyStarlarkValueSerDeForTest()
itvs, err := CreateInterpretationTimeValueStore(enclaveDb, dummySerde)
require.NoError(t, err)

// no service config exists in store
_, err = itvs.GetServiceConfig(testServiceName)
require.Error(t, err)
}

func TestPutServiceConfig(t *testing.T) {
enclaveDb := getEnclaveDBForTest(t)
dummySerde := shared_helpers.NewDummyStarlarkValueSerDeForTest()
itvs, err := CreateInterpretationTimeValueStore(enclaveDb, dummySerde)
require.NoError(t, err)

expectedServiceConfig, err := getTestServiceConfigForService(testServiceName, "latest")
require.NoError(t, err)

itvs.PutServiceConfig(testServiceName, expectedServiceConfig)

actualServiceConfig, err := itvs.GetServiceConfig(testServiceName)
require.NoError(t, err)
require.Equal(t, expectedServiceConfig.GetContainerImageName(), actualServiceConfig.GetContainerImageName())
}

func TestPutNewServiceConfig(t *testing.T) {
enclaveDb := getEnclaveDBForTest(t)
dummySerde := shared_helpers.NewDummyStarlarkValueSerDeForTest()
itvs, err := CreateInterpretationTimeValueStore(enclaveDb, dummySerde)
require.NoError(t, err)

oldServiceConfig, err := getTestServiceConfigForService(testServiceName, "older")
require.NoError(t, err)
itvs.PutServiceConfig(testServiceName, oldServiceConfig)

newerServiceConfig, err := getTestServiceConfigForService(testServiceName, "latest")
require.NoError(t, err)
itvs.SetServiceConfig(testServiceName, newerServiceConfig)

actualNewerServiceConfig, err := itvs.GetNewServiceConfig(testServiceName)
require.NoError(t, err)
require.Equal(t, newerServiceConfig.GetContainerImageName(), actualNewerServiceConfig.GetContainerImageName())
}

func getTestServiceConfigForService(name service.ServiceName, imageTag string) (*service.ServiceConfig, error) {
return service.CreateServiceConfig(
fmt.Sprintf("%v-%v:%v", name, testContainerImageName, imageTag),
nil,
nil,
nil,
nil,
nil,
[]string{},
[]string{},
map[string]string{},
nil,
nil,
0,
0,
"IP-ADDRESS",
0,
0,
map[string]string{},
nil,
nil,
nil,
image_download_mode.ImageDownloadMode_Always)
}

func getEnclaveDBForTest(t *testing.T) *enclave_db.EnclaveDB {
file, err := os.CreateTemp("/tmp", "*.db")
defer func() {
err = os.Remove(file.Name())
require.NoError(t, err)
}()

require.NoError(t, err)
db, err := bolt.Open(file.Name(), enclaveDbFilePerm, nil)
require.NoError(t, err)
enclaveDb := &enclave_db.EnclaveDB{
DB: db,
}

return enclaveDb
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"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"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/request"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/set_service"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/start_service"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/stop_service"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/store_service_files"
Expand Down Expand Up @@ -73,6 +74,7 @@ func KurtosisPlanInstructions(
add_service.NewAddServices(serviceNetwork, runtimeValueStore, packageId, packageContentProvider, packageReplaceOptions, interpretationTimeValueStore, imageDownloadMode),
get_service.NewGetService(interpretationTimeValueStore),
get_services.NewGetServices(interpretationTimeValueStore),
set_service.NewSetService(serviceNetwork, interpretationTimeValueStore, packageId, packageContentProvider, packageReplaceOptions, imageDownloadMode),
get_files_artifact.NewGetFilesArtifact(),
verify.NewVerify(runtimeValueStore),
exec.NewExec(serviceNetwork, runtimeValueStore),
Expand Down
Loading

0 comments on commit 7056164

Please sign in to comment.