diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f07c1546..0c5162390 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * Update favicon to be the Elastic Package Registry logo. [#858](https://github.com/elastic/package-registry/pull/858) +* Implement proxy mode. [#860](https://github.com/elastic/package-registry/pull/860) ### Deprecated diff --git a/artifacts.go b/artifacts.go index ad1f9a6bc..6adc2648c 100644 --- a/artifacts.go +++ b/artifacts.go @@ -14,6 +14,7 @@ import ( "go.uber.org/zap" "github.com/elastic/package-registry/packages" + "github.com/elastic/package-registry/proxymode" "github.com/elastic/package-registry/util" ) @@ -22,6 +23,10 @@ const artifactsRouterPath = "/epr/{packageName}/{packageName:[a-z0-9_]+}-{packag var errArtifactNotFound = errors.New("artifact not found") func artifactsHandler(indexer Indexer, cacheTime time.Duration) func(w http.ResponseWriter, r *http.Request) { + return artifactsHandlerWithProxyMode(indexer, proxymode.NoProxy(), cacheTime) +} + +func artifactsHandlerWithProxyMode(indexer Indexer, proxyMode *proxymode.ProxyMode, cacheTime time.Duration) func(w http.ResponseWriter, r *http.Request) { logger := util.Logger() return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -44,7 +49,7 @@ func artifactsHandler(indexer Indexer, cacheTime time.Duration) func(w http.Resp } opts := packages.NameVersionFilter(packageName, packageVersion) - packageList, err := indexer.Get(r.Context(), &opts) + pkgs, err := indexer.Get(r.Context(), &opts) if err != nil { logger.Error("getting package path failed", zap.String("package.name", packageName), @@ -53,12 +58,21 @@ func artifactsHandler(indexer Indexer, cacheTime time.Duration) func(w http.Resp http.Error(w, "internal server error", http.StatusInternalServerError) return } - if len(packageList) == 0 { + if len(pkgs) == 0 && proxyMode.Enabled() { + proxiedPackage, err := proxyMode.Package(r) + if err != nil { + logger.Error("proxy mode: package failed", zap.Error(err)) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + pkgs = pkgs.Join(packages.Packages{proxiedPackage}) + } + if len(pkgs) == 0 { notFoundError(w, errArtifactNotFound) return } cacheHeaders(w, cacheTime) - packages.ServePackage(w, r, packageList[0]) + packages.ServePackage(w, r, pkgs[0]) } } diff --git a/categories.go b/categories.go index a1fca0319..045f32de5 100644 --- a/categories.go +++ b/categories.go @@ -13,21 +13,24 @@ import ( "strconv" "time" + "go.uber.org/zap" + "github.com/Masterminds/semver/v3" "go.elastic.co/apm" "github.com/elastic/package-registry/packages" + "github.com/elastic/package-registry/proxymode" "github.com/elastic/package-registry/util" ) -type Category struct { - Id string `yaml:"id" json:"id"` - Title string `yaml:"title" json:"title"` - Count int `yaml:"count" json:"count"` +// categoriesHandler is a dynamic handler as it will also allow filtering in the future. +func categoriesHandler(indexer Indexer, cacheTime time.Duration) func(w http.ResponseWriter, r *http.Request) { + return categoriesHandlerWithProxyMode(indexer, proxymode.NoProxy(), cacheTime) } // categoriesHandler is a dynamic handler as it will also allow filtering in the future. -func categoriesHandler(indexer Indexer, cacheTime time.Duration) func(w http.ResponseWriter, r *http.Request) { +func categoriesHandlerWithProxyMode(indexer Indexer, proxyMode *proxymode.ProxyMode, cacheTime time.Duration) func(w http.ResponseWriter, r *http.Request) { + logger := util.Logger() return func(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() @@ -49,13 +52,33 @@ func categoriesHandler(indexer Indexer, cacheTime time.Duration) func(w http.Res opts := packages.GetOptions{ Filter: filter, } - packages, err := indexer.Get(r.Context(), &opts) + pkgs, err := indexer.Get(r.Context(), &opts) if err != nil { notFoundError(w, err) return } + categories := getCategories(r.Context(), pkgs, includePolicyTemplates) + + if proxyMode.Enabled() { + proxiedCategories, err := proxyMode.Categories(r) + if err != nil { + logger.Error("proxy mode: categories failed", zap.Error(err)) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } - categories := getCategories(r.Context(), packages, includePolicyTemplates) + for _, category := range proxiedCategories { + if _, ok := categories[category.Id]; !ok { + categories[category.Id] = &packages.Category{ + Id: category.Id, + Title: category.Title, + Count: category.Count, + } + } else { + categories[category.Id].Count += category.Count + } + } + } data, err := getCategoriesOutput(r.Context(), categories) if err != nil { @@ -108,16 +131,16 @@ func newCategoriesFilterFromQuery(query url.Values) (*packages.Filter, error) { return &filter, nil } -func getCategories(ctx context.Context, packages packages.Packages, includePolicyTemplates bool) map[string]*Category { +func getCategories(ctx context.Context, pkgs packages.Packages, includePolicyTemplates bool) map[string]*packages.Category { span, ctx := apm.StartSpan(ctx, "FilterCategories", "app") defer span.End() - categories := map[string]*Category{} + categories := map[string]*packages.Category{} - for _, p := range packages { + for _, p := range pkgs { for _, c := range p.Categories { if _, ok := categories[c]; !ok { - categories[c] = &Category{ + categories[c] = &packages.Category{ Id: c, Title: c, Count: 0, @@ -146,7 +169,7 @@ func getCategories(ctx context.Context, packages packages.Packages, includePolic // Add policy template level categories. for _, c := range t.Categories { if _, ok := categories[c]; !ok { - categories[c] = &Category{ + categories[c] = &packages.Category{ Id: c, Title: c, Count: 0, @@ -167,7 +190,7 @@ func getCategories(ctx context.Context, packages packages.Packages, includePolic return categories } -func getCategoriesOutput(ctx context.Context, categories map[string]*Category) ([]byte, error) { +func getCategoriesOutput(ctx context.Context, categories map[string]*packages.Category) ([]byte, error) { span, ctx := apm.StartSpan(ctx, "GetCategoriesOutput", "app") defer span.End() @@ -177,7 +200,7 @@ func getCategoriesOutput(ctx context.Context, categories map[string]*Category) ( } sort.Strings(keys) - var outputCategories []*Category + var outputCategories []*packages.Category for _, k := range keys { c := categories[k] if title, ok := packages.CategoryTitles[c.Title]; ok { diff --git a/main.go b/main.go index 91be9f01c..be1550319 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "context" "flag" "fmt" + "log" "net/http" _ "net/http/pprof" "os" @@ -29,6 +30,7 @@ import ( "github.com/elastic/package-registry/metrics" "github.com/elastic/package-registry/packages" + "github.com/elastic/package-registry/proxymode" "github.com/elastic/package-registry/storage" "github.com/elastic/package-registry/util" ) @@ -57,6 +59,9 @@ var ( storageEndpoint string storageIndexerWatchInterval time.Duration + featureProxyMode bool + proxyTo string + defaultConfig = Config{ CacheTimeIndex: 10 * time.Second, CacheTimeSearch: 10 * time.Minute, @@ -82,6 +87,9 @@ func init() { flag.StringVar(&storageEndpoint, "storage-endpoint", "https://package-storage.elastic.co/", "Package Storage public endpoint.") flag.DurationVar(&storageIndexerWatchInterval, "storage-indexer-watch-interval", 1*time.Minute, "Address of the package-registry service.") + // The following proxy-indexer related flags are technical preview and might be removed in the future or renamed + flag.BoolVar(&featureProxyMode, "feature-proxy-mode", false, "Enable proxy mode to include packages from other endpoint (technical preview).") + flag.StringVar(&proxyTo, "proxy-to", "https://epr-snapshot.elastic.co/", "Proxy-to endpoint") } type Config struct { @@ -180,26 +188,26 @@ func initMetricsServer(logger *zap.Logger) { func initIndexer(ctx context.Context, logger *zap.Logger, config *Config) Indexer { packagesBasePaths := getPackagesBasePaths(config) - var indexer Indexer + var combined CombinedIndexer + if featureStorageIndexer { storageClient, err := gstorage.NewClient(ctx) if err != nil { logger.Fatal("can't initialize storage client", zap.Error(err)) } - indexer = storage.NewIndexer(storageClient, storage.IndexerOptions{ + combined = append(combined, storage.NewIndexer(storageClient, storage.IndexerOptions{ PackageStorageBucketInternal: storageIndexerBucketInternal, PackageStorageEndpoint: storageEndpoint, WatchInterval: storageIndexerWatchInterval, - }) - } else { - indexer = NewCombinedIndexer( - packages.NewZipFileSystemIndexer(packagesBasePaths...), - packages.NewFileSystemIndexer(packagesBasePaths...), - ) + })) } - ensurePackagesAvailable(ctx, logger, indexer) - return indexer + combined = append(combined, + packages.NewZipFileSystemIndexer(packagesBasePaths...), + packages.NewFileSystemIndexer(packagesBasePaths...), + ) + ensurePackagesAvailable(ctx, logger, combined) + return combined } func initServer(logger *zap.Logger, config *Config) *http.Server { @@ -310,8 +318,18 @@ func mustLoadRouter(logger *zap.Logger, config *Config, indexer Indexer) *mux.Ro } func getRouter(logger *zap.Logger, config *Config, indexer Indexer) (*mux.Router, error) { - artifactsHandler := artifactsHandler(indexer, config.CacheTimeCatchAll) - signaturesHandler := signaturesHandler(indexer, config.CacheTimeCatchAll) + if featureProxyMode { + log.Println("Technical preview: Proxy mode is an experimental feature and it may be unstable.") + } + proxyMode, err := proxymode.NewProxyMode(proxymode.ProxyOptions{ + Enabled: featureProxyMode, + ProxyTo: proxyTo, + }) + if err != nil { + return nil, errors.Wrapf(err, "can't create proxy mode") + } + artifactsHandler := artifactsHandlerWithProxyMode(indexer, proxyMode, config.CacheTimeCatchAll) + signaturesHandler := signaturesHandlerWithProxyMode(indexer, proxyMode, config.CacheTimeCatchAll) faviconHandleFunc, err := faviconHandler(config.CacheTimeCatchAll) if err != nil { return nil, err @@ -321,14 +339,16 @@ func getRouter(logger *zap.Logger, config *Config, indexer Indexer) (*mux.Router return nil, err } - packageIndexHandler := packageIndexHandler(indexer, config.CacheTimeCatchAll) - staticHandler := staticHandler(indexer, config.CacheTimeCatchAll) + categoriesHandler := categoriesHandlerWithProxyMode(indexer, proxyMode, config.CacheTimeCategories) + packageIndexHandler := packageIndexHandlerWithProxyMode(indexer, proxyMode, config.CacheTimeCatchAll) + searchHandler := searchHandlerWithProxyMode(indexer, proxyMode, config.CacheTimeSearch) + staticHandler := staticHandlerWithProxyMode(indexer, proxyMode, config.CacheTimeCatchAll) router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/", indexHandlerFunc) router.HandleFunc("/index.json", indexHandlerFunc) - router.HandleFunc("/search", searchHandler(indexer, config.CacheTimeSearch)) - router.HandleFunc("/categories", categoriesHandler(indexer, config.CacheTimeCategories)) + router.HandleFunc("/search", searchHandler) + router.HandleFunc("/categories", categoriesHandler) router.HandleFunc("/health", healthHandler) router.HandleFunc("/favicon.ico", faviconHandleFunc) router.HandleFunc(artifactsRouterPath, artifactsHandler) @@ -339,7 +359,7 @@ func getRouter(logger *zap.Logger, config *Config, indexer Indexer) (*mux.Router if metricsAddress != "" { router.Use(metrics.MetricsMiddleware()) } - router.NotFoundHandler = http.Handler(notFoundHandler(fmt.Errorf("404 page not found"))) + router.NotFoundHandler = notFoundHandler(fmt.Errorf("404 page not found")) return router, nil } diff --git a/package_index.go b/package_index.go index 331fe2c50..b048a1053 100644 --- a/package_index.go +++ b/package_index.go @@ -15,6 +15,7 @@ import ( "github.com/gorilla/mux" "github.com/elastic/package-registry/packages" + "github.com/elastic/package-registry/proxymode" "github.com/elastic/package-registry/util" ) @@ -25,6 +26,10 @@ const ( var errPackageRevisionNotFound = errors.New("package revision not found") func packageIndexHandler(indexer Indexer, cacheTime time.Duration) func(w http.ResponseWriter, r *http.Request) { + return packageIndexHandlerWithProxyMode(indexer, proxymode.NoProxy(), cacheTime) +} + +func packageIndexHandlerWithProxyMode(indexer Indexer, proxyMode *proxymode.ProxyMode, cacheTime time.Duration) func(w http.ResponseWriter, r *http.Request) { logger := util.Logger() return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -47,13 +52,22 @@ func packageIndexHandler(indexer Indexer, cacheTime time.Duration) func(w http.R } opts := packages.NameVersionFilter(packageName, packageVersion) - packages, err := indexer.Get(r.Context(), &opts) + pkgs, err := indexer.Get(r.Context(), &opts) if err != nil { logger.Error("getting package path failed", zap.Error(err)) http.Error(w, "internal server error", http.StatusInternalServerError) return } - if len(packages) == 0 { + if len(pkgs) == 0 && proxyMode.Enabled() { + proxiedPackage, err := proxyMode.Package(r) + if err != nil { + logger.Error("proxy mode: package failed", zap.Error(err)) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + pkgs = pkgs.Join(packages.Packages{proxiedPackage}) + } + if len(pkgs) == 0 { notFoundError(w, errPackageRevisionNotFound) return } @@ -61,10 +75,10 @@ func packageIndexHandler(indexer Indexer, cacheTime time.Duration) func(w http.R w.Header().Set("Content-Type", "application/json") cacheHeaders(w, cacheTime) - err = util.WriteJSONPretty(w, packages[0]) + err = util.WriteJSONPretty(w, pkgs[0]) if err != nil { logger.Error("marshaling package index failed", - zap.String("package.path", packages[0].BasePath), + zap.String("package.path", pkgs[0].BasePath), zap.Error(err)) http.Error(w, "internal server error", http.StatusInternalServerError) return diff --git a/packages/category.go b/packages/category.go new file mode 100644 index 000000000..1b8720067 --- /dev/null +++ b/packages/category.go @@ -0,0 +1,11 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package packages + +type Category struct { + Id string `yaml:"id" json:"id"` + Title string `yaml:"title" json:"title"` + Count int `yaml:"count" json:"count"` +} diff --git a/proxymode/proxymode.go b/proxymode/proxymode.go new file mode 100644 index 000000000..46ed3f596 --- /dev/null +++ b/proxymode/proxymode.go @@ -0,0 +1,165 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package proxymode + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/gorilla/mux" + "github.com/pkg/errors" + "go.uber.org/zap" + + "github.com/elastic/package-registry/packages" + "github.com/elastic/package-registry/util" +) + +type ProxyMode struct { + options ProxyOptions + + httpClient *http.Client + destinationURL *url.URL + resolver *proxyResolver +} + +type ProxyOptions struct { + Enabled bool + ProxyTo string +} + +func NoProxy() *ProxyMode { + proxyMode, err := NewProxyMode(ProxyOptions{Enabled: false}) + if err != nil { + panic(errors.Wrapf(err, "unexpected error")) + } + return proxyMode +} + +func NewProxyMode(options ProxyOptions) (*ProxyMode, error) { + var pm ProxyMode + pm.options = options + + if !options.Enabled { + return &pm, nil + } + + pm.httpClient = &http.Client{ + Timeout: 10 * time.Second, + Transport: &http.Transport{ + MaxIdleConns: 100, + MaxIdleConnsPerHost: 100, + IdleConnTimeout: 90 * time.Second, + }, + } + + var err error + pm.destinationURL, err = url.Parse(pm.options.ProxyTo) + if err != nil { + return nil, errors.Wrap(err, "can't create proxy destination URL") + } + + pm.resolver = &proxyResolver{destinationURL: *pm.destinationURL} + return &pm, nil +} + +func (pm *ProxyMode) Enabled() bool { + return pm.options.Enabled +} + +func (pm *ProxyMode) Search(r *http.Request) (packages.Packages, error) { + logger := util.Logger() + + proxyURL := *r.URL + proxyURL.Host = pm.destinationURL.Host + proxyURL.Scheme = pm.destinationURL.Scheme + proxyURL.User = pm.destinationURL.User + + proxyRequest, err := http.NewRequest(http.MethodGet, proxyURL.String(), nil) + if err != nil { + return nil, errors.Wrap(err, "can't create proxy request") + } + + logger.Debug("Proxy /search request", zap.String("request.uri", proxyURL.String())) + response, err := pm.httpClient.Do(proxyRequest) + if err != nil { + return nil, errors.Wrap(err, "can't proxy search request") + } + defer response.Body.Close() + var pkgs packages.Packages + err = json.NewDecoder(response.Body).Decode(&pkgs) + if err != nil { + return nil, errors.Wrap(err, "can't proxy search request") + } + for i := 0; i < len(pkgs); i++ { + pkgs[i].SetRemoteResolver(pm.resolver) + } + return pkgs, nil +} + +func (pm *ProxyMode) Categories(r *http.Request) ([]packages.Category, error) { + logger := util.Logger() + + proxyURL := *r.URL + proxyURL.Host = pm.destinationURL.Host + proxyURL.Scheme = pm.destinationURL.Scheme + proxyURL.User = pm.destinationURL.User + + proxyRequest, err := http.NewRequest(http.MethodGet, proxyURL.String(), nil) + if err != nil { + return nil, errors.Wrap(err, "can't create proxy request") + } + + logger.Debug("Proxy /categories request", zap.String("request.uri", proxyURL.String())) + response, err := pm.httpClient.Do(proxyRequest) + if err != nil { + return nil, errors.Wrap(err, "can't proxy search request") + } + defer response.Body.Close() + var cats []packages.Category + err = json.NewDecoder(response.Body).Decode(&cats) + if err != nil { + return nil, errors.Wrap(err, "can't proxy search request") + } + return cats, nil +} + +func (pm *ProxyMode) Package(r *http.Request) (*packages.Package, error) { + logger := util.Logger() + + vars := mux.Vars(r) + packageName, ok := vars["packageName"] + if !ok { + return nil, errors.New("missing package name") + } + + packageVersion, ok := vars["packageVersion"] + if !ok { + return nil, errors.New("missing package version") + } + + urlPath := fmt.Sprintf("/package/%s/%s/", packageName, packageVersion) + proxyURL := pm.destinationURL.ResolveReference(&url.URL{Path: urlPath}) + proxyRequest, err := http.NewRequest(http.MethodGet, proxyURL.String(), nil) + if err != nil { + return nil, errors.Wrap(err, "can't create proxy request") + } + + logger.Debug("Proxy /package request", zap.String("request.uri", proxyURL.String())) + response, err := pm.httpClient.Do(proxyRequest) + if err != nil { + return nil, errors.Wrap(err, "can't proxy search request") + } + defer response.Body.Close() + var pkg packages.Package + err = json.NewDecoder(response.Body).Decode(&pkg) + if err != nil { + return nil, errors.Wrap(err, "can't proxy search request") + } + pkg.SetRemoteResolver(pm.resolver) + return &pkg, nil +} diff --git a/proxymode/resolver.go b/proxymode/resolver.go new file mode 100644 index 000000000..f29b4fed5 --- /dev/null +++ b/proxymode/resolver.go @@ -0,0 +1,40 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package proxymode + +import ( + "fmt" + "net/http" + "net/url" + + "github.com/elastic/package-registry/packages" +) + +type proxyResolver struct { + destinationURL url.URL +} + +func (pr proxyResolver) RedirectArtifactsHandler(w http.ResponseWriter, r *http.Request, p *packages.Package) { + remotePath := fmt.Sprintf("/epr/package/%s-%s.zip", p.Name, p.Version) + anURL := pr.destinationURL. + ResolveReference(&url.URL{Path: remotePath}) + http.Redirect(w, r, anURL.String(), http.StatusMovedPermanently) +} + +func (pr proxyResolver) RedirectStaticHandler(w http.ResponseWriter, r *http.Request, p *packages.Package, resourcePath string) { + remotePath := fmt.Sprintf("/package/%s/%s/%s", p.Name, p.Version, resourcePath) + staticURL := pr.destinationURL. + ResolveReference(&url.URL{Path: remotePath}) + http.Redirect(w, r, staticURL.String(), http.StatusMovedPermanently) +} + +func (pr proxyResolver) RedirectSignaturesHandler(w http.ResponseWriter, r *http.Request, p *packages.Package) { + remotePath := fmt.Sprintf("/epr/package/%s-%s.zip.sig", p.Name, p.Version) + anURL := pr.destinationURL. + ResolveReference(&url.URL{Path: remotePath}) + http.Redirect(w, r, anURL.String(), http.StatusMovedPermanently) +} + +var _ packages.RemoteResolver = new(proxyResolver) diff --git a/search.go b/search.go index 2095e3ac0..6e67df451 100644 --- a/search.go +++ b/search.go @@ -16,12 +16,19 @@ import ( "github.com/Masterminds/semver/v3" "github.com/pkg/errors" "go.elastic.co/apm" + "go.uber.org/zap" "github.com/elastic/package-registry/packages" + "github.com/elastic/package-registry/proxymode" "github.com/elastic/package-registry/util" ) func searchHandler(indexer Indexer, cacheTime time.Duration) func(w http.ResponseWriter, r *http.Request) { + return searchHandlerWithProxyMode(indexer, proxymode.NoProxy(), cacheTime) +} + +func searchHandlerWithProxyMode(indexer Indexer, proxyMode *proxymode.ProxyMode, cacheTime time.Duration) func(w http.ResponseWriter, r *http.Request) { + logger := util.Logger() return func(w http.ResponseWriter, r *http.Request) { filter, err := newSearchFilterFromQuery(r.URL.Query()) if err != nil { @@ -38,6 +45,16 @@ func searchHandler(indexer Indexer, cacheTime time.Duration) func(w http.Respons return } + if proxyMode.Enabled() { + proxiedPackages, err := proxyMode.Search(r) + if err != nil { + logger.Error("proxy mode: search failed", zap.Error(err)) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + packages = packages.Join(proxiedPackages) + } + data, err := getPackageOutput(r.Context(), packages) if err != nil { notFoundError(w, err) diff --git a/signatures.go b/signatures.go index 6a4570d42..946b58b93 100644 --- a/signatures.go +++ b/signatures.go @@ -14,6 +14,7 @@ import ( "go.uber.org/zap" "github.com/elastic/package-registry/packages" + "github.com/elastic/package-registry/proxymode" "github.com/elastic/package-registry/util" ) @@ -22,6 +23,10 @@ const signaturesRouterPath = "/epr/{packageName}/{packageName:[a-z0-9_]+}-{packa var errSignatureFileNotFound = errors.New("signature file not found") func signaturesHandler(indexer Indexer, cacheTime time.Duration) func(w http.ResponseWriter, r *http.Request) { + return signaturesHandlerWithProxyMode(indexer, proxymode.NoProxy(), cacheTime) +} + +func signaturesHandlerWithProxyMode(indexer Indexer, proxyMode *proxymode.ProxyMode, cacheTime time.Duration) func(w http.ResponseWriter, r *http.Request) { logger := util.Logger() return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -44,7 +49,7 @@ func signaturesHandler(indexer Indexer, cacheTime time.Duration) func(w http.Res } opts := packages.NameVersionFilter(packageName, packageVersion) - packageList, err := indexer.Get(r.Context(), &opts) + pkgs, err := indexer.Get(r.Context(), &opts) if err != nil { logger.Error("getting package path failed", zap.String("package.name", packageName), @@ -53,12 +58,21 @@ func signaturesHandler(indexer Indexer, cacheTime time.Duration) func(w http.Res http.Error(w, "internal server error", http.StatusInternalServerError) return } - if len(packageList) == 0 { + if len(pkgs) == 0 && proxyMode.Enabled() { + proxiedPackage, err := proxyMode.Package(r) + if err != nil { + logger.Error("proxy mode: package failed", zap.Error(err)) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + pkgs = pkgs.Join(packages.Packages{proxiedPackage}) + } + if len(pkgs) == 0 { notFoundError(w, errSignatureFileNotFound) return } cacheHeaders(w, cacheTime) - packages.ServePackageSignature(w, r, packageList[0]) + packages.ServePackageSignature(w, r, pkgs[0]) } } diff --git a/static.go b/static.go index 4d14dab82..63e397459 100644 --- a/static.go +++ b/static.go @@ -14,6 +14,7 @@ import ( "go.uber.org/zap" "github.com/elastic/package-registry/packages" + "github.com/elastic/package-registry/proxymode" "github.com/elastic/package-registry/util" ) @@ -26,6 +27,10 @@ type staticParams struct { } func staticHandler(indexer Indexer, cacheTime time.Duration) http.HandlerFunc { + return staticHandlerWithProxyMode(indexer, proxymode.NoProxy(), cacheTime) +} + +func staticHandlerWithProxyMode(indexer Indexer, proxyMode *proxymode.ProxyMode, cacheTime time.Duration) http.HandlerFunc { logger := util.Logger() return func(w http.ResponseWriter, r *http.Request) { params, err := staticParamsFromRequest(r) @@ -35,7 +40,7 @@ func staticHandler(indexer Indexer, cacheTime time.Duration) http.HandlerFunc { } opts := packages.NameVersionFilter(params.packageName, params.packageVersion) - packageList, err := indexer.Get(r.Context(), &opts) + pkgs, err := indexer.Get(r.Context(), &opts) if err != nil { logger.Error("getting package path failed", zap.String("package.name", params.packageName), @@ -44,13 +49,22 @@ func staticHandler(indexer Indexer, cacheTime time.Duration) http.HandlerFunc { http.Error(w, "internal server error", http.StatusInternalServerError) return } - if len(packageList) == 0 { + if len(pkgs) == 0 && proxyMode.Enabled() { + proxiedPackage, err := proxyMode.Package(r) + if err != nil { + logger.Error("proxy mode: package failed", zap.Error(err)) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + pkgs = pkgs.Join(packages.Packages{proxiedPackage}) + } + if len(pkgs) == 0 { notFoundError(w, errPackageRevisionNotFound) return } cacheHeaders(w, cacheTime) - packages.ServePackageResource(w, r, packageList[0], params.fileName) + packages.ServePackageResource(w, r, pkgs[0], params.fileName) } }