Skip to content

Commit 75d63ce

Browse files
committed
Initial commit
0 parents  commit 75d63ce

File tree

10 files changed

+515
-0
lines changed

10 files changed

+515
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
docker-setup

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright © 2023 Louis Royer
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Docker-setup
2+
Docker-setup is a program that allow configure a container (for networking) via environment variables.
3+
4+
## Usage
5+
This program only use environment variables for its configuration:
6+
- `ONESHOT`: when this environment variable is equal to `true`, the program will *not* sleep until a signal (SIGINT or SIGTERM) is received, and not perform cleaning scripts on exit
7+
- `NAT4_IFACES` is a list of interfaces where MASQUERADE will be enabled
8+
- `ROUTES_INIT` is a list of routes modifications that will be performed on init
9+
- `ROUTES_EXIT` is a list of routes modifications that will be performed on exit
10+
- `PRE_INIT_HOOK` is a command that is run before the init (nat & routes init). The command can takes some arguments from `PRE_INIT_HOOK_0`, `PRE_INIT_HOOK_1`, and so on.
11+
- `POST_INIT_HOOK` is a command that is run after the init (nat & routes init). The command can takes some arguments from `POST_INIT_HOOK_0`, `POST_INIT_HOOK_1`, and so on.
12+
- `PRE_EXIT_HOOK` is a command that is run before the exit (nat & routes cleaning). The command can takes some arguments from `PRE_EXIT_HOOK_0`, `PRE_EXIT_HOOK_1`, and so on.
13+
- `POST_EXIT_HOOK` is a command that is run after the exit (nat & routes cleaning). The command can takes some arguments from `POST_EXIT_HOOK_0`, `POST_EXIT_HOOK_1`, and so on.
14+
15+
### Example
16+
In Docker Compose:
17+
```yaml
18+
volumes:
19+
- "./config_init.sh:/usr/local/bin/config_init.sh:ro"
20+
- "./config_exit.sh:/usr/local/bin/config_exit.sh:ro"
21+
environment:
22+
ONESHOT: "false"
23+
ROUTES_INIT: |-
24+
- add 10.0.1.0/24 via 10.0.0.2
25+
- add 10.0.2.0/24 via 10.0.0.3
26+
ROUTES_EXIT: |-
27+
- del 10.0.1.0/24
28+
- del 10.0.2.0/24
29+
NAT4_IFACES: |-
30+
- eth0
31+
- eth1
32+
PRE_INIT_HOOK: config_init.sh
33+
PRE_INIT_HOOK_0: "Hello"
34+
PRE_INIT_HOOK_1: "World"
35+
PRE_INIT_HOOK_2: "!"
36+
PRE_EXIT_HOOK: config_exit.sh
37+
PRE_EXIT_HOOK_0: "Goodbye"
38+
```
39+
40+
## Getting started
41+
### Build dependencies
42+
- golang
43+
44+
### Runtime dependencies
45+
- iproute2
46+
- iptables
47+
48+
### Build
49+
Run `go build`
50+
51+
### Docker
52+
- The container requires the `NET_ADMIN` capability;
53+
54+
This can be done in `docker-compose.yaml` by defining the following for the service:
55+
56+
```yaml
57+
cap_add:
58+
- NET_ADMIN
59+
```
60+
61+
## Author
62+
Louis Royer
63+
64+
## License
65+
MIT

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/louisroyer/docker-setup
2+
3+
go 1.19

main.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"os"
6+
"os/signal"
7+
"syscall"
8+
9+
setup "github.com/louisroyer/docker-setup/runtime"
10+
)
11+
12+
// Initialize signals handling
13+
func initSignals(conf setup.Conf) {
14+
cancelChan := make(chan os.Signal, 1)
15+
signal.Notify(cancelChan, syscall.SIGTERM, syscall.SIGINT)
16+
func(_ os.Signal) {}(<-cancelChan)
17+
conf.RunExitHooks()
18+
os.Exit(0)
19+
}
20+
21+
// Print the configuration, then startup
22+
func main() {
23+
log.SetPrefix("[docker-setup]")
24+
conf := setup.NewConf()
25+
conf.Log()
26+
go initSignals(conf)
27+
conf.RunInitHooks()
28+
if !conf.Oneshot() {
29+
select {}
30+
}
31+
}

runtime/conf.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package setup
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
)
8+
9+
// Configuration
10+
type Conf struct {
11+
hooksList map[string]Hook
12+
oneshot bool
13+
}
14+
15+
// Create a new configuration from env variables
16+
func NewConf() Conf {
17+
conf := Conf{
18+
hooksList: make(map[string]Hook, 0),
19+
}
20+
conf.AddHooks()
21+
conf.AddUserHooks("pre", "PRE")
22+
conf.AddUserHooks("post", "POST")
23+
conf.oneshot = false
24+
if oneshot, isset := os.LookupEnv("ONESHOT"); isset && oneshot == "true" {
25+
conf.oneshot = true
26+
}
27+
return conf
28+
}
29+
30+
// Return true if Oneshot is set
31+
func (conf Conf) Oneshot() bool {
32+
return conf.oneshot
33+
}
34+
35+
// Run exit hooks
36+
func (conf Conf) RunExitHooks() {
37+
conf.RunExitHook("pre")
38+
conf.RunExitHook("nat4")
39+
conf.RunExitHook("iproute")
40+
conf.RunExitHook("post")
41+
}
42+
43+
// Run init hooks
44+
func (conf Conf) RunInitHooks() {
45+
conf.RunInitHook("pre")
46+
conf.RunInitHook("iproute")
47+
conf.RunInitHook("nat4")
48+
conf.RunInitHook("post")
49+
}
50+
51+
// Add a new hook to the configuration
52+
func (conf Conf) AddUserHooks(name string, env string) {
53+
conf.hooksList[name] = NewUserHooks(
54+
fmt.Sprintf("%s init", name), fmt.Sprintf("%s_INIT_HOOK", env),
55+
fmt.Sprintf("%s exit", name), fmt.Sprintf("%s_EXIT_HOOK", env))
56+
}
57+
58+
// Add default hooks
59+
func (conf Conf) AddHooks() {
60+
conf.hooksList["iproute"] = NewIPRouteHooks(
61+
"iproute init", "ROUTES_INIT",
62+
"iproute exit", "ROUTES_EXIT")
63+
conf.hooksList["nat4"] = NewNat4Hooks()
64+
}
65+
66+
// Run an init hook
67+
func (conf Conf) RunInitHook(name string) {
68+
if conf.hooksList[name] != nil {
69+
if err := conf.hooksList[name].RunInit(); err != nil {
70+
log.Printf("Error while running %s init hook: %s", name, err)
71+
}
72+
}
73+
}
74+
75+
// Run an exit hook
76+
func (conf Conf) RunExitHook(name string) {
77+
if conf.hooksList[name] != nil {
78+
if err := conf.hooksList[name].RunExit(); err != nil {
79+
log.Printf("Error while running %s exit hook: %s", name, err)
80+
}
81+
}
82+
}
83+
84+
// Log the configuration
85+
func (conf Conf) Log() {
86+
log.Println("Current configuration:")
87+
if conf.oneshot {
88+
log.Printf("\t- mode oneshot is enabled")
89+
} else {
90+
log.Printf("\t- mode oneshot is disabled")
91+
}
92+
for _, h := range conf.hooksList {
93+
for _, s := range h.String() {
94+
log.Printf("\t- %s\n", s)
95+
}
96+
}
97+
98+
}

runtime/hook.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package setup
2+
3+
// init & exit hooks iface
4+
type Hook interface {
5+
String() []string
6+
RunInit() error
7+
RunExit() error
8+
}
9+
10+
// init or exit hook iface
11+
type HookSingle interface {
12+
String() []string
13+
Run() error
14+
}
15+
16+
// init & exit hooks
17+
type HookMulti struct {
18+
init HookSingle
19+
exit HookSingle
20+
}
21+
22+
// Returns hooks info
23+
func (hooks HookMulti) String() []string {
24+
r := []string{}
25+
for _, i := range hooks.init.String() {
26+
r = append(r, i)
27+
}
28+
for _, i := range hooks.exit.String() {
29+
r = append(r, i)
30+
}
31+
return r
32+
}
33+
34+
// Runs init hook
35+
func (hooks HookMulti) RunInit() error {
36+
return hooks.init.Run()
37+
}
38+
39+
// Runs exit hook
40+
func (hooks HookMulti) RunExit() error {
41+
return hooks.exit.Run()
42+
}

runtime/iproutehook.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package setup
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"strings"
8+
)
9+
10+
// Runs iptables
11+
func runIPRoute(args ...string) error {
12+
r := []string{"route"}
13+
r = append(r, args...)
14+
cmd := exec.Command("ip", r...)
15+
return cmd.Run()
16+
}
17+
18+
// Create init & exit hooks
19+
func NewIPRouteHooks(nameInit string, envInit string, nameExit string, envExit string) HookMulti {
20+
return HookMulti{
21+
init: NewIPRouteHook(nameInit, envInit),
22+
exit: NewIPRouteHook(nameExit, envExit),
23+
}
24+
}
25+
26+
// IP Route Hook
27+
type IPRouteHook struct {
28+
name string // name of the hook
29+
env string // environment variable
30+
isset bool // false when no cmd is set
31+
routes [][]string
32+
}
33+
34+
// Create an IP Route Hook
35+
func NewIPRouteHook(name string, env string) IPRouteHook {
36+
hook := IPRouteHook{
37+
name: name,
38+
env: env,
39+
}
40+
if ifaces, ok := os.LookupEnv(hook.env); !ok {
41+
hook.isset = false
42+
} else {
43+
rl := strings.Split(ifaces, "\n")
44+
45+
hook.routes = make([][]string, len(rl))
46+
for i, route := range rl {
47+
hook.routes[i] = strings.Split(strings.TrimSpace(strings.TrimLeft(strings.TrimSpace(route), "-")), " ")
48+
}
49+
hook.isset = true
50+
}
51+
return hook
52+
}
53+
54+
// Run the hook if it is set
55+
func (hook IPRouteHook) Run() error {
56+
for _, r := range hook.routes {
57+
r = append(r, "proto", "static")
58+
if err := runIPRoute(r...); err != nil {
59+
return err
60+
}
61+
}
62+
return nil
63+
}
64+
65+
// Returns hook information in an human format
66+
func (hook IPRouteHook) String() []string {
67+
r := []string{}
68+
if !hook.isset {
69+
return []string{fmt.Sprintf("%s hook ($%s) is not set.", hook.name, hook.env)}
70+
}
71+
for i, route := range hook.routes {
72+
args := ""
73+
for _, cmd := range route {
74+
args += fmt.Sprintf(", %s", cmd)
75+
}
76+
r = append(r, fmt.Sprintf("%s #%d hook is set with : [ip, route%s, proto, static]", hook.name, i, args))
77+
}
78+
return r
79+
}

0 commit comments

Comments
 (0)