From 6037be925bd2bf6b187e024224606ffcdb7839e4 Mon Sep 17 00:00:00 2001 From: Ajay Kidave Date: Fri, 16 Aug 2024 14:37:43 -0700 Subject: [PATCH] Added support for container build args and run options --- cmd/clace/app_cmds.go | 47 +++++++++++++++++++++++++------ internal/app/container/command.go | 26 +++++++++++++++-- internal/app/container/manager.go | 10 ++++--- internal/app/setup.go | 6 ++++ internal/server/app_apis.go | 2 ++ internal/types/api.go | 18 ++++++------ internal/types/types.go | 16 ++++++----- 7 files changed, 95 insertions(+), 30 deletions(-) diff --git a/cmd/clace/app_cmds.go b/cmd/clace/app_cmds.go index 5dcec83..969496d 100644 --- a/cmd/clace/app_cmds.go +++ b/cmd/clace/app_cmds.go @@ -67,6 +67,18 @@ func appCreateCommand(commonFlags []cli.Flag, clientConfig *types.ClientConfig) Aliases: []string{"p"}, Usage: "Set a parameter value. Format is paramName=paramValue", }) + flags = append(flags, + &cli.StringSliceFlag{ + Name: "container-options", + Aliases: []string{"copt"}, + Usage: "Set a container option. Format is opt[=optValue]", + }) + flags = append(flags, + &cli.StringSliceFlag{ + Name: "container-args", + Aliases: []string{"carg"}, + Usage: "Set an argument for building the container image. Format is argKey=argValue", + }) flags = append(flags, dryRunFlag()) @@ -114,15 +126,34 @@ Examples: paramValues[key] = value } + containerOptions := cCtx.StringSlice("container-options") + coptMap := make(map[string]string) + for _, param := range containerOptions { + key, value, _ := strings.Cut(param, "=") + coptMap[key] = value // value can be empty string + } + + containerArgs := cCtx.StringSlice("container-args") + cargMap := make(map[string]string) + for _, param := range containerArgs { + key, value, ok := strings.Cut(param, "=") + if !ok { + return fmt.Errorf("invalid container arg format: %s", param) + } + cargMap[key] = value + } + body := types.CreateAppRequest{ - SourceUrl: cCtx.Args().Get(0), - IsDev: cCtx.Bool("dev"), - AppAuthn: types.AppAuthnType(cCtx.String("auth")), - GitBranch: cCtx.String("branch"), - GitCommit: cCtx.String("commit"), - GitAuthName: cCtx.String("git-auth"), - Spec: types.AppSpec(cCtx.String("spec")), - ParamValues: paramValues, + SourceUrl: cCtx.Args().Get(0), + IsDev: cCtx.Bool("dev"), + AppAuthn: types.AppAuthnType(cCtx.String("auth")), + GitBranch: cCtx.String("branch"), + GitCommit: cCtx.String("commit"), + GitAuthName: cCtx.String("git-auth"), + Spec: types.AppSpec(cCtx.String("spec")), + ParamValues: paramValues, + ContainerOptions: coptMap, + ContainerArgs: cargMap, } var createResult types.AppCreateResponse err := client.Post("/_clace/app", values, body, &createResult) diff --git a/internal/app/container/command.go b/internal/app/container/command.go index f16fd40..7b33745 100644 --- a/internal/app/container/command.go +++ b/internal/app/container/command.go @@ -75,9 +75,18 @@ func (c ContainerCommand) RemoveImage(config *types.SystemConfig, name ImageName return nil } -func (c ContainerCommand) BuildImage(config *types.SystemConfig, name ImageName, sourceUrl, containerFile string) error { +func (c ContainerCommand) BuildImage(config *types.SystemConfig, name ImageName, sourceUrl, containerFile string, containerArgs map[string]string) error { c.Debug().Msgf("Building image %s from %s with %s", name, containerFile, sourceUrl) - cmd := exec.Command(config.ContainerCommand, "build", "-t", string(name), "-f", containerFile, ".") + args := []string{config.ContainerCommand, "build", "-t", string(name), "-f", containerFile} + + for k, v := range containerArgs { + args = append(args, "--build-arg", fmt.Sprintf("%s=%s", k, v)) + } + + args = append(args, ".") + cmd := exec.Command(args[0], args[1:]...) + + c.Debug().Msgf("Running command: %s", cmd.String()) cmd.Dir = sourceUrl output, err := cmd.CombinedOutput() if err != nil { @@ -228,7 +237,8 @@ func (c ContainerCommand) StartContainer(config *types.SystemConfig, name Contai const LABEL_PREFIX = "io.clace." func (c ContainerCommand) RunContainer(config *types.SystemConfig, appEntry *types.AppEntry, containerName ContainerName, - imageName ImageName, port int64, envMap map[string]string, mountArgs []string) error { + imageName ImageName, port int64, envMap map[string]string, mountArgs []string, + containerOptions map[string]string) error { c.Debug().Msgf("Running container %s from image %s with port %d env %+v mountArgs %+v", containerName, imageName, port, envMap, mountArgs) publish := fmt.Sprintf("127.0.0.1::%d", port) @@ -248,10 +258,20 @@ func (c ContainerCommand) RunContainer(config *types.SystemConfig, appEntry *typ args = append(args, "--label", LABEL_PREFIX+"git.message="+appEntry.Metadata.VersionMetadata.GitMessage) } + // Add env args for k, v := range envMap { args = append(args, "--env", fmt.Sprintf("%s=%s", k, v)) } + // Add container related args + for k, v := range containerOptions { + if v == "" { + args = append(args, fmt.Sprintf("--%s", k)) + } else { + args = append(args, fmt.Sprintf("--%s=%s", k, v)) + } + } + args = append(args, string(imageName)) c.Debug().Msgf("Running container with args: %v", args) diff --git a/internal/app/container/manager.go b/internal/app/container/manager.go index 771fcfc..9c4e949 100644 --- a/internal/app/container/manager.go +++ b/internal/app/container/manager.go @@ -261,7 +261,7 @@ func (m *Manager) DevReload(dryRun bool) error { return err } buildDir := path.Join(m.appEntry.SourceUrl, m.buildDir) - err = m.command.BuildImage(m.systemConfig, imageName, buildDir, m.containerFile) + err = m.command.BuildImage(m.systemConfig, imageName, buildDir, m.containerFile, m.appEntry.Metadata.ContainerArgs) if err != nil { return err } @@ -277,7 +277,8 @@ func (m *Manager) DevReload(dryRun bool) error { } envMap, _ := m.GetEnvMap() - err = m.command.RunContainer(m.systemConfig, m.appEntry, containerName, imageName, m.port, envMap, m.getMountArgs()) + err = m.command.RunContainer(m.systemConfig, m.appEntry, containerName, + imageName, m.port, envMap, m.getMountArgs(), m.appEntry.Metadata.ContainerOptions) if err != nil { return fmt.Errorf("error building image: %w", err) } @@ -391,7 +392,7 @@ func (m *Manager) ProdReload(excludeGlob []string, dryRun bool) error { return fmt.Errorf("error creating temp source dir: %w", err) } buildDir := path.Join(tempDir, m.buildDir) - buildErr := m.command.BuildImage(m.systemConfig, imageName, buildDir, m.containerFile) + buildErr := m.command.BuildImage(m.systemConfig, imageName, buildDir, m.containerFile, m.appEntry.Metadata.ContainerArgs) // Cleanup temp dir after image has been built (even if build failed) if err = os.RemoveAll(tempDir); err != nil { @@ -410,7 +411,8 @@ func (m *Manager) ProdReload(excludeGlob []string, dryRun bool) error { } // Start the container with newly built image - err = m.command.RunContainer(m.systemConfig, m.appEntry, containerName, imageName, m.port, envMap, m.getMountArgs()) + err = m.command.RunContainer(m.systemConfig, m.appEntry, containerName, + imageName, m.port, envMap, m.getMountArgs(), m.appEntry.Metadata.ContainerOptions) if err != nil { return fmt.Errorf("error building image: %w", err) } diff --git a/internal/app/setup.go b/internal/app/setup.go index 8e8dbaa..2c5c39b 100644 --- a/internal/app/setup.go +++ b/internal/app/setup.go @@ -309,6 +309,12 @@ func (a *App) addParams(builtin starlark.StringDict) (starlark.StringDict, error } newBuiltins[apptype.PARAM_MODULE] = ¶mModule + + for k, v := range a.Metadata.ParamValues { + if _, ok := paramDict[k]; !ok { + a.paramMap[k] = v // add additional param values to paramMap + } + } return newBuiltins, nil } diff --git a/internal/server/app_apis.go b/internal/server/app_apis.go index 2907217..72c3603 100644 --- a/internal/server/app_apis.go +++ b/internal/server/app_apis.go @@ -92,6 +92,8 @@ func (s *Server) CreateApp(ctx context.Context, appPath string, approve, dryRun appEntry.Metadata.Spec = appRequest.Spec // validated in createApp appEntry.Metadata.ParamValues = appRequest.ParamValues + appEntry.Metadata.ContainerOptions = appRequest.ContainerOptions + appEntry.Metadata.ContainerArgs = appRequest.ContainerArgs auditResult, err := s.createApp(ctx, &appEntry, approve, dryRun, appRequest.GitBranch, appRequest.GitCommit, appRequest.GitAuthName) if err != nil { diff --git a/internal/types/api.go b/internal/types/api.go index 2ed23a1..39dc3c1 100644 --- a/internal/types/api.go +++ b/internal/types/api.go @@ -30,14 +30,16 @@ func (r RequestError) Error() string { // CreateAppRequest is the request body for creating an app type CreateAppRequest struct { - SourceUrl string `json:"source_url"` - IsDev bool `json:"is_dev"` - AppAuthn AppAuthnType `json:"app_authn"` - GitBranch string `json:"git_branch"` - GitCommit string `json:"git_commit"` - GitAuthName string `json:"git_auth_name"` - Spec AppSpec `json:"spec"` - ParamValues map[string]string `json:"param_values"` + SourceUrl string `json:"source_url"` + IsDev bool `json:"is_dev"` + AppAuthn AppAuthnType `json:"app_authn"` + GitBranch string `json:"git_branch"` + GitCommit string `json:"git_commit"` + GitAuthName string `json:"git_auth_name"` + Spec AppSpec `json:"spec"` + ParamValues map[string]string `json:"param_values"` + ContainerOptions map[string]string `json:"container_options"` + ContainerArgs map[string]string `json:"container_args"` } // UpdateAppRequest is the request body for updating an app settings diff --git a/internal/types/types.go b/internal/types/types.go index f1c79cc..ac336af 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -259,13 +259,15 @@ func (ae *AppEntry) AppPathDomain() AppPathDomain { // AppMetadata contains the configuration for an app. App configurations are version controlled. type AppMetadata struct { - VersionMetadata VersionMetadata `json:"version_metadata"` - Loads []string `json:"loads"` - Permissions []Permission `json:"permissions"` - Accounts []AccountLink `json:"accounts"` - ParamValues map[string]string `json:"param_values"` - Spec AppSpec `json:"spec"` - SpecFiles *SpecFiles `json:"spec_files"` + VersionMetadata VersionMetadata `json:"version_metadata"` + Loads []string `json:"loads"` + Permissions []Permission `json:"permissions"` + Accounts []AccountLink `json:"accounts"` + ParamValues map[string]string `json:"param_values"` + Spec AppSpec `json:"spec"` + SpecFiles *SpecFiles `json:"spec_files"` + ContainerOptions map[string]string `json:"container_options"` + ContainerArgs map[string]string `json:"container_args"` } // AppSettings contains the settings for an app. Settings are not version controlled.