Add managed namespace wiki reset deploy
This commit is contained in:
+5
-1
@@ -1950,8 +1950,10 @@ func parseDeployWikiArgs(commandName string, args []string) (topdata.DeployWikiO
|
||||
opts.Force = true
|
||||
case "--create":
|
||||
opts.AllowCreates = true
|
||||
case "--reset-managed-namespaces":
|
||||
opts.ResetManagedNamespaces = true
|
||||
case "-h", "--help":
|
||||
return opts, fmt.Errorf("usage: %s [--source-dir <path>] [--endpoint <url>] [--token <token>] [--version <ver>] [--namespace <ns> ...] [--category <namespace=cid> ...] [--manifest <path>] [--stale-policy <report|archive|purge>] [--dry-run] [--create] [--force]", commandName)
|
||||
return opts, fmt.Errorf("usage: %s [--source-dir <path>] [--endpoint <url>] [--token <token>] [--version <ver>] [--namespace <ns> ...] [--category <namespace=cid> ...] [--manifest <path>] [--stale-policy <report|archive|purge>] [--dry-run] [--create] [--force] [--reset-managed-namespaces]", commandName)
|
||||
default:
|
||||
if value, ok, err := requireInlineFlagValue(arg, "--source-dir"); ok || err != nil {
|
||||
if err != nil {
|
||||
@@ -2014,6 +2016,8 @@ func parseDeployWikiArgs(commandName string, args []string) (topdata.DeployWikiO
|
||||
opts.Force = true
|
||||
} else if arg == "--create" {
|
||||
opts.AllowCreates = true
|
||||
} else if arg == "--reset-managed-namespaces" {
|
||||
opts.ResetManagedNamespaces = true
|
||||
} else {
|
||||
return opts, fmt.Errorf("unknown %s argument %q", commandName, arg)
|
||||
}
|
||||
|
||||
@@ -271,6 +271,16 @@ func TestParseDeployWikiHelpListsPurgeStalePolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDeployWikiResetManagedNamespacesFlag(t *testing.T) {
|
||||
opts, err := parseDeployWikiArgs("deploy-wiki", []string{"--reset-managed-namespaces"})
|
||||
if err != nil {
|
||||
t.Fatalf("parse deploy wiki reset flag: %v", err)
|
||||
}
|
||||
if !opts.ResetManagedNamespaces {
|
||||
t.Fatalf("expected --reset-managed-namespaces to enable namespace reset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectConsoleSuppressesBuildModuleProgressInNormalMode(t *testing.T) {
|
||||
var stdout bytes.Buffer
|
||||
console := &projectConsole{
|
||||
|
||||
@@ -33,21 +33,22 @@ const (
|
||||
)
|
||||
|
||||
type DeployWikiOptions struct {
|
||||
SourceDir string
|
||||
Endpoint string
|
||||
Token string
|
||||
Version string
|
||||
Namespaces []string
|
||||
CategoryIDs map[string]int
|
||||
ManifestPath string
|
||||
DryRun bool
|
||||
Force bool
|
||||
AllowCreates bool
|
||||
StalePolicy string
|
||||
Username string
|
||||
Password string
|
||||
NotesDelimiter string
|
||||
TitlePrefixMinLength int
|
||||
SourceDir string
|
||||
Endpoint string
|
||||
Token string
|
||||
Version string
|
||||
Namespaces []string
|
||||
CategoryIDs map[string]int
|
||||
ManifestPath string
|
||||
DryRun bool
|
||||
Force bool
|
||||
AllowCreates bool
|
||||
ResetManagedNamespaces bool
|
||||
StalePolicy string
|
||||
Username string
|
||||
Password string
|
||||
NotesDelimiter string
|
||||
TitlePrefixMinLength int
|
||||
}
|
||||
|
||||
type deployResult struct {
|
||||
@@ -456,6 +457,23 @@ func planNodeBBDeploy(pages map[string]wikiDeployPage, manifest wikiDeployManife
|
||||
remotePagesByCID := map[int][]nodeBBWikiPage{}
|
||||
var plans []wikiDeployPlan
|
||||
var result deployResult
|
||||
if opts.ResetManagedNamespaces {
|
||||
if !opts.AllowCreates && len(pageIDs) > 0 {
|
||||
return nil, result, next, errors.New("wiki managed namespace reset requires --create so current generated pages can be recreated")
|
||||
}
|
||||
resetPlans, purged, err := planManagedNamespaceReset(opts, client)
|
||||
if err != nil {
|
||||
return nil, result, next, err
|
||||
}
|
||||
plans = append(plans, resetPlans...)
|
||||
result.Purged += purged
|
||||
manifest = wikiDeployManifest{Version: "nodebb-v1", Pages: map[string]wikiDeployManifestPage{}}
|
||||
for _, cid := range opts.CategoryIDs {
|
||||
if cid != 0 {
|
||||
remotePagesByCID[cid] = []nodeBBWikiPage{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, pageID := range pageIDs {
|
||||
page := pages[pageID]
|
||||
@@ -615,6 +633,52 @@ func planNodeBBDeploy(pages map[string]wikiDeployPage, manifest wikiDeployManife
|
||||
return plans, result, next, nil
|
||||
}
|
||||
|
||||
func planManagedNamespaceReset(opts DeployWikiOptions, client *nodeBBClient) ([]wikiDeployPlan, int, error) {
|
||||
namespaces := slices.Clone(opts.Namespaces)
|
||||
if len(namespaces) == 0 {
|
||||
for namespace := range opts.CategoryIDs {
|
||||
namespaces = append(namespaces, namespace)
|
||||
}
|
||||
}
|
||||
slices.Sort(namespaces)
|
||||
|
||||
seenTIDs := map[int]struct{}{}
|
||||
var plans []wikiDeployPlan
|
||||
for _, namespace := range namespaces {
|
||||
cid := opts.CategoryIDs[namespace]
|
||||
if cid == 0 {
|
||||
continue
|
||||
}
|
||||
remotePages, err := client.listNamespacePages(cid)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
slices.SortFunc(remotePages, func(a, b nodeBBWikiPage) int {
|
||||
return a.TID - b.TID
|
||||
})
|
||||
for _, remotePage := range remotePages {
|
||||
if remotePage.TID == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seenTIDs[remotePage.TID]; ok {
|
||||
continue
|
||||
}
|
||||
seenTIDs[remotePage.TID] = struct{}{}
|
||||
title := strings.TrimSpace(remotePage.Title)
|
||||
if title == "" {
|
||||
title = strings.TrimSpace(remotePage.TitleLeaf)
|
||||
}
|
||||
pageID := fmt.Sprintf("%s:reset-topic-%d", namespace, remotePage.TID)
|
||||
plans = append(plans, wikiDeployPlan{
|
||||
Page: wikiDeployPage{PageID: pageID, Title: title, Namespace: namespace},
|
||||
Entry: wikiDeployManifestPage{TID: remotePage.TID, CID: cid, Namespace: namespace, Title: title},
|
||||
Action: "purge",
|
||||
})
|
||||
}
|
||||
}
|
||||
return plans, len(plans), nil
|
||||
}
|
||||
|
||||
func findMappedRemotePage(entry wikiDeployManifestPage, remotePagesByCID map[int][]nodeBBWikiPage, client *nodeBBClient) (nodeBBWikiPage, bool, error) {
|
||||
remotePages, ok := remotePagesByCID[entry.CID]
|
||||
if !ok {
|
||||
|
||||
@@ -1575,6 +1575,134 @@ func TestDeployWikiPurgesTrackedStaleTopicsBeforeCreatingReplacementPages(t *tes
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeployWikiResetManagedNamespacesPurgesRemotePagesBeforeCreatingFreshManifest(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
sourceDir := filepath.Join(root, "pages")
|
||||
if err := os.MkdirAll(filepath.Join(sourceDir, "skills"), 0755); err != nil {
|
||||
t.Fatalf("create source dir: %v", err)
|
||||
}
|
||||
generated := `<!-- sow-topdata-wiki:page=skills:replacement -->
|
||||
<!-- sow-topdata-wiki:managed:start hash="sha256:local" -->
|
||||
<h1>Replacement</h1>
|
||||
<p>Generated replacement page</p>
|
||||
<!-- sow-topdata-wiki:managed:end -->
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(sourceDir, "skills", "replacement.html"), []byte(generated), 0644); err != nil {
|
||||
t.Fatalf("write source page: %v", err)
|
||||
}
|
||||
manifestPath := filepath.Join(root, "deploy-manifest.json")
|
||||
if err := saveDeployManifest(manifestPath, wikiDeployManifest{
|
||||
Version: "nodebb-v1",
|
||||
Pages: map[string]wikiDeployManifestPage{
|
||||
"skills:stale": {Hash: "old-hash", Title: "Replacement", Namespace: "skills", TID: 7, PID: 42, CID: 3},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("write deploy manifest: %v", err)
|
||||
}
|
||||
|
||||
var calls []string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.Method == http.MethodGet && r.URL.Path == "/api/v3/plugins/westgate-wiki/namespace/3/pages":
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"response": map[string]any{
|
||||
"pages": []map[string]any{
|
||||
{"tid": 7, "title": "Old Replacement", "titleLeaf": "Old Replacement", "slug": "7/replacement", "wikiPath": "/wiki/skills/replacement"},
|
||||
{"tid": 8, "title": "Manual Note", "titleLeaf": "Manual Note", "slug": "8/manual-note", "wikiPath": "/wiki/skills/manual-note"},
|
||||
},
|
||||
"hasMore": false,
|
||||
},
|
||||
})
|
||||
case r.Method == http.MethodDelete && r.URL.Path == "/api/v3/topics/7":
|
||||
calls = append(calls, "purge:7")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"status": map[string]any{"code": "ok"}})
|
||||
case r.Method == http.MethodDelete && r.URL.Path == "/api/v3/topics/8":
|
||||
calls = append(calls, "purge:8")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"status": map[string]any{"code": "ok"}})
|
||||
case r.Method == http.MethodPost && r.URL.Path == "/api/v3/topics":
|
||||
calls = append(calls, "create")
|
||||
if strings.Join(calls, ",") != "purge:7,purge:8,create" {
|
||||
t.Fatalf("expected namespace reset purges before create, got %#v", calls)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"response": map[string]any{
|
||||
"tid": 11,
|
||||
"mainPost": map[string]any{
|
||||
"pid": 99,
|
||||
"tid": 11,
|
||||
},
|
||||
},
|
||||
})
|
||||
default:
|
||||
t.Fatalf("unexpected request %s %s", r.Method, r.URL.String())
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
result, err := DeployWikiWithOptions(&project.Project{}, DeployWikiOptions{
|
||||
SourceDir: sourceDir,
|
||||
Endpoint: server.URL,
|
||||
Token: "nodebb-token",
|
||||
ManifestPath: manifestPath,
|
||||
CategoryIDs: map[string]int{"skills": 3},
|
||||
AllowCreates: true,
|
||||
ResetManagedNamespaces: true,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("DeployWikiWithOptions reset-managed-namespaces failed: %v", err)
|
||||
}
|
||||
if result.Created != 1 || result.Purged != 2 || result.Stale != 0 {
|
||||
t.Fatalf("expected one create and two namespace purges, got %#v", result)
|
||||
}
|
||||
if strings.Join(calls, ",") != "purge:7,purge:8,create" {
|
||||
t.Fatalf("expected reset purges before create, got %#v", calls)
|
||||
}
|
||||
manifest := loadDeployManifest(manifestPath)
|
||||
if _, ok := manifest.Pages["skills:stale"]; ok {
|
||||
t.Fatalf("expected reset to remove previous manifest mapping")
|
||||
}
|
||||
entry := manifest.Pages["skills:replacement"]
|
||||
if entry.TID != 11 || entry.PID != 99 || entry.CID != 3 {
|
||||
t.Fatalf("expected fresh created mapping in manifest, got %#v", entry)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeployWikiResetManagedNamespacesRequiresCreateForLocalPages(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
sourceDir := filepath.Join(root, "pages")
|
||||
if err := os.MkdirAll(filepath.Join(sourceDir, "skills"), 0755); err != nil {
|
||||
t.Fatalf("create source dir: %v", err)
|
||||
}
|
||||
generated := `<!-- sow-topdata-wiki:page=skills:replacement -->
|
||||
<!-- sow-topdata-wiki:managed:start hash="sha256:local" -->
|
||||
<h1>Replacement</h1>
|
||||
<!-- sow-topdata-wiki:managed:end -->
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(sourceDir, "skills", "replacement.html"), []byte(generated), 0644); err != nil {
|
||||
t.Fatalf("write source page: %v", err)
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("reset without --create must fail before remote calls, got %s %s", r.Method, r.URL.String())
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
_, err := DeployWikiWithOptions(&project.Project{}, DeployWikiOptions{
|
||||
SourceDir: sourceDir,
|
||||
Endpoint: server.URL,
|
||||
Token: "nodebb-token",
|
||||
CategoryIDs: map[string]int{"skills": 3},
|
||||
ResetManagedNamespaces: true,
|
||||
}, nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "requires --create") {
|
||||
t.Fatalf("expected reset without create to fail clearly, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeployWikiPurgeTreatsAlreadyMissingTrackedTopicAsSuccess(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
sourceDir := filepath.Join(root, "pages")
|
||||
|
||||
Reference in New Issue
Block a user