From 98bee14385787b6927629fc36f25eb0874a67206 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Sun, 1 Nov 2026 15:17:25 +1100 Subject: [PATCH 1/3] Preserve repo_type on remote update --- internal/api/v2/remotes.go | 8 +++++ internal/api/v2/remotes_ttl_test.go | 39 +++++++++++++++++++++ migrations/0011_remote_repo_type_repair.sql | 1 + 3 files changed, 48 insertions(+) create mode 100644 migrations/0011_remote_repo_type_repair.sql diff --git a/internal/api/v2/remotes.go b/internal/api/v2/remotes.go index 838532d..8f378ab 100644 --- a/internal/api/v2/remotes.go +++ b/internal/api/v2/remotes.go @@ -135,12 +135,20 @@ func (h *RemotesHandler) update(w http.ResponseWriter, r *http.Request) { if oldKnown { remote.MutableTTL = existing.MutableTTL remote.QuarantineDays = existing.QuarantineDays + remote.RepoType = existing.RepoType } if err := json.NewDecoder(r.Body).Decode(&remote); err != nil { http.Error(w, "invalid json", http.StatusBadRequest) return } remote.Name = name + if remote.RepoType == "" { + remote.RepoType = models.RepoTypeRemote + } + if !remote.RepoType.Valid() { + http.Error(w, fmt.Sprintf("invalid repo type: %q", remote.RepoType), http.StatusBadRequest) + return + } if err := remote.ValidateMirrorlist(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return diff --git a/internal/api/v2/remotes_ttl_test.go b/internal/api/v2/remotes_ttl_test.go index 47a990b..0ce8f44 100644 --- a/internal/api/v2/remotes_ttl_test.go +++ b/internal/api/v2/remotes_ttl_test.go @@ -172,3 +172,42 @@ func TestQuarantineDaysValidation(t *testing.T) { } } } + +func TestRepoTypeKeptOnUpdate(t *testing.T) { + if testDSN == "" { + t.Skip("Docker unavailable") + } + db, err := database.New(testDSN) + if err != nil { + t.Fatal(err) + } + defer db.Close() + h := NewRemotesHandler(db, nil, nil).Routes() + repoType := func(name string) string { + t.Helper() + r, err := db.GetRemote(context.Background(), name) + if err != nil { + t.Fatalf("get %s: %v", name, err) + } + return string(r.RepoType) + } + const body = `"package_type":"rpm","base_url":"https://example.com/repo"` + for i, s := range []struct { + method, path, json string + code int + name, want string + }{ + {"POST", "/", `{"name":"rt-remote",` + body + `}`, 201, "rt-remote", "remote"}, + {"PUT", "/rt-remote", `{` + body + `}`, 200, "rt-remote", "remote"}, + {"POST", "/", `{"name":"rt-local","package_type":"rpm","repo_type":"local"}`, 201, "rt-local", "local"}, + {"PUT", "/rt-local", `{"package_type":"rpm","description":"x"}`, 200, "rt-local", "local"}, + {"PUT", "/rt-local", `{"package_type":"rpm","repo_type":"bogus"}`, 400, "rt-local", "local"}, + } { + if c := do(t, h, s.method, s.path, s.json); c != s.code { + t.Fatalf("step %d %s %s = %d, want %d", i, s.method, s.path, c, s.code) + } + if got := repoType(s.name); got != s.want { + t.Errorf("step %d: repo_type = %q, want %q", i, got, s.want) + } + } +} diff --git a/migrations/0011_remote_repo_type_repair.sql b/migrations/0011_remote_repo_type_repair.sql new file mode 100644 index 0000000..164e44b --- /dev/null +++ b/migrations/0011_remote_repo_type_repair.sql @@ -0,0 +1 @@ +UPDATE remotes SET repo_type = 'remote' WHERE repo_type = '' OR repo_type IS NULL; -- 2.47.3 From f28fb217054e58ca935b632d2f7f61a21143a968 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Sun, 1 Nov 2026 15:20:58 +1100 Subject: [PATCH 2/3] keep existing repo_type on explicit empty PUT; repair wiped locals as local --- internal/api/v2/remotes.go | 4 +++- internal/api/v2/remotes_ttl_test.go | 1 + migrations/0011_remote_repo_type_repair.sql | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/api/v2/remotes.go b/internal/api/v2/remotes.go index 8f378ab..42c92e0 100644 --- a/internal/api/v2/remotes.go +++ b/internal/api/v2/remotes.go @@ -135,13 +135,15 @@ func (h *RemotesHandler) update(w http.ResponseWriter, r *http.Request) { if oldKnown { remote.MutableTTL = existing.MutableTTL remote.QuarantineDays = existing.QuarantineDays - remote.RepoType = existing.RepoType } if err := json.NewDecoder(r.Body).Decode(&remote); err != nil { http.Error(w, "invalid json", http.StatusBadRequest) return } remote.Name = name + if remote.RepoType == "" && oldKnown { + remote.RepoType = existing.RepoType + } if remote.RepoType == "" { remote.RepoType = models.RepoTypeRemote } diff --git a/internal/api/v2/remotes_ttl_test.go b/internal/api/v2/remotes_ttl_test.go index 0ce8f44..ecaa824 100644 --- a/internal/api/v2/remotes_ttl_test.go +++ b/internal/api/v2/remotes_ttl_test.go @@ -201,6 +201,7 @@ func TestRepoTypeKeptOnUpdate(t *testing.T) { {"PUT", "/rt-remote", `{` + body + `}`, 200, "rt-remote", "remote"}, {"POST", "/", `{"name":"rt-local","package_type":"rpm","repo_type":"local"}`, 201, "rt-local", "local"}, {"PUT", "/rt-local", `{"package_type":"rpm","description":"x"}`, 200, "rt-local", "local"}, + {"PUT", "/rt-local", `{"package_type":"rpm","repo_type":""}`, 200, "rt-local", "local"}, {"PUT", "/rt-local", `{"package_type":"rpm","repo_type":"bogus"}`, 400, "rt-local", "local"}, } { if c := do(t, h, s.method, s.path, s.json); c != s.code { diff --git a/migrations/0011_remote_repo_type_repair.sql b/migrations/0011_remote_repo_type_repair.sql index 164e44b..9c4d7b2 100644 --- a/migrations/0011_remote_repo_type_repair.sql +++ b/migrations/0011_remote_repo_type_repair.sql @@ -1 +1 @@ -UPDATE remotes SET repo_type = 'remote' WHERE repo_type = '' OR repo_type IS NULL; +UPDATE remotes SET repo_type = CASE WHEN COALESCE(base_url, '') = '' THEN 'local' ELSE 'remote' END WHERE repo_type = '' OR repo_type IS NULL; -- 2.47.3 From 771bb13333807f12424e01d4d432394ccf9a28a5 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Sun, 1 Nov 2026 15:24:09 +1100 Subject: [PATCH 3/3] require base_url when an update sets repo_type remote --- internal/api/v2/remotes.go | 4 ++++ internal/api/v2/remotes_ttl_test.go | 2 ++ 2 files changed, 6 insertions(+) diff --git a/internal/api/v2/remotes.go b/internal/api/v2/remotes.go index 42c92e0..aad0b58 100644 --- a/internal/api/v2/remotes.go +++ b/internal/api/v2/remotes.go @@ -151,6 +151,10 @@ func (h *RemotesHandler) update(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("invalid repo type: %q", remote.RepoType), http.StatusBadRequest) return } + if remote.RepoType == models.RepoTypeRemote && remote.BaseURL == "" { + http.Error(w, "base_url is required for remote repositories", http.StatusBadRequest) + return + } if err := remote.ValidateMirrorlist(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return diff --git a/internal/api/v2/remotes_ttl_test.go b/internal/api/v2/remotes_ttl_test.go index ecaa824..39548d3 100644 --- a/internal/api/v2/remotes_ttl_test.go +++ b/internal/api/v2/remotes_ttl_test.go @@ -203,6 +203,8 @@ func TestRepoTypeKeptOnUpdate(t *testing.T) { {"PUT", "/rt-local", `{"package_type":"rpm","description":"x"}`, 200, "rt-local", "local"}, {"PUT", "/rt-local", `{"package_type":"rpm","repo_type":""}`, 200, "rt-local", "local"}, {"PUT", "/rt-local", `{"package_type":"rpm","repo_type":"bogus"}`, 400, "rt-local", "local"}, + {"PUT", "/rt-local", `{"package_type":"rpm","repo_type":"remote"}`, 400, "rt-local", "local"}, + {"PUT", "/rt-remote", `{"package_type":"rpm","repo_type":"local"}`, 200, "rt-remote", "local"}, } { if c := do(t, h, s.method, s.path, s.json); c != s.code { t.Fatalf("step %d %s %s = %d, want %d", i, s.method, s.path, c, s.code) -- 2.47.3