Skip to content

Commit

Permalink
(server) Expose content URL on CR status (#168)
Browse files Browse the repository at this point in the history
closes #119

Signed-off-by: Anik <anikbhattacharya93@gmail.com>
  • Loading branch information
anik120 committed Sep 18, 2023
1 parent 21ba18e commit 20d83c1
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 18 deletions.
3 changes: 3 additions & 0 deletions api/core/v1alpha1/catalog_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ type CatalogStatus struct {

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

// CatalogSource contains the sourcing information for a Catalog
Expand Down
10 changes: 9 additions & 1 deletion cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"flag"
"fmt"
"net/http"
"net/url"
"os"
"time"

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

localStorage = storage.LocalDir{RootDir: storageDir}
baseStorageURL, err := url.Parse(fmt.Sprintf("%s/catalogs/", httpExternalAddr))
if err != nil {
setupLog.Error(err, "unable to create base storage URL")
os.Exit(1)
}
localStorage = storage.LocalDir{RootDir: storageDir, BaseURL: baseStorageURL}
shutdownTimeout := 30 * time.Second
catalogServer := server.Server{
Kind: "catalogs",
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/catalogd.operatorframework.io_catalogs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ spec:
- type
type: object
type: array
contentURL:
description: ContentURL is a cluster-internal address that on-cluster
components can read the content of a catalog from
type: string
phase:
type: string
resolvedSource:
Expand Down
1 change: 1 addition & 0 deletions config/default/manager_auth_proxy_patch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,5 @@ spec:
- "--leader-elect"
- "--catalogs-storage-dir=/var/cache/catalogs"
- "--feature-gates=HTTPServer=true"
- "--http-external-address=http://catalogd-catalogserver.catalogd-system.svc"

31 changes: 24 additions & 7 deletions docs/fetching-catalog-contents.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,32 @@ This document covers how to fetch the contents for a `Catalog` from the
Catalogd HTTP Server that runs when the `HTTPServer` feature-gate is enabled
(enabled by default).

All `Catalog`s currently have their contents served via the following endpoint pattern:
`http://{httpServerBaseUrl}/catalogs/{Catalog.Name}/all.json`
For example purposes we make the following assumption:
- A `Catalog` named `operatorhubio` has been created and successfully unpacked
(denoted in the `Catalog.Status`)

`Catalog` CRs have a status.contentURL field whose value is the location where the content
of a catalog can be read from:

```yaml
status:
conditions:
- lastTransitionTime: "2023-09-14T15:21:18Z"
message: successfully unpacked the catalog image "quay.io/operatorhubio/catalog@sha256:e53267559addc85227c2a7901ca54b980bc900276fc24d3f4db0549cb38ecf76"
reason: UnpackSuccessful
status: "True"
type: Unpacked
contentURL: http://catalogd-catalogserver.catalogd-system.svc/catalogs/operatorhubio/all.json
phase: Unpacked
resolvedSource:
image:
ref: quay.io/operatorhubio/catalog@sha256:e53267559addc85227c2a7901ca54b980bc900276fc24d3f4db0549cb38ecf76
type: image
```

All responses will be a JSON stream where each JSON object is a File-Based Catalog (FBC)
object.

For example purposes we make the following assumption:
- A `Catalog` named `operatorhubio` has been created and successfully unpacked
(denoted in the `Catalog.Status`)

## On cluster

Expand All @@ -30,11 +47,11 @@ When making a request for the contents of the `operatorhubio` `Catalog` from out
the cluster, we have to perform an extra step:
1. Port forward the `catalogd-catalogserver` service in the `catalogd-system` namespace:
```sh
kubectl -n catalogd-system port-forward svc/catalogd-catalogserver <port>:80
kubectl -n catalogd-system port-forward svc/catalogd-catalogserver 8080:80
```

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

An example `curl` request that assumes the port-forwarding is mapped to port 8080 on the local machine:
```sh
Expand Down
10 changes: 7 additions & 3 deletions pkg/controllers/core/catalog_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,19 @@ func (r *CatalogReconciler) reconcile(ctx context.Context, catalog *v1alpha1.Cat
updateStatusUnpacking(&catalog.Status, unpackResult)
return ctrl.Result{}, nil
case source.StateUnpacked:
contentURL := ""
// TODO: We should check to see if the unpacked result has the same content
// as the already unpacked content. If it does, we should skip this rest
// of the unpacking steps.
if features.CatalogdFeatureGate.Enabled(features.HTTPServer) {
if err := r.Storage.Store(catalog.Name, unpackResult.FS); err != nil {
err := r.Storage.Store(catalog.Name, unpackResult.FS)
if err != nil {
return ctrl.Result{}, updateStatusStorageError(&catalog.Status, fmt.Errorf("error storing fbc: %v", err))
}
contentURL = r.Storage.ContentURL(catalog.Name)
}

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

func updateStatusUnpacked(status *v1alpha1.CatalogStatus, result *source.Result) {
func updateStatusUnpacked(status *v1alpha1.CatalogStatus, result *source.Result, contentURL string) {
status.ResolvedSource = result.ResolvedSource
status.ContentURL = contentURL
status.Phase = v1alpha1.PhaseUnpacked
meta.SetStatusCondition(&status.Conditions, metav1.Condition{
Type: v1alpha1.TypeUnpacked,
Expand Down
4 changes: 4 additions & 0 deletions pkg/controllers/core/catalog_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ func (m MockStore) Delete(_ string) error {
return nil
}

func (m MockStore) ContentURL(_ string) string {
return "URL"
}

func (m MockStore) StorageServerHandler() http.Handler {
panic("not needed")
}
Expand Down
8 changes: 7 additions & 1 deletion pkg/storage/localdir.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io/fs"
"net/http"
"net/url"
"os"
"path/filepath"

Expand All @@ -17,6 +18,7 @@ import (
// atomic view of the content for a catalog.
type LocalDir struct {
RootDir string
BaseURL *url.URL
}

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

func (s LocalDir) ContentURL(catalog string) string {
return fmt.Sprintf("%s%s/all.json", s.BaseURL, catalog)
}

func (s LocalDir) StorageServerHandler() http.Handler {
mux := http.NewServeMux()
mux.Handle("/catalogs/", http.StripPrefix("/catalogs/", http.FileServer(http.FS(&filesOnlyFilesystem{os.DirFS(s.RootDir)}))))
mux.Handle(s.BaseURL.Path, http.StripPrefix(s.BaseURL.Path, http.FileServer(http.FS(&filesOnlyFilesystem{os.DirFS(s.RootDir)}))))
return mux
}

Expand Down
12 changes: 10 additions & 2 deletions pkg/storage/localdir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io/fs"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"testing/fstest"
Expand All @@ -16,11 +17,14 @@ import (
"github.com/operator-framework/operator-registry/alpha/declcfg"
)

const urlPrefix = "/catalogs/"

var _ = Describe("LocalDir Storage Test", func() {
var (
catalog = "test-catalog"
store Instance
rootDir string
baseURL *url.URL
testBundleName = "bundle.v0.0.1"
testBundleImage = "quaydock.io/namespace/bundle:0.0.3"
testBundleRelatedImageName = "test"
Expand All @@ -40,7 +44,8 @@ var _ = Describe("LocalDir Storage Test", func() {
Expect(err).ToNot(HaveOccurred())
rootDir = d

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

})
Expand Down
1 change: 1 addition & 0 deletions pkg/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ import (
type Instance interface {
Store(catalog string, fsys fs.FS) error
Delete(catalog string) error
ContentURL(catalog string) string
StorageServerHandler() http.Handler
}
7 changes: 3 additions & 4 deletions test/e2e/unpack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package e2e

import (
"context"
"fmt"
"io"
"os"

Expand Down Expand Up @@ -81,8 +80,8 @@ var _ = Describe("Catalog Unpacking", func() {
}).Should(Succeed())

By("Making sure the catalog content is available via the http server")
// (TODO): Get the URL from the CR once https://github.com/operator-framework/catalogd/issues/119 is done
catalogURL := fmt.Sprintf("%s.%s.svc/catalogs/%s/all.json", "catalogd-catalogserver", defaultSystemNamespace, catalogName)
err = c.Get(ctx, types.NamespacedName{Name: catalog.Name}, catalog)
Expect(err).ToNot(HaveOccurred())
job = batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-svr-job",
Expand All @@ -95,7 +94,7 @@ var _ = Describe("Catalog Unpacking", func() {
{
Name: "test-svr",
Image: "curlimages/curl",
Command: []string{"sh", "-c", "curl --silent --show-error --location -o - " + catalogURL},
Command: []string{"sh", "-c", "curl --silent --show-error --location -o - " + catalog.Status.ContentURL},
},
},
RestartPolicy: "Never",
Expand Down

0 comments on commit 20d83c1

Please sign in to comment.