Skip to content

Commit 20d83c1

Browse files
authored
(server) Expose content URL on CR status (#168)
closes #119 Signed-off-by: Anik <[email protected]>
1 parent 21ba18e commit 20d83c1

File tree

11 files changed

+73
-18
lines changed

11 files changed

+73
-18
lines changed

api/core/v1alpha1/catalog_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ type CatalogStatus struct {
8181

8282
ResolvedSource *CatalogSource `json:"resolvedSource,omitempty"`
8383
Phase string `json:"phase,omitempty"`
84+
// ContentURL is a cluster-internal address that on-cluster components
85+
// can read the content of a catalog from
86+
ContentURL string `json:"contentURL,omitempty"`
8487
}
8588

8689
// CatalogSource contains the sourcing information for a Catalog

cmd/manager/main.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"flag"
2121
"fmt"
2222
"net/http"
23+
"net/url"
2324
"os"
2425
"time"
2526

@@ -73,6 +74,7 @@ func main() {
7374
systemNamespace string
7475
storageDir string
7576
catalogServerAddr string
77+
httpExternalAddr string
7678
)
7779
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
7880
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
@@ -84,6 +86,7 @@ func main() {
8486
flag.StringVar(&systemNamespace, "system-namespace", "", "The namespace catalogd uses for internal state, configuration, and workloads")
8587
flag.StringVar(&storageDir, "catalogs-storage-dir", "/var/cache/catalogs", "The directory in the filesystem where unpacked catalog content will be stored and served from")
8688
flag.StringVar(&catalogServerAddr, "catalogs-server-addr", ":8083", "The address where the unpacked catalogs' content will be accessible")
89+
flag.StringVar(&httpExternalAddr, "http-external-address", "http://catalogd-catalogserver.catalogd-system.svc", "The external address at which the http server is reachable.")
8790
flag.BoolVar(&profiling, "profiling", false, "enable profiling endpoints to allow for using pprof")
8891
flag.BoolVar(&catalogdVersion, "version", false, "print the catalogd version and exit")
8992
opts := zap.Options{
@@ -135,7 +138,12 @@ func main() {
135138
os.Exit(1)
136139
}
137140

138-
localStorage = storage.LocalDir{RootDir: storageDir}
141+
baseStorageURL, err := url.Parse(fmt.Sprintf("%s/catalogs/", httpExternalAddr))
142+
if err != nil {
143+
setupLog.Error(err, "unable to create base storage URL")
144+
os.Exit(1)
145+
}
146+
localStorage = storage.LocalDir{RootDir: storageDir, BaseURL: baseStorageURL}
139147
shutdownTimeout := 30 * time.Second
140148
catalogServer := server.Server{
141149
Kind: "catalogs",

config/crd/bases/catalogd.operatorframework.io_catalogs.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ spec:
135135
- type
136136
type: object
137137
type: array
138+
contentURL:
139+
description: ContentURL is a cluster-internal address that on-cluster
140+
components can read the content of a catalog from
141+
type: string
138142
phase:
139143
type: string
140144
resolvedSource:

config/default/manager_auth_proxy_patch.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,5 @@ spec:
5252
- "--leader-elect"
5353
- "--catalogs-storage-dir=/var/cache/catalogs"
5454
- "--feature-gates=HTTPServer=true"
55+
- "--http-external-address=http://catalogd-catalogserver.catalogd-system.svc"
5556

docs/fetching-catalog-contents.md

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,32 @@ This document covers how to fetch the contents for a `Catalog` from the
33
Catalogd HTTP Server that runs when the `HTTPServer` feature-gate is enabled
44
(enabled by default).
55

6-
All `Catalog`s currently have their contents served via the following endpoint pattern:
7-
`http://{httpServerBaseUrl}/catalogs/{Catalog.Name}/all.json`
6+
For example purposes we make the following assumption:
7+
- A `Catalog` named `operatorhubio` has been created and successfully unpacked
8+
(denoted in the `Catalog.Status`)
9+
10+
`Catalog` CRs have a status.contentURL field whose value is the location where the content
11+
of a catalog can be read from:
12+
13+
```yaml
14+
status:
15+
conditions:
16+
- lastTransitionTime: "2023-09-14T15:21:18Z"
17+
message: successfully unpacked the catalog image "quay.io/operatorhubio/catalog@sha256:e53267559addc85227c2a7901ca54b980bc900276fc24d3f4db0549cb38ecf76"
18+
reason: UnpackSuccessful
19+
status: "True"
20+
type: Unpacked
21+
contentURL: http://catalogd-catalogserver.catalogd-system.svc/catalogs/operatorhubio/all.json
22+
phase: Unpacked
23+
resolvedSource:
24+
image:
25+
ref: quay.io/operatorhubio/catalog@sha256:e53267559addc85227c2a7901ca54b980bc900276fc24d3f4db0549cb38ecf76
26+
type: image
27+
```
828
929
All responses will be a JSON stream where each JSON object is a File-Based Catalog (FBC)
1030
object.
1131
12-
For example purposes we make the following assumption:
13-
- A `Catalog` named `operatorhubio` has been created and successfully unpacked
14-
(denoted in the `Catalog.Status`)
1532
1633
## On cluster
1734
@@ -30,11 +47,11 @@ When making a request for the contents of the `operatorhubio` `Catalog` from out
3047
the cluster, we have to perform an extra step:
3148
1. Port forward the `catalogd-catalogserver` service in the `catalogd-system` namespace:
3249
```sh
33-
kubectl -n catalogd-system port-forward svc/catalogd-catalogserver <port>:80
50+
kubectl -n catalogd-system port-forward svc/catalogd-catalogserver 8080:80
3451
```
3552

3653
Once the service has been successfully forwarded to a localhost port, issue a HTTP `GET`
37-
request to `http://localhost:<port>/catalogs/operatorhubio/all.json`
54+
request to `http://localhost:8080/catalogs/operatorhubio/all.json`
3855

3956
An example `curl` request that assumes the port-forwarding is mapped to port 8080 on the local machine:
4057
```sh

pkg/controllers/core/catalog_controller.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,19 @@ func (r *CatalogReconciler) reconcile(ctx context.Context, catalog *v1alpha1.Cat
128128
updateStatusUnpacking(&catalog.Status, unpackResult)
129129
return ctrl.Result{}, nil
130130
case source.StateUnpacked:
131+
contentURL := ""
131132
// TODO: We should check to see if the unpacked result has the same content
132133
// as the already unpacked content. If it does, we should skip this rest
133134
// of the unpacking steps.
134135
if features.CatalogdFeatureGate.Enabled(features.HTTPServer) {
135-
if err := r.Storage.Store(catalog.Name, unpackResult.FS); err != nil {
136+
err := r.Storage.Store(catalog.Name, unpackResult.FS)
137+
if err != nil {
136138
return ctrl.Result{}, updateStatusStorageError(&catalog.Status, fmt.Errorf("error storing fbc: %v", err))
137139
}
140+
contentURL = r.Storage.ContentURL(catalog.Name)
138141
}
139142

140-
updateStatusUnpacked(&catalog.Status, unpackResult)
143+
updateStatusUnpacked(&catalog.Status, unpackResult, contentURL)
141144
return ctrl.Result{}, nil
142145
default:
143146
return ctrl.Result{}, updateStatusUnpackFailing(&catalog.Status, fmt.Errorf("unknown unpack state %q: %v", unpackResult.State, err))
@@ -166,8 +169,9 @@ func updateStatusUnpacking(status *v1alpha1.CatalogStatus, result *source.Result
166169
})
167170
}
168171

169-
func updateStatusUnpacked(status *v1alpha1.CatalogStatus, result *source.Result) {
172+
func updateStatusUnpacked(status *v1alpha1.CatalogStatus, result *source.Result, contentURL string) {
170173
status.ResolvedSource = result.ResolvedSource
174+
status.ContentURL = contentURL
171175
status.Phase = v1alpha1.PhaseUnpacked
172176
meta.SetStatusCondition(&status.Conditions, metav1.Condition{
173177
Type: v1alpha1.TypeUnpacked,

pkg/controllers/core/catalog_controller_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ func (m MockStore) Delete(_ string) error {
6868
return nil
6969
}
7070

71+
func (m MockStore) ContentURL(_ string) string {
72+
return "URL"
73+
}
74+
7175
func (m MockStore) StorageServerHandler() http.Handler {
7276
panic("not needed")
7377
}

pkg/storage/localdir.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"io/fs"
66
"net/http"
7+
"net/url"
78
"os"
89
"path/filepath"
910

@@ -17,6 +18,7 @@ import (
1718
// atomic view of the content for a catalog.
1819
type LocalDir struct {
1920
RootDir string
21+
BaseURL *url.URL
2022
}
2123

2224
func (s LocalDir) Store(catalog string, fsys fs.FS) error {
@@ -47,9 +49,13 @@ func (s LocalDir) Delete(catalog string) error {
4749
return os.RemoveAll(filepath.Join(s.RootDir, catalog))
4850
}
4951

52+
func (s LocalDir) ContentURL(catalog string) string {
53+
return fmt.Sprintf("%s%s/all.json", s.BaseURL, catalog)
54+
}
55+
5056
func (s LocalDir) StorageServerHandler() http.Handler {
5157
mux := http.NewServeMux()
52-
mux.Handle("/catalogs/", http.StripPrefix("/catalogs/", http.FileServer(http.FS(&filesOnlyFilesystem{os.DirFS(s.RootDir)}))))
58+
mux.Handle(s.BaseURL.Path, http.StripPrefix(s.BaseURL.Path, http.FileServer(http.FS(&filesOnlyFilesystem{os.DirFS(s.RootDir)}))))
5359
return mux
5460
}
5561

pkg/storage/localdir_test.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io/fs"
77
"net/http"
88
"net/http/httptest"
9+
"net/url"
910
"os"
1011
"path/filepath"
1112
"testing/fstest"
@@ -16,11 +17,14 @@ import (
1617
"github.com/operator-framework/operator-registry/alpha/declcfg"
1718
)
1819

20+
const urlPrefix = "/catalogs/"
21+
1922
var _ = Describe("LocalDir Storage Test", func() {
2023
var (
2124
catalog = "test-catalog"
2225
store Instance
2326
rootDir string
27+
baseURL *url.URL
2428
testBundleName = "bundle.v0.0.1"
2529
testBundleImage = "quaydock.io/namespace/bundle:0.0.3"
2630
testBundleRelatedImageName = "test"
@@ -40,7 +44,8 @@ var _ = Describe("LocalDir Storage Test", func() {
4044
Expect(err).ToNot(HaveOccurred())
4145
rootDir = d
4246

43-
store = LocalDir{RootDir: rootDir}
47+
baseURL = &url.URL{Scheme: "http", Host: "test-addr", Path: urlPrefix}
48+
store = LocalDir{RootDir: rootDir, BaseURL: baseURL}
4449
unpackResultFS = &fstest.MapFS{
4550
"bundle.yaml": &fstest.MapFile{Data: []byte(testBundle), Mode: os.ModePerm},
4651
"package.yaml": &fstest.MapFile{Data: []byte(testPackage), Mode: os.ModePerm},
@@ -64,6 +69,9 @@ var _ = Describe("LocalDir Storage Test", func() {
6469
diff := cmp.Diff(gotConfig, storedConfig)
6570
Expect(diff).To(Equal(""))
6671
})
72+
It("should form the content URL correctly", func() {
73+
Expect(store.ContentURL(catalog)).To(Equal(fmt.Sprintf("%s%s/all.json", baseURL, catalog)))
74+
})
6775
When("The stored content is deleted", func() {
6876
BeforeEach(func() {
6977
err := store.Delete(catalog)
@@ -88,7 +96,7 @@ var _ = Describe("LocalDir Server Handler tests", func() {
8896
d, err := os.MkdirTemp(GinkgoT().TempDir(), "cache")
8997
Expect(err).ToNot(HaveOccurred())
9098
Expect(os.Mkdir(filepath.Join(d, "test-catalog"), 0700)).To(Succeed())
91-
store = LocalDir{RootDir: d}
99+
store = LocalDir{RootDir: d, BaseURL: &url.URL{Path: urlPrefix}}
92100
testServer = httptest.NewServer(store.StorageServerHandler())
93101

94102
})

pkg/storage/storage.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ import (
1212
type Instance interface {
1313
Store(catalog string, fsys fs.FS) error
1414
Delete(catalog string) error
15+
ContentURL(catalog string) string
1516
StorageServerHandler() http.Handler
1617
}

0 commit comments

Comments
 (0)