Compare commits
31 Commits
v2023.4.6-
...
v2023.5.23
Author | SHA1 | Date | |
---|---|---|---|
4311cf6fed | |||
6a976c0b51 | |||
d188af81af | |||
e975381b4f | |||
0d03a708f9 | |||
64ea0e05a9 | |||
10844a15a3 | |||
0394e34055 | |||
541d30d74f | |||
87a45090e0 | |||
fcd159c0fb | |||
eda02c6be3 | |||
ef3048b005 | |||
51e1dc3b2d | |||
3d01cf0f93 | |||
bb03b3a54a | |||
813f837291 | |||
ed35ee5002 | |||
4b5bc0bc82 | |||
dee62184b9 | |||
76656e8dbf | |||
41b1619fc1 | |||
35d5ee868f | |||
765257b4b1 | |||
2315ee7b61 | |||
86a6d81e1d | |||
c4427dfd2b | |||
280b0fbd50 | |||
8fb86c600f | |||
12f8b3aa25 | |||
2d2dc29c84 |
11
Makefile
11
Makefile
@ -154,4 +154,13 @@ load-sample-specs:
|
||||
cat misc/spec-samples/mdns.emissary.cadoles.com.json | ./bin/server api agent spec update -a $(AGENT_ID) --no-patch --spec-data - --spec-name mdns.emissary.cadoles.com
|
||||
|
||||
full-version:
|
||||
@echo $(FULL_VERSION)
|
||||
@echo $(FULL_VERSION)
|
||||
|
||||
update-edge-lib:
|
||||
git pull --rebase
|
||||
GOPROXY=direct GOPRIVATE=forge.cadoles.com/arcad/edge go get -u forge.cadoles.com/arcad/edge
|
||||
go mod tidy
|
||||
$(MAKE) test
|
||||
git add go.mod go.sum
|
||||
git commit -m "feat: update arcad/edge dependency"
|
||||
git push
|
@ -1,5 +1,7 @@
|
||||
# Documentation
|
||||
|
||||
- (FR) - [Introduction](./fr/introduction.md)
|
||||
|
||||
## Tutorials
|
||||
|
||||
- (FR) - [Premiers pas](./tutorials/fr/first-steps.md)
|
||||
|
15
doc/fr/introduction.md
Normal file
15
doc/fr/introduction.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Introduction
|
||||
|
||||
"Emissary" est un programme entrant dans la catégorie des outils de gestion et déploiement de configuration.
|
||||
|
||||
En utilisant un agent déployé sur chaque système cible, il permet aux administrateurs système de centraliser le contrôle et la supervision de la configuration. Grâce à ses fonctionnalités avancées, il est capable de faire converger la configuration d'une machine vers un modèle précis défini par une ou plusieurs spécifications centralisées sur un serveur de pilotage dédié.
|
||||
|
||||
Le principal atout d'"Emissary" réside dans sa capacité à activer des "contrôleurs" spécifiques pour chaque aspect de la configuration système. Ces contrôleurs sont des modules intelligents qui agissent comme des agents spécialisés, veillant à ce que les paramètres de configuration soient correctement appliqués et respectent les spécifications définies.
|
||||
|
||||
Grâce à cette approche modulaire, "Emissary" peut gérer diverses facettes de la configuration, telles que les paramètres réseau, les règles de sécurité, les options de performance et bien plus encore. Chaque contrôleur est conçu pour répondre à des besoins spécifiques, offrant ainsi une flexibilité et une granularité optimales dans la gestion de la configuration.
|
||||
|
||||
Certains contrôleurs permettent également l'exécution de services spécialisés comme des serveurs mandataires inverses ou des applications web autonomes.
|
||||
|
||||
L'utilisation d'un serveur de pilotage centralisé permet à "Emissary" de stocker et de mettre à jour les spécifications de configuration de manière cohérente. Les administrateurs peuvent définir des modèles de configuration précis, les affiner au fil du temps et les appliquer en un seul clic sur l'ensemble du parc de machines gérées. Cela garantit une uniformité et une conformité accrues, tout en facilitant la maintenance et les mises à jour à grande échelle.
|
||||
|
||||
À l'heure actuelle, Emissary est conçu pour cibler spécifiquement le système d'exploitation OpenWRT. L'activation des "contrôleurs" spécifiques à cet OS permet de converger la configuration de la machine OpenWRT vers un modèle correspondant aux spécifications centralisées sur le serveur de pilotage. Ces spécifications peuvent inclure des paramètres réseau, des configurations de sécurité, des règles de pare-feu, des options de routage, des services système, et bien d'autres éléments spécifiques à OpenWRT.
|
18
go.mod
18
go.mod
@ -3,7 +3,7 @@ module forge.cadoles.com/Cadoles/emissary
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
forge.cadoles.com/arcad/edge v0.0.0-20230406171836-da73b842e112
|
||||
forge.cadoles.com/arcad/edge v0.0.0-20230426135323-17808d14c978
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/alecthomas/participle/v2 v2.0.0-beta.5
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883
|
||||
@ -25,7 +25,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/qri-io/jsonschema v0.2.1
|
||||
github.com/urfave/cli/v2 v2.24.4
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230227162855-a1f09bafccb3
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230419082146-a94d9ed7202b
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.21.0
|
||||
)
|
||||
@ -44,14 +44,14 @@ require (
|
||||
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/miekg/dns v1.1.53 // 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
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220728213248-dd149ef739b9 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
@ -96,12 +96,12 @@ require (
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/term v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/tools v0.8.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
|
||||
|
32
go.sum
32
go.sum
@ -54,8 +54,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
forge.cadoles.com/arcad/edge v0.0.0-20230406171836-da73b842e112 h1:XlDIFCdUf3Pq26uecJ+KYPCCx5le8GJvFGfryT9HFSA=
|
||||
forge.cadoles.com/arcad/edge v0.0.0-20230406171836-da73b842e112/go.mod h1:Vx4iq/oewXUOkGyi8QKc14clTLNO1sWpb0SjBYELlAs=
|
||||
forge.cadoles.com/arcad/edge v0.0.0-20230426135323-17808d14c978 h1:fekSRSb8gYcVx8C0B9K6B7+KiFHVixIwvPUkxcnRFp4=
|
||||
forge.cadoles.com/arcad/edge v0.0.0-20230426135323-17808d14c978/go.mod h1:uv3wBa+UbcEUb7IiJCj1T96Xo3cmx1BwNxbBYRZhln8=
|
||||
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=
|
||||
@ -981,8 +981,8 @@ github.com/miekg/dns v0.0.0-20161006100029-fc4e1e2843d8/go.mod h1:W1PPwlIAgtquWB
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
|
||||
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
|
||||
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
|
||||
github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
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=
|
||||
@ -1310,8 +1310,8 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPS
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230227162855-a1f09bafccb3 h1:ddXRTeqEr7LcHQEtkd6gogZOh9tI1Y6Gappr0a1oa2I=
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230227162855-a1f09bafccb3/go.mod h1:3sus4zjoUv1GB7eDLL60QaPkUnXJCWBpjvbe0jWifeY=
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230419082146-a94d9ed7202b h1:nkvOl8TCj/mErADnwFFynjxBtC+hHsrESw6rw56JGmg=
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230419082146-a94d9ed7202b/go.mod h1:3sus4zjoUv1GB7eDLL60QaPkUnXJCWBpjvbe0jWifeY=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
@ -1449,9 +1449,9 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20161013035702-8b4af36cd21a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -1528,8 +1528,9 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@ -1693,8 +1694,9 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@ -1703,8 +1705,9 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -1718,8 +1721,9 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -1809,9 +1813,9 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
|
||||
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -3,13 +3,11 @@ package app
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"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"
|
||||
@ -17,19 +15,31 @@ import (
|
||||
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"
|
||||
fetchModule "forge.cadoles.com/arcad/edge/pkg/module/fetch"
|
||||
netModule "forge.cadoles.com/arcad/edge/pkg/module/net"
|
||||
shareModule "forge.cadoles.com/arcad/edge/pkg/module/share"
|
||||
shareSqlite "forge.cadoles.com/arcad/edge/pkg/module/share/sqlite"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type Dependencies struct {
|
||||
Bus bus.Bus
|
||||
DocumentStore storage.DocumentStore
|
||||
BlobStore storage.BlobStore
|
||||
KeySet jwk.Set
|
||||
AppRepository appModule.Repository
|
||||
AppID app.ID
|
||||
ShareRepository shareModule.Repository
|
||||
}
|
||||
|
||||
const defaultSQLiteParams = "?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000"
|
||||
|
||||
func (c *Controller) getHandlerOptions(ctx context.Context, appKey string, specs *spec.Spec) ([]edgeHTTP.HandlerOptionFunc, error) {
|
||||
@ -44,23 +54,46 @@ func (c *Controller) getHandlerOptions(ctx context.Context, appKey string, specs
|
||||
return nil, errors.Wrapf(err, "could not open database file '%s'", dbFile)
|
||||
}
|
||||
|
||||
shareDBFile := filepath.Join(dataDir, "shared.sqlite")
|
||||
shareDB, err := sqlite.Open(shareDBFile + defaultSQLiteParams)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not open database file '%s'", shareDBFile)
|
||||
}
|
||||
|
||||
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)
|
||||
mounts := make([]func(r chi.Router), 0)
|
||||
|
||||
authMount, err := getAuthMount(specs.Config.Auth, keySet)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
bus := memory.NewBus()
|
||||
modules := c.getAppModules(bus, db, specs, keySet)
|
||||
if authMount != nil {
|
||||
mounts = append(mounts, authMount)
|
||||
}
|
||||
|
||||
mounts = append(mounts, appModule.Mount(c.appRepository))
|
||||
|
||||
deps := Dependencies{
|
||||
Bus: memory.NewBus(),
|
||||
DocumentStore: sqlite.NewDocumentStoreWithDB(db),
|
||||
BlobStore: sqlite.NewBlobStoreWithDB(db),
|
||||
KeySet: keySet,
|
||||
AppRepository: c.appRepository,
|
||||
AppID: app.ID(appKey),
|
||||
ShareRepository: shareSqlite.NewRepositoryWithDB(shareDB),
|
||||
}
|
||||
|
||||
modules := c.getAppModules(deps)
|
||||
|
||||
options := []edgeHTTP.HandlerOptionFunc{
|
||||
edgeHTTP.WithBus(bus),
|
||||
edgeHTTP.WithBus(deps.Bus),
|
||||
edgeHTTP.WithServerModules(modules...),
|
||||
edgeHTTP.WithHTTPMounts(mounts...),
|
||||
}
|
||||
|
||||
return options, nil
|
||||
@ -239,44 +272,19 @@ func createResolveAppURL(specs *spec.Spec) (ResolveAppURLFunc, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Controller) getAppModules(bus bus.Bus, db *sql.DB, spec *appSpec.Spec, keySet jwk.Set) []app.ServerModuleFactory {
|
||||
ds := sqlite.NewDocumentStoreWithDB(db)
|
||||
bs := sqlite.NewBlobStoreWithDB(db)
|
||||
|
||||
func (c *Controller) getAppModules(deps Dependencies) []app.ServerModuleFactory {
|
||||
return []app.ServerModuleFactory{
|
||||
module.ContextModuleFactory(),
|
||||
module.ConsoleModuleFactory(),
|
||||
cast.CastModuleFactory(),
|
||||
module.LifecycleModuleFactory(),
|
||||
netModule.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(c.appRepository),
|
||||
fetchModule.ModuleFactory(bus),
|
||||
netModule.ModuleFactory(deps.Bus),
|
||||
module.RPCModuleFactory(deps.Bus),
|
||||
module.StoreModuleFactory(deps.DocumentStore),
|
||||
blob.ModuleFactory(deps.Bus, deps.BlobStore),
|
||||
authModuleFactory(deps.KeySet),
|
||||
appModule.ModuleFactory(deps.AppRepository),
|
||||
fetchModule.ModuleFactory(deps.Bus),
|
||||
shareModule.ModuleFactory(deps.AppID, deps.ShareRepository),
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
@ -77,6 +78,8 @@ func (r *AppRepository) List(ctx context.Context) ([]*app.Manifest, error) {
|
||||
manifests = append(manifests, manifest)
|
||||
}
|
||||
|
||||
sort.Sort(ByID(manifests))
|
||||
|
||||
return manifests, nil
|
||||
}
|
||||
|
||||
@ -126,3 +129,9 @@ func NewAppRepository() *AppRepository {
|
||||
}
|
||||
|
||||
var _ appModule.Repository = &AppRepository{}
|
||||
|
||||
type ByID []*app.Manifest
|
||||
|
||||
func (a ByID) Len() int { return len(a) }
|
||||
func (a ByID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByID) Less(i, j int) bool { return a[i].ID > a[j].ID }
|
||||
|
93
internal/agent/controller/app/auth_module.go
Normal file
93
internal/agent/controller/app/auth_module.go
Normal file
@ -0,0 +1,93 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"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/module"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/auth"
|
||||
authModule "forge.cadoles.com/arcad/edge/pkg/module/auth"
|
||||
authHTTP "forge.cadoles.com/arcad/edge/pkg/module/auth/http"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
RoleVisitor string = "visitor"
|
||||
RoleUser string = "user"
|
||||
RoleSuperuser string = "superuser"
|
||||
RoleAdmin string = "admin"
|
||||
RoleSuperadmin string = "superadmin"
|
||||
)
|
||||
|
||||
func authModuleFactory(keySet jwk.Set) app.ServerModuleFactory {
|
||||
return module.Extends(
|
||||
authModule.ModuleFactory(
|
||||
authModule.WithJWT(func() (jwk.Set, error) {
|
||||
return keySet, nil
|
||||
}),
|
||||
),
|
||||
func(o *goja.Object) {
|
||||
if err := o.Set("ROLE_VISITOR", RoleVisitor); err != nil {
|
||||
panic(errors.New("could not set 'ROLE_VISITOR' property"))
|
||||
}
|
||||
|
||||
if err := o.Set("ROLE_USER", RoleUser); err != nil {
|
||||
panic(errors.New("could not set 'ROLE_USER' property"))
|
||||
}
|
||||
|
||||
if err := o.Set("ROLE_SUPERUSER", RoleSuperuser); err != nil {
|
||||
panic(errors.New("could not set 'ROLE_SUPERUSER' property"))
|
||||
}
|
||||
|
||||
if err := o.Set("ROLE_ADMIN", RoleAdmin); err != nil {
|
||||
panic(errors.New("could not set 'ROLE_ADMIN' property"))
|
||||
}
|
||||
|
||||
if err := o.Set("ROLE_SUPERADMIN", RoleSuperadmin); err != nil {
|
||||
panic(errors.New("could not set 'ROLE_SUPERADMIN' property"))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func getAuthMount(auth *spec.Auth, keySet jwk.Set) (auth.MountFunc, error) {
|
||||
switch {
|
||||
case auth.Local != nil:
|
||||
var rawKey any = auth.Local.Key
|
||||
if strKey, ok := rawKey.(string); ok {
|
||||
rawKey = []byte(strKey)
|
||||
}
|
||||
|
||||
key, err := jwk.FromRaw(rawKey)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
cookieDuration := defaultCookieDuration
|
||||
if auth.Local.CookieDuration != "" {
|
||||
cookieDuration, err = time.ParseDuration(auth.Local.CookieDuration)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return authModule.Mount(
|
||||
authHTTP.NewLocalHandler(
|
||||
jwa.HS256, key,
|
||||
authHTTP.WithRoutePrefix("/auth"),
|
||||
authHTTP.WithAccounts(auth.Local.Accounts...),
|
||||
authHTTP.WithCookieOptions(getCookieDomain, cookieDuration),
|
||||
),
|
||||
authModule.WithJWT(func() (jwk.Set, error) {
|
||||
return keySet, nil
|
||||
}),
|
||||
), nil
|
||||
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
"github.com/pkg/errors"
|
||||
@ -276,7 +277,21 @@ func (c *Controller) ensureAppBundle(ctx context.Context, appID string, spec spe
|
||||
return nil, "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return bdle, "", nil
|
||||
manifest, err := app.LoadManifest(bdle)
|
||||
if err != nil {
|
||||
return nil, "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
valid, err := validateManifest(manifest)
|
||||
if err != nil {
|
||||
return nil, "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
if !valid {
|
||||
return nil, "", errors.New("bundle's manifest is invalid")
|
||||
}
|
||||
|
||||
return bdle, spec.SHA256Sum, nil
|
||||
}
|
||||
|
||||
func (c *Controller) downloadFile(url string, sha256sum string, dest string) error {
|
||||
|
19
internal/agent/controller/app/manifest.go
Normal file
19
internal/agent/controller/app/manifest.go
Normal file
@ -0,0 +1,19 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/app/metadata"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func validateManifest(manifest *app.Manifest) (bool, error) {
|
||||
valid, err := manifest.Validate(
|
||||
metadata.WithMinimumRoleValidator(RoleVisitor, RoleUser, RoleSuperuser, RoleAdmin, RoleSuperadmin),
|
||||
metadata.WithNamedPathsValidator(metadata.NamedPathAdmin, metadata.NamedPathIcon),
|
||||
)
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return valid, nil
|
||||
}
|
@ -12,14 +12,11 @@ import (
|
||||
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"
|
||||
authHTTP "forge.cadoles.com/arcad/edge/pkg/module/auth/http"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
_ "forge.cadoles.com/Cadoles/emissary/internal/imports/passwd"
|
||||
@ -47,7 +44,9 @@ func (s *Server) Start(ctx context.Context, addr string) (err error) {
|
||||
|
||||
router := chi.NewRouter()
|
||||
|
||||
router.Use(middleware.RealIP)
|
||||
router.Use(middleware.Logger)
|
||||
router.Use(middleware.Compress(5))
|
||||
|
||||
handler := edgeHTTP.NewHandler(s.handlerOptions...)
|
||||
if err := handler.Load(s.bundle); err != nil {
|
||||
@ -61,12 +60,6 @@ func (s *Server) Start(ctx context.Context, addr string) (err error) {
|
||||
s.config.UnexpectedHostRedirect.AcceptedHostPatterns...,
|
||||
))
|
||||
}
|
||||
|
||||
if s.config.Auth != nil {
|
||||
if err := s.configureAuth(router, s.config.Auth); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
router.Handle("/*", handler)
|
||||
@ -135,38 +128,6 @@ func (s *Server) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) configureAuth(router chi.Router, auth *spec.Auth) error {
|
||||
switch {
|
||||
case auth.Local != nil:
|
||||
var rawKey any = 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 auth.Local.CookieDuration != "" {
|
||||
cookieDuration, err = time.ParseDuration(auth.Local.CookieDuration)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
router.Handle("/auth/*", authHTTP.NewLocalHandler(
|
||||
jwa.HS256, key,
|
||||
authHTTP.WithRoutePrefix("/auth"),
|
||||
authHTTP.WithAccounts(auth.Local.Accounts...),
|
||||
authHTTP.WithCookieOptions(getCookieDomain, cookieDuration),
|
||||
))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewServer(bundle bundle.Bundle, config *spec.Config, handlerOptions ...edgeHTTP.HandlerOptionFunc) *Server {
|
||||
return &Server{
|
||||
bundle: bundle,
|
||||
@ -182,7 +143,7 @@ func getCookieDomain(r *http.Request) (string, error) {
|
||||
}
|
||||
|
||||
// If host is an IP address
|
||||
if wildcard.Match(host, "*.*.*.*") {
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
|
@ -20,9 +20,24 @@ type AgentRepository struct {
|
||||
|
||||
// DeleteSpec implements datastore.AgentRepository.
|
||||
func (r *AgentRepository) DeleteSpec(ctx context.Context, agentID datastore.AgentID, name string) error {
|
||||
query := `DELETE FROM specs WHERE agent_id = $1 AND name = $2`
|
||||
err := r.withTx(ctx, func(tx *sql.Tx) error {
|
||||
exists, err := r.agentExists(ctx, tx, agentID)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
_, err := r.db.ExecContext(ctx, query, agentID, name)
|
||||
if !exists {
|
||||
return errors.WithStack(datastore.ErrNotFound)
|
||||
}
|
||||
|
||||
query := `DELETE FROM specs WHERE agent_id = $1 AND name = $2`
|
||||
|
||||
if _, err = tx.ExecContext(ctx, query, agentID, name); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
@ -34,41 +49,57 @@ func (r *AgentRepository) DeleteSpec(ctx context.Context, agentID datastore.Agen
|
||||
func (r *AgentRepository) GetSpecs(ctx context.Context, agentID datastore.AgentID) ([]*datastore.Spec, error) {
|
||||
specs := make([]*datastore.Spec, 0)
|
||||
|
||||
query := `
|
||||
err := r.withTx(ctx, func(tx *sql.Tx) error {
|
||||
exists, err := r.agentExists(ctx, tx, agentID)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return errors.WithStack(datastore.ErrNotFound)
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT id, name, revision, data, created_at, updated_at
|
||||
FROM specs
|
||||
WHERE agent_id = $1
|
||||
`
|
||||
`
|
||||
|
||||
rows, err := r.db.QueryContext(ctx, query, agentID)
|
||||
rows, err := tx.QueryContext(ctx, query, agentID)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := rows.Close(); err != nil {
|
||||
logger.Error(ctx, "could not close rows", logger.E(errors.WithStack(err)))
|
||||
}
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
spec := &datastore.Spec{}
|
||||
|
||||
data := JSONMap{}
|
||||
|
||||
if err := rows.Scan(&spec.ID, &spec.Name, &spec.Revision, &data, &spec.CreatedAt, &spec.UpdatedAt); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
spec.Data = data
|
||||
|
||||
specs = append(specs, spec)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := rows.Close(); err != nil {
|
||||
logger.Error(ctx, "could not close rows", logger.E(errors.WithStack(err)))
|
||||
}
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
spec := &datastore.Spec{}
|
||||
|
||||
data := JSONMap{}
|
||||
|
||||
if err := rows.Scan(&spec.ID, &spec.Name, &spec.Revision, &data, &spec.CreatedAt, &spec.UpdatedAt); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
spec.Data = data
|
||||
|
||||
specs = append(specs, spec)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return specs, nil
|
||||
}
|
||||
|
||||
@ -77,6 +108,15 @@ func (r *AgentRepository) UpdateSpec(ctx context.Context, agentID datastore.Agen
|
||||
spec := &datastore.Spec{}
|
||||
|
||||
err := r.withTx(ctx, func(tx *sql.Tx) error {
|
||||
exists, err := r.agentExists(ctx, tx, agentID)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return errors.WithStack(datastore.ErrNotFound)
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
query := `
|
||||
@ -96,7 +136,7 @@ func (r *AgentRepository) UpdateSpec(ctx context.Context, agentID datastore.Agen
|
||||
|
||||
data := JSONMap{}
|
||||
|
||||
err := row.Scan(&spec.ID, &spec.Name, &spec.Revision, &data, &spec.CreatedAt, &spec.UpdatedAt)
|
||||
err = row.Scan(&spec.ID, &spec.Name, &spec.Revision, &data, &spec.CreatedAt, &spec.UpdatedAt)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return errors.WithStack(datastore.ErrUnexpectedRevision)
|
||||
@ -472,8 +512,28 @@ func (r *AgentRepository) Update(ctx context.Context, id datastore.AgentID, opts
|
||||
return agent, nil
|
||||
}
|
||||
|
||||
func (r *AgentRepository) agentExists(ctx context.Context, tx *sql.Tx, agentID datastore.AgentID) (bool, error) {
|
||||
row := tx.QueryRowContext(ctx, `SELECT count(id) FROM agents WHERE id = $1`, agentID)
|
||||
|
||||
var count int
|
||||
|
||||
if err := row.Scan(&count); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return false, errors.WithStack(datastore.ErrNotFound)
|
||||
}
|
||||
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return false, errors.WithStack(datastore.ErrNotFound)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (r *AgentRepository) withTx(ctx context.Context, fn func(*sql.Tx) error) error {
|
||||
tx, err := r.db.Begin()
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
46
internal/datastore/sqlite/agent_repository_test.go
Normal file
46
internal/datastore/sqlite/agent_repository_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/datastore/testsuite"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/migrate"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
func TestSQLiteAgentRepository(t *testing.T) {
|
||||
logger.SetLevel(logger.LevelDebug)
|
||||
|
||||
file := "testdata/agent_repository_test.sqlite"
|
||||
|
||||
if err := os.Remove(file); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
dsn := fmt.Sprintf("%s?_pragma=foreign_keys(1)&_pragma=busy_timeout=%d", file, (60 * time.Second).Milliseconds())
|
||||
|
||||
migr, err := migrate.New("../../../migrations", "sqlite", "sqlite://"+dsn)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
if err := migr.Up(); err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite", dsn)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
repo := NewAgentRepository(db)
|
||||
|
||||
testsuite.TestAgentRepository(t, repo)
|
||||
}
|
1
internal/datastore/sqlite/testdata/.gitignore
vendored
Normal file
1
internal/datastore/sqlite/testdata/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.sqlite*
|
14
internal/datastore/testsuite/agent_repository.go
Normal file
14
internal/datastore/testsuite/agent_repository.go
Normal file
@ -0,0 +1,14 @@
|
||||
package testsuite
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||
)
|
||||
|
||||
func TestAgentRepository(t *testing.T, repo datastore.AgentRepository) {
|
||||
t.Run("Cases", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
runAgentRepositoryTests(t, repo)
|
||||
})
|
||||
}
|
129
internal/datastore/testsuite/agent_repository_cases.go
Normal file
129
internal/datastore/testsuite/agent_repository_cases.go
Normal file
@ -0,0 +1,129 @@
|
||||
package testsuite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/mdns/spec"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/jwk"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type agentRepositoryTestCase struct {
|
||||
Name string
|
||||
Skip bool
|
||||
Run func(ctx context.Context, repo datastore.AgentRepository) error
|
||||
}
|
||||
|
||||
var agentRepositoryTestCases = []agentRepositoryTestCase{
|
||||
{
|
||||
Name: "Create a new agent",
|
||||
Run: func(ctx context.Context, repo datastore.AgentRepository) error {
|
||||
thumbprint := "foo"
|
||||
keySet := jwk.NewSet()
|
||||
var metadata map[string]any
|
||||
|
||||
agent, err := repo.Create(ctx, thumbprint, keySet, metadata)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if agent.CreatedAt.IsZero() {
|
||||
return errors.Errorf("agent.CreatedAt should not be zero time")
|
||||
}
|
||||
|
||||
if agent.UpdatedAt.IsZero() {
|
||||
return errors.Errorf("agent.UpdatedAt should not be zero time")
|
||||
}
|
||||
|
||||
if e, g := datastore.AgentStatusPending, agent.Status; e != g {
|
||||
return errors.Errorf("agent.Status: expected '%v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Try to update spec for an unexistant agent",
|
||||
Run: func(ctx context.Context, repo datastore.AgentRepository) error {
|
||||
var unexistantAgentID datastore.AgentID = 9999
|
||||
var specData map[string]any
|
||||
|
||||
agent, err := repo.UpdateSpec(ctx, unexistantAgentID, string(spec.Name), 0, specData)
|
||||
if err == nil {
|
||||
return errors.New("error should not be nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, datastore.ErrNotFound) {
|
||||
return errors.Errorf("error should be datastore.ErrNotFound, got '%+v'", err)
|
||||
}
|
||||
|
||||
if agent != nil {
|
||||
return errors.New("agent should be nil")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Try to delete spec of an unexistant agent",
|
||||
Run: func(ctx context.Context, repo datastore.AgentRepository) error {
|
||||
var unexistantAgentID datastore.AgentID = 9999
|
||||
|
||||
err := repo.DeleteSpec(ctx, unexistantAgentID, string(spec.Name))
|
||||
if err == nil {
|
||||
return errors.New("error should not be nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, datastore.ErrNotFound) {
|
||||
return errors.Errorf("error should be datastore.ErrNotFound, got '%+v'", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Try to get specs of an unexistant agent",
|
||||
Run: func(ctx context.Context, repo datastore.AgentRepository) error {
|
||||
var unexistantAgentID datastore.AgentID = 9999
|
||||
|
||||
specs, err := repo.GetSpecs(ctx, unexistantAgentID)
|
||||
if err == nil {
|
||||
return errors.New("error should not be nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, datastore.ErrNotFound) {
|
||||
return errors.Errorf("error should be datastore.ErrNotFound, got '%+v'", err)
|
||||
}
|
||||
|
||||
if specs != nil {
|
||||
return errors.Errorf("specs should be nil, got '%+v'", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runAgentRepositoryTests(t *testing.T, repo datastore.AgentRepository) {
|
||||
for _, tc := range agentRepositoryTestCases {
|
||||
func(tc agentRepositoryTestCase) {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if tc.Skip {
|
||||
t.SkipNow()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if err := tc.Run(ctx, repo); err != nil {
|
||||
t.Errorf("%+v", errors.WithStack(err))
|
||||
}
|
||||
})
|
||||
}(tc)
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ package migrate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
@ -23,8 +22,6 @@ func New(migrationDir, driver, dsn string) (*migrate.Migrate, error) {
|
||||
fmt.Sprintf("file://%s/%s", migrationDir, driver),
|
||||
dsn,
|
||||
)
|
||||
|
||||
log.Println(migrationDir, driver, dsn)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
@ -58,6 +58,12 @@ func (s *Server) updateSpec(w http.ResponseWriter, r *http.Request) {
|
||||
updateSpecReq.SpecData(),
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, datastore.ErrNotFound) {
|
||||
api.ErrorResponse(w, http.StatusNotFound, ErrCodeNotFound, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if errors.Is(err, datastore.ErrUnexpectedRevision) {
|
||||
api.ErrorResponse(w, http.StatusConflict, ErrCodeUnexpectedRevision, nil)
|
||||
|
||||
@ -87,6 +93,12 @@ func (s *Server) getAgentSpecs(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
specs, err := s.agentRepo.GetSpecs(ctx, agentID)
|
||||
if err != nil {
|
||||
if errors.Is(err, datastore.ErrNotFound) {
|
||||
api.ErrorResponse(w, http.StatusNotFound, ErrCodeNotFound, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not list specs", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, ErrCodeUnknownError, nil)
|
||||
|
||||
|
@ -4,7 +4,7 @@ ARG HTTP_PROXY=
|
||||
ARG HTTPS_PROXY=
|
||||
ARG http_proxy=
|
||||
ARG https_proxy=
|
||||
ARG GO_VERSION=1.19.2
|
||||
ARG GO_VERSION=1.20.2
|
||||
|
||||
# Install dev environment dependencies
|
||||
RUN export DEBIAN_FRONTEND=noninteractive &&\
|
||||
|
@ -1,34 +1,21 @@
|
||||
{
|
||||
"apps": {
|
||||
"edge.portal": {
|
||||
"url": "https://emissary.cadol.es/files/apps/edge.portal_v2023.4.5-45546c4.zip",
|
||||
"sha256sum": "c83e7e4b3785f5f4d3fcae7cad334819626015b11b446520aa79f42176a2744d",
|
||||
"address": ":8082",
|
||||
"format": "zip"
|
||||
},
|
||||
"app.arcad.edge.hextris": {
|
||||
"url": "https://emissary.cadol.es/files/apps/app.arcad.edge.hextris_v2023.3.22-33ece28.zip",
|
||||
"sha256sum": "5f9f3c8d6f22796beb051d747d7ff12efa17af9d1552c0ab08baef13703a2aba",
|
||||
"url": "https://emissary.cadol.es/files/apps/app.arcad.edge.hextris_v2023.4.20-2bbbe94.zip",
|
||||
"sha256sum": "67942ef4b623c46308c3f640b534bd4cb6b1d6021a422e40b62ab97658ba4586",
|
||||
"address": ":8083",
|
||||
"format": "zip"
|
||||
},
|
||||
"edge.sdk.client.test": {
|
||||
"url": "https://emissary.cadol.es/files/apps/edge.sdk.client.test_v2023.4.2-f08f645.zip",
|
||||
"sha256sum": "8b48388c817802ebeb38907b3a42f1189dc0759f94c5f33de4546c1a7ebfc784",
|
||||
"url": "https://emissary.cadol.es/files/apps/edge.sdk.client.test_v2023.4.20-20c4189.zip",
|
||||
"sha256sum": "1edeb4aa75c1675db49cf27367b1537234a04526848ea6657931ca63f26e5dae",
|
||||
"address": ":8084",
|
||||
"format": "zip"
|
||||
},
|
||||
"arcad.diffusion": {
|
||||
"url": "https://emissary.cadol.es/files/apps/arcad.diffusion_v2023.4.5-ffcd1c7.zip",
|
||||
"sha256sum": "a51a961212470ce1de4527aaaec9e8e0286a978ec675ff9df29b2029daf05a55",
|
||||
"address": ":8085",
|
||||
"format": "zip"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"appUrlResolving": {
|
||||
"ifaceMappings": {
|
||||
"lo": "http://{{ .DeviceIP }}:{{ .AppPort }}",
|
||||
"wlp4s0": "http://{{ .DeviceIP }}:{{ .AppPort }}",
|
||||
"enp0s31f6": "http://{{ .DeviceIP }}:{{ .AppPort }}"
|
||||
},
|
||||
@ -48,11 +35,44 @@
|
||||
"algo": "plain",
|
||||
"password": "admin",
|
||||
"claims": {
|
||||
"arcad_role": "admin",
|
||||
"arcad_tenant": "x86",
|
||||
"edge_role": "admin",
|
||||
"edge_tenant": "emissary-dev",
|
||||
"preferred_username": "Admin",
|
||||
"sub": "admin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"username": "superadmin",
|
||||
"algo": "plain",
|
||||
"password": "superadmin",
|
||||
"claims": {
|
||||
"edge_role": "superadmin",
|
||||
"edge_tenant": "emissary-dev",
|
||||
"preferred_username": "SuperAdmin",
|
||||
"sub": "superadmin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"username": "user",
|
||||
"algo": "plain",
|
||||
"password": "user",
|
||||
"claims": {
|
||||
"edge_role": "user",
|
||||
"edge_tenant": "emissary-dev",
|
||||
"preferred_username": "User",
|
||||
"sub": "user"
|
||||
}
|
||||
},
|
||||
{
|
||||
"username": "superuser",
|
||||
"algo": "plain",
|
||||
"password": "superuser",
|
||||
"claims": {
|
||||
"edge_role": "superuser",
|
||||
"edge_tenant": "emissary-dev",
|
||||
"preferred_username": "SuperUser",
|
||||
"sub": "superuser"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -5,11 +5,6 @@
|
||||
"port": 8080,
|
||||
"host": "arcad"
|
||||
},
|
||||
"portal": {
|
||||
"type": "_http._tcp",
|
||||
"port": 8080,
|
||||
"host": "arcad-portal"
|
||||
},
|
||||
"hextris": {
|
||||
"type": "_http._tcp",
|
||||
"port": 8080,
|
||||
@ -19,11 +14,6 @@
|
||||
"type": "_http._tcp",
|
||||
"port": 8080,
|
||||
"host": "arcad-test"
|
||||
},
|
||||
"diffusion": {
|
||||
"type": "_http._tcp",
|
||||
"port": 8080,
|
||||
"host": "arcad-diffusion"
|
||||
}
|
||||
}
|
||||
}
|
@ -3,10 +3,6 @@
|
||||
"main": {
|
||||
"address": ":8080",
|
||||
"mappings": [
|
||||
{
|
||||
"hostPattern": "portal.localhost.arcad.lan:*",
|
||||
"target": "http://localhost:8082"
|
||||
},
|
||||
{
|
||||
"hostPattern": "hextris.localhost.arcad.lan:*",
|
||||
"target": "http://localhost:8083"
|
||||
@ -15,14 +11,6 @@
|
||||
"hostPattern": "test.localhost.arcad.lan:*",
|
||||
"target": "http://localhost:8084"
|
||||
},
|
||||
{
|
||||
"hostPattern": "diffusion.localhost.arcad.lan:*",
|
||||
"target": "http://localhost:8085"
|
||||
},
|
||||
{
|
||||
"hostPattern": "arcad-portal.local:*",
|
||||
"target": "http://localhost:8082"
|
||||
},
|
||||
{
|
||||
"hostPattern": "arcad-hextris.local:*",
|
||||
"target": "http://localhost:8083"
|
||||
@ -31,13 +19,9 @@
|
||||
"hostPattern": "arcad-test.local:*",
|
||||
"target": "http://localhost:8084"
|
||||
},
|
||||
{
|
||||
"hostPattern": "arcad-diffusion.local:*",
|
||||
"target": "http://localhost:8085"
|
||||
},
|
||||
{
|
||||
"hostPattern": "*",
|
||||
"target": "http://localhost:8082"
|
||||
"target": "http://localhost:8084"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user