diff --git a/cmd/wfctl/infra.go b/cmd/wfctl/infra.go index ee92c98d..e4b70e5f 100644 --- a/cmd/wfctl/infra.go +++ b/cmd/wfctl/infra.go @@ -74,6 +74,8 @@ func runInfra(args []string) error { return runInfraImport(args[1:]) case "state": return runInfraState(args[1:]) + case "logs": + return runInfraLogs(args[1:]) case "bootstrap": return runInfraBootstrap(args[1:]) case "outputs": @@ -122,6 +124,7 @@ Actions: destroy Tear down infrastructure import Import an existing cloud resource into state state Manage IaC state (list, export, import) + logs Capture provider logs for an infrastructure resource outputs Print captured resource outputs from state refresh-outputs Read live outputs and reconcile state (no cloud writes) align Validate IaC config + plan alignment (8 rule families) diff --git a/cmd/wfctl/infra_env_integration_test.go b/cmd/wfctl/infra_env_integration_test.go index aebac2e6..91acb26d 100644 --- a/cmd/wfctl/infra_env_integration_test.go +++ b/cmd/wfctl/infra_env_integration_test.go @@ -80,6 +80,7 @@ func TestInfraUsageDocumentsConfigAwareImportFlags(t *testing.T) { "--env ", "--name ", "--id ", + "logs", } { if !strings.Contains(out, want) { t.Fatalf("infra usage missing %q:\n%s", want, out) diff --git a/cmd/wfctl/logs.go b/cmd/wfctl/logs.go index 8c72ecb2..164afdc5 100644 --- a/cmd/wfctl/logs.go +++ b/cmd/wfctl/logs.go @@ -29,6 +29,13 @@ func runLogsWithOutput(args []string, out io.Writer) error { } } +func runInfraLogs(args []string) error { + if len(args) > 0 && args[0] == "capture" { + args = args[1:] + } + return runLogsCapture(args, os.Stdout) +} + func logsUsage() error { fmt.Fprintf(flag.CommandLine.Output(), `Usage: wfctl logs [options] diff --git a/cmd/wfctl/logs_test.go b/cmd/wfctl/logs_test.go index aed0b240..d824a3f5 100644 --- a/cmd/wfctl/logs_test.go +++ b/cmd/wfctl/logs_test.go @@ -88,6 +88,53 @@ modules: } } +func TestInfraLogsAliasUsesProviderLogCapture(t *testing.T) { + tmp := t.TempDir() + cfg := filepath.Join(tmp, "app.yaml") + if err := os.WriteFile(cfg, []byte(` +version: "1" +modules: + - name: do + type: iac.provider + config: + provider: digitalocean + - name: web + type: infra.container_service + config: + provider: do + app_name: bmw-staging +`), 0o600); err != nil { + t.Fatal(err) + } + + provider := &fakeLogProvider{} + orig := resolveIaCProvider + resolveIaCProvider = func(_ context.Context, _ string, _ map[string]any) (interfaces.IaCProvider, io.Closer, error) { + return provider, nil, nil + } + t.Cleanup(func() { resolveIaCProvider = orig }) + + err := runInfra([]string{ + "logs", + "--config", cfg, + "--resource", "web", + "--component", "api", + "--tail", "7", + }) + if err != nil { + t.Fatalf("runInfra logs: %v", err) + } + if provider.req.ResourceName != "bmw-staging" { + t.Fatalf("ResourceName = %q, want bmw-staging", provider.req.ResourceName) + } + if provider.req.ComponentName != "api" { + t.Fatalf("ComponentName = %q, want api", provider.req.ComponentName) + } + if provider.req.TailLines != 7 { + t.Fatalf("TailLines = %d, want 7", provider.req.TailLines) + } +} + func TestLogsCaptureFollowSetsDuration(t *testing.T) { tmp := t.TempDir() cfg := filepath.Join(tmp, "app.yaml")