Compare commits

...

6 Commits

11 changed files with 145 additions and 32 deletions

View File

@ -51,10 +51,8 @@ func (c *Controller) getHandlerOptions(ctx context.Context, appKey string, specs
bundles = append(bundles, path) bundles = append(bundles, path)
} }
getAppURL := createGetAppURL(specs)
bus := memory.NewBus() bus := memory.NewBus()
modules := getAppModules(bus, db, specs, keySet, getAppURL, bundles) modules := c.getAppModules(bus, db, specs, keySet)
options := []edgeHTTP.HandlerOptionFunc{ options := []edgeHTTP.HandlerOptionFunc{
edgeHTTP.WithBus(bus), edgeHTTP.WithBus(bus),
@ -148,7 +146,7 @@ func createGetAppURL(specs *spec.Spec) GetURLFunc {
} }
} }
func getAppModules(bus bus.Bus, db *sql.DB, spec *appSpec.Spec, keySet jwk.Set, getAppURL GetURLFunc, bundles []string) []app.ServerModuleFactory { func (c *Controller) getAppModules(bus bus.Bus, db *sql.DB, spec *appSpec.Spec, keySet jwk.Set) []app.ServerModuleFactory {
ds := sqlite.NewDocumentStoreWithDB(db) ds := sqlite.NewDocumentStoreWithDB(db)
bs := sqlite.NewBlobStoreWithDB(db) bs := sqlite.NewBlobStoreWithDB(db)
@ -185,6 +183,6 @@ func getAppModules(bus bus.Bus, db *sql.DB, spec *appSpec.Spec, keySet jwk.Set,
} }
}, },
), ),
appModule.ModuleFactory(NewAppRepository(getAppURL, bundles...)), appModule.ModuleFactory(c.appRepository),
} }
} }

View File

@ -2,6 +2,7 @@ package app
import ( import (
"context" "context"
"sync"
"forge.cadoles.com/arcad/edge/pkg/app" "forge.cadoles.com/arcad/edge/pkg/app"
"forge.cadoles.com/arcad/edge/pkg/bundle" "forge.cadoles.com/arcad/edge/pkg/bundle"
@ -15,10 +16,14 @@ type GetURLFunc func(context.Context, *app.Manifest) (string, error)
type AppRepository struct { type AppRepository struct {
getURL GetURLFunc getURL GetURLFunc
bundles []string bundles []string
mutex sync.RWMutex
} }
// Get implements app.Repository // Get implements app.Repository
func (r *AppRepository) Get(ctx context.Context, id app.ID) (*app.Manifest, error) { func (r *AppRepository) Get(ctx context.Context, id app.ID) (*app.Manifest, error) {
r.mutex.RLock()
defer r.mutex.RUnlock()
manifest, err := r.findManifest(ctx, id) manifest, err := r.findManifest(ctx, id)
if err != nil { if err != nil {
return nil, errors.WithStack(err) return nil, errors.WithStack(err)
@ -29,6 +34,9 @@ func (r *AppRepository) Get(ctx context.Context, id app.ID) (*app.Manifest, erro
// GetURL implements app.Repository // GetURL implements app.Repository
func (r *AppRepository) GetURL(ctx context.Context, id app.ID) (string, error) { func (r *AppRepository) GetURL(ctx context.Context, id app.ID) (string, error) {
r.mutex.RLock()
defer r.mutex.RUnlock()
manifest, err := r.findManifest(ctx, id) manifest, err := r.findManifest(ctx, id)
if err != nil { if err != nil {
return "", errors.WithStack(err) return "", errors.WithStack(err)
@ -44,6 +52,9 @@ func (r *AppRepository) GetURL(ctx context.Context, id app.ID) (string, error) {
// List implements app.Repository // List implements app.Repository
func (r *AppRepository) List(ctx context.Context) ([]*app.Manifest, error) { func (r *AppRepository) List(ctx context.Context) ([]*app.Manifest, error) {
r.mutex.RLock()
defer r.mutex.RUnlock()
manifests := make([]*app.Manifest, 0) manifests := make([]*app.Manifest, 0)
for _, path := range r.bundles { for _, path := range r.bundles {
@ -69,6 +80,14 @@ func (r *AppRepository) List(ctx context.Context) ([]*app.Manifest, error) {
return manifests, nil return manifests, nil
} }
func (r *AppRepository) Update(getURL GetURLFunc, bundles []string) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.getURL = getURL
r.bundles = bundles
}
func (r *AppRepository) findManifest(ctx context.Context, id app.ID) (*app.Manifest, error) { func (r *AppRepository) findManifest(ctx context.Context, id app.ID) (*app.Manifest, error) {
for _, path := range r.bundles { for _, path := range r.bundles {
bundleCtx := logger.With(ctx, logger.F("path", path)) bundleCtx := logger.With(ctx, logger.F("path", path))
@ -97,8 +116,13 @@ func (r *AppRepository) findManifest(ctx context.Context, id app.ID) (*app.Manif
return nil, errors.WithStack(appModule.ErrNotFound) return nil, errors.WithStack(appModule.ErrNotFound)
} }
func NewAppRepository(getURL GetURLFunc, bundles ...string) *AppRepository { func NewAppRepository() *AppRepository {
return &AppRepository{getURL, bundles} return &AppRepository{
getURL: func(ctx context.Context, m *app.Manifest) (string, error) {
return "", errors.New("unavailable")
},
bundles: []string{},
}
} }
var _ appModule.Repository = &AppRepository{} var _ appModule.Repository = &AppRepository{}

View File

@ -16,15 +16,16 @@ import (
) )
type serverEntry struct { type serverEntry struct {
SpecHash uint64 AppDefHash uint64
Server *Server Server *Server
} }
type Controller struct { type Controller struct {
client *http.Client client *http.Client
downloadDir string downloadDir string
dataDir string dataDir string
servers map[string]*serverEntry servers map[string]*serverEntry
appRepository *AppRepository
} }
// Name implements node.Controller. // Name implements node.Controller.
@ -95,7 +96,9 @@ func (c *Controller) updateApps(ctx context.Context, specs *spec.Spec) {
} }
} }
// (Re)start apps c.updateAppRepository(ctx, specs)
// (Re)start apps if necessary
for appKey := range specs.Apps { for appKey := range specs.Apps {
appCtx := logger.With(ctx, logger.F("appKey", appKey)) appCtx := logger.With(ctx, logger.F("appKey", appKey))
@ -106,10 +109,35 @@ func (c *Controller) updateApps(ctx context.Context, specs *spec.Spec) {
} }
} }
func (c *Controller) updateAppRepository(ctx context.Context, specs *spec.Spec) {
bundles := make([]string, 0, len(specs.Apps))
for appKey, app := range specs.Apps {
path := c.getAppBundlePath(appKey, app.Format)
bundles = append(bundles, path)
}
getURL := createGetAppURL(specs)
c.appRepository.Update(getURL, bundles)
}
func (c *Controller) updateApp(ctx context.Context, specs *spec.Spec, appKey string) (err error) { func (c *Controller) updateApp(ctx context.Context, specs *spec.Spec, appKey string) (err error) {
appEntry := specs.Apps[appKey] appEntry := specs.Apps[appKey]
newAppSpecHash, err := hashstructure.Hash(appEntry, hashstructure.FormatV2, nil) var auth *spec.Auth
if specs.Config != nil {
auth = specs.Config.Auth
}
appDef := struct {
App spec.AppEntry
Auth *spec.Auth
}{
App: appEntry,
Auth: auth,
}
newAppDefHash, err := hashstructure.Hash(appDef, hashstructure.FormatV2, nil)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
@ -148,20 +176,20 @@ func (c *Controller) updateApp(ctx context.Context, specs *spec.Spec, appKey str
} }
server = &serverEntry{ server = &serverEntry{
Server: NewServer(bundle, auth, options...), Server: NewServer(bundle, auth, options...),
SpecHash: 0, AppDefHash: 0,
} }
c.servers[appKey] = server c.servers[appKey] = server
} }
specChanged := newAppSpecHash != server.SpecHash defChanged := newAppDefHash != server.AppDefHash
if server.Server.Running() && !specChanged { if server.Server.Running() && !defChanged {
return nil return nil
} }
if specChanged && server.SpecHash != 0 { if defChanged && server.AppDefHash != 0 {
logger.Info( logger.Info(
ctx, "restarting app", ctx, "restarting app",
logger.F("address", appEntry.Address), logger.F("address", appEntry.Address),
@ -179,7 +207,7 @@ func (c *Controller) updateApp(ctx context.Context, specs *spec.Spec, appKey str
return errors.Wrap(err, "could not start app") return errors.Wrap(err, "could not start app")
} }
server.SpecHash = newAppSpecHash server.AppDefHash = newAppDefHash
return nil return nil
} }
@ -294,10 +322,11 @@ func NewController(funcs ...OptionFunc) *Controller {
} }
return &Controller{ return &Controller{
client: opts.Client, client: opts.Client,
downloadDir: opts.DownloadDir, downloadDir: opts.DownloadDir,
dataDir: opts.DataDir, dataDir: opts.DataDir,
servers: make(map[string]*serverEntry), servers: make(map[string]*serverEntry),
appRepository: NewAppRepository(),
} }
} }

View File

@ -3,11 +3,13 @@ package app
import ( import (
"context" "context"
"net/http" "net/http"
"strings"
"sync" "sync"
"time" "time"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
appSpec "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" appSpec "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
"forge.cadoles.com/Cadoles/emissary/internal/proxy/wildcard"
edgeHTTP "forge.cadoles.com/arcad/edge/pkg/http" edgeHTTP "forge.cadoles.com/arcad/edge/pkg/http"
authHTTP "forge.cadoles.com/arcad/edge/pkg/module/auth/http" authHTTP "forge.cadoles.com/arcad/edge/pkg/module/auth/http"
"gitlab.com/wpetit/goweb/logger" "gitlab.com/wpetit/goweb/logger"
@ -109,7 +111,7 @@ func (s *Server) Stop() error {
}() }()
if err := s.server.Close(); err != nil { if err := s.server.Close(); err != nil {
panic(errors.WithStack(err)) return errors.WithStack(err)
} }
return nil return nil
@ -140,6 +142,10 @@ func (s *Server) configureAuth(router chi.Router, auth *spec.Auth) error {
} }
} }
if s.auth.Local.CookieDomain != "" {
router.Use(invalidCookieDomainRedirect(s.auth.Local.CookieDomain))
}
router.Handle("/auth/*", authHTTP.NewLocalHandler( router.Handle("/auth/*", authHTTP.NewLocalHandler(
jwa.HS256, key, jwa.HS256, key,
authHTTP.WithRoutePrefix("/auth"), authHTTP.WithRoutePrefix("/auth"),
@ -158,3 +164,33 @@ func NewServer(bundle bundle.Bundle, auth *appSpec.Auth, handlerOptions ...edgeH
handlerOptions: handlerOptions, handlerOptions: handlerOptions,
} }
} }
func invalidCookieDomainRedirect(cookieDomain string) func(http.Handler) http.Handler {
domain := strings.TrimPrefix(cookieDomain, ".")
hostPattern := "*" + domain
return func(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
hostParts := strings.SplitN(r.Host, ":", 2)
if !wildcard.Match(hostParts[0], hostPattern) {
url := r.URL
newHost := domain
if len(hostParts) > 1 {
newHost += ":" + hostParts[1]
}
url.Host = newHost
http.Redirect(w, r, url.String(), http.StatusTemporaryRedirect)
return
}
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
}

View File

@ -18,7 +18,7 @@ func GenerateToken(key jwk.Key, thumbprint string) (string, error) {
return "", errors.WithStack(err) return "", errors.WithStack(err)
} }
now := time.Now() now := time.Now().UTC()
if err := token.Set(jwt.NotBeforeKey, now); err != nil { if err := token.Set(jwt.NotBeforeKey, now); err != nil {
return "", errors.WithStack(err) return "", errors.WithStack(err)

View File

@ -42,7 +42,7 @@ func GenerateToken(ctx context.Context, key jwk.Key, issuer, subject string, rol
return "", errors.WithStack(err) return "", errors.WithStack(err)
} }
now := time.Now() now := time.Now().UTC()
if err := token.Set(jwt.NotBeforeKey, now); err != nil { if err := token.Set(jwt.NotBeforeKey, now); err != nil {
return "", errors.WithStack(err) return "", errors.WithStack(err)

View File

@ -15,6 +15,6 @@ type DatabaseConfig struct {
func NewDefaultDatabaseConfig() DatabaseConfig { func NewDefaultDatabaseConfig() DatabaseConfig {
return DatabaseConfig{ return DatabaseConfig{
Driver: "sqlite", Driver: "sqlite",
DSN: "sqlite://emissary.sqlite", DSN: "sqlite://emissary.sqlite?_fk=true&_journal=WAL",
} }
} }

View File

@ -45,7 +45,11 @@ func (r *AgentRepository) GetSpecs(ctx context.Context, agentID datastore.AgentI
return nil, errors.WithStack(err) return nil, errors.WithStack(err)
} }
defer rows.Close() defer func() {
if err := rows.Close(); err != nil {
logger.Error(ctx, "could not close rows", logger.E(errors.WithStack(err)))
}
}()
for rows.Next() { for rows.Next() {
spec := &datastore.Spec{} spec := &datastore.Spec{}
@ -61,6 +65,10 @@ func (r *AgentRepository) GetSpecs(ctx context.Context, agentID datastore.AgentI
specs = append(specs, spec) specs = append(specs, spec)
} }
if err := rows.Err(); err != nil {
return nil, errors.WithStack(err)
}
return specs, nil return specs, nil
} }
@ -176,7 +184,11 @@ func (r *AgentRepository) Query(ctx context.Context, opts ...datastore.AgentQuer
return errors.WithStack(err) return errors.WithStack(err)
} }
defer rows.Close() defer func() {
if err := rows.Close(); err != nil {
logger.Error(ctx, "could not close rows", logger.E(errors.WithStack(err)))
}
}()
for rows.Next() { for rows.Next() {
agent := &datastore.Agent{} agent := &datastore.Agent{}
@ -192,6 +204,10 @@ func (r *AgentRepository) Query(ctx context.Context, opts ...datastore.AgentQuer
agents = append(agents, agent) agents = append(agents, agent)
} }
if err := rows.Err(); err != nil {
return errors.WithStack(err)
}
row := tx.QueryRowContext(ctx, `SELECT count(id) FROM agents `+filters, args...) row := tx.QueryRowContext(ctx, `SELECT count(id) FROM agents `+filters, args...)
if err := row.Scan(&count); err != nil { if err := row.Scan(&count); err != nil {
return errors.WithStack(err) return errors.WithStack(err)

View File

@ -9,7 +9,7 @@ server:
port: 3000 port: 3000
database: database:
driver: sqlite driver: sqlite
dsn: sqlite:///var/lib/emissary/data.sqlite dsn: sqlite:///var/lib/emissary/data.sqlite?_fk=true&_journal=WAL
cors: cors:
allowedOrigins: [] allowedOrigins: []
allowCredentials: true allowCredentials: true

View File

@ -17,6 +17,12 @@
"sha256sum": "e97b7b79159bb5d6a13b05644c091272b02a1a3cbb1b613dd5eda37e1eb84623", "sha256sum": "e97b7b79159bb5d6a13b05644c091272b02a1a3cbb1b613dd5eda37e1eb84623",
"address": "127.0.0.1:8084", "address": "127.0.0.1:8084",
"format": "zip" "format": "zip"
},
"diffusion": {
"url": "https://emissary.cadol.es/files/apps/arcad.diffusion_v2023.3.29-5b3fab4.zip",
"sha256sum": "1282e75719beedbc7c7e67879389d0f3e11c86d3d2c37cf13da624a66faaeb58",
"address": "127.0.0.1:8085",
"format": "zip"
} }
}, },
"config": { "config": {

View File

@ -15,6 +15,10 @@
"hostPattern": "test.arcad.local:*", "hostPattern": "test.arcad.local:*",
"target": "http://localhost:8084" "target": "http://localhost:8084"
}, },
{
"hostPattern": "diffusion.arcad.local:*",
"target": "http://localhost:8085"
},
{ {
"hostPattern": "*", "hostPattern": "*",
"target": "http://localhost:8082" "target": "http://localhost:8082"