Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a398ce82f7 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,4 @@
|
|||||||
*.swp
|
*.swp
|
||||||
bin/
|
bin/
|
||||||
coverage/
|
coverage/
|
||||||
gopath/
|
pkg/
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
language: go
|
|
||||||
go: 1.2
|
|
||||||
|
|
||||||
install:
|
|
||||||
- go get code.google.com/p/go.tools/cmd/cover
|
|
||||||
|
|
||||||
script:
|
|
||||||
- ./test
|
|
@@ -70,44 +70,15 @@ Note that hyphens in the coreos.etcd.* keys are mapped to underscores.
|
|||||||
|
|
||||||
[etcd-config]: https://github.com/coreos/etcd/blob/master/Documentation/configuration.md
|
[etcd-config]: https://github.com/coreos/etcd/blob/master/Documentation/configuration.md
|
||||||
|
|
||||||
#### fleet
|
|
||||||
|
|
||||||
The `coreos.fleet.*` parameters work very similarly to `coreos.etcd.*`, and allow for the configuration of fleet through environment variables. For example, the following cloud-config document...
|
|
||||||
|
|
||||||
```
|
|
||||||
#cloud-config
|
|
||||||
|
|
||||||
coreos:
|
|
||||||
fleet:
|
|
||||||
public-ip: $public_ipv4
|
|
||||||
metadata: region=us-west
|
|
||||||
```
|
|
||||||
|
|
||||||
...will generate a systemd unit drop-in like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
[Service]
|
|
||||||
Environment="FLEET_PUBLIC_IP=203.0.113.29"
|
|
||||||
Environment="FLEET_METADATA=region=us-west"
|
|
||||||
```
|
|
||||||
|
|
||||||
For more information on fleet configuration, see the [fleet documentation][fleet-config].
|
|
||||||
|
|
||||||
[fleet-config]: https://github.com/coreos/fleet/blob/master/Documentation/configuration.md
|
|
||||||
|
|
||||||
#### update
|
#### update
|
||||||
|
|
||||||
The `coreos.update.*` parameters manipulate settings related to how CoreOS instances are updated.
|
The `coreos.update.*` parameters manipulate settings related to how CoreOS instances are updated.
|
||||||
|
|
||||||
These fields will be written out to and replace `/etc/coreos/update.conf`. If only one of the parameters is given it will only overwrite the given field.
|
|
||||||
|
|
||||||
- **reboot-strategy**: One of "reboot", "etcd-lock", "best-effort" or "off" for controlling when reboots are issued after an update is performed.
|
- **reboot-strategy**: One of "reboot", "etcd-lock", "best-effort" or "off" for controlling when reboots are issued after an update is performed.
|
||||||
- _reboot_: Reboot immediately after an update is applied.
|
- _reboot_: Reboot immediately after an update is applied.
|
||||||
- _etcd-lock_: Reboot after first taking a distributed lock in etcd, this guarantees that only one host will reboot concurrently and that the cluster will remain available during the update.
|
- _etcd-lock_: Reboot after first taking a distributed lock in etcd, this guarantees that only one host will reboot concurrently and that the cluster will remain available during the update.
|
||||||
- _best-effort_ - If etcd is running, "etcd-lock", otherwise simply "reboot".
|
- _best-effort_ - If etcd is running, "etcd-lock", otherwise simply "reboot".
|
||||||
- _off_ - Disable rebooting after updates are applied (not recommended).
|
- _off_ - Disable rebooting after updates are applied (not recommended).
|
||||||
- **server**: is the omaha endpoint URL which will be queried for updates.
|
|
||||||
- **group**: signifies the channel which should be used for automatic updates. This value defaults to the version of the image initially downloaded. (one of "master", "alpha", "beta", "stable")
|
|
||||||
|
|
||||||
```
|
```
|
||||||
#cloud-config
|
#cloud-config
|
||||||
@@ -121,11 +92,10 @@ coreos:
|
|||||||
The `coreos.units.*` parameters define a list of arbitrary systemd units to start. Each item is an object with the following fields:
|
The `coreos.units.*` parameters define a list of arbitrary systemd units to start. Each item is an object with the following fields:
|
||||||
|
|
||||||
- **name**: String representing unit's name. Required.
|
- **name**: String representing unit's name. Required.
|
||||||
- **runtime**: Boolean indicating whether or not to persist the unit across reboots. This is analogous to the `--runtime` argument to `systemctl enable`. Default value is false.
|
- **runtime**: Boolean indicating whether or not to persist the unit across reboots. This is analagous to the `--runtime` argument to `systemd enable`. Default value is false.
|
||||||
- **enable**: Boolean indicating whether or not to handle the [Install] section of the unit file. This is similar to running `systemctl enable <name>`. Default value is false.
|
- **enable**: Boolean indicating whether or not to handle the [Install] section of the unit file. This is similar to running `systemctl enable <name>`. Default value is false.
|
||||||
- **content**: Plaintext string representing entire unit file. If no value is provided, the unit is assumed to exist already.
|
- **content**: Plaintext string representing entire unit file. If no value is provided, the unit is assumed to exist already.
|
||||||
- **command**: Command to execute on unit: start, stop, reload, restart, try-restart, reload-or-restart, reload-or-try-restart. Default value is restart.
|
- **command**: Command to execute on unit: start, stop, reload, restart, try-restart, reload-or-restart, reload-or-try-restart. Default value is restart.
|
||||||
- **mask**: Whether to mask the unit file by symlinking it to `/dev/null` (analogous to `systemctl mask <name>`). Note that unlike `systemctl mask`, **this will destructively remove any existing unit file** located at `/etc/systemd/system/<unit>`, to ensure that the mask succeeds. Default value is false.
|
|
||||||
|
|
||||||
**NOTE:** The command field is ignored for all network, netdev, and link units. The systemd-networkd.service unit will be restarted in their place.
|
**NOTE:** The command field is ignored for all network, netdev, and link units. The systemd-networkd.service unit will be restarted in their place.
|
||||||
|
|
||||||
@@ -155,10 +125,10 @@ coreos:
|
|||||||
WantedBy=local.target
|
WantedBy=local.target
|
||||||
```
|
```
|
||||||
|
|
||||||
Start the built-in `etcd` and `fleet` services:
|
Start the builtin `etcd` and `fleet` services:
|
||||||
|
|
||||||
```
|
```
|
||||||
#cloud-config
|
# cloud-config
|
||||||
|
|
||||||
coreos:
|
coreos:
|
||||||
units:
|
units:
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
# coreos-cloudinit [](https://travis-ci.org/coreos/coreos-cloudinit)
|
# coreos-cloudinit
|
||||||
|
|
||||||
coreos-cloudinit enables a user to customize CoreOS machines by providing either a cloud-config document or an executable script through user-data.
|
coreos-cloudinit enables a user to customize CoreOS machines by providing either a cloud-config document or an executable script through user-data.
|
||||||
|
|
||||||
|
7
build
7
build
@@ -3,12 +3,7 @@
|
|||||||
ORG_PATH="github.com/coreos"
|
ORG_PATH="github.com/coreos"
|
||||||
REPO_PATH="${ORG_PATH}/coreos-cloudinit"
|
REPO_PATH="${ORG_PATH}/coreos-cloudinit"
|
||||||
|
|
||||||
if [ ! -h gopath/src/${REPO_PATH} ]; then
|
|
||||||
mkdir -p gopath/src/${ORG_PATH}
|
|
||||||
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
|
|
||||||
fi
|
|
||||||
|
|
||||||
export GOBIN=${PWD}/bin
|
export GOBIN=${PWD}/bin
|
||||||
export GOPATH=${PWD}/gopath
|
export GOPATH=${PWD}
|
||||||
|
|
||||||
go build -o bin/coreos-cloudinit ${REPO_PATH}
|
go build -o bin/coreos-cloudinit ${REPO_PATH}
|
||||||
|
@@ -1,20 +1,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/datasource"
|
"github.com/coreos/coreos-cloudinit/datasource"
|
||||||
"github.com/coreos/coreos-cloudinit/initialize"
|
"github.com/coreos/coreos-cloudinit/initialize"
|
||||||
"github.com/coreos/coreos-cloudinit/network"
|
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
const version = "0.7.4"
|
const version = "0.6.1"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var printVersion bool
|
var printVersion bool
|
||||||
@@ -26,18 +23,12 @@ func main() {
|
|||||||
var file string
|
var file string
|
||||||
flag.StringVar(&file, "from-file", "", "Read user-data from provided file")
|
flag.StringVar(&file, "from-file", "", "Read user-data from provided file")
|
||||||
|
|
||||||
var configdrive string
|
|
||||||
flag.StringVar(&configdrive, "from-configdrive", "", "Read user-data from provided cloud-drive directory")
|
|
||||||
|
|
||||||
var url string
|
var url string
|
||||||
flag.StringVar(&url, "from-url", "", "Download user-data from provided url")
|
flag.StringVar(&url, "from-url", "", "Download user-data from provided url")
|
||||||
|
|
||||||
var useProcCmdline bool
|
var useProcCmdline bool
|
||||||
flag.BoolVar(&useProcCmdline, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", datasource.ProcCmdlineLocation, datasource.ProcCmdlineCloudConfigFlag))
|
flag.BoolVar(&useProcCmdline, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", datasource.ProcCmdlineLocation, datasource.ProcCmdlineCloudConfigFlag))
|
||||||
|
|
||||||
var convertNetconf string
|
|
||||||
flag.StringVar(&convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files (requires the -from-configdrive flag)")
|
|
||||||
|
|
||||||
var workspace string
|
var workspace string
|
||||||
flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
|
flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
|
||||||
|
|
||||||
@@ -56,32 +47,17 @@ func main() {
|
|||||||
ds = datasource.NewLocalFile(file)
|
ds = datasource.NewLocalFile(file)
|
||||||
} else if url != "" {
|
} else if url != "" {
|
||||||
ds = datasource.NewMetadataService(url)
|
ds = datasource.NewMetadataService(url)
|
||||||
} else if configdrive != "" {
|
|
||||||
ds = datasource.NewConfigDrive(configdrive)
|
|
||||||
} else if useProcCmdline {
|
} else if useProcCmdline {
|
||||||
ds = datasource.NewProcCmdline()
|
ds = datasource.NewProcCmdline()
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Provide one of --from-file, --from-configdrive, --from-url or --from-proc-cmdline")
|
fmt.Println("Provide one of --from-file, --from-url or --from-proc-cmdline")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if convertNetconf != "" && configdrive == "" {
|
log.Printf("Fetching user-data from datasource of type %q", ds.Type())
|
||||||
fmt.Println("-convert-netconf flag requires -from-configdrive")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch convertNetconf {
|
|
||||||
case "":
|
|
||||||
case "debian":
|
|
||||||
default:
|
|
||||||
fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian'\n", convertNetconf)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Fetching user-data from datasource of type %q\n", ds.Type())
|
|
||||||
userdataBytes, err := ds.Fetch()
|
userdataBytes, err := ds.Fetch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed fetching user-data from datasource: %v\n", err)
|
log.Printf("Failed fetching user-data from datasource: %v", err)
|
||||||
if ignoreFailure {
|
if ignoreFailure {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
} else {
|
} else {
|
||||||
@@ -89,41 +65,29 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(userdataBytes) == 0 {
|
||||||
|
log.Printf("No user data to handle, exiting.")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
env := initialize.NewEnvironment("/", workspace)
|
env := initialize.NewEnvironment("/", workspace)
|
||||||
if len(userdataBytes) > 0 {
|
|
||||||
if err := processUserdata(string(userdataBytes), env); err != nil {
|
|
||||||
fmt.Printf("Failed resolving user-data: %v\n", err)
|
|
||||||
if !ignoreFailure {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Println("No user data to handle.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if convertNetconf != "" {
|
userdata := string(userdataBytes)
|
||||||
if err := processNetconf(convertNetconf, configdrive); err != nil {
|
|
||||||
fmt.Printf("Failed to process network config: %v\n", err)
|
|
||||||
if !ignoreFailure {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func processUserdata(userdata string, env *initialize.Environment) error {
|
|
||||||
userdata = env.Apply(userdata)
|
userdata = env.Apply(userdata)
|
||||||
|
|
||||||
parsed, err := initialize.ParseUserData(userdata)
|
parsed, err := initialize.ParseUserData(userdata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed parsing user-data: %v\n", err)
|
log.Printf("Failed parsing user-data: %v", err)
|
||||||
return err
|
if ignoreFailure {
|
||||||
|
os.Exit(0)
|
||||||
|
} else {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = initialize.PrepWorkspace(env.Workspace())
|
err = initialize.PrepWorkspace(env.Workspace())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed preparing workspace: %v\n", err)
|
log.Fatalf("Failed preparing workspace: %v", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := parsed.(type) {
|
switch t := parsed.(type) {
|
||||||
@@ -135,54 +99,11 @@ func processUserdata(userdata string, env *initialize.Environment) error {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
var name string
|
var name string
|
||||||
name, err = system.ExecuteScript(path)
|
name, err = system.ExecuteScript(path)
|
||||||
initialize.PersistUnitNameInWorkspace(name, env.Workspace())
|
initialize.PersistUnitNameInWorkspace(name, workspace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
if err != nil {
|
||||||
}
|
log.Fatalf("Failed resolving user-data: %v", err)
|
||||||
|
}
|
||||||
func processNetconf(convertNetconf, configdrive string) error {
|
|
||||||
openstackRoot := path.Join(configdrive, "openstack")
|
|
||||||
metadataFilename := path.Join(openstackRoot, "latest", "meta_data.json")
|
|
||||||
metadataBytes, err := ioutil.ReadFile(metadataFilename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var metadata struct {
|
|
||||||
NetworkConfig struct {
|
|
||||||
ContentPath string `json:"content_path"`
|
|
||||||
} `json:"network_config"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
configPath := metadata.NetworkConfig.ContentPath
|
|
||||||
if configPath == "" {
|
|
||||||
fmt.Printf("No network config specified in %q.\n", metadataFilename)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
netconfBytes, err := ioutil.ReadFile(path.Join(openstackRoot, configPath))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var interfaces []network.InterfaceGenerator
|
|
||||||
switch convertNetconf {
|
|
||||||
case "debian":
|
|
||||||
interfaces, err = network.ProcessDebianNetconf(string(netconfBytes))
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Unsupported network config format %q", convertNetconf)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := system.WriteNetworkdConfigs(interfaces); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return system.RestartNetwork(interfaces)
|
|
||||||
}
|
}
|
||||||
|
@@ -1,27 +0,0 @@
|
|||||||
package datasource
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
type configDrive struct {
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfigDrive(path string) *configDrive {
|
|
||||||
return &configDrive{path}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *configDrive) Fetch() ([]byte, error) {
|
|
||||||
data, err := ioutil.ReadFile(path.Join(self.path, "openstack", "latest", "user_data"))
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *configDrive) Type() string {
|
|
||||||
return "cloud-drive"
|
|
||||||
}
|
|
@@ -1,6 +1,31 @@
|
|||||||
package datasource
|
package datasource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
type Datasource interface {
|
type Datasource interface {
|
||||||
Fetch() ([]byte, error)
|
Fetch() ([]byte, error)
|
||||||
Type() string
|
Type() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchURL(url string) ([]byte, error) {
|
||||||
|
client := http.Client{}
|
||||||
|
resp, err := client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode / 100 != 2 {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
package datasource
|
package datasource
|
||||||
|
|
||||||
import "github.com/coreos/coreos-cloudinit/pkg"
|
|
||||||
|
|
||||||
type metadataService struct {
|
type metadataService struct {
|
||||||
url string
|
url string
|
||||||
}
|
}
|
||||||
@@ -11,8 +9,7 @@ func NewMetadataService(url string) *metadataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ms *metadataService) Fetch() ([]byte, error) {
|
func (ms *metadataService) Fetch() ([]byte, error) {
|
||||||
client := pkg.NewHttpClient()
|
return fetchURL(ms.url)
|
||||||
return client.Get(ms.url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *metadataService) Type() string {
|
func (ms *metadataService) Type() string {
|
||||||
|
@@ -5,8 +5,6 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/pkg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -14,28 +12,24 @@ const (
|
|||||||
ProcCmdlineCloudConfigFlag = "cloud-config-url"
|
ProcCmdlineCloudConfigFlag = "cloud-config-url"
|
||||||
)
|
)
|
||||||
|
|
||||||
type procCmdline struct{
|
type procCmdline struct{}
|
||||||
Location string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProcCmdline() *procCmdline {
|
func NewProcCmdline() *procCmdline {
|
||||||
return &procCmdline{Location: ProcCmdlineLocation}
|
return &procCmdline{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *procCmdline) Fetch() ([]byte, error) {
|
func (self *procCmdline) Fetch() ([]byte, error) {
|
||||||
contents, err := ioutil.ReadFile(self.Location)
|
cmdline, err := ioutil.ReadFile(ProcCmdlineLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdline := strings.TrimSpace(string(contents))
|
url, err := findCloudConfigURL(string(cmdline))
|
||||||
url, err := findCloudConfigURL(cmdline)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client := pkg.NewHttpClient()
|
cfg, err := fetchURL(url)
|
||||||
cfg, err := client.Get(url)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,6 @@
|
|||||||
package datasource
|
package datasource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,39 +45,3 @@ func TestParseCmdlineCloudConfigFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcCmdlineAndFetchConfig(t *testing.T) {
|
|
||||||
|
|
||||||
var (
|
|
||||||
ProcCmdlineTmpl = "foo=bar cloud-config-url=%s/config\n"
|
|
||||||
CloudConfigContent = "#cloud-config\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method == "GET" && r.RequestURI == "/config" {
|
|
||||||
fmt.Fprint(w, CloudConfigContent)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
file, err := ioutil.TempFile(os.TempDir(), "test_proc_cmdline")
|
|
||||||
defer os.Remove(file.Name())
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Test produced error: %v", err)
|
|
||||||
}
|
|
||||||
_, err = file.Write([]byte(fmt.Sprintf(ProcCmdlineTmpl, ts.URL)))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Test produced error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
p := NewProcCmdline()
|
|
||||||
p.Location = file.Name()
|
|
||||||
cfg, err := p.Fetch()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Test produced error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(cfg) != CloudConfigContent {
|
|
||||||
t.Errorf("Test failed, response body: %s != %s", cfg, CloudConfigContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package initialize
|
package initialize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"path"
|
"path"
|
||||||
@@ -11,122 +10,24 @@ import (
|
|||||||
"github.com/coreos/coreos-cloudinit/system"
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CloudConfigFile represents a CoreOS specific configuration option that can generate
|
|
||||||
// an associated system.File to be written to disk
|
|
||||||
type CloudConfigFile interface {
|
|
||||||
// File should either return (*system.File, error), or (nil, nil) if nothing
|
|
||||||
// needs to be done for this configuration option.
|
|
||||||
File(root string) (*system.File, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloudConfigUnit represents a CoreOS specific configuration option that can generate
|
|
||||||
// an associated system.Unit to be created/enabled appropriately
|
|
||||||
type CloudConfigUnit interface {
|
|
||||||
// Unit should either return (*system.Unit, error), or (nil, nil) if nothing
|
|
||||||
// needs to be done for this configuration option.
|
|
||||||
Unit(root string) (*system.Unit, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloudConfig encapsulates the entire cloud-config configuration file and maps directly to YAML
|
|
||||||
type CloudConfig struct {
|
type CloudConfig struct {
|
||||||
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
||||||
Coreos struct {
|
Coreos struct {
|
||||||
Etcd EtcdEnvironment
|
Etcd EtcdEnvironment
|
||||||
Fleet FleetEnvironment
|
Update map[string]string
|
||||||
OEM OEMRelease
|
|
||||||
Update UpdateConfig
|
|
||||||
Units []system.Unit
|
Units []system.Unit
|
||||||
|
OEM OEMRelease
|
||||||
}
|
}
|
||||||
WriteFiles []system.File `yaml:"write_files"`
|
WriteFiles []system.File `yaml:"write_files"`
|
||||||
Hostname string
|
Hostname string
|
||||||
Users []system.User
|
Users []system.User
|
||||||
ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"`
|
ManageEtcHosts string `yaml:"manage_etc_hosts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type warner func(format string, v ...interface{})
|
|
||||||
|
|
||||||
// warnOnUnrecognizedKeys parses the contents of a cloud-config file and calls
|
|
||||||
// warn(msg, key) for every unrecognized key (i.e. those not present in CloudConfig)
|
|
||||||
func warnOnUnrecognizedKeys(contents string, warn warner) {
|
|
||||||
// Generate a map of all understood cloud config options
|
|
||||||
var cc map[string]interface{}
|
|
||||||
b, _ := goyaml.Marshal(&CloudConfig{})
|
|
||||||
goyaml.Unmarshal(b, &cc)
|
|
||||||
|
|
||||||
// Now unmarshal the entire provided contents
|
|
||||||
var c map[string]interface{}
|
|
||||||
goyaml.Unmarshal([]byte(contents), &c)
|
|
||||||
|
|
||||||
// Check that every key in the contents exists in the cloud config
|
|
||||||
for k, _ := range c {
|
|
||||||
if _, ok := cc[k]; !ok {
|
|
||||||
warn("Warning: unrecognized key %q in provided cloud config - ignoring section", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for unrecognized coreos options, if any are set
|
|
||||||
coreos, ok := c["coreos"]
|
|
||||||
if ok {
|
|
||||||
set := coreos.(map[interface{}]interface{})
|
|
||||||
known := cc["coreos"].(map[interface{}]interface{})
|
|
||||||
for k, _ := range set {
|
|
||||||
key := k.(string)
|
|
||||||
if _, ok := known[key]; !ok {
|
|
||||||
warn("Warning: unrecognized key %q in coreos section of provided cloud config - ignoring", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for any badly-specified users, if any are set
|
|
||||||
users, ok := c["users"]
|
|
||||||
if ok {
|
|
||||||
var known map[string]interface{}
|
|
||||||
b, _ := goyaml.Marshal(&system.User{})
|
|
||||||
goyaml.Unmarshal(b, &known)
|
|
||||||
|
|
||||||
set := users.([]interface{})
|
|
||||||
for _, u := range set {
|
|
||||||
user := u.(map[interface{}]interface{})
|
|
||||||
for k, _ := range user {
|
|
||||||
key := k.(string)
|
|
||||||
if _, ok := known[key]; !ok {
|
|
||||||
warn("Warning: unrecognized key %q in user section of cloud config - ignoring", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for any badly-specified files, if any are set
|
|
||||||
files, ok := c["write_files"]
|
|
||||||
if ok {
|
|
||||||
var known map[string]interface{}
|
|
||||||
b, _ := goyaml.Marshal(&system.File{})
|
|
||||||
goyaml.Unmarshal(b, &known)
|
|
||||||
|
|
||||||
set := files.([]interface{})
|
|
||||||
for _, f := range set {
|
|
||||||
file := f.(map[interface{}]interface{})
|
|
||||||
for k, _ := range file {
|
|
||||||
key := k.(string)
|
|
||||||
if _, ok := known[key]; !ok {
|
|
||||||
warn("Warning: unrecognized key %q in file section of cloud config - ignoring", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCloudConfig instantiates a new CloudConfig from the given contents (a
|
|
||||||
// string of YAML), returning any error encountered. It will ignore unknown
|
|
||||||
// fields but log encountering them.
|
|
||||||
func NewCloudConfig(contents string) (*CloudConfig, error) {
|
func NewCloudConfig(contents string) (*CloudConfig, error) {
|
||||||
var cfg CloudConfig
|
var cfg CloudConfig
|
||||||
err := goyaml.Unmarshal([]byte(contents), &cfg)
|
err := goyaml.Unmarshal([]byte(contents), &cfg)
|
||||||
if err != nil {
|
return &cfg, err
|
||||||
return &cfg, err
|
|
||||||
}
|
|
||||||
warnOnUnrecognizedKeys(contents, log.Printf)
|
|
||||||
return &cfg, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc CloudConfig) String() string {
|
func (cc CloudConfig) String() string {
|
||||||
@@ -141,9 +42,6 @@ func (cc CloudConfig) String() string {
|
|||||||
return stringified
|
return stringified
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply renders a CloudConfig to an Environment. This can involve things like
|
|
||||||
// configuring the hostname, adding new users, writing various configuration
|
|
||||||
// files to disk, and manipulating systemd services.
|
|
||||||
func Apply(cfg CloudConfig, env *Environment) error {
|
func Apply(cfg CloudConfig, env *Environment) error {
|
||||||
if cfg.Hostname != "" {
|
if cfg.Hostname != "" {
|
||||||
if err := system.SetHostname(cfg.Hostname); err != nil {
|
if err := system.SetHostname(cfg.Hostname); err != nil {
|
||||||
@@ -152,45 +50,54 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
log.Printf("Set hostname to %s", cfg.Hostname)
|
log.Printf("Set hostname to %s", cfg.Hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, user := range cfg.Users {
|
if cfg.Coreos.OEM.ID != "" {
|
||||||
if user.Name == "" {
|
if err := WriteOEMRelease(&cfg.Coreos.OEM, env.Root()); err != nil {
|
||||||
log.Printf("User object has no 'name' field, skipping")
|
return err
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
log.Printf("Wrote /etc/oem-release to filesystem")
|
||||||
|
}
|
||||||
|
|
||||||
if system.UserExists(&user) {
|
if len(cfg.Users) > 0 {
|
||||||
log.Printf("User '%s' exists, ignoring creation-time fields", user.Name)
|
for _, user := range cfg.Users {
|
||||||
if user.PasswordHash != "" {
|
if user.Name == "" {
|
||||||
log.Printf("Setting '%s' user's password", user.Name)
|
log.Printf("User object has no 'name' field, skipping")
|
||||||
if err := system.SetUserPassword(user.Name, user.PasswordHash); err != nil {
|
continue
|
||||||
log.Printf("Failed setting '%s' user's password: %v", user.Name, err)
|
}
|
||||||
|
|
||||||
|
if system.UserExists(&user) {
|
||||||
|
log.Printf("User '%s' exists, ignoring creation-time fields", user.Name)
|
||||||
|
if user.PasswordHash != "" {
|
||||||
|
log.Printf("Setting '%s' user's password", user.Name)
|
||||||
|
if err := system.SetUserPassword(user.Name, user.PasswordHash); err != nil {
|
||||||
|
log.Printf("Failed setting '%s' user's password: %v", user.Name, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("Creating user '%s'", user.Name)
|
||||||
|
if err := system.CreateUser(&user); err != nil {
|
||||||
|
log.Printf("Failed creating user '%s': %v", user.Name, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.Printf("Creating user '%s'", user.Name)
|
|
||||||
if err := system.CreateUser(&user); err != nil {
|
|
||||||
log.Printf("Failed creating user '%s': %v", user.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(user.SSHAuthorizedKeys) > 0 {
|
if len(user.SSHAuthorizedKeys) > 0 {
|
||||||
log.Printf("Authorizing %d SSH keys for user '%s'", len(user.SSHAuthorizedKeys), user.Name)
|
log.Printf("Authorizing %d SSH keys for user '%s'", len(user.SSHAuthorizedKeys), user.Name)
|
||||||
if err := system.AuthorizeSSHKeys(user.Name, env.SSHKeyName(), user.SSHAuthorizedKeys); err != nil {
|
if err := system.AuthorizeSSHKeys(user.Name, env.SSHKeyName(), user.SSHAuthorizedKeys); err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if user.SSHImportGithubUser != "" {
|
||||||
if user.SSHImportGithubUser != "" {
|
log.Printf("Authorizing github user %s SSH keys for CoreOS user '%s'", user.SSHImportGithubUser, user.Name)
|
||||||
log.Printf("Authorizing github user %s SSH keys for CoreOS user '%s'", user.SSHImportGithubUser, user.Name)
|
if err := SSHImportGithubUser(user.Name, user.SSHImportGithubUser); err != nil {
|
||||||
if err := SSHImportGithubUser(user.Name, user.SSHImportGithubUser); err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
}
|
||||||
}
|
if user.SSHImportURL != "" {
|
||||||
if user.SSHImportURL != "" {
|
log.Printf("Authorizing SSH keys for CoreOS user '%s' from '%s'", user.Name, user.SSHImportURL)
|
||||||
log.Printf("Authorizing SSH keys for CoreOS user '%s' from '%s'", user.Name, user.SSHImportURL)
|
if err := SSHImportKeysFromURL(user.Name, user.SSHImportURL); err != nil {
|
||||||
if err := SSHImportKeysFromURL(user.Name, user.SSHImportURL); err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,86 +111,86 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ccf := range []CloudConfigFile{cfg.Coreos.OEM, cfg.Coreos.Update, cfg.ManageEtcHosts} {
|
if len(cfg.WriteFiles) > 0 {
|
||||||
f, err := ccf.File(env.Root())
|
for _, file := range cfg.WriteFiles {
|
||||||
if err != nil {
|
file.Path = path.Join(env.Root(), file.Path)
|
||||||
return err
|
if err := system.WriteFile(&file); err != nil {
|
||||||
}
|
|
||||||
if f != nil {
|
|
||||||
cfg.WriteFiles = append(cfg.WriteFiles, *f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ccu := range []CloudConfigUnit{cfg.Coreos.Etcd, cfg.Coreos.Fleet, cfg.Coreos.Update} {
|
|
||||||
u, err := ccu.Unit(env.Root())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if u != nil {
|
|
||||||
cfg.Coreos.Units = append(cfg.Coreos.Units, *u)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range cfg.WriteFiles {
|
|
||||||
file.Path = path.Join(env.Root(), file.Path)
|
|
||||||
if err := system.WriteFile(&file); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("Wrote file %s to filesystem", file.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
commands := make(map[string]string, 0)
|
|
||||||
reload := false
|
|
||||||
for _, unit := range cfg.Coreos.Units {
|
|
||||||
dst := system.UnitDestination(&unit, env.Root())
|
|
||||||
if unit.Content != "" {
|
|
||||||
log.Printf("Writing unit %s to filesystem at path %s", unit.Name, dst)
|
|
||||||
if err := system.PlaceUnit(&unit, dst); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("Placed unit %s at %s", unit.Name, dst)
|
log.Printf("Wrote file %s to filesystem", file.Path)
|
||||||
reload = true
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Coreos.Etcd) > 0 {
|
||||||
|
if err := WriteEtcdEnvironment(cfg.Coreos.Etcd, env.Root()); err != nil {
|
||||||
|
log.Fatalf("Failed to write etcd config to filesystem: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if unit.Mask {
|
log.Printf("Wrote etcd config file to filesystem")
|
||||||
log.Printf("Masking unit file %s", unit.Name)
|
}
|
||||||
if err := system.MaskUnit(unit.Name, env.Root()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if unit.Enable {
|
if s, ok := cfg.Coreos.Update["reboot-strategy"]; ok {
|
||||||
if unit.Group() != "network" {
|
if err := WriteLocksmithConfig(s, env.Root()); err != nil {
|
||||||
log.Printf("Enabling unit file %s", unit.Name)
|
log.Fatalf("Failed to write locksmith config to filesystem: %v", err)
|
||||||
if err := system.EnableUnitFile(unit.Name, unit.Runtime); err != nil {
|
}
|
||||||
|
log.Printf("Wrote locksmith config file to filesystem")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Coreos.Units) > 0 {
|
||||||
|
commands := make(map[string]string, 0)
|
||||||
|
for _, unit := range cfg.Coreos.Units {
|
||||||
|
dst := system.UnitDestination(&unit, env.Root())
|
||||||
|
if unit.Content != "" {
|
||||||
|
log.Printf("Writing unit %s to filesystem at path %s", unit.Name, dst)
|
||||||
|
if err := system.PlaceUnit(&unit, dst); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("Enabled unit %s", unit.Name)
|
log.Printf("Placed unit %s at %s", unit.Name, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
if unit.Enable {
|
||||||
|
if unit.Group() != "network" {
|
||||||
|
log.Printf("Enabling unit file %s", dst)
|
||||||
|
if err := system.EnableUnitFile(dst, unit.Runtime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("Enabled unit %s", unit.Name)
|
||||||
|
} else {
|
||||||
|
log.Printf("Skipping enable for network-like unit %s", unit.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if unit.Group() == "network" {
|
||||||
|
commands["systemd-networkd.service"] = "restart"
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Skipping enable for network-like unit %s", unit.Name)
|
if unit.Command != "" {
|
||||||
|
commands[unit.Name] = unit.Command
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if unit.Group() == "network" {
|
|
||||||
commands["systemd-networkd.service"] = "restart"
|
|
||||||
} else if unit.Command != "" {
|
|
||||||
commands[unit.Name] = unit.Command
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if reload {
|
|
||||||
if err := system.DaemonReload(); err != nil {
|
if err := system.DaemonReload(); err != nil {
|
||||||
return errors.New(fmt.Sprintf("failed systemd daemon-reload: %v", err))
|
log.Fatalf("Failed systemd daemon-reload: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for unit, command := range commands {
|
||||||
|
log.Printf("Calling unit command '%s %s'", command, unit)
|
||||||
|
res, err := system.RunUnitCommand(command, unit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("Result of '%s %s': %s", command, unit, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for unit, command := range commands {
|
if cfg.ManageEtcHosts != "" {
|
||||||
log.Printf("Calling unit command '%s %s'", command, unit)
|
|
||||||
res, err := system.RunUnitCommand(command, unit)
|
if err := WriteEtcHosts(cfg.ManageEtcHosts, env.Root()); err != nil {
|
||||||
if err != nil {
|
log.Fatalf("Failed to write /etc/hosts to filesystem: %v", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
log.Printf("Result of '%s %s': %s", command, unit, res)
|
|
||||||
|
log.Printf("Wrote /etc/hosts file to filesystem")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@@ -1,75 +1,10 @@
|
|||||||
package initialize
|
package initialize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCloudConfigUnknownKeys(t *testing.T) {
|
|
||||||
contents := `
|
|
||||||
coreos:
|
|
||||||
etcd:
|
|
||||||
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
|
|
||||||
coreos_unknown:
|
|
||||||
foo: "bar"
|
|
||||||
section_unknown:
|
|
||||||
dunno:
|
|
||||||
something
|
|
||||||
bare_unknown:
|
|
||||||
bar
|
|
||||||
write_files:
|
|
||||||
- content: fun
|
|
||||||
path: /var/party
|
|
||||||
file_unknown: nofun
|
|
||||||
users:
|
|
||||||
- name: fry
|
|
||||||
passwd: somehash
|
|
||||||
user_unknown: philip
|
|
||||||
hostname:
|
|
||||||
foo
|
|
||||||
`
|
|
||||||
cfg, err := NewCloudConfig(contents)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error instantiating CloudConfig with unknown keys: %v", err)
|
|
||||||
}
|
|
||||||
if cfg.Hostname != "foo" {
|
|
||||||
t.Fatalf("hostname not correctly set when invalid keys are present")
|
|
||||||
}
|
|
||||||
if len(cfg.Coreos.Etcd) < 1 {
|
|
||||||
t.Fatalf("etcd section not correctly set when invalid keys are present")
|
|
||||||
}
|
|
||||||
if len(cfg.WriteFiles) < 1 || cfg.WriteFiles[0].Content != "fun" || cfg.WriteFiles[0].Path != "/var/party" {
|
|
||||||
t.Fatalf("write_files section not correctly set when invalid keys are present")
|
|
||||||
}
|
|
||||||
if len(cfg.Users) < 1 || cfg.Users[0].Name != "fry" || cfg.Users[0].PasswordHash != "somehash" {
|
|
||||||
t.Fatalf("users section not correctly set when invalid keys are present")
|
|
||||||
}
|
|
||||||
|
|
||||||
var warnings string
|
|
||||||
catchWarn := func(f string, v ...interface{}) {
|
|
||||||
warnings += fmt.Sprintf(f, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
warnOnUnrecognizedKeys(contents, catchWarn)
|
|
||||||
|
|
||||||
if !strings.Contains(warnings, "coreos_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized coreos option coreos_unknown")
|
|
||||||
}
|
|
||||||
if !strings.Contains(warnings, "bare_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized key bare_unknown")
|
|
||||||
}
|
|
||||||
if !strings.Contains(warnings, "section_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized key section_unknown")
|
|
||||||
}
|
|
||||||
if !strings.Contains(warnings, "user_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized user key user_unknown")
|
|
||||||
}
|
|
||||||
if !strings.Contains(warnings, "file_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized file key file_unknown")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that the parsing of a cloud config file "generally works"
|
// Assert that the parsing of a cloud config file "generally works"
|
||||||
func TestCloudConfigEmpty(t *testing.T) {
|
func TestCloudConfigEmpty(t *testing.T) {
|
||||||
cfg, err := NewCloudConfig("")
|
cfg, err := NewCloudConfig("")
|
||||||
@@ -209,7 +144,7 @@ ssh_authorized_keys:
|
|||||||
`
|
`
|
||||||
cfg, err := NewCloudConfig(contents)
|
cfg, err := NewCloudConfig(contents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Encountered unexpected error: %v", err)
|
t.Fatalf("Encountered unexpected error :%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
keys := cfg.SSHAuthorizedKeys
|
keys := cfg.SSHAuthorizedKeys
|
||||||
@@ -227,26 +162,6 @@ func TestCloudConfigSerializationHeader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestDropInIgnored asserts that users are unable to set DropIn=True on units
|
|
||||||
func TestDropInIgnored(t *testing.T) {
|
|
||||||
contents := `
|
|
||||||
coreos:
|
|
||||||
units:
|
|
||||||
- name: test
|
|
||||||
dropin: true
|
|
||||||
`
|
|
||||||
cfg, err := NewCloudConfig(contents)
|
|
||||||
if err != nil || len(cfg.Coreos.Units) != 1 {
|
|
||||||
t.Fatalf("Encountered unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if len(cfg.Coreos.Units) != 1 || cfg.Coreos.Units[0].Name != "test" {
|
|
||||||
t.Fatalf("Expected 1 unit, but got %d: %v", len(cfg.Coreos.Units), cfg.Coreos.Units)
|
|
||||||
}
|
|
||||||
if cfg.Coreos.Units[0].DropIn {
|
|
||||||
t.Errorf("dropin option on unit in cloud-config was not ignored!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCloudConfigUsers(t *testing.T) {
|
func TestCloudConfigUsers(t *testing.T) {
|
||||||
contents := `
|
contents := `
|
||||||
users:
|
users:
|
||||||
|
@@ -45,16 +45,3 @@ func (self *Environment) Apply(data string) string {
|
|||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalizeSvcEnv standardizes the keys of the map (environment variables for a service)
|
|
||||||
// by replacing any dashes with underscores and ensuring they are entirely upper case.
|
|
||||||
// For example, "some-env" --> "SOME_ENV"
|
|
||||||
func normalizeSvcEnv(m map[string]string) map[string]string {
|
|
||||||
out := make(map[string]string, len(m))
|
|
||||||
for key, val := range m {
|
|
||||||
key = strings.ToUpper(key)
|
|
||||||
key = strings.Replace(key, "-", "_", -1)
|
|
||||||
out[key] = val
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
@@ -3,14 +3,26 @@ package initialize
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EtcdEnvironment map[string]string
|
type EtcdEnvironment map[string]string
|
||||||
|
|
||||||
func (ee EtcdEnvironment) String() (out string) {
|
func (ec EtcdEnvironment) normalized() map[string]string {
|
||||||
norm := normalizeSvcEnv(ee)
|
out := make(map[string]string, len(ec))
|
||||||
|
for key, val := range ec {
|
||||||
|
key = strings.ToUpper(key)
|
||||||
|
key = strings.Replace(key, "-", "_", -1)
|
||||||
|
out[key] = val
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec EtcdEnvironment) String() (out string) {
|
||||||
|
norm := ec.normalized()
|
||||||
|
|
||||||
if val, ok := norm["DISCOVERY_URL"]; ok {
|
if val, ok := norm["DISCOVERY_URL"]; ok {
|
||||||
delete(norm, "DISCOVERY_URL")
|
delete(norm, "DISCOVERY_URL")
|
||||||
@@ -28,27 +40,23 @@ func (ee EtcdEnvironment) String() (out string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unit creates a Unit file drop-in for etcd, using any configured
|
// Write an EtcdEnvironment to the appropriate path on disk for etcd.service
|
||||||
// options and adding a default MachineID if unset.
|
func WriteEtcdEnvironment(env EtcdEnvironment, root string) error {
|
||||||
func (ee EtcdEnvironment) Unit(root string) (*system.Unit, error) {
|
if _, ok := env["name"]; !ok {
|
||||||
if ee == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := ee["name"]; !ok {
|
|
||||||
if machineID := system.MachineID(root); machineID != "" {
|
if machineID := system.MachineID(root); machineID != "" {
|
||||||
ee["name"] = machineID
|
env["name"] = machineID
|
||||||
} else if hostname, err := system.Hostname(); err == nil {
|
} else if hostname, err := system.Hostname(); err == nil {
|
||||||
ee["name"] = hostname
|
env["name"] = hostname
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("Unable to determine default etcd name")
|
return errors.New("Unable to determine default etcd name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &system.Unit{
|
file := system.File{
|
||||||
Name: "etcd.service",
|
Path: path.Join(root, "run", "systemd", "system", "etcd.service.d", "20-cloudinit.conf"),
|
||||||
Runtime: true,
|
RawFilePermissions: "0644",
|
||||||
DropIn: true,
|
Content: env.String(),
|
||||||
Content: ee.String(),
|
}
|
||||||
}, nil
|
|
||||||
|
return system.WriteFile(&file)
|
||||||
}
|
}
|
||||||
|
@@ -3,10 +3,9 @@ package initialize
|
|||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEtcdEnvironment(t *testing.T) {
|
func TestEtcdEnvironment(t *testing.T) {
|
||||||
@@ -70,18 +69,8 @@ func TestEtcdEnvironmentWrittenToDisk(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
u, err := ec.Unit(dir)
|
if err := WriteEtcdEnvironment(ec, dir); err != nil {
|
||||||
if err != nil {
|
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)
|
||||||
t.Fatalf("Generating etcd unit failed: %v", err)
|
|
||||||
}
|
|
||||||
if u == nil {
|
|
||||||
t.Fatalf("Returned nil etcd unit unexpectedly")
|
|
||||||
}
|
|
||||||
|
|
||||||
dst := system.UnitDestination(u, dir)
|
|
||||||
os.Stderr.WriteString("writing to " + dir + "\n")
|
|
||||||
if err := system.PlaceUnit(u, dst); err != nil {
|
|
||||||
t.Fatalf("Writing of EtcdEnvironment failed: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := path.Join(dir, "run", "systemd", "system", "etcd.service.d", "20-cloudinit.conf")
|
fullPath := path.Join(dir, "run", "systemd", "system", "etcd.service.d", "20-cloudinit.conf")
|
||||||
@@ -111,7 +100,7 @@ Environment="ETCD_PEER_BIND_ADDR=127.0.0.1:7002"
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEtcdEnvironmentWrittenToDiskDefaultToMachineID(t *testing.T) {
|
func TestEtcdEnvironmentWrittenToDiskDefaultToMachineID(t *testing.T) {
|
||||||
ee := EtcdEnvironment{}
|
ec := EtcdEnvironment{}
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
t.Fatalf("Unable to create tempdir: %v", err)
|
||||||
@@ -124,18 +113,8 @@ func TestEtcdEnvironmentWrittenToDiskDefaultToMachineID(t *testing.T) {
|
|||||||
t.Fatalf("Failed writing out /etc/machine-id: %v", err)
|
t.Fatalf("Failed writing out /etc/machine-id: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := ee.Unit(dir)
|
if err := WriteEtcdEnvironment(ec, dir); err != nil {
|
||||||
if err != nil {
|
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)
|
||||||
t.Fatalf("Generating etcd unit failed: %v", err)
|
|
||||||
}
|
|
||||||
if u == nil {
|
|
||||||
t.Fatalf("Returned nil etcd unit unexpectedly")
|
|
||||||
}
|
|
||||||
|
|
||||||
dst := system.UnitDestination(u, dir)
|
|
||||||
os.Stderr.WriteString("writing to " + dir + "\n")
|
|
||||||
if err := system.PlaceUnit(u, dst); err != nil {
|
|
||||||
t.Fatalf("Writing of EtcdEnvironment failed: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := path.Join(dir, "run", "systemd", "system", "etcd.service.d", "20-cloudinit.conf")
|
fullPath := path.Join(dir, "run", "systemd", "system", "etcd.service.d", "20-cloudinit.conf")
|
||||||
@@ -153,14 +132,7 @@ Environment="ETCD_NAME=node007"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEtcdEnvironmentWhenNil(t *testing.T) {
|
func rmdir(path string) error {
|
||||||
// EtcdEnvironment will be a nil map if it wasn't in the yaml
|
cmd := exec.Command("rm", "-rf", path)
|
||||||
var ee EtcdEnvironment
|
return cmd.Run()
|
||||||
if ee != nil {
|
|
||||||
t.Fatalf("EtcdEnvironment is not nil")
|
|
||||||
}
|
|
||||||
u, err := ee.Unit("")
|
|
||||||
if u != nil || err != nil {
|
|
||||||
t.Fatalf("Unit returned a non-nil value for nil input")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,34 +0,0 @@
|
|||||||
package initialize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FleetEnvironment map[string]string
|
|
||||||
|
|
||||||
func (fe FleetEnvironment) String() (out string) {
|
|
||||||
norm := normalizeSvcEnv(fe)
|
|
||||||
out += "[Service]\n"
|
|
||||||
|
|
||||||
for key, val := range norm {
|
|
||||||
out += fmt.Sprintf("Environment=\"FLEET_%s=%s\"\n", key, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unit generates a Unit file drop-in for fleet, if any fleet options were
|
|
||||||
// configured in cloud-config
|
|
||||||
func (fe FleetEnvironment) Unit(root string) (*system.Unit, error) {
|
|
||||||
if len(fe) < 1 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return &system.Unit{
|
|
||||||
Name: "fleet.service",
|
|
||||||
Runtime: true,
|
|
||||||
DropIn: true,
|
|
||||||
Content: fe.String(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
@@ -1,42 +0,0 @@
|
|||||||
package initialize
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestFleetEnvironment(t *testing.T) {
|
|
||||||
cfg := make(FleetEnvironment, 0)
|
|
||||||
cfg["public-ip"] = "12.34.56.78"
|
|
||||||
|
|
||||||
env := cfg.String()
|
|
||||||
|
|
||||||
expect := `[Service]
|
|
||||||
Environment="FLEET_PUBLIC_IP=12.34.56.78"
|
|
||||||
`
|
|
||||||
|
|
||||||
if env != expect {
|
|
||||||
t.Errorf("Generated environment:\n%s\nExpected environment:\n%s", env, expect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFleetUnit(t *testing.T) {
|
|
||||||
cfg := make(FleetEnvironment, 0)
|
|
||||||
u, err := cfg.Unit("/")
|
|
||||||
if u != nil {
|
|
||||||
t.Errorf("unexpectedly generated unit with empty FleetEnvironment")
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg["public-ip"] = "12.34.56.78"
|
|
||||||
|
|
||||||
u, err = cfg.Unit("/")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error generating fleet unit: %v", err)
|
|
||||||
}
|
|
||||||
if u == nil {
|
|
||||||
t.Fatalf("unexpectedly got nil unit generating fleet unit!")
|
|
||||||
}
|
|
||||||
if !u.Runtime {
|
|
||||||
t.Errorf("bad Runtime for generated fleet unit!")
|
|
||||||
}
|
|
||||||
if !u.DropIn {
|
|
||||||
t.Errorf("bad DropIn for generated fleet unit!")
|
|
||||||
}
|
|
||||||
}
|
|
85
initialize/locksmith.go
Normal file
85
initialize/locksmith.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package initialize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
const locksmithUnit = "locksmithd.service"
|
||||||
|
|
||||||
|
// addStrategy creates an `/etc/coreos/update.conf` file with the requested
|
||||||
|
// strategy via rewriting the file on disk or by starting from
|
||||||
|
// `/usr/share/coreos/update.conf`.
|
||||||
|
func addStrategy(strategy string, root string) error {
|
||||||
|
etcUpdate := path.Join(root, "etc", "coreos", "update.conf")
|
||||||
|
usrUpdate := path.Join(root, "usr", "share", "coreos", "update.conf")
|
||||||
|
|
||||||
|
// Ensure /etc/coreos/ exists before attempting to write a file in it
|
||||||
|
os.MkdirAll(path.Join(root, "etc", "coreos"), 0755)
|
||||||
|
|
||||||
|
tmp, err := ioutil.TempFile(path.Join(root, "etc", "coreos"), ".update.conf")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tmp.Chmod(0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf, err := os.Open(etcUpdate)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
conf, err = os.Open(usrUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(conf)
|
||||||
|
|
||||||
|
sawStrat := false
|
||||||
|
stratLine := "REBOOT_STRATEGY="+strategy
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.HasPrefix(line, "REBOOT_STRATEGY=") {
|
||||||
|
line = stratLine
|
||||||
|
sawStrat = true
|
||||||
|
}
|
||||||
|
fmt.Fprintln(tmp, line)
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sawStrat {
|
||||||
|
fmt.Fprintln(tmp, stratLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Rename(tmp.Name(), etcUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteLocksmithConfig updates the `update.conf` file with a REBOOT_STRATEGY for locksmith.
|
||||||
|
func WriteLocksmithConfig(strategy string, root string) error {
|
||||||
|
cmd := "restart"
|
||||||
|
if strategy == "off" {
|
||||||
|
err := system.MaskUnit(locksmithUnit, root)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd = "stop"
|
||||||
|
} else {
|
||||||
|
return addStrategy(strategy, root)
|
||||||
|
}
|
||||||
|
if err := system.DaemonReload(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := system.RunUnitCommand(cmd, locksmithUnit); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
91
initialize/locksmith_test.go
Normal file
91
initialize/locksmith_test.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package initialize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
base = `SERVER=https://example.com
|
||||||
|
GROUP=thegroupc`
|
||||||
|
|
||||||
|
configured = base + `
|
||||||
|
REBOOT_STRATEGY=awesome
|
||||||
|
`
|
||||||
|
|
||||||
|
expected = base + `
|
||||||
|
REBOOT_STRATEGY=etcd-lock
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupFixtures(dir string) {
|
||||||
|
os.MkdirAll(path.Join(dir, "usr", "share", "coreos"), 0755)
|
||||||
|
os.MkdirAll(path.Join(dir, "run", "systemd", "system"), 0755)
|
||||||
|
|
||||||
|
ioutil.WriteFile(path.Join(dir, "usr", "share", "coreos", "update.conf"), []byte(base), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocksmithEnvironmentWrittenToDisk(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create tempdir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
setupFixtures(dir)
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
if i == 1 {
|
||||||
|
err = ioutil.WriteFile(path.Join(dir, "etc", "coreos", "update.conf"), []byte(configured), 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := WriteLocksmithConfig("etcd-lock", dir); err != nil {
|
||||||
|
t.Fatalf("Processing of LocksmithEnvironment failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := path.Join(dir, "etc", "coreos", "update.conf")
|
||||||
|
|
||||||
|
fi, err := os.Stat(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to stat file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode() != os.FileMode(0644) {
|
||||||
|
t.Errorf("File has incorrect mode: %v", fi.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
contents, err := ioutil.ReadFile(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to read expected file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(contents) != expected {
|
||||||
|
t.Fatalf("File has incorrect contents, got %v, wanted %v", string(contents), expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestLocksmithEnvironmentMasked(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create tempdir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
setupFixtures(dir)
|
||||||
|
|
||||||
|
if err := WriteLocksmithConfig("off", dir); err != nil {
|
||||||
|
t.Fatalf("Processing of LocksmithEnvironment failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := path.Join(dir, "etc", "systemd", "system", "locksmithd.service")
|
||||||
|
target, err := os.Readlink(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to read link %v", err)
|
||||||
|
}
|
||||||
|
if target != "/dev/null" {
|
||||||
|
t.Fatalf("Locksmith not masked, unit target %v", target)
|
||||||
|
}
|
||||||
|
}
|
@@ -11,10 +11,8 @@ import (
|
|||||||
|
|
||||||
const DefaultIpv4Address = "127.0.0.1"
|
const DefaultIpv4Address = "127.0.0.1"
|
||||||
|
|
||||||
type EtcHosts string
|
func generateEtcHosts(option string) (out string, err error) {
|
||||||
|
if option != "localhost" {
|
||||||
func (eh EtcHosts) generateEtcHosts() (out string, err error) {
|
|
||||||
if eh != "localhost" {
|
|
||||||
return "", errors.New("Invalid option to manage_etc_hosts")
|
return "", errors.New("Invalid option to manage_etc_hosts")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,19 +26,19 @@ func (eh EtcHosts) generateEtcHosts() (out string, err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (eh EtcHosts) File(root string) (*system.File, error) {
|
// Write an /etc/hosts file
|
||||||
if eh == "" {
|
func WriteEtcHosts(option string, root string) error {
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
etcHosts, err := eh.generateEtcHosts()
|
etcHosts, err := generateEtcHosts(option)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &system.File{
|
file := system.File{
|
||||||
Path: path.Join("etc", "hosts"),
|
Path: path.Join(root, "etc", "hosts"),
|
||||||
RawFilePermissions: "0644",
|
RawFilePermissions: "0644",
|
||||||
Content: etcHosts,
|
Content: etcHosts,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
return system.WriteFile(&file)
|
||||||
}
|
}
|
||||||
|
@@ -6,8 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCloudConfigManageEtcHosts(t *testing.T) {
|
func TestCloudConfigManageEtcHosts(t *testing.T) {
|
||||||
@@ -27,9 +25,14 @@ manage_etc_hosts: localhost
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestManageEtcHostsInvalidValue(t *testing.T) {
|
func TestManageEtcHostsInvalidValue(t *testing.T) {
|
||||||
eh := EtcHosts("invalid")
|
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||||
if f, err := eh.File(""); err == nil || f != nil {
|
if err != nil {
|
||||||
t.Fatalf("EtcHosts File succeeded with invalid value!")
|
t.Fatalf("Unable to create tempdir: %v", err)
|
||||||
|
}
|
||||||
|
defer rmdir(dir)
|
||||||
|
|
||||||
|
if err := WriteEtcHosts("invalid", dir); err == nil {
|
||||||
|
t.Fatalf("WriteEtcHosts succeeded with invalid value: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,22 +41,10 @@ func TestEtcHostsWrittenToDisk(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
t.Fatalf("Unable to create tempdir: %v", err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer rmdir(dir)
|
||||||
|
|
||||||
eh := EtcHosts("localhost")
|
if err := WriteEtcHosts("localhost", dir); err != nil {
|
||||||
|
t.Fatalf("WriteEtcHosts failed: %v", err)
|
||||||
f, err := eh.File(dir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error calling File on EtcHosts: %v", err)
|
|
||||||
}
|
|
||||||
if f == nil {
|
|
||||||
t.Fatalf("manageEtcHosts returned nil file unexpectedly")
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Path = path.Join(dir, f.Path)
|
|
||||||
|
|
||||||
if err := system.WriteFile(f); err != nil {
|
|
||||||
t.Fatalf("Error writing EtcHosts: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := path.Join(dir, "etc", "hosts")
|
fullPath := path.Join(dir, "etc", "hosts")
|
||||||
|
@@ -16,7 +16,7 @@ type OEMRelease struct {
|
|||||||
BugReportURL string `yaml:"bug-report-url"`
|
BugReportURL string `yaml:"bug-report-url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (oem OEMRelease) String() string {
|
func (oem *OEMRelease) String() string {
|
||||||
fields := []string{
|
fields := []string{
|
||||||
fmt.Sprintf("ID=%s", oem.ID),
|
fmt.Sprintf("ID=%s", oem.ID),
|
||||||
fmt.Sprintf("VERSION_ID=%s", oem.VersionID),
|
fmt.Sprintf("VERSION_ID=%s", oem.VersionID),
|
||||||
@@ -28,14 +28,12 @@ func (oem OEMRelease) String() string {
|
|||||||
return strings.Join(fields, "\n") + "\n"
|
return strings.Join(fields, "\n") + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (oem OEMRelease) File(root string) (*system.File, error) {
|
func WriteOEMRelease(oem *OEMRelease, root string) error {
|
||||||
if oem.ID == "" {
|
file := system.File{
|
||||||
return nil, nil
|
Path: path.Join(root, "etc", "oem-release"),
|
||||||
}
|
|
||||||
|
|
||||||
return &system.File{
|
|
||||||
Path: path.Join("etc", "oem-release"),
|
|
||||||
RawFilePermissions: "0644",
|
RawFilePermissions: "0644",
|
||||||
Content: oem.String(),
|
Content: oem.String(),
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
return system.WriteFile(&file)
|
||||||
}
|
}
|
||||||
|
@@ -5,8 +5,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOEMReleaseWrittenToDisk(t *testing.T) {
|
func TestOEMReleaseWrittenToDisk(t *testing.T) {
|
||||||
@@ -23,17 +21,8 @@ func TestOEMReleaseWrittenToDisk(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
f, err := oem.File(dir)
|
if err := WriteOEMRelease(&oem, dir); err != nil {
|
||||||
if err != nil {
|
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)
|
||||||
t.Fatalf("Processing of OEMRelease failed: %v", err)
|
|
||||||
}
|
|
||||||
if f == nil {
|
|
||||||
t.Fatalf("OEMRelease returned nil file unexpectedly")
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Path = path.Join(dir, f.Path)
|
|
||||||
if err := system.WriteFile(f); err != nil {
|
|
||||||
t.Fatalf("Writing of OEMRelease failed: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := path.Join(dir, "etc", "oem-release")
|
fullPath := path.Join(dir, "etc", "oem-release")
|
||||||
|
@@ -3,8 +3,9 @@ package initialize
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/pkg"
|
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,19 +25,22 @@ func SSHImportKeysFromURL(system_user string, url string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fetchUserKeys(url string) ([]string, error) {
|
func fetchUserKeys(url string) ([]string, error) {
|
||||||
client := pkg.NewHttpClient()
|
res, err := http.Get(url)
|
||||||
data, err := client.Get(url)
|
defer res.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
var userKeys []UserKey
|
if err != nil {
|
||||||
err = json.Unmarshal(data, &userKeys)
|
return nil, err
|
||||||
|
}
|
||||||
|
var data []UserKey
|
||||||
|
err = json.Unmarshal(body, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
keys := make([]string, 0)
|
keys := make([]string, 0)
|
||||||
for _, key := range userKeys {
|
for _, key := range data {
|
||||||
keys = append(keys, key.Key)
|
keys = append(keys, key.Key)
|
||||||
}
|
}
|
||||||
return keys, err
|
return keys, err
|
||||||
|
@@ -1,149 +0,0 @@
|
|||||||
package initialize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
locksmithUnit = "locksmithd.service"
|
|
||||||
)
|
|
||||||
|
|
||||||
// updateOption represents a configurable update option, which, if set, will be
|
|
||||||
// written into update.conf, replacing any existing value for the option
|
|
||||||
type updateOption struct {
|
|
||||||
key string // key used to configure this option in cloud-config
|
|
||||||
valid []string // valid values for the option
|
|
||||||
prefix string // prefix for the option in the update.conf file
|
|
||||||
value string // used to store the new value in update.conf (including prefix)
|
|
||||||
seen bool // whether the option has been seen in any existing update.conf
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateOptions defines the update options understood by cloud-config.
|
|
||||||
// The keys represent the string used in cloud-config to configure the option.
|
|
||||||
var updateOptions = []*updateOption{
|
|
||||||
&updateOption{
|
|
||||||
key: "reboot-strategy",
|
|
||||||
prefix: "REBOOT_STRATEGY=",
|
|
||||||
valid: []string{"best-effort", "etcd-lock", "reboot", "off"},
|
|
||||||
},
|
|
||||||
&updateOption{
|
|
||||||
key: "group",
|
|
||||||
prefix: "GROUP=",
|
|
||||||
valid: []string{"master", "beta", "alpha", "stable"},
|
|
||||||
},
|
|
||||||
&updateOption{
|
|
||||||
key: "server",
|
|
||||||
prefix: "SERVER=",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// isValid checks whether a supplied value is valid for this option
|
|
||||||
func (uo updateOption) isValid(val string) bool {
|
|
||||||
if len(uo.valid) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, v := range uo.valid {
|
|
||||||
if val == v {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateConfig map[string]string
|
|
||||||
|
|
||||||
// File generates an `/etc/coreos/update.conf` file (if any update
|
|
||||||
// configuration options are set in cloud-config) by either rewriting the
|
|
||||||
// existing file on disk, or starting from `/usr/share/coreos/update.conf`
|
|
||||||
func (uc UpdateConfig) File(root string) (*system.File, error) {
|
|
||||||
if len(uc) < 1 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var out string
|
|
||||||
|
|
||||||
// Generate the list of possible substitutions to be performed based on the options that are configured
|
|
||||||
subs := make([]*updateOption, 0)
|
|
||||||
for _, uo := range updateOptions {
|
|
||||||
val, ok := uc[uo.key]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !uo.isValid(val) {
|
|
||||||
return nil, errors.New(fmt.Sprintf("invalid value %v for option %v (valid options: %v)", val, uo.key, uo.valid))
|
|
||||||
}
|
|
||||||
uo.value = uo.prefix + val
|
|
||||||
subs = append(subs, uo)
|
|
||||||
}
|
|
||||||
|
|
||||||
etcUpdate := path.Join(root, "etc", "coreos", "update.conf")
|
|
||||||
usrUpdate := path.Join(root, "usr", "share", "coreos", "update.conf")
|
|
||||||
|
|
||||||
conf, err := os.Open(etcUpdate)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
conf, err = os.Open(usrUpdate)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(conf)
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
for _, s := range subs {
|
|
||||||
if strings.HasPrefix(line, s.prefix) {
|
|
||||||
line = s.value
|
|
||||||
s.seen = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out += line
|
|
||||||
out += "\n"
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range subs {
|
|
||||||
if !s.seen {
|
|
||||||
out += s.value
|
|
||||||
out += "\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &system.File{
|
|
||||||
Path: path.Join("etc", "coreos", "update.conf"),
|
|
||||||
RawFilePermissions: "0644",
|
|
||||||
Content: out,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUnit generates a locksmith system.Unit, if reboot-strategy was set in
|
|
||||||
// cloud-config, for the cloud-init initializer to act on appropriately
|
|
||||||
func (uc UpdateConfig) Unit(root string) (*system.Unit, error) {
|
|
||||||
strategy, ok := uc["reboot-strategy"]
|
|
||||||
if !ok {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
u := &system.Unit{
|
|
||||||
Name: locksmithUnit,
|
|
||||||
Command: "restart",
|
|
||||||
Mask: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
if strategy == "off" {
|
|
||||||
u.Command = "stop"
|
|
||||||
u.Mask = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return u, nil
|
|
||||||
}
|
|
@@ -1,217 +0,0 @@
|
|||||||
package initialize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
base = `SERVER=https://example.com
|
|
||||||
GROUP=thegroupc`
|
|
||||||
configured = base + `
|
|
||||||
REBOOT_STRATEGY=awesome
|
|
||||||
`
|
|
||||||
expected = base + `
|
|
||||||
REBOOT_STRATEGY=etcd-lock
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
func setupFixtures(dir string) {
|
|
||||||
os.MkdirAll(path.Join(dir, "usr", "share", "coreos"), 0755)
|
|
||||||
os.MkdirAll(path.Join(dir, "run", "systemd", "system"), 0755)
|
|
||||||
|
|
||||||
ioutil.WriteFile(path.Join(dir, "usr", "share", "coreos", "update.conf"), []byte(base), 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyUpdateConfig(t *testing.T) {
|
|
||||||
uc := &UpdateConfig{}
|
|
||||||
f, err := uc.File("")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected error getting file from empty UpdateConfig")
|
|
||||||
}
|
|
||||||
if f != nil {
|
|
||||||
t.Errorf("getting file from empty UpdateConfig should have returned nil, got %v", f)
|
|
||||||
}
|
|
||||||
u, err := uc.Unit("")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected error getting unit from empty UpdateConfig")
|
|
||||||
}
|
|
||||||
if u != nil {
|
|
||||||
t.Errorf("getting unit from empty UpdateConfig should have returned nil, got %v", u)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInvalidUpdateOptions(t *testing.T) {
|
|
||||||
uon := &updateOption{
|
|
||||||
key: "numbers",
|
|
||||||
prefix: "numero_",
|
|
||||||
valid: []string{"one", "two"},
|
|
||||||
}
|
|
||||||
uoa := &updateOption{
|
|
||||||
key: "any_will_do",
|
|
||||||
prefix: "any_",
|
|
||||||
}
|
|
||||||
|
|
||||||
if !uon.isValid("one") {
|
|
||||||
t.Error("update option did not accept valid option \"one\"")
|
|
||||||
}
|
|
||||||
if uon.isValid("three") {
|
|
||||||
t.Error("update option accepted invalid option \"three\"")
|
|
||||||
}
|
|
||||||
for _, s := range []string{"one", "asdf", "foobarbaz"} {
|
|
||||||
if !uoa.isValid(s) {
|
|
||||||
t.Errorf("update option with no \"valid\" field did not accept %q", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uc := &UpdateConfig{"reboot-strategy": "wizzlewazzle"}
|
|
||||||
f, err := uc.File("")
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("File did not give an error on invalid UpdateOption")
|
|
||||||
}
|
|
||||||
if f != nil {
|
|
||||||
t.Errorf("File did not return a nil file on invalid UpdateOption")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServerGroupOptions(t *testing.T) {
|
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to create tempdir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
setupFixtures(dir)
|
|
||||||
u := &UpdateConfig{"group": "master", "server": "http://foo.com"}
|
|
||||||
|
|
||||||
want := `
|
|
||||||
GROUP=master
|
|
||||||
SERVER=http://foo.com`
|
|
||||||
|
|
||||||
f, err := u.File(dir)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error getting file from UpdateConfig: %v", err)
|
|
||||||
} else if f == nil {
|
|
||||||
t.Error("unexpectedly got empty file from UpdateConfig")
|
|
||||||
} else {
|
|
||||||
out := strings.Split(f.Content, "\n")
|
|
||||||
sort.Strings(out)
|
|
||||||
got := strings.Join(out, "\n")
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("File has incorrect contents, got %v, want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRebootStrategies(t *testing.T) {
|
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
setupFixtures(dir)
|
|
||||||
strategies := []struct {
|
|
||||||
name string
|
|
||||||
line string
|
|
||||||
uMask bool
|
|
||||||
uCommand string
|
|
||||||
}{
|
|
||||||
{"best-effort", "REBOOT_STRATEGY=best-effort", false, "restart"},
|
|
||||||
{"etcd-lock", "REBOOT_STRATEGY=etcd-lock", false, "restart"},
|
|
||||||
{"reboot", "REBOOT_STRATEGY=reboot", false, "restart"},
|
|
||||||
{"off", "REBOOT_STRATEGY=off", true, "stop"},
|
|
||||||
}
|
|
||||||
for _, s := range strategies {
|
|
||||||
uc := &UpdateConfig{"reboot-strategy": s.name}
|
|
||||||
f, err := uc.File(dir)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("update failed to generate file for reboot-strategy=%v: %v", s.name, err)
|
|
||||||
} else if f == nil {
|
|
||||||
t.Errorf("generated empty file for reboot-strategy=%v", s.name)
|
|
||||||
} else {
|
|
||||||
seen := false
|
|
||||||
for _, line := range strings.Split(f.Content, "\n") {
|
|
||||||
if line == s.line {
|
|
||||||
seen = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !seen {
|
|
||||||
t.Errorf("couldn't find expected line %v for reboot-strategy=%v", s.line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
u, err := uc.Unit(dir)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to generate unit for reboot-strategy=%v!", s.name)
|
|
||||||
} else if u == nil {
|
|
||||||
t.Errorf("generated empty unit for reboot-strategy=%v", s.name)
|
|
||||||
} else {
|
|
||||||
if u.Name != locksmithUnit {
|
|
||||||
t.Errorf("unit generated for reboot strategy=%v had bad name: %v", s.name, u.Name)
|
|
||||||
}
|
|
||||||
if u.Mask != s.uMask {
|
|
||||||
t.Errorf("unit generated for reboot strategy=%v had bad mask: %t", s.name, u.Mask)
|
|
||||||
}
|
|
||||||
if u.Command != s.uCommand {
|
|
||||||
t.Errorf("unit generated for reboot strategy=%v had bad command: %v", s.name, u.Command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateConfWrittenToDisk(t *testing.T) {
|
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
setupFixtures(dir)
|
|
||||||
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
if i == 1 {
|
|
||||||
err = ioutil.WriteFile(path.Join(dir, "etc", "coreos", "update.conf"), []byte(configured), 0644)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uc := &UpdateConfig{"reboot-strategy": "etcd-lock"}
|
|
||||||
|
|
||||||
f, err := uc.File(dir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Processing UpdateConfig failed: %v", err)
|
|
||||||
} else if f == nil {
|
|
||||||
t.Fatal("Unexpectedly got nil updateconfig file")
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Path = path.Join(dir, f.Path)
|
|
||||||
if err := system.WriteFile(f); err != nil {
|
|
||||||
t.Fatalf("Error writing update config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fullPath := path.Join(dir, "etc", "coreos", "update.conf")
|
|
||||||
|
|
||||||
fi, err := os.Stat(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to stat file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.Mode() != os.FileMode(0644) {
|
|
||||||
t.Errorf("File has incorrect mode: %v", fi.Mode())
|
|
||||||
}
|
|
||||||
|
|
||||||
contents, err := ioutil.ReadFile(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to read expected file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(contents) != expected {
|
|
||||||
t.Fatalf("File has incorrect contents, got %v, wanted %v", string(contents), expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,193 +0,0 @@
|
|||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type InterfaceGenerator interface {
|
|
||||||
Name() string
|
|
||||||
Netdev() string
|
|
||||||
Link() string
|
|
||||||
Network() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type logicalInterface struct {
|
|
||||||
name string
|
|
||||||
config configMethod
|
|
||||||
children []InterfaceGenerator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *logicalInterface) Network() string {
|
|
||||||
config := fmt.Sprintf("[Match]\nName=%s\n\n[Network]\n", i.name)
|
|
||||||
|
|
||||||
for _, child := range i.children {
|
|
||||||
switch iface := child.(type) {
|
|
||||||
case *vlanInterface:
|
|
||||||
config += fmt.Sprintf("VLAN=%s\n", iface.name)
|
|
||||||
case *bondInterface:
|
|
||||||
config += fmt.Sprintf("Bond=%s\n", iface.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch conf := i.config.(type) {
|
|
||||||
case configMethodStatic:
|
|
||||||
for _, nameserver := range conf.nameservers {
|
|
||||||
config += fmt.Sprintf("DNS=%s\n", nameserver)
|
|
||||||
}
|
|
||||||
if conf.address.IP != nil {
|
|
||||||
config += fmt.Sprintf("\n[Address]\nAddress=%s\n", conf.address.String())
|
|
||||||
}
|
|
||||||
for _, route := range conf.routes {
|
|
||||||
config += fmt.Sprintf("\n[Route]\nDestination=%s\nGateway=%s\n", route.destination.String(), route.gateway)
|
|
||||||
}
|
|
||||||
case configMethodDHCP:
|
|
||||||
config += "DHCP=true\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
type physicalInterface struct {
|
|
||||||
logicalInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *physicalInterface) Name() string {
|
|
||||||
return p.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *physicalInterface) Netdev() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *physicalInterface) Link() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type bondInterface struct {
|
|
||||||
logicalInterface
|
|
||||||
slaves []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bondInterface) Name() string {
|
|
||||||
return b.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bondInterface) Netdev() string {
|
|
||||||
return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bondInterface) Link() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type vlanInterface struct {
|
|
||||||
logicalInterface
|
|
||||||
id int
|
|
||||||
rawDevice string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *vlanInterface) Name() string {
|
|
||||||
return v.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *vlanInterface) Netdev() string {
|
|
||||||
return fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n\n[VLAN]\nId=%d\n", v.name, v.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *vlanInterface) Link() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator {
|
|
||||||
bondStanzas := make(map[string]*stanzaInterface)
|
|
||||||
physicalStanzas := make(map[string]*stanzaInterface)
|
|
||||||
vlanStanzas := make(map[string]*stanzaInterface)
|
|
||||||
for _, iface := range stanzas {
|
|
||||||
switch iface.kind {
|
|
||||||
case interfaceBond:
|
|
||||||
bondStanzas[iface.name] = iface
|
|
||||||
case interfacePhysical:
|
|
||||||
physicalStanzas[iface.name] = iface
|
|
||||||
case interfaceVLAN:
|
|
||||||
vlanStanzas[iface.name] = iface
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
physicals := make(map[string]*physicalInterface)
|
|
||||||
for _, p := range physicalStanzas {
|
|
||||||
if _, ok := p.configMethod.(configMethodLoopback); ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
physicals[p.name] = &physicalInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: p.name,
|
|
||||||
config: p.configMethod,
|
|
||||||
children: []InterfaceGenerator{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bonds := make(map[string]*bondInterface)
|
|
||||||
for _, b := range bondStanzas {
|
|
||||||
bonds[b.name] = &bondInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: b.name,
|
|
||||||
config: b.configMethod,
|
|
||||||
children: []InterfaceGenerator{},
|
|
||||||
},
|
|
||||||
b.options["slaves"],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vlans := make(map[string]*vlanInterface)
|
|
||||||
for _, v := range vlanStanzas {
|
|
||||||
var rawDevice string
|
|
||||||
id, _ := strconv.Atoi(v.options["id"][0])
|
|
||||||
if device := v.options["raw_device"]; len(device) == 1 {
|
|
||||||
rawDevice = device[0]
|
|
||||||
}
|
|
||||||
vlans[v.name] = &vlanInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: v.name,
|
|
||||||
config: v.configMethod,
|
|
||||||
children: []InterfaceGenerator{},
|
|
||||||
},
|
|
||||||
id,
|
|
||||||
rawDevice,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, vlan := range vlans {
|
|
||||||
if physical, ok := physicals[vlan.rawDevice]; ok {
|
|
||||||
physical.children = append(physical.children, vlan)
|
|
||||||
}
|
|
||||||
if bond, ok := bonds[vlan.rawDevice]; ok {
|
|
||||||
bond.children = append(bond.children, vlan)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, bond := range bonds {
|
|
||||||
for _, slave := range bond.slaves {
|
|
||||||
if physical, ok := physicals[slave]; ok {
|
|
||||||
physical.children = append(physical.children, bond)
|
|
||||||
}
|
|
||||||
if pBond, ok := bonds[slave]; ok {
|
|
||||||
pBond.children = append(pBond.children, bond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interfaces := make([]InterfaceGenerator, 0, len(physicals)+len(bonds)+len(vlans))
|
|
||||||
for _, physical := range physicals {
|
|
||||||
interfaces = append(interfaces, physical)
|
|
||||||
}
|
|
||||||
for _, bond := range bonds {
|
|
||||||
interfaces = append(interfaces, bond)
|
|
||||||
}
|
|
||||||
for _, vlan := range vlans {
|
|
||||||
interfaces = append(interfaces, vlan)
|
|
||||||
}
|
|
||||||
|
|
||||||
return interfaces
|
|
||||||
}
|
|
@@ -1,321 +0,0 @@
|
|||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPhysicalInterfaceName(t *testing.T) {
|
|
||||||
p := physicalInterface{logicalInterface{name: "testname"}}
|
|
||||||
if p.Name() != "testname" {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPhysicalInterfaceNetdev(t *testing.T) {
|
|
||||||
p := physicalInterface{}
|
|
||||||
if p.Netdev() != "" {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPhysicalInterfaceLink(t *testing.T) {
|
|
||||||
p := physicalInterface{}
|
|
||||||
if p.Link() != "" {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPhysicalInterfaceNetwork(t *testing.T) {
|
|
||||||
p := physicalInterface{logicalInterface{
|
|
||||||
name: "testname",
|
|
||||||
children: []InterfaceGenerator{
|
|
||||||
&bondInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "testbond1",
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
&vlanInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "testvlan1",
|
|
||||||
},
|
|
||||||
1,
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
&vlanInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "testvlan2",
|
|
||||||
},
|
|
||||||
1,
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
network := `[Match]
|
|
||||||
Name=testname
|
|
||||||
|
|
||||||
[Network]
|
|
||||||
Bond=testbond1
|
|
||||||
VLAN=testvlan1
|
|
||||||
VLAN=testvlan2
|
|
||||||
`
|
|
||||||
if p.Network() != network {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBondInterfaceName(t *testing.T) {
|
|
||||||
b := bondInterface{logicalInterface{name: "testname"}, nil}
|
|
||||||
if b.Name() != "testname" {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBondInterfaceNetdev(t *testing.T) {
|
|
||||||
b := bondInterface{logicalInterface{name: "testname"}, nil}
|
|
||||||
netdev := `[NetDev]
|
|
||||||
Kind=bond
|
|
||||||
Name=testname
|
|
||||||
`
|
|
||||||
if b.Netdev() != netdev {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBondInterfaceLink(t *testing.T) {
|
|
||||||
b := bondInterface{}
|
|
||||||
if b.Link() != "" {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBondInterfaceNetwork(t *testing.T) {
|
|
||||||
b := bondInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "testname",
|
|
||||||
config: configMethodDHCP{},
|
|
||||||
children: []InterfaceGenerator{
|
|
||||||
&bondInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "testbond1",
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
&vlanInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "testvlan1",
|
|
||||||
},
|
|
||||||
1,
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
&vlanInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "testvlan2",
|
|
||||||
},
|
|
||||||
1,
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
}
|
|
||||||
network := `[Match]
|
|
||||||
Name=testname
|
|
||||||
|
|
||||||
[Network]
|
|
||||||
Bond=testbond1
|
|
||||||
VLAN=testvlan1
|
|
||||||
VLAN=testvlan2
|
|
||||||
DHCP=true
|
|
||||||
`
|
|
||||||
if b.Network() != network {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVLANInterfaceName(t *testing.T) {
|
|
||||||
v := vlanInterface{logicalInterface{name: "testname"}, 1, ""}
|
|
||||||
if v.Name() != "testname" {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVLANInterfaceNetdev(t *testing.T) {
|
|
||||||
v := vlanInterface{logicalInterface{name: "testname"}, 1, ""}
|
|
||||||
netdev := `[NetDev]
|
|
||||||
Kind=vlan
|
|
||||||
Name=testname
|
|
||||||
|
|
||||||
[VLAN]
|
|
||||||
Id=1
|
|
||||||
`
|
|
||||||
if v.Netdev() != netdev {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVLANInterfaceLink(t *testing.T) {
|
|
||||||
v := vlanInterface{}
|
|
||||||
if v.Link() != "" {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVLANInterfaceNetwork(t *testing.T) {
|
|
||||||
v := vlanInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "testname",
|
|
||||||
config: configMethodStatic{
|
|
||||||
address: net.IPNet{
|
|
||||||
IP: []byte{192, 168, 1, 100},
|
|
||||||
Mask: []byte{255, 255, 255, 0},
|
|
||||||
},
|
|
||||||
nameservers: []net.IP{
|
|
||||||
[]byte{8, 8, 8, 8},
|
|
||||||
},
|
|
||||||
routes: []route{
|
|
||||||
route{
|
|
||||||
destination: net.IPNet{
|
|
||||||
IP: []byte{0, 0, 0, 0},
|
|
||||||
Mask: []byte{0, 0, 0, 0},
|
|
||||||
},
|
|
||||||
gateway: []byte{1, 2, 3, 4},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
"",
|
|
||||||
}
|
|
||||||
network := `[Match]
|
|
||||||
Name=testname
|
|
||||||
|
|
||||||
[Network]
|
|
||||||
DNS=8.8.8.8
|
|
||||||
|
|
||||||
[Address]
|
|
||||||
Address=192.168.1.100/24
|
|
||||||
|
|
||||||
[Route]
|
|
||||||
Destination=0.0.0.0/0
|
|
||||||
Gateway=1.2.3.4
|
|
||||||
`
|
|
||||||
if v.Network() != network {
|
|
||||||
t.Log(v.Network())
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildInterfacesLo(t *testing.T) {
|
|
||||||
stanzas := []*stanzaInterface{
|
|
||||||
&stanzaInterface{
|
|
||||||
name: "lo",
|
|
||||||
kind: interfacePhysical,
|
|
||||||
auto: false,
|
|
||||||
configMethod: configMethodLoopback{},
|
|
||||||
options: map[string][]string{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
interfaces := buildInterfaces(stanzas)
|
|
||||||
if len(interfaces) != 0 {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildInterfaces(t *testing.T) {
|
|
||||||
stanzas := []*stanzaInterface{
|
|
||||||
&stanzaInterface{
|
|
||||||
name: "eth0",
|
|
||||||
kind: interfacePhysical,
|
|
||||||
auto: false,
|
|
||||||
configMethod: configMethodManual{},
|
|
||||||
options: map[string][]string{},
|
|
||||||
},
|
|
||||||
&stanzaInterface{
|
|
||||||
name: "bond0",
|
|
||||||
kind: interfaceBond,
|
|
||||||
auto: false,
|
|
||||||
configMethod: configMethodManual{},
|
|
||||||
options: map[string][]string{
|
|
||||||
"slaves": []string{"eth0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&stanzaInterface{
|
|
||||||
name: "bond1",
|
|
||||||
kind: interfaceBond,
|
|
||||||
auto: false,
|
|
||||||
configMethod: configMethodManual{},
|
|
||||||
options: map[string][]string{
|
|
||||||
"slaves": []string{"bond0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&stanzaInterface{
|
|
||||||
name: "vlan0",
|
|
||||||
kind: interfaceVLAN,
|
|
||||||
auto: false,
|
|
||||||
configMethod: configMethodManual{},
|
|
||||||
options: map[string][]string{
|
|
||||||
"id": []string{"0"},
|
|
||||||
"raw_device": []string{"eth0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&stanzaInterface{
|
|
||||||
name: "vlan1",
|
|
||||||
kind: interfaceVLAN,
|
|
||||||
auto: false,
|
|
||||||
configMethod: configMethodManual{},
|
|
||||||
options: map[string][]string{
|
|
||||||
"id": []string{"1"},
|
|
||||||
"raw_device": []string{"bond0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
interfaces := buildInterfaces(stanzas)
|
|
||||||
vlan1 := &vlanInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "vlan1",
|
|
||||||
config: configMethodManual{},
|
|
||||||
children: []InterfaceGenerator{},
|
|
||||||
},
|
|
||||||
1,
|
|
||||||
"bond0",
|
|
||||||
}
|
|
||||||
vlan0 := &vlanInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "vlan0",
|
|
||||||
config: configMethodManual{},
|
|
||||||
children: []InterfaceGenerator{},
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
"eth0",
|
|
||||||
}
|
|
||||||
bond1 := &bondInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "bond1",
|
|
||||||
config: configMethodManual{},
|
|
||||||
children: []InterfaceGenerator{},
|
|
||||||
},
|
|
||||||
[]string{"bond0"},
|
|
||||||
}
|
|
||||||
bond0 := &bondInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "bond0",
|
|
||||||
config: configMethodManual{},
|
|
||||||
children: []InterfaceGenerator{vlan1, bond1},
|
|
||||||
},
|
|
||||||
[]string{"eth0"},
|
|
||||||
}
|
|
||||||
eth0 := &physicalInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "eth0",
|
|
||||||
config: configMethodManual{},
|
|
||||||
children: []InterfaceGenerator{vlan0, bond0},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
expect := []InterfaceGenerator{eth0, bond0, bond1, vlan0, vlan1}
|
|
||||||
if !reflect.DeepEqual(interfaces, expect) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,45 +0,0 @@
|
|||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) {
|
|
||||||
lines := formatConfig(config)
|
|
||||||
stanzas, err := parseStanzas(lines)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
interfaces := make([]*stanzaInterface, 0, len(stanzas))
|
|
||||||
for _, stanza := range stanzas {
|
|
||||||
switch s := stanza.(type) {
|
|
||||||
case *stanzaInterface:
|
|
||||||
interfaces = append(interfaces, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildInterfaces(interfaces), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatConfig(config string) []string {
|
|
||||||
lines := []string{}
|
|
||||||
config = strings.Replace(config, "\\\n", "", -1)
|
|
||||||
for config != "" {
|
|
||||||
split := strings.SplitN(config, "\n", 2)
|
|
||||||
line := strings.TrimSpace(split[0])
|
|
||||||
|
|
||||||
if len(split) == 2 {
|
|
||||||
config = split[1]
|
|
||||||
} else {
|
|
||||||
config = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(line, "#") || line == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, line)
|
|
||||||
}
|
|
||||||
return lines
|
|
||||||
}
|
|
@@ -1,42 +0,0 @@
|
|||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFormatConfigs(t *testing.T) {
|
|
||||||
for in, n := range map[string]int{
|
|
||||||
"": 0,
|
|
||||||
"line1\\\nis long": 1,
|
|
||||||
"#comment": 0,
|
|
||||||
"#comment\\\ncomment": 0,
|
|
||||||
" #comment \\\n comment\nline 1\nline 2\\\n is long": 2,
|
|
||||||
} {
|
|
||||||
lines := formatConfig(in)
|
|
||||||
if len(lines) != n {
|
|
||||||
t.Fatalf("bad number of lines for config %q: got %d, want %d", in, len(lines), n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProcessDebianNetconf(t *testing.T) {
|
|
||||||
for _, tt := range []struct {
|
|
||||||
in string
|
|
||||||
fail bool
|
|
||||||
n int
|
|
||||||
}{
|
|
||||||
{"", false, 0},
|
|
||||||
{"iface", true, -1},
|
|
||||||
{"auto eth1\nauto eth2", false, 0},
|
|
||||||
{"iface eth1 inet manual", false, 1},
|
|
||||||
} {
|
|
||||||
interfaces, err := ProcessDebianNetconf(tt.in)
|
|
||||||
failed := err != nil
|
|
||||||
if tt.fail != failed {
|
|
||||||
t.Fatalf("bad failure state for %q: got %b, want %b", failed, tt.fail)
|
|
||||||
}
|
|
||||||
if tt.n != -1 && tt.n != len(interfaces) {
|
|
||||||
t.Fatalf("bad number of interfaces for %q: got %d, want %q", tt.in, len(interfaces), tt.n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,295 +0,0 @@
|
|||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stanza interface{}
|
|
||||||
|
|
||||||
type stanzaAuto struct {
|
|
||||||
interfaces []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type stanzaInterface struct {
|
|
||||||
name string
|
|
||||||
kind interfaceKind
|
|
||||||
auto bool
|
|
||||||
configMethod configMethod
|
|
||||||
options map[string][]string
|
|
||||||
}
|
|
||||||
|
|
||||||
type interfaceKind int
|
|
||||||
|
|
||||||
const (
|
|
||||||
interfaceBond = interfaceKind(iota)
|
|
||||||
interfacePhysical
|
|
||||||
interfaceVLAN
|
|
||||||
)
|
|
||||||
|
|
||||||
type route struct {
|
|
||||||
destination net.IPNet
|
|
||||||
gateway net.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
type configMethod interface{}
|
|
||||||
|
|
||||||
type configMethodStatic struct {
|
|
||||||
address net.IPNet
|
|
||||||
nameservers []net.IP
|
|
||||||
routes []route
|
|
||||||
}
|
|
||||||
|
|
||||||
type configMethodLoopback struct{}
|
|
||||||
|
|
||||||
type configMethodManual struct{}
|
|
||||||
|
|
||||||
type configMethodDHCP struct{}
|
|
||||||
|
|
||||||
func parseStanzas(lines []string) (stanzas []stanza, err error) {
|
|
||||||
rawStanzas, err := splitStanzas(lines)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
stanzas = make([]stanza, 0, len(rawStanzas))
|
|
||||||
for _, rawStanza := range rawStanzas {
|
|
||||||
if stanza, err := parseStanza(rawStanza); err == nil {
|
|
||||||
stanzas = append(stanzas, stanza)
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
autos := make([]string, 0)
|
|
||||||
interfaceMap := make(map[string]*stanzaInterface)
|
|
||||||
for _, stanza := range stanzas {
|
|
||||||
switch c := stanza.(type) {
|
|
||||||
case *stanzaAuto:
|
|
||||||
autos = append(autos, c.interfaces...)
|
|
||||||
case *stanzaInterface:
|
|
||||||
interfaceMap[c.name] = c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the auto attribute
|
|
||||||
for _, auto := range autos {
|
|
||||||
if iface, ok := interfaceMap[auto]; ok {
|
|
||||||
iface.auto = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stanzas, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitStanzas(lines []string) ([][]string, error) {
|
|
||||||
var curStanza []string
|
|
||||||
stanzas := make([][]string, 0)
|
|
||||||
for _, line := range lines {
|
|
||||||
if isStanzaStart(line) {
|
|
||||||
if curStanza != nil {
|
|
||||||
stanzas = append(stanzas, curStanza)
|
|
||||||
}
|
|
||||||
curStanza = []string{line}
|
|
||||||
} else if curStanza != nil {
|
|
||||||
curStanza = append(curStanza, line)
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("missing stanza start '%s'", line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if curStanza != nil {
|
|
||||||
stanzas = append(stanzas, curStanza)
|
|
||||||
}
|
|
||||||
|
|
||||||
return stanzas, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isStanzaStart(line string) bool {
|
|
||||||
switch strings.Split(line, " ")[0] {
|
|
||||||
case "auto":
|
|
||||||
fallthrough
|
|
||||||
case "iface":
|
|
||||||
fallthrough
|
|
||||||
case "mapping":
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(line, "allow-") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseStanza(rawStanza []string) (stanza, error) {
|
|
||||||
if len(rawStanza) == 0 {
|
|
||||||
panic("empty stanza")
|
|
||||||
}
|
|
||||||
tokens := strings.Fields(rawStanza[0])
|
|
||||||
if len(tokens) < 2 {
|
|
||||||
return nil, fmt.Errorf("malformed stanza start %q", rawStanza[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
kind := tokens[0]
|
|
||||||
attributes := tokens[1:]
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case "auto":
|
|
||||||
return parseAutoStanza(attributes, rawStanza[1:])
|
|
||||||
case "iface":
|
|
||||||
return parseInterfaceStanza(attributes, rawStanza[1:])
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown stanza '%s'", kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseAutoStanza(attributes []string, options []string) (*stanzaAuto, error) {
|
|
||||||
return &stanzaAuto{interfaces: attributes}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterface, error) {
|
|
||||||
if len(attributes) != 3 {
|
|
||||||
return nil, fmt.Errorf("incorrect number of attributes")
|
|
||||||
}
|
|
||||||
|
|
||||||
iface := attributes[0]
|
|
||||||
confMethod := attributes[2]
|
|
||||||
|
|
||||||
optionMap := make(map[string][]string, 0)
|
|
||||||
for _, option := range options {
|
|
||||||
if strings.HasPrefix(option, "post-up") {
|
|
||||||
tokens := strings.SplitAfterN(option, " ", 2)
|
|
||||||
if len(tokens) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if v, ok := optionMap["post-up"]; ok {
|
|
||||||
optionMap["post-up"] = append(v, tokens[1])
|
|
||||||
} else {
|
|
||||||
optionMap["post-up"] = []string{tokens[1]}
|
|
||||||
}
|
|
||||||
} else if strings.HasPrefix(option, "pre-down") {
|
|
||||||
tokens := strings.SplitAfterN(option, " ", 2)
|
|
||||||
if len(tokens) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if v, ok := optionMap["pre-down"]; ok {
|
|
||||||
optionMap["pre-down"] = append(v, tokens[1])
|
|
||||||
} else {
|
|
||||||
optionMap["pre-down"] = []string{tokens[1]}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tokens := strings.Fields(option)
|
|
||||||
optionMap[tokens[0]] = tokens[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var conf configMethod
|
|
||||||
switch confMethod {
|
|
||||||
case "static":
|
|
||||||
config := configMethodStatic{
|
|
||||||
routes: make([]route, 0),
|
|
||||||
nameservers: make([]net.IP, 0),
|
|
||||||
}
|
|
||||||
if addresses, ok := optionMap["address"]; ok {
|
|
||||||
if len(addresses) == 1 {
|
|
||||||
config.address.IP = net.ParseIP(addresses[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if netmasks, ok := optionMap["netmask"]; ok {
|
|
||||||
if len(netmasks) == 1 {
|
|
||||||
config.address.Mask = net.IPMask(net.ParseIP(netmasks[0]).To4())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if config.address.IP == nil || config.address.Mask == nil {
|
|
||||||
return nil, fmt.Errorf("malformed static network config for '%s'", iface)
|
|
||||||
}
|
|
||||||
if gateways, ok := optionMap["gateway"]; ok {
|
|
||||||
if len(gateways) == 1 {
|
|
||||||
config.routes = append(config.routes, route{
|
|
||||||
destination: net.IPNet{
|
|
||||||
IP: net.IPv4(0, 0, 0, 0),
|
|
||||||
Mask: net.IPv4Mask(0, 0, 0, 0),
|
|
||||||
},
|
|
||||||
gateway: net.ParseIP(gateways[0]),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, nameserver := range optionMap["dns-nameservers"] {
|
|
||||||
config.nameservers = append(config.nameservers, net.ParseIP(nameserver))
|
|
||||||
}
|
|
||||||
for _, postup := range optionMap["post-up"] {
|
|
||||||
if strings.HasPrefix(postup, "route add") {
|
|
||||||
route := route{}
|
|
||||||
fields := strings.Fields(postup)
|
|
||||||
for i, field := range fields[:len(fields)-1] {
|
|
||||||
switch field {
|
|
||||||
case "-net":
|
|
||||||
route.destination.IP = net.ParseIP(fields[i+1])
|
|
||||||
case "netmask":
|
|
||||||
route.destination.Mask = net.IPMask(net.ParseIP(fields[i+1]).To4())
|
|
||||||
case "gw":
|
|
||||||
route.gateway = net.ParseIP(fields[i+1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if route.destination.IP != nil && route.destination.Mask != nil && route.gateway != nil {
|
|
||||||
config.routes = append(config.routes, route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conf = config
|
|
||||||
case "loopback":
|
|
||||||
conf = configMethodLoopback{}
|
|
||||||
case "manual":
|
|
||||||
conf = configMethodManual{}
|
|
||||||
case "dhcp":
|
|
||||||
conf = configMethodDHCP{}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid config method '%s'", confMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := optionMap["vlan_raw_device"]; ok {
|
|
||||||
return parseVLANStanza(iface, conf, attributes, optionMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(iface, ".") {
|
|
||||||
return parseVLANStanza(iface, conf, attributes, optionMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := optionMap["bond-slaves"]; ok {
|
|
||||||
return parseBondStanza(iface, conf, attributes, optionMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsePhysicalStanza(iface, conf, attributes, optionMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseBondStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
|
|
||||||
options["slaves"] = options["bond-slaves"]
|
|
||||||
return &stanzaInterface{name: iface, kind: interfaceBond, configMethod: conf, options: options}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsePhysicalStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
|
|
||||||
return &stanzaInterface{name: iface, kind: interfacePhysical, configMethod: conf, options: options}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseVLANStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
|
|
||||||
var id string
|
|
||||||
if strings.Contains(iface, ".") {
|
|
||||||
tokens := strings.Split(iface, ".")
|
|
||||||
id = tokens[len(tokens)-1]
|
|
||||||
} else if strings.HasPrefix(iface, "vlan") {
|
|
||||||
id = strings.TrimPrefix(iface, "vlan")
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("malformed vlan name %s", iface)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := strconv.Atoi(id); err != nil {
|
|
||||||
return nil, fmt.Errorf("malformed vlan name %s", iface)
|
|
||||||
}
|
|
||||||
options["id"] = []string{id}
|
|
||||||
options["raw_device"] = options["vlan_raw_device"]
|
|
||||||
|
|
||||||
return &stanzaInterface{name: iface, kind: interfaceVLAN, configMethod: conf, options: options}, nil
|
|
||||||
}
|
|
@@ -1,502 +0,0 @@
|
|||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSplitStanzasNoParent(t *testing.T) {
|
|
||||||
in := []string{"test"}
|
|
||||||
e := "missing stanza start"
|
|
||||||
_, err := splitStanzas(in)
|
|
||||||
if err == nil || !strings.HasPrefix(err.Error(), e) {
|
|
||||||
t.Fatalf("bad error for splitStanzas(%q): got %q, want %q", in, err, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBadParseStanzas(t *testing.T) {
|
|
||||||
for in, e := range map[string]string{
|
|
||||||
"": "missing stanza start",
|
|
||||||
"iface": "malformed stanza start",
|
|
||||||
"allow-?? unknown": "unknown stanza",
|
|
||||||
} {
|
|
||||||
_, err := parseStanzas([]string{in})
|
|
||||||
if err == nil || !strings.HasPrefix(err.Error(), e) {
|
|
||||||
t.Fatalf("bad error for parseStanzas(%q): got %q, want %q", in, err, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBadParseInterfaceStanza(t *testing.T) {
|
|
||||||
for _, tt := range []struct {
|
|
||||||
in []string
|
|
||||||
opts []string
|
|
||||||
e string
|
|
||||||
}{
|
|
||||||
{[]string{}, nil, "incorrect number of attributes"},
|
|
||||||
{[]string{"eth", "inet", "invalid"}, nil, "invalid config method"},
|
|
||||||
{[]string{"eth", "inet", "static"}, []string{"address 192.168.1.100"}, "malformed static network config"},
|
|
||||||
{[]string{"eth", "inet", "static"}, []string{"netmask 255.255.255.0"}, "malformed static network config"},
|
|
||||||
{[]string{"eth", "inet", "static"}, []string{"address invalid", "netmask 255.255.255.0"}, "malformed static network config"},
|
|
||||||
{[]string{"eth", "inet", "static"}, []string{"address 192.168.1.100", "netmask invalid"}, "malformed static network config"},
|
|
||||||
} {
|
|
||||||
_, err := parseInterfaceStanza(tt.in, tt.opts)
|
|
||||||
if err == nil || !strings.HasPrefix(err.Error(), tt.e) {
|
|
||||||
t.Fatalf("bad error parsing interface stanza %q: got %q, want %q", tt.in, err.Error(), tt.e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBadParseVLANStanzas(t *testing.T) {
|
|
||||||
conf := configMethodManual{}
|
|
||||||
options := map[string][]string{}
|
|
||||||
for _, in := range []string{"myvlan", "eth.vlan"} {
|
|
||||||
_, err := parseVLANStanza(in, conf, nil, options)
|
|
||||||
if err == nil || !strings.HasPrefix(err.Error(), "malformed vlan name") {
|
|
||||||
t.Fatalf("did not error on bad vlan %q", in)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSplitStanzas(t *testing.T) {
|
|
||||||
expect := [][]string{
|
|
||||||
{"auto lo"},
|
|
||||||
{"iface eth1", "option: 1"},
|
|
||||||
{"mapping"},
|
|
||||||
{"allow-"},
|
|
||||||
}
|
|
||||||
lines := make([]string, 0, 5)
|
|
||||||
for _, stanza := range expect {
|
|
||||||
for _, line := range stanza {
|
|
||||||
lines = append(lines, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stanzas, err := splitStanzas(lines)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
for i, stanza := range stanzas {
|
|
||||||
if len(stanza) != len(expect[i]) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
for j, line := range stanza {
|
|
||||||
if line != expect[i][j] {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseStanzaNil(t *testing.T) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r == nil {
|
|
||||||
t.Fatal("parseStanza(nil) did not panic")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
parseStanza(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseStanzaSuccess(t *testing.T) {
|
|
||||||
for _, in := range []string{
|
|
||||||
"auto a",
|
|
||||||
"iface a inet manual",
|
|
||||||
} {
|
|
||||||
if _, err := parseStanza([]string{in}); err != nil {
|
|
||||||
t.Fatalf("unexpected error parsing stanza %q: %s", in, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseAutoStanza(t *testing.T) {
|
|
||||||
interfaces := []string{"test", "attribute"}
|
|
||||||
stanza, err := parseAutoStanza(interfaces, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error parsing auto stanza %q: %s", interfaces, err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(stanza.interfaces, interfaces) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseBondStanzaNoSlaves(t *testing.T) {
|
|
||||||
bond, err := parseBondStanza("", nil, nil, map[string][]string{})
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if bond.options["slaves"] != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseBondStanza(t *testing.T) {
|
|
||||||
conf := configMethodManual{}
|
|
||||||
options := map[string][]string{
|
|
||||||
"bond-slaves": []string{"1", "2"},
|
|
||||||
}
|
|
||||||
bond, err := parseBondStanza("test", conf, nil, options)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if bond.name != "test" {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if bond.kind != interfaceBond {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if bond.configMethod != conf {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(bond.options["slaves"], options["bond-slaves"]) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParsePhysicalStanza(t *testing.T) {
|
|
||||||
conf := configMethodManual{}
|
|
||||||
options := map[string][]string{
|
|
||||||
"a": []string{"1", "2"},
|
|
||||||
"b": []string{"1"},
|
|
||||||
}
|
|
||||||
physical, err := parsePhysicalStanza("test", conf, nil, options)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if physical.name != "test" {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if physical.kind != interfacePhysical {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if physical.configMethod != conf {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(physical.options, options) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseVLANStanzas(t *testing.T) {
|
|
||||||
conf := configMethodManual{}
|
|
||||||
options := map[string][]string{}
|
|
||||||
for _, in := range []string{"vlan25", "eth.25"} {
|
|
||||||
vlan, err := parseVLANStanza(in, conf, nil, options)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error from parseVLANStanza(%q): %s", in, err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(vlan.options["id"], []string{"25"}) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInterfaceStanzaStaticAddress(t *testing.T) {
|
|
||||||
options := []string{"address 192.168.1.100", "netmask 255.255.255.0"}
|
|
||||||
expect := net.IPNet{
|
|
||||||
IP: net.IPv4(192, 168, 1, 100),
|
|
||||||
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
static, ok := iface.configMethod.(configMethodStatic)
|
|
||||||
if !ok {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(static.address, expect) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInterfaceStanzaStaticGateway(t *testing.T) {
|
|
||||||
options := []string{"address 192.168.1.100", "netmask 255.255.255.0", "gateway 192.168.1.1"}
|
|
||||||
expect := []route{
|
|
||||||
{
|
|
||||||
destination: net.IPNet{
|
|
||||||
IP: net.IPv4(0, 0, 0, 0),
|
|
||||||
Mask: net.IPv4Mask(0, 0, 0, 0),
|
|
||||||
},
|
|
||||||
gateway: net.IPv4(192, 168, 1, 1),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
static, ok := iface.configMethod.(configMethodStatic)
|
|
||||||
if !ok {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(static.routes, expect) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInterfaceStanzaStaticDNS(t *testing.T) {
|
|
||||||
options := []string{"address 192.168.1.100", "netmask 255.255.255.0", "dns-nameservers 192.168.1.10 192.168.1.11 192.168.1.12"}
|
|
||||||
expect := []net.IP{
|
|
||||||
net.IPv4(192, 168, 1, 10),
|
|
||||||
net.IPv4(192, 168, 1, 11),
|
|
||||||
net.IPv4(192, 168, 1, 12),
|
|
||||||
}
|
|
||||||
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
static, ok := iface.configMethod.(configMethodStatic)
|
|
||||||
if !ok {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(static.nameservers, expect) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBadParseInterfaceStanzasStaticPostUp(t *testing.T) {
|
|
||||||
for _, in := range []string{
|
|
||||||
"post-up invalid",
|
|
||||||
"post-up route add",
|
|
||||||
"post-up route add -net",
|
|
||||||
"post-up route add gw",
|
|
||||||
"post-up route add netmask",
|
|
||||||
"gateway",
|
|
||||||
"gateway 192.168.1.1 192.168.1.2",
|
|
||||||
} {
|
|
||||||
options := []string{"address 192.168.1.100", "netmask 255.255.255.0", in}
|
|
||||||
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("parseInterfaceStanza with options %s got unexpected error", options)
|
|
||||||
}
|
|
||||||
static, ok := iface.configMethod.(configMethodStatic)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("parseInterfaceStanza with options %s did not return configMethodStatic", options)
|
|
||||||
}
|
|
||||||
if len(static.routes) != 0 {
|
|
||||||
t.Fatalf("parseInterfaceStanza with options %s did not return zero-length static routes", options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInterfaceStanzaStaticPostUp(t *testing.T) {
|
|
||||||
options := []string{
|
|
||||||
"address 192.168.1.100",
|
|
||||||
"netmask 255.255.255.0",
|
|
||||||
"post-up route add gw 192.168.1.1 -net 192.168.1.0 netmask 255.255.255.0",
|
|
||||||
}
|
|
||||||
expect := []route{
|
|
||||||
{
|
|
||||||
destination: net.IPNet{
|
|
||||||
IP: net.IPv4(192, 168, 1, 0),
|
|
||||||
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
||||||
},
|
|
||||||
gateway: net.IPv4(192, 168, 1, 1),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
static, ok := iface.configMethod.(configMethodStatic)
|
|
||||||
if !ok {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(static.routes, expect) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInterfaceStanzaLoopback(t *testing.T) {
|
|
||||||
iface, err := parseInterfaceStanza([]string{"eth", "inet", "loopback"}, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if _, ok := iface.configMethod.(configMethodLoopback); !ok {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInterfaceStanzaManual(t *testing.T) {
|
|
||||||
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if _, ok := iface.configMethod.(configMethodManual); !ok {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInterfaceStanzaDHCP(t *testing.T) {
|
|
||||||
iface, err := parseInterfaceStanza([]string{"eth", "inet", "dhcp"}, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if _, ok := iface.configMethod.(configMethodDHCP); !ok {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInterfaceStanzaPostUpOption(t *testing.T) {
|
|
||||||
options := []string{
|
|
||||||
"post-up",
|
|
||||||
"post-up 1 2",
|
|
||||||
"post-up 3 4",
|
|
||||||
}
|
|
||||||
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(iface.options["post-up"], []string{"1 2", "3 4"}) {
|
|
||||||
t.Log(iface.options["post-up"])
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInterfaceStanzaPreDownOption(t *testing.T) {
|
|
||||||
options := []string{
|
|
||||||
"pre-down",
|
|
||||||
"pre-down 3",
|
|
||||||
"pre-down 4",
|
|
||||||
}
|
|
||||||
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(iface.options["pre-down"], []string{"3", "4"}) {
|
|
||||||
t.Log(iface.options["pre-down"])
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInterfaceStanzaEmptyOption(t *testing.T) {
|
|
||||||
options := []string{
|
|
||||||
"test",
|
|
||||||
}
|
|
||||||
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(iface.options["test"], []string{}) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInterfaceStanzaOptions(t *testing.T) {
|
|
||||||
options := []string{
|
|
||||||
"test1 1",
|
|
||||||
"test2 2 3",
|
|
||||||
"test1 5 6",
|
|
||||||
}
|
|
||||||
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(iface.options["test1"], []string{"5", "6"}) {
|
|
||||||
t.Log(iface.options["test1"])
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(iface.options["test2"], []string{"2", "3"}) {
|
|
||||||
t.Log(iface.options["test2"])
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInterfaceStazaBond(t *testing.T) {
|
|
||||||
iface, err := parseInterfaceStanza([]string{"mybond", "inet", "manual"}, []string{"bond-slaves eth"})
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if iface.kind != interfaceBond {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInterfaceStazaVLANName(t *testing.T) {
|
|
||||||
iface, err := parseInterfaceStanza([]string{"eth0.1", "inet", "manual"}, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if iface.kind != interfaceVLAN {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInterfaceStazaVLANOption(t *testing.T) {
|
|
||||||
iface, err := parseInterfaceStanza([]string{"vlan1", "inet", "manual"}, []string{"vlan_raw_device eth"})
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if iface.kind != interfaceVLAN {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseStanzasNone(t *testing.T) {
|
|
||||||
stanzas, err := parseStanzas(nil)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if len(stanzas) != 0 {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseStanzas(t *testing.T) {
|
|
||||||
lines := []string{
|
|
||||||
"auto lo",
|
|
||||||
"iface lo inet loopback",
|
|
||||||
"iface eth1 inet manual",
|
|
||||||
"iface eth2 inet manual",
|
|
||||||
"iface eth3 inet manual",
|
|
||||||
"auto eth1 eth3",
|
|
||||||
}
|
|
||||||
expect := []stanza{
|
|
||||||
&stanzaAuto{
|
|
||||||
interfaces: []string{"lo"},
|
|
||||||
},
|
|
||||||
&stanzaInterface{
|
|
||||||
name: "lo",
|
|
||||||
kind: interfacePhysical,
|
|
||||||
auto: true,
|
|
||||||
configMethod: configMethodLoopback{},
|
|
||||||
options: map[string][]string{},
|
|
||||||
},
|
|
||||||
&stanzaInterface{
|
|
||||||
name: "eth1",
|
|
||||||
kind: interfacePhysical,
|
|
||||||
auto: true,
|
|
||||||
configMethod: configMethodManual{},
|
|
||||||
options: map[string][]string{},
|
|
||||||
},
|
|
||||||
&stanzaInterface{
|
|
||||||
name: "eth2",
|
|
||||||
kind: interfacePhysical,
|
|
||||||
auto: false,
|
|
||||||
configMethod: configMethodManual{},
|
|
||||||
options: map[string][]string{},
|
|
||||||
},
|
|
||||||
&stanzaInterface{
|
|
||||||
name: "eth3",
|
|
||||||
kind: interfacePhysical,
|
|
||||||
auto: true,
|
|
||||||
configMethod: configMethodManual{},
|
|
||||||
options: map[string][]string{},
|
|
||||||
},
|
|
||||||
&stanzaAuto{
|
|
||||||
interfaces: []string{"eth1", "eth3"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
stanzas, err := parseStanzas(lines)
|
|
||||||
if err != err {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(stanzas, expect) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,123 +0,0 @@
|
|||||||
package pkg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
neturl "net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
HTTP_2xx = 2
|
|
||||||
HTTP_4xx = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
type HttpClient struct {
|
|
||||||
// Maximum exp backoff duration. Defaults to 5 seconds
|
|
||||||
MaxBackoff time.Duration
|
|
||||||
|
|
||||||
// Maximum number of connection retries. Defaults to 15
|
|
||||||
MaxRetries int
|
|
||||||
|
|
||||||
// HTTP client timeout, this is suggested to be low since exponential
|
|
||||||
// backoff will kick off too. Defaults to 2 seconds
|
|
||||||
Timeout time.Duration
|
|
||||||
|
|
||||||
// Whether or not to skip TLS verification. Defaults to false
|
|
||||||
SkipTLS bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHttpClient() *HttpClient {
|
|
||||||
return &HttpClient{
|
|
||||||
MaxBackoff: time.Second * 5,
|
|
||||||
MaxRetries: 15,
|
|
||||||
Timeout: time.Duration(2) * time.Second,
|
|
||||||
SkipTLS: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func expBackoff(interval, max time.Duration) time.Duration {
|
|
||||||
interval = interval * 2
|
|
||||||
if interval > max {
|
|
||||||
interval = max
|
|
||||||
}
|
|
||||||
return interval
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetches a given URL with support for exponential backoff and maximum retries
|
|
||||||
func (h *HttpClient) Get(rawurl string) ([]byte, error) {
|
|
||||||
if rawurl == "" {
|
|
||||||
return nil, errors.New("URL is empty. Skipping.")
|
|
||||||
}
|
|
||||||
|
|
||||||
url, err := neturl.Parse(rawurl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unfortunately, url.Parse is too generic to throw errors if a URL does not
|
|
||||||
// have a valid HTTP scheme. So, we have to do this extra validation
|
|
||||||
if !strings.HasPrefix(url.Scheme, "http") {
|
|
||||||
return nil, fmt.Errorf("URL %s does not have a valid HTTP scheme. Skipping.", rawurl)
|
|
||||||
}
|
|
||||||
|
|
||||||
dataURL := url.String()
|
|
||||||
|
|
||||||
// We need to create our own client in order to add timeout support.
|
|
||||||
// TODO(c4milo) Replace it once Go 1.3 is officially used by CoreOS
|
|
||||||
// More info: https://code.google.com/p/go/source/detail?r=ada6f2d5f99f
|
|
||||||
transport := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: h.SkipTLS,
|
|
||||||
},
|
|
||||||
Dial: func(network, addr string) (net.Conn, error) {
|
|
||||||
deadline := time.Now().Add(h.Timeout)
|
|
||||||
c, err := net.DialTimeout(network, addr, h.Timeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.SetDeadline(deadline)
|
|
||||||
return c, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: transport,
|
|
||||||
}
|
|
||||||
|
|
||||||
duration := 50 * time.Millisecond
|
|
||||||
for retry := 1; retry <= h.MaxRetries; retry++ {
|
|
||||||
log.Printf("Fetching data from %s. Attempt #%d", dataURL, retry)
|
|
||||||
|
|
||||||
resp, err := client.Get(dataURL)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
status := resp.StatusCode / 100
|
|
||||||
|
|
||||||
if status == HTTP_2xx {
|
|
||||||
return ioutil.ReadAll(resp.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
if status == HTTP_4xx {
|
|
||||||
return nil, fmt.Errorf("Not found. HTTP status code: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Server error. HTTP status code: %d", resp.StatusCode)
|
|
||||||
} else {
|
|
||||||
log.Printf("Unable to fetch data: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
duration = expBackoff(duration, h.MaxBackoff)
|
|
||||||
log.Printf("Sleeping for %v...", duration)
|
|
||||||
time.Sleep(duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("Unable to fetch data. Maximum retries reached: %d", h.MaxRetries)
|
|
||||||
}
|
|
@@ -1,140 +0,0 @@
|
|||||||
package pkg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestExpBackoff(t *testing.T) {
|
|
||||||
duration := time.Millisecond
|
|
||||||
max := time.Hour
|
|
||||||
for i := 0; i < math.MaxUint16; i++ {
|
|
||||||
duration = expBackoff(duration, max)
|
|
||||||
if duration < 0 {
|
|
||||||
t.Fatalf("duration too small: %v %v", duration, i)
|
|
||||||
}
|
|
||||||
if duration > max {
|
|
||||||
t.Fatalf("duration too large: %v %v", duration, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test exponential backoff and that it continues retrying if a 5xx response is
|
|
||||||
// received
|
|
||||||
func TestGetURLExpBackOff(t *testing.T) {
|
|
||||||
var expBackoffTests = []struct {
|
|
||||||
count int
|
|
||||||
body string
|
|
||||||
}{
|
|
||||||
{0, "number of attempts: 0"},
|
|
||||||
{1, "number of attempts: 1"},
|
|
||||||
{2, "number of attempts: 2"},
|
|
||||||
}
|
|
||||||
client := NewHttpClient()
|
|
||||||
|
|
||||||
for i, tt := range expBackoffTests {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
count := 0
|
|
||||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if count == tt.count {
|
|
||||||
io.WriteString(w, fmt.Sprintf("number of attempts: %d", count))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
count++
|
|
||||||
http.Error(w, "", 500)
|
|
||||||
})
|
|
||||||
ts := httptest.NewServer(mux)
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
data, err := client.Get(ts.URL)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Test case %d produced error: %v", i, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if count != tt.count {
|
|
||||||
t.Errorf("Test case %d failed: %d != %d", i, count, tt.count)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(data) != tt.body {
|
|
||||||
t.Errorf("Test case %d failed: %s != %s", i, tt.body, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that it stops retrying if a 4xx response comes back
|
|
||||||
func TestGetURL4xx(t *testing.T) {
|
|
||||||
client := NewHttpClient()
|
|
||||||
retries := 0
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
retries++
|
|
||||||
http.Error(w, "", 404)
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
_, err := client.Get(ts.URL)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Incorrect result\ngot: %s\nwant: %s", err.Error(), "Not found. HTTP status code: 404")
|
|
||||||
}
|
|
||||||
|
|
||||||
if retries > 1 {
|
|
||||||
t.Errorf("Number of retries:\n%d\nExpected number of retries:\n%s", retries, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that it fetches and returns user-data just fine
|
|
||||||
func TestGetURL2xx(t *testing.T) {
|
|
||||||
var cloudcfg = `
|
|
||||||
#cloud-config
|
|
||||||
coreos:
|
|
||||||
oem:
|
|
||||||
id: test
|
|
||||||
name: CoreOS.box for Test
|
|
||||||
version-id: %VERSION_ID%+%BUILD_ID%
|
|
||||||
home-url: https://github.com/coreos/coreos-cloudinit
|
|
||||||
bug-report-url: https://github.com/coreos/coreos-cloudinit
|
|
||||||
update:
|
|
||||||
reboot-strategy: best-effort
|
|
||||||
`
|
|
||||||
|
|
||||||
client := NewHttpClient()
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fmt.Fprint(w, cloudcfg)
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
data, err := client.Get(ts.URL)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Incorrect result\ngot: %v\nwant: %v", err, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(data) != cloudcfg {
|
|
||||||
t.Errorf("Incorrect result\ngot: %s\nwant: %s", string(data), cloudcfg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test attempt to fetching using malformed URL
|
|
||||||
func TestGetMalformedURL(t *testing.T) {
|
|
||||||
client := NewHttpClient()
|
|
||||||
|
|
||||||
var tests = []struct {
|
|
||||||
url string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{"boo", "URL boo does not have a valid HTTP scheme. Skipping."},
|
|
||||||
{"mailto://boo", "URL mailto://boo does not have a valid HTTP scheme. Skipping."},
|
|
||||||
{"ftp://boo", "URL ftp://boo does not have a valid HTTP scheme. Skipping."},
|
|
||||||
{"", "URL is empty. Skipping."},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
_, err := client.Get(test.url)
|
|
||||||
if err == nil || err.Error() != test.want {
|
|
||||||
t.Errorf("Incorrect result\ngot: %v\nwant: %v", err, test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
1
src/github.com/coreos/coreos-cloudinit
Symbolic link
1
src/github.com/coreos/coreos-cloudinit
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../
|
@@ -1,89 +0,0 @@
|
|||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"os/exec"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/network"
|
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/dotcloud/docker/pkg/netlink"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
runtimeNetworkPath = "/run/systemd/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RestartNetwork(interfaces []network.InterfaceGenerator) (err error) {
|
|
||||||
defer func() {
|
|
||||||
if e := restartNetworkd(); e != nil {
|
|
||||||
err = e
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = downNetworkInterfaces(interfaces); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = probe8012q(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error {
|
|
||||||
sysInterfaceMap := make(map[string]*net.Interface)
|
|
||||||
if systemInterfaces, err := net.Interfaces(); err == nil {
|
|
||||||
for _, iface := range systemInterfaces {
|
|
||||||
sysInterfaceMap[iface.Name] = &iface
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, iface := range interfaces {
|
|
||||||
if systemInterface, ok := sysInterfaceMap[iface.Name()]; ok {
|
|
||||||
if err := netlink.NetworkLinkDown(systemInterface); err != nil {
|
|
||||||
fmt.Printf("Error while downing interface %q (%s). Continuing...\n", systemInterface.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func probe8012q() error {
|
|
||||||
return exec.Command("modprobe", "8021q").Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func restartNetworkd() error {
|
|
||||||
_, err := RunUnitCommand("restart", "systemd-networkd.service")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func WriteNetworkdConfigs(interfaces []network.InterfaceGenerator) error {
|
|
||||||
for _, iface := range interfaces {
|
|
||||||
filename := path.Join(runtimeNetworkPath, fmt.Sprintf("%s.netdev", iface.Name()))
|
|
||||||
if err := writeConfig(filename, iface.Netdev()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.link", iface.Name()))
|
|
||||||
if err := writeConfig(filename, iface.Link()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.network", iface.Name()))
|
|
||||||
if err := writeConfig(filename, iface.Network()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeConfig(filename string, config string) error {
|
|
||||||
if config == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ioutil.WriteFile(filename, []byte(config), 0444)
|
|
||||||
}
|
|
@@ -17,21 +17,12 @@ import (
|
|||||||
// never be used as a true MachineID
|
// never be used as a true MachineID
|
||||||
const fakeMachineID = "42000000000000000000000000000042"
|
const fakeMachineID = "42000000000000000000000000000042"
|
||||||
|
|
||||||
// Name for drop-in service configuration files created by cloudconfig
|
|
||||||
const cloudConfigDropIn = "20-cloudinit.conf"
|
|
||||||
|
|
||||||
type Unit struct {
|
type Unit struct {
|
||||||
Name string
|
Name string
|
||||||
Mask bool
|
|
||||||
Enable bool
|
Enable bool
|
||||||
Runtime bool
|
Runtime bool
|
||||||
Content string
|
Content string
|
||||||
Command string
|
Command string
|
||||||
|
|
||||||
// For drop-in units, a cloudinit.conf is generated.
|
|
||||||
// This is currently unbound in YAML (and hence unsettable in cloud-config files)
|
|
||||||
// until the correct behaviour for multiple drop-in units is determined.
|
|
||||||
DropIn bool `yaml:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unit) Type() string {
|
func (u *Unit) Type() string {
|
||||||
@@ -51,8 +42,8 @@ func (u *Unit) Group() (group string) {
|
|||||||
|
|
||||||
type Script []byte
|
type Script []byte
|
||||||
|
|
||||||
// UnitDestination builds the appropriate absolute file path for
|
// UnitDestination builds the appropriate absolte file path for
|
||||||
// the given Unit. The root argument indicates the effective base
|
// the given unit. The root argument indicates the effective base
|
||||||
// directory of the system (similar to a chroot).
|
// directory of the system (similar to a chroot).
|
||||||
func UnitDestination(u *Unit, root string) string {
|
func UnitDestination(u *Unit, root string) string {
|
||||||
dir := "etc"
|
dir := "etc"
|
||||||
@@ -60,11 +51,7 @@ func UnitDestination(u *Unit, root string) string {
|
|||||||
dir = "run"
|
dir = "run"
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.DropIn {
|
return path.Join(root, dir, "systemd", u.Group(), u.Name)
|
||||||
return path.Join(root, dir, "systemd", u.Group(), fmt.Sprintf("%s.d", u.Name), cloudConfigDropIn)
|
|
||||||
} else {
|
|
||||||
return path.Join(root, dir, "systemd", u.Group(), u.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlaceUnit writes a unit file at the provided destination, creating
|
// PlaceUnit writes a unit file at the provided destination, creating
|
||||||
@@ -91,14 +78,14 @@ func PlaceUnit(u *Unit, dst string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func EnableUnitFile(unit string, runtime bool) error {
|
func EnableUnitFile(file string, runtime bool) error {
|
||||||
conn, err := dbus.New()
|
conn, err := dbus.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
units := []string{unit}
|
files := []string{file}
|
||||||
_, _, err = conn.EnableUnitFiles(units, runtime, true)
|
_, _, err = conn.EnableUnitFiles(files, runtime, true)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,17 +166,9 @@ func MachineID(root string) string {
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaskUnit masks a Unit by the given name by symlinking its unit file (in
|
|
||||||
// /etc/systemd/system) to /dev/null, analogous to `systemctl mask`
|
|
||||||
// N.B.: Unlike `systemctl mask`, this function will *remove any existing unit
|
|
||||||
// file* in /etc/systemd/system, to ensure that the mask will succeed.
|
|
||||||
func MaskUnit(unit string, root string) error {
|
func MaskUnit(unit string, root string) error {
|
||||||
masked := path.Join(root, "etc", "systemd", "system", unit)
|
masked := path.Join(root, "etc", "systemd", "system", unit)
|
||||||
if _, err := os.Stat(masked); os.IsNotExist(err) {
|
if err := os.MkdirAll(path.Dir(masked), os.FileMode(0755)); err != nil {
|
||||||
if err := os.MkdirAll(path.Dir(masked), os.FileMode(0755)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if err := os.Remove(masked); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return os.Symlink("/dev/null", masked)
|
return os.Symlink("/dev/null", masked)
|
||||||
|
@@ -60,30 +60,6 @@ Address=10.209.171.177/19
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnitDestination(t *testing.T) {
|
|
||||||
dir := "/some/dir"
|
|
||||||
name := "foobar.service"
|
|
||||||
|
|
||||||
u := Unit{
|
|
||||||
Name: name,
|
|
||||||
DropIn: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
dst := UnitDestination(&u, dir)
|
|
||||||
expectDst := path.Join(dir, "etc", "systemd", "system", "foobar.service")
|
|
||||||
if dst != expectDst {
|
|
||||||
t.Errorf("UnitDestination returned %s, expected %s", dst, expectDst)
|
|
||||||
}
|
|
||||||
|
|
||||||
u.DropIn = true
|
|
||||||
|
|
||||||
dst = UnitDestination(&u, dir)
|
|
||||||
expectDst = path.Join(dir, "etc", "systemd", "system", "foobar.service.d", cloudConfigDropIn)
|
|
||||||
if dst != expectDst {
|
|
||||||
t.Errorf("UnitDestination returned %s, expected %s", dst, expectDst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPlaceMountUnit(t *testing.T) {
|
func TestPlaceMountUnit(t *testing.T) {
|
||||||
u := Unit{
|
u := Unit{
|
||||||
Name: "media-state.mount",
|
Name: "media-state.mount",
|
||||||
@@ -147,40 +123,22 @@ func TestMachineID(t *testing.T) {
|
|||||||
t.Fatalf("File has incorrect contents")
|
t.Fatalf("File has incorrect contents")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMaskUnit(t *testing.T) {
|
func TestMaskUnit(t *testing.T) {
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
t.Fatalf("Unable to create tempdir: %v", err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
// Ensure mask works with units that do not currently exist
|
|
||||||
if err := MaskUnit("foo.service", dir); err != nil {
|
if err := MaskUnit("foo.service", dir); err != nil {
|
||||||
t.Fatalf("Unable to mask new unit: %v", err)
|
t.Fatalf("Unable to mask unit: %v", err)
|
||||||
}
|
|
||||||
fooPath := path.Join(dir, "etc", "systemd", "system", "foo.service")
|
|
||||||
fooTgt, err := os.Readlink(fooPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to read link", err)
|
|
||||||
}
|
|
||||||
if fooTgt != "/dev/null" {
|
|
||||||
t.Fatalf("unit not masked, got unit target", fooTgt)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure mask works with unit files that already exist
|
fullPath := path.Join(dir, "etc", "systemd", "system", "foo.service")
|
||||||
barPath := path.Join(dir, "etc", "systemd", "system", "bar.service")
|
target, err := os.Readlink(fullPath)
|
||||||
if _, err := os.Create(barPath); err != nil {
|
|
||||||
t.Fatalf("Error creating new unit file: %v", err)
|
|
||||||
}
|
|
||||||
if err := MaskUnit("bar.service", dir); err != nil {
|
|
||||||
t.Fatalf("Unable to mask existing unit: %v", err)
|
|
||||||
}
|
|
||||||
barTgt, err := os.Readlink(barPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to read link", err)
|
t.Fatalf("Unable to read link", err)
|
||||||
}
|
}
|
||||||
if barTgt != "/dev/null" {
|
if target != "/dev/null" {
|
||||||
t.Fatalf("unit not masked, got unit target", barTgt)
|
t.Fatalf("unit not masked, got unit target", target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
test
2
test
@@ -13,7 +13,7 @@ COVER=${COVER:-"-cover"}
|
|||||||
|
|
||||||
source ./build
|
source ./build
|
||||||
|
|
||||||
declare -a TESTPKGS=(initialize system datasource pkg network)
|
declare -a TESTPKGS=(initialize system datasource)
|
||||||
|
|
||||||
if [ -z "$PKG" ]; then
|
if [ -z "$PKG" ]; then
|
||||||
GOFMTPATH="$TESTPKGS coreos-cloudinit.go"
|
GOFMTPATH="$TESTPKGS coreos-cloudinit.go"
|
||||||
|
@@ -1,2 +0,0 @@
|
|||||||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
|
||||||
Guillaume J. Charmes <guillaume@docker.com> (@creack)
|
|
@@ -1,23 +0,0 @@
|
|||||||
// Packet netlink provide access to low level Netlink sockets and messages.
|
|
||||||
//
|
|
||||||
// Actual implementations are in:
|
|
||||||
// netlink_linux.go
|
|
||||||
// netlink_darwin.go
|
|
||||||
package netlink
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrWrongSockType = errors.New("Wrong socket type")
|
|
||||||
ErrShortResponse = errors.New("Got short response from netlink")
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Route is a subnet associated with the interface to reach it.
|
|
||||||
type Route struct {
|
|
||||||
*net.IPNet
|
|
||||||
Iface *net.Interface
|
|
||||||
Default bool
|
|
||||||
}
|
|
@@ -1,891 +0,0 @@
|
|||||||
// +build amd64
|
|
||||||
|
|
||||||
package netlink
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
IFNAMSIZ = 16
|
|
||||||
DEFAULT_CHANGE = 0xFFFFFFFF
|
|
||||||
IFLA_INFO_KIND = 1
|
|
||||||
IFLA_INFO_DATA = 2
|
|
||||||
VETH_INFO_PEER = 1
|
|
||||||
IFLA_NET_NS_FD = 28
|
|
||||||
SIOC_BRADDBR = 0x89a0
|
|
||||||
SIOC_BRADDIF = 0x89a2
|
|
||||||
)
|
|
||||||
|
|
||||||
var nextSeqNr int
|
|
||||||
|
|
||||||
type ifreqHwaddr struct {
|
|
||||||
IfrnName [16]byte
|
|
||||||
IfruHwaddr syscall.RawSockaddr
|
|
||||||
}
|
|
||||||
|
|
||||||
type ifreqIndex struct {
|
|
||||||
IfrnName [16]byte
|
|
||||||
IfruIndex int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func nativeEndian() binary.ByteOrder {
|
|
||||||
var x uint32 = 0x01020304
|
|
||||||
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
|
||||||
return binary.BigEndian
|
|
||||||
}
|
|
||||||
return binary.LittleEndian
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSeq() int {
|
|
||||||
nextSeqNr = nextSeqNr + 1
|
|
||||||
return nextSeqNr
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIpFamily(ip net.IP) int {
|
|
||||||
if len(ip) <= net.IPv4len {
|
|
||||||
return syscall.AF_INET
|
|
||||||
}
|
|
||||||
if ip.To4() != nil {
|
|
||||||
return syscall.AF_INET
|
|
||||||
}
|
|
||||||
return syscall.AF_INET6
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetlinkRequestData interface {
|
|
||||||
Len() int
|
|
||||||
ToWireFormat() []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type IfInfomsg struct {
|
|
||||||
syscall.IfInfomsg
|
|
||||||
}
|
|
||||||
|
|
||||||
func newIfInfomsg(family int) *IfInfomsg {
|
|
||||||
return &IfInfomsg{
|
|
||||||
IfInfomsg: syscall.IfInfomsg{
|
|
||||||
Family: uint8(family),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newIfInfomsgChild(parent *RtAttr, family int) *IfInfomsg {
|
|
||||||
msg := newIfInfomsg(family)
|
|
||||||
parent.children = append(parent.children, msg)
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (msg *IfInfomsg) ToWireFormat() []byte {
|
|
||||||
native := nativeEndian()
|
|
||||||
|
|
||||||
length := syscall.SizeofIfInfomsg
|
|
||||||
b := make([]byte, length)
|
|
||||||
b[0] = msg.Family
|
|
||||||
b[1] = 0
|
|
||||||
native.PutUint16(b[2:4], msg.Type)
|
|
||||||
native.PutUint32(b[4:8], uint32(msg.Index))
|
|
||||||
native.PutUint32(b[8:12], msg.Flags)
|
|
||||||
native.PutUint32(b[12:16], msg.Change)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (msg *IfInfomsg) Len() int {
|
|
||||||
return syscall.SizeofIfInfomsg
|
|
||||||
}
|
|
||||||
|
|
||||||
type IfAddrmsg struct {
|
|
||||||
syscall.IfAddrmsg
|
|
||||||
}
|
|
||||||
|
|
||||||
func newIfAddrmsg(family int) *IfAddrmsg {
|
|
||||||
return &IfAddrmsg{
|
|
||||||
IfAddrmsg: syscall.IfAddrmsg{
|
|
||||||
Family: uint8(family),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (msg *IfAddrmsg) ToWireFormat() []byte {
|
|
||||||
native := nativeEndian()
|
|
||||||
|
|
||||||
length := syscall.SizeofIfAddrmsg
|
|
||||||
b := make([]byte, length)
|
|
||||||
b[0] = msg.Family
|
|
||||||
b[1] = msg.Prefixlen
|
|
||||||
b[2] = msg.Flags
|
|
||||||
b[3] = msg.Scope
|
|
||||||
native.PutUint32(b[4:8], msg.Index)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (msg *IfAddrmsg) Len() int {
|
|
||||||
return syscall.SizeofIfAddrmsg
|
|
||||||
}
|
|
||||||
|
|
||||||
type RtMsg struct {
|
|
||||||
syscall.RtMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRtMsg(family int) *RtMsg {
|
|
||||||
return &RtMsg{
|
|
||||||
RtMsg: syscall.RtMsg{
|
|
||||||
Family: uint8(family),
|
|
||||||
Table: syscall.RT_TABLE_MAIN,
|
|
||||||
Scope: syscall.RT_SCOPE_UNIVERSE,
|
|
||||||
Protocol: syscall.RTPROT_BOOT,
|
|
||||||
Type: syscall.RTN_UNICAST,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (msg *RtMsg) ToWireFormat() []byte {
|
|
||||||
native := nativeEndian()
|
|
||||||
|
|
||||||
length := syscall.SizeofRtMsg
|
|
||||||
b := make([]byte, length)
|
|
||||||
b[0] = msg.Family
|
|
||||||
b[1] = msg.Dst_len
|
|
||||||
b[2] = msg.Src_len
|
|
||||||
b[3] = msg.Tos
|
|
||||||
b[4] = msg.Table
|
|
||||||
b[5] = msg.Protocol
|
|
||||||
b[6] = msg.Scope
|
|
||||||
b[7] = msg.Type
|
|
||||||
native.PutUint32(b[8:12], msg.Flags)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (msg *RtMsg) Len() int {
|
|
||||||
return syscall.SizeofRtMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
func rtaAlignOf(attrlen int) int {
|
|
||||||
return (attrlen + syscall.RTA_ALIGNTO - 1) & ^(syscall.RTA_ALIGNTO - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
type RtAttr struct {
|
|
||||||
syscall.RtAttr
|
|
||||||
Data []byte
|
|
||||||
children []NetlinkRequestData
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRtAttr(attrType int, data []byte) *RtAttr {
|
|
||||||
return &RtAttr{
|
|
||||||
RtAttr: syscall.RtAttr{
|
|
||||||
Type: uint16(attrType),
|
|
||||||
},
|
|
||||||
children: []NetlinkRequestData{},
|
|
||||||
Data: data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRtAttrChild(parent *RtAttr, attrType int, data []byte) *RtAttr {
|
|
||||||
attr := newRtAttr(attrType, data)
|
|
||||||
parent.children = append(parent.children, attr)
|
|
||||||
return attr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RtAttr) Len() int {
|
|
||||||
l := 0
|
|
||||||
for _, child := range a.children {
|
|
||||||
l += child.Len() + syscall.SizeofRtAttr
|
|
||||||
}
|
|
||||||
if l == 0 {
|
|
||||||
l++
|
|
||||||
}
|
|
||||||
return rtaAlignOf(l + len(a.Data))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RtAttr) ToWireFormat() []byte {
|
|
||||||
native := nativeEndian()
|
|
||||||
|
|
||||||
length := a.Len()
|
|
||||||
buf := make([]byte, rtaAlignOf(length+syscall.SizeofRtAttr))
|
|
||||||
|
|
||||||
if a.Data != nil {
|
|
||||||
copy(buf[4:], a.Data)
|
|
||||||
} else {
|
|
||||||
next := 4
|
|
||||||
for _, child := range a.children {
|
|
||||||
childBuf := child.ToWireFormat()
|
|
||||||
copy(buf[next:], childBuf)
|
|
||||||
next += rtaAlignOf(len(childBuf))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if l := uint16(rtaAlignOf(length)); l != 0 {
|
|
||||||
native.PutUint16(buf[0:2], l+1)
|
|
||||||
}
|
|
||||||
native.PutUint16(buf[2:4], a.Type)
|
|
||||||
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetlinkRequest struct {
|
|
||||||
syscall.NlMsghdr
|
|
||||||
Data []NetlinkRequestData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rr *NetlinkRequest) ToWireFormat() []byte {
|
|
||||||
native := nativeEndian()
|
|
||||||
|
|
||||||
length := rr.Len
|
|
||||||
dataBytes := make([][]byte, len(rr.Data))
|
|
||||||
for i, data := range rr.Data {
|
|
||||||
dataBytes[i] = data.ToWireFormat()
|
|
||||||
length += uint32(len(dataBytes[i]))
|
|
||||||
}
|
|
||||||
b := make([]byte, length)
|
|
||||||
native.PutUint32(b[0:4], length)
|
|
||||||
native.PutUint16(b[4:6], rr.Type)
|
|
||||||
native.PutUint16(b[6:8], rr.Flags)
|
|
||||||
native.PutUint32(b[8:12], rr.Seq)
|
|
||||||
native.PutUint32(b[12:16], rr.Pid)
|
|
||||||
|
|
||||||
next := 16
|
|
||||||
for _, data := range dataBytes {
|
|
||||||
copy(b[next:], data)
|
|
||||||
next += len(data)
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rr *NetlinkRequest) AddData(data NetlinkRequestData) {
|
|
||||||
if data != nil {
|
|
||||||
rr.Data = append(rr.Data, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newNetlinkRequest(proto, flags int) *NetlinkRequest {
|
|
||||||
return &NetlinkRequest{
|
|
||||||
NlMsghdr: syscall.NlMsghdr{
|
|
||||||
Len: uint32(syscall.NLMSG_HDRLEN),
|
|
||||||
Type: uint16(proto),
|
|
||||||
Flags: syscall.NLM_F_REQUEST | uint16(flags),
|
|
||||||
Seq: uint32(getSeq()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetlinkSocket struct {
|
|
||||||
fd int
|
|
||||||
lsa syscall.SockaddrNetlink
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNetlinkSocket() (*NetlinkSocket, error) {
|
|
||||||
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_ROUTE)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s := &NetlinkSocket{
|
|
||||||
fd: fd,
|
|
||||||
}
|
|
||||||
s.lsa.Family = syscall.AF_NETLINK
|
|
||||||
if err := syscall.Bind(fd, &s.lsa); err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *NetlinkSocket) Close() {
|
|
||||||
syscall.Close(s.fd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *NetlinkSocket) Send(request *NetlinkRequest) error {
|
|
||||||
if err := syscall.Sendto(s.fd, request.ToWireFormat(), 0, &s.lsa); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *NetlinkSocket) Receive() ([]syscall.NetlinkMessage, error) {
|
|
||||||
rb := make([]byte, syscall.Getpagesize())
|
|
||||||
nr, _, err := syscall.Recvfrom(s.fd, rb, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if nr < syscall.NLMSG_HDRLEN {
|
|
||||||
return nil, ErrShortResponse
|
|
||||||
}
|
|
||||||
rb = rb[:nr]
|
|
||||||
return syscall.ParseNetlinkMessage(rb)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *NetlinkSocket) GetPid() (uint32, error) {
|
|
||||||
lsa, err := syscall.Getsockname(s.fd)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
switch v := lsa.(type) {
|
|
||||||
case *syscall.SockaddrNetlink:
|
|
||||||
return v.Pid, nil
|
|
||||||
}
|
|
||||||
return 0, ErrWrongSockType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *NetlinkSocket) HandleAck(seq uint32) error {
|
|
||||||
native := nativeEndian()
|
|
||||||
|
|
||||||
pid, err := s.GetPid()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
done:
|
|
||||||
for {
|
|
||||||
msgs, err := s.Receive()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, m := range msgs {
|
|
||||||
if m.Header.Seq != seq {
|
|
||||||
return fmt.Errorf("Wrong Seq nr %d, expected %d", m.Header.Seq, seq)
|
|
||||||
}
|
|
||||||
if m.Header.Pid != pid {
|
|
||||||
return fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
|
|
||||||
}
|
|
||||||
if m.Header.Type == syscall.NLMSG_DONE {
|
|
||||||
break done
|
|
||||||
}
|
|
||||||
if m.Header.Type == syscall.NLMSG_ERROR {
|
|
||||||
error := int32(native.Uint32(m.Data[0:4]))
|
|
||||||
if error == 0 {
|
|
||||||
break done
|
|
||||||
}
|
|
||||||
return syscall.Errno(-error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a new default gateway. Identical to:
|
|
||||||
// ip route add default via $ip
|
|
||||||
func AddDefaultGw(ip net.IP) error {
|
|
||||||
s, err := getNetlinkSocket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
family := getIpFamily(ip)
|
|
||||||
|
|
||||||
wb := newNetlinkRequest(syscall.RTM_NEWROUTE, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
|
|
||||||
|
|
||||||
msg := newRtMsg(family)
|
|
||||||
wb.AddData(msg)
|
|
||||||
|
|
||||||
var ipData []byte
|
|
||||||
if family == syscall.AF_INET {
|
|
||||||
ipData = ip.To4()
|
|
||||||
} else {
|
|
||||||
ipData = ip.To16()
|
|
||||||
}
|
|
||||||
|
|
||||||
gateway := newRtAttr(syscall.RTA_GATEWAY, ipData)
|
|
||||||
|
|
||||||
wb.AddData(gateway)
|
|
||||||
|
|
||||||
if err := s.Send(wb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.HandleAck(wb.Seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bring up a particular network interface
|
|
||||||
func NetworkLinkUp(iface *net.Interface) error {
|
|
||||||
s, err := getNetlinkSocket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_ACK)
|
|
||||||
|
|
||||||
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
|
||||||
msg.Change = syscall.IFF_UP
|
|
||||||
msg.Flags = syscall.IFF_UP
|
|
||||||
msg.Index = int32(iface.Index)
|
|
||||||
wb.AddData(msg)
|
|
||||||
|
|
||||||
if err := s.Send(wb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.HandleAck(wb.Seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkLinkDown(iface *net.Interface) error {
|
|
||||||
s, err := getNetlinkSocket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_ACK)
|
|
||||||
|
|
||||||
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
|
||||||
msg.Change = syscall.IFF_UP
|
|
||||||
msg.Flags = 0 & ^syscall.IFF_UP
|
|
||||||
msg.Index = int32(iface.Index)
|
|
||||||
wb.AddData(msg)
|
|
||||||
|
|
||||||
if err := s.Send(wb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.HandleAck(wb.Seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkSetMTU(iface *net.Interface, mtu int) error {
|
|
||||||
s, err := getNetlinkSocket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
|
|
||||||
|
|
||||||
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
|
||||||
msg.Type = syscall.RTM_SETLINK
|
|
||||||
msg.Flags = syscall.NLM_F_REQUEST
|
|
||||||
msg.Index = int32(iface.Index)
|
|
||||||
msg.Change = DEFAULT_CHANGE
|
|
||||||
wb.AddData(msg)
|
|
||||||
|
|
||||||
var (
|
|
||||||
b = make([]byte, 4)
|
|
||||||
native = nativeEndian()
|
|
||||||
)
|
|
||||||
native.PutUint32(b, uint32(mtu))
|
|
||||||
|
|
||||||
data := newRtAttr(syscall.IFLA_MTU, b)
|
|
||||||
wb.AddData(data)
|
|
||||||
|
|
||||||
if err := s.Send(wb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.HandleAck(wb.Seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
// same as ip link set $name master $master
|
|
||||||
func NetworkSetMaster(iface, master *net.Interface) error {
|
|
||||||
s, err := getNetlinkSocket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
|
|
||||||
|
|
||||||
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
|
||||||
msg.Type = syscall.RTM_SETLINK
|
|
||||||
msg.Flags = syscall.NLM_F_REQUEST
|
|
||||||
msg.Index = int32(iface.Index)
|
|
||||||
msg.Change = DEFAULT_CHANGE
|
|
||||||
wb.AddData(msg)
|
|
||||||
|
|
||||||
var (
|
|
||||||
b = make([]byte, 4)
|
|
||||||
native = nativeEndian()
|
|
||||||
)
|
|
||||||
native.PutUint32(b, uint32(master.Index))
|
|
||||||
|
|
||||||
data := newRtAttr(syscall.IFLA_MASTER, b)
|
|
||||||
wb.AddData(data)
|
|
||||||
|
|
||||||
if err := s.Send(wb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.HandleAck(wb.Seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkSetNsPid(iface *net.Interface, nspid int) error {
|
|
||||||
s, err := getNetlinkSocket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
|
|
||||||
|
|
||||||
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
|
||||||
msg.Type = syscall.RTM_SETLINK
|
|
||||||
msg.Flags = syscall.NLM_F_REQUEST
|
|
||||||
msg.Index = int32(iface.Index)
|
|
||||||
msg.Change = DEFAULT_CHANGE
|
|
||||||
wb.AddData(msg)
|
|
||||||
|
|
||||||
var (
|
|
||||||
b = make([]byte, 4)
|
|
||||||
native = nativeEndian()
|
|
||||||
)
|
|
||||||
native.PutUint32(b, uint32(nspid))
|
|
||||||
|
|
||||||
data := newRtAttr(syscall.IFLA_NET_NS_PID, b)
|
|
||||||
wb.AddData(data)
|
|
||||||
|
|
||||||
if err := s.Send(wb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.HandleAck(wb.Seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkSetNsFd(iface *net.Interface, fd int) error {
|
|
||||||
s, err := getNetlinkSocket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
|
|
||||||
|
|
||||||
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
|
||||||
msg.Type = syscall.RTM_SETLINK
|
|
||||||
msg.Flags = syscall.NLM_F_REQUEST
|
|
||||||
msg.Index = int32(iface.Index)
|
|
||||||
msg.Change = DEFAULT_CHANGE
|
|
||||||
wb.AddData(msg)
|
|
||||||
|
|
||||||
var (
|
|
||||||
b = make([]byte, 4)
|
|
||||||
native = nativeEndian()
|
|
||||||
)
|
|
||||||
native.PutUint32(b, uint32(fd))
|
|
||||||
|
|
||||||
data := newRtAttr(IFLA_NET_NS_FD, b)
|
|
||||||
wb.AddData(data)
|
|
||||||
|
|
||||||
if err := s.Send(wb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.HandleAck(wb.Seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an Ip address to an interface. This is identical to:
|
|
||||||
// ip addr add $ip/$ipNet dev $iface
|
|
||||||
func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
|
||||||
s, err := getNetlinkSocket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
family := getIpFamily(ip)
|
|
||||||
|
|
||||||
wb := newNetlinkRequest(syscall.RTM_NEWADDR, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
|
|
||||||
|
|
||||||
msg := newIfAddrmsg(family)
|
|
||||||
msg.Index = uint32(iface.Index)
|
|
||||||
prefixLen, _ := ipNet.Mask.Size()
|
|
||||||
msg.Prefixlen = uint8(prefixLen)
|
|
||||||
wb.AddData(msg)
|
|
||||||
|
|
||||||
var ipData []byte
|
|
||||||
if family == syscall.AF_INET {
|
|
||||||
ipData = ip.To4()
|
|
||||||
} else {
|
|
||||||
ipData = ip.To16()
|
|
||||||
}
|
|
||||||
|
|
||||||
localData := newRtAttr(syscall.IFA_LOCAL, ipData)
|
|
||||||
wb.AddData(localData)
|
|
||||||
|
|
||||||
addrData := newRtAttr(syscall.IFA_ADDRESS, ipData)
|
|
||||||
wb.AddData(addrData)
|
|
||||||
|
|
||||||
if err := s.Send(wb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.HandleAck(wb.Seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
func zeroTerminated(s string) []byte {
|
|
||||||
return []byte(s + "\000")
|
|
||||||
}
|
|
||||||
|
|
||||||
func nonZeroTerminated(s string) []byte {
|
|
||||||
return []byte(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a new network link of a specified type. This is identical to
|
|
||||||
// running: ip add link $name type $linkType
|
|
||||||
func NetworkLinkAdd(name string, linkType string) error {
|
|
||||||
s, err := getNetlinkSocket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
|
|
||||||
|
|
||||||
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
|
||||||
wb.AddData(msg)
|
|
||||||
|
|
||||||
if name != "" {
|
|
||||||
nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name))
|
|
||||||
wb.AddData(nameData)
|
|
||||||
}
|
|
||||||
|
|
||||||
kindData := newRtAttr(IFLA_INFO_KIND, nonZeroTerminated(linkType))
|
|
||||||
|
|
||||||
infoData := newRtAttr(syscall.IFLA_LINKINFO, kindData.ToWireFormat())
|
|
||||||
wb.AddData(infoData)
|
|
||||||
|
|
||||||
if err := s.Send(wb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.HandleAck(wb.Seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns an array of IPNet for all the currently routed subnets on ipv4
|
|
||||||
// This is similar to the first column of "ip route" output
|
|
||||||
func NetworkGetRoutes() ([]Route, error) {
|
|
||||||
native := nativeEndian()
|
|
||||||
|
|
||||||
s, err := getNetlinkSocket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
wb := newNetlinkRequest(syscall.RTM_GETROUTE, syscall.NLM_F_DUMP)
|
|
||||||
|
|
||||||
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
|
||||||
wb.AddData(msg)
|
|
||||||
|
|
||||||
if err := s.Send(wb); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pid, err := s.GetPid()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res := make([]Route, 0)
|
|
||||||
|
|
||||||
done:
|
|
||||||
for {
|
|
||||||
msgs, err := s.Receive()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, m := range msgs {
|
|
||||||
if m.Header.Seq != wb.Seq {
|
|
||||||
return nil, fmt.Errorf("Wrong Seq nr %d, expected 1", m.Header.Seq)
|
|
||||||
}
|
|
||||||
if m.Header.Pid != pid {
|
|
||||||
return nil, fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
|
|
||||||
}
|
|
||||||
if m.Header.Type == syscall.NLMSG_DONE {
|
|
||||||
break done
|
|
||||||
}
|
|
||||||
if m.Header.Type == syscall.NLMSG_ERROR {
|
|
||||||
error := int32(native.Uint32(m.Data[0:4]))
|
|
||||||
if error == 0 {
|
|
||||||
break done
|
|
||||||
}
|
|
||||||
return nil, syscall.Errno(-error)
|
|
||||||
}
|
|
||||||
if m.Header.Type != syscall.RTM_NEWROUTE {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var r Route
|
|
||||||
|
|
||||||
msg := (*RtMsg)(unsafe.Pointer(&m.Data[0:syscall.SizeofRtMsg][0]))
|
|
||||||
|
|
||||||
if msg.Flags&syscall.RTM_F_CLONED != 0 {
|
|
||||||
// Ignore cloned routes
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Table != syscall.RT_TABLE_MAIN {
|
|
||||||
// Ignore non-main tables
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Family != syscall.AF_INET {
|
|
||||||
// Ignore non-ipv4 routes
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Dst_len == 0 {
|
|
||||||
// Default routes
|
|
||||||
r.Default = true
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, attr := range attrs {
|
|
||||||
switch attr.Attr.Type {
|
|
||||||
case syscall.RTA_DST:
|
|
||||||
ip := attr.Value
|
|
||||||
r.IPNet = &net.IPNet{
|
|
||||||
IP: ip,
|
|
||||||
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(ip)),
|
|
||||||
}
|
|
||||||
case syscall.RTA_OIF:
|
|
||||||
index := int(native.Uint32(attr.Value[0:4]))
|
|
||||||
r.Iface, _ = net.InterfaceByIndex(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.Default || r.IPNet != nil {
|
|
||||||
res = append(res, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIfSocket() (fd int, err error) {
|
|
||||||
for _, socket := range []int{
|
|
||||||
syscall.AF_INET,
|
|
||||||
syscall.AF_PACKET,
|
|
||||||
syscall.AF_INET6,
|
|
||||||
} {
|
|
||||||
if fd, err = syscall.Socket(socket, syscall.SOCK_DGRAM, 0); err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
return fd, nil
|
|
||||||
}
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkChangeName(iface *net.Interface, newName string) error {
|
|
||||||
fd, err := getIfSocket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer syscall.Close(fd)
|
|
||||||
|
|
||||||
data := [IFNAMSIZ * 2]byte{}
|
|
||||||
// the "-1"s here are very important for ensuring we get proper null
|
|
||||||
// termination of our new C strings
|
|
||||||
copy(data[:IFNAMSIZ-1], iface.Name)
|
|
||||||
copy(data[IFNAMSIZ:IFNAMSIZ*2-1], newName)
|
|
||||||
|
|
||||||
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&data[0]))); errno != 0 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkCreateVethPair(name1, name2 string) error {
|
|
||||||
s, err := getNetlinkSocket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
|
|
||||||
|
|
||||||
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
|
||||||
wb.AddData(msg)
|
|
||||||
|
|
||||||
nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name1))
|
|
||||||
wb.AddData(nameData)
|
|
||||||
|
|
||||||
nest1 := newRtAttr(syscall.IFLA_LINKINFO, nil)
|
|
||||||
newRtAttrChild(nest1, IFLA_INFO_KIND, zeroTerminated("veth"))
|
|
||||||
nest2 := newRtAttrChild(nest1, IFLA_INFO_DATA, nil)
|
|
||||||
nest3 := newRtAttrChild(nest2, VETH_INFO_PEER, nil)
|
|
||||||
|
|
||||||
newIfInfomsgChild(nest3, syscall.AF_UNSPEC)
|
|
||||||
newRtAttrChild(nest3, syscall.IFLA_IFNAME, zeroTerminated(name2))
|
|
||||||
|
|
||||||
wb.AddData(nest1)
|
|
||||||
|
|
||||||
if err := s.Send(wb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.HandleAck(wb.Seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the actual bridge device. This is more backward-compatible than
|
|
||||||
// netlink.NetworkLinkAdd and works on RHEL 6.
|
|
||||||
func CreateBridge(name string, setMacAddr bool) error {
|
|
||||||
s, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_IP)
|
|
||||||
if err != nil {
|
|
||||||
// ipv6 issue, creating with ipv4
|
|
||||||
s, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_IP)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer syscall.Close(s)
|
|
||||||
|
|
||||||
nameBytePtr, err := syscall.BytePtrFromString(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), SIOC_BRADDBR, uintptr(unsafe.Pointer(nameBytePtr))); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if setMacAddr {
|
|
||||||
return setBridgeMacAddress(s, name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a slave to abridge device. This is more backward-compatible than
|
|
||||||
// netlink.NetworkSetMaster and works on RHEL 6.
|
|
||||||
func AddToBridge(iface, master *net.Interface) error {
|
|
||||||
s, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_IP)
|
|
||||||
if err != nil {
|
|
||||||
// ipv6 issue, creating with ipv4
|
|
||||||
s, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_IP)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer syscall.Close(s)
|
|
||||||
|
|
||||||
ifr := ifreqIndex{}
|
|
||||||
copy(ifr.IfrnName[:], master.Name)
|
|
||||||
ifr.IfruIndex = int32(iface.Index)
|
|
||||||
|
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), SIOC_BRADDIF, uintptr(unsafe.Pointer(&ifr))); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setBridgeMacAddress(s int, name string) error {
|
|
||||||
ifr := ifreqHwaddr{}
|
|
||||||
ifr.IfruHwaddr.Family = syscall.ARPHRD_ETHER
|
|
||||||
copy(ifr.IfrnName[:], name)
|
|
||||||
|
|
||||||
for i := 0; i < 6; i++ {
|
|
||||||
ifr.IfruHwaddr.Data[i] = int8(rand.Intn(255))
|
|
||||||
}
|
|
||||||
|
|
||||||
ifr.IfruHwaddr.Data[0] &^= 0x1 // clear multicast bit
|
|
||||||
ifr.IfruHwaddr.Data[0] |= 0x2 // set local assignment bit (IEEE802)
|
|
||||||
|
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), syscall.SIOCSIFHWADDR, uintptr(unsafe.Pointer(&ifr))); err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -1,69 +0,0 @@
|
|||||||
// +build !linux !amd64
|
|
||||||
|
|
||||||
package netlink
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNotImplemented = errors.New("not implemented")
|
|
||||||
)
|
|
||||||
|
|
||||||
func NetworkGetRoutes() ([]Route, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkLinkAdd(name string, linkType string) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkLinkUp(iface *net.Interface) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddDefaultGw(ip net.IP) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkSetMTU(iface *net.Interface, mtu int) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkCreateVethPair(name1, name2 string) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkChangeName(iface *net.Interface, newName string) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkSetNsFd(iface *net.Interface, fd int) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkSetNsPid(iface *net.Interface, nspid int) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkSetMaster(iface, master *net.Interface) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func NetworkLinkDown(iface *net.Interface) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateBridge(name string, setMacAddr bool) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddToBridge(iface, master *net.Interface) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
@@ -5,6 +5,10 @@ ConditionPathIsMountPoint=!/media/configdrive
|
|||||||
# Only mount config drive block devices automatically in virtual machines
|
# Only mount config drive block devices automatically in virtual machines
|
||||||
ConditionVirtualization=vm
|
ConditionVirtualization=vm
|
||||||
|
|
||||||
|
# OpenStack defined config drive so they get to stick their name in it
|
||||||
|
Wants=user-cloudinit@media-configdrive-openstack-latest-user_data.service
|
||||||
|
Before=user-cloudinit@media-configdrive-openstack-latest-user_data.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
RemainAfterExit=no
|
RemainAfterExit=no
|
||||||
|
@@ -4,6 +4,10 @@ Conflicts=configdrive-block.service umount.target
|
|||||||
ConditionPathIsMountPoint=!/media/configdrive
|
ConditionPathIsMountPoint=!/media/configdrive
|
||||||
ConditionVirtualization=vm
|
ConditionVirtualization=vm
|
||||||
|
|
||||||
|
# OpenStack defined config drive so they get to stick their name in it
|
||||||
|
Wants=user-cloudinit@media-configdrive-openstack-latest-user_data.service
|
||||||
|
Before=user-cloudinit@media-configdrive-openstack-latest-user_data.service
|
||||||
|
|
||||||
# Support old style setup for now
|
# Support old style setup for now
|
||||||
Wants=addon-run@media-configdrive.service addon-config@media-configdrive.service
|
Wants=addon-run@media-configdrive.service addon-config@media-configdrive.service
|
||||||
Before=addon-run@media-configdrive.service addon-config@media-configdrive.service
|
Before=addon-run@media-configdrive.service addon-config@media-configdrive.service
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Watch for a cloud-config at %f
|
|
||||||
|
|
||||||
[Path]
|
|
||||||
PathExists=%f
|
|
@@ -1,8 +1,9 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Load cloud-config from /media/configdrive
|
Description=Load cloud-config from %f
|
||||||
Requires=coreos-setup-environment.service
|
Requires=coreos-setup-environment.service
|
||||||
After=coreos-setup-environment.service
|
After=coreos-setup-environment.service
|
||||||
Before=user-config.target
|
Before=user-config.target
|
||||||
|
ConditionFileNotEmpty=%f
|
||||||
|
|
||||||
# HACK: work around ordering between config drive and ec2 metadata It is
|
# HACK: work around ordering between config drive and ec2 metadata It is
|
||||||
# possible for OpenStack style systems to provide both the metadata service
|
# possible for OpenStack style systems to provide both the metadata service
|
||||||
@@ -20,4 +21,4 @@ After=ec2-cloudinit.service
|
|||||||
Type=oneshot
|
Type=oneshot
|
||||||
RemainAfterExit=yes
|
RemainAfterExit=yes
|
||||||
EnvironmentFile=-/etc/environment
|
EnvironmentFile=-/etc/environment
|
||||||
ExecStart=/usr/bin/coreos-cloudinit --from-configdrive=/media/configdrive
|
ExecStart=/usr/bin/coreos-cloudinit --from-file=%f
|
@@ -3,11 +3,9 @@ Description=Load user-provided cloud configs
|
|||||||
Requires=system-config.target
|
Requires=system-config.target
|
||||||
After=system-config.target
|
After=system-config.target
|
||||||
|
|
||||||
# Watch for configs at a couple common paths
|
# Load user_data placed by coreos-install
|
||||||
Requires=user-configdrive.path
|
Requires=user-cloudinit@var-lib-coreos\x2dinstall-user_data.service
|
||||||
After=user-configdrive.path
|
After=user-cloudinit@var-lib-coreos\x2dinstall-user_data.service
|
||||||
Requires=user-cloudinit@var-lib-coreos\x2dinstall-user_data.path
|
|
||||||
After=user-cloudinit@var-lib-coreos\x2dinstall-user_data.path
|
|
||||||
|
|
||||||
Requires=user-cloudinit-proc-cmdline.service
|
Requires=user-cloudinit-proc-cmdline.service
|
||||||
After=user-cloudinit-proc-cmdline.service
|
After=user-cloudinit-proc-cmdline.service
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Watch for a cloud-config at /media/configdrive
|
|
||||||
|
|
||||||
[Path]
|
|
||||||
DirectoryNotEmpty=/media/configdrive
|
|
Reference in New Issue
Block a user