Skip to content

Commit ffd0628

Browse files
author
💥Hedi Ghediri
committed
✨ Add new a store of type filesystem
This new store will handle registries metadata in a persistent way on a local filesystem. The store takes as a parameter the target folder. Each registry has its own dedicated json file.
1 parent e26611f commit ffd0628

File tree

2 files changed

+758
-0
lines changed

2 files changed

+758
-0
lines changed

remote-registry/store/filestore.go

+388
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
package store
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"sort"
9+
"sync"
10+
11+
remote "github.com/criteo/command-launcher/internal/remote"
12+
model "github.com/criteo/command-launcher/remote-registry/model"
13+
)
14+
15+
// FileStore implements the storage interface with file system persistence
16+
type FileStore struct {
17+
basePath string
18+
mutex sync.RWMutex
19+
// Cache to avoid reading from disk on every operation
20+
registriesCache map[string]*model.Registry
21+
}
22+
23+
// NewFileStore creates a new file-based store
24+
func NewFileStore(storagePath string) (*FileStore, error) {
25+
if err := os.MkdirAll(storagePath, 0755); err != nil {
26+
return nil, fmt.Errorf("failed to create storage directory: %w", err)
27+
}
28+
29+
store := &FileStore{
30+
basePath: storagePath,
31+
registriesCache: make(map[string]*model.Registry),
32+
}
33+
34+
// Initialize cache by loading existing registries
35+
if err := store.loadRegistriesFromDisk(); err != nil {
36+
return nil, err
37+
}
38+
39+
return store, nil
40+
}
41+
42+
// loadRegistriesFromDisk reads all registry files into cache
43+
func (s *FileStore) loadRegistriesFromDisk() error {
44+
entries, err := os.ReadDir(s.basePath)
45+
if err != nil {
46+
return fmt.Errorf("failed to read storage directory: %w", err)
47+
}
48+
49+
for _, entry := range entries {
50+
if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" {
51+
continue
52+
}
53+
54+
registryName := entry.Name()[:len(entry.Name())-5] // Remove .json
55+
registry, err := s.loadRegistryFromDisk(registryName)
56+
if err != nil {
57+
return err
58+
}
59+
60+
s.registriesCache[registryName] = registry
61+
}
62+
63+
return nil
64+
}
65+
66+
// loadRegistryFromDisk loads a single registry from disk
67+
func (s *FileStore) loadRegistryFromDisk(name string) (*model.Registry, error) {
68+
path := filepath.Join(s.basePath, fmt.Sprintf("%s.json", name))
69+
data, err := os.ReadFile(path)
70+
if err != nil {
71+
if os.IsNotExist(err) {
72+
return nil, RegistryDoesNotExistError
73+
}
74+
return nil, fmt.Errorf("failed to read registry file: %w", err)
75+
}
76+
77+
var registry model.Registry
78+
if err := json.Unmarshal(data, &registry); err != nil {
79+
return nil, fmt.Errorf("failed to parse registry file: %w", err)
80+
}
81+
82+
return &registry, nil
83+
}
84+
85+
// saveRegistryToDisk writes a registry to disk
86+
func (s *FileStore) saveRegistryToDisk(registry *model.Registry) error {
87+
data, err := json.MarshalIndent(registry, "", " ")
88+
if err != nil {
89+
return fmt.Errorf("failed to marshal registry: %w", err)
90+
}
91+
92+
path := filepath.Join(s.basePath, fmt.Sprintf("%s.json", registry.Name))
93+
return os.WriteFile(path, data, 0644)
94+
}
95+
96+
// NewRegistry creates a new registry
97+
func (s *FileStore) NewRegistry(name string, registryInfo model.RegistryMetadata) error {
98+
s.mutex.Lock()
99+
defer s.mutex.Unlock()
100+
101+
if _, exists := s.registriesCache[name]; exists {
102+
return RegistryAlreadyExistsError
103+
}
104+
if name != registryInfo.Name {
105+
return RegistryNameMismatchError
106+
}
107+
108+
registry := &model.Registry{
109+
RegistryMetadata: registryInfo,
110+
Packages: make(map[string]model.Package),
111+
}
112+
113+
// Save to disk
114+
if err := s.saveRegistryToDisk(registry); err != nil {
115+
return err
116+
}
117+
118+
// Update cache
119+
s.registriesCache[name] = registry
120+
return nil
121+
}
122+
123+
// UpdateRegistry updates an existing registry
124+
func (s *FileStore) UpdateRegistry(name string, registryInfo model.RegistryMetadata) error {
125+
s.mutex.Lock()
126+
defer s.mutex.Unlock()
127+
128+
registry, exists := s.registriesCache[name]
129+
if !exists {
130+
return RegistryDoesNotExistError
131+
}
132+
if name != registryInfo.Name {
133+
return RegistryNameMismatchError
134+
}
135+
136+
// Update registry metadata
137+
registry.Description = registryInfo.Description
138+
registry.Admin = registryInfo.Admin
139+
registry.CustomValues = registryInfo.CustomValues
140+
141+
// Save to disk
142+
if err := s.saveRegistryToDisk(registry); err != nil {
143+
return err
144+
}
145+
146+
return nil
147+
}
148+
149+
// DeleteRegistry removes a registry
150+
func (s *FileStore) DeleteRegistry(name string) error {
151+
s.mutex.Lock()
152+
defer s.mutex.Unlock()
153+
154+
if _, exists := s.registriesCache[name]; !exists {
155+
return RegistryDoesNotExistError
156+
}
157+
158+
// Remove from disk
159+
path := filepath.Join(s.basePath, fmt.Sprintf("%s.json", name))
160+
if err := os.Remove(path); err != nil {
161+
return fmt.Errorf("failed to delete registry file: %w", err)
162+
}
163+
164+
// Remove from cache
165+
delete(s.registriesCache, name)
166+
return nil
167+
}
168+
169+
// AllRegistries returns all registries
170+
func (s *FileStore) AllRegistries() ([]model.Registry, error) {
171+
s.mutex.RLock()
172+
defer s.mutex.RUnlock()
173+
174+
registries := []model.Registry{}
175+
for _, registry := range s.registriesCache {
176+
registries = append(registries, *registry)
177+
}
178+
179+
// Sort registries by name
180+
sort.Slice(registries, func(i, j int) bool {
181+
return registries[i].Name < registries[j].Name
182+
})
183+
184+
return registries, nil
185+
}
186+
187+
// NewPackage creates a new package in a registry
188+
func (s *FileStore) NewPackage(registryName string, packageName string, packageInfo model.PackageMetadata) error {
189+
s.mutex.Lock()
190+
defer s.mutex.Unlock()
191+
192+
registry, exists := s.registriesCache[registryName]
193+
if !exists {
194+
return RegistryDoesNotExistError
195+
}
196+
if _, exists := registry.Packages[packageName]; exists {
197+
return PackageAlreadyExistsError
198+
}
199+
if packageName != packageInfo.Name {
200+
return PackageNameMismatchError
201+
}
202+
203+
// Add package
204+
registry.Packages[packageName] = model.Package{
205+
PackageMetadata: packageInfo,
206+
Versions: []remote.PackageInfo{},
207+
}
208+
209+
// Save to disk
210+
if err := s.saveRegistryToDisk(registry); err != nil {
211+
return err
212+
}
213+
214+
return nil
215+
}
216+
217+
// UpdatePackage updates an existing package
218+
func (s *FileStore) UpdatePackage(registryName string, packageName string, packageInfo model.PackageMetadata) error {
219+
s.mutex.Lock()
220+
defer s.mutex.Unlock()
221+
222+
registry, exists := s.registriesCache[registryName]
223+
if !exists {
224+
return RegistryDoesNotExistError
225+
}
226+
pkg, exists := registry.Packages[packageName]
227+
if !exists {
228+
return PackageDoesNotExistError
229+
}
230+
if packageName != packageInfo.Name {
231+
return PackageNameMismatchError
232+
}
233+
234+
// Update package metadata
235+
pkg.Description = packageInfo.Description
236+
pkg.Admin = packageInfo.Admin
237+
pkg.CustomValues = packageInfo.CustomValues
238+
registry.Packages[packageName] = pkg
239+
240+
// Save to disk
241+
if err := s.saveRegistryToDisk(registry); err != nil {
242+
return err
243+
}
244+
245+
return nil
246+
}
247+
248+
// DeletePackage removes a package from a registry
249+
func (s *FileStore) DeletePackage(registryName string, packageName string) error {
250+
s.mutex.Lock()
251+
defer s.mutex.Unlock()
252+
253+
registry, exists := s.registriesCache[registryName]
254+
if !exists {
255+
return RegistryDoesNotExistError
256+
}
257+
if _, exists := registry.Packages[packageName]; !exists {
258+
return PackageDoesNotExistError
259+
}
260+
261+
// Delete package
262+
delete(registry.Packages, packageName)
263+
264+
// Save to disk
265+
if err := s.saveRegistryToDisk(registry); err != nil {
266+
return err
267+
}
268+
269+
return nil
270+
}
271+
272+
// AllPackagesFromRegistry returns all packages in a registry
273+
func (s *FileStore) AllPackagesFromRegistry(registryName string) ([]model.Package, error) {
274+
s.mutex.RLock()
275+
defer s.mutex.RUnlock()
276+
277+
registry, exists := s.registriesCache[registryName]
278+
if !exists {
279+
return nil, RegistryDoesNotExistError
280+
}
281+
282+
packages := []model.Package{}
283+
for _, pkg := range registry.Packages {
284+
packages = append(packages, pkg)
285+
}
286+
287+
// Sort packages by name
288+
sort.Slice(packages, func(i, j int) bool {
289+
return packages[i].Name < packages[j].Name
290+
})
291+
292+
return packages, nil
293+
}
294+
295+
// NewPackageVersion adds a version to a package
296+
func (s *FileStore) NewPackageVersion(registryName string, packageName string, version string, packageInfo remote.PackageInfo) error {
297+
s.mutex.Lock()
298+
defer s.mutex.Unlock()
299+
300+
registry, exists := s.registriesCache[registryName]
301+
if !exists {
302+
return RegistryDoesNotExistError
303+
}
304+
pkg, exists := registry.Packages[packageName]
305+
if !exists {
306+
return PackageDoesNotExistError
307+
}
308+
if packageName != packageInfo.Name {
309+
return PackageNameMismatchError
310+
}
311+
312+
// Check if version already exists
313+
for _, v := range pkg.Versions {
314+
if v.Version == version {
315+
return PackageVersionAlreadyExistsError
316+
}
317+
}
318+
319+
// Add version
320+
pkg.Versions = append(pkg.Versions, packageInfo)
321+
registry.Packages[packageName] = pkg
322+
323+
// Save to disk
324+
if err := s.saveRegistryToDisk(registry); err != nil {
325+
return err
326+
}
327+
328+
return nil
329+
}
330+
331+
// DeletePackageVersion removes a version from a package
332+
func (s *FileStore) DeletePackageVersion(registryName string, packageName string, version string) error {
333+
s.mutex.Lock()
334+
defer s.mutex.Unlock()
335+
336+
registry, exists := s.registriesCache[registryName]
337+
if !exists {
338+
return RegistryDoesNotExistError
339+
}
340+
pkg, exists := registry.Packages[packageName]
341+
if !exists {
342+
return PackageDoesNotExistError
343+
}
344+
345+
// Find and remove version
346+
newVersions := []remote.PackageInfo{}
347+
exists = false
348+
for _, v := range pkg.Versions {
349+
if v.Version == version {
350+
exists = true
351+
}
352+
if v.Version != version {
353+
newVersions = append(newVersions, v)
354+
}
355+
}
356+
if !exists {
357+
return PackageVersionDoesNotExistError
358+
}
359+
pkg.Versions = newVersions
360+
registry.Packages[packageName] = pkg
361+
362+
// Save to disk
363+
if err := s.saveRegistryToDisk(registry); err != nil {
364+
return err
365+
}
366+
367+
return nil
368+
}
369+
370+
// AllPackageVersionsFromRegistry returns all versions of a package
371+
func (s *FileStore) AllPackageVersionsFromRegistry(registryName string, packageName string) ([]remote.PackageInfo, error) {
372+
s.mutex.RLock()
373+
defer s.mutex.RUnlock()
374+
375+
registry, exists := s.registriesCache[registryName]
376+
if !exists {
377+
return nil, RegistryDoesNotExistError
378+
}
379+
pkg, exists := registry.Packages[packageName]
380+
if !exists {
381+
return nil, PackageDoesNotExistError
382+
}
383+
384+
versions := make([]remote.PackageInfo, len(pkg.Versions))
385+
copy(versions, pkg.Versions)
386+
387+
return versions, nil
388+
}

0 commit comments

Comments
 (0)