Compare commits

...
Sign in to create a new pull request.

79 commits

Author SHA1 Message Date
f8c0ee62ec
Merge pull request 'fix and test' (#20) from feature-15 into main
Reviewed-on: #20
2026-03-31 20:03:00 +00:00
c7d20b4124
f-19: test: ajouter des test
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-03-31 20:05:30 +02:00
3288a2a413
f-15: ci: add upload scripts
All checks were successful
Pre Release Workflow / set-release-target (push) Successful in 36s
Pre Release Workflow / build (agent, amd64, linux) (push) Successful in 1m17s
Pre Release Workflow / build (db, amd64, linux) (push) Successful in 1m47s
Pre Release Workflow / build (dhcp, amd64, linux) (push) Successful in 1m27s
Pre Release Workflow / build (metacli, amd64, linux) (push) Successful in 1m28s
Pre Release Workflow / build (metadata, amd64, linux) (push) Successful in 1m29s
Pre Release Workflow / build (vpc, amd64, linux) (push) Successful in 1m31s
Pre Release Workflow / upload-scripts (run-dnsmasq-in-netns.sh) (push) Successful in 6s
Pre Release Workflow / prerelease (push) Successful in 11s
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-03-30 23:40:12 +02:00
deac1afe9f
f-15: script: add script for running dhcp in netns
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-03-30 23:40:12 +02:00
02e558e0e2
f-15: systemd: add systemd unit for dhcp
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-03-30 23:40:11 +02:00
cfbac0034b
f-15: ci: add dhcp binary build
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-03-30 23:20:24 +02:00
a346876cfb
f-15: code add code for binary
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-03-30 23:19:34 +02:00
5d980514b8
f-15: code: add generate dhcp file
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-03-30 23:16:52 +02:00
2bbe9b40f7
Merge pull request 'feature-14' (#18) from feature-14 into main
Reviewed-on: #18
2026-03-24 19:38:41 +00:00
d3402dd163
f-14: fix: change return code
All checks were successful
Pre Release Workflow / set-release-target (push) Successful in 1s
Pre Release Workflow / build (agent, amd64, linux) (push) Successful in 1m59s
Pre Release Workflow / build (db, amd64, linux) (push) Successful in 1m26s
Pre Release Workflow / build (metacli, amd64, linux) (push) Successful in 1m26s
Pre Release Workflow / build (metadata, amd64, linux) (push) Successful in 1m25s
Pre Release Workflow / build (vpc, amd64, linux) (push) Successful in 1m27s
Pre Release Workflow / prerelease (push) Successful in 12s
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-03-24 20:28:49 +01:00
c1b9b3faa4
f-14: fix: add return code
Some checks failed
Pre Release Workflow / set-release-target (push) Successful in 1s
Pre Release Workflow / build (db, amd64, linux) (push) Has been cancelled
Pre Release Workflow / build (metacli, amd64, linux) (push) Has been cancelled
Pre Release Workflow / build (metadata, amd64, linux) (push) Has been cancelled
Pre Release Workflow / build (vpc, amd64, linux) (push) Has been cancelled
Pre Release Workflow / prerelease (push) Has been cancelled
Pre Release Workflow / build (agent, amd64, linux) (push) Has been cancelled
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-03-24 20:24:37 +01:00
88787fd8f7
f-14: code: add check feature
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-03-23 18:24:06 +01:00
019ca104e8
Merge pull request 'feature-14' (#17) from feature-14 into main
Reviewed-on: #17
2026-03-23 16:00:53 +00:00
0b9714ce10
f-14: code: add delete operation #14
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-27 22:24:47 +01:00
f9c1cd7d32
f-14: code: add db usage in vpc creation #14
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-26 20:10:56 +01:00
44f01c2373
f-14: clean: remove not used setBridgeSTP
All checks were successful
Pre Release Workflow / set-release-target (push) Successful in 1s
Pre Release Workflow / build (agent, amd64, linux) (push) Successful in 1m26s
Pre Release Workflow / build (db, amd64, linux) (push) Successful in 1m33s
Pre Release Workflow / build (metacli, amd64, linux) (push) Successful in 1m31s
Pre Release Workflow / build (metadata, amd64, linux) (push) Successful in 1m29s
Pre Release Workflow / build (vpc, amd64, linux) (push) Successful in 1m31s
Pre Release Workflow / prerelease (push) Successful in 11s
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-19 22:42:13 +01:00
2ea3c6dd14
f-14: fix: rename interface name
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-19 22:41:39 +01:00
6f40b3e921
f-14: code: add vpc in prerelease bin #14
All checks were successful
Pre Release Workflow / set-release-target (push) Successful in 1s
Pre Release Workflow / build (agent, amd64, linux) (push) Successful in 1m7s
Pre Release Workflow / build (db, amd64, linux) (push) Successful in 1m50s
Pre Release Workflow / build (metacli, amd64, linux) (push) Successful in 1m34s
Pre Release Workflow / build (metadata, amd64, linux) (push) Successful in 1m31s
Pre Release Workflow / build (vpc, amd64, linux) (push) Successful in 1m32s
Pre Release Workflow / prerelease (push) Successful in 11s
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-18 22:32:49 +01:00
0b797d1c0c
f-14: bin: remove netns bin #14
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-18 22:32:21 +01:00
d050568638
f-14: bin: update bin #14
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-18 22:30:38 +01:00
9fb4d10d27
f-14: code: add create and delete vpc #14
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-18 22:28:38 +01:00
d3b471b24a
f-14: code: add set master bridge #14
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-17 20:59:05 +01:00
db222b383c
f-14: code: add delete interface function #14
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-16 22:15:23 +01:00
fd4c7e9a3a
f-14: code: add an up and down ip #14
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-15 22:54:22 +01:00
9420a9f7b4
f-14: code: add create bridge code #14
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-13 10:53:43 +01:00
5a38e09b85
f-14: bin: add a new binarie for test #14
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-11 18:38:59 +01:00
15a913643e
f-14: code: add a new package internal #14
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-11 18:38:02 +01:00
fc62d5a524
f-14: package: add a new package
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-11 18:37:31 +01:00
487972e698
Merge pull request 'feature-10' (#16) from feature-10 into main
Reviewed-on: #16
2026-01-10 20:26:48 +00:00
d1559b2ba0
f-10: ci: change ci format #10
All checks were successful
Pre Release Workflow / set-release-target (push) Successful in 1s
Pre Release Workflow / build (agent, amd64, linux) (push) Successful in 1m28s
Pre Release Workflow / build (db, amd64, linux) (push) Successful in 1m31s
Pre Release Workflow / build (metacli, amd64, linux) (push) Successful in 1m32s
Pre Release Workflow / build (metadata, amd64, linux) (push) Successful in 1m29s
Pre Release Workflow / build (netns, amd64, linux) (push) Successful in 1m24s
Pre Release Workflow / prerelease (push) Successful in 11s
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-08 23:58:31 +01:00
0d3d59a019
f-10: bin: add a new binarie #10
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-08 23:57:35 +01:00
a650a34fc3
f-10: code: add function to call a function in a netns #10
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-08 23:47:52 +01:00
454005d6ac
f-10: code: add create and delete netns #10
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-08 23:37:35 +01:00
473e1d0108
Merge pull request 'feature-11' (#13) from feature-11 into main
Reviewed-on: #13
2026-01-08 22:13:03 +00:00
00852ec1d3
f-11: debug: remove debug print from db kv interface #11
All checks were successful
Pre Release Workflow / set-release-target (push) Successful in 1s
Pre Release Workflow / build (agent, amd64, linux) (push) Successful in 1m23s
Pre Release Workflow / build (db, amd64, linux) (push) Successful in 1m30s
Pre Release Workflow / build (metacli, amd64, linux) (push) Successful in 1m29s
Pre Release Workflow / build (metadata, amd64, linux) (push) Successful in 1m30s
Pre Release Workflow / prerelease (push) Successful in 11s
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-07 23:30:01 +01:00
07cf77ba7d
f-11: fix: rename meta_cli to metacli
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-07 23:24:43 +01:00
a8ffeebb72
f-11: code: move start and stop metadata #11
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-07 23:16:58 +01:00
9780947805
f-11: code: change struct name
All checks were successful
Pre Release Workflow / set-release-target (push) Successful in 1s
Pre Release Workflow / build (agent, amd64, linux) (push) Successful in 1m17s
Pre Release Workflow / build (db, amd64, linux) (push) Successful in 1m45s
Pre Release Workflow / build (meta_cli, amd64, linux) (push) Successful in 1m29s
Pre Release Workflow / build (metadata, amd64, linux) (push) Successful in 1m45s
Pre Release Workflow / prerelease (push) Successful in 11s
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-07 21:10:37 +01:00
37e345609e
f-11: fix: clean error in nocloud work #11
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-07 21:09:04 +01:00
24e3de8088
f-11: improvement: add a dryrun function
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-07 21:06:47 +01:00
44b58b3614
f-11: fix: clean print not usefull #11
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-07 21:06:03 +01:00
c1c93f9618
Merge pull request 'feature-8' (#9) from feature-8 into main
All checks were successful
Pre Release Workflow / set-release-target (push) Successful in 1s
Pre Release Workflow / build (agent, amd64, linux) (push) Successful in 1m23s
Pre Release Workflow / build (db, amd64, linux) (push) Successful in 1m31s
Pre Release Workflow / build (meta_cli, amd64, linux) (push) Successful in 1m33s
Pre Release Workflow / build (metadata, amd64, linux) (push) Successful in 1m28s
Pre Release Workflow / prerelease (push) Successful in 11s
Reviewed-on: #9
2026-01-04 16:51:22 +00:00
9892646814
f-8: code: start/stop metadata service
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-04 17:43:51 +01:00
93a2bc6d33
f-8: systemd: change systemd file #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-04 17:42:26 +01:00
de7a678640
f-8: fix: implemente readonly usage #8
All checks were successful
Pre Release Workflow / set-release-target (push) Successful in 1s
Pre Release Workflow / build (agent, amd64, linux) (push) Successful in 1m24s
Pre Release Workflow / build (db, amd64, linux) (push) Successful in 1m31s
Pre Release Workflow / build (meta_cli, amd64, linux) (push) Successful in 1m31s
Pre Release Workflow / build (metadata, amd64, linux) (push) Successful in 1m28s
Pre Release Workflow / prerelease (push) Successful in 12s
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-02 22:51:53 +01:00
8787904833
f-8: code: add readonly feature in kv db #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-02 22:51:15 +01:00
7dbb508058
f-8: fix: close db for metadata
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-01 23:24:11 +01:00
f3898b288d
f-8: code: set correct config path and data path #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-01-01 23:19:25 +01:00
d2bc7e5b7a
f-8: code: use db for database config #8
All checks were successful
Pre Release Workflow / set-release-target (push) Successful in 1s
Pre Release Workflow / build (agent, amd64, linux) (push) Successful in 1m5s
Pre Release Workflow / build (db, amd64, linux) (push) Successful in 1m48s
Pre Release Workflow / build (meta_cli, amd64, linux) (push) Successful in 1m27s
Pre Release Workflow / build (metadata, amd64, linux) (push) Successful in 1m28s
Pre Release Workflow / prerelease (push) Successful in 12s
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-30 22:31:19 +01:00
fc624e7e96
f-8: code: move flags from pkg to binari #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-30 21:27:35 +01:00
7b43cecc57
f-8: ci: add two binaries #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-29 23:35:24 +01:00
e3583e7021
f-8: import local info for start/deploy metadata #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-28 15:01:37 +01:00
2c6c1ff2df
f-8: code: use local usage of netns #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-28 14:58:09 +01:00
348eb4aee3
f-8: code: implemete netns first code #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-28 14:57:36 +01:00
c2664e94fa
f-8: clean: use kv func for db bin #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-28 14:20:25 +01:00
97132550b0
f-8: code: move Load in DB to internal lib #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-26 23:48:42 +01:00
38f2ed1b83
f-8: code: use recursive delete #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-26 23:48:41 +01:00
b2d1922eaa
f-8: pkg: make delete in db recursif #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-26 23:48:41 +01:00
df95d8b4a7
f-8: code: use flags in db binary #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-26 23:48:36 +01:00
1ac1e61864
f-8: code: move binary name #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-26 23:48:35 +01:00
55ed2c8e53
f-8: code: add module to load nocloud in db #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-26 23:48:35 +01:00
b43e45488e
f-8: code: add func to control db #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-26 23:48:35 +01:00
2418e08ee0
f-8: code: add params for meta_cli #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-26 23:48:34 +01:00
014ae61dbb
f-8: internal/conf: delete conf file existance check #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-26 23:48:25 +01:00
1ac5a9fe49
f-8: conf: move configuration files #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-26 23:47:45 +01:00
e1f317aeb9
f-8: code: first implement of metadata cli #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-26 23:47:31 +01:00
72b2e9afa9
f-8: code: add first agent binari #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-22 22:49:17 +01:00
6a225a6162
f-8: code: add systemd lib #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-18 23:39:46 +01:00
3831aa2d71
f-8: package: add go-systemd #8
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-18 23:39:14 +01:00
6a3e849121
Merge pull request 'feature-1' (#6) from feature-1 into main
Reviewed-on: #6
2025-12-18 22:07:18 +00:00
fc6bb43616
f-1: scripts: add correct echo
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-18 23:06:02 +01:00
8d4a66bc89
f-1: scripts: add change execut for db #1
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-18 23:05:26 +01:00
ec1d1e0588
f-1: scripts: fix jq parsing for linux
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-18 23:04:34 +01:00
a50dcf6ad9
Merge pull request 'feature-1' (#5) from feature-1 into main
Reviewed-on: #5
2025-12-18 14:49:57 +00:00
6ed9d8abd9
f-1: scripts: add download scipts #1
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-18 15:29:09 +01:00
b25ce92c66
f-1: scripts: edit script add dry-run
All checks were successful
Pre Release Workflow / set-release-target (push) Successful in 1s
Pre Release Workflow / build (db, amd64, linux) (push) Successful in 1m50s
Pre Release Workflow / build (metadata, amd64, linux) (push) Successful in 1m46s
Pre Release Workflow / prerelease (push) Successful in 9s
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-18 14:30:41 +01:00
05b3827e3c
f-1: fix: fix forgeten args #1
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-18 14:09:37 +01:00
f67376964a
f-1: scripts: start deploy script #1
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-18 14:06:15 +01:00
25d4bfa0ee
f-1: clean: delete keep files #1
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2025-12-18 13:34:22 +01:00
57 changed files with 1845 additions and 58 deletions

View file

@ -32,7 +32,13 @@ jobs:
matrix: matrix:
goos: [linux] goos: [linux]
goarch: [amd64] goarch: [amd64]
binaries: [db, metadata] binaries:
- db
- metadata
- metacli
- agent
- vpc
- dhcp
uses: ./.forgejo/workflows/build.yml uses: ./.forgejo/workflows/build.yml
with: with:
tag: ${{ needs.set-release-target.outputs.release_cible }} tag: ${{ needs.set-release-target.outputs.release_cible }}
@ -40,10 +46,28 @@ jobs:
goarch: ${{ matrix.goarch }} goarch: ${{ matrix.goarch }}
binari: ${{ matrix.binaries }} binari: ${{ matrix.binaries }}
secrets: inherit secrets: inherit
upload-scripts:
runs-on: docker
needs: [set-release-target]
strategy:
matrix:
script:
- run-dnsmasq-in-netns.sh
steps:
- uses: actions/checkout@v3
- name: Move asset
run: |
mkdir -p "dist"
cp scripts/${{ matrix.script }} dist/
- name: Upload script
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.script }}-${{ needs.set-release-target.outputs.release_cible }}
path: dist/${{ matrix.script }}
prerelease: prerelease:
runs-on: docker runs-on: docker
needs: [set-release-target, build] needs: [set-release-target, build]
uses: ./.forgejo/workflows/release.yml uses: ./.forgejo/workflows/release.yml
with: with:
tag: ${{ needs.set-release-target.outputs.release_cible }} tag: ${{ needs.set-release-target.outputs.release_cible }}
secrets: inherit secrets: inherit

View file

17
cmd/agent/main.go Normal file
View file

@ -0,0 +1,17 @@
package main
import (
"fmt"
"os"
)
var (
bin_name = os.Args[0]
)
func main() {
fmt.Printf("%s: Start process\n", bin_name)
os.Exit(0)
}

View file

@ -1,6 +1,7 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"os" "os"
"strings" "strings"
@ -51,17 +52,13 @@ func AddInDB(dbName string, line string) error {
id := strings.Split(line, ";")[0] + "/bash" id := strings.Split(line, ";")[0] + "/bash"
key := []byte(dbName + "/" + id) key := []byte(dbName + "/" + id)
return DB.Update(func(txn *badger.Txn) error { return kv.AddInDB(DB, string(key), line)
return txn.Set(key, []byte(line))
})
} }
func DeleteInDB(dbName, id string) error { func DeleteInDB(dbName, id string) error {
key := []byte(dbName + "/" + id + "/bash") key := []byte(dbName + "/" + id + "/bash")
return DB.Update(func(txn *badger.Txn) error { return kv.DeleteInDB(DB, string(key))
return txn.Delete(key)
})
} }
func CountInDB(dbName, id string) int { func CountInDB(dbName, id string) int {
@ -125,7 +122,13 @@ func printDB() {
} }
func main() { func main() {
conf, err := configuration.LoadConfig("/etc/two/agent.yml") conf_file := flag.String("conf", "/etc/two/agent.yml", "configuration file")
flag.Parse()
args := flag.Args()
conf, err := configuration.LoadConfig(*conf_file)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return
@ -133,56 +136,56 @@ func main() {
DB = kv.InitDB(kv.Config{ DB = kv.InitDB(kv.Config{
Path: conf.Database.Path, Path: conf.Database.Path,
}) }, false)
defer DB.Close() defer DB.Close()
if len(os.Args) < 2 { if len(args) < 1 {
fmt.Println("Usage: db <cmd> [args...]") fmt.Println("Usage: db <cmd> [args...]")
return return
} }
cmd := os.Args[1] cmd := args[0]
switch cmd { switch cmd {
case "check_in_db": case "check_in_db":
if len(os.Args) != 4 { if len(args) != 3 {
fmt.Println("Usage: check_in_db <db_name> <id>") fmt.Println("Usage: check_in_db <db_name> <id>")
os.Exit(1) os.Exit(1)
} }
ret := CheckInDB(os.Args[2], os.Args[3]) ret := CheckInDB(args[1], args[2])
os.Exit(ret) os.Exit(ret)
case "add_in_db": case "add_in_db":
if len(os.Args) < 4 { if len(args) < 3 {
fmt.Println("Usage: add_in_db <db_name> <line...>") fmt.Println("Usage: add_in_db <db_name> <line...>")
os.Exit(1) os.Exit(1)
} }
line := strings.Join(os.Args[3:], ";") line := strings.Join(args[2:], ";")
if err := AddInDB(os.Args[2], line); err != nil { if err := AddInDB(args[1], line); err != nil {
fmt.Println("Error:", err) fmt.Println("Error:", err)
os.Exit(1) os.Exit(1)
} }
case "delete_in_db": case "delete_in_db":
if len(os.Args) != 4 { if len(args) != 3 {
fmt.Println("Usage: delete_in_db <db_name> <id>") fmt.Println("Usage: delete_in_db <db_name> <id>")
os.Exit(1) os.Exit(1)
} }
if err := DeleteInDB(os.Args[2], os.Args[3]); err != nil { if err := DeleteInDB(args[1], args[2]); err != nil {
fmt.Println("Error:", err) fmt.Println("Error:", err)
os.Exit(1) os.Exit(1)
} }
case "count_in_db": case "count_in_db":
if len(os.Args) != 4 { if len(args) != 3 {
fmt.Println("Usage: count_in_db <db_name> <id>") fmt.Println("Usage: count_in_db <db_name> <id>")
os.Exit(1) os.Exit(1)
} }
count := CountInDB(os.Args[2], os.Args[3]) count := CountInDB(args[1], args[2])
fmt.Println(count) fmt.Println(count)
case "get_from_db": case "get_from_db":
if len(os.Args) != 4 { if len(args) != 3 {
fmt.Println("Usage: get_from_db <db_name> <id>") fmt.Println("Usage: get_from_db <db_name> <id>")
os.Exit(1) os.Exit(1)
} }
line, _ := GetFromDB(os.Args[2], os.Args[3]) line, _ := GetFromDB(args[1], args[2])
fmt.Println(line) fmt.Println(line)
case "print": case "print":
printDB() printDB()

64
cmd/dhcp/main.go Normal file
View file

@ -0,0 +1,64 @@
package main
import (
"flag"
"fmt"
"net"
"os"
"git.g3e.fr/syonad/two/internal/dhcp"
"git.g3e.fr/syonad/two/pkg/systemd"
)
func main() {
subnet := flag.String("subnet", "", "Subnet CIDR (e.g. 10.10.10.0/24)")
name := flag.String("name", "", "Config name (e.g. vpc1_br-00002)")
gateway := flag.String("gateway", "", "Gateway IP (e.g. 10.10.10.1)")
confDir := flag.String("confdir", "/etc/dnsmasq.d", "dnsmasq config directory")
flag.Parse()
if *subnet == "" || *name == "" || *gateway == "" {
flag.Usage()
os.Exit(1)
}
_, network, err := net.ParseCIDR(*subnet)
if err != nil {
fmt.Fprintf(os.Stderr, "invalid subnet: %v\n", err)
os.Exit(1)
}
gw := net.ParseIP(*gateway)
if gw == nil {
fmt.Fprintf(os.Stderr, "invalid gateway IP: %q\n", *gateway)
os.Exit(1)
}
conf := dhcp.Config{
Network: network,
Gateway: gw,
Name: *name,
ConfDir: *confDir,
}
confPath, err := dhcp.GenerateConfig(conf)
if err != nil {
fmt.Fprintf(os.Stderr, "error generating config: %v\n", err)
os.Exit(1)
}
fmt.Printf("dnsmasq config written to %s\n", confPath)
svc, err := systemd.New()
if err != nil {
fmt.Fprintf(os.Stderr, "error connecting to systemd: %v\n", err)
os.Exit(1)
}
defer svc.Close()
unit := "dnsmasq@" + *name + ".service"
if err := svc.Start(unit); err != nil {
fmt.Fprintf(os.Stderr, "error starting %s: %v\n", unit, err)
os.Exit(1)
}
fmt.Printf("started %s\n", unit)
}

49
cmd/metacli/main.go Normal file
View file

@ -0,0 +1,49 @@
package main
import (
"flag"
"fmt"
configuration "git.g3e.fr/syonad/two/internal/config/agent"
"git.g3e.fr/syonad/two/internal/metadata"
"git.g3e.fr/syonad/two/pkg/db/kv"
)
func main() {
conf_file := flag.String("conf", "/etc/two/agent.yml", "configuration file")
vm_name := flag.String("vm_name", "", "Nom de la vm")
vpc := flag.String("vpc_name", "", "vpc name")
bind_ip := flag.String("ip", "", "bind ip")
bind_port := flag.String("port", "", "bind port")
ssh_key := flag.String("key", "", "Clef ssh")
password := flag.String("pass", "", "password user")
start := flag.Bool("start", false, "start metadata server")
stop := flag.Bool("stop", false, "stop metadata server")
dryrun := flag.Bool("dryrun", false, "launch in dry node")
flag.Parse()
conf, err := configuration.LoadConfig(*conf_file)
if err != nil {
fmt.Println(err)
return
}
db := kv.InitDB(kv.Config{
Path: conf.Database.Path,
}, false)
defer db.Close()
if *start {
metadata.StartMetadata(metadata.NoCloudConfig{
VpcName: *vpc,
Name: *vm_name,
BindIP: *bind_ip,
BindPort: *bind_port,
Password: *password,
SSHKEY: *ssh_key,
}, db, *dryrun)
} else if *stop {
metadata.StopMetadata(*vm_name, db, *dryrun)
}
}

View file

@ -1,9 +1,27 @@
package main package main
import ( import (
"flag"
"git.g3e.fr/syonad/two/internal/metadata" "git.g3e.fr/syonad/two/internal/metadata"
) )
var (
iface = flag.String("interface", "0.0.0.0", "Interface IP à écouter")
port = flag.Int("port", 0, "Port à utiliser")
netns_name = flag.String("netns", "", "Network namespace à utiliser")
conf_file = flag.String("conf", "/etc/two/agent.yml", "configuration file")
vm_name = flag.String("vm", "", "Name of the vm")
)
func main() { func main() {
metadata.StartServer() flag.Parse()
metadata.StartServer(metadata.ServerConfig{
Netns: *netns_name,
Iface: *iface,
Port: *port,
ConfFile: *conf_file,
VmName: *vm_name,
})
} }

65
cmd/vpc/main.go Normal file
View file

@ -0,0 +1,65 @@
package main
import (
"flag"
"fmt"
"os"
configuration "git.g3e.fr/syonad/two/internal/config/agent"
"git.g3e.fr/syonad/two/internal/vpc"
"git.g3e.fr/syonad/two/pkg/db/kv"
"github.com/dgraph-io/badger/v4"
)
var (
netns = flag.String("netns", "", "Network namespace à faire")
name = flag.String("name", "", "interface name")
action = flag.String("action", "", "Action a faire")
conf_file = flag.String("conf", "/etc/two/agent.yml", "configuration file")
)
var DB *badger.DB
func main() {
flag.Parse()
conf, err := configuration.LoadConfig(*conf_file)
if err != nil {
fmt.Println(err)
return
}
DB = kv.InitDB(kv.Config{
Path: conf.Database.Path,
}, false)
defer DB.Close()
switch *action {
case "create":
kv.AddInDB(DB, "vpc/"+*name+"/state", "creating")
if err := vpc.CreateVPC(DB, *name); err != nil {
fmt.Println(err)
}
case "delete":
kv.AddInDB(DB, "vpc/"+*name+"/state", "deleting")
if err := vpc.DeleteVPC(DB, *name); err != nil {
fmt.Println(err)
}
if state, err := kv.GetFromDB(DB, "vpc/"+*name+"/state"); err != nil {
fmt.Println(err)
os.Exit(1)
} else if state == "deleted" {
kv.DeleteInDB(DB, "vpc/"+*name)
}
case "check":
if state, err := kv.GetFromDB(DB, "vpc/"+*name+"/state"); err != nil {
os.Exit(1)
} else if state != "created" {
os.Exit(1)
}
default:
fmt.Printf("Available commande:\n - create\n - delete\n - check\n")
os.Exit(1)
}
os.Exit(0)
}

View file

@ -0,0 +1,2 @@
database:
path: "./data/"

10
go.mod
View file

@ -1,9 +1,12 @@
module git.g3e.fr/syonad/two module git.g3e.fr/syonad/two
go 1.23.8 go 1.24.0
toolchain go1.24.11
require ( require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.6.0 // indirect
github.com/dgraph-io/badger/v4 v4.8.0 // indirect github.com/dgraph-io/badger/v4 v4.8.0 // indirect
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
@ -11,6 +14,7 @@ require (
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
@ -21,13 +25,15 @@ require (
github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/pflag v1.0.10 // indirect
github.com/spf13/viper v1.21.0 // indirect github.com/spf13/viper v1.21.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/vishvananda/netlink v1.3.1 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.41.0 // indirect golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.34.0 // indirect golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.28.0 // indirect golang.org/x/text v0.28.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.6 // indirect
) )

12
go.sum
View file

@ -1,5 +1,7 @@
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=
github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU=
github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs= github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs=
github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w=
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
@ -15,6 +17,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
@ -35,6 +39,10 @@ github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
@ -47,8 +55,12 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=

View file

View file

@ -0,0 +1,83 @@
package configuration
import (
"os"
"path/filepath"
"testing"
)
func writeYAML(t *testing.T, content string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "config.yml")
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
t.Fatalf("impossible d'écrire le fichier de config : %v", err)
}
return path
}
// --- LoadConfig ---
func TestLoadConfig_ValidFile(t *testing.T) {
path := writeYAML(t, `
database:
path: /tmp/mydb
`)
cfg, err := LoadConfig(path)
if err != nil {
t.Fatalf("LoadConfig a échoué : %v", err)
}
if cfg.Database.Path != "/tmp/mydb" {
t.Errorf("database.path attendu %q, obtenu %q", "/tmp/mydb", cfg.Database.Path)
}
}
func TestLoadConfig_DefaultPath(t *testing.T) {
// Fichier vide → viper applique la valeur par défaut
path := writeYAML(t, "")
cfg, err := LoadConfig(path)
if err != nil {
t.Fatalf("LoadConfig a échoué : %v", err)
}
if cfg.Database.Path != "/var/lib/two/data/" {
t.Errorf("valeur par défaut attendue %q, obtenu %q", "/var/lib/two/data/", cfg.Database.Path)
}
}
func TestLoadConfig_MissingFile_UsesDefaults(t *testing.T) {
// Fichier inexistant : viper ignore l'erreur ReadInConfig et retourne les défauts
cfg, err := LoadConfig("/chemin/inexistant/config.yml")
if err != nil {
t.Fatalf("LoadConfig devrait retourner les défauts si le fichier est absent : %v", err)
}
if cfg.Database.Path != "/var/lib/two/data/" {
t.Errorf("valeur par défaut attendue, obtenu %q", cfg.Database.Path)
}
}
func TestLoadConfig_PartialConfig_MissingDatabaseKey(t *testing.T) {
// Fichier sans la clé database → valeur par défaut
path := writeYAML(t, `
autrekey: valeur
`)
cfg, err := LoadConfig(path)
if err != nil {
t.Fatalf("LoadConfig a échoué : %v", err)
}
if cfg.Database.Path != "/var/lib/two/data/" {
t.Errorf("valeur par défaut attendue, obtenu %q", cfg.Database.Path)
}
}
func TestLoadConfig_CustomPath(t *testing.T) {
path := writeYAML(t, `
database:
path: /opt/two/data
`)
cfg, err := LoadConfig(path)
if err != nil {
t.Fatalf("LoadConfig a échoué : %v", err)
}
if cfg.Database.Path != "/opt/two/data" {
t.Errorf("attendu %q, obtenu %q", "/opt/two/data", cfg.Database.Path)
}
}

View file

@ -17,9 +17,7 @@ func LoadConfig(path string) (*Config, error) {
v.SetDefault("database.path", "/var/lib/two/data/") v.SetDefault("database.path", "/var/lib/two/data/")
if err := v.ReadInConfig(); err != nil { v.ReadInConfig()
return nil, err
}
var cfg Config var cfg Config
if err := v.Unmarshal(&cfg); err != nil { if err := v.Unmarshal(&cfg); err != nil {

157
internal/dhcp/dhcp_test.go Normal file
View file

@ -0,0 +1,157 @@
package dhcp
import (
"net"
"os"
"path/filepath"
"strings"
"testing"
)
func parseNet(t *testing.T, cidr string) *net.IPNet {
t.Helper()
_, network, err := net.ParseCIDR(cidr)
if err != nil {
t.Fatalf("ParseCIDR(%q) : %v", cidr, err)
}
return network
}
// --- cloneIP ---
func TestCloneIP_IsIndependent(t *testing.T) {
ip := net.ParseIP("10.0.0.1").To4()
clone := cloneIP(ip)
clone[3] = 99
if ip[3] == 99 {
t.Error("cloneIP devrait retourner une copie indépendante")
}
}
// --- incrementIP ---
func TestIncrementIP_Simple(t *testing.T) {
ip := net.ParseIP("10.0.0.1").To4()
incrementIP(ip)
if ip.String() != "10.0.0.2" {
t.Errorf("attendu 10.0.0.2, obtenu %s", ip)
}
}
func TestIncrementIP_Carry(t *testing.T) {
ip := net.ParseIP("10.0.0.255").To4()
incrementIP(ip)
if ip.String() != "10.0.1.0" {
t.Errorf("attendu 10.0.1.0, obtenu %s", ip)
}
}
// --- GenerateConfig ---
func newConf(t *testing.T, cidr string) Config {
t.Helper()
_, network, _ := net.ParseCIDR(cidr)
return Config{
Network: network,
Gateway: net.ParseIP("192.168.1.1").To4(),
Name: "test",
ConfDir: t.TempDir(),
}
}
func TestGenerateConfig_CreatesFile(t *testing.T) {
conf := newConf(t, "192.168.1.0/29") // 6 hôtes
path, err := GenerateConfig(conf)
if err != nil {
t.Fatalf("GenerateConfig a échoué : %v", err)
}
if _, err := os.Stat(path); os.IsNotExist(err) {
t.Errorf("le fichier %q n'a pas été créé", path)
}
}
func TestGenerateConfig_FilenameMatchesName(t *testing.T) {
conf := newConf(t, "192.168.1.0/29")
path, err := GenerateConfig(conf)
if err != nil {
t.Fatalf("GenerateConfig a échoué : %v", err)
}
expected := filepath.Join(conf.ConfDir, "test.conf")
if path != expected {
t.Errorf("chemin attendu %q, obtenu %q", expected, path)
}
}
func TestGenerateConfig_ContainsGateway(t *testing.T) {
conf := newConf(t, "192.168.1.0/29")
path, _ := GenerateConfig(conf)
content, _ := os.ReadFile(path)
if !strings.Contains(string(content), "dhcp-option=3,192.168.1.1") {
t.Errorf("gateway absente du fichier généré :\n%s", content)
}
}
func TestGenerateConfig_ContainsDhcpRange(t *testing.T) {
_, network, _ := net.ParseCIDR("10.10.0.0/24")
conf := Config{
Network: network,
Gateway: net.ParseIP("10.10.0.1").To4(),
Name: "vpc1",
ConfDir: t.TempDir(),
}
path, _ := GenerateConfig(conf)
content, _ := os.ReadFile(path)
if !strings.Contains(string(content), "dhcp-range=10.10.0.0,static,255.255.255.0,12h") {
t.Errorf("dhcp-range absent ou incorrect :\n%s", content)
}
}
func TestGenerateConfig_OneHostEntryPerIP(t *testing.T) {
// /29 = réseau + broadcast + 6 hôtes → 8 adresses
conf := newConf(t, "10.0.0.0/29")
path, _ := GenerateConfig(conf)
content, _ := os.ReadFile(path)
lines := strings.Split(string(content), "\n")
count := 0
for _, l := range lines {
if strings.HasPrefix(l, "dhcp-host=") {
count++
}
}
// /29 contient 8 adresses (0 à 7)
if count != 8 {
t.Errorf("attendu 8 entrées dhcp-host, obtenu %d", count)
}
}
func TestGenerateConfig_MACPrefix(t *testing.T) {
conf := newConf(t, "10.0.0.0/30") // 4 adresses
path, _ := GenerateConfig(conf)
content, _ := os.ReadFile(path)
if !strings.Contains(string(content), "00:22:33:") {
t.Errorf("préfixe MAC 00:22:33: absent :\n%s", content)
}
}
func TestGenerateConfig_CreatesConfDir(t *testing.T) {
dir := filepath.Join(t.TempDir(), "sous", "dossier")
_, network, _ := net.ParseCIDR("10.0.0.0/30")
conf := Config{
Network: network,
Gateway: net.ParseIP("10.0.0.1").To4(),
Name: "net",
ConfDir: dir,
}
if _, err := GenerateConfig(conf); err != nil {
t.Fatalf("GenerateConfig devrait créer les répertoires manquants : %v", err)
}
if _, err := os.Stat(dir); os.IsNotExist(err) {
t.Errorf("répertoire %q non créé", dir)
}
}

47
internal/dhcp/generate.go Normal file
View file

@ -0,0 +1,47 @@
package dhcp
import (
"fmt"
"net"
"os"
"path/filepath"
"strings"
)
func GenerateConfig(c Config) (string, error) {
mask := fmt.Sprintf("%d.%d.%d.%d", c.Network.Mask[0], c.Network.Mask[1], c.Network.Mask[2], c.Network.Mask[3])
var sb strings.Builder
fmt.Fprintf(&sb, "no-resolv\n")
fmt.Fprintf(&sb, "dhcp-range=%s,static,%s,12h\n", c.Network.IP.String(), mask)
fmt.Fprintf(&sb, "dhcp-option=3,%s\n", c.Gateway.String())
fmt.Fprintf(&sb, "dhcp-option=6,1.1.1.1,8.8.8.8\n\n")
i := 0
for ip := cloneIP(c.Network.IP); c.Network.Contains(ip); incrementIP(ip) {
fmt.Fprintf(&sb, "dhcp-host=00:22:33:%02X:%02X:%02X,%s\n",
(i>>16)&0xFF, (i>>8)&0xFF, i&0xFF, ip)
i++
}
outPath := filepath.Join(c.ConfDir, c.Name+".conf")
if err := os.MkdirAll(c.ConfDir, 0755); err != nil {
return "", err
}
return outPath, os.WriteFile(outPath, []byte(sb.String()), 0644)
}
func incrementIP(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] != 0 {
break
}
}
}
func cloneIP(ip net.IP) net.IP {
clone := make(net.IP, len(ip))
copy(clone, ip)
return clone
}

12
internal/dhcp/struct.go Normal file
View file

@ -0,0 +1,12 @@
package dhcp
import (
"net"
)
type Config struct {
Network *net.IPNet
Gateway net.IP
Name string
ConfDir string
}

View file

@ -0,0 +1,26 @@
package metadata
import (
"git.g3e.fr/syonad/two/pkg/systemd"
"github.com/dgraph-io/badger/v4"
)
func StartMetadata(config NoCloudConfig, db *badger.DB, dryrun bool) {
service, _ := systemd.New()
defer service.Close()
LoadNcCloudInDB(config, db)
if !dryrun {
service.Start("metadata@" + config.Name)
}
}
func StopMetadata(vm_name string, db *badger.DB, dryrun bool) {
service, _ := systemd.New()
defer service.Close()
UnLoadNoCloudInDB(vm_name, db)
if !dryrun {
service.Stop("metadata@" + vm_name)
}
}

View file

@ -0,0 +1,200 @@
package metadata
import (
"strings"
"testing"
"git.g3e.fr/syonad/two/pkg/db/kv"
)
func newCfg() NoCloudConfig {
return NoCloudConfig{
VpcName: "vpc-test",
BindIP: "169.254.169.254",
BindPort: "80",
Name: "vm1",
Password: "s3cr3t",
SSHKEY: "ssh-ed25519 AAAA... user@host",
}
}
func newTestDB(t *testing.T) interface{ Close() error } {
t.Helper()
db := kv.InitDB(kv.Config{Path: t.TempDir()}, false)
t.Cleanup(func() { db.Close() })
return db
}
// --- RenderConfig ---
func TestRenderConfig_MetaData(t *testing.T) {
cfg := newCfg()
out, err := RenderConfig("templates/meta-data.tmpl", cfg)
if err != nil {
t.Fatalf("RenderConfig meta-data : %v", err)
}
if !strings.Contains(out, "instance-id: vm1") {
t.Errorf("instance-id absent :\n%s", out)
}
if !strings.Contains(out, "local-hostname: vm1") {
t.Errorf("local-hostname absent :\n%s", out)
}
}
func TestRenderConfig_VendorData_ContainsPassword(t *testing.T) {
cfg := newCfg()
out, err := RenderConfig("templates/vendor-data.tmpl", cfg)
if err != nil {
t.Fatalf("RenderConfig vendor-data : %v", err)
}
if !strings.Contains(out, "s3cr3t") {
t.Errorf("password absent du vendor-data :\n%s", out)
}
}
func TestRenderConfig_VendorData_ContainsSSHKey(t *testing.T) {
cfg := newCfg()
out, err := RenderConfig("templates/vendor-data.tmpl", cfg)
if err != nil {
t.Fatalf("RenderConfig vendor-data : %v", err)
}
if !strings.Contains(out, "ssh-ed25519 AAAA... user@host") {
t.Errorf("clé SSH absente du vendor-data :\n%s", out)
}
}
func TestRenderConfig_NetworkConfig(t *testing.T) {
cfg := newCfg()
out, err := RenderConfig("templates/network-config.tmpl", cfg)
if err != nil {
t.Fatalf("RenderConfig network-config : %v", err)
}
if !strings.Contains(out, "dhcp4: true") {
t.Errorf("dhcp4 absent du network-config :\n%s", out)
}
}
func TestRenderConfig_UserData(t *testing.T) {
cfg := newCfg()
out, err := RenderConfig("templates/user-data.tmpl", cfg)
if err != nil {
t.Fatalf("RenderConfig user-data : %v", err)
}
if !strings.Contains(out, "passwd -d root") {
t.Errorf("user-data inattendu :\n%s", out)
}
}
func TestRenderConfig_InvalidTemplate(t *testing.T) {
_, err := RenderConfig("templates/inexistant.tmpl", newCfg())
if err == nil {
t.Error("RenderConfig devrait retourner une erreur pour un template inexistant")
}
}
func TestRenderConfig_SpecialCharsInName(t *testing.T) {
cfg := newCfg()
cfg.Name = "vm-prod-01"
out, err := RenderConfig("templates/meta-data.tmpl", cfg)
if err != nil {
t.Fatalf("RenderConfig : %v", err)
}
if !strings.Contains(out, "vm-prod-01") {
t.Errorf("nom vm-prod-01 absent :\n%s", out)
}
}
// --- LoadNcCloudInDB / UnLoadNoCloudInDB ---
func TestLoadNcCloudInDB_StoresAllKeys(t *testing.T) {
db := kv.InitDB(kv.Config{Path: t.TempDir()}, false)
t.Cleanup(func() { db.Close() })
cfg := newCfg()
LoadNcCloudInDB(cfg, db)
keys := []string{
"metadata/vm1/meta-data",
"metadata/vm1/user-data",
"metadata/vm1/network-config",
"metadata/vm1/vendor-data",
"metadata/vm1/vpc",
"metadata/vm1/bind_ip",
"metadata/vm1/bind_port",
}
for _, key := range keys {
val, err := kv.GetFromDB(db, key)
if err != nil {
t.Errorf("clé %q absente après LoadNcCloudInDB : %v", key, err)
}
if val == "" && key != "metadata/vm1/user-data" {
t.Errorf("clé %q vide après LoadNcCloudInDB", key)
}
}
}
func TestLoadNcCloudInDB_VpcAndBindValues(t *testing.T) {
db := kv.InitDB(kv.Config{Path: t.TempDir()}, false)
t.Cleanup(func() { db.Close() })
cfg := newCfg()
LoadNcCloudInDB(cfg, db)
vpc, _ := kv.GetFromDB(db, "metadata/vm1/vpc")
if vpc != "vpc-test" {
t.Errorf("vpc attendu %q, obtenu %q", "vpc-test", vpc)
}
ip, _ := kv.GetFromDB(db, "metadata/vm1/bind_ip")
if ip != "169.254.169.254" {
t.Errorf("bind_ip attendu %q, obtenu %q", "169.254.169.254", ip)
}
port, _ := kv.GetFromDB(db, "metadata/vm1/bind_port")
if port != "80" {
t.Errorf("bind_port attendu %q, obtenu %q", "80", port)
}
}
func TestUnLoadNoCloudInDB_RemovesAllKeys(t *testing.T) {
db := kv.InitDB(kv.Config{Path: t.TempDir()}, false)
t.Cleanup(func() { db.Close() })
cfg := newCfg()
LoadNcCloudInDB(cfg, db)
UnLoadNoCloudInDB("vm1", db)
keys := []string{
"metadata/vm1/meta-data",
"metadata/vm1/user-data",
"metadata/vm1/network-config",
"metadata/vm1/vendor-data",
"metadata/vm1/vpc",
"metadata/vm1/bind_ip",
"metadata/vm1/bind_port",
}
for _, key := range keys {
_, err := kv.GetFromDB(db, key)
if err == nil {
t.Errorf("clé %q devrait être supprimée après UnLoadNoCloudInDB", key)
}
}
}
func TestUnLoadNoCloudInDB_DoesNotAffectOtherVMs(t *testing.T) {
db := kv.InitDB(kv.Config{Path: t.TempDir()}, false)
t.Cleanup(func() { db.Close() })
cfg1 := newCfg()
cfg2 := newCfg()
cfg2.Name = "vm2"
LoadNcCloudInDB(cfg1, db)
LoadNcCloudInDB(cfg2, db)
UnLoadNoCloudInDB("vm1", db)
_, err := kv.GetFromDB(db, "metadata/vm2/vpc")
if err != nil {
t.Errorf("vm2 ne devrait pas être supprimée : %v", err)
}
}

View file

@ -0,0 +1,46 @@
package metadata
import (
"bytes"
"embed"
"text/template"
"git.g3e.fr/syonad/two/pkg/db/kv"
"github.com/dgraph-io/badger/v4"
)
//go:embed templates/*.tmpl
var templateFS embed.FS
func RenderConfig(path string, cfg NoCloudConfig) (string, error) {
tpl, err := template.ParseFS(templateFS, path)
if err != nil {
return "", err
}
var buf bytes.Buffer
if err := tpl.Execute(&buf, cfg); err != nil {
return "", err
}
return buf.String(), nil
}
func LoadNcCloudInDB(config NoCloudConfig, db *badger.DB) {
meta_data, _ := RenderConfig("templates/meta-data.tmpl", config)
user_data, _ := RenderConfig("templates/user-data.tmpl", config)
network_config, _ := RenderConfig("templates/network-config.tmpl", config)
vendor_data, _ := RenderConfig("templates/vendor-data.tmpl", config)
kv.AddInDB(db, "metadata/"+config.Name+"/meta-data", meta_data)
kv.AddInDB(db, "metadata/"+config.Name+"/user-data", user_data)
kv.AddInDB(db, "metadata/"+config.Name+"/network-config", network_config)
kv.AddInDB(db, "metadata/"+config.Name+"/vendor-data", vendor_data)
kv.AddInDB(db, "metadata/"+config.Name+"/vpc", config.VpcName)
kv.AddInDB(db, "metadata/"+config.Name+"/bind_ip", config.BindIP)
kv.AddInDB(db, "metadata/"+config.Name+"/bind_port", config.BindPort)
}
func UnLoadNoCloudInDB(vm_name string, db *badger.DB) {
kv.DeleteInDB(db, "metadata/"+vm_name)
}

View file

@ -1,24 +1,20 @@
package metadata package metadata
import ( import (
"encoding/json"
"flag"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
"strconv"
"time" "time"
configuration "git.g3e.fr/syonad/two/internal/config/agent"
"git.g3e.fr/syonad/two/internal/netns"
"git.g3e.fr/syonad/two/pkg/db/kv"
) )
var data NoCloudData var data NoCloudData
var (
iface = flag.String("interface", "0.0.0.0", "Interface IP à écouter")
port = flag.Int("port", 8080, "Port à utiliser")
file = flag.String("file", "", "Fichier JSON contenant les données NoCloud")
)
func getIP(r *http.Request) string { func getIP(r *http.Request) string {
ip, _, err := net.SplitHostPort(r.RemoteAddr) ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil { if err != nil {
@ -27,6 +23,51 @@ func getIP(r *http.Request) string {
return ip return ip
} }
func getFromDB(config ServerConfig) NoCloudData {
var netns_name string
var port int
var iface string
conf_db, _ := configuration.LoadConfig(config.ConfFile)
db := kv.InitDB(kv.Config{Path: conf_db.Database.Path}, true)
defer db.Close()
metadata, _ := kv.GetFromDB(db, "metadata/"+config.VmName+"/meta-data")
userdata, _ := kv.GetFromDB(db, "metadata/"+config.VmName+"/user-data")
networkconfig, _ := kv.GetFromDB(db, "metadata/"+config.VmName+"/network-config")
vendordata, _ := kv.GetFromDB(db, "metadata/"+config.VmName+"/vendor-data")
if config.Netns == "" {
netns_name, _ = kv.GetFromDB(db, "metadata/"+config.VmName+"/vpc")
} else {
netns_name = config.Netns
}
if config.Iface == "" {
iface, _ = kv.GetFromDB(db, "metadata/"+config.VmName+"/bind_ip")
} else {
iface = config.Iface
}
if config.Port == 0 {
sport, _ := kv.GetFromDB(db, "metadata/"+config.VmName+"/bind_port")
port, _ = strconv.Atoi(sport)
} else {
port = config.Port
}
return NoCloudData{
MetaData: metadata,
UserData: userdata,
NetworkConfig: networkconfig,
VendorData: vendordata,
NetNs: netns_name,
Iface: iface,
Port: port,
}
}
func rootHandler(w http.ResponseWriter, r *http.Request) { func rootHandler(w http.ResponseWriter, r *http.Request) {
ip := getIP(r) ip := getIP(r)
path := r.URL.Path path := r.URL.Path
@ -51,25 +92,18 @@ func rootHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
func StartServer() { func StartServer(config ServerConfig) {
flag.Parse() data = getFromDB(config)
if *file == "" { if data.NetNs != "" {
log.Fatal("Vous devez spécifier un fichier via --file") if err := netns.Enter(data.NetNs); err != nil {
} log.Fatalf("Impossible d'entrer dans le netns: %v", err)
}
raw, err := ioutil.ReadFile(*file)
if err != nil {
log.Fatalf("Erreur de lecture du fichier: %v", err)
}
if err := json.Unmarshal(raw, &data); err != nil {
log.Fatalf("Erreur de parsing JSON: %v", err)
} }
http.HandleFunc("/", rootHandler) http.HandleFunc("/", rootHandler)
address := fmt.Sprintf("%s:%d", *iface, *port) address := fmt.Sprintf("%s:%d", data.Iface, data.Port)
log.Printf("Serveur NoCloud démarré sur http://%s/", address) log.Printf("Serveur NoCloud démarré sur http://%s/", address)
log.Fatal(http.ListenAndServe(address, nil)) log.Fatal(http.ListenAndServe(address, nil))
} }

View file

@ -1,8 +1,29 @@
package metadata package metadata
type NoCloudData struct { type NoCloudData struct {
MetaData string `json:"meta-data"` MetaData string
UserData string `json:"user-data"` UserData string
NetworkConfig string `json:"network-config"` NetworkConfig string
VendorData string `json:"vendor-data"` VendorData string
NetNs string
Iface string
Port int
}
type ServerConfig struct {
Netns string
File string
Iface string
Port int
ConfFile string
VmName string
}
type NoCloudConfig struct {
VpcName string
BindIP string
BindPort string
Name string
Password string
SSHKEY string
} }

View file

@ -0,0 +1,2 @@
instance-id: {{ .Name }}
local-hostname: {{ .Name }}

View file

@ -0,0 +1,4 @@
version: 2
ethernets:
eth0:
dhcp4: true

View file

@ -0,0 +1,3 @@
#!/bin/sh
passwd -d root

View file

@ -0,0 +1,13 @@
#cloud-config
users:
- name: syonad
lock_passwd: false
gecos: alpine Cloud User
groups: [adm, wheel]
doas:
- permit nopass syonad
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
shell: /bin/ash
passwd: "{{ .Password }}"
ssh_authorized_keys:
- "{{ .SSHKEY }}"

38
internal/netif/bridge.go Normal file
View file

@ -0,0 +1,38 @@
package netif
import (
"github.com/vishvananda/netlink"
)
func CreateBridge(name string, mtu int) error {
br := &netlink.Bridge{
LinkAttrs: netlink.LinkAttrs{
Name: name,
MTU: mtu,
},
}
if err := netlink.LinkAdd(br); err != nil {
return err
}
if err := netlink.LinkSetUp(br); err != nil {
return err
}
return nil
}
func BridgeSetMaster(iface, bridge string) error {
link, err := netlink.LinkByName(iface)
if err != nil {
return err
}
br, err := netlink.LinkByName(bridge)
if err != nil {
return err
}
return netlink.LinkSetMaster(link, br)
}

13
internal/netif/delete.go Normal file
View file

@ -0,0 +1,13 @@
package netif
import (
"github.com/vishvananda/netlink"
)
func DeleteLink(name string) error {
link, err := netlink.LinkByName(name)
if err != nil {
return err
}
return netlink.LinkDel(link)
}

21
internal/netif/upDown.go Normal file
View file

@ -0,0 +1,21 @@
package netif
import (
"github.com/vishvananda/netlink"
)
func LinkSetUp(name string) error {
link, err := netlink.LinkByName(name)
if err != nil {
return err
}
return netlink.LinkSetUp(link)
}
func LinkSetDown(name string) error {
link, err := netlink.LinkByName(name)
if err != nil {
return err
}
return netlink.LinkSetDown(link)
}

48
internal/netif/veth.go Normal file
View file

@ -0,0 +1,48 @@
package netif
import (
"fmt"
"runtime"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
)
func CreateVethToNetns(rootIf, nsIf, netnsPath string, mtu int) error {
// Obligatoire : netns lié au thread
runtime.LockOSThread()
defer runtime.UnlockOSThread()
// Ouvrir le netns cible
ns, err := netns.GetFromPath(netnsPath)
if err != nil {
return fmt.Errorf("open netns: %w, %s", err, netnsPath)
}
defer ns.Close()
// Créer le veth dans le netns courant
veth := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{
Name: rootIf,
MTU: mtu,
},
PeerName: nsIf,
}
if err := netlink.LinkAdd(veth); err != nil {
return fmt.Errorf("link add: %w", err)
}
// Récupérer l'interface peer
peer, err := netlink.LinkByName(nsIf)
if err != nil {
return fmt.Errorf("peer not found: %w", err)
}
// Déplacer le peer dans le netns cible
if err := netlink.LinkSetNsFd(peer, int(ns)); err != nil {
return fmt.Errorf("set ns: %w", err)
}
return nil
}

5
internal/netns/call.go Normal file
View file

@ -0,0 +1,5 @@
package netns
func Call(name string, fn func() error) error {
return call(name, fn)
}

View file

@ -0,0 +1,44 @@
//go:build linux
package netns
import (
"fmt"
"os"
"runtime"
"golang.org/x/sys/unix"
)
func call(name string, fn func() error) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
// sauvegarde du netns courant
orig, err := os.Open("/proc/self/ns/net")
if err != nil {
return err
}
defer orig.Close()
// entrer dans le netns cible
f, err := os.Open(fmt.Sprintf("/var/run/netns/%s", name))
if err != nil {
return err
}
defer f.Close()
if err := unix.Setns(int(f.Fd()), unix.CLONE_NEWNET); err != nil {
return err
}
// exécuter la fonction dans le netns
err = fn()
// toujours revenir au netns d'origine
if restoreErr := unix.Setns(int(orig.Fd()), unix.CLONE_NEWNET); restoreErr != nil {
return restoreErr
}
return err
}

View file

@ -0,0 +1,7 @@
//go:build !linux
package netns
func call(name string, fn func() error) error {
return fn()
}

5
internal/netns/create.go Normal file
View file

@ -0,0 +1,5 @@
package netns
func Create(name string) error {
return create(name)
}

View file

@ -0,0 +1,55 @@
//go:build linux
package netns
import (
"os"
"golang.org/x/sys/unix"
)
func create(name string) error {
base := "/var/run/netns"
path := base + "/" + name
if err := os.MkdirAll(base, 0755); err != nil {
return err
}
// fichier cible
f, err := os.Create(path)
if err != nil {
return err
}
f.Close()
// sauvegarde du netns courant
orig, err := os.Open("/proc/self/ns/net")
if err != nil {
return err
}
defer orig.Close()
// nouveau netns
if err := unix.Unshare(unix.CLONE_NEWNET); err != nil {
return err
}
// bind mount du netns courant vers /var/run/netns/<name>
if err := unix.Mount(
"/proc/self/ns/net",
path,
"",
unix.MS_BIND,
"",
); err != nil {
return err
}
// revenir au netns original
if err := unix.Setns(int(orig.Fd()), unix.CLONE_NEWNET); err != nil {
return err
}
return nil
}

View file

@ -0,0 +1,5 @@
//go:build !linux
package netns
func create(string) error { return nil }

5
internal/netns/delete.go Normal file
View file

@ -0,0 +1,5 @@
package netns
func Delete(name string) error {
return delete(name)
}

View file

@ -0,0 +1,18 @@
//go:build linux
package netns
import (
"os"
"golang.org/x/sys/unix"
)
func delete(name string) error {
path := "/var/run/netns/" + name
if err := unix.Unmount(path, unix.MNT_DETACH); err != nil {
return err
}
return os.Remove(path)
}

View file

@ -0,0 +1,5 @@
//go:build !linux
package netns
func delete(string) error { return nil }

5
internal/netns/enter.go Normal file
View file

@ -0,0 +1,5 @@
package netns
func Enter(name string) error {
return enter(name)
}

View file

@ -0,0 +1,26 @@
//go:build linux
package netns
import (
"fmt"
"os"
"runtime"
"golang.org/x/sys/unix"
)
func enter(name string) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
path := fmt.Sprintf("/var/run/netns/%s", name)
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
return unix.Setns(int(f.Fd()), unix.CLONE_NEWNET)
}

View file

@ -0,0 +1,8 @@
//go:build !linux
package netns
func enter(name string) error {
// Ignoré hors Linux
return nil
}

14
internal/netns/exist.go Normal file
View file

@ -0,0 +1,14 @@
package netns
import (
"os"
)
func exist(name string) bool {
_, err := os.Stat("/var/run/netns/" + name)
return err == nil
}
func Exist(name string) bool {
return exist(name)
}

61
internal/vpc/create.go Normal file
View file

@ -0,0 +1,61 @@
package vpc
import (
"git.g3e.fr/syonad/two/internal/netif"
"git.g3e.fr/syonad/two/internal/netns"
"git.g3e.fr/syonad/two/pkg/db/kv"
"github.com/dgraph-io/badger/v4"
)
func CreateVPC(db *badger.DB, name string) error {
// missing
// search data in db
// change state in db
// create netns
if state, err := kv.GetFromDB(db, "vpc/"+name+"/state"); err != nil {
return err
} else if state == "creating" {
if err := netns.Create(name); err != nil {
return err
}
// create veth public for this netns
if err := netif.CreateVethToNetns("veth"+name+"ext", "vethpublicint", "/var/run/netns/"+name, 9000); err != nil {
return err
}
// create public bridge in netns
if err := netns.Call(name, func() error {
return netif.CreateBridge("br-public", 1500)
}); err != nil {
return err
}
// set veth to ext public bridge
if err := netif.BridgeSetMaster("veth"+name+"ext", "br-public"); err != nil {
return err
}
// set veth to int public bridge
if err := netns.Call(name, func() error {
return netif.BridgeSetMaster("vethpublicint", "br-public")
}); err != nil {
return err
}
// set set ext veth up
if err := netif.LinkSetUp("veth" + name + "ext"); err != nil {
return nil
}
// set set int veth up
if err := netns.Call(name, func() error {
return netif.LinkSetUp("vethpublicint")
}); err != nil {
return err
}
kv.AddInDB(db, "vpc/"+name+"/state", "created")
}
return nil
}

26
internal/vpc/delete.go Normal file
View file

@ -0,0 +1,26 @@
package vpc
import (
"git.g3e.fr/syonad/two/internal/netif"
"git.g3e.fr/syonad/two/internal/netns"
"git.g3e.fr/syonad/two/pkg/db/kv"
"github.com/dgraph-io/badger/v4"
)
func DeleteVPC(db *badger.DB, name string) error {
if state, err := kv.GetFromDB(db, "vpc/"+name+"/state"); err != nil {
return err
} else if state == "deleting" {
if err := netif.DeleteLink(name + "-ext"); err != nil {
return err
}
if err := netns.Delete(name); err != nil {
return err
}
kv.AddInDB(db, "vpc/"+name+"/state", "deleted")
}
return nil
}

View file

11
pkg/db/kv/addInDB.go Normal file
View file

@ -0,0 +1,11 @@
package kv
import (
"github.com/dgraph-io/badger/v4"
)
func AddInDB(db *badger.DB, key string, value string) error {
return db.Update(func(txn *badger.Txn) error {
return txn.Set([]byte(key), []byte(value))
})
}

42
pkg/db/kv/deleteInDB.go Normal file
View file

@ -0,0 +1,42 @@
package kv
import (
"log"
"github.com/dgraph-io/badger/v4"
)
func deleteKey(db *badger.DB, key string) error {
return db.Update(func(txn *badger.Txn) error {
return txn.Delete([]byte(key))
})
}
func DeleteInDB(db *badger.DB, key string) error {
prefix := []byte(key + "/")
err := db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.PrefetchValues = false
it := txn.NewIterator(opts)
defer it.Close()
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
item := it.Item()
key := item.Key()
k := append([]byte{}, key...)
if err := deleteKey(db, string(k)); err != nil {
return err
}
}
return nil
})
if err != nil {
log.Fatal(err)
}
return deleteKey(db, key)
}

22
pkg/db/kv/getInDB.go Normal file
View file

@ -0,0 +1,22 @@
package kv
import (
"github.com/dgraph-io/badger/v4"
)
func GetFromDB(db *badger.DB, key string) (string, error) {
var result string
err := db.View(func(txn *badger.Txn) error {
item, err := txn.Get([]byte(key))
if err != nil {
return err
}
return item.Value(func(val []byte) error {
result = string(val)
return nil
})
})
return result, err
}

View file

@ -4,8 +4,9 @@ import (
"github.com/dgraph-io/badger/v4" "github.com/dgraph-io/badger/v4"
) )
func InitDB(conf Config) *badger.DB { func InitDB(conf Config, readonly bool) *badger.DB {
opts := badger.DefaultOptions(conf.Path) opts := badger.DefaultOptions(conf.Path).
WithReadOnly(readonly)
opts.Logger = nil opts.Logger = nil
opts.ValueLogFileSize = 10 << 20 // 10 Mo par fichier vlog opts.ValueLogFileSize = 10 << 20 // 10 Mo par fichier vlog
opts.NumMemtables = 1 opts.NumMemtables = 1

153
pkg/db/kv/kv_test.go Normal file
View file

@ -0,0 +1,153 @@
package kv
import (
"errors"
"testing"
"github.com/dgraph-io/badger/v4"
)
// newTestDB ouvre une base BadgerDB dans un répertoire temporaire.
// La base est fermée automatiquement en fin de test.
func newTestDB(t *testing.T) *badger.DB {
t.Helper()
db := InitDB(Config{Path: t.TempDir()}, false)
t.Cleanup(func() { db.Close() })
return db
}
// --- InitDB ---
func TestInitDB_ValidPath(t *testing.T) {
db := newTestDB(t)
if db == nil {
t.Fatal("InitDB devrait retourner une DB non-nil")
}
}
func TestInitDB_InvalidPath_Panics(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("InitDB avec un chemin invalide devrait paniquer")
}
}()
InitDB(Config{Path: "/chemin/inexistant/absolu"}, false)
}
// --- AddInDB ---
func TestAddInDB_NewKey(t *testing.T) {
db := newTestDB(t)
if err := AddInDB(db, "vpc/test", "valeur"); err != nil {
t.Fatalf("AddInDB a échoué : %v", err)
}
}
func TestAddInDB_OverwriteExistingKey(t *testing.T) {
db := newTestDB(t)
AddInDB(db, "vpc/test", "premiere")
if err := AddInDB(db, "vpc/test", "deuxieme"); err != nil {
t.Fatalf("AddInDB (écrasement) a échoué : %v", err)
}
val, _ := GetFromDB(db, "vpc/test")
if val != "deuxieme" {
t.Errorf("valeur attendue %q, obtenu %q", "deuxieme", val)
}
}
// --- GetFromDB ---
func TestGetFromDB_ExistingKey(t *testing.T) {
db := newTestDB(t)
AddInDB(db, "vpc/foo", "bar")
val, err := GetFromDB(db, "vpc/foo")
if err != nil {
t.Fatalf("GetFromDB a échoué : %v", err)
}
if val != "bar" {
t.Errorf("valeur attendue %q, obtenu %q", "bar", val)
}
}
func TestGetFromDB_MissingKey(t *testing.T) {
db := newTestDB(t)
_, err := GetFromDB(db, "inexistant")
if !errors.Is(err, badger.ErrKeyNotFound) {
t.Errorf("erreur attendue ErrKeyNotFound, obtenu : %v", err)
}
}
func TestGetFromDB_EmptyValue(t *testing.T) {
db := newTestDB(t)
AddInDB(db, "vpc/vide", "")
val, err := GetFromDB(db, "vpc/vide")
if err != nil {
t.Fatalf("GetFromDB a échoué : %v", err)
}
if val != "" {
t.Errorf("valeur attendue vide, obtenu %q", val)
}
}
// --- DeleteInDB ---
func TestDeleteInDB_SimpleKey(t *testing.T) {
db := newTestDB(t)
AddInDB(db, "vpc/a", "v")
if err := DeleteInDB(db, "vpc/a"); err != nil {
t.Fatalf("DeleteInDB a échoué : %v", err)
}
_, err := GetFromDB(db, "vpc/a")
if !errors.Is(err, badger.ErrKeyNotFound) {
t.Errorf("la clé devrait être supprimée, obtenu : %v", err)
}
}
func TestDeleteInDB_WithSubkeys(t *testing.T) {
db := newTestDB(t)
// Clé parente + sous-clés (préfixe "vpc/net1/")
AddInDB(db, "vpc/net1", "parent")
AddInDB(db, "vpc/net1/ip", "10.0.0.1")
AddInDB(db, "vpc/net1/gw", "10.0.0.254")
if err := DeleteInDB(db, "vpc/net1"); err != nil {
t.Fatalf("DeleteInDB a échoué : %v", err)
}
for _, key := range []string{"vpc/net1", "vpc/net1/ip", "vpc/net1/gw"} {
_, err := GetFromDB(db, key)
if !errors.Is(err, badger.ErrKeyNotFound) {
t.Errorf("clé %q devrait être supprimée, obtenu : %v", key, err)
}
}
}
func TestDeleteInDB_DoesNotDeleteSiblings(t *testing.T) {
db := newTestDB(t)
AddInDB(db, "vpc/net1", "a")
AddInDB(db, "vpc/net2", "b") // ne doit pas être supprimée
DeleteInDB(db, "vpc/net1")
val, err := GetFromDB(db, "vpc/net2")
if err != nil {
t.Fatalf("vpc/net2 ne devrait pas être supprimée : %v", err)
}
if val != "b" {
t.Errorf("valeur attendue %q, obtenu %q", "b", val)
}
}
func TestDeleteInDB_MissingKey(t *testing.T) {
db := newTestDB(t)
// Supprimer une clé inexistante ne doit pas crasher
if err := DeleteInDB(db, "inexistant"); err != nil {
t.Logf("DeleteInDB clé inexistante retourne : %v (non bloquant)", err)
}
}

109
pkg/systemd/main.go Normal file
View file

@ -0,0 +1,109 @@
package systemd
import (
"context"
"errors"
"fmt"
"time"
"github.com/coreos/go-systemd/v22/dbus"
)
const (
defaultTimeout = 5 * time.Second
jobMode = "replace"
)
type Manager struct {
conn *dbus.Conn
}
type ServiceStatus struct {
Name string
LoadState string
ActiveState string
SubState string
MainPID uint32
}
// New crée une connexion D-Bus systemd (scope système)
func New() (*Manager, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
conn, err := dbus.NewSystemConnectionContext(ctx)
if err != nil {
return nil, err
}
return &Manager{conn: conn}, nil
}
// Close ferme la connexion D-Bus
func (m *Manager) Close() {
if m.conn != nil {
m.conn.Close()
}
}
// Start démarre un service systemd
func (m *Manager) Start(service string) error {
return m.job("StartUnit", service)
}
// Stop arrête un service systemd
func (m *Manager) Stop(service string) error {
return m.job("StopUnit", service)
}
func (m *Manager) job(method, service string) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
ch := make(chan string, 1)
var err error
switch method {
case "StartUnit":
_, err = m.conn.StartUnitContext(ctx, service, jobMode, ch)
case "StopUnit":
_, err = m.conn.StopUnitContext(ctx, service, jobMode, ch)
default:
return errors.New("unsupported job method")
}
if err != nil {
return err
}
result := <-ch
if result != "done" {
return fmt.Errorf("%s %s failed: %s", method, service, result)
}
return nil
}
// Status retourne létat courant du service
func (m *Manager) Status(service string) (*ServiceStatus, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
props, err := m.conn.GetUnitPropertiesContext(ctx, service)
if err != nil {
return nil, err
}
status := &ServiceStatus{
Name: service,
LoadState: props["LoadState"].(string),
ActiveState: props["ActiveState"].(string),
SubState: props["SubState"].(string),
}
if pid, ok := props["MainPID"].(uint32); ok {
status.MainPID = pid
}
return status, nil
}

92
scripts/deploy.sh Normal file
View file

@ -0,0 +1,92 @@
#!/bin/bash
set -e
SED_PARAM=""
unameOut="$(uname -s)"
case "${unameOut}" in
Linux*) SED_PARAM=" -i ";;
Darwin*) SED_PARAM=" -i '' ";;
*) exit 1
esac
SCRIPT_PATH="scripts/deploy.sh"
exec_with_dry_run () {
if [[ ${1} -eq ${FLAGS_TRUE} ]]; then
echo "# ${2}"
else
eval "${2}" 2> /tmp/error || \
{
echo -e "failed with following error";
output=$(cat /tmp/error | sed -e "s/^/ error -> /g");
echo -e "${output}";
return 1;
}
fi
return 0
}
check_latest_script () {
REMOTE_URL="${1}"
LOCAL_PATH="${2}"
REMOTE=$(curl --silent "${REMOTE_URL}" | sha256sum)
LOCAL=$(cat ${LOCAL_PATH} | sha256sum)
[[ "${REMOTE}" == "${LOCAL}" ]] || return 1
return 0
}
download_binaries () {
DRY_RUN="${1}"
TAG="${2}"
GIT_SERVER="${3}"
REPO_PATH="${4}"
#'.[0].assets.[].browser_download_url'
[[ "${TAG}" == "" ]] && TAG=$(curl --silent "${GIT_SERVER}api/v1/repos/${REPO_PATH}releases/?limit=1" | jq -r '.[0].tag_name')
echo "Deploy ${TAG} binaries"
BIN_PATH="/opt/two/${TAG}/bin/"
LN_PATH="/opt/two/bin/"
exec_with_dry_run "${DRY_RUN}" "mkdir -p \"${BIN_PATH}\""
exec_with_dry_run "${DRY_RUN}" "mkdir -p \"${LN_PATH}\""
curl --silent "${GIT_SERVER}api/v1/repos/${REPO_PATH}releases/tags/${TAG}" | jq -c '.assets[]' | while read tmp
do
BINARY_NAME=$(echo "${tmp}" | jq -r '.name')
BINARY_SHORT_NAME=$(echo "${BINARY_NAME}" | cut -d_ -f 1)
BINARY_URL=$(echo "${tmp}" | jq -r '.browser_download_url')
exec_with_dry_run "${DRY_RUN}" "curl --silent '${BINARY_URL}' -o '${BIN_PATH}${BINARY_NAME}'"
exec_with_dry_run "${DRY_RUN}" "chmod +x '${BIN_PATH}${BINARY_NAME}'"
exec_with_dry_run "${DRY_RUN}" "rm -f '${LN_PATH}${BINARY_SHORT_NAME}'"
exec_with_dry_run "${DRY_RUN}" "ln -s '${BIN_PATH}${BINARY_NAME}' '${LN_PATH}${BINARY_SHORT_NAME}'"
done
}
main () {
[[ -f ./libs/shflags ]] && . ./libs/shflags || eval "$(curl --silent https://git.g3e.fr/H6N/tools/raw/branch/main/libs/shflags)"
DEFINE_boolean 'dryrun' false 'Enable dry-run mode' 'd'
DEFINE_boolean 'up_script' true 'Upgrade script' 's'
DEFINE_string 'git_server' 'https://git.g3e.fr/' 'Git Server' 'g'
DEFINE_string 'repo_path' 'syonad/two/' 'Path of repository' 'r'
DEFINE_string 'branch' 'main/' 'Branch name' 'b'
DEFINE_string 'tag' '' 'Tag name' 't'
FLAGS "$@" || exit $?
eval set -- "${FLAGS_ARGV}"
SCRIPT_URL="${FLAGS_git_server}${FLAGS_repo_path}raw/branch/${FLAGS_branch}${SCRIPT_PATH}"
check_latest_script "${SCRIPT_URL}" "${0}" || (
[[ ${FLAGS_up_script} -eq ${FLAGS_TRUE} ]] && \
exec_with_dry_run "${FLAGS_dryrun}" "curl --silent '${SCRIPT_URL}' -o '${0}'"
exit 1
)
download_binaries "${FLAGS_dryrun}" "${FLAGS_tag}" "${FLAGS_git_server}" "${FLAGS_repo_path}"
}
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && (main "$@" || exit 1)
[[ "${BASH_SOURCE[0]}" == "" ]] && (main "$@" || exit 1)

View file

@ -0,0 +1,21 @@
#!/bin/bash
set -e
# Expects one argument: netns_bridge (e.g. vpc-00003_br-00002 or vpc1_br0)
arg="$1"
NETNS="${arg%%_*}"
BRIDGE="${arg#*_}"
echo "start dnsmasq ${NETNS} ${BRIDGE}"
exec ip netns exec "${NETNS}" \
dnsmasq \
--no-daemon \
--interface="${BRIDGE}" \
--bind-interfaces \
--pid-file="/run/dnsmasq-$arg.pid" \
--conf-file="/etc/dnsmasq.d/$arg.conf" \
--no-hosts \
--no-resolv \
--log-facility="/var/log/dnsmasq-$arg.log" \
--no-daemon -p0

11
systemd/dnsmasq@.service Normal file
View file

@ -0,0 +1,11 @@
[Unit]
Description=dnsmasq in netns %i
After=network.target
[Service]
Type=simple
ExecStart=/opt/two/bin/run-dnsmasq-in-netns.sh %i
ExecStopPost=/bin/rm -f /run/dnsmasq-%i.pid
[Install]
WantedBy=multi-user.target

10
systemd/metadata@.service Normal file
View file

@ -0,0 +1,10 @@
[Unit]
Description=metadata in netns %i
After=network.target
[Service]
Type=simple
ExecStart=/opt/two/bin/metadata --vm %i
[Install]
WantedBy=multi-user.target