Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy upstream support #60

Merged
merged 13 commits into from
Jun 11, 2020
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version: 2.1
executors:
build:
docker:
- image: circleci/golang:1.13
- image: circleci/golang:1.14
deploy:
docker:
- image: circleci/node:8.10
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ BUILD_HASH_SHORT = $(shell git rev-parse --short HEAD)
LDFLAGS += -X "github.com/mattermost/mattermost-marketplace/internal/api.buildTag=$(BUILD_TAG)"
LDFLAGS += -X "github.com/mattermost/mattermost-marketplace/internal/api.buildHash=$(BUILD_HASH)"
LDFLAGS += -X "github.com/mattermost/mattermost-marketplace/internal/api.buildHashShort=$(BUILD_HASH_SHORT)"
LDFLAGS += -X "main.upstreamURL=$(BUILD_UPSTREAM_URL)"
SLS_STAGE ?= "dev"

## Checks the code style, tests, builds and bundles.
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ Running all tests:
$ make test
```

### Proxying upstream

The marketplace can be configured to proxy to an upstream marketplace, overlaying any locally defined plugins on top of the remote service. Invoke the server with the appropriate flag:

```
go run ./cmd/marketplace server --upstream https://api.integrations.mattermost.com
```

To compile this flag into the binary such as when building the lambda function, define the appropriate environment variable:
```
export BUILD_UPSTREAM_URL=https://api.integrations.mattermost.com
make build-lambda
```

### Updating plugins.json

To fetch all new release from GitHub, run
Expand Down
24 changes: 20 additions & 4 deletions cmd/lambda/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import (
"github.com/mattermost/mattermost-marketplace/internal/store"
)

var (
// upstreamURL may be compiled into the binary by defining $BUILD_UPSTREAM_URL
upstreamURL = ""
)

var logger *logrus.Logger

func main() {
Expand All @@ -22,7 +27,7 @@ func main() {
}
}

func newStatikStore(statikPath string, logger logrus.FieldLogger) (*store.Store, error) {
func newStatikStore(statikPath string, logger logrus.FieldLogger) (*store.StaticStore, error) {
statikFS, err := fs.New()
if err != nil {
return nil, errors.Wrap(err, "failed to open statik fileystem")
Expand All @@ -34,7 +39,7 @@ func newStatikStore(statikPath string, logger logrus.FieldLogger) (*store.Store,
}
defer database.Close()

statikStore, err := store.New(database, logger)
statikStore, err := store.NewStaticFromReader(database, logger)
if err != nil {
return nil, errors.Wrap(err, "failed to initialize store")
}
Expand All @@ -45,14 +50,25 @@ func newStatikStore(statikPath string, logger logrus.FieldLogger) (*store.Store,
func listenAndServe() error {
logger = logrus.New()

statikStore, err := newStatikStore("/plugins.json", logger)
var apiStore store.Store
var err error
apiStore, err = newStatikStore("/plugins.json", logger)
if err != nil {
return err
}

if upstreamURL != "" {
upstreamStore, err := store.NewProxy(upstreamURL, logger)
if err != nil {
return errors.Wrap(err, "failed to initialize upstream store")
}

apiStore = store.NewMerged(logger, apiStore, upstreamStore)
}

router := mux.NewRouter()
api.Register(router, &api.Context{
Store: statikStore,
Store: apiStore,
Logger: logger,
})

Expand Down
27 changes: 24 additions & 3 deletions cmd/marketplace/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ import (
"github.com/mattermost/mattermost-marketplace/internal/store"
)

var instanceID string
var (
instanceID string

// upstreamURL may be compiled into the binary by defining $BUILD_UPSTREAM_URL
upstreamURL string
)

func init() {
instanceID = model.NewId()

serverCmd.PersistentFlags().String("database", "plugins.json", "The read-only JSON file backing the server.")
serverCmd.PersistentFlags().String("listen", ":8085", "The interface and port on which to listen.")
serverCmd.PersistentFlags().String("upstream", upstreamURL, "An upstream marketplace server with which to merge results.")
serverCmd.PersistentFlags().Bool("debug", false, "Whether to output debug logs.")
}

Expand All @@ -46,18 +52,33 @@ var serverCmd = &cobra.Command{
}
defer databaseFile.Close()

fileStore, err := store.New(databaseFile, logger)
var apiStore store.Store

apiStore, err = store.NewStaticFromReader(databaseFile, logger)
if err != nil {
return errors.Wrap(err, "failed to initialize store")
}

upstreamURL, _ := command.Flags().GetString("upstream")
if upstreamURL != "" {
var upstreamStore *store.Proxy
upstreamStore, err = store.NewProxy(upstreamURL, logger)
if err != nil {
return errors.Wrap(err, "failed to initialize upstream store")
}

logger.WithField("upstream", upstreamURL).Info("Proxying to upstream marketplace")

apiStore = store.NewMerged(logger, apiStore, upstreamStore)
}

logger := logger.WithField("instance", instanceID)
logger.Info("Starting Plugin Marketplace")

router := mux.NewRouter()

api.Register(router, &api.Context{
Store: fileStore,
Store: apiStore,
Logger: logger,
})

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/mattermost/mattermost-marketplace

go 1.12
go 1.14

require (
github.com/akrylysov/algnhsa v0.0.0-20190319020909-05b3d192e9a7
Expand Down
2 changes: 1 addition & 1 deletion internal/api/plugins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func setupAPI(t *testing.T, plugins []*model.Plugin) (*api.Client, func()) {

data, err := json.Marshal(plugins)
require.NoError(t, err)
store, err := store.New(bytes.NewReader(data), logger)
store, err := store.NewStaticFromReader(bytes.NewReader(data), logger)
require.NoError(t, err)

router := mux.NewRouter()
Expand Down
56 changes: 56 additions & 0 deletions internal/store/merged.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package store

import (
"github.com/pkg/errors"
"github.com/sirupsen/logrus"

"github.com/mattermost/mattermost-marketplace/internal/model"
)

// Merged is a store that merges the results of multiple stores together.
//
// If a plugin is present in multiple stores, the later version is preferred. If a plugin with
// the same version is present in multiple stores, the one from the later store (as initialized)
// is preferred.
type Merged struct {
stores []Store
logger logrus.FieldLogger
}

// NewMerged creates a new instance of the merged store.
func NewMerged(logger logrus.FieldLogger, stores ...Store) *Merged {
return &Merged{
stores: stores,
logger: logger,
}
}

// GetPlugins fetches the given page of plugins. The first page is 0.
func (store *Merged) GetPlugins(pluginFilter *model.PluginFilter) ([]*model.Plugin, error) {
// Short-circuit if only one store is configured.
if len(store.stores) == 1 {
return store.stores[0].GetPlugins(pluginFilter)
}

plugins := []*model.Plugin{}
for i, store := range store.stores {
storePlugins, err := store.GetPlugins(&model.PluginFilter{
Page: 0,
PerPage: model.AllPerPage,
Filter: pluginFilter.Filter,
ServerVersion: pluginFilter.ServerVersion,
})
if err != nil {
return nil, errors.Wrapf(err, "failed to query store %d", i)
}

plugins = append(plugins, storePlugins...)
}

staticStore, err := NewStatic(plugins, store.logger)
if err != nil {
return nil, errors.Wrap(err, "failed to initialize static store")
}

return staticStore.GetPlugins(pluginFilter)
}
Loading