Skip to content

Commit 5d9b9b5

Browse files
committed
Added volume support in app config
1 parent 1968063 commit 5d9b9b5

File tree

10 files changed

+130
-16
lines changed

10 files changed

+130
-16
lines changed

cmd/clace/app_cmds.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ func appCreateCommand(commonFlags []cli.Flag, clientConfig *types.ClientConfig)
8080
Usage: "Set an argument for building the container image. Format is argKey=argValue",
8181
})
8282

83+
flags = append(flags,
84+
&cli.StringSliceFlag{
85+
Name: "container-volume",
86+
Aliases: []string{"cvol"},
87+
Usage: "Set an container volume entry",
88+
})
89+
8390
flags = append(flags,
8491
&cli.StringSliceFlag{
8592
Name: "app-config",
@@ -150,6 +157,8 @@ Examples:
150157
cargMap[key] = value
151158
}
152159

160+
containerVolumes := cCtx.StringSlice("container-volume")
161+
153162
appConfig := cCtx.StringSlice("app-config")
154163
confMap := make(map[string]string)
155164
for _, def := range appConfig {
@@ -171,6 +180,7 @@ Examples:
171180
ParamValues: paramValues,
172181
ContainerOptions: coptMap,
173182
ContainerArgs: cargMap,
183+
ContainerVolumes: containerVolumes,
174184
AppConfig: confMap,
175185
}
176186
var createResult types.AppCreateResponse

cmd/clace/app_update_cmds.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ func appUpdateMetadataCommand(commonFlags []cli.Flag, clientConfig *types.Client
266266
appUpdateAppSpec(commonFlags, clientConfig),
267267
appUpdateConfig(commonFlags, clientConfig, "container-option", "copt", types.AppMetadataContainerOptions),
268268
appUpdateConfig(commonFlags, clientConfig, "container-arg", "carg", types.AppMetadataContainerArgs),
269+
appUpdateConfig(commonFlags, clientConfig, "container-volumes", "cvol", types.AppMetadataContainerVolumes),
269270
appUpdateConfig(commonFlags, clientConfig, "app-config", "conf", types.AppMetadataAppConfig),
270271
},
271272
}

internal/app/app.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,8 @@ func (a *App) loadContainerManager(stripAppPath bool) error {
460460
}
461461

462462
a.containerManager, err = NewContainerManager(a.Logger, a,
463-
fileName, a.systemConfig, port, lifetime, scheme, health, buildDir, a.sourceFS, a.paramMap, a.appConfig.Container, stripAppPath)
463+
fileName, a.systemConfig, port, lifetime, scheme, health, buildDir,
464+
a.sourceFS, a.paramMap, a.appConfig.Container, stripAppPath, a.Metadata.ContainerVolumes)
464465
if err != nil {
465466
return fmt.Errorf("error creating container manager: %w", err)
466467
}

internal/app/container/volume.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func GenVolumeName(appId types.AppId, dirName string) VolumeName {
2121

2222
func (c ContainerCommand) VolumeExists(config *types.SystemConfig, name VolumeName) bool {
2323
c.Debug().Msgf("Checking volume exists %s", name)
24-
cmd := exec.Command(config.ContainerCommand, "volume", "exists", string(name))
24+
cmd := exec.Command(config.ContainerCommand, "volume", "inspect", string(name))
2525
output, err := cmd.CombinedOutput()
2626
if err != nil {
2727
c.Debug().Msgf("volume exists check failed %s %s %s", name, err, output)

internal/app/container_manager.go

Lines changed: 105 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ type ContainerManager struct {
6666

6767
func NewContainerManager(logger *types.Logger, app *App, containerFile string,
6868
systemConfig *types.SystemConfig, configPort int64, lifetime, scheme, health, buildDir string, sourceFS appfs.ReadableFS,
69-
paramMap map[string]string, containerConfig types.Container, stripAppPath bool) (*ContainerManager, error) {
69+
paramMap map[string]string, containerConfig types.Container, stripAppPath bool,
70+
containerVolumes []string) (*ContainerManager, error) {
7071

7172
image := ""
7273
volumes := []string{}
@@ -138,6 +139,7 @@ func NewContainerManager(logger *types.Logger, app *App, containerFile string,
138139
command: container.ContainerCommand{Logger: logger},
139140
paramMap: paramMap,
140141
volumes: volumes,
142+
extraVolumes: containerVolumes,
141143
containerConfig: containerConfig,
142144
stateLock: sync.RWMutex{},
143145
currentState: ContainerStateUnknown,
@@ -268,6 +270,46 @@ func (m *ContainerManager) GetHealthUrl(appHealthUrl string) string {
268270
return healthUrl
269271
}
270272

273+
func getMapHash(input map[string]string) (string, error) {
274+
keys := []string{}
275+
for k := range input {
276+
keys = append(keys, k)
277+
}
278+
slices.Sort(keys) // Sort the keys to ensure consistent hash
279+
280+
hashBuilder := strings.Builder{}
281+
for _, paramName := range keys {
282+
paramVal := input[paramName]
283+
// Default to string
284+
hashBuilder.WriteString(paramName)
285+
hashBuilder.WriteByte(0)
286+
hashBuilder.WriteString(paramVal)
287+
hashBuilder.WriteByte(0)
288+
}
289+
290+
sha := sha256.New()
291+
if _, err := sha.Write([]byte(hashBuilder.String())); err != nil {
292+
return "", err
293+
}
294+
return hex.EncodeToString(sha.Sum(nil)), nil
295+
}
296+
297+
func getSliceHash(input []string) (string, error) {
298+
slices.Sort(input) // Sort the keys to ensure consistent hash
299+
300+
hashBuilder := strings.Builder{}
301+
for _, v := range input {
302+
hashBuilder.WriteString(v)
303+
hashBuilder.WriteByte(0)
304+
}
305+
306+
sha := sha256.New()
307+
if _, err := sha.Write([]byte(hashBuilder.String())); err != nil {
308+
return "", err
309+
}
310+
return hex.EncodeToString(sha.Sum(nil)), nil
311+
}
312+
271313
func (m *ContainerManager) GetEnvMap() (map[string]string, string) {
272314
paramKeys := []string{}
273315
for k := range m.paramMap {
@@ -327,15 +369,8 @@ func (m *ContainerManager) createSpecFiles() ([]string, error) {
327369
return created, nil
328370
}
329371

330-
func (m *ContainerManager) getVolumes() []string {
331-
allVolumes := append(m.volumes, m.extraVolumes...)
332-
slices.Sort(allVolumes)
333-
return slices.Compact(allVolumes)
334-
}
335-
336372
func (m *ContainerManager) createVolumes() error {
337-
allVolumes := m.getVolumes()
338-
for _, v := range allVolumes {
373+
for _, v := range m.volumes {
339374
volumeName := container.GenVolumeName(m.app.Id, v)
340375
if !m.command.VolumeExists(m.systemConfig, volumeName) {
341376
err := m.command.VolumeCreate(m.systemConfig, volumeName)
@@ -344,19 +379,62 @@ func (m *ContainerManager) createVolumes() error {
344379
}
345380
}
346381
}
382+
383+
for _, volArg := range m.extraVolumes {
384+
parsedName := m.parseVolumeName(volArg)
385+
if parsedName == "" {
386+
continue
387+
}
388+
genVolumeName := container.GenVolumeName(m.app.Id, parsedName)
389+
if !m.command.VolumeExists(m.systemConfig, genVolumeName) {
390+
err := m.command.VolumeCreate(m.systemConfig, genVolumeName)
391+
if err != nil {
392+
return fmt.Errorf("error creating volume %s: %w", genVolumeName, err)
393+
}
394+
}
395+
}
347396
return nil
348397
}
349398

350399
func (m *ContainerManager) getMountArgs() []string {
351-
allVolumes := m.getVolumes()
352400
args := []string{}
353-
for _, v := range allVolumes {
401+
for _, v := range m.volumes {
354402
volumeName := container.GenVolumeName(m.app.Id, v)
355403
args = append(args, fmt.Sprintf("--mount=type=volume,source=%s,target=%s", volumeName, v))
356404
}
405+
406+
for _, volArg := range m.extraVolumes {
407+
parsedName := m.parseVolumeName(volArg)
408+
if parsedName == "" {
409+
args = append(args, fmt.Sprintf("--volume=%s", volArg))
410+
} else {
411+
genVolumeName := container.GenVolumeName(m.app.Id, parsedName)
412+
split := strings.Split(volArg, ":")
413+
var volString string
414+
if len(split) > 1 {
415+
split[0] = string(genVolumeName)
416+
volString = strings.Join(split, ":")
417+
} else {
418+
volString = string(genVolumeName) + ":" + volArg
419+
}
420+
args = append(args, fmt.Sprintf("--volume=%s", volString))
421+
}
422+
}
357423
return args
358424
}
359425

426+
// parseVolumeName gets the first part of the volume definition. It returns "" for a bind
427+
// mount. Otherwise it returns the volume name
428+
func (m *ContainerManager) parseVolumeName(arg string) string {
429+
split := strings.Split(arg, ":")
430+
firstPart := split[0]
431+
if len(split) > 1 && len(firstPart) > 0 && firstPart[:1] == "/" {
432+
return ""
433+
}
434+
435+
return firstPart
436+
}
437+
360438
func (m *ContainerManager) DevReload(dryRun bool) error {
361439
var imageName container.ImageName = container.ImageName(m.image)
362440
if imageName == "" {
@@ -485,13 +563,27 @@ func (m *ContainerManager) getAppHash() (string, error) {
485563
}
486564

487565
_, envHash := m.GetEnvMap()
488-
fullHashVal := fmt.Sprintf("%s-%s", sourceHash, envHash)
566+
567+
coptHash, err := getMapHash(m.app.Metadata.ContainerOptions)
568+
if err != nil {
569+
return "", fmt.Errorf("error getting copt hash: %w", err)
570+
}
571+
cargHash, err := getMapHash(m.app.Metadata.ContainerArgs)
572+
if err != nil {
573+
return "", fmt.Errorf("error getting carg hash: %w", err)
574+
}
575+
cvolHash, err := getSliceHash(m.app.Metadata.ContainerVolumes)
576+
if err != nil {
577+
return "", fmt.Errorf("error getting cvol hash: %w", err)
578+
}
579+
fullHashVal := fmt.Sprintf("%s-%s-%s-%s-%s", sourceHash, envHash, coptHash, cargHash, cvolHash)
489580
sha := sha256.New()
490581
if _, err := sha.Write([]byte(fullHashVal)); err != nil {
491582
return "", err
492583
}
493584
fullHash := hex.EncodeToString(sha.Sum(nil))
494-
m.Debug().Msgf("Source hash %s Env hash %s Full hash %s", sourceHash, envHash, fullHash)
585+
m.Debug().Msgf("Source hash %s Env hash %s copt hash %s args hash %s cvol hash %s Full hash %s",
586+
sourceHash, envHash, coptHash, cargHash, cvolHash, fullHash)
495587
return fullHash, nil
496588
}
497589

internal/server/app_apis.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ func (s *Server) CreateApp(ctx context.Context, appPath string, approve, dryRun
9595
appEntry.Metadata.ParamValues = appRequest.ParamValues
9696
appEntry.Metadata.ContainerOptions = appRequest.ContainerOptions
9797
appEntry.Metadata.ContainerArgs = appRequest.ContainerArgs
98+
appEntry.Metadata.ContainerVolumes = appRequest.ContainerVolumes
9899
appEntry.Metadata.AppConfig = appRequest.AppConfig
99100

100101
auditResult, err := s.createApp(ctx, &appEntry, approve, dryRun, appRequest.GitBranch, appRequest.GitCommit, appRequest.GitAuthName)

internal/server/app_updates.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,11 @@ func (s *Server) updateAppMetadataConfig(appEntry *types.AppEntry, configType ty
566566
return nil
567567
}
568568

569+
if configType == types.AppMetadataContainerVolumes {
570+
appEntry.Metadata.ContainerVolumes = configEntries
571+
return nil
572+
}
573+
569574
for _, entry := range configEntries {
570575
key, value, ok := strings.Cut(entry, "=")
571576

@@ -604,6 +609,7 @@ func (s *Server) updateAppMetadataConfig(appEntry *types.AppEntry, configType ty
604609
} else {
605610
delete(appEntry.Metadata.AppConfig, key)
606611
}
612+
// case AppMetadataContainerVolumes not expected here, already handled
607613
default:
608614
return fmt.Errorf("invalid config type %s", configType)
609615
}

internal/types/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type CreateAppRequest struct {
4040
ParamValues map[string]string `json:"param_values"`
4141
ContainerOptions map[string]string `json:"container_options"`
4242
ContainerArgs map[string]string `json:"container_args"`
43+
ContainerVolumes []string `json:"container_volumes"`
4344
AppConfig map[string]string `json:"appconfig"`
4445
}
4546

internal/types/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ type AppMetadata struct {
315315
SpecFiles *SpecFiles `json:"spec_files"`
316316
ContainerOptions map[string]string `json:"container_options"`
317317
ContainerArgs map[string]string `json:"container_args"`
318+
ContainerVolumes []string `json:"container_volumes"`
318319
AppConfig map[string]string `json:"appconfig"`
319320
}
320321

@@ -398,6 +399,7 @@ const (
398399
AppMetadataAppConfig AppMetadataConfigType = "app_config"
399400
AppMetadataContainerOptions AppMetadataConfigType = "container_options"
400401
AppMetadataContainerArgs AppMetadataConfigType = "container_args"
402+
AppMetadataContainerVolumes AppMetadataConfigType = "container_volumes"
401403
)
402404

403405
type AppVersion struct {

tests/test_containers.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ tests:
1010
container0030: # setup flask prod app
1111
command: ../clace app create --spec python-flask --carg PYTHON_VERSION=3.12.4-slim --copt cpu-shares=1000 --approve ./flaskapp /cont_flaskprod
1212
container0031: # invalid python version carg fails
13-
command: ../clace app create --spec python-flask --container-arg PYTHON_VERSION=4invalid-slim --approve ./flaskapp /cont_flaskdev2
13+
command: ../clace app create --spec python-flask --cvol /tmp:/atmp --cvol /testvol --container-arg PYTHON_VERSION=4invalid-slim --approve ./flaskapp /cont_flaskdev2
1414
container0032: # check curl
1515
command: curl -sS localhost:${HTTP_PORT}/cont_flaskdev2
1616
stdout: 4invalid

0 commit comments

Comments
 (0)