feat(controller,app): add app module
This commit is contained in:
parent
3d0b0102c2
commit
e832b7dedd
8
Makefile
8
Makefile
@ -144,4 +144,10 @@ tools/gitea-release/bin/gitea-release.sh:
|
||||
chmod +x tools/gitea-release/bin/gitea-release.sh
|
||||
|
||||
.emissary-token:
|
||||
$(MAKE) run-emissary-server EMISSARY_CMD="--debug --config tmp/server.yml server auth create-token --role writer > .emissary-token"
|
||||
$(MAKE) run-emissary-server EMISSARY_CMD="--debug --config tmp/server.yml server auth create-token --role writer > .emissary-token"
|
||||
|
||||
AGENT_ID ?= 1
|
||||
|
||||
load-sample-specs:
|
||||
cat misc/spec-samples/app.emissary.cadoles.com.json | ./bin/server api agent spec update -a $(AGENT_ID) --no-patch --spec-data - --spec-name app.emissary.cadoles.com
|
||||
cat misc/spec-samples/proxy.emissary.cadoles.com.json | ./bin/server api agent spec update -a $(AGENT_ID) --no-patch --spec-data - --spec-name proxy.emissary.cadoles.com
|
11
go.mod
11
go.mod
@ -3,7 +3,7 @@ module forge.cadoles.com/Cadoles/emissary
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
forge.cadoles.com/arcad/edge v0.0.0-20230322170544-cf8a3f8ac077
|
||||
forge.cadoles.com/arcad/edge v0.0.0-20230328183829-d8ce2901d2ab
|
||||
github.com/alecthomas/participle/v2 v2.0.0-beta.5
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3
|
||||
@ -29,6 +29,9 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/barnybug/go-cast v0.0.0-20201201064555-a87ccbc26692 // indirect
|
||||
github.com/dop251/goja_nodejs v0.0.0-20230320130059-dcf93ba651dd // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.1 // indirect
|
||||
@ -37,10 +40,16 @@ require (
|
||||
github.com/google/pprof v0.0.0-20230309165930-d61513b1440d // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/hashicorp/mdns v1.0.5 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/igm/sockjs-go/v3 v3.0.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/miekg/dns v1.1.51 // indirect
|
||||
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.0 // indirect
|
||||
github.com/oklog/ulid/v2 v2.1.0 // indirect
|
||||
github.com/orcaman/concurrent-map v1.0.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220728213248-dd149ef739b9 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
21
go.sum
21
go.sum
@ -56,6 +56,10 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
forge.cadoles.com/arcad/edge v0.0.0-20230322170544-cf8a3f8ac077 h1:vsYcNHZevZrs0VeOTasvJoqvPynb8OvH+MMpIUvNT6Q=
|
||||
forge.cadoles.com/arcad/edge v0.0.0-20230322170544-cf8a3f8ac077/go.mod h1:ONd6vyQ0IM0vHi1i+bmZBRc1Fd0BoXMuDdY/+0sZefw=
|
||||
forge.cadoles.com/arcad/edge v0.0.0-20230328081549-e09de0b0a4f4 h1:ZBBOOKqCEt6F9/Ikkwc2xwYDr7JpLybvxtoRJwXt7Gw=
|
||||
forge.cadoles.com/arcad/edge v0.0.0-20230328081549-e09de0b0a4f4/go.mod h1:ONd6vyQ0IM0vHi1i+bmZBRc1Fd0BoXMuDdY/+0sZefw=
|
||||
forge.cadoles.com/arcad/edge v0.0.0-20230328183829-d8ce2901d2ab h1:xOtzLAYOUcKd/VBx/PzL2riC0zNuQ/cxxf5r3AmEvJE=
|
||||
forge.cadoles.com/arcad/edge v0.0.0-20230328183829-d8ce2901d2ab/go.mod h1:ONd6vyQ0IM0vHi1i+bmZBRc1Fd0BoXMuDdY/+0sZefw=
|
||||
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg=
|
||||
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
|
||||
@ -84,6 +88,12 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
|
||||
github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
@ -761,6 +771,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
@ -771,6 +783,7 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ=
|
||||
@ -974,6 +987,8 @@ github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW
|
||||
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
@ -987,6 +1002,8 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
||||
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
||||
@ -1180,6 +1197,7 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
@ -1204,6 +1222,8 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
@ -1379,6 +1399,7 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
190
internal/agent/controller/app/app_handler.go
Normal file
190
internal/agent/controller/app/app_handler.go
Normal file
@ -0,0 +1,190 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"text/template"
|
||||
|
||||
"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/jwk"
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/bus"
|
||||
"forge.cadoles.com/arcad/edge/pkg/bus/memory"
|
||||
edgeHTTP "forge.cadoles.com/arcad/edge/pkg/http"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module"
|
||||
appModule "forge.cadoles.com/arcad/edge/pkg/module/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/auth"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/blob"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/net"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (c *Controller) getHandlerOptions(ctx context.Context, appKey string, specs *spec.Spec) ([]edgeHTTP.HandlerOptionFunc, error) {
|
||||
dataDir, err := c.ensureAppDataDir(ctx, appKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not retrieve app data dir")
|
||||
}
|
||||
|
||||
dbFile := filepath.Join(dataDir, appKey+".sqlite")
|
||||
db, err := sqlite.Open(dbFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not open database file '%s'", dbFile)
|
||||
}
|
||||
|
||||
keySet, err := getAuthKeySet(specs.Config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not retrieve auth key set")
|
||||
}
|
||||
|
||||
bundles := make([]string, 0, len(specs.Apps))
|
||||
for appKey, app := range specs.Apps {
|
||||
path := c.getAppBundlePath(appKey, app.Format)
|
||||
bundles = append(bundles, path)
|
||||
}
|
||||
|
||||
getAppURL := createGetAppURL(specs)
|
||||
|
||||
bus := memory.NewBus()
|
||||
modules := getAppModules(bus, db, specs, keySet, getAppURL, bundles)
|
||||
|
||||
options := []edgeHTTP.HandlerOptionFunc{
|
||||
edgeHTTP.WithBus(bus),
|
||||
edgeHTTP.WithServerModules(modules...),
|
||||
}
|
||||
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func getAuthKeySet(config *spec.Config) (jwk.Set, error) {
|
||||
keySet := jwk.NewSet()
|
||||
if config == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
auth := config.Auth
|
||||
|
||||
if auth == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case auth.Local != nil:
|
||||
var (
|
||||
key jwk.Key
|
||||
err error
|
||||
)
|
||||
|
||||
switch typedKey := auth.Local.Key.(type) {
|
||||
case string:
|
||||
key, err = jwk.FromRaw([]byte(typedKey))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not parse local auth key")
|
||||
}
|
||||
|
||||
if err := key.Set(jwk.AlgorithmKey, jwa.HS256); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, errors.Errorf("unexpected key type '%T'", auth.Local.Key)
|
||||
}
|
||||
|
||||
if err := keySet.AddKey(key); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return keySet, nil
|
||||
}
|
||||
|
||||
func createGetAppURL(specs *spec.Spec) GetURLFunc {
|
||||
var (
|
||||
compileOnce sync.Once
|
||||
urlTemplate *template.Template
|
||||
err error
|
||||
)
|
||||
|
||||
return func(ctx context.Context, manifest *app.Manifest) (string, error) {
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
var appURLTemplate string
|
||||
|
||||
if specs.Config == nil || specs.Config.AppURLTemplate == "" {
|
||||
appURLTemplate = `http://{{ last ( splitList "." ( toString .Manifest.ID ) ) }}.local`
|
||||
} else {
|
||||
appURLTemplate = specs.Config.AppURLTemplate
|
||||
}
|
||||
|
||||
compileOnce.Do(func() {
|
||||
urlTemplate, err = template.New("").Funcs(sprig.TxtFuncMap()).Parse(appURLTemplate)
|
||||
})
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
data := struct {
|
||||
Manifest *app.Manifest
|
||||
Specs *spec.Spec
|
||||
}{
|
||||
Manifest: manifest,
|
||||
Specs: specs,
|
||||
}
|
||||
|
||||
if err := urlTemplate.Execute(&buf, data); err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func getAppModules(bus bus.Bus, db *sql.DB, spec *appSpec.Spec, keySet jwk.Set, getAppURL GetURLFunc, bundles []string) []app.ServerModuleFactory {
|
||||
ds := sqlite.NewDocumentStoreWithDB(db)
|
||||
bs := sqlite.NewBlobStoreWithDB(db)
|
||||
|
||||
return []app.ServerModuleFactory{
|
||||
module.ContextModuleFactory(),
|
||||
module.ConsoleModuleFactory(),
|
||||
cast.CastModuleFactory(),
|
||||
module.LifecycleModuleFactory(),
|
||||
net.ModuleFactory(bus),
|
||||
module.RPCModuleFactory(bus),
|
||||
module.StoreModuleFactory(ds),
|
||||
blob.ModuleFactory(bus, bs),
|
||||
module.Extends(
|
||||
auth.ModuleFactory(
|
||||
auth.WithJWT(func() (jwk.Set, error) {
|
||||
return keySet, nil
|
||||
}),
|
||||
),
|
||||
func(o *goja.Object) {
|
||||
if err := o.Set("CLAIM_TENANT", "arcad_tenant"); err != nil {
|
||||
panic(errors.New("could not set 'CLAIM_TENANT' property"))
|
||||
}
|
||||
|
||||
if err := o.Set("CLAIM_ENTRYPOINT", "arcad_entrypoint"); err != nil {
|
||||
panic(errors.New("could not set 'CLAIM_ENTRYPOINT' property"))
|
||||
}
|
||||
|
||||
if err := o.Set("CLAIM_ROLE", "arcad_role"); err != nil {
|
||||
panic(errors.New("could not set 'CLAIM_ROLE' property"))
|
||||
}
|
||||
|
||||
if err := o.Set("CLAIM_PREFERRED_USERNAME", "preferred_username"); err != nil {
|
||||
panic(errors.New("could not set 'CLAIM_PREFERRED_USERNAME' property"))
|
||||
}
|
||||
},
|
||||
),
|
||||
appModule.ModuleFactory(NewAppRepository(getAppURL, bundles...)),
|
||||
}
|
||||
}
|
104
internal/agent/controller/app/app_repository.go
Normal file
104
internal/agent/controller/app/app_repository.go
Normal file
@ -0,0 +1,104 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||
appModule "forge.cadoles.com/arcad/edge/pkg/module/app"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type GetURLFunc func(context.Context, *app.Manifest) (string, error)
|
||||
|
||||
type AppRepository struct {
|
||||
getURL GetURLFunc
|
||||
bundles []string
|
||||
}
|
||||
|
||||
// Get implements app.Repository
|
||||
func (r *AppRepository) Get(ctx context.Context, id app.ID) (*app.Manifest, error) {
|
||||
manifest, err := r.findManifest(ctx, id)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return manifest, nil
|
||||
}
|
||||
|
||||
// GetURL implements app.Repository
|
||||
func (r *AppRepository) GetURL(ctx context.Context, id app.ID) (string, error) {
|
||||
manifest, err := r.findManifest(ctx, id)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
url, err := r.getURL(ctx, manifest)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// List implements app.Repository
|
||||
func (r *AppRepository) List(ctx context.Context) ([]*app.Manifest, error) {
|
||||
manifests := make([]*app.Manifest, 0)
|
||||
|
||||
for _, path := range r.bundles {
|
||||
bundleCtx := logger.With(ctx, logger.F("path", path))
|
||||
|
||||
bundle, err := bundle.FromPath(path)
|
||||
if err != nil {
|
||||
logger.Error(bundleCtx, "could not load bundle", logger.E(errors.WithStack(err)))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
manifest, err := app.LoadManifest(bundle)
|
||||
if err != nil {
|
||||
logger.Error(bundleCtx, "could not load manifest", logger.E(errors.WithStack(err)))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
manifests = append(manifests, manifest)
|
||||
}
|
||||
|
||||
return manifests, nil
|
||||
}
|
||||
|
||||
func (r *AppRepository) findManifest(ctx context.Context, id app.ID) (*app.Manifest, error) {
|
||||
for _, path := range r.bundles {
|
||||
bundleCtx := logger.With(ctx, logger.F("path", path))
|
||||
|
||||
bundle, err := bundle.FromPath(path)
|
||||
if err != nil {
|
||||
logger.Error(bundleCtx, "could not load bundle", logger.E(errors.WithStack(err)))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
manifest, err := app.LoadManifest(bundle)
|
||||
if err != nil {
|
||||
logger.Error(bundleCtx, "could not load manifest", logger.E(errors.WithStack(err)))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if manifest.ID != id {
|
||||
continue
|
||||
}
|
||||
|
||||
return manifest, nil
|
||||
}
|
||||
|
||||
return nil, errors.WithStack(appModule.ErrNotFound)
|
||||
}
|
||||
|
||||
func NewAppRepository(getURL GetURLFunc, bundles ...string) *AppRepository {
|
||||
return &AppRepository{getURL, bundles}
|
||||
}
|
||||
|
||||
var _ appModule.Repository = &AppRepository{}
|
@ -8,9 +8,8 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/spec/app"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
|
||||
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
@ -35,9 +34,9 @@ func (c *Controller) Name() string {
|
||||
|
||||
// Reconcile implements node.Controller.
|
||||
func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
|
||||
appSpec := app.NewSpec()
|
||||
appSpec := spec.NewSpec()
|
||||
|
||||
if err := state.GetSpec(app.NameApp, appSpec); err != nil {
|
||||
if err := state.GetSpec(spec.Name, appSpec); err != nil {
|
||||
if errors.Is(err, agent.ErrSpecNotFound) {
|
||||
logger.Info(ctx, "could not find app spec")
|
||||
|
||||
@ -56,7 +55,7 @@ func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) stopAllApps(ctx context.Context, spec *app.Spec) {
|
||||
func (c *Controller) stopAllApps(ctx context.Context, spec *spec.Spec) {
|
||||
if len(c.servers) > 0 {
|
||||
logger.Info(ctx, "stopping all apps")
|
||||
}
|
||||
@ -76,122 +75,121 @@ func (c *Controller) stopAllApps(ctx context.Context, spec *app.Spec) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) updateApps(ctx context.Context, spec *app.Spec) {
|
||||
func (c *Controller) updateApps(ctx context.Context, specs *spec.Spec) {
|
||||
// Stop and remove obsolete apps
|
||||
for appID, entry := range c.servers {
|
||||
if _, exists := spec.Apps[appID]; exists {
|
||||
for appKey, server := range c.servers {
|
||||
if _, exists := specs.Apps[appKey]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Info(ctx, "stopping app", logger.F("appID", appID))
|
||||
logger.Info(ctx, "stopping app", logger.F("appKey", appKey))
|
||||
|
||||
if err := entry.Server.Stop(); err != nil {
|
||||
if err := server.Server.Stop(); err != nil {
|
||||
logger.Error(
|
||||
ctx, "error while stopping app",
|
||||
logger.F("gatewayID", appID),
|
||||
logger.F("appKey", appKey),
|
||||
logger.E(errors.WithStack(err)),
|
||||
)
|
||||
|
||||
delete(c.servers, appID)
|
||||
delete(c.servers, appKey)
|
||||
}
|
||||
}
|
||||
|
||||
// (Re)start apps
|
||||
for appID, appSpec := range spec.Apps {
|
||||
appCtx := logger.With(ctx, logger.F("appID", appID))
|
||||
for appKey := range specs.Apps {
|
||||
appCtx := logger.With(ctx, logger.F("appKey", appKey))
|
||||
|
||||
if err := c.updateApp(ctx, appID, appSpec, spec.Auth); err != nil {
|
||||
if err := c.updateApp(ctx, specs, appKey); err != nil {
|
||||
logger.Error(appCtx, "could not update app", logger.E(errors.WithStack(err)))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) updateApp(ctx context.Context, appID string, appSpec app.AppEntry, auth *app.Auth) (err error) {
|
||||
newAppSpecHash, err := hashstructure.Hash(appSpec, hashstructure.FormatV2, nil)
|
||||
func (c *Controller) updateApp(ctx context.Context, specs *spec.Spec, appKey string) (err error) {
|
||||
appEntry := specs.Apps[appKey]
|
||||
|
||||
newAppSpecHash, err := hashstructure.Hash(appEntry, hashstructure.FormatV2, nil)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
bundle, sha256sum, err := c.ensureAppBundle(ctx, appID, appSpec)
|
||||
bundle, sha256sum, err := c.ensureAppBundle(ctx, appKey, appEntry)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not download app bundle")
|
||||
}
|
||||
|
||||
dataDir, err := c.ensureAppDataDir(ctx, appID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not retrieve app data dir")
|
||||
}
|
||||
|
||||
var entry *serverEntry
|
||||
|
||||
entry, exists := c.servers[appID]
|
||||
server, exists := c.servers[appKey]
|
||||
if !exists {
|
||||
logger.Info(ctx, "app currently not running")
|
||||
} else if sha256sum != appSpec.SHA256Sum {
|
||||
} else if sha256sum != appEntry.SHA256Sum {
|
||||
logger.Info(
|
||||
ctx, "bundle hash mismatch, stopping app",
|
||||
logger.F("currentHash", sha256sum),
|
||||
logger.F("specHash", appSpec.SHA256Sum),
|
||||
logger.F("specHash", appEntry.SHA256Sum),
|
||||
)
|
||||
|
||||
if err := entry.Server.Stop(); err != nil {
|
||||
if err := server.Server.Stop(); err != nil {
|
||||
return errors.Wrap(err, "could not stop app")
|
||||
}
|
||||
|
||||
entry = nil
|
||||
server = nil
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
dbFile := filepath.Join(dataDir, appID+".sqlite")
|
||||
db, err := sqlite.Open(dbFile)
|
||||
if server == nil {
|
||||
options, err := c.getHandlerOptions(ctx, appKey, specs)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not opend database file '%s'", dbFile)
|
||||
return errors.Wrap(err, "could not create handler options")
|
||||
}
|
||||
|
||||
entry = &serverEntry{
|
||||
Server: NewServer(bundle, db, auth),
|
||||
var auth *spec.Auth
|
||||
if specs.Config != nil {
|
||||
auth = specs.Config.Auth
|
||||
}
|
||||
|
||||
server = &serverEntry{
|
||||
Server: NewServer(bundle, auth, options...),
|
||||
SpecHash: 0,
|
||||
}
|
||||
|
||||
c.servers[appID] = entry
|
||||
c.servers[appKey] = server
|
||||
}
|
||||
|
||||
specChanged := newAppSpecHash != entry.SpecHash
|
||||
specChanged := newAppSpecHash != server.SpecHash
|
||||
|
||||
if entry.Server.Running() && !specChanged {
|
||||
if server.Server.Running() && !specChanged {
|
||||
return nil
|
||||
}
|
||||
|
||||
if specChanged && entry.SpecHash != 0 {
|
||||
if specChanged && server.SpecHash != 0 {
|
||||
logger.Info(
|
||||
ctx, "restarting app",
|
||||
logger.F("address", appSpec.Address),
|
||||
logger.F("address", appEntry.Address),
|
||||
)
|
||||
} else {
|
||||
logger.Info(
|
||||
ctx, "starting app",
|
||||
logger.F("address", appSpec.Address),
|
||||
logger.F("address", appEntry.Address),
|
||||
)
|
||||
}
|
||||
|
||||
if err := entry.Server.Start(ctx, appSpec.Address); err != nil {
|
||||
delete(c.servers, appID)
|
||||
if err := server.Server.Start(ctx, appEntry.Address); err != nil {
|
||||
delete(c.servers, appKey)
|
||||
|
||||
return errors.Wrap(err, "could not start app")
|
||||
}
|
||||
|
||||
entry.SpecHash = newAppSpecHash
|
||||
server.SpecHash = newAppSpecHash
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) ensureAppBundle(ctx context.Context, appID string, spec app.AppEntry) (bundle.Bundle, string, error) {
|
||||
func (c *Controller) ensureAppBundle(ctx context.Context, appID string, spec spec.AppEntry) (bundle.Bundle, string, error) {
|
||||
if err := os.MkdirAll(c.downloadDir, os.ModePerm); err != nil {
|
||||
return nil, "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
bundlePath := filepath.Join(c.downloadDir, appID+"."+spec.Format)
|
||||
bundlePath := c.getAppBundlePath(appID, spec.Format)
|
||||
|
||||
_, err := os.Stat(bundlePath)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
@ -285,6 +283,10 @@ func (c *Controller) ensureAppDataDir(ctx context.Context, appID string) (string
|
||||
return dataDir, nil
|
||||
}
|
||||
|
||||
func (c *Controller) getAppBundlePath(appKey string, format string) string {
|
||||
return filepath.Join(c.downloadDir, appKey+"."+format)
|
||||
}
|
||||
|
||||
func NewController(funcs ...OptionFunc) *Controller {
|
||||
opts := defaultOptions()
|
||||
for _, fn := range funcs {
|
||||
|
@ -2,26 +2,17 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
appSpec "forge.cadoles.com/Cadoles/emissary/internal/spec/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/bus"
|
||||
"forge.cadoles.com/arcad/edge/pkg/bus/memory"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
|
||||
appSpec "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
|
||||
edgeHTTP "forge.cadoles.com/arcad/edge/pkg/http"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/auth"
|
||||
authHTTP "forge.cadoles.com/arcad/edge/pkg/module/auth/http"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/net"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
@ -31,13 +22,14 @@ import (
|
||||
_ "forge.cadoles.com/Cadoles/emissary/internal/imports/passwd"
|
||||
)
|
||||
|
||||
const defaultCookieDuration time.Duration = 24 * time.Hour
|
||||
|
||||
type Server struct {
|
||||
bundle bundle.Bundle
|
||||
db *sql.DB
|
||||
server *http.Server
|
||||
serverMutex sync.RWMutex
|
||||
auth *appSpec.Auth
|
||||
keySet jwk.Set
|
||||
bundle bundle.Bundle
|
||||
handlerOptions []edgeHTTP.HandlerOptionFunc
|
||||
server *http.Server
|
||||
serverMutex sync.RWMutex
|
||||
auth *appSpec.Auth
|
||||
}
|
||||
|
||||
func (s *Server) Start(ctx context.Context, addr string) (err error) {
|
||||
@ -51,47 +43,13 @@ func (s *Server) Start(ctx context.Context, addr string) (err error) {
|
||||
|
||||
router.Use(middleware.Logger)
|
||||
|
||||
bus := memory.NewBus()
|
||||
ds := sqlite.NewDocumentStoreWithDB(s.db)
|
||||
bs := sqlite.NewBlobStoreWithDB(s.db)
|
||||
|
||||
handler := edgeHTTP.NewHandler(
|
||||
edgeHTTP.WithBus(bus),
|
||||
edgeHTTP.WithServerModules(s.getAppModules(bus, ds, bs)...),
|
||||
)
|
||||
handler := edgeHTTP.NewHandler(s.handlerOptions...)
|
||||
if err := handler.Load(s.bundle); err != nil {
|
||||
return errors.Wrap(err, "could not load app bundle")
|
||||
}
|
||||
|
||||
if s.auth != nil {
|
||||
if s.auth.Local != nil {
|
||||
var rawKey any = s.auth.Local.Key
|
||||
if strKey, ok := rawKey.(string); ok {
|
||||
rawKey = []byte(strKey)
|
||||
}
|
||||
|
||||
key, err := jwk.FromRaw(rawKey)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := key.Set(jwk.AlgorithmKey, jwa.HS256); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
keySet := jwk.NewSet()
|
||||
if err := keySet.AddKey(key); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
s.keySet = keySet
|
||||
|
||||
router.Handle("/auth/*", authHTTP.NewLocalHandler(
|
||||
jwa.HS256, key,
|
||||
authHTTP.WithRoutePrefix("/auth"),
|
||||
authHTTP.WithAccounts(s.auth.Local.Accounts...),
|
||||
))
|
||||
}
|
||||
if err := s.configureAuth(router, s.auth); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
router.Handle("/*", handler)
|
||||
@ -157,49 +115,46 @@ func (s *Server) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) getAppModules(bus bus.Bus, ds storage.DocumentStore, bs storage.BlobStore) []app.ServerModuleFactory {
|
||||
return []app.ServerModuleFactory{
|
||||
module.ContextModuleFactory(),
|
||||
module.ConsoleModuleFactory(),
|
||||
cast.CastModuleFactory(),
|
||||
module.LifecycleModuleFactory(),
|
||||
net.ModuleFactory(bus),
|
||||
module.RPCModuleFactory(bus),
|
||||
module.StoreModuleFactory(ds),
|
||||
module.BlobModuleFactory(bus, bs),
|
||||
module.Extends(
|
||||
auth.ModuleFactory(
|
||||
auth.WithJWT(s.getJWTKeySet),
|
||||
),
|
||||
func(o *goja.Object) {
|
||||
if err := o.Set("CLAIM_TENANT", "arcad_tenant"); err != nil {
|
||||
panic(errors.New("could not set 'CLAIM_TENANT' property"))
|
||||
}
|
||||
|
||||
if err := o.Set("CLAIM_ENTRYPOINT", "arcad_entrypoint"); err != nil {
|
||||
panic(errors.New("could not set 'CLAIM_ENTRYPOINT' property"))
|
||||
}
|
||||
|
||||
if err := o.Set("CLAIM_ROLE", "arcad_role"); err != nil {
|
||||
panic(errors.New("could not set 'CLAIM_ROLE' property"))
|
||||
}
|
||||
|
||||
if err := o.Set("CLAIM_PREFERRED_USERNAME", "preferred_username"); err != nil {
|
||||
panic(errors.New("could not set 'CLAIM_PREFERRED_USERNAME' property"))
|
||||
}
|
||||
},
|
||||
),
|
||||
func (s *Server) configureAuth(router chi.Router, auth *spec.Auth) error {
|
||||
if auth == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case auth.Local != nil:
|
||||
var rawKey any = s.auth.Local.Key
|
||||
if strKey, ok := rawKey.(string); ok {
|
||||
rawKey = []byte(strKey)
|
||||
}
|
||||
|
||||
key, err := jwk.FromRaw(rawKey)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
cookieDuration := defaultCookieDuration
|
||||
if s.auth.Local.CookieDuration != "" {
|
||||
cookieDuration, err = time.ParseDuration(s.auth.Local.CookieDuration)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
router.Handle("/auth/*", authHTTP.NewLocalHandler(
|
||||
jwa.HS256, key,
|
||||
authHTTP.WithRoutePrefix("/auth"),
|
||||
authHTTP.WithAccounts(s.auth.Local.Accounts...),
|
||||
authHTTP.WithCookieOptions(s.auth.Local.CookieDomain, cookieDuration),
|
||||
))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) getJWTKeySet() (jwk.Set, error) {
|
||||
return s.keySet, nil
|
||||
}
|
||||
|
||||
func NewServer(bundle bundle.Bundle, db *sql.DB, auth *appSpec.Auth) *Server {
|
||||
func NewServer(bundle bundle.Bundle, auth *appSpec.Auth, handlerOptions ...edgeHTTP.HandlerOptionFunc) *Server {
|
||||
return &Server{
|
||||
bundle: bundle,
|
||||
db: db,
|
||||
auth: auth,
|
||||
bundle: bundle,
|
||||
auth: auth,
|
||||
handlerOptions: handlerOptions,
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package app
|
||||
package spec
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
@ -11,7 +11,7 @@ import (
|
||||
var schema []byte
|
||||
|
||||
func init() {
|
||||
if err := spec.Register(NameApp, schema); err != nil {
|
||||
if err := spec.Register(Name, schema); err != nil {
|
||||
panic(errors.WithStack(err))
|
||||
}
|
||||
}
|
@ -71,6 +71,12 @@
|
||||
"algo"
|
||||
]
|
||||
}
|
||||
},
|
||||
"cookieDomain": {
|
||||
"type": "string"
|
||||
},
|
||||
"cookieDuration": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@ -78,6 +84,11 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"appUrlTemplate": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
@ -1,16 +1,16 @@
|
||||
package app
|
||||
package spec
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||
edgeAuth "forge.cadoles.com/arcad/edge/pkg/module/auth/http"
|
||||
)
|
||||
|
||||
const NameApp spec.Name = "app.emissary.cadoles.com"
|
||||
const Name spec.Name = "app.emissary.cadoles.com"
|
||||
|
||||
type Spec struct {
|
||||
Revision int `json:"revision"`
|
||||
Apps map[string]AppEntry `json:"apps"`
|
||||
Auth *Auth `json:"auth"`
|
||||
Config *Config `json:"config"`
|
||||
}
|
||||
|
||||
type AppEntry struct {
|
||||
@ -25,12 +25,19 @@ type Auth struct {
|
||||
}
|
||||
|
||||
type LocalAuth struct {
|
||||
Key any `json:"key"`
|
||||
Accounts []edgeAuth.LocalAccount `json:"accounts"`
|
||||
Key any `json:"key"`
|
||||
Accounts []edgeAuth.LocalAccount `json:"accounts"`
|
||||
CookieDomain string `json:"cookieDomain"`
|
||||
CookieDuration string `json:"cookieDuration"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Auth *Auth `json:"auth"`
|
||||
AppURLTemplate string `json:"appUrlTemplate"`
|
||||
}
|
||||
|
||||
func (s *Spec) SpecName() spec.Name {
|
||||
return NameApp
|
||||
return Name
|
||||
}
|
||||
|
||||
func (s *Spec) SpecRevision() int {
|
||||
@ -39,8 +46,8 @@ func (s *Spec) SpecRevision() int {
|
||||
|
||||
func (s *Spec) SpecData() map[string]any {
|
||||
return map[string]any{
|
||||
"apps": s.Apps,
|
||||
"auth": s.Auth,
|
||||
"apps": s.Apps,
|
||||
"config": s.Config,
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package app
|
||||
package spec
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -28,7 +28,7 @@ func TestValidator(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validator := spec.NewValidator()
|
||||
if err := validator.Register(NameApp, schema); err != nil {
|
||||
if err := validator.Register(Name, schema); err != nil {
|
||||
t.Fatalf("+%v", errors.WithStack(err))
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
_ "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
|
||||
_ "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/openwrt/spec/sysupgrade"
|
||||
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/app"
|
||||
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/proxy"
|
||||
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/uci"
|
||||
)
|
||||
|
@ -23,6 +23,13 @@ type (
|
||||
ParseOption = jwk.ParseOption
|
||||
)
|
||||
|
||||
var (
|
||||
FromRaw = jwk.FromRaw
|
||||
NewSet = jwk.NewSet
|
||||
)
|
||||
|
||||
const AlgorithmKey = jwk.AlgorithmKey
|
||||
|
||||
func Parse(src []byte, options ...jwk.ParseOption) (Set, error) {
|
||||
return jwk.Parse(src, options...)
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package proxy
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/proxy/wildcard"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/proxy/wildcard"
|
||||
)
|
||||
|
||||
func FilterHosts(allowedHostPatterns ...string) Middleware {
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/proxy/wildcard"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/proxy/wildcard"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
@ -17,6 +17,7 @@ func RewriteHosts(mappings map[string]*url.URL) Middleware {
|
||||
}
|
||||
|
||||
sort.Strings(patterns)
|
||||
reverse(patterns)
|
||||
|
||||
return func(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"apps": {
|
||||
"edge.sdk.client.test": {
|
||||
"url": "http://localhost:3001/edge.sdk.client.test_0.0.0.zip",
|
||||
"sha256sum": "58019192dacdae17755707719707db007e26dac856102280583fbd18427dd352",
|
||||
"format": "zip",
|
||||
"address": "127.0.0.1:8081"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
@baseUrl = http://localhost:3000
|
||||
|
||||
### Get agents
|
||||
|
||||
# @name getAgents
|
||||
GET {{ baseUrl }}/api/v1/agents
|
||||
Content-Type: application/json
|
||||
|
||||
@agentId = {{ getAgents.response.body.Data.Agents.0.ID }}
|
||||
|
||||
### Update an agent (accept it)
|
||||
|
||||
PUT {{ baseUrl }}/api/v1/agents/{{ agentId }}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Status": 1
|
||||
}
|
||||
|
||||
### Get an agent
|
||||
|
||||
GET {{ baseUrl }}/api/v1/agents/{{ agentId }}
|
||||
Content-Type: application/json
|
||||
|
||||
### Get an agent specs
|
||||
|
||||
# @name getSpecs
|
||||
GET {{ baseUrl }}/api/v1/agents/{{ agentId }}/specs
|
||||
Content-Type: application/json
|
||||
|
||||
@specName = {{ getSpecs.response.body.Data.Specs.0.Name }}
|
||||
|
||||
### Update an agent specs
|
||||
|
||||
POST {{ baseUrl }}/api/v1/agents/{{ agentId }}/specs
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Name": "gateway.emissary.cadoles.com",
|
||||
"Revision": 2,
|
||||
"Data": {
|
||||
"gateways": {
|
||||
"cadoles.com":{
|
||||
"address":":3003",
|
||||
"target":"https://www.cadoles.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
### Delete an agent spec
|
||||
|
||||
DELETE {{ baseUrl }}/api/v1/agents/{{ agentId }}/specs
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Name": "gateway.emissary.cadoles.com"
|
||||
}
|
||||
|
||||
### Update UCI spec with uhttpd config
|
||||
|
||||
POST {{ baseUrl }}/api/v1/agents/2/specs
|
||||
Content-Type: application/json
|
||||
|
||||
< ./uci-spec.payload.json
|
@ -1,163 +0,0 @@
|
||||
{
|
||||
"Name": "uci.emissary.cadoles.com",
|
||||
"Revision": 6,
|
||||
"Data": {
|
||||
"config": {
|
||||
"packages": [
|
||||
{
|
||||
"name": "uhttpd",
|
||||
"configs": [
|
||||
{
|
||||
"name": "uhttpd",
|
||||
"section": "main",
|
||||
"options": [
|
||||
{
|
||||
"type": "list",
|
||||
"name": "listen_http",
|
||||
"value": "0.0.0.0:8080"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"name": "listen_http",
|
||||
"value": "[::]:8080"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"name": "listen_https",
|
||||
"value": "0.0.0.0:8443"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"name": "listen_https",
|
||||
"value": "[::]:8443"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "redirect_https",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "home",
|
||||
"value": "/www"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "rfc1918_filter",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "max_requests",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "max_connections",
|
||||
"value": "100"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "cert",
|
||||
"value": "/etc/uhttpd.crt"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "key",
|
||||
"value": "/etc/uhttpd.key"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "cgi_prefix",
|
||||
"value": "/cgi-bin"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"name": "lua_prefix",
|
||||
"value": "/cgi-bin/luci=/usr/lib/lua/luci/sgi/uhttpd.lua"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "script_timeout",
|
||||
"value": "60"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "network_timeout",
|
||||
"value": "30"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "http_keepalive",
|
||||
"value": "20"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "tcp_keepalive",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "ubus_prefix",
|
||||
"value": "/ubus"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "cert",
|
||||
"section": "defaults",
|
||||
"options": [
|
||||
{
|
||||
"type": "option",
|
||||
"name": "days",
|
||||
"value": "730"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "key_type",
|
||||
"value": "ec"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "bits",
|
||||
"value": "2048"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "ec_curve",
|
||||
"value": "P-256"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "country",
|
||||
"value": "ZZ"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "state",
|
||||
"value": "Somewhere"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "location",
|
||||
"value": "Unknown"
|
||||
},
|
||||
{
|
||||
"type": "option",
|
||||
"name": "commonname",
|
||||
"value": "OpenWrt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"postImportCommands": [
|
||||
{
|
||||
"command": "reload_config",
|
||||
"args": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
45
misc/spec-samples/app.emissary.cadoles.com.json
Normal file
45
misc/spec-samples/app.emissary.cadoles.com.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"apps": {
|
||||
"portal": {
|
||||
"url": "https://emissary.cadol.es/files/apps/arcad.portal_v2023.3.28-3feda80.zip",
|
||||
"sha256sum": "921402c44a5fa554d5b630d1284957b05416aa6872b402314cf52e964e06fac5",
|
||||
"address": "127.0.0.1:8082",
|
||||
"format": "zip"
|
||||
},
|
||||
"hextris": {
|
||||
"url": "https://emissary.cadol.es/files/apps/app.arcad.edge.hextris_v2023.3.22-33ece28.zip",
|
||||
"sha256sum": "5f9f3c8d6f22796beb051d747d7ff12efa17af9d1552c0ab08baef13703a2aba",
|
||||
"address": "127.0.0.1:8083",
|
||||
"format": "zip"
|
||||
},
|
||||
"test": {
|
||||
"url": "https://emissary.cadol.es/files/apps/edge.sdk.client.test_v2023.3.24-ed535b6.zip",
|
||||
"sha256sum": "e97b7b79159bb5d6a13b05644c091272b02a1a3cbb1b613dd5eda37e1eb84623",
|
||||
"address": "127.0.0.1:8084",
|
||||
"format": "zip"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"appUrlTemplate": "http://{{ last ( splitList \".\" ( toString .Manifest.ID ) ) }}.arcad.local:8080",
|
||||
"auth": {
|
||||
"local": {
|
||||
"key": "absolutlynotsecret",
|
||||
"cookieDomain": ".arcad.local",
|
||||
"cookieDuration": "1h",
|
||||
"accounts": [
|
||||
{
|
||||
"username": "admin",
|
||||
"algo": "plain",
|
||||
"password": "admin",
|
||||
"claims": {
|
||||
"arcad_role": "admin",
|
||||
"arcad_tenant": "x86",
|
||||
"preferred_username": "Admin",
|
||||
"sub": "admin"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
misc/spec-samples/proxy.emissary.cadoles.com.json
Normal file
25
misc/spec-samples/proxy.emissary.cadoles.com.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"proxies": {
|
||||
"main": {
|
||||
"address": ":8080",
|
||||
"mappings": [
|
||||
{
|
||||
"hostPattern": "portal.arcad.local:*",
|
||||
"target": "http://localhost:8082"
|
||||
},
|
||||
{
|
||||
"hostPattern": "hextris.arcad.local:*",
|
||||
"target": "http://localhost:8083"
|
||||
},
|
||||
{
|
||||
"hostPattern": "test.arcad.local:*",
|
||||
"target": "http://localhost:8084"
|
||||
},
|
||||
{
|
||||
"hostPattern": "*",
|
||||
"target": "http://localhost:8082"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user