diff --git a/connet.service b/connet.service index 2573f25..a3cf0e2 100644 --- a/connet.service +++ b/connet.service @@ -9,15 +9,14 @@ DefaultDependencies=no After=systemd-udevd.service dbus.service network-pre.target systemd-sysusers.service systemd-sysctl.service Before=network.target multi-user.target shutdown.target Conflicts=shutdown.target -Wants=network.target +Wants=network-online.target [Service] # the VT is cleared by TTYVTDisallocate RemainAfterExit=yes -#Environment=IP_ADDR= GATEWAY_ADDR= EnvironmentFile=/proc/1/environ -ExecStart=/connet +ExecStart=/usr/bin/connet Type=oneshot [Install] WantedBy=multi-user.target diff --git a/gp/gp b/gp/gp index 573b731..6ffc763 100755 Binary files a/gp/gp and b/gp/gp differ diff --git a/gp/main.go b/gp/main.go index 6c37a0a..6808781 100644 --- a/gp/main.go +++ b/gp/main.go @@ -1,123 +1,419 @@ package main import ( - "github.com/vishvananda/netlink" - "io/ioutil" - "strings" - "net" - "fmt" - "os" - "regexp" + // "github.com/ttacon/libphonenumber" + + "bytes" + "errors" + "fmt" + "net" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + + "io/ioutil" + + kingpin "gopkg.in/alecthomas/kingpin.v2" + "gopkg.in/yaml.v2" + + "strconv" + + errs "github.com/pkg/errors" + "github.com/vishvananda/netlink" ) -var env map[string]string=make(map[string]string) +var ( + app = kingpin.New("plugin", "nspawn Bootstrapper.").Author("Sangbum Kim") + verbosePtr = app.Flag("verbose", "Enable verbose mode.").Short('v').Bool() + clearPtr = app.Flag("clear", "clear os environment.").Short('p').Bool() + nspawnPathPtr = app.Flag("nspawn-path", "systemd-nspawn location.").Short('s').String() + containerHomePtr = app.Flag("container-home", "container residence path").Default("/container").Short('d').String() + configFileNamePtr = app.Flag("config-file", "config file name").Default("settings.yml").HintOptions("FILENAME").Short('c').String() + nodeNamePtr = app.Arg("nodeName", "nodename to spawn.").Required().String() + bindIfNamePtr = app.Arg("bindInterface", "interface name to bind.").Required().String() +) -func main(){ - readInitEnviron() - - macAddr,_ := net.ParseMAC(getEnv("IF_ADDR")) -// fpAddr,_ := netlink.ParseAddr(getEnv("IP_ADDR")) -// gatewayAddr := net.ParseIP(getEnv("GATEWAY_ADDR")) - - linkInterfaceType:=getEnvWithDefault("IF_TYPE","macvlan") - - link:=findFirstInterface(linkInterfaceType) - - renameInterface(link,"eth0") - - renameMacAddress(link,macAddr) - -/* - #netlink.AddrAdd(link,ipAddr) - - #netlink.LinkSetUp(link) - - #addRoute(gatewayAddr,link) -*/ +type mountPoint struct { + Name string `yaml:"name"` + Map string `yaml:"map"` + Readonly bool `yaml:"readonly"` } -func readInitEnviron(){ - envFilePath:="/proc/1/environ" - content, err := ioutil.ReadFile(envFilePath) - kvParseRegex,_:=regexp.Compile("^([^=]+)=(.+)$") - if err != nil { - panic(fmt.Sprintf("environ file %s not existed!",envFilePath)) - } - lines := strings.Split(string(content), "\x00") - for _,element := range lines { - result_slice := kvParseRegex.FindStringSubmatch(element) - if len(result_slice) < 2 { - continue - }else{ - env[result_slice[1]]=result_slice[2] - } - } -} -func renameMacAddress(link netlink.Link,new_name net.HardwareAddr){ - err:=netlink.LinkSetHardwareAddr(link,new_name) - if(err != nil){ - panic(fmt.Sprintf("cannot rename mac address %s -> %s",link.Attrs().HardwareAddr,new_name)) - } else { - fmt.Printf("rename mac address %s -> %s\n",link.Attrs().HardwareAddr,new_name) - } - -} - -func renameInterface(link netlink.Link,new_name string) { - err:=netlink.LinkSetName(link,new_name) - if(err != nil){ - panic(fmt.Sprintf("cannot rename interface %s -> %s",link.Attrs().Name,new_name)) - } else { - fmt.Printf("rename interface %s -> %s\n",link.Attrs().Name,new_name) - } -} - -func findFirstInterface(if_type string) netlink.Link { - links,_:=netlink.LinkList() - for _,element := range links { - if(element.Type() == if_type){ - fmt.Println(element.Attrs().Name) - return element - } - } - panic(fmt.Sprintf("cannot get type: %s interface",if_type)) -} - -func getEnv(variable string) string { - envValue,ok:=env[variable] - if ok { - return envValue - } - envValue=os.Getenv(variable) - - if(envValue == ""){ - panic(fmt.Sprintf("cannot get ${%s}",variable)) - } else{ - return envValue - } -} - -func getEnvWithDefault(variable string, ret_is_empty string) string { - envValue,ok:=env[variable] - if ok { - return envValue - } - envValue=os.Getenv(variable) - - if(envValue == ""){ - return ret_is_empty - } else{ - return envValue - } -} - -func addRoute(gateway net.IP,link netlink.Link){ - default_network := &net.IPNet{ - IP: net.IPv4(0, 0, 0, 0), - Mask: net.CIDRMask(0, 32), +func (mp mountPoint) Option(nodePath string) (option string, err error) { + var mountPointPath string + if filepath.IsAbs(mp.Name){ + mountPointPath= mp.Name + }else{ + mountPointPath= filepath.Clean(filepath.Join(nodePath, "volume", mp.Name)) } - route := &netlink.Route{LinkIndex: link.Attrs().Index, Dst: default_network, Gw:gateway} + if _, err = os.Stat(mountPointPath); os.IsNotExist(err) { + err = errors.New(fmt.Sprintf("mount point %s is not exist", mountPointPath)) + return + } - netlink.RouteAdd(route) + var buffer bytes.Buffer + if mp.Readonly { + buffer.WriteString("--bind-ro=") + } else { + buffer.WriteString("--bind=") + } + buffer.WriteString(mountPointPath) + buffer.WriteRune(':') + buffer.WriteString(mp.Map) + option = buffer.String() + return } +func (mp mountPoint) String() string { + var buffer bytes.Buffer + buffer.WriteString(mp.Name) + if mp.Readonly { + buffer.WriteString("(readonly)") + } + buffer.WriteString("->") + buffer.WriteString(mp.Map) + return buffer.String() +} + +type config struct { + MoundPoint []mountPoint `yaml:"mount-point"` +} + +func (c config) String() string { + var buffer bytes.Buffer + buffer.WriteString("config:\n") + buffer.WriteString(" mount-point:\n") + for _, entry := range c.MoundPoint { + buffer.WriteString(" - ") + buffer.WriteString(entry.String()) + buffer.WriteRune('\n') + } + return buffer.String() +} + +// LookupIP looks up host using the local resolver. +// It returns an array of that host's IPv4 and IPv6 addresses. +func getIP(host string) (ip net.IP, err error) { + var addrs []net.IP + addrs, err = net.LookupIP(host) + if err != nil { + return + } + return addrs[0], nil +} + +func getOutboundIPInfo(verbose bool) (gw net.IP, cidr uint64, linkname string, err error) { + // netlink.RouteList + var links []netlink.Link + links, err = netlink.LinkList() + if err != nil { + return + } + + if verbose { + fmt.Println("scan system network interfaces:") + } + for _, link := range links { + attrs := link.Attrs() + + if verbose { + fmt.Printf(" interface %s : ", attrs.Name) + } + if (attrs.Flags & net.FlagUp) == 0 { + if verbose { + fmt.Printf(" not up -- abandon!\n") + } + continue + } else { + if verbose { + fmt.Printf(" up!\n") + } + } + + if verbose { + fmt.Println(" getting routing tables ") + } + var routes []netlink.Route + routes, err = netlink.RouteList(link, netlink.FAMILY_V4) + if err != nil { + continue + } + var ( + matchedGw net.IP + matchedCidr uint64 + cidrOk = false + ) + for _, route := range routes { + + if verbose { + fmt.Printf(" routing table %s\n", route.String()) + } + if route.Dst == nil { + if verbose { + fmt.Printf(" - default gateway\n") + } + matchedGw = route.Gw + break + } else if verbose { + fmt.Printf(" - no default gateway\n") + } + } + if matchedGw == nil { + if verbose { + fmt.Println(" interface have not a default routing rule.") + } + continue + } else if verbose { + fmt.Printf(" interface have a default routing rule. gateway: %s", matchedGw.String()) + } + + if verbose { + fmt.Println(" getting networks ") + } + var networks []netlink.Addr + networks, err = netlink.AddrList(link, netlink.FAMILY_V4) + if err != nil { + fmt.Println(" - cannot getting networks ") + continue + } + for _, network := range networks { + if verbose { + fmt.Printf(" network %s\n", network.String()) + } + if network.Contains(matchedGw) { + if verbose { + fmt.Printf(" - default network\n") + } + netSlice := strings.Split(network.IPNet.String(), "/") + if len(netSlice) < 2 { + continue + } + if cidrData, err := strconv.ParseUint(netSlice[len(netSlice)-1], 10, 64); err == nil { + matchedCidr = cidrData + cidrOk = true + break + } + } else if verbose { + fmt.Printf(" - no default network\n") + } + } + if cidrOk { + gw, cidr, linkname = matchedGw, matchedCidr, attrs.Name + if verbose { + fmt.Println(" ok! matched ") + } + break + } else if verbose { + fmt.Println(" abandon this interface ") + } + } + if gw == nil { + err = errors.New("default gateway or cidr not found!") + } + + return +} + +func checkInterfaceValid(bindIfName string) (err error) { + var link netlink.Link + link, err = netlink.LinkByName(bindIfName) + + if err != nil { + return + } + attrs := link.Attrs() + if (attrs.Flags & net.FlagUp) == 0 { + err = errors.New("bind interface had been deactivated") + } + return +} + +func checkNodeBasicDirectory(containerHomePath string, nodeName string, configFileName string) (nodePath string, nodeConfigPath string, err error) { + var containerHomeInfo os.FileInfo + if containerHomeInfo, err = os.Stat(containerHomePath); os.IsNotExist(err) { + err = errors.New(fmt.Sprintf("container home path %s is not exist", containerHomePath)) + return + } else if !containerHomeInfo.IsDir() { + err = errors.New(fmt.Sprintf("container home path %s is not a directory", containerHomePath)) + return + } + + nodePath = filepath.Join(containerHomePath, nodeName) + + var nodePathInfo os.FileInfo + if nodePathInfo, err = os.Stat(nodePath); os.IsNotExist(err) { + err = errors.New(fmt.Sprintf("node path %s is not exist", nodePath)) + return + } else if !nodePathInfo.IsDir() { + err = errors.New(fmt.Sprintf("node path %s is not a directory", nodePath)) + return + } else { + subDirNames := [...]string{"merge", "work", "delta"} + for _, subDirName := range subDirNames { + subPath := filepath.Join(nodePath, subDirName) + + var subPathInfo os.FileInfo + if subPathInfo, err = os.Stat(subPath); os.IsNotExist(err) { + err = errors.New(fmt.Sprintf("sub path %s is not exist", subPath)) + return + } else if !subPathInfo.IsDir() { + err = errors.New(fmt.Sprintf("sub path %s is not a directory", subPath)) + return + } + } + } + nodeConfigPath = filepath.Join(containerHomePath, nodeName, configFileName) + + var nodeConfigPathInfo os.FileInfo + if nodeConfigPathInfo, err = os.Stat(nodeConfigPath); os.IsNotExist(err) || nodeConfigPathInfo.IsDir() { + nodeConfigPath = "" + } + + return + +} + +func LoadConfig(path string) (config config, err error) { + var ( + source []byte + ) + if path == "" { + } else if source, err = ioutil.ReadFile(path); err != nil { + } else if err = yaml.Unmarshal(source, &config); err != nil { + } + return +} + +func main() { + _, err := app.Parse(os.Args[1:]) + var ( + verbose = *verbosePtr + clear = *clearPtr + nodeName = *nodeNamePtr + bindIfName = *bindIfNamePtr + configFileName = *configFileNamePtr + nspawnPath string + progress = -1 + ) + + defer func() { + if r := recover(); r != nil { + fmt.Fprintln(os.Stderr, r) + os.Exit(progress) + } + }() + if err != nil { + panic(err) + } + + progress = 1 + containerHomePath, err := filepath.Abs(*containerHomePtr) + if err != nil { + panic(err) + } + + progress++ + nodePath, nodeConfigPath, err := checkNodeBasicDirectory(containerHomePath, nodeName, configFileName) + if err != nil { + panic(err) + } else if verbose { + var nodeConfigPathDesc = "not used" + if nodeConfigPath != "" { + nodeConfigPathDesc = nodeConfigPath + } + fmt.Printf(`sane container environment: + - container home : %s + - node home : %s + - node config : %s +`, containerHomePath, nodePath, nodeConfigPathDesc) + } + config, err := LoadConfig(nodeConfigPath) + if err != nil { + panic(err) + } else if verbose { + fmt.Printf(config.String()) + } + + progress++ + + if nspawnPathPtr != nil && *nspawnPathPtr != "" { + nspawnPath = *nspawnPathPtr + } else if nspawnPathData, err := exec.LookPath("systemd-nspawn"); err != nil { + panic(errs.Wrap(err, "cannot find systemd-nspawn")) + } else { + nspawnPath = nspawnPathData + if verbose { + fmt.Printf("systemd-nspawn found : %s\n", nspawnPath) + } + } + + progress++ + err = checkInterfaceValid(bindIfName) + if err != nil { + panic(errs.Wrap(err, "error in checkInterfaceValid")) + } + progress++ + nodeAddr, err := getIP(nodeName) + + if err != nil { + panic(errs.Wrap(err, "error in getIP")) + } + + progress++ + gw, cidr, linkname, err := getOutboundIPInfo(verbose) + if err != nil { + panic(errs.Wrap(err, "error in getOutboundIPInfo")) + } + + if verbose { + fmt.Println("network configuration:") + fmt.Printf(" - bind interface{%s} : valid\n", bindIfName) + fmt.Printf(" - default routing info : ip %s/%d link %s\n", gw.String(), cidr, linkname) + fmt.Printf(" - node{%s} IP address : %s\n", nodeName, nodeAddr.String()) + } + option := []string{ + "--quiet", + "--boot", + "--link-journal=try-host", + "--notify-ready=true"} + option = append(option, fmt.Sprintf("--network-ipvlan=%s", bindIfName)) + option = append(option, fmt.Sprintf("--setenv=IP_ADDR=%s/%d", nodeAddr.String(), cidr)) + option = append(option, fmt.Sprintf("--setenv=GATEWAY_ADDR=%s", gw.String())) + option = append(option, fmt.Sprintf("--machine=%s", nodeName)) + option = append(option, fmt.Sprintf("--directory=%s", filepath.Join(nodePath, "merge"))) + + for _, mountPoint := range config.MoundPoint { + if mountOption, err := mountPoint.Option(nodePath); err != nil { + panic(errs.Wrap(err, "error in mountOption")) + } else { + option = append(option, mountOption) + } + } + var env []string + if clear { + env = []string{} + } else { + env = os.Environ() + if verbose { + fmt.Println("os environment:") + for _, segment := range env { + fmt.Println(" ", segment) + } + } + } + if verbose { + fmt.Println("systemd-nspawn spawning:") + fmt.Println(nspawnPath, " \\") + for _, segment := range option { + fmt.Println(" ", segment, " \\") + } + fmt.Println(" ;") + fmt.Println("bye!") + } + if err := syscall.Exec(nspawnPath, option, env); err != nil { + panic(errs.Wrap(err, "error in systemd-nspawn execution")) + } +} + diff --git a/gp/src/github.com/vishvananda/netlink b/gp/src/github.com/vishvananda/netlink index 814b43a..13fb20a 160000 --- a/gp/src/github.com/vishvananda/netlink +++ b/gp/src/github.com/vishvananda/netlink @@ -1 +1 @@ -Subproject commit 814b43a0c71f01a137581a3fadf37619e71751b8 +Subproject commit 13fb20a9787c14bfc4c7996ce27d7224aa815812 diff --git a/main.go b/main.go index 6c37a0a..8826cae 100644 --- a/main.go +++ b/main.go @@ -15,25 +15,24 @@ var env map[string]string=make(map[string]string) func main(){ readInitEnviron() - macAddr,_ := net.ParseMAC(getEnv("IF_ADDR")) -// fpAddr,_ := netlink.ParseAddr(getEnv("IP_ADDR")) -// gatewayAddr := net.ParseIP(getEnv("GATEWAY_ADDR")) - linkInterfaceType:=getEnvWithDefault("IF_TYPE","macvlan") + linkInterfaceType:=getEnvWithDefault("IF_TYPE","ipvlan") link:=findFirstInterface(linkInterfaceType) renameInterface(link,"eth0") - renameMacAddress(link,macAddr) - -/* - #netlink.AddrAdd(link,ipAddr) - - #netlink.LinkSetUp(link) - - #addRoute(gatewayAddr,link) -*/ + switch linkInterfaceType { + case "macvlan": + macAddr,_ := net.ParseMAC(getEnv("IF_ADDR")) + renameMacAddress(link,macAddr) + case "ipvlan": + ipAddr,_ := netlink.ParseAddr(getEnv("IP_ADDR")) + gatewayAddr := net.ParseIP(getEnv("GATEWAY_ADDR")) + netlink.AddrAdd(link,ipAddr) + netlink.LinkSetUp(link) + addRoute(gatewayAddr,link) + } } func readInitEnviron(){ diff --git a/plugin.go b/plugin.go new file mode 100644 index 0000000..6808781 --- /dev/null +++ b/plugin.go @@ -0,0 +1,419 @@ +package main + +import ( + // "github.com/ttacon/libphonenumber" + + "bytes" + "errors" + "fmt" + "net" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + + "io/ioutil" + + kingpin "gopkg.in/alecthomas/kingpin.v2" + "gopkg.in/yaml.v2" + + "strconv" + + errs "github.com/pkg/errors" + "github.com/vishvananda/netlink" +) + +var ( + app = kingpin.New("plugin", "nspawn Bootstrapper.").Author("Sangbum Kim") + verbosePtr = app.Flag("verbose", "Enable verbose mode.").Short('v').Bool() + clearPtr = app.Flag("clear", "clear os environment.").Short('p').Bool() + nspawnPathPtr = app.Flag("nspawn-path", "systemd-nspawn location.").Short('s').String() + containerHomePtr = app.Flag("container-home", "container residence path").Default("/container").Short('d').String() + configFileNamePtr = app.Flag("config-file", "config file name").Default("settings.yml").HintOptions("FILENAME").Short('c').String() + nodeNamePtr = app.Arg("nodeName", "nodename to spawn.").Required().String() + bindIfNamePtr = app.Arg("bindInterface", "interface name to bind.").Required().String() +) + +type mountPoint struct { + Name string `yaml:"name"` + Map string `yaml:"map"` + Readonly bool `yaml:"readonly"` +} + +func (mp mountPoint) Option(nodePath string) (option string, err error) { + var mountPointPath string + if filepath.IsAbs(mp.Name){ + mountPointPath= mp.Name + }else{ + mountPointPath= filepath.Clean(filepath.Join(nodePath, "volume", mp.Name)) + } + + if _, err = os.Stat(mountPointPath); os.IsNotExist(err) { + err = errors.New(fmt.Sprintf("mount point %s is not exist", mountPointPath)) + return + } + + var buffer bytes.Buffer + if mp.Readonly { + buffer.WriteString("--bind-ro=") + } else { + buffer.WriteString("--bind=") + } + buffer.WriteString(mountPointPath) + buffer.WriteRune(':') + buffer.WriteString(mp.Map) + option = buffer.String() + return +} +func (mp mountPoint) String() string { + var buffer bytes.Buffer + buffer.WriteString(mp.Name) + if mp.Readonly { + buffer.WriteString("(readonly)") + } + buffer.WriteString("->") + buffer.WriteString(mp.Map) + return buffer.String() +} + +type config struct { + MoundPoint []mountPoint `yaml:"mount-point"` +} + +func (c config) String() string { + var buffer bytes.Buffer + buffer.WriteString("config:\n") + buffer.WriteString(" mount-point:\n") + for _, entry := range c.MoundPoint { + buffer.WriteString(" - ") + buffer.WriteString(entry.String()) + buffer.WriteRune('\n') + } + return buffer.String() +} + +// LookupIP looks up host using the local resolver. +// It returns an array of that host's IPv4 and IPv6 addresses. +func getIP(host string) (ip net.IP, err error) { + var addrs []net.IP + addrs, err = net.LookupIP(host) + if err != nil { + return + } + return addrs[0], nil +} + +func getOutboundIPInfo(verbose bool) (gw net.IP, cidr uint64, linkname string, err error) { + // netlink.RouteList + var links []netlink.Link + links, err = netlink.LinkList() + if err != nil { + return + } + + if verbose { + fmt.Println("scan system network interfaces:") + } + for _, link := range links { + attrs := link.Attrs() + + if verbose { + fmt.Printf(" interface %s : ", attrs.Name) + } + if (attrs.Flags & net.FlagUp) == 0 { + if verbose { + fmt.Printf(" not up -- abandon!\n") + } + continue + } else { + if verbose { + fmt.Printf(" up!\n") + } + } + + if verbose { + fmt.Println(" getting routing tables ") + } + var routes []netlink.Route + routes, err = netlink.RouteList(link, netlink.FAMILY_V4) + if err != nil { + continue + } + var ( + matchedGw net.IP + matchedCidr uint64 + cidrOk = false + ) + for _, route := range routes { + + if verbose { + fmt.Printf(" routing table %s\n", route.String()) + } + if route.Dst == nil { + if verbose { + fmt.Printf(" - default gateway\n") + } + matchedGw = route.Gw + break + } else if verbose { + fmt.Printf(" - no default gateway\n") + } + } + if matchedGw == nil { + if verbose { + fmt.Println(" interface have not a default routing rule.") + } + continue + } else if verbose { + fmt.Printf(" interface have a default routing rule. gateway: %s", matchedGw.String()) + } + + if verbose { + fmt.Println(" getting networks ") + } + var networks []netlink.Addr + networks, err = netlink.AddrList(link, netlink.FAMILY_V4) + if err != nil { + fmt.Println(" - cannot getting networks ") + continue + } + for _, network := range networks { + if verbose { + fmt.Printf(" network %s\n", network.String()) + } + if network.Contains(matchedGw) { + if verbose { + fmt.Printf(" - default network\n") + } + netSlice := strings.Split(network.IPNet.String(), "/") + if len(netSlice) < 2 { + continue + } + if cidrData, err := strconv.ParseUint(netSlice[len(netSlice)-1], 10, 64); err == nil { + matchedCidr = cidrData + cidrOk = true + break + } + } else if verbose { + fmt.Printf(" - no default network\n") + } + } + if cidrOk { + gw, cidr, linkname = matchedGw, matchedCidr, attrs.Name + if verbose { + fmt.Println(" ok! matched ") + } + break + } else if verbose { + fmt.Println(" abandon this interface ") + } + } + if gw == nil { + err = errors.New("default gateway or cidr not found!") + } + + return +} + +func checkInterfaceValid(bindIfName string) (err error) { + var link netlink.Link + link, err = netlink.LinkByName(bindIfName) + + if err != nil { + return + } + attrs := link.Attrs() + if (attrs.Flags & net.FlagUp) == 0 { + err = errors.New("bind interface had been deactivated") + } + return +} + +func checkNodeBasicDirectory(containerHomePath string, nodeName string, configFileName string) (nodePath string, nodeConfigPath string, err error) { + var containerHomeInfo os.FileInfo + if containerHomeInfo, err = os.Stat(containerHomePath); os.IsNotExist(err) { + err = errors.New(fmt.Sprintf("container home path %s is not exist", containerHomePath)) + return + } else if !containerHomeInfo.IsDir() { + err = errors.New(fmt.Sprintf("container home path %s is not a directory", containerHomePath)) + return + } + + nodePath = filepath.Join(containerHomePath, nodeName) + + var nodePathInfo os.FileInfo + if nodePathInfo, err = os.Stat(nodePath); os.IsNotExist(err) { + err = errors.New(fmt.Sprintf("node path %s is not exist", nodePath)) + return + } else if !nodePathInfo.IsDir() { + err = errors.New(fmt.Sprintf("node path %s is not a directory", nodePath)) + return + } else { + subDirNames := [...]string{"merge", "work", "delta"} + for _, subDirName := range subDirNames { + subPath := filepath.Join(nodePath, subDirName) + + var subPathInfo os.FileInfo + if subPathInfo, err = os.Stat(subPath); os.IsNotExist(err) { + err = errors.New(fmt.Sprintf("sub path %s is not exist", subPath)) + return + } else if !subPathInfo.IsDir() { + err = errors.New(fmt.Sprintf("sub path %s is not a directory", subPath)) + return + } + } + } + nodeConfigPath = filepath.Join(containerHomePath, nodeName, configFileName) + + var nodeConfigPathInfo os.FileInfo + if nodeConfigPathInfo, err = os.Stat(nodeConfigPath); os.IsNotExist(err) || nodeConfigPathInfo.IsDir() { + nodeConfigPath = "" + } + + return + +} + +func LoadConfig(path string) (config config, err error) { + var ( + source []byte + ) + if path == "" { + } else if source, err = ioutil.ReadFile(path); err != nil { + } else if err = yaml.Unmarshal(source, &config); err != nil { + } + return +} + +func main() { + _, err := app.Parse(os.Args[1:]) + var ( + verbose = *verbosePtr + clear = *clearPtr + nodeName = *nodeNamePtr + bindIfName = *bindIfNamePtr + configFileName = *configFileNamePtr + nspawnPath string + progress = -1 + ) + + defer func() { + if r := recover(); r != nil { + fmt.Fprintln(os.Stderr, r) + os.Exit(progress) + } + }() + if err != nil { + panic(err) + } + + progress = 1 + containerHomePath, err := filepath.Abs(*containerHomePtr) + if err != nil { + panic(err) + } + + progress++ + nodePath, nodeConfigPath, err := checkNodeBasicDirectory(containerHomePath, nodeName, configFileName) + if err != nil { + panic(err) + } else if verbose { + var nodeConfigPathDesc = "not used" + if nodeConfigPath != "" { + nodeConfigPathDesc = nodeConfigPath + } + fmt.Printf(`sane container environment: + - container home : %s + - node home : %s + - node config : %s +`, containerHomePath, nodePath, nodeConfigPathDesc) + } + config, err := LoadConfig(nodeConfigPath) + if err != nil { + panic(err) + } else if verbose { + fmt.Printf(config.String()) + } + + progress++ + + if nspawnPathPtr != nil && *nspawnPathPtr != "" { + nspawnPath = *nspawnPathPtr + } else if nspawnPathData, err := exec.LookPath("systemd-nspawn"); err != nil { + panic(errs.Wrap(err, "cannot find systemd-nspawn")) + } else { + nspawnPath = nspawnPathData + if verbose { + fmt.Printf("systemd-nspawn found : %s\n", nspawnPath) + } + } + + progress++ + err = checkInterfaceValid(bindIfName) + if err != nil { + panic(errs.Wrap(err, "error in checkInterfaceValid")) + } + progress++ + nodeAddr, err := getIP(nodeName) + + if err != nil { + panic(errs.Wrap(err, "error in getIP")) + } + + progress++ + gw, cidr, linkname, err := getOutboundIPInfo(verbose) + if err != nil { + panic(errs.Wrap(err, "error in getOutboundIPInfo")) + } + + if verbose { + fmt.Println("network configuration:") + fmt.Printf(" - bind interface{%s} : valid\n", bindIfName) + fmt.Printf(" - default routing info : ip %s/%d link %s\n", gw.String(), cidr, linkname) + fmt.Printf(" - node{%s} IP address : %s\n", nodeName, nodeAddr.String()) + } + option := []string{ + "--quiet", + "--boot", + "--link-journal=try-host", + "--notify-ready=true"} + option = append(option, fmt.Sprintf("--network-ipvlan=%s", bindIfName)) + option = append(option, fmt.Sprintf("--setenv=IP_ADDR=%s/%d", nodeAddr.String(), cidr)) + option = append(option, fmt.Sprintf("--setenv=GATEWAY_ADDR=%s", gw.String())) + option = append(option, fmt.Sprintf("--machine=%s", nodeName)) + option = append(option, fmt.Sprintf("--directory=%s", filepath.Join(nodePath, "merge"))) + + for _, mountPoint := range config.MoundPoint { + if mountOption, err := mountPoint.Option(nodePath); err != nil { + panic(errs.Wrap(err, "error in mountOption")) + } else { + option = append(option, mountOption) + } + } + var env []string + if clear { + env = []string{} + } else { + env = os.Environ() + if verbose { + fmt.Println("os environment:") + for _, segment := range env { + fmt.Println(" ", segment) + } + } + } + if verbose { + fmt.Println("systemd-nspawn spawning:") + fmt.Println(nspawnPath, " \\") + for _, segment := range option { + fmt.Println(" ", segment, " \\") + } + fmt.Println(" ;") + fmt.Println("bye!") + } + if err := syscall.Exec(nspawnPath, option, env); err != nil { + panic(errs.Wrap(err, "error in systemd-nspawn execution")) + } +} +