Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
|
ceda93338b |
58 changed files with 69 additions and 1845 deletions
|
|
@ -32,13 +32,7 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
goos: [linux]
|
goos: [linux]
|
||||||
goarch: [amd64]
|
goarch: [amd64]
|
||||||
binaries:
|
binaries: [db, metadata]
|
||||||
- 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 }}
|
||||||
|
|
@ -46,24 +40,6 @@ 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]
|
||||||
|
|
|
||||||
11
NOTICE.md
Normal file
11
NOTICE.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# 🔒 Utilisation de logiciels open source
|
||||||
|
|
||||||
|
Notre logiciel utilise des composants open source, notamment **NetBox**, un outil de gestion d'infrastructure réseau développé par la communauté NetBox.
|
||||||
|
|
||||||
|
NetBox est distribué sous la **licence Apache License, Version 2.0**. Vous pouvez consulter cette licence à l'adresse suivante :
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Aucune modification n’a été apportée à NetBox dans notre intégration. Il est utilisé tel quel, en tant que base de données d'infrastructure.
|
||||||
|
|
||||||
|
Copyright © 2025 NetBox Contributors.
|
||||||
|
Nous reconnaissons et respectons le travail de la communauté NetBox.
|
||||||
0
cmd/agent/.keep
Normal file
0
cmd/agent/.keep
Normal file
|
|
@ -1,17 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
bin_name = os.Args[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
fmt.Printf("%s: Start process\n", bin_name)
|
|
||||||
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -52,13 +51,17 @@ 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 kv.AddInDB(DB, string(key), line)
|
return DB.Update(func(txn *badger.Txn) error {
|
||||||
|
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 kv.DeleteInDB(DB, string(key))
|
return DB.Update(func(txn *badger.Txn) error {
|
||||||
|
return txn.Delete(key)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func CountInDB(dbName, id string) int {
|
func CountInDB(dbName, id string) int {
|
||||||
|
|
@ -122,13 +125,7 @@ func printDB() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
conf_file := flag.String("conf", "/etc/two/agent.yml", "configuration file")
|
conf, err := configuration.LoadConfig("/etc/two/agent.yml")
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -136,56 +133,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(args) < 1 {
|
if len(os.Args) < 2 {
|
||||||
fmt.Println("Usage: db <cmd> [args...]")
|
fmt.Println("Usage: db <cmd> [args...]")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := args[0]
|
cmd := os.Args[1]
|
||||||
|
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case "check_in_db":
|
case "check_in_db":
|
||||||
if len(args) != 3 {
|
if len(os.Args) != 4 {
|
||||||
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(args[1], args[2])
|
ret := CheckInDB(os.Args[2], os.Args[3])
|
||||||
os.Exit(ret)
|
os.Exit(ret)
|
||||||
case "add_in_db":
|
case "add_in_db":
|
||||||
if len(args) < 3 {
|
if len(os.Args) < 4 {
|
||||||
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(args[2:], ";")
|
line := strings.Join(os.Args[3:], ";")
|
||||||
if err := AddInDB(args[1], line); err != nil {
|
if err := AddInDB(os.Args[2], 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(args) != 3 {
|
if len(os.Args) != 4 {
|
||||||
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(args[1], args[2]); err != nil {
|
if err := DeleteInDB(os.Args[2], os.Args[3]); 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(args) != 3 {
|
if len(os.Args) != 4 {
|
||||||
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(args[1], args[2])
|
count := CountInDB(os.Args[2], os.Args[3])
|
||||||
fmt.Println(count)
|
fmt.Println(count)
|
||||||
case "get_from_db":
|
case "get_from_db":
|
||||||
if len(args) != 3 {
|
if len(os.Args) != 4 {
|
||||||
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(args[1], args[2])
|
line, _ := GetFromDB(os.Args[2], os.Args[3])
|
||||||
fmt.Println(line)
|
fmt.Println(line)
|
||||||
case "print":
|
case "print":
|
||||||
printDB()
|
printDB()
|
||||||
|
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +1,9 @@
|
||||||
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() {
|
||||||
flag.Parse()
|
metadata.StartServer()
|
||||||
|
|
||||||
metadata.StartServer(metadata.ServerConfig{
|
|
||||||
Netns: *netns_name,
|
|
||||||
Iface: *iface,
|
|
||||||
Port: *port,
|
|
||||||
ConfFile: *conf_file,
|
|
||||||
VmName: *vm_name,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
database:
|
|
||||||
path: "./data/"
|
|
||||||
10
go.mod
10
go.mod
|
|
@ -1,12 +1,9 @@
|
||||||
module git.g3e.fr/syonad/two
|
module git.g3e.fr/syonad/two
|
||||||
|
|
||||||
go 1.24.0
|
go 1.23.8
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -14,7 +11,6 @@ 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
|
||||||
|
|
@ -25,15 +21,13 @@ 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.39.0 // indirect
|
golang.org/x/sys v0.34.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
12
go.sum
|
|
@ -1,7 +1,5 @@
|
||||||
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=
|
||||||
|
|
@ -17,8 +15,6 @@ 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=
|
||||||
|
|
@ -39,10 +35,6 @@ 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=
|
||||||
|
|
@ -55,12 +47,8 @@ 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=
|
||||||
|
|
|
||||||
0
internal/.keep
Normal file
0
internal/.keep
Normal file
|
|
@ -1,83 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -17,7 +17,9 @@ func LoadConfig(path string) (*Config, error) {
|
||||||
|
|
||||||
v.SetDefault("database.path", "/var/lib/two/data/")
|
v.SetDefault("database.path", "/var/lib/two/data/")
|
||||||
|
|
||||||
v.ReadInConfig()
|
if err := v.ReadInConfig(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var cfg Config
|
var cfg Config
|
||||||
if err := v.Unmarshal(&cfg); err != nil {
|
if err := v.Unmarshal(&cfg); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,157 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package dhcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Network *net.IPNet
|
|
||||||
Gateway net.IP
|
|
||||||
Name string
|
|
||||||
ConfDir string
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,200 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +1,24 @@
|
||||||
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 {
|
||||||
|
|
@ -23,51 +27,6 @@ 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
|
||||||
|
|
@ -92,18 +51,25 @@ func rootHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartServer(config ServerConfig) {
|
func StartServer() {
|
||||||
data = getFromDB(config)
|
flag.Parse()
|
||||||
|
|
||||||
if data.NetNs != "" {
|
if *file == "" {
|
||||||
if err := netns.Enter(data.NetNs); err != nil {
|
log.Fatal("Vous devez spécifier un fichier via --file")
|
||||||
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", data.Iface, data.Port)
|
address := fmt.Sprintf("%s:%d", *iface, *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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,8 @@
|
||||||
package metadata
|
package metadata
|
||||||
|
|
||||||
type NoCloudData struct {
|
type NoCloudData struct {
|
||||||
MetaData string
|
MetaData string `json:"meta-data"`
|
||||||
UserData string
|
UserData string `json:"user-data"`
|
||||||
NetworkConfig string
|
NetworkConfig string `json:"network-config"`
|
||||||
VendorData string
|
VendorData string `json:"vendor-data"`
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
instance-id: {{ .Name }}
|
|
||||||
local-hostname: {{ .Name }}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
version: 2
|
|
||||||
ethernets:
|
|
||||||
eth0:
|
|
||||||
dhcp4: true
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
passwd -d root
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
#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 }}"
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package netns
|
|
||||||
|
|
||||||
func Call(name string, fn func() error) error {
|
|
||||||
return call(name, fn)
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
//go:build !linux
|
|
||||||
|
|
||||||
package netns
|
|
||||||
|
|
||||||
func call(name string, fn func() error) error {
|
|
||||||
return fn()
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package netns
|
|
||||||
|
|
||||||
func Create(name string) error {
|
|
||||||
return create(name)
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
//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
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
//go:build !linux
|
|
||||||
|
|
||||||
package netns
|
|
||||||
|
|
||||||
func create(string) error { return nil }
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package netns
|
|
||||||
|
|
||||||
func Delete(name string) error {
|
|
||||||
return delete(name)
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
//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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
//go:build !linux
|
|
||||||
|
|
||||||
package netns
|
|
||||||
|
|
||||||
func delete(string) error { return nil }
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package netns
|
|
||||||
|
|
||||||
func Enter(name string) error {
|
|
||||||
return enter(name)
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
//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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
//go:build !linux
|
|
||||||
|
|
||||||
package netns
|
|
||||||
|
|
||||||
func enter(name string) error {
|
|
||||||
// Ignoré hors Linux
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
0
pkg/.keep
Normal file
0
pkg/.keep
Normal file
|
|
@ -1,11 +0,0 @@
|
||||||
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))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -4,9 +4,8 @@ import (
|
||||||
"github.com/dgraph-io/badger/v4"
|
"github.com/dgraph-io/badger/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitDB(conf Config, readonly bool) *badger.DB {
|
func InitDB(conf Config) *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
|
||||||
|
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
#!/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)
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
[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
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=metadata in netns %i
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=/opt/two/bin/metadata --vm %i
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue