Skip to content
This repository has been archived by the owner on Mar 3, 2025. It is now read-only.

(server) Expose content URL on CR status #168

Merged
merged 1 commit into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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