Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 59 additions & 16 deletions src/cmd/cli/command/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,17 @@ func makeComposeUpCmd() *cobra.Command {

deploy, project, err := cli.ComposeUp(ctx, project, client, provider, upload, mode.Value())
if err != nil {
return handleComposeUpErr(ctx, err, project, provider)
retry, err2 := handleComposeUpErr(ctx, err, project, provider)
if err2 != nil {
return err2
}

if retry {
deploy, project, err = cli.ComposeUp(ctx, project, client, provider, upload, mode.Value())
if err != nil {
return err
}
}
}

if len(deploy.Services) == 0 {
Expand Down Expand Up @@ -218,34 +228,67 @@ func makeComposeUpCmd() *cobra.Command {
return composeUpCmd
}

func handleComposeUpErr(ctx context.Context, err error, project *compose.Project, provider cliClient.Provider) error {
func handleComposeUpErr(ctx context.Context, err error, project *compose.Project, provider cliClient.Provider) (bool, error) {
if errors.Is(err, types.ErrComposeFileNotFound) {
// TODO: generate a compose file based on the current project
printDefangHint("To start a new project, do:", "new")
return false, err
}

term.Error("Error:", cliClient.PrettyError(err))
if nonInteractive {
return err
return false, err
}

if strings.Contains(err.Error(), "maximum number of projects") {
if projectName, err2 := provider.RemoteProjectName(ctx); err2 == nil {
term.Error("Error:", cliClient.PrettyError(err))
if _, err := cli.InteractiveComposeDown(ctx, provider, projectName); err != nil {
term.Debug("ComposeDown failed:", err)
printDefangHint("To deactivate a project, do:", "compose down --project-name "+projectName)
} else {
// TODO: actually do the "compose up" (because that's what the user intended in the first place)
printDefangHint("To try deployment again, do:", "compose up")
}
return nil
projectName, err2 := provider.RemoteProjectName(ctx)
if err2 != nil {
term.Warn("Failed to fetch remote project name for interactive down", err2)
return false, err2
}
if _, err2 := cli.InteractiveComposeDown(ctx, provider, projectName); err2 != nil {
term.Debug("ComposeDown failed:", err2)
printDefangHint("To deactivate a project, do:", "compose down --project-name "+projectName)
return false, err2
}
return true, nil
}

var missingConfigErr *compose.ErrMissingConfig
if errors.As(err, &missingConfigErr) {
// printDefangHint("To fix the missing config, do:", "defang config set <name> <value>")
missingNames := missingConfigErr.Names()
term.Warn("missing config names: ", missingNames)
err2 := InteractiveConfigSet(ctx, project, provider, missingNames)
if err2 != nil {
term.Warn("Failed to interactively set missing config", err2)
return true, err2
}
return err
}

term.Error("Error:", cliClient.PrettyError(err))
track.Evt("Debug Prompted", P("composeErr", err))
return cli.InteractiveDebugForClientError(ctx, client, project, err)
return false, cli.InteractiveDebugForClientError(ctx, client, project, err)
}

func InteractiveConfigSet(ctx context.Context, project *compose.Project, provider cliClient.Provider, missingNames []string) error {
for _, name := range missingNames {
var value string
message := fmt.Sprintf("Enter value for %q: ", name)
prompt := &survey.Input{
Message: message,
Default: os.Getenv(name),
}
err := survey.AskOne(prompt, &value)
if err != nil {
return err
}
err = cli.ConfigSet(ctx, project.Name, provider, name, value)
if err != nil {
return err
}
}

return nil
}

func handleTailAndMonitorErr(ctx context.Context, err error, client *cliClient.GrpcClient, debugConfig cli.DebugConfig) {
Expand Down
4 changes: 4 additions & 0 deletions src/pkg/cli/compose/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ type ListConfigNamesFunc func(context.Context) ([]string, error)

type ErrMissingConfig []string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cleaner way to do this is to make this a struct:

type ErrMissingConfig struct {
	Names []string
}


func (e ErrMissingConfig) Names() []string {
return ([]string)(e)
}

func (e ErrMissingConfig) Error() string {
return fmt.Sprintf("missing configs %q (https://docs.defang.io/docs/concepts/configuration)", ([]string)(e))
}
Expand Down
4 changes: 3 additions & 1 deletion src/pkg/mcp/tools/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tools
import (
"crypto/rand"
"encoding/base64"
"errors"
"regexp"
"strings"

Expand Down Expand Up @@ -53,7 +54,8 @@ func HandleTermsOfServiceError(err error) *mcp.CallToolResult {
}

func HandleConfigError(err error) *mcp.CallToolResult {
if strings.Contains(err.Error(), "missing configs") {
var missingConfigErr *compose.ErrMissingConfig
if errors.As(err, &missingConfigErr) {
mcpResult := mcp.NewToolResultErrorFromErr("The operation failed due to missing configs not being set. Please use the Defang tool called set_config to set the variable.", err)
term.Debugf("MCP output error: %v", mcpResult)
return mcpResult
Expand Down
Loading