Skip to content

Commit 1ae9b4a

Browse files
committed
use catalogd HTTP server instead of CatalogMetadata API
Signed-off-by: Bryce Palmer <bpalmer@redhat.com>
1 parent 6640aac commit 1ae9b4a

File tree

12 files changed

+612
-340
lines changed

12 files changed

+612
-340
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ require (
1010
github.com/onsi/ginkgo/v2 v2.12.1
1111
github.com/onsi/gomega v1.27.10
1212
github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42
13-
github.com/operator-framework/catalogd v0.6.0
13+
github.com/operator-framework/catalogd v0.7.0
1414
github.com/operator-framework/deppy v0.0.0-20230629133131-bb7b6ae7b266
1515
github.com/operator-framework/operator-registry v1.28.0
1616
github.com/operator-framework/rukpak v0.13.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -702,8 +702,8 @@ github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3
702702
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
703703
github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42 h1:d/Pnr19TnmIq3zQ6ebewC+5jt5zqYbRkvYd37YZENQY=
704704
github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42/go.mod h1:l/cuwtPxkVUY7fzYgdust2m9tlmb8I4pOvbsUufRb24=
705-
github.com/operator-framework/catalogd v0.6.0 h1:dSZ54MVSHJ8hcoV7OCRxnk3x4O3ramlyPvvz0vsKYdk=
706-
github.com/operator-framework/catalogd v0.6.0/go.mod h1:I0n086a4a+nP1YZy742IrPaWvOlWu0Mj6qA6j4K96Vg=
705+
github.com/operator-framework/catalogd v0.7.0 h1:L0uesxq+r59rGubtxMoVtIShKn7gSSSLqxpWLfwpAaw=
706+
github.com/operator-framework/catalogd v0.7.0/go.mod h1:tVhaenJVFTHHgdJ0Pju7U4G3epeoZfUWWM1J5nPISPQ=
707707
github.com/operator-framework/deppy v0.0.0-20230629133131-bb7b6ae7b266 h1:SQEUaAoRWNhr2poLH6z/RsEWZG7PppDWHsr5vAvJkJc=
708708
github.com/operator-framework/deppy v0.0.0-20230629133131-bb7b6ae7b266/go.mod h1:6kgHMeS5vQt3gqWGgJIig1yT5uflBUsCc1orP+I3nbk=
709709
github.com/operator-framework/operator-registry v1.28.0 h1:vtmd2WgJxkx7vuuOxW4k5Le/oo0SfonSeJVMU3rKIfk=
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package cache
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"os"
9+
"path/filepath"
10+
"sync"
11+
12+
catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
13+
14+
"github.com/operator-framework/operator-controller/internal/catalogmetadata/client"
15+
)
16+
17+
var _ client.Fetcher = &filesystemCache{}
18+
19+
// NewFilesystemCache returns a client.Fetcher implementation that uses a
20+
// local filesystem to cache Catalog contents. When fetching the Catalog contents
21+
// it will:
22+
// - Check if the Catalog is cached
23+
// - IF !cached it will fetch from the catalogd HTTP server and cache the response
24+
// - IF cached it will verify the cache is up to date. If it is up to date it will return
25+
// the cached contents, if not it will fetch the new contents from the catalogd HTTP
26+
// server and update the cached contents.
27+
func NewFilesystemCache(cachePath string, tripper http.RoundTripper) client.Fetcher {
28+
return &filesystemCache{
29+
cachePath: cachePath,
30+
mutex: sync.RWMutex{},
31+
tripper: tripper,
32+
cacheDataByCatalogName: map[string]cacheData{},
33+
}
34+
}
35+
36+
// cacheData holds information about a catalog
37+
// other than it's contents that is used for
38+
// making decisions on when to attempt to refresh
39+
// the cache.
40+
type cacheData struct {
41+
ResolvedRef string
42+
}
43+
44+
// FilesystemCache is a cache that
45+
// uses the local filesystem for caching
46+
// catalog contents. It will fetch catalog
47+
// contents if the catalog does not already
48+
// exist in the cache.
49+
type filesystemCache struct {
50+
mutex sync.RWMutex
51+
cachePath string
52+
tripper http.RoundTripper
53+
cacheDataByCatalogName map[string]cacheData
54+
}
55+
56+
// FetchCatalogContents implements the client.Fetcher interface and
57+
// will fetch the contents for the provided Catalog from the filesystem.
58+
// If the provided Catalog has not yet been cached, it will make a GET
59+
// request to the Catalogd HTTP server to get the Catalog contents and cache
60+
// them. The cache will be updated automatically if a Catalog is noticed to
61+
// have a different resolved image reference.
62+
// The Catalog provided to this function is expected to:
63+
// - Be non-nil
64+
// - Have a non-nil Catalog.Status.ResolvedSource.Image
65+
// This ensures that we are only attempting to fetch catalog contents for Catalog
66+
// resources that have been successfully reconciled, unpacked, and are being served.
67+
// These requirements help ensure that we can rely on status conditions to determine
68+
// when to issue a request to update the cached Catalog contents.
69+
func (fsc *filesystemCache) FetchCatalogContents(ctx context.Context, catalog *catalogd.Catalog) (io.ReadCloser, error) {
70+
if catalog == nil {
71+
return nil, fmt.Errorf("error: provided catalog must be non-nil")
72+
}
73+
74+
if catalog.Status.ResolvedSource == nil {
75+
return nil, fmt.Errorf("error: catalog %q has a nil status.resolvedSource value", catalog.Name)
76+
}
77+
78+
if catalog.Status.ResolvedSource.Image == nil {
79+
return nil, fmt.Errorf("error: catalog %q has a nil status.resolvedSource.image value", catalog.Name)
80+
}
81+
82+
cacheDir := filepath.Join(fsc.cachePath, catalog.Name)
83+
cacheFilePath := filepath.Join(cacheDir, "data.json")
84+
85+
fsc.mutex.RLock()
86+
if data, ok := fsc.cacheDataByCatalogName[catalog.Name]; ok {
87+
if catalog.Status.ResolvedSource.Image.Ref == data.ResolvedRef {
88+
fsc.mutex.RUnlock()
89+
return os.Open(cacheFilePath)
90+
}
91+
}
92+
fsc.mutex.RUnlock()
93+
94+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, catalog.Status.ContentURL, nil)
95+
if err != nil {
96+
return nil, fmt.Errorf("error forming request: %s", err)
97+
}
98+
99+
resp, err := fsc.tripper.RoundTrip(req)
100+
if err != nil {
101+
return nil, fmt.Errorf("error performing request: %s", err)
102+
}
103+
defer resp.Body.Close()
104+
105+
switch resp.StatusCode {
106+
case http.StatusOK:
107+
contents, err := io.ReadAll(resp.Body)
108+
if err != nil {
109+
return nil, fmt.Errorf("error reading response body: %s", err)
110+
}
111+
112+
fsc.mutex.Lock()
113+
defer fsc.mutex.Unlock()
114+
115+
// make sure we only write if this info hasn't been updated
116+
// by another thread. The check here, if multiple threads are
117+
// updating this, has no way to tell if the current ref is the
118+
// newest possible ref. If another thread has already updated
119+
// this to be the same value, skip the write logic and return
120+
// the cached contents
121+
if data, ok := fsc.cacheDataByCatalogName[catalog.Name]; ok {
122+
if data.ResolvedRef == catalog.Status.ResolvedSource.Image.Ref {
123+
break
124+
}
125+
}
126+
127+
if err = os.MkdirAll(cacheDir, os.ModePerm); err != nil {
128+
return nil, fmt.Errorf("error creating cache directory for Catalog %q: %s", catalog.Name, err)
129+
}
130+
131+
if err = os.WriteFile(cacheFilePath, contents, os.ModePerm); err != nil {
132+
return nil, fmt.Errorf("error caching response: %s", err)
133+
}
134+
135+
fsc.cacheDataByCatalogName[catalog.Name] = cacheData{
136+
ResolvedRef: catalog.Status.ResolvedSource.Image.Ref,
137+
}
138+
default:
139+
return nil, fmt.Errorf("error: received unexpected response status code %d", resp.StatusCode)
140+
}
141+
142+
return os.Open(cacheFilePath)
143+
}

0 commit comments

Comments
 (0)