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
2 changes: 1 addition & 1 deletion .github/command-registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -18946,7 +18946,7 @@
"shorthand": null,
"type": "bool",
"default": "false",
"usage": "Delete plugin data (schema/tables). A bundle remove asks for confirmation (or pass --yes); a plugin remove never asks and drops data unless --keep-data",
"usage": "Delete plugin data (schema/tables). v1.5 keeps data unless --delete-data and asks for confirmation (or pass --yes); v1.4 plugin remove never asks and drops data unless --keep-data. Bundle data deletion asks for confirmation (or pass --yes) in both modes",
"hidden": false,
"deprecated": null,
"required": false,
Expand Down
4 changes: 2 additions & 2 deletions .github/facts.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
},
"schema_version": "1",
"sources": {
"openapi_sha256": "915c754aea9a69aba703d6c29d35d0e16ef0f81290a85f41581200a57ad7a7b1",
"registry_sha256": "05a06d867e0e8a0c3d5a6ccd201ca60cca3530fde14e67e1d046039153d15a90"
"openapi_sha256": "01ea16e2ac6cebd687983d758b80a9f1ddffe3b6a3a85bda4fc70ce077ca71b6",
"registry_sha256": "37fe98fe4dfa26f0c8af15f5cc50e9a5b81f499976850ae2b6066c536e8fe6c9"
},
"verbs": [
"init",
Expand Down
2 changes: 1 addition & 1 deletion .github/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -47459,7 +47459,7 @@
"properties": {
"delete-data": {
"default": false,
"description": "Delete plugin data (schema/tables). A bundle remove asks for confirmation (or pass --yes); a plugin remove never asks and drops data unless --keep-data",
"description": "Delete plugin data (schema/tables). v1.5 keeps data unless --delete-data and asks for confirmation (or pass --yes); v1.4 plugin remove never asks and drops data unless --keep-data. Bundle data deletion asks for confirmation (or pass --yes) in both modes",
"type": "boolean"
},
"dry-run": {
Expand Down
3 changes: 3 additions & 0 deletions .github/wiki/cmd-deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,9 @@ set with the same pull, per-service `up`, health gate, `nginx -t`, nginx reload
apply. The database restore returns the data to the moment of the backup: writes made since then,
while the new containers ran, are lost.

Rollback resolves SSH keys and server names only from the target environment's inventory;
the remote path comes from the recorded tar.

The database is restored at most once per record: `db_restored_at` is saved right after
`nself backup restore` succeeds, before the re-apply, so a retry of a rollback that failed later
(on any host) skips it and redoes only files and services. The `backup_host` is rolled back first. If it
Expand Down
2 changes: 1 addition & 1 deletion .github/wiki/cmd-remove.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ name removes the whole bundle.
<!-- GENERATED BY tools/wikigen — DO NOT HAND EDIT -->
| Name | Shorthand | Type | Default | Env | Escalates to | JSON override | Description |
|---|---|---|---|---|---|---|---|
| `--delete-data` | | `bool` | `false` | | | | Delete plugin data (schema/tables). A bundle remove asks for confirmation (or pass --yes); a plugin remove never asks and drops data unless --keep-data |
| `--delete-data` | | `bool` | `false` | | | | Delete plugin data (schema/tables). v1.5 keeps data unless --delete-data and asks for confirmation (or pass --yes); v1.4 plugin remove never asks and drops data unless --keep-data. Bundle data deletion asks for confirmation (or pass --yes) in both modes |
| `--dry-run` | | `bool` | `false` | | | | Print the planned actions without removing |
| `--force` | | `bool` | `false` | | | | Remove even if other plugins depend on this one |
| `--keep-data` | | `bool` | `false` | | | | Preserve database data on remove |
Expand Down
2 changes: 1 addition & 1 deletion .github/wiki/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ nself remove <name> [flags]

Flags:

- `--delete-data` (default false) — Delete plugin data (schema/tables). A bundle remove asks for confirmation (or pass --yes); a plugin remove never asks and drops data unless --keep-data
- `--delete-data` (default false) — Delete plugin data (schema/tables). v1.5 keeps data unless --delete-data and asks for confirmation (or pass --yes); v1.4 plugin remove never asks and drops data unless --keep-data. Bundle data deletion asks for confirmation (or pass --yes) in both modes
- `--dry-run` (default false) — Print the planned actions without removing
- `--force` (default false) — Remove even if other plugins depend on this one
- `--keep-data` (default false) — Preserve database data on remove
Expand Down
2 changes: 1 addition & 1 deletion cmd/commands/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func init() {

// remove flags
bundleRemoveCmd.Flags().Bool("dry-run", false, "Print the planned actions without removing")
bundleRemoveCmd.Flags().Bool("delete-data", false, "Delete plugin data (schema/tables). A bundle remove asks for confirmation (or pass --yes); a plugin remove never asks and drops data unless --keep-data")
bundleRemoveCmd.Flags().Bool("delete-data", false, removeDeleteDataHelp)
bundleRemoveCmd.Flags().Bool("keep-data", false, "Deprecated: no-op from v1.5 (data is kept by default); removed in v1.6.0")
_ = bundleRemoveCmd.Flags().MarkHidden("keep-data")

Expand Down
12 changes: 5 additions & 7 deletions cmd/commands/deploy_lifecycle_rollback.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,11 @@ func rollbackRemoteV15(cmd *cobra.Command, workdir, target string) (bool, error)
idx := strings.Index(tar, "/"+deploy.BackupDir+"/pre-deploy-")
srv, key := controlplane.Server{Name: target + "-app", Host: host, RemotePath: tar[:idx]}, sshKeyPath()
if inv != nil {
for _, e := range inv.Environments {
for _, c := range e.Servers {
if spec, err := remote.ParseHostSpec(c.Host); err == nil && spec.String() == host {
srv.Name, srv.Host = c.Name, c.Host
if v := os.Getenv(c.SSHKeyRef); v != "" {
key = v
}
for _, c := range inv.Environments[target].Servers {
if spec, err := remote.ParseHostSpec(c.Host); err == nil && spec.String() == host {
srv.Name, srv.Host = c.Name, c.Host
if v := os.Getenv(c.SSHKeyRef); v != "" {
key = v
}
}
}
Expand Down
54 changes: 54 additions & 0 deletions cmd/commands/deploy_rollback_inventory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package commands

import (
"context"
"errors"
"testing"

"github.com/nself-org/cli/internal/controlplane"
"github.com/nself-org/cli/internal/deploy"
)

func TestRollbackInventoryUsesOnlyTargetEnvironment(t *testing.T) {
for _, targetPresent := range []bool{true, false} {
name := "target present"
if !targetPresent {
name = "target absent"
}
t.Run(name, func(t *testing.T) {
proj, _ := v15Project(t)
const host = "deploy@app.example.test"
t.Setenv("NSELF_TEST_PROD_KEY", "/keys/prod")
t.Setenv("NSELF_TEST_OTHER_KEY", "/keys/other")
t.Setenv("NSELF_DEPLOY_SSH_KEY", "/keys/fallback")
envs := map[string]controlplane.Environment{
"other": {Name: "other", Kind: "remote", Servers: []controlplane.Server{{
Name: "other-app", Role: controlplane.RoleApp, Host: host,
SSHKeyRef: "NSELF_TEST_OTHER_KEY", RemotePath: "/other", Primary: true,
}}},
}
wantName, wantKey := "prod-app", "/keys/fallback"
if targetPresent {
envs["prod"] = controlplane.Environment{Name: "prod", Kind: "remote", Servers: []controlplane.Server{{
Name: "target-app", Role: controlplane.RoleApp, Host: host,
SSHKeyRef: "NSELF_TEST_PROD_KEY", RemotePath: "/inventory-path", Primary: true,
}}}
wantName, wantKey = "target-app", "/keys/prod"
}
writeInventory(t, proj, &controlplane.Inventory{SchemaVersion: 2, Environments: envs})
rollbackRecord(t, proj, host, host)
stop := errors.New("test stops before remote operations")
opened := false
deployRemoteOpen = func(_ context.Context, srv controlplane.Server, key, env string) (deploy.Remote, error) {
opened = true
if srv.Name != wantName || key != wantKey || env != "prod" || srv.Host != host || srv.RemotePath != "/opt/nself" {
t.Errorf("rollback opened server=%+v key=%q env=%q; want name=%q key=%q and recorded path", srv, key, env, wantName, wantKey)
}
return nil, stop
}
if handled, err := rollbackRemoteV15(yesCmd(), proj, "prod"); !handled || err == nil || !opened {
t.Fatalf("rollback handled=%v err=%v opened=%v", handled, err, opened)
}
})
}
}
4 changes: 3 additions & 1 deletion cmd/commands/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import (
// Constraints: `install` and `remove` must stay thin. Anything that belongs to
// installation belongs in internal/plugin or internal/bundle, not here.

const removeDeleteDataHelp = "Delete plugin data (schema/tables). v1.5 keeps data unless --delete-data and asks for confirmation (or pass --yes); v1.4 plugin remove never asks and drops data unless --keep-data. Bundle data deletion asks for confirmation (or pass --yes) in both modes"

var installCmd = &cobra.Command{
Use: "install <name> [name...]",
Short: "Install a plugin or bundle",
Expand Down Expand Up @@ -92,7 +94,7 @@ func init() {

removeCmd.Flags().Bool("yes", false, "Skip confirmation prompts")
removeCmd.Flags().Bool("keep-data", false, "Preserve database data on remove")
removeCmd.Flags().Bool("delete-data", false, "Delete plugin data (schema/tables). A bundle remove asks for confirmation (or pass --yes); a plugin remove never asks and drops data unless --keep-data")
removeCmd.Flags().Bool("delete-data", false, removeDeleteDataHelp)
removeCmd.Flags().Bool("force", false, "Remove even if other plugins depend on this one")
removeCmd.Flags().Bool("dry-run", false, "Print the planned actions without removing")

Expand Down
18 changes: 18 additions & 0 deletions cmd/commands/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ import (
"github.com/nself-org/cli/internal/plugin"
)

// TestRemoveDeleteDataHelp describes both compatibility modes so operators do
// not mistake v1.4's implicit data deletion for the v1.5 removal policy.
func TestRemoveDeleteDataHelp(t *testing.T) {
flag := removeCmd.Flags().Lookup("delete-data")
if flag == nil {
t.Fatal("remove is missing --delete-data")
}
for _, policy := range []string{
"v1.5 keeps data unless --delete-data",
"asks for confirmation (or pass --yes)",
"v1.4 plugin remove never asks and drops data unless --keep-data",
} {
if !strings.Contains(flag.Usage, policy) {
t.Errorf("--delete-data help missing %q: %s", policy, flag.Usage)
}
}
}

// TestInstallResolvesBundlesBeforePlugins pins the resolution order CLI-R19
// depends on. A bundle and a plugin can share a name; `nself install nchat`
// must install the bundle, not a single plugin that happens to match.
Expand Down
2 changes: 1 addition & 1 deletion internal/mcpgen/testdata/tools.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -25802,7 +25802,7 @@
"properties": {
"delete-data": {
"default": false,
"description": "Delete plugin data (schema/tables). A bundle remove asks for confirmation (or pass --yes); a plugin remove never asks and drops data unless --keep-data",
"description": "Delete plugin data (schema/tables). v1.5 keeps data unless --delete-data and asks for confirmation (or pass --yes); v1.4 plugin remove never asks and drops data unless --keep-data. Bundle data deletion asks for confirmation (or pass --yes) in both modes",
"type": "boolean"
},
"dry-run": {
Expand Down
2 changes: 1 addition & 1 deletion sdk/ts-cli-api/src/generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6335,7 +6335,7 @@ export interface RemoveData {
}

export type RemoveFlags = {
/** Delete plugin data (schema/tables). A bundle remove asks for confirmation (or pass --yes); a plugin remove never asks and drops data unless --keep-data */
/** Delete plugin data (schema/tables). v1.5 keeps data unless --delete-data and asks for confirmation (or pass --yes); v1.4 plugin remove never asks and drops data unless --keep-data. Bundle data deletion asks for confirmation (or pass --yes) in both modes */
"delete-data"?: boolean;
/** Print the planned actions without removing */
"dry-run"?: boolean;
Expand Down
Loading