Skip to content

Commit 7028661

Browse files
committed
new sqlite backed impl of storage instance for api/v1/all endpoint
1 parent a46ff7d commit 7028661

File tree

4 files changed

+271
-4
lines changed

4 files changed

+271
-4
lines changed

catalogd/cmd/catalogd/main.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ func main() {
279279
},
280280
}
281281

282-
var localStorage storage.Instance
282+
var store storage.Instance
283283
metrics.Registry.MustRegister(catalogdmetrics.RequestDurationMetric)
284284

285285
storeDir := filepath.Join(cacheDir, storageDir)
@@ -294,15 +294,19 @@ func main() {
294294
os.Exit(1)
295295
}
296296

297-
localStorage = storage.LocalDirV1{RootDir: storeDir, RootURL: baseStorageURL}
297+
store, err = storage.NewSQLiteV1(storeDir, baseStorageURL)
298+
if err != nil {
299+
setupLog.Error(err, "unable to create storage instance")
300+
os.Exit(1)
301+
}
298302

299303
// Config for the the catalogd web server
300304
catalogServerConfig := serverutil.CatalogServerConfig{
301305
ExternalAddr: externalAddr,
302306
CatalogAddr: catalogServerAddr,
303307
CertFile: certFile,
304308
KeyFile: keyFile,
305-
LocalStorage: localStorage,
309+
LocalStorage: store,
306310
}
307311

308312
err = serverutil.AddCatalogServerToManager(mgr, catalogServerConfig, cw)
@@ -314,7 +318,7 @@ func main() {
314318
if err = (&corecontrollers.ClusterCatalogReconciler{
315319
Client: mgr.GetClient(),
316320
Unpacker: unpacker,
317-
Storage: localStorage,
321+
Storage: store,
318322
}).SetupWithManager(mgr); err != nil {
319323
setupLog.Error(err, "unable to create controller", "controller", "ClusterCatalog")
320324
os.Exit(1)
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
package storage
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"encoding/json"
7+
"fmt"
8+
"io/fs"
9+
"net/http"
10+
"net/url"
11+
"os"
12+
"path/filepath"
13+
"sync"
14+
15+
"github.com/google/uuid"
16+
"github.com/operator-framework/operator-registry/alpha/declcfg"
17+
_ "modernc.org/sqlite"
18+
)
19+
20+
type SQLiteV1 struct {
21+
db *sql.DB
22+
RootDir string
23+
RootURL *url.URL
24+
mu sync.RWMutex
25+
}
26+
27+
func NewSQLiteV1(rootDir string, rootURL *url.URL) (*SQLiteV1, error) {
28+
if err := os.MkdirAll(rootDir, 0700); err != nil {
29+
return nil, fmt.Errorf("failed to create root directory: %w", err)
30+
}
31+
32+
dbPath := filepath.Join(rootDir, "storage.db")
33+
db, err := sql.Open("sqlite", dbPath+"?_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)")
34+
if err != nil {
35+
return nil, fmt.Errorf("failed to open database: %w", err)
36+
}
37+
38+
if err := initializeDB(db); err != nil {
39+
db.Close()
40+
return nil, err
41+
}
42+
43+
return &SQLiteV1{
44+
db: db,
45+
RootDir: rootDir,
46+
RootURL: rootURL,
47+
}, nil
48+
}
49+
50+
func initializeDB(db *sql.DB) error {
51+
_, err := db.Exec(`
52+
CREATE TABLE IF NOT EXISTS metas (
53+
id TEXT PRIMARY KEY,
54+
catalog_name TEXT NOT NULL,
55+
schema TEXT NOT NULL,
56+
package TEXT,
57+
name TEXT NOT NULL,
58+
blob TEXT NOT NULL,
59+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
60+
);
61+
62+
-- Index for catalog lookups
63+
CREATE INDEX IF NOT EXISTS idx_metas_catalog
64+
ON metas(catalog_name);
65+
`)
66+
return err
67+
}
68+
69+
func (s *SQLiteV1) Store(ctx context.Context, catalog string, fsys fs.FS) error {
70+
s.mu.Lock()
71+
defer s.mu.Unlock()
72+
73+
tx, err := s.db.BeginTx(ctx, nil)
74+
if err != nil {
75+
return fmt.Errorf("failed to begin transaction: %w", err)
76+
}
77+
defer tx.Rollback()
78+
79+
// Delete existing entries for this catalog
80+
if _, err := tx.ExecContext(ctx, "DELETE FROM metas WHERE catalog_name = ?", catalog); err != nil {
81+
return fmt.Errorf("failed to delete existing catalog entries: %w", err)
82+
}
83+
84+
stmt, err := tx.PrepareContext(ctx, `
85+
INSERT INTO metas (id, catalog_name, schema, package, name, blob)
86+
VALUES (?, ?, ?, ?, ?, ?)
87+
`)
88+
if err != nil {
89+
return fmt.Errorf("failed to prepare statement: %w", err)
90+
}
91+
defer stmt.Close()
92+
93+
err = declcfg.WalkMetasFS(ctx, fsys, func(path string, meta *declcfg.Meta, err error) error {
94+
if err != nil {
95+
return err
96+
}
97+
98+
// Generate a new UUID for each meta entry
99+
id := uuid.New().String()
100+
101+
// Handle empty package as NULL
102+
// since schema=olm.package blobs don't have a `package`
103+
// value and the value is instead in `name`
104+
var pkgValue interface{}
105+
if meta.Package != "" {
106+
pkgValue = meta.Package
107+
}
108+
109+
_, err = stmt.ExecContext(ctx,
110+
id,
111+
catalog,
112+
meta.Schema,
113+
pkgValue,
114+
meta.Name,
115+
string(meta.Blob),
116+
)
117+
if err != nil {
118+
return fmt.Errorf("failed to insert meta: %w", err)
119+
}
120+
return nil
121+
})
122+
if err != nil {
123+
return fmt.Errorf("error walking FBC root: %w", err)
124+
}
125+
126+
return tx.Commit()
127+
}
128+
129+
func (s *SQLiteV1) Delete(catalog string) error {
130+
s.mu.Lock()
131+
defer s.mu.Unlock()
132+
133+
_, err := s.db.Exec("DELETE FROM metas WHERE catalog_name = ?", catalog)
134+
if err != nil {
135+
return fmt.Errorf("failed to delete catalog: %w", err)
136+
}
137+
return nil
138+
}
139+
140+
func (s *SQLiteV1) BaseURL(catalog string) string {
141+
return s.RootURL.JoinPath(catalog).String()
142+
}
143+
144+
func (s *SQLiteV1) handleV1All(w http.ResponseWriter, r *http.Request) {
145+
if r.Method != http.MethodGet {
146+
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
147+
return
148+
}
149+
150+
catalog := r.PathValue("catalog")
151+
if catalog == "" {
152+
http.Error(w, "Not found", http.StatusNotFound)
153+
return
154+
}
155+
156+
s.mu.RLock()
157+
defer s.mu.RUnlock()
158+
159+
rows, err := s.db.Query(`
160+
SELECT schema, package, name, blob
161+
FROM metas
162+
WHERE catalog_name = ?
163+
ORDER BY schema,
164+
CASE WHEN package IS NULL THEN 1 ELSE 0 END,
165+
package,
166+
name
167+
`, catalog)
168+
if err != nil {
169+
http.Error(w, "Internal server error", http.StatusInternalServerError)
170+
return
171+
}
172+
defer rows.Close()
173+
174+
w.Header().Set("Content-Type", "application/jsonl")
175+
encoder := json.NewEncoder(w)
176+
177+
for rows.Next() {
178+
var meta declcfg.Meta
179+
var packageVal sql.NullString
180+
var blobStr string
181+
182+
if err := rows.Scan(&meta.Schema, &packageVal, &meta.Name, &blobStr); err != nil {
183+
http.Error(w, "Internal server error", http.StatusInternalServerError)
184+
return
185+
}
186+
187+
// Convert NULL package to empty string
188+
if packageVal.Valid {
189+
meta.Package = packageVal.String
190+
}
191+
192+
meta.Blob = json.RawMessage(blobStr)
193+
194+
if err := encoder.Encode(meta); err != nil {
195+
return
196+
}
197+
}
198+
}
199+
200+
func (s *SQLiteV1) StorageServerHandler() http.Handler {
201+
mux := http.NewServeMux()
202+
mux.HandleFunc(s.RootURL.JoinPath("{catalog}", "api", "v1", "all").Path, s.handleV1All)
203+
return mux
204+
}
205+
206+
func (s *SQLiteV1) ContentExists(catalog string) bool {
207+
s.mu.RLock()
208+
defer s.mu.RUnlock()
209+
210+
var exists bool
211+
err := s.db.QueryRow(`
212+
SELECT EXISTS(
213+
SELECT 1 FROM metas WHERE catalog_name = ? LIMIT 1
214+
)
215+
`, catalog).Scan(&exists)
216+
if err != nil {
217+
return false
218+
}
219+
return exists
220+
}
221+
222+
func (s *SQLiteV1) Close() error {
223+
s.mu.Lock()
224+
defer s.mu.Unlock()
225+
return s.db.Close()
226+
}

go.mod

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ require (
3636
k8s.io/component-base v0.32.0
3737
k8s.io/klog/v2 v2.130.1
3838
k8s.io/utils v0.0.0-20241210054802-24370beab758
39+
modernc.org/sqlite v1.34.5
3940
sigs.k8s.io/controller-runtime v0.19.4
4041
sigs.k8s.io/yaml v1.4.0
4142
)
@@ -88,6 +89,7 @@ require (
8889
github.com/docker/go-connections v0.5.0 // indirect
8990
github.com/docker/go-metrics v0.0.1 // indirect
9091
github.com/docker/go-units v0.5.0 // indirect
92+
github.com/dustin/go-humanize v1.0.1 // indirect
9193
github.com/emicklei/go-restful/v3 v3.11.2 // indirect
9294
github.com/evanphx/json-patch v5.9.0+incompatible // indirect
9395
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
@@ -173,6 +175,7 @@ require (
173175
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
174176
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
175177
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
178+
github.com/ncruces/go-strftime v0.1.9 // indirect
176179
github.com/oklog/ulid v1.3.1 // indirect
177180
github.com/opencontainers/image-spec v1.1.0 // indirect
178181
github.com/opencontainers/runtime-spec v1.2.0 // indirect
@@ -186,6 +189,7 @@ require (
186189
github.com/prometheus/client_model v0.6.1 // indirect
187190
github.com/prometheus/common v0.57.0 // indirect
188191
github.com/prometheus/procfs v0.15.1 // indirect
192+
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
189193
github.com/rivo/uniseg v0.4.7 // indirect
190194
github.com/rubenv/sql-migrate v1.7.1 // indirect
191195
github.com/russross/blackfriday/v2 v2.1.0 // indirect
@@ -245,6 +249,9 @@ require (
245249
gopkg.in/yaml.v3 v3.0.1 // indirect
246250
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
247251
k8s.io/kubectl v0.32.0 // indirect
252+
modernc.org/libc v1.55.3 // indirect
253+
modernc.org/mathutil v1.6.0 // indirect
254+
modernc.org/memory v1.8.0 // indirect
248255
oras.land/oras-go v1.2.5 // indirect
249256
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect
250257
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect

go.sum

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
177177
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
178178
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
179179
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
180+
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
181+
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
180182
github.com/emicklei/go-restful/v3 v3.11.2 h1:1onLa9DcsMYO9P+CXaL0dStDqQ2EHHXLiz+BtnqkLAU=
181183
github.com/emicklei/go-restful/v3 v3.11.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
182184
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -509,6 +511,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
509511
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
510512
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
511513
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
514+
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
515+
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
512516
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
513517
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
514518
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
@@ -594,6 +598,8 @@ github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb
594598
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ=
595599
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
596600
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
601+
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
602+
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
597603
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
598604
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
599605
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -1014,6 +1020,30 @@ k8s.io/kubectl v0.32.0 h1:rpxl+ng9qeG79YA4Em9tLSfX0G8W0vfaiPVrc/WR7Xw=
10141020
k8s.io/kubectl v0.32.0/go.mod h1:qIjSX+QgPQUgdy8ps6eKsYNF+YmFOAO3WygfucIqFiE=
10151021
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
10161022
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
1023+
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
1024+
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
1025+
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
1026+
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
1027+
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
1028+
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
1029+
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
1030+
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
1031+
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
1032+
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
1033+
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
1034+
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
1035+
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
1036+
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
1037+
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
1038+
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
1039+
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
1040+
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
1041+
modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g=
1042+
modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE=
1043+
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
1044+
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
1045+
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
1046+
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
10171047
oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo=
10181048
oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo=
10191049
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

0 commit comments

Comments
 (0)