From 4a5aec9853cd8dd0bf9b69e4f25a51393364bb43 Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:39:26 -0700 Subject: [PATCH] feat(add): preview well-known services with dry run Closes #486 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/src/cmd/app/commands/add.go | 47 ++++++++++++++++++++++ cli/src/cmd/app/commands/add_test.go | 59 ++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/cli/src/cmd/app/commands/add.go b/cli/src/cmd/app/commands/add.go index c1ed63d99..d73fa21c9 100644 --- a/cli/src/cmd/app/commands/add.go +++ b/cli/src/cmd/app/commands/add.go @@ -54,6 +54,7 @@ Examples: cmd.Flags().Bool("list", false, "List all available services") cmd.Flags().Bool("show-connection", false, "Show connection string after adding") + cmd.Flags().Bool("dry-run", false, "Show the azure.yaml service block without modifying the file") return cmd } @@ -63,12 +64,14 @@ type AddResult struct { Service string `json:"service"` Added bool `json:"added"` Message string `json:"message,omitempty"` + Preview string `json:"preview,omitempty"` ConnectionStrings map[string]string `json:"connectionStrings,omitempty"` } func runAdd(cmd *cobra.Command, args []string) error { listServices, _ := cmd.Flags().GetBool("list") showConnection, _ := cmd.Flags().GetBool("show-connection") + dryRun, _ := cmd.Flags().GetBool("dry-run") // Handle --list flag if listServices { @@ -113,6 +116,28 @@ func runAdd(cmd *cobra.Command, args []string) error { return nil } + if dryRun { + preview, previewErr := buildServicePreview(serviceName, def) + if previewErr != nil { + return fmt.Errorf("failed to build service preview: %w", previewErr) + } + if cliout.IsJSON() { + return cliout.PrintJSON(AddResult{ + Service: serviceName, + Added: false, + Message: fmt.Sprintf("Would add %s to azure.yaml", def.DisplayName), + Preview: preview, + }) + } + cliout.Info("Dry run: would add %s to azure.yaml", def.DisplayName) + cliout.Newline() + fmt.Print(preview) + if !strings.HasSuffix(preview, "\n") { + cliout.Newline() + } + return nil + } + // Add the service to azure.yaml if err := addServiceToYaml(azureYamlPath, serviceName, def); err != nil { return fmt.Errorf("failed to add service: %w", err) @@ -386,3 +411,25 @@ func buildServiceNode(def *wellknown.ServiceDefinition) *yaml.Node { return node } + +func buildServicePreview(serviceName string, def *wellknown.ServiceDefinition) (string, error) { + servicesNode := &yaml.Node{ + Kind: yaml.MappingNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: serviceName, Tag: "!!str"}, + buildServiceNode(def), + }, + } + root := &yaml.Node{ + Kind: yaml.MappingNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: "services", Tag: "!!str"}, + servicesNode, + }, + } + out, err := yaml.Marshal(root) + if err != nil { + return "", err + } + return string(out), nil +} diff --git a/cli/src/cmd/app/commands/add_test.go b/cli/src/cmd/app/commands/add_test.go index 5de0c9ebe..91892b95e 100644 --- a/cli/src/cmd/app/commands/add_test.go +++ b/cli/src/cmd/app/commands/add_test.go @@ -212,3 +212,62 @@ func TestBuildServiceNode(t *testing.T) { t.Error("buildServiceNode() did not include environment (postgres has env vars)") } } + +func TestBuildServicePreview(t *testing.T) { + def := wellknown.Get("redis") + if def == nil { + t.Fatal("redis service not found in wellknown registry") + } + + preview, err := buildServicePreview("redis", def) + if err != nil { + t.Fatalf("buildServicePreview() error: %v", err) + } + + for _, want := range []string{"services:", "redis:", "image: redis:7-alpine", "6379:6379"} { + if !strings.Contains(preview, want) { + t.Errorf("preview missing %q:\n%s", want, preview) + } + } +} + +func TestRunAddDryRunDoesNotModifyAzureYaml(t *testing.T) { + tempDir := t.TempDir() + content := `name: test-app +services: + api: + language: python + project: ./api +` + azureYamlPath := filepath.Join(tempDir, "azure.yaml") + if err := os.WriteFile(azureYamlPath, []byte(content), 0o644); err != nil { + t.Fatalf("failed to create azure.yaml: %v", err) + } + + originalWD, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get cwd: %v", err) + } + t.Cleanup(func() { + if chdirErr := os.Chdir(originalWD); chdirErr != nil { + t.Fatalf("failed to restore cwd: %v", chdirErr) + } + }) + if err := os.Chdir(tempDir); err != nil { + t.Fatalf("failed to chdir: %v", err) + } + + cmd := NewAddCommand() + cmd.SetArgs([]string{"redis", "--dry-run"}) + if err := cmd.Execute(); err != nil { + t.Fatalf("add --dry-run failed: %v", err) + } + + after, err := os.ReadFile(azureYamlPath) + if err != nil { + t.Fatalf("failed to read azure.yaml: %v", err) + } + if string(after) != content { + t.Fatalf("dry-run modified azure.yaml:\n%s", string(after)) + } +}