Skip to content
Merged
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
25 changes: 24 additions & 1 deletion cmd/apps/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -717,9 +717,32 @@ func runCreate(ctx context.Context, opts createOptions) error {
// Always include mandatory plugins regardless of user selection or flags.
selectedPlugins = appendUnique(selectedPlugins, m.GetMandatoryPluginNames()...)

// In flags/non-interactive mode, validate that all required resources are provided.
// In flags/non-interactive mode, resolve derived values and validate resources.
if flagsMode || !isInteractive {
resources := m.CollectResources(selectedPlugins)

// Resolve derived values for resources that support it.
if resourceValues == nil {
resourceValues = make(map[string]string)
}
for _, r := range resources {
resolveFn, ok := prompt.GetResolveFunc(r.Type)
if !ok {
continue
}
resolved, err := resolveFn(ctx, r, resourceValues)
if err != nil {
log.Warnf(ctx, "Could not resolve derived values for %s: %v", r.Alias, err)
continue
}
for k, v := range resolved {
if resourceValues[k] == "" {
resourceValues[k] = v
}
}
}

// Validate that all required resources are provided.
for _, r := range resources {
found := false
for k := range resourceValues {
Expand Down
97 changes: 65 additions & 32 deletions libs/apps/prompt/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"maps"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -564,29 +565,6 @@ func PromptForPostgres(ctx context.Context, r manifest.Resource, required bool)
return nil, nil
}

// Step 2.5: resolve endpoint details from the branch (non-fatal).
var host, endpointPath string
endpointErr := RunWithSpinnerCtx(ctx, "Resolving connection details...", func() error {
endpoints, fetchErr := ListPostgresEndpoints(ctx, branchName)
if fetchErr != nil {
return fetchErr
}
for _, ep := range endpoints {
if ep.Status != nil && ep.Status.EndpointType == postgres.EndpointTypeEndpointTypeReadWrite {
endpointPath = ep.Name
if ep.Status.Hosts != nil && ep.Status.Hosts.Host != "" {
host = ep.Status.Hosts.Host
}
break
}
}
return nil
})
if endpointErr != nil {
log.Warnf(ctx, "Could not resolve endpoint details: %v", endpointErr)
// non-fatal: user can fill values manually
}

// Step 3: pick a database within the branch
var databases []ListItem
err = RunWithSpinnerCtx(ctx, "Fetching databases...", func() error {
Expand All @@ -605,22 +583,77 @@ func PromptForPostgres(ctx context.Context, r manifest.Resource, required bool)
return nil, nil
}

// Build resolver results map keyed by resolver name.
resolvedValues := map[string]string{
"postgres:host": host,
"postgres:databaseName": pgDatabaseName,
"postgres:endpointPath": endpointPath,
}

// Start with prompted values (fields without resolve).
result := map[string]string{
r.Key() + ".branch": branchName,
r.Key() + ".database": dbName,
}

// Map resolved values to fields using the manifest's resolve property.
applyResolvedValues(r, resolvedValues, result)
// Resolve derived values (host, databaseName, endpointPath) — non-fatal.
var resolved map[string]string
resolveErr := RunWithSpinnerCtx(ctx, "Resolving connection details...", func() error {
var err error
resolved, err = ResolvePostgresValues(ctx, r, branchName, dbName, pgDatabaseName)
return err
})
if resolveErr != nil {
log.Warnf(ctx, "Could not resolve connection details: %v", resolveErr)
}
maps.Copy(result, resolved)

return result, nil
}

// resolvePostgresResource adapts ResolvePostgresValues for the generic ResolveResourceFunc signature.
func resolvePostgresResource(ctx context.Context, r manifest.Resource, provided map[string]string) (map[string]string, error) {
branchName := provided[r.Key()+".branch"]
dbName := provided[r.Key()+".database"]
if branchName == "" || dbName == "" {
return nil, nil
}
return ResolvePostgresValues(ctx, r, branchName, dbName, "")
}

// ResolvePostgresValues resolves derived field values (host, databaseName, endpointPath)
// from a branch and database resource name. If pgDatabaseName is already known
// (e.g. from a prior prompt), pass it to skip the ListDatabases API call.
func ResolvePostgresValues(ctx context.Context, r manifest.Resource, branchName, dbName, pgDatabaseName string) (map[string]string, error) {
var host, endpointPath string
endpoints, err := ListPostgresEndpoints(ctx, branchName)
if err != nil {
return nil, fmt.Errorf("resolving endpoint details: %w", err)
}
for _, ep := range endpoints {
if ep.Status != nil && ep.Status.EndpointType == postgres.EndpointTypeEndpointTypeReadWrite {
endpointPath = ep.Name
if ep.Status.Hosts != nil && ep.Status.Hosts.Host != "" {
host = ep.Status.Hosts.Host
}
break
}
}

if pgDatabaseName == "" {
databases, err := ListPostgresDatabases(ctx, branchName)
if err != nil {
return nil, fmt.Errorf("resolving database name: %w", err)
}
for _, db := range databases {
if db.ID == dbName {
pgDatabaseName = db.Label
break
}
}
}

resolvedValues := map[string]string{
"postgres:host": host,
"postgres:databaseName": pgDatabaseName,
"postgres:endpointPath": endpointPath,
}

result := make(map[string]string)
applyResolvedValues(r, resolvedValues, result)
return result, nil
}

Expand Down
Loading
Loading