Skip to content

Commit

Permalink
feat!: allow to mount multiple artifacts to the same folder in a serv…
Browse files Browse the repository at this point in the history
…ice. Users will need to replace the `Directory.artifac_name` field key with `Directory.artifac_names` (#2025)

## Description:
allow to mount multiple artifacts to the same folder in a service

## Is this change user facing?
YES

## References (if applicable):
Fix #1519
  • Loading branch information
leoporoli committed Jan 8, 2024
1 parent 6541884 commit b51df93
Show file tree
Hide file tree
Showing 29 changed files with 411 additions and 98 deletions.
Expand Up @@ -1738,7 +1738,7 @@ func (manager *DockerManager) getContainerHostConfig(
}

// NOTE: Do NOT use PublishAllPorts here!!!! This will work if a Dockerfile doesn't have an EXPOSE directive, but
// if the Dockerfile *does* have an EXPOSE directive then _only_ the ports with EXPOSE will be published
// if the Dockerfile *does* have and EXPOSE directive then _only_ the ports with EXPOSE will be published
// See also: https://www.ctl.io/developers/blog/post/docker-networking-rules/

containerHostConfigPtr := &container.HostConfig{
Expand Down
Expand Up @@ -97,9 +97,9 @@ func testFilesArtifactExpansion() *service_directory.FilesArtifactsExpansion {
"ENV_VAR1": "env_var1_value",
"ENV_VAR2": "env_var2_value",
},
ServiceDirpathsToArtifactIdentifiers: map[string]string{
"/pahth/number1": "first_identifier",
"/path/number2": "second_idenfifier",
ServiceDirpathsToArtifactIdentifiers: map[string][]string{
"/path/number1": {"first_identifier"},
"/path/number2": {"second_identifier"},
},
ExpanderDirpathsToServiceDirpaths: map[string]string{
"/expander/dir1": "/service/dir1",
Expand Down
Expand Up @@ -9,8 +9,8 @@ type FilesArtifactsExpansion struct {
// its operation
ExpanderEnvVars map[string]string

// Map of dirpaths on the target service to the artificat identifier that will be mounted at this location
ServiceDirpathsToArtifactIdentifiers map[string]string
// Map of dirpaths on the target service to the artifact identifier that will be mounted at this location
ServiceDirpathsToArtifactIdentifiers map[string][]string

// Map of dirpaths that the expander container expects (which the expander will expand into), mapped to
// dirpaths on the user service container where those same directories should be made available
Expand Down
Expand Up @@ -342,9 +342,9 @@ func testFilesArtifactExpansion() *service_directory.FilesArtifactsExpansion {
"ENV_VAR1": "env_var1_value",
"ENV_VAR2": "env_var2_value",
},
ServiceDirpathsToArtifactIdentifiers: map[string]string{
"/pahth/number1": "first_identifier",
"/path/number2": "second_idenfifier",
ServiceDirpathsToArtifactIdentifiers: map[string][]string{
"/path/number1": {"first_identifier"},
"/path/number2": {"second_identifier"},
},
ExpanderDirpathsToServiceDirpaths: map[string]string{
"/expander/dir1": "/service/dir1",
Expand Down
Expand Up @@ -209,10 +209,12 @@ func (builtin *AddServiceCapabilities) TryResolveWith(instructionsAreEqual bool,
// We check if there has been some updates to the files it's mounting. If that's the case, it should be rerun
filesArtifactsExpansion := builtin.serviceConfig.GetFilesArtifactsExpansion()
if filesArtifactsExpansion != nil {
for _, filesArtifactName := range filesArtifactsExpansion.ServiceDirpathsToArtifactIdentifiers {
if enclaveComponents.HasFilesArtifactBeenUpdated(filesArtifactName) {
enclaveComponents.AddService(builtin.serviceName, enclave_structure.ComponentIsUpdated)
return enclave_structure.InstructionIsUpdate
for _, filesArtifactNames := range filesArtifactsExpansion.ServiceDirpathsToArtifactIdentifiers {
for _, filesArtifactName := range filesArtifactNames {
if enclaveComponents.HasFilesArtifactBeenUpdated(filesArtifactName) {
enclaveComponents.AddService(builtin.serviceName, enclave_structure.ComponentIsUpdated)
return enclave_structure.InstructionIsUpdate
}
}
}
}
Expand Down
Expand Up @@ -87,9 +87,11 @@ func validateSingleService(validatorEnvironment *startosis_validator.ValidatorEn
return startosis_errors.NewValidationError("There was an error validating '%s' as service with the name '%s' already exists inside the package. Adding two different services with the same name isn't allowed; we recommend prefixing/suffixing the two service names or using two different names entirely.", AddServiceBuiltinName, serviceName)
}
if serviceConfig.GetFilesArtifactsExpansion() != nil {
for _, artifactName := range serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers {
if validatorEnvironment.DoesArtifactNameExist(artifactName) == startosis_validator.ComponentNotFound {
return startosis_errors.NewValidationError("There was an error validating '%s' as artifact name '%s' does not exist", AddServiceBuiltinName, artifactName)
for _, artifactNames := range serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers {
for _, artifactName := range artifactNames {
if validatorEnvironment.DoesArtifactNameExist(artifactName) == startosis_validator.ComponentNotFound {
return startosis_errors.NewValidationError("There was an error validating '%s' as artifact name '%s' does not exist", AddServiceBuiltinName, artifactName)
}
}
}
}
Expand Down
Expand Up @@ -259,9 +259,11 @@ func (builtin *AddServicesCapabilities) TryResolveWith(instructionsAreEqual bool
// Check whether one file as been updated - if yes the instruction will need to be rerun
filesArtifactsExpansion := serviceConfig.GetFilesArtifactsExpansion()
if filesArtifactsExpansion != nil {
for _, filesArtifactName := range filesArtifactsExpansion.ServiceDirpathsToArtifactIdentifiers {
if enclaveComponents.HasFilesArtifactBeenUpdated(filesArtifactName) {
atLeastOneFileHasBeenUpdated = true
for _, filesArtifactNames := range filesArtifactsExpansion.ServiceDirpathsToArtifactIdentifiers {
for _, filesArtifactName := range filesArtifactNames {
if enclaveComponents.HasFilesArtifactBeenUpdated(filesArtifactName) {
atLeastOneFileHasBeenUpdated = true
}
}
}
}
Expand Down
Expand Up @@ -192,7 +192,11 @@ func (builtin *RunPythonCapabilities) Interpret(_ string, arguments *builtin_arg
if interpretationErr != nil {
return nil, interpretationErr
}
filesArtifactExpansion, interpretationErr = service_config.ConvertFilesArtifactsMounts(filesArtifactMountDirPaths, builtin.serviceNetwork)
multipleFilesArtifactsMountDirPaths := map[string][]string{}
for pathToFile, fileArtifactName := range filesArtifactMountDirPaths {
multipleFilesArtifactsMountDirPaths[pathToFile] = []string{fileArtifactName}
}
filesArtifactExpansion, interpretationErr = service_config.ConvertFilesArtifactsMounts(multipleFilesArtifactsMountDirPaths, builtin.serviceNetwork)
if interpretationErr != nil {
return nil, interpretationErr
}
Expand Down Expand Up @@ -233,7 +237,7 @@ func (builtin *RunPythonCapabilities) Interpret(_ string, arguments *builtin_arg

func (builtin *RunPythonCapabilities) Validate(_ *builtin_argument.ArgumentValuesSet, validatorEnvironment *startosis_validator.ValidatorEnvironment) *startosis_errors.ValidationError {
// TODO add validation for python script
var serviceDirpathsToArtifactIdentifiers map[string]string
var serviceDirpathsToArtifactIdentifiers map[string][]string
if builtin.serviceConfig.GetFilesArtifactsExpansion() != nil {
serviceDirpathsToArtifactIdentifiers = builtin.serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers
}
Expand Down
Expand Up @@ -136,7 +136,11 @@ func (builtin *RunShCapabilities) Interpret(_ string, arguments *builtin_argumen
if interpretationErr != nil {
return nil, interpretationErr
}
filesArtifactExpansion, interpretationErr = service_config.ConvertFilesArtifactsMounts(filesArtifactMountDirPaths, builtin.serviceNetwork)
multipleFilesArtifactsMountDirPaths := map[string][]string{}
for pathToFile, fileArtifactName := range filesArtifactMountDirPaths {
multipleFilesArtifactsMountDirPaths[pathToFile] = []string{fileArtifactName}
}
filesArtifactExpansion, interpretationErr = service_config.ConvertFilesArtifactsMounts(multipleFilesArtifactsMountDirPaths, builtin.serviceNetwork)
if interpretationErr != nil {
return nil, interpretationErr
}
Expand Down Expand Up @@ -179,7 +183,7 @@ func (builtin *RunShCapabilities) Interpret(_ string, arguments *builtin_argumen

func (builtin *RunShCapabilities) Validate(_ *builtin_argument.ArgumentValuesSet, validatorEnvironment *startosis_validator.ValidatorEnvironment) *startosis_errors.ValidationError {
// TODO validate bash
var serviceDirpathsToArtifactIdentifiers map[string]string
var serviceDirpathsToArtifactIdentifiers map[string][]string
if builtin.serviceConfig.GetFilesArtifactsExpansion() != nil {
serviceDirpathsToArtifactIdentifiers = builtin.serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers
}
Expand Down
Expand Up @@ -134,7 +134,7 @@ func createInterpretationResult(resultUuid string, storeSpecList []*store_spec.S
return result
}

func validateTasksCommon(validatorEnvironment *startosis_validator.ValidatorEnvironment, storeSpecList []*store_spec.StoreSpec, serviceDirpathsToArtifactIdentifiers map[string]string, imageName string) *startosis_errors.ValidationError {
func validateTasksCommon(validatorEnvironment *startosis_validator.ValidatorEnvironment, storeSpecList []*store_spec.StoreSpec, serviceDirpathsToArtifactIdentifiers map[string][]string, imageName string) *startosis_errors.ValidationError {
if storeSpecList != nil {
if err := validatePathIsUniqueWhileCreatingFileArtifact(storeSpecList); err != nil {
return startosis_errors.WrapWithValidationError(err, "error occurred while validating file paths to copy into file artifact")
Expand All @@ -145,9 +145,11 @@ func validateTasksCommon(validatorEnvironment *startosis_validator.ValidatorEnvi
}
}

for _, artifactName := range serviceDirpathsToArtifactIdentifiers {
if validatorEnvironment.DoesArtifactNameExist(artifactName) == startosis_validator.ComponentNotFound {
return startosis_errors.NewValidationError("There was an error validating '%s' as artifact name '%s' does not exist", RunPythonBuiltinName, artifactName)
for _, artifactNames := range serviceDirpathsToArtifactIdentifiers {
for _, artifactName := range artifactNames {
if validatorEnvironment.DoesArtifactNameExist(artifactName) == startosis_validator.ComponentNotFound {
return startosis_errors.NewValidationError("There was an error validating '%s' as artifact name '%s' does not exist", RunPythonBuiltinName, artifactName)
}
}
}

Expand Down
@@ -1,6 +1,7 @@
package builtin_argument

import (
"fmt"
"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_errors"
"go.starlark.net/starlark"
Expand All @@ -21,6 +22,25 @@ func NonEmptyString(value starlark.Value, argNameForLogging string) *startosis_e
return nil
}

func StringListWithNotEmptyValues(value starlark.Value, argNameForLogging string) *startosis_errors.InterpretationError {
starlarkList, ok := value.(*starlark.List)
if !ok {
return startosis_errors.NewInterpretationError("Value for '%s' was expected to be a starlark.List but was '%s'", argNameForLogging, reflect.TypeOf(value))
}
iterator := starlarkList.Iterate()
defer iterator.Done()
var itemValue starlark.Value
var index = 0
for iterator.Next(&itemValue) {
argumentDescription := fmt.Sprintf("element %d in argument '%s'", index, argNameForLogging)
if interpretationErr := NonEmptyString(itemValue, argumentDescription); interpretationErr != nil {
return interpretationErr
}
index++
}
return nil
}

func Uint64InRange(value starlark.Value, argNameForLogging string, min uint64, max uint64) *startosis_errors.InterpretationError {
valueInt, ok := value.(starlark.Int)
if !ok {
Expand Down
Expand Up @@ -20,17 +20,17 @@ func (suite *KurtosisTypeConstructorTestSuite) TestDirectoryFileArtifact() {
}

func (t *directoryFileArtifactTestCase) GetStarlarkCode() string {
return fmt.Sprintf("%s(%s=%q)", directory.DirectoryTypeName, directory.ArtifactNameAttr, testFilesArtifactName1)
return fmt.Sprintf("%s(%s=[%q])", directory.DirectoryTypeName, directory.ArtifactNamesAttr, testFilesArtifactName1)
}

func (t *directoryFileArtifactTestCase) Assert(typeValue builtin_argument.KurtosisValueType) {
directoryStarlark, ok := typeValue.(*directory.Directory)
require.True(t, ok)

artifactName, found, err := directoryStarlark.GetArtifactNameIfSet()
artifactNames, found, err := directoryStarlark.GetArtifactNamesIfSet()
require.Nil(t, err)
require.True(t, found)
require.Equal(t, testFilesArtifactName1, artifactName)
require.Equal(t, []string{testFilesArtifactName1}, artifactNames)

persistentKey, found, err := directoryStarlark.GetPersistentKeyIfSet()
require.Nil(t, err)
Expand Down
@@ -0,0 +1,33 @@
package test_engine

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

type directoryMultipleFileArtifactsTestCase struct {
*testing.T
}

func (suite *KurtosisTypeConstructorTestSuite) TestDirectoryMultipleFileArtifacts() {
suite.run(&directoryMultipleFileArtifactsTestCase{
T: suite.T(),
})
}

func (t *directoryMultipleFileArtifactsTestCase) GetStarlarkCode() string {
return fmt.Sprintf("%s(%s=[%q, %q])", directory.DirectoryTypeName, directory.ArtifactNamesAttr, testFilesArtifactName1, testFilesArtifactName2)
}

func (t *directoryMultipleFileArtifactsTestCase) Assert(typeValue builtin_argument.KurtosisValueType) {
directoryStarlark, ok := typeValue.(*directory.Directory)
require.True(t, ok)

artifactNames, found, err := directoryStarlark.GetArtifactNamesIfSet()
require.Nil(t, err)
require.True(t, found)
require.Equal(t, []string{testFilesArtifactName1, testFilesArtifactName2}, artifactNames)
}
Expand Up @@ -26,10 +26,10 @@ func (t *directoryPersistentDirectoryTestCase) Assert(typeValue builtin_argument
directoryStarlark, ok := typeValue.(*directory.Directory)
require.True(t, ok)

artifactName, found, err := directoryStarlark.GetArtifactNameIfSet()
artifactNames, found, err := directoryStarlark.GetArtifactNamesIfSet()
require.Nil(t, err)
require.False(t, found)
require.Empty(t, artifactName)
require.Empty(t, artifactNames)

persistentKey, found, err := directoryStarlark.GetPersistentKeyIfSet()
require.Nil(t, err)
Expand Down
Expand Up @@ -82,9 +82,9 @@ func (t *serviceConfigFullTestCaseBackwardCompatible) Assert(typeValue builtin_a
}
require.Equal(t, expectedPublicPorts, serviceConfig.GetPublicPorts())

expectedFilesArtifactMap := map[string]string{
testFilesArtifactPath1: testFilesArtifactName1,
testFilesArtifactPath2: testFilesArtifactName2,
expectedFilesArtifactMap := map[string][]string{
testFilesArtifactPath1: {testFilesArtifactName1},
testFilesArtifactPath2: {testFilesArtifactName2},
}
require.NotNil(t, serviceConfig.GetFilesArtifactsExpansion())
require.Equal(t, expectedFilesArtifactMap, serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers)
Expand Down
Expand Up @@ -35,8 +35,8 @@ func (suite *KurtosisTypeConstructorTestSuite) TestServiceConfigFull() {
}

func (t *serviceConfigFullTestCase) GetStarlarkCode() string {
fileArtifact1 := fmt.Sprintf("%s(%s=%q)", directory.DirectoryTypeName, directory.ArtifactNameAttr, testFilesArtifactName1)
fileArtifact2 := fmt.Sprintf("%s(%s=%q)", directory.DirectoryTypeName, directory.ArtifactNameAttr, testFilesArtifactName2)
fileArtifact1 := fmt.Sprintf("%s(%s=[%q])", directory.DirectoryTypeName, directory.ArtifactNamesAttr, testFilesArtifactName1)
fileArtifact2 := fmt.Sprintf("%s(%s=[%q])", directory.DirectoryTypeName, directory.ArtifactNamesAttr, testFilesArtifactName2)
persistentDirectory := fmt.Sprintf("%s(%s=%q)", directory.DirectoryTypeName, directory.PersistentKeyAttr, testPersistentDirectoryKey)
starlarkCode := fmt.Sprintf("%s(%s=%q, %s=%s, %s=%s, %s=%s, %s=%s, %s=%s, %s=%s, %s=%q, %s=%d, %s=%d, %s=%d, %s=%d, %s=%s, %s=%v)",
service_config.ServiceConfigTypeName,
Expand Down Expand Up @@ -90,9 +90,9 @@ func (t *serviceConfigFullTestCase) Assert(typeValue builtin_argument.KurtosisVa
}
require.Equal(t, expectedPublicPorts, serviceConfig.GetPublicPorts())

expectedFilesArtifactMap := map[string]string{
testFilesArtifactPath1: testFilesArtifactName1,
testFilesArtifactPath2: testFilesArtifactName2,
expectedFilesArtifactMap := map[string][]string{
testFilesArtifactPath1: {testFilesArtifactName1},
testFilesArtifactPath2: {testFilesArtifactName2},
}
require.NotNil(t, serviceConfig.GetFilesArtifactsExpansion())
require.Equal(t, expectedFilesArtifactMap, serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers)
Expand Down
@@ -0,0 +1,64 @@
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/kurtosis_starlark_framework/builtin_argument"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/directory"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/service_config"
"github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages"
"github.com/stretchr/testify/require"
"net"
"testing"
)

type serviceConfigMultipleFilesInSameFolderTestCase struct {
*testing.T
serviceNetwork *service_network.MockServiceNetwork
packageContentProvider *startosis_packages.MockPackageContentProvider
}

func (suite *KurtosisTypeConstructorTestSuite) TestServiceConfigMultipleFilesInSameFolder() {

suite.serviceNetwork.EXPECT().GetApiContainerInfo().Times(1).Return(
service_network.NewApiContainerInfo(net.IPv4(0, 0, 0, 0), 0, "0.0.0"),
)

suite.run(&serviceConfigMultipleFilesInSameFolderTestCase{
T: suite.T(),
serviceNetwork: suite.serviceNetwork,
packageContentProvider: suite.packageContentProvider,
})
}

func (t *serviceConfigMultipleFilesInSameFolderTestCase) GetStarlarkCode() string {
filesArtifactsDirectory := fmt.Sprintf("%s(%s=[%q, %q])", directory.DirectoryTypeName, directory.ArtifactNamesAttr, testFilesArtifactName1, testFilesArtifactName2)
starlarkCode := fmt.Sprintf("%s(%s=%q, %s=%s)",
service_config.ServiceConfigTypeName,
service_config.ImageAttr, testContainerImageName,
service_config.FilesAttr, fmt.Sprintf("{%q: %s}", testFilesArtifactPath1, filesArtifactsDirectory),
)

return starlarkCode
}

func (t *serviceConfigMultipleFilesInSameFolderTestCase) Assert(typeValue builtin_argument.KurtosisValueType) {
serviceConfigStarlark, ok := typeValue.(*service_config.ServiceConfig)
require.True(t, ok)

serviceConfig, err := serviceConfigStarlark.ToKurtosisType(
t.serviceNetwork,
testModulePackageId,
testModuleMainFileLocator,
t.packageContentProvider,
testNoPackageReplaceOptions)
require.Nil(t, err)

require.Equal(t, testContainerImageName, serviceConfig.GetContainerImageName())

expectedFilesArtifactMap := map[string][]string{
testFilesArtifactPath1: {testFilesArtifactName1, testFilesArtifactName2},
}
require.NotNil(t, serviceConfig.GetFilesArtifactsExpansion())
require.Equal(t, expectedFilesArtifactMap, serviceConfig.GetFilesArtifactsExpansion().ServiceDirpathsToArtifactIdentifiers)
}

0 comments on commit b51df93

Please sign in to comment.