diff --git a/docs/guide/command-reference.md b/docs/guide/command-reference.md index 75b1c36..f743725 100644 --- a/docs/guide/command-reference.md +++ b/docs/guide/command-reference.md @@ -12,9 +12,8 @@ covered in [global-flags.md](global-flags.md). > `queue`/`topic`/`stream rm`) **require `--yes`** when run under `--json` or > non-interactively. -> **Promotion note.** `shared expose` / `shared ports` are the current spelling; -> a branch is promoting them to top-level `expose` / `ports` (mid-refactor). Use -> the `shared`-prefixed forms today. +> **Naming note.** `expose` / `ports` exist at the top level (canonical) and as +> `shared expose` / `shared ports` (backward-compatible aliases) — same behavior. ## Lifecycle diff --git a/docs/guide/concepts.md b/docs/guide/concepts.md index f4de0fb..c840d56 100644 --- a/docs/guide/concepts.md +++ b/docs/guide/concepts.md @@ -87,8 +87,8 @@ devstack shared ports # show the published ports + connection stri ``` See [Shared services & host access](shared-services.md) for the details. (These -two verbs are currently spelled `shared expose` / `shared ports`; a branch is -promoting them to top-level `expose` / `ports`.) +verbs work as top-level `expose` / `ports` and as `shared expose` / `shared ports` +aliases.) ## Stateless CLI, no daemon diff --git a/docs/guide/shared-services.md b/docs/guide/shared-services.md index 53d3a46..9d330c7 100644 --- a/docs/guide/shared-services.md +++ b/docs/guide/shared-services.md @@ -132,11 +132,10 @@ Key properties: - **Opt-in per service** — with no arguments every exposable engine is published; name engines to narrow it. -> **Naming note.** The current, shipping spelling is `devstack shared expose` and -> `devstack shared ports` (documented here). These are being **promoted to -> top-level** commands — `devstack expose` and `devstack ports` — on a branch -> that is still mid-refactor. When that lands, the top-level forms become the -> canonical spelling; the `shared`-prefixed forms are what you use today. +> **Naming note.** These are available **both** as top-level `devstack expose` / +> `devstack ports` (the canonical spelling) **and** as `devstack shared expose` / +> `devstack shared ports` (backward-compatible aliases). Use whichever you prefer — +> they run the same logic. ## Seeing the ports: `shared ports` diff --git a/internal/cli/expose.go b/internal/cli/expose.go index 6a10d15..6ae89e6 100644 --- a/internal/cli/expose.go +++ b/internal/cli/expose.go @@ -9,12 +9,12 @@ import ( "github.com/open-source-cloud/devstack/internal/orchestrate" ) -// newSharedExposeCmd wires `shared expose [services...]` — publish the shared -// engines on stable 127.0.0.1 host ports so GUI clients (DataGrip, a Redis/S3 -// browser, the RabbitMQ UI) can connect. Opt-in and loopback-only; it never -// touches the deterministic generated compose (an up-time overlay). `--off` -// removes the publish and returns the stack to DNS-only. -func newSharedExposeCmd(g *GlobalOpts) *cobra.Command { +// newExposeCmd wires the top-level `expose [services...]` (also `shared expose`) — +// publish the shared engines on stable 127.0.0.1 host ports so GUI clients +// (DataGrip, a Redis/S3 browser, the RabbitMQ UI) can connect. Opt-in and +// loopback-only; it never touches the deterministic generated compose (an up-time +// overlay). `--off` removes the publish and returns the stack to DNS-only. +func newExposeCmd(g *GlobalOpts) *cobra.Command { var off bool cmd := &cobra.Command{ Use: "expose [services...]", @@ -53,9 +53,9 @@ func newSharedExposeCmd(g *GlobalOpts) *cobra.Command { return cmd } -// newSharedPortsCmd wires `shared ports` — the read-only projection of the +// newPortsCmd wires `ports` (and `shared ports`) — the read-only projection of the // currently-published host ports + connection strings (lock-free snapshot). -func newSharedPortsCmd(g *GlobalOpts) *cobra.Command { +func newPortsCmd(g *GlobalOpts) *cobra.Command { return &cobra.Command{ Use: "ports", Short: "Show the published 127.0.0.1 host ports for shared services (and connection strings)", diff --git a/internal/cli/expose_test.go b/internal/cli/expose_test.go index dc312a9..1f96331 100644 --- a/internal/cli/expose_test.go +++ b/internal/cli/expose_test.go @@ -1,76 +1,23 @@ package cli -import ( - "bytes" - "strings" - "testing" - - "github.com/spf13/cobra" - - "github.com/open-source-cloud/devstack/internal/orchestrate" -) - -func exposeFixture() []orchestrate.ExposedPort { - return []orchestrate.ExposedPort{ - {Instance: "postgres", Engine: "postgres", Alias: "shared-postgres", Label: "postgres", Host: "127.0.0.1", Port: 55432, Container: 5432, Primary: true, URL: "postgres://devstack:devstack@127.0.0.1:55432/postgres?sslmode=disable"}, - {Instance: "minio", Engine: "minio", Alias: "shared-minio", Label: "console", Host: "127.0.0.1", Port: 59001, Container: 9001, Primary: false, URL: "http://127.0.0.1:59001"}, - } -} - -func TestRenderExposed_Table(t *testing.T) { - var buf bytes.Buffer - cmd := &cobra.Command{} - cmd.SetOut(&buf) - if err := renderExposed(cmd, &GlobalOpts{}, exposeFixture()); err != nil { - t.Fatal(err) - } - out := buf.String() - for _, want := range []string{"shared-postgres", "55432", "shared-minio (console)", "59001", "per-project database:"} { - if !strings.Contains(out, want) { - t.Errorf("table missing %q:\n%s", want, out) +import "testing" + +// TestExposePortsRegistered asserts expose/ports are available both at the top +// level (the promotion) and under `shared` (backward-compatible aliases). +func TestExposePortsRegistered(t *testing.T) { + root := NewRootCmd(Options{}) + for _, path := range [][]string{ + {"expose"}, {"ports"}, + {"shared", "expose"}, {"shared", "ports"}, + } { + c, _, err := root.Find(path) + if err != nil || c.RunE == nil { + t.Errorf("%v not registered as a real command: %v", path, err) } } -} - -func TestRenderExposed_JSON(t *testing.T) { - var buf bytes.Buffer - cmd := &cobra.Command{} - cmd.SetOut(&buf) - if err := renderExposed(cmd, &GlobalOpts{JSON: true}, exposeFixture()); err != nil { - t.Fatal(err) - } - out := buf.String() - if !strings.Contains(out, "\"exposed\"") || !strings.Contains(out, "\"port\": 55432") { - t.Errorf("json missing fields:\n%s", out) - } -} - -func TestRenderExposed_Quiet(t *testing.T) { - var buf bytes.Buffer - cmd := &cobra.Command{} - cmd.SetOut(&buf) - if err := renderExposed(cmd, &GlobalOpts{Quiet: true}, exposeFixture()); err != nil { - t.Fatal(err) - } - out := strings.TrimSpace(buf.String()) - // Quiet emits only the connection URLs, one per line. - lines := strings.Split(out, "\n") - if len(lines) != 2 || !strings.HasPrefix(lines[0], "postgres://") { - t.Errorf("quiet should print only URLs, got:\n%s", out) - } -} - -// TestSharedExposeCommandsRegistered guards that `shared expose` and -// `shared ports` are wired into the shared command tree. -func TestSharedExposeCommandsRegistered(t *testing.T) { - sh := newSharedCmd(&GlobalOpts{}) - have := map[string]bool{} - for _, c := range sh.Commands() { - have[c.Name()] = true - } - for _, want := range []string{"expose", "ports", "status", "gc", "doctor"} { - if !have[want] { - t.Errorf("shared subcommand %q not registered", want) - } + // The top-level expose carries its --off flag. + c, _, _ := root.Find([]string{"expose"}) + if c.Flags().Lookup("off") == nil { + t.Error("expose missing --off flag") } } diff --git a/internal/cli/root.go b/internal/cli/root.go index 7cedee4..4be92dc 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -85,6 +85,8 @@ func NewRootCmd(opts Options) *cobra.Command { newStatusCmd(g), newUseCmd(g), newContextCmd(g), + newExposeCmd(g), + newPortsCmd(g), newShellInitCmd(g), newLogsCmd(g), newDashboardCmd(g), diff --git a/internal/cli/shared.go b/internal/cli/shared.go index 2dbef72..0e5f81e 100644 --- a/internal/cli/shared.go +++ b/internal/cli/shared.go @@ -27,8 +27,10 @@ func newSharedCmd(g *GlobalOpts) *cobra.Command { newSharedStatusCmd(g), newSharedGcCmd(g), newSharedDoctorCmd(g), - newSharedExposeCmd(g), - newSharedPortsCmd(g), + // `shared expose`/`shared ports` stay as aliases of the top-level `expose`/ + // `ports` (fresh instances; same logic) for backward compatibility. + newExposeCmd(g), + newPortsCmd(g), ) return cmd }