feat(module,app): basic module to list apps
This commit is contained in:
parent
07452ad8ab
commit
ed535b6f5d
|
@ -1,7 +1,9 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -13,6 +15,8 @@ import (
|
||||||
"forge.cadoles.com/arcad/edge/pkg/bus/memory"
|
"forge.cadoles.com/arcad/edge/pkg/bus/memory"
|
||||||
appHTTP "forge.cadoles.com/arcad/edge/pkg/http"
|
appHTTP "forge.cadoles.com/arcad/edge/pkg/http"
|
||||||
"forge.cadoles.com/arcad/edge/pkg/module"
|
"forge.cadoles.com/arcad/edge/pkg/module"
|
||||||
|
appModule "forge.cadoles.com/arcad/edge/pkg/module/app"
|
||||||
|
appModuleMemory "forge.cadoles.com/arcad/edge/pkg/module/app/memory"
|
||||||
"forge.cadoles.com/arcad/edge/pkg/module/auth"
|
"forge.cadoles.com/arcad/edge/pkg/module/auth"
|
||||||
authHTTP "forge.cadoles.com/arcad/edge/pkg/module/auth/http"
|
authHTTP "forge.cadoles.com/arcad/edge/pkg/module/auth/http"
|
||||||
"forge.cadoles.com/arcad/edge/pkg/module/blob"
|
"forge.cadoles.com/arcad/edge/pkg/module/blob"
|
||||||
|
@ -106,6 +110,10 @@ func RunCommand() *cli.Command {
|
||||||
|
|
||||||
storageFile := injectAppID(ctx.String("storage-file"), manifest.ID)
|
storageFile := injectAppID(ctx.String("storage-file"), manifest.ID)
|
||||||
|
|
||||||
|
if err := ensureDir(storageFile); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
db, err := sqlite.Open(storageFile)
|
db, err := sqlite.Open(storageFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
|
@ -117,7 +125,7 @@ func RunCommand() *cli.Command {
|
||||||
|
|
||||||
handler := appHTTP.NewHandler(
|
handler := appHTTP.NewHandler(
|
||||||
appHTTP.WithBus(bus),
|
appHTTP.WithBus(bus),
|
||||||
appHTTP.WithServerModules(getServerModules(bus, ds, bs)...),
|
appHTTP.WithServerModules(getServerModules(bus, ds, bs, manifest, address)...),
|
||||||
)
|
)
|
||||||
if err := handler.Load(bundle); err != nil {
|
if err := handler.Load(bundle); err != nil {
|
||||||
return errors.Wrap(err, "could not load app bundle")
|
return errors.Wrap(err, "could not load app bundle")
|
||||||
|
@ -158,7 +166,7 @@ func RunCommand() *cli.Command {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServerModules(bus bus.Bus, ds storage.DocumentStore, bs storage.BlobStore) []app.ServerModuleFactory {
|
func getServerModules(bus bus.Bus, ds storage.DocumentStore, bs storage.BlobStore, manifest *app.Manifest, address string) []app.ServerModuleFactory {
|
||||||
return []app.ServerModuleFactory{
|
return []app.ServerModuleFactory{
|
||||||
module.ContextModuleFactory(),
|
module.ContextModuleFactory(),
|
||||||
module.ConsoleModuleFactory(),
|
module.ConsoleModuleFactory(),
|
||||||
|
@ -190,6 +198,16 @@ func getServerModules(bus bus.Bus, ds storage.DocumentStore, bs storage.BlobStor
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
appModule.ModuleFactory(appModuleMemory.NewRepository(
|
||||||
|
func(ctx context.Context, i app.ID) (string, error) {
|
||||||
|
if strings.HasPrefix(address, ":") {
|
||||||
|
address = "0.0.0.0" + address
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("http://%s", address), nil
|
||||||
|
},
|
||||||
|
manifest,
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ function onInit() {
|
||||||
|
|
||||||
Listes des modules disponibles côté serveur.
|
Listes des modules disponibles côté serveur.
|
||||||
|
|
||||||
|
- [`app`](./app.md)
|
||||||
- [`auth`](./auth.md)
|
- [`auth`](./auth.md)
|
||||||
- [`blob`](./blob.md)
|
- [`blob`](./blob.md)
|
||||||
- [`cast`](./cast.md)
|
- [`cast`](./cast.md)
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Module `app`
|
||||||
|
|
||||||
|
Ce module permet de récupérer des informations sur les applications actives dans l'environnement Edge courant.
|
||||||
|
|
||||||
|
## Méthodes
|
||||||
|
|
||||||
|
### `app.list(ctx: Context): []Manifest`
|
||||||
|
|
||||||
|
Récupère la liste des applications actives.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
- `ctx` **Context** Le contexte d'exécution. Voir la documentation du module [`context`](./context.md)
|
||||||
|
|
||||||
|
#### Valeur de retour
|
||||||
|
|
||||||
|
Liste des objets `Manifest` décrivant chaque application active.
|
||||||
|
|
||||||
|
### `app.get(ctx: Context, appId: string): Manifest`
|
||||||
|
|
||||||
|
Récupère les informations de l'application identifiée par `appId`.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
- `ctx` **Context** Le contexte d'exécution. Voir la documentation du module [`context`](./context.md)
|
||||||
|
- `appId` **string** Identifiant de l'application
|
||||||
|
|
||||||
|
#### Valeur de retour
|
||||||
|
|
||||||
|
Objet `Manifest` associé à l'application, ou `null` si aucune application n'a été trouvée correspondant à l'identifiant.
|
||||||
|
|
||||||
|
### `app.getUrl(ctx: Context, appId: string): Manifest`
|
||||||
|
|
||||||
|
Retourne l'URL permettant d'accéder à l'application identifiée par `appId`.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
- `ctx` **Context** Le contexte d'exécution. Voir la documentation du module [`context`](./context.md)
|
||||||
|
- `appId` **string** Identifiant de l'application
|
||||||
|
|
||||||
|
#### Valeur de retour
|
||||||
|
|
||||||
|
URL associée à l'application ou `null`, ou `null` si aucune application n'a été trouvée correspondant à l'identifiant.
|
||||||
|
|
||||||
|
## Objets
|
||||||
|
|
||||||
|
### `Manifest`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Manifest {
|
||||||
|
id: string // Identifiant de l'application
|
||||||
|
version: string // Version de l'application
|
||||||
|
title: string // Titre associé à l'application
|
||||||
|
description: string // Description associée à l'application
|
||||||
|
tags: string[] // Mots clés associés à l'application
|
||||||
|
}
|
||||||
|
```
|
|
@ -25,6 +25,7 @@
|
||||||
<script src="/test/net-module.js"></script>
|
<script src="/test/net-module.js"></script>
|
||||||
<script src="/test/rpc-module.js"></script>
|
<script src="/test/rpc-module.js"></script>
|
||||||
<script src="/test/file-module.js"></script>
|
<script src="/test/file-module.js"></script>
|
||||||
|
<script src="/test/app-module.js"></script>
|
||||||
<script class="mocha-exec">
|
<script class="mocha-exec">
|
||||||
mocha.run();
|
mocha.run();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
describe('App Module', function() {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
return Edge.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
Edge.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list apps', function() {
|
||||||
|
return Edge.rpc("listApps")
|
||||||
|
.then(apps => {
|
||||||
|
console.log("listApps result:", apps);
|
||||||
|
chai.assert.isNotNull(apps);
|
||||||
|
chai.assert.isAtLeast(apps.length, 1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve requested app', function() {
|
||||||
|
return Edge.rpc("getApp", { appId: "edge.sdk.client.test" })
|
||||||
|
.then(app => {
|
||||||
|
console.log("getApp result:", app);
|
||||||
|
chai.assert.isNotNull(app);
|
||||||
|
chai.assert.equal(app.id, "edge.sdk.client.test");
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve requested app url', function() {
|
||||||
|
return Edge.rpc("getAppUrl", { appId: "edge.sdk.client.test" })
|
||||||
|
.then(url => {
|
||||||
|
console.log("getAppUrl result:", url);
|
||||||
|
chai.assert.isNotEmpty(url);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -11,6 +11,10 @@ function onInit() {
|
||||||
rpc.register("reset", reset);
|
rpc.register("reset", reset);
|
||||||
rpc.register("total", total);
|
rpc.register("total", total);
|
||||||
rpc.register("getUserInfo", getUserInfo);
|
rpc.register("getUserInfo", getUserInfo);
|
||||||
|
|
||||||
|
rpc.register("listApps");
|
||||||
|
rpc.register("getApp");
|
||||||
|
rpc.register("getAppUrl");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called for each client message
|
// Called for each client message
|
||||||
|
@ -80,3 +84,17 @@ function getUserInfo(ctx, params) {
|
||||||
preferredUsername: preferredUsername,
|
preferredUsername: preferredUsername,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function listApps(ctx) {
|
||||||
|
return app.list(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getApp(ctx, params) {
|
||||||
|
var appId = params.appId;
|
||||||
|
return app.get(ctx, appId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAppUrl(ctx, params) {
|
||||||
|
var appId = params.appId;
|
||||||
|
return app.getUrl(ctx, appId);
|
||||||
|
}
|
|
@ -9,11 +9,11 @@ import (
|
||||||
type ID string
|
type ID string
|
||||||
|
|
||||||
type Manifest struct {
|
type Manifest struct {
|
||||||
ID ID `yaml:"id"`
|
ID ID `yaml:"id" json:"id"`
|
||||||
Version string `yaml:"version"`
|
Version string `yaml:"version" json:"version"`
|
||||||
Title string `yaml:"title"`
|
Title string `yaml:"title" json:"title"`
|
||||||
Description string `yaml:"description"`
|
Description string `yaml:"description" json:"description"`
|
||||||
Tags []string `yaml:"tags"`
|
Tags []string `yaml:"tags" json:"tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadManifest(b bundle.Bundle) (*Manifest, error) {
|
func LoadManifest(b bundle.Bundle) (*Manifest, error) {
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var ErrNotFound = errors.New("not found")
|
|
@ -0,0 +1,58 @@
|
||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/module"
|
||||||
|
appModule "forge.cadoles.com/arcad/edge/pkg/module/app"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppModuleWithMemoryRepository(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
server := app.NewServer(
|
||||||
|
module.ContextModuleFactory(),
|
||||||
|
module.ConsoleModuleFactory(),
|
||||||
|
appModule.ModuleFactory(NewRepository(
|
||||||
|
func(ctx context.Context, id app.ID) (string, error) {
|
||||||
|
return fmt.Sprintf("http//%s.example.com", id), nil
|
||||||
|
},
|
||||||
|
&app.Manifest{
|
||||||
|
ID: "dummy1.arcad.app",
|
||||||
|
Version: "0.0.0",
|
||||||
|
Title: "Dummy 1",
|
||||||
|
Description: "Dummy App 1",
|
||||||
|
Tags: []string{"dummy", "first"},
|
||||||
|
},
|
||||||
|
&app.Manifest{
|
||||||
|
ID: "dummy2.arcad.app",
|
||||||
|
Version: "0.0.0",
|
||||||
|
Title: "Dummy 2",
|
||||||
|
Description: "Dummy App 2",
|
||||||
|
Tags: []string{"dummy", "second"},
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
|
file := "testdata/app.js"
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := server.Load(file, string(data)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer server.Stop()
|
||||||
|
|
||||||
|
if err := server.Start(); err != nil {
|
||||||
|
t.Fatalf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||||
|
module "forge.cadoles.com/arcad/edge/pkg/module/app"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetURLFunc func(context.Context, app.ID) (string, error)
|
||||||
|
|
||||||
|
type Repository struct {
|
||||||
|
getURL GetURLFunc
|
||||||
|
apps []*app.Manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetURL implements app.Repository
|
||||||
|
func (r *Repository) GetURL(ctx context.Context, id app.ID) (string, error) {
|
||||||
|
url, err := r.getURL(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return url, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements app.Repository
|
||||||
|
func (r *Repository) Get(ctx context.Context, id app.ID) (*app.Manifest, error) {
|
||||||
|
for _, app := range r.apps {
|
||||||
|
if app.ID != id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return app, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, module.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// List implements app.Repository
|
||||||
|
func (r *Repository) List(ctx context.Context) ([]*app.Manifest, error) {
|
||||||
|
return r.apps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRepository(getURL GetURLFunc, manifests ...*app.Manifest) *Repository {
|
||||||
|
return &Repository{getURL, manifests}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ module.Repository = &Repository{}
|
|
@ -0,0 +1,17 @@
|
||||||
|
var ctx = context.new();
|
||||||
|
|
||||||
|
var manifests = app.list(ctx);
|
||||||
|
|
||||||
|
if (manifests.length !== 2) {
|
||||||
|
throw new Error("apps.length: expected '2', got '"+manifests.length+"'");
|
||||||
|
}
|
||||||
|
|
||||||
|
var manifest = app.get(ctx, 'dummy2.arcad.app');
|
||||||
|
|
||||||
|
if (!manifest) {
|
||||||
|
throw new Error("manifest should not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifest.id !== "dummy2.arcad.app") {
|
||||||
|
throw new Error("manifest.id: expected 'dummy2.arcad.app', got '"+manifest.id+"'");
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/module/util"
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Module struct {
|
||||||
|
repository Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
type gojaManifest struct {
|
||||||
|
ID string `goja:"id" json:"id"`
|
||||||
|
Version string `goja:"version" json:"version"`
|
||||||
|
Title string `goja:"title" json:"title"`
|
||||||
|
Description string `goja:"description" json:"description"`
|
||||||
|
Tags []string `goja:"tags" json:"tags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGojaManifest(manifest *app.Manifest) *gojaManifest {
|
||||||
|
return &gojaManifest{
|
||||||
|
ID: string(manifest.ID),
|
||||||
|
Version: manifest.Version,
|
||||||
|
Title: manifest.Title,
|
||||||
|
Description: manifest.Description,
|
||||||
|
Tags: manifest.Tags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGojaManifests(manifests []*app.Manifest) []*gojaManifest {
|
||||||
|
gojaManifests := make([]*gojaManifest, len(manifests))
|
||||||
|
|
||||||
|
for i, m := range manifests {
|
||||||
|
gojaManifests[i] = toGojaManifest(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gojaManifests
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) Name() string {
|
||||||
|
return "app"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) Export(export *goja.Object) {
|
||||||
|
if err := export.Set("list", m.list); err != nil {
|
||||||
|
panic(errors.Wrap(err, "could not set 'list' function"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := export.Set("get", m.get); err != nil {
|
||||||
|
panic(errors.Wrap(err, "could not set 'get' function"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := export.Set("getUrl", m.getURL); err != nil {
|
||||||
|
panic(errors.Wrap(err, "could not set 'list' function"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) list(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
||||||
|
ctx := util.AssertContext(call.Argument(0), rt)
|
||||||
|
|
||||||
|
manifests, err := m.repository.List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(rt.ToValue(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return rt.ToValue(toGojaManifests(manifests))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) get(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
||||||
|
ctx := util.AssertContext(call.Argument(0), rt)
|
||||||
|
appID := assertAppID(call.Argument(1), rt)
|
||||||
|
|
||||||
|
manifest, err := m.repository.Get(ctx, appID)
|
||||||
|
if err != nil {
|
||||||
|
panic(rt.ToValue(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return rt.ToValue(toGojaManifest(manifest))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) getURL(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
||||||
|
ctx := util.AssertContext(call.Argument(0), rt)
|
||||||
|
appID := assertAppID(call.Argument(1), rt)
|
||||||
|
|
||||||
|
url, err := m.repository.GetURL(ctx, appID)
|
||||||
|
if err != nil {
|
||||||
|
panic(rt.ToValue(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return rt.ToValue(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ModuleFactory(repository Repository) app.ServerModuleFactory {
|
||||||
|
return func(server *app.Server) app.ServerModule {
|
||||||
|
return &Module{
|
||||||
|
repository: repository,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertAppID(value goja.Value, rt *goja.Runtime) app.ID {
|
||||||
|
appID, ok := value.Export().(app.ID)
|
||||||
|
if !ok {
|
||||||
|
rawAppID, ok := value.Export().(string)
|
||||||
|
if !ok {
|
||||||
|
panic(rt.NewTypeError(fmt.Sprintf("app id must be an appid or a string, got '%T'", value.Export())))
|
||||||
|
}
|
||||||
|
|
||||||
|
appID = app.ID(rawAppID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return appID
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
List(context.Context) ([]*app.Manifest, error)
|
||||||
|
Get(context.Context, app.ID) (*app.Manifest, error)
|
||||||
|
GetURL(context.Context, app.ID) (string, error)
|
||||||
|
}
|
Loading…
Reference in New Issue