Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
685a38c6c8 | ||
|
9d15f2cfaf | ||
|
2134fce791 | ||
|
3abd6b2225 | ||
|
2a8e6c9566 | ||
|
abe43537da | ||
|
3a550af651 | ||
|
61c3a0eb2d | ||
|
480176bc11 | ||
|
01b18eb551 | ||
|
970ef435b6 | ||
|
e8d0021140 | ||
|
e9ec78ac6f | ||
|
4a2e417781 | ||
|
604ef7ecb4 | ||
|
c39dd5cc67 | ||
|
a923161f4a | ||
|
e59e2f6cd5 | ||
|
e90fe3eba8 | ||
|
fb0187b197 | ||
|
6babe74716 | ||
|
b1e88284ca | ||
|
18a65f7dac | ||
|
0c212c72c9 | ||
|
6a800d8cc0 | ||
|
5e112147bb | ||
|
7e78b1563f | ||
|
ecbe81f103 | ||
|
45c20c1dd3 | ||
|
8ce925a060 | ||
|
7320a2cbf2 | ||
|
57950b3ed9 | ||
|
85c6a2a16a |
@@ -1,5 +1,7 @@
|
|||||||
language: go
|
language: go
|
||||||
go: 1.2
|
go:
|
||||||
|
- 1.3
|
||||||
|
- 1.2
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- go get code.google.com/p/go.tools/cmd/cover
|
- go get code.google.com/p/go.tools/cmd/cover
|
||||||
|
@@ -95,7 +95,7 @@ Environment="FLEET_METADATA=region=us-west"
|
|||||||
|
|
||||||
For more information on fleet configuration, see the [fleet documentation][fleet-config].
|
For more information on fleet configuration, see the [fleet documentation][fleet-config].
|
||||||
|
|
||||||
[fleet-config]: https://github.com/coreos/fleet/blob/master/Documentation/configuration.md
|
[fleet-config]: https://github.com/coreos/fleet/blob/master/Documentation/deployment-and-configuration.md
|
||||||
|
|
||||||
#### update
|
#### update
|
||||||
|
|
||||||
@@ -298,7 +298,8 @@ users:
|
|||||||
|
|
||||||
### write_files
|
### write_files
|
||||||
|
|
||||||
The `write-file` parameter defines a list of files to create on the local filesystem. Each file is represented as an associative array which has the following keys:
|
The `write_files` directive defines a set of files to create on the local filesystem.
|
||||||
|
Each item in the list may have the following keys:
|
||||||
|
|
||||||
- **path**: Absolute location on disk where contents should be written
|
- **path**: Absolute location on disk where contents should be written
|
||||||
- **content**: Data to write at the provided `path`
|
- **content**: Data to write at the provided `path`
|
||||||
@@ -311,11 +312,16 @@ The **content** field must represent exactly what should be written to disk.
|
|||||||
```yaml
|
```yaml
|
||||||
#cloud-config
|
#cloud-config
|
||||||
write_files:
|
write_files:
|
||||||
- path: /etc/fleet/fleet.conf
|
- path: /etc/resolv.conf
|
||||||
permissions: 0644
|
permissions: 0644
|
||||||
|
owner: root
|
||||||
content: |
|
content: |
|
||||||
verbosity=1
|
nameserver 8.8.8.8
|
||||||
metadata="region=us-west,type=ssd"
|
- path: /etc/motd
|
||||||
|
permissions: 0644
|
||||||
|
owner: root
|
||||||
|
content: |
|
||||||
|
Good news, everyone!
|
||||||
```
|
```
|
||||||
|
|
||||||
### manage_etc_hosts
|
### manage_etc_hosts
|
||||||
|
@@ -10,6 +10,8 @@ import (
|
|||||||
"github.com/coreos/coreos-cloudinit/datasource"
|
"github.com/coreos/coreos-cloudinit/datasource"
|
||||||
"github.com/coreos/coreos-cloudinit/datasource/configdrive"
|
"github.com/coreos/coreos-cloudinit/datasource/configdrive"
|
||||||
"github.com/coreos/coreos-cloudinit/datasource/file"
|
"github.com/coreos/coreos-cloudinit/datasource/file"
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata/cloudsigma"
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
|
||||||
"github.com/coreos/coreos-cloudinit/datasource/metadata/ec2"
|
"github.com/coreos/coreos-cloudinit/datasource/metadata/ec2"
|
||||||
"github.com/coreos/coreos-cloudinit/datasource/proc_cmdline"
|
"github.com/coreos/coreos-cloudinit/datasource/proc_cmdline"
|
||||||
"github.com/coreos/coreos-cloudinit/datasource/url"
|
"github.com/coreos/coreos-cloudinit/datasource/url"
|
||||||
@@ -19,7 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "0.9.3"
|
version = "0.9.5"
|
||||||
datasourceInterval = 100 * time.Millisecond
|
datasourceInterval = 100 * time.Millisecond
|
||||||
datasourceMaxInterval = 30 * time.Second
|
datasourceMaxInterval = 30 * time.Second
|
||||||
datasourceTimeout = 5 * time.Minute
|
datasourceTimeout = 5 * time.Minute
|
||||||
@@ -33,6 +35,8 @@ var (
|
|||||||
configDrive string
|
configDrive string
|
||||||
metadataService bool
|
metadataService bool
|
||||||
ec2MetadataService string
|
ec2MetadataService string
|
||||||
|
cloudSigmaMetadataService bool
|
||||||
|
digitalOceanMetadataService string
|
||||||
url string
|
url string
|
||||||
procCmdLine bool
|
procCmdLine bool
|
||||||
}
|
}
|
||||||
@@ -47,10 +51,12 @@ func init() {
|
|||||||
flag.StringVar(&sources.file, "from-file", "", "Read user-data from provided file")
|
flag.StringVar(&sources.file, "from-file", "", "Read user-data from provided file")
|
||||||
flag.StringVar(&sources.configDrive, "from-configdrive", "", "Read data from provided cloud-drive directory")
|
flag.StringVar(&sources.configDrive, "from-configdrive", "", "Read data from provided cloud-drive directory")
|
||||||
flag.BoolVar(&sources.metadataService, "from-metadata-service", false, "[DEPRECATED - Use -from-ec2-metadata] Download data from metadata service")
|
flag.BoolVar(&sources.metadataService, "from-metadata-service", false, "[DEPRECATED - Use -from-ec2-metadata] Download data from metadata service")
|
||||||
flag.StringVar(&sources.ec2MetadataService, "from-ec2-metadata", "", "Download data from the provided metadata service")
|
flag.StringVar(&sources.ec2MetadataService, "from-ec2-metadata", "", "Download EC2 data from the provided url")
|
||||||
|
flag.BoolVar(&sources.cloudSigmaMetadataService, "from-cloudsigma-metadata", false, "Download data from CloudSigma server context")
|
||||||
|
flag.StringVar(&sources.digitalOceanMetadataService, "from-digitalocean-metadata", "", "Download DigitalOcean data from the provided url")
|
||||||
flag.StringVar(&sources.url, "from-url", "", "Download user-data from provided url")
|
flag.StringVar(&sources.url, "from-url", "", "Download user-data from provided url")
|
||||||
flag.BoolVar(&sources.procCmdLine, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", proc_cmdline.ProcCmdlineLocation, proc_cmdline.ProcCmdlineCloudConfigFlag))
|
flag.BoolVar(&sources.procCmdLine, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", proc_cmdline.ProcCmdlineLocation, proc_cmdline.ProcCmdlineCloudConfigFlag))
|
||||||
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)")
|
flag.StringVar(&convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files")
|
||||||
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")
|
||||||
flag.StringVar(&sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name")
|
flag.StringVar(&sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name")
|
||||||
}
|
}
|
||||||
@@ -70,22 +76,18 @@ func main() {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if convertNetconf != "" && sources.configDrive == "" {
|
|
||||||
fmt.Println("-convert-netconf flag requires -from-configdrive")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch convertNetconf {
|
switch convertNetconf {
|
||||||
case "":
|
case "":
|
||||||
case "debian":
|
case "debian":
|
||||||
|
case "digitalocean":
|
||||||
default:
|
default:
|
||||||
fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian'\n", convertNetconf)
|
fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian, digitalocean'\n", convertNetconf)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
dss := getDatasources()
|
dss := getDatasources()
|
||||||
if len(dss) == 0 {
|
if len(dss) == 0 {
|
||||||
fmt.Println("Provide at least one of --from-file, --from-configdrive, --from-ec2-metadata, --from-url or --from-proc-cmdline")
|
fmt.Println("Provide at least one of --from-file, --from-configdrive, --from-ec2-metadata, --from-cloudsigma-metadata, --from-url or --from-proc-cmdline")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +131,17 @@ func main() {
|
|||||||
fmt.Printf("Failed to parse meta-data: %v\n", err)
|
fmt.Printf("Failed to parse meta-data: %v\n", err)
|
||||||
die()
|
die()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ccm != nil {
|
||||||
|
fmt.Printf("Fetching network config from datasource of type %q\n", ds.Type())
|
||||||
|
netconfBytes, err := ds.FetchNetworkConfig(ccm.NetworkConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed fetching network config from datasource: %v\n", err)
|
||||||
|
die()
|
||||||
|
}
|
||||||
|
ccm.NetworkConfig = string(netconfBytes)
|
||||||
|
}
|
||||||
|
|
||||||
if ud, err := initialize.ParseUserData(userdata); err != nil {
|
if ud, err := initialize.ParseUserData(userdata); err != nil {
|
||||||
fmt.Printf("Failed to parse user-data: %v\n", err)
|
fmt.Printf("Failed to parse user-data: %v\n", err)
|
||||||
die()
|
die()
|
||||||
@@ -195,6 +208,13 @@ func mergeCloudConfig(mdcc, udcc initialize.CloudConfig) (cc initialize.CloudCon
|
|||||||
udcc.NetworkConfigPath = mdcc.NetworkConfigPath
|
udcc.NetworkConfigPath = mdcc.NetworkConfigPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if mdcc.NetworkConfig != "" {
|
||||||
|
if udcc.NetworkConfig != "" {
|
||||||
|
fmt.Printf("Warning: user-data NetworkConfig %s overrides metadata NetworkConfig %s\n", udcc.NetworkConfig, mdcc.NetworkConfig)
|
||||||
|
} else {
|
||||||
|
udcc.NetworkConfig = mdcc.NetworkConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
return udcc
|
return udcc
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +237,12 @@ func getDatasources() []datasource.Datasource {
|
|||||||
if sources.ec2MetadataService != "" {
|
if sources.ec2MetadataService != "" {
|
||||||
dss = append(dss, ec2.NewDatasource(sources.ec2MetadataService))
|
dss = append(dss, ec2.NewDatasource(sources.ec2MetadataService))
|
||||||
}
|
}
|
||||||
|
if sources.cloudSigmaMetadataService {
|
||||||
|
dss = append(dss, cloudsigma.NewServerContextService())
|
||||||
|
}
|
||||||
|
if sources.digitalOceanMetadataService != "" {
|
||||||
|
dss = append(dss, digitalocean.NewDatasource(sources.digitalOceanMetadataService))
|
||||||
|
}
|
||||||
if sources.procCmdLine {
|
if sources.procCmdLine {
|
||||||
dss = append(dss, proc_cmdline.NewDatasource())
|
dss = append(dss, proc_cmdline.NewDatasource())
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,7 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
SSHAuthorizedKeys: []string{"abc", "def"},
|
SSHAuthorizedKeys: []string{"abc", "def"},
|
||||||
Hostname: "foobar",
|
Hostname: "foobar",
|
||||||
NetworkConfigPath: "/path/somewhere",
|
NetworkConfigPath: "/path/somewhere",
|
||||||
|
NetworkConfig: `{}`,
|
||||||
}
|
}
|
||||||
for i, tt := range []struct {
|
for i, tt := range []struct {
|
||||||
udcc initialize.CloudConfig
|
udcc initialize.CloudConfig
|
||||||
@@ -36,6 +37,7 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
Hostname: "meta-hostname",
|
Hostname: "meta-hostname",
|
||||||
NetworkConfigPath: "/path/meta",
|
NetworkConfigPath: "/path/meta",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
simplecc,
|
simplecc,
|
||||||
},
|
},
|
||||||
@@ -45,6 +47,7 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
SSHAuthorizedKeys: []string{"abc", "def"},
|
SSHAuthorizedKeys: []string{"abc", "def"},
|
||||||
Hostname: "user-hostname",
|
Hostname: "user-hostname",
|
||||||
NetworkConfigPath: "/path/somewhere",
|
NetworkConfigPath: "/path/somewhere",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
SSHAuthorizedKeys: []string{"woof", "qux"},
|
SSHAuthorizedKeys: []string{"woof", "qux"},
|
||||||
@@ -54,6 +57,7 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
SSHAuthorizedKeys: []string{"abc", "def", "woof", "qux"},
|
SSHAuthorizedKeys: []string{"abc", "def", "woof", "qux"},
|
||||||
Hostname: "user-hostname",
|
Hostname: "user-hostname",
|
||||||
NetworkConfigPath: "/path/somewhere",
|
NetworkConfigPath: "/path/somewhere",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -64,11 +68,13 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
||||||
NetworkConfigPath: "/dev/fun",
|
NetworkConfigPath: "/dev/fun",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
Hostname: "supercool",
|
Hostname: "supercool",
|
||||||
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
||||||
NetworkConfigPath: "/dev/fun",
|
NetworkConfigPath: "/dev/fun",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -80,11 +86,13 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
Hostname: "youyouyou",
|
Hostname: "youyouyou",
|
||||||
NetworkConfigPath: "meta-meta-yo",
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
Hostname: "mememe",
|
Hostname: "mememe",
|
||||||
ManageEtcHosts: initialize.EtcHosts("lolz"),
|
ManageEtcHosts: initialize.EtcHosts("lolz"),
|
||||||
NetworkConfigPath: "meta-meta-yo",
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -95,10 +103,12 @@ func TestMergeCloudConfig(t *testing.T) {
|
|||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
ManageEtcHosts: initialize.EtcHosts("lolz"),
|
ManageEtcHosts: initialize.EtcHosts("lolz"),
|
||||||
NetworkConfigPath: "meta-meta-yo",
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
initialize.CloudConfig{
|
initialize.CloudConfig{
|
||||||
Hostname: "mememe",
|
Hostname: "mememe",
|
||||||
NetworkConfigPath: "meta-meta-yo",
|
NetworkConfigPath: "meta-meta-yo",
|
||||||
|
NetworkConfig: `{"hostname":"test"}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
@@ -41,6 +41,10 @@ func (cd *configDrive) FetchUserdata() ([]byte, error) {
|
|||||||
return cd.tryReadFile(path.Join(cd.openstackVersionRoot(), "user_data"))
|
return cd.tryReadFile(path.Join(cd.openstackVersionRoot(), "user_data"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cd *configDrive) FetchNetworkConfig(filename string) ([]byte, error) {
|
||||||
|
return cd.tryReadFile(path.Join(cd.openstackRoot(), filename))
|
||||||
|
}
|
||||||
|
|
||||||
func (cd *configDrive) Type() string {
|
func (cd *configDrive) Type() string {
|
||||||
return "cloud-drive"
|
return "cloud-drive"
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,11 @@
|
|||||||
package datasource
|
package datasource
|
||||||
|
|
||||||
const (
|
|
||||||
Ec2ApiVersion = "2009-04-04"
|
|
||||||
OpenstackApiVersion = "2012-08-10"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Datasource interface {
|
type Datasource interface {
|
||||||
IsAvailable() bool
|
IsAvailable() bool
|
||||||
AvailabilityChanges() bool
|
AvailabilityChanges() bool
|
||||||
ConfigRoot() string
|
ConfigRoot() string
|
||||||
FetchMetadata() ([]byte, error)
|
FetchMetadata() ([]byte, error)
|
||||||
FetchUserdata() ([]byte, error)
|
FetchUserdata() ([]byte, error)
|
||||||
|
FetchNetworkConfig(string) ([]byte, error)
|
||||||
Type() string
|
Type() string
|
||||||
}
|
}
|
||||||
|
@@ -34,6 +34,10 @@ func (f *localFile) FetchUserdata() ([]byte, error) {
|
|||||||
return ioutil.ReadFile(f.path)
|
return ioutil.ReadFile(f.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *localFile) FetchNetworkConfig(filename string) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *localFile) Type() string {
|
func (f *localFile) Type() string {
|
||||||
return "local-file"
|
return "local-file"
|
||||||
}
|
}
|
||||||
|
145
datasource/metadata/cloudsigma/server_context.go
Normal file
145
datasource/metadata/cloudsigma/server_context.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package cloudsigma
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/third_party/github.com/cloudsigma/cepgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
userDataFieldName = "cloudinit-user-data"
|
||||||
|
)
|
||||||
|
|
||||||
|
type serverContextService struct {
|
||||||
|
client interface {
|
||||||
|
All() (interface{}, error)
|
||||||
|
Key(string) (interface{}, error)
|
||||||
|
Meta() (map[string]string, error)
|
||||||
|
FetchRaw(string) ([]byte, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServerContextService() *serverContextService {
|
||||||
|
return &serverContextService{
|
||||||
|
client: cepgo.NewCepgo(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *serverContextService) IsAvailable() bool {
|
||||||
|
productNameFile, err := os.Open("/sys/class/dmi/id/product_name")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
productName := make([]byte, 10)
|
||||||
|
_, err = productNameFile.Read(productName)
|
||||||
|
return err == nil && string(productName) == "CloudSigma"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *serverContextService) AvailabilityChanges() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *serverContextService) ConfigRoot() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *serverContextService) Type() string {
|
||||||
|
return "server-context"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scs *serverContextService) FetchMetadata() ([]byte, error) {
|
||||||
|
var (
|
||||||
|
inputMetadata struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Meta map[string]string `json:"meta"`
|
||||||
|
Nics []struct {
|
||||||
|
Runtime struct {
|
||||||
|
InterfaceType string `json:"interface_type"`
|
||||||
|
IPv4 struct {
|
||||||
|
IP string `json:"uuid"`
|
||||||
|
} `json:"ip_v4"`
|
||||||
|
} `json:"runtime"`
|
||||||
|
} `json:"nics"`
|
||||||
|
}
|
||||||
|
outputMetadata struct {
|
||||||
|
Hostname string `json:"name"`
|
||||||
|
PublicKeys map[string]string `json:"public_keys"`
|
||||||
|
LocalIPv4 string `json:"local-ipv4"`
|
||||||
|
PublicIPv4 string `json:"public-ipv4"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
rawMetadata, err := scs.client.FetchRaw("")
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(rawMetadata, &inputMetadata)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if inputMetadata.Name != "" {
|
||||||
|
outputMetadata.Hostname = inputMetadata.Name
|
||||||
|
} else {
|
||||||
|
outputMetadata.Hostname = inputMetadata.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
if key, ok := inputMetadata.Meta["ssh_public_key"]; ok {
|
||||||
|
splitted := strings.Split(key, " ")
|
||||||
|
outputMetadata.PublicKeys = make(map[string]string)
|
||||||
|
outputMetadata.PublicKeys[splitted[len(splitted)-1]] = key
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, nic := range inputMetadata.Nics {
|
||||||
|
if nic.Runtime.IPv4.IP != "" {
|
||||||
|
if nic.Runtime.InterfaceType == "public" {
|
||||||
|
outputMetadata.PublicIPv4 = nic.Runtime.IPv4.IP
|
||||||
|
} else {
|
||||||
|
outputMetadata.LocalIPv4 = nic.Runtime.IPv4.IP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(outputMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scs *serverContextService) FetchUserdata() ([]byte, error) {
|
||||||
|
metadata, err := scs.client.Meta()
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userData, ok := metadata[userDataFieldName]
|
||||||
|
if ok && isBase64Encoded(userDataFieldName, metadata) {
|
||||||
|
if decodedUserData, err := base64.StdEncoding.DecodeString(userData); err == nil {
|
||||||
|
return decodedUserData, nil
|
||||||
|
} else {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(userData), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scs *serverContextService) FetchNetworkConfig(a string) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBase64Encoded(field string, userdata map[string]string) bool {
|
||||||
|
base64Fields, ok := userdata["base64_fields"]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, base64Field := range strings.Split(base64Fields, ",") {
|
||||||
|
if field == base64Field {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
152
datasource/metadata/cloudsigma/server_context_test.go
Normal file
152
datasource/metadata/cloudsigma/server_context_test.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package cloudsigma
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeCepgoClient struct {
|
||||||
|
raw []byte
|
||||||
|
meta map[string]string
|
||||||
|
keys map[string]interface{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeCepgoClient) All() (interface{}, error) {
|
||||||
|
return f.keys, f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeCepgoClient) Key(key string) (interface{}, error) {
|
||||||
|
return f.keys[key], f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeCepgoClient) Meta() (map[string]string, error) {
|
||||||
|
return f.meta, f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeCepgoClient) FetchRaw(key string) ([]byte, error) {
|
||||||
|
return f.raw, f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerContextFetchMetadata(t *testing.T) {
|
||||||
|
var metadata struct {
|
||||||
|
Hostname string `json:"name"`
|
||||||
|
PublicKeys map[string]string `json:"public_keys"`
|
||||||
|
LocalIPv4 string `json:"local-ipv4"`
|
||||||
|
PublicIPv4 string `json:"public-ipv4"`
|
||||||
|
}
|
||||||
|
client := new(fakeCepgoClient)
|
||||||
|
scs := NewServerContextService()
|
||||||
|
scs.client = client
|
||||||
|
client.raw = []byte(`{
|
||||||
|
"context": true,
|
||||||
|
"cpu": 4000,
|
||||||
|
"cpu_model": null,
|
||||||
|
"cpus_instead_of_cores": false,
|
||||||
|
"enable_numa": false,
|
||||||
|
"grantees": [],
|
||||||
|
"hv_relaxed": false,
|
||||||
|
"hv_tsc": false,
|
||||||
|
"jobs": [],
|
||||||
|
"mem": 4294967296,
|
||||||
|
"meta": {
|
||||||
|
"base64_fields": "cloudinit-user-data",
|
||||||
|
"cloudinit-user-data": "I2Nsb3VkLWNvbmZpZwoKaG9zdG5hbWU6IGNvcmVvczE=",
|
||||||
|
"ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2E.../hQ5D5 john@doe"
|
||||||
|
},
|
||||||
|
"name": "coreos",
|
||||||
|
"nics": [
|
||||||
|
{
|
||||||
|
"runtime": {
|
||||||
|
"interface_type": "public",
|
||||||
|
"ip_v4": {
|
||||||
|
"uuid": "31.171.251.74"
|
||||||
|
},
|
||||||
|
"ip_v6": null
|
||||||
|
},
|
||||||
|
"vlan": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"smp": 2,
|
||||||
|
"status": "running",
|
||||||
|
"uuid": "20a0059b-041e-4d0c-bcc6-9b2852de48b3"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
metadataBytes, err := scs.FetchMetadata()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
|
||||||
|
t.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata.Hostname != "coreos" {
|
||||||
|
t.Errorf("Hostname is not 'coreos' but %s instead", metadata.Hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata.PublicKeys["john@doe"] != "ssh-rsa AAAAB3NzaC1yc2E.../hQ5D5 john@doe" {
|
||||||
|
t.Error("Public SSH Keys are not being read properly")
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata.LocalIPv4 != "" {
|
||||||
|
t.Errorf("Local IP is not empty but %s instead", metadata.LocalIPv4)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata.PublicIPv4 != "31.171.251.74" {
|
||||||
|
t.Errorf("Local IP is not 31.171.251.74 but %s instead", metadata.PublicIPv4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerContextFetchUserdata(t *testing.T) {
|
||||||
|
client := new(fakeCepgoClient)
|
||||||
|
scs := NewServerContextService()
|
||||||
|
scs.client = client
|
||||||
|
userdataSets := []struct {
|
||||||
|
in map[string]string
|
||||||
|
err bool
|
||||||
|
out []byte
|
||||||
|
}{
|
||||||
|
{map[string]string{
|
||||||
|
"base64_fields": "cloudinit-user-data",
|
||||||
|
"cloudinit-user-data": "aG9zdG5hbWU6IGNvcmVvc190ZXN0",
|
||||||
|
}, false, []byte("hostname: coreos_test")},
|
||||||
|
{map[string]string{
|
||||||
|
"cloudinit-user-data": "#cloud-config\\nhostname: coreos1",
|
||||||
|
}, false, []byte("#cloud-config\\nhostname: coreos1")},
|
||||||
|
{map[string]string{}, false, []byte{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, set := range userdataSets {
|
||||||
|
client.meta = set.in
|
||||||
|
got, err := scs.FetchUserdata()
|
||||||
|
if (err != nil) != set.err {
|
||||||
|
t.Errorf("case %d: bad error state (got %t, want %t)", i, err != nil, set.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, set.out) {
|
||||||
|
t.Errorf("case %d: got %s, want %s", i, got, set.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerContextDecodingBase64UserData(t *testing.T) {
|
||||||
|
base64Sets := []struct {
|
||||||
|
in string
|
||||||
|
out bool
|
||||||
|
}{
|
||||||
|
{"cloudinit-user-data,foo,bar", true},
|
||||||
|
{"bar,cloudinit-user-data,foo,bar", true},
|
||||||
|
{"cloudinit-user-data", true},
|
||||||
|
{"", false},
|
||||||
|
{"foo", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, set := range base64Sets {
|
||||||
|
userdata := map[string]string{"base64_fields": set.in}
|
||||||
|
if isBase64Encoded("cloudinit-user-data", userdata) != set.out {
|
||||||
|
t.Errorf("isBase64Encoded(cloudinit-user-data, %s) should be %t", userdata, set.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
107
datasource/metadata/digitalocean/metadata.go
Normal file
107
datasource/metadata/digitalocean/metadata.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultAddress = "http://169.254.169.254/"
|
||||||
|
apiVersion = "metadata/v1"
|
||||||
|
userdataUrl = apiVersion + "/user-data"
|
||||||
|
metadataPath = apiVersion + ".json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Address struct {
|
||||||
|
IPAddress string `json:"ip_address"`
|
||||||
|
Netmask string `json:"netmask"`
|
||||||
|
Cidr int `json:"cidr"`
|
||||||
|
Gateway string `json:"gateway"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Interface struct {
|
||||||
|
IPv4 *Address `json:"ipv4"`
|
||||||
|
IPv6 *Address `json:"ipv6"`
|
||||||
|
MAC string `json:"mac"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Interfaces struct {
|
||||||
|
Public []Interface `json:"public"`
|
||||||
|
Private []Interface `json:"private"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNS struct {
|
||||||
|
Nameservers []string `json:"nameservers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Metadata struct {
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
Interfaces Interfaces `json:"interfaces"`
|
||||||
|
PublicKeys []string `json:"public_keys"`
|
||||||
|
DNS DNS `json:"dns"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type metadataService struct {
|
||||||
|
interfaces Interfaces
|
||||||
|
dns DNS
|
||||||
|
metadata.MetadataService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDatasource(root string) *metadataService {
|
||||||
|
return &metadataService{MetadataService: metadata.NewDatasource(root, apiVersion, userdataUrl, metadataPath)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *metadataService) FetchMetadata() ([]byte, error) {
|
||||||
|
data, err := ms.FetchData(ms.MetadataUrl())
|
||||||
|
if err != nil || len(data) == 0 {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata Metadata
|
||||||
|
if err := json.Unmarshal(data, &metadata); err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ms.interfaces = metadata.Interfaces
|
||||||
|
ms.dns = metadata.DNS
|
||||||
|
|
||||||
|
attrs := make(map[string]interface{})
|
||||||
|
if len(metadata.Interfaces.Public) > 0 {
|
||||||
|
if metadata.Interfaces.Public[0].IPv4 != nil {
|
||||||
|
attrs["public-ipv4"] = metadata.Interfaces.Public[0].IPv4.IPAddress
|
||||||
|
}
|
||||||
|
if metadata.Interfaces.Public[0].IPv6 != nil {
|
||||||
|
attrs["public-ipv6"] = metadata.Interfaces.Public[0].IPv6.IPAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(metadata.Interfaces.Private) > 0 {
|
||||||
|
if metadata.Interfaces.Private[0].IPv4 != nil {
|
||||||
|
attrs["local-ipv4"] = metadata.Interfaces.Private[0].IPv4.IPAddress
|
||||||
|
}
|
||||||
|
if metadata.Interfaces.Private[0].IPv6 != nil {
|
||||||
|
attrs["local-ipv6"] = metadata.Interfaces.Private[0].IPv6.IPAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attrs["hostname"] = metadata.Hostname
|
||||||
|
keys := make(map[string]string)
|
||||||
|
for i, key := range metadata.PublicKeys {
|
||||||
|
keys[strconv.Itoa(i)] = key
|
||||||
|
}
|
||||||
|
attrs["public_keys"] = keys
|
||||||
|
|
||||||
|
return json.Marshal(attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms metadataService) FetchNetworkConfig(filename string) ([]byte, error) {
|
||||||
|
return json.Marshal(Metadata{
|
||||||
|
Interfaces: ms.interfaces,
|
||||||
|
DNS: ms.dns,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms metadataService) Type() string {
|
||||||
|
return "digitalocean-metadata-service"
|
||||||
|
}
|
99
datasource/metadata/digitalocean/metadata_test.go
Normal file
99
datasource/metadata/digitalocean/metadata_test.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata"
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata/test"
|
||||||
|
"github.com/coreos/coreos-cloudinit/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestType(t *testing.T) {
|
||||||
|
want := "digitalocean-metadata-service"
|
||||||
|
if kind := (metadataService{}).Type(); kind != want {
|
||||||
|
t.Fatalf("bad type: want %q, got %q", want, kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchMetadata(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
root string
|
||||||
|
metadataPath string
|
||||||
|
resources map[string]string
|
||||||
|
expect []byte
|
||||||
|
clientErr error
|
||||||
|
expectErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
root: "/",
|
||||||
|
metadataPath: "v1.json",
|
||||||
|
resources: map[string]string{
|
||||||
|
"/v1.json": "bad",
|
||||||
|
},
|
||||||
|
expectErr: fmt.Errorf("invalid character 'b' looking for beginning of value"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "/",
|
||||||
|
metadataPath: "v1.json",
|
||||||
|
resources: map[string]string{
|
||||||
|
"/v1.json": `{
|
||||||
|
"droplet_id": 1,
|
||||||
|
"user_data": "hello",
|
||||||
|
"vendor_data": "hello",
|
||||||
|
"public_keys": [
|
||||||
|
"publickey1",
|
||||||
|
"publickey2"
|
||||||
|
],
|
||||||
|
"region": "nyc2",
|
||||||
|
"interfaces": {
|
||||||
|
"public": [
|
||||||
|
{
|
||||||
|
"ipv4": {
|
||||||
|
"ip_address": "192.168.1.2",
|
||||||
|
"netmask": "255.255.255.0",
|
||||||
|
"gateway": "192.168.1.1"
|
||||||
|
},
|
||||||
|
"ipv6": {
|
||||||
|
"ip_address": "fe00::",
|
||||||
|
"cidr": 126,
|
||||||
|
"gateway": "fe00::"
|
||||||
|
},
|
||||||
|
"mac": "ab:cd:ef:gh:ij",
|
||||||
|
"type": "public"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
expect: []byte(`{"hostname":"","public-ipv4":"192.168.1.2","public-ipv6":"fe00::","public_keys":{"0":"publickey1","1":"publickey2"}}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientErr: pkg.ErrTimeout{fmt.Errorf("test error")},
|
||||||
|
expectErr: pkg.ErrTimeout{fmt.Errorf("test error")},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
service := &metadataService{
|
||||||
|
MetadataService: metadata.MetadataService{
|
||||||
|
Root: tt.root,
|
||||||
|
Client: &test.HttpClient{tt.resources, tt.clientErr},
|
||||||
|
MetadataPath: tt.metadataPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
metadata, err := service.FetchMetadata()
|
||||||
|
if Error(err) != Error(tt.expectErr) {
|
||||||
|
t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(metadata, tt.expect) {
|
||||||
|
t.Fatalf("bad fetch (%q): want %q, got %q", tt.resources, tt.expect, metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(err error) string {
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
@@ -7,44 +7,28 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata"
|
||||||
"github.com/coreos/coreos-cloudinit/pkg"
|
"github.com/coreos/coreos-cloudinit/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultAddress = "http://169.254.169.254/"
|
DefaultAddress = "http://169.254.169.254/"
|
||||||
apiVersion = "2009-04-04"
|
apiVersion = "2009-04-04"
|
||||||
userdataUrl = apiVersion + "/user-data"
|
userdataPath = apiVersion + "/user-data"
|
||||||
metadataUrl = apiVersion + "/meta-data"
|
metadataPath = apiVersion + "/meta-data"
|
||||||
)
|
)
|
||||||
|
|
||||||
type metadataService struct {
|
type metadataService struct {
|
||||||
root string
|
metadata.MetadataService
|
||||||
client pkg.Getter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDatasource(root string) *metadataService {
|
func NewDatasource(root string) *metadataService {
|
||||||
if !strings.HasSuffix(root, "/") {
|
return &metadataService{metadata.NewDatasource(root, apiVersion, userdataPath, metadataPath)}
|
||||||
root += "/"
|
|
||||||
}
|
|
||||||
return &metadataService{root, pkg.NewHttpClient()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms metadataService) IsAvailable() bool {
|
|
||||||
_, err := ms.client.Get(ms.root + apiVersion)
|
|
||||||
return (err == nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms metadataService) AvailabilityChanges() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms metadataService) ConfigRoot() string {
|
|
||||||
return ms.root
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms metadataService) FetchMetadata() ([]byte, error) {
|
func (ms metadataService) FetchMetadata() ([]byte, error) {
|
||||||
attrs := make(map[string]interface{})
|
attrs := make(map[string]interface{})
|
||||||
if keynames, err := fetchAttributes(ms.client, fmt.Sprintf("%s/public-keys", ms.metadataUrl())); err == nil {
|
if keynames, err := ms.fetchAttributes(fmt.Sprintf("%s/public-keys", ms.MetadataUrl())); err == nil {
|
||||||
keyIDs := make(map[string]string)
|
keyIDs := make(map[string]string)
|
||||||
for _, keyname := range keynames {
|
for _, keyname := range keynames {
|
||||||
tokens := strings.SplitN(keyname, "=", 2)
|
tokens := strings.SplitN(keyname, "=", 2)
|
||||||
@@ -56,7 +40,7 @@ func (ms metadataService) FetchMetadata() ([]byte, error) {
|
|||||||
|
|
||||||
keys := make(map[string]string)
|
keys := make(map[string]string)
|
||||||
for name, id := range keyIDs {
|
for name, id := range keyIDs {
|
||||||
sshkey, err := fetchAttribute(ms.client, fmt.Sprintf("%s/public-keys/%s/openssh-key", ms.metadataUrl(), id))
|
sshkey, err := ms.fetchAttribute(fmt.Sprintf("%s/public-keys/%s/openssh-key", ms.MetadataUrl(), id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -68,25 +52,25 @@ func (ms metadataService) FetchMetadata() ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if hostname, err := fetchAttribute(ms.client, fmt.Sprintf("%s/hostname", ms.metadataUrl())); err == nil {
|
if hostname, err := ms.fetchAttribute(fmt.Sprintf("%s/hostname", ms.MetadataUrl())); err == nil {
|
||||||
attrs["hostname"] = hostname
|
attrs["hostname"] = hostname
|
||||||
} else if _, ok := err.(pkg.ErrNotFound); !ok {
|
} else if _, ok := err.(pkg.ErrNotFound); !ok {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if localAddr, err := fetchAttribute(ms.client, fmt.Sprintf("%s/local-ipv4", ms.metadataUrl())); err == nil {
|
if localAddr, err := ms.fetchAttribute(fmt.Sprintf("%s/local-ipv4", ms.MetadataUrl())); err == nil {
|
||||||
attrs["local-ipv4"] = localAddr
|
attrs["local-ipv4"] = localAddr
|
||||||
} else if _, ok := err.(pkg.ErrNotFound); !ok {
|
} else if _, ok := err.(pkg.ErrNotFound); !ok {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if publicAddr, err := fetchAttribute(ms.client, fmt.Sprintf("%s/public-ipv4", ms.metadataUrl())); err == nil {
|
if publicAddr, err := ms.fetchAttribute(fmt.Sprintf("%s/public-ipv4", ms.MetadataUrl())); err == nil {
|
||||||
attrs["public-ipv4"] = publicAddr
|
attrs["public-ipv4"] = publicAddr
|
||||||
} else if _, ok := err.(pkg.ErrNotFound); !ok {
|
} else if _, ok := err.(pkg.ErrNotFound); !ok {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if content_path, err := fetchAttribute(ms.client, fmt.Sprintf("%s/network_config/content_path", ms.metadataUrl())); err == nil {
|
if content_path, err := ms.fetchAttribute(fmt.Sprintf("%s/network_config/content_path", ms.MetadataUrl())); err == nil {
|
||||||
attrs["network_config"] = map[string]string{
|
attrs["network_config"] = map[string]string{
|
||||||
"content_path": content_path,
|
"content_path": content_path,
|
||||||
}
|
}
|
||||||
@@ -97,30 +81,12 @@ func (ms metadataService) FetchMetadata() ([]byte, error) {
|
|||||||
return json.Marshal(attrs)
|
return json.Marshal(attrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms metadataService) FetchUserdata() ([]byte, error) {
|
|
||||||
if data, err := ms.client.GetRetry(ms.userdataUrl()); err == nil {
|
|
||||||
return data, err
|
|
||||||
} else if _, ok := err.(pkg.ErrNotFound); ok {
|
|
||||||
return []byte{}, nil
|
|
||||||
} else {
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms metadataService) Type() string {
|
func (ms metadataService) Type() string {
|
||||||
return "ec2-metadata-service"
|
return "ec2-metadata-service"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms metadataService) metadataUrl() string {
|
func (ms metadataService) fetchAttributes(url string) ([]string, error) {
|
||||||
return (ms.root + metadataUrl)
|
resp, err := ms.FetchData(url)
|
||||||
}
|
|
||||||
|
|
||||||
func (ms metadataService) userdataUrl() string {
|
|
||||||
return (ms.root + userdataUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchAttributes(client pkg.Getter, url string) ([]string, error) {
|
|
||||||
resp, err := client.GetRetry(url)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -132,8 +98,8 @@ func fetchAttributes(client pkg.Getter, url string) ([]string, error) {
|
|||||||
return data, scanner.Err()
|
return data, scanner.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchAttribute(client pkg.Getter, url string) (string, error) {
|
func (ms metadataService) fetchAttribute(url string) (string, error) {
|
||||||
if attrs, err := fetchAttributes(client, url); err == nil && len(attrs) > 0 {
|
if attrs, err := ms.fetchAttributes(url); err == nil && len(attrs) > 0 {
|
||||||
return attrs[0], nil
|
return attrs[0], nil
|
||||||
} else {
|
} else {
|
||||||
return "", err
|
return "", err
|
||||||
|
@@ -6,36 +6,11 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata"
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata/test"
|
||||||
"github.com/coreos/coreos-cloudinit/pkg"
|
"github.com/coreos/coreos-cloudinit/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testHttpClient struct {
|
|
||||||
resources map[string]string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testHttpClient) GetRetry(url string) ([]byte, error) {
|
|
||||||
if t.err != nil {
|
|
||||||
return nil, t.err
|
|
||||||
}
|
|
||||||
if val, ok := t.resources[url]; ok {
|
|
||||||
return []byte(val), nil
|
|
||||||
} else {
|
|
||||||
return nil, pkg.ErrNotFound{fmt.Errorf("not found: %q", url)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testHttpClient) Get(url string) ([]byte, error) {
|
|
||||||
return t.GetRetry(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAvailabilityChanges(t *testing.T) {
|
|
||||||
want := true
|
|
||||||
if ac := (metadataService{}).AvailabilityChanges(); ac != want {
|
|
||||||
t.Fatalf("bad AvailabilityChanges: want %q, got %q", want, ac)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestType(t *testing.T) {
|
func TestType(t *testing.T) {
|
||||||
want := "ec2-metadata-service"
|
want := "ec2-metadata-service"
|
||||||
if kind := (metadataService{}).Type(); kind != want {
|
if kind := (metadataService{}).Type(); kind != want {
|
||||||
@@ -43,102 +18,6 @@ func TestType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsAvailable(t *testing.T) {
|
|
||||||
for _, tt := range []struct {
|
|
||||||
root string
|
|
||||||
resources map[string]string
|
|
||||||
expect bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
root: "/",
|
|
||||||
resources: map[string]string{
|
|
||||||
"/2009-04-04": "",
|
|
||||||
},
|
|
||||||
expect: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "/",
|
|
||||||
resources: map[string]string{},
|
|
||||||
expect: false,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
service := &metadataService{tt.root, &testHttpClient{tt.resources, nil}}
|
|
||||||
if a := service.IsAvailable(); a != tt.expect {
|
|
||||||
t.Fatalf("bad isAvailable (%q): want %q, got %q", tt.resources, tt.expect, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetchUserdata(t *testing.T) {
|
|
||||||
for _, tt := range []struct {
|
|
||||||
root string
|
|
||||||
resources map[string]string
|
|
||||||
userdata []byte
|
|
||||||
clientErr error
|
|
||||||
expectErr error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
root: "/",
|
|
||||||
resources: map[string]string{
|
|
||||||
"/2009-04-04/user-data": "hello",
|
|
||||||
},
|
|
||||||
userdata: []byte("hello"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "/",
|
|
||||||
clientErr: pkg.ErrNotFound{fmt.Errorf("test not found error")},
|
|
||||||
userdata: []byte{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "/",
|
|
||||||
clientErr: pkg.ErrTimeout{fmt.Errorf("test timeout error")},
|
|
||||||
expectErr: pkg.ErrTimeout{fmt.Errorf("test timeout error")},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
service := &metadataService{tt.root, &testHttpClient{tt.resources, tt.clientErr}}
|
|
||||||
data, err := service.FetchUserdata()
|
|
||||||
if Error(err) != Error(tt.expectErr) {
|
|
||||||
t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(data, tt.userdata) {
|
|
||||||
t.Fatalf("bad userdata (%q): want %q, got %q", tt.resources, tt.userdata, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUrls(t *testing.T) {
|
|
||||||
for _, tt := range []struct {
|
|
||||||
root string
|
|
||||||
expectRoot string
|
|
||||||
userdata string
|
|
||||||
metadata string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
root: "/",
|
|
||||||
expectRoot: "/",
|
|
||||||
userdata: "/2009-04-04/user-data",
|
|
||||||
metadata: "/2009-04-04/meta-data",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "http://169.254.169.254/",
|
|
||||||
expectRoot: "http://169.254.169.254/",
|
|
||||||
userdata: "http://169.254.169.254/2009-04-04/user-data",
|
|
||||||
metadata: "http://169.254.169.254/2009-04-04/meta-data",
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
service := &metadataService{tt.root, nil}
|
|
||||||
if url := service.userdataUrl(); url != tt.userdata {
|
|
||||||
t.Fatalf("bad url (%q): want %q, got %q", tt.root, tt.userdata, url)
|
|
||||||
}
|
|
||||||
if url := service.metadataUrl(); url != tt.metadata {
|
|
||||||
t.Fatalf("bad url (%q): want %q, got %q", tt.root, tt.metadata, url)
|
|
||||||
}
|
|
||||||
if url := service.ConfigRoot(); url != tt.expectRoot {
|
|
||||||
t.Fatalf("bad url (%q): want %q, got %q", tt.root, tt.expectRoot, url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetchAttributes(t *testing.T) {
|
func TestFetchAttributes(t *testing.T) {
|
||||||
for _, s := range []struct {
|
for _, s := range []struct {
|
||||||
resources map[string]string
|
resources map[string]string
|
||||||
@@ -169,7 +48,7 @@ func TestFetchAttributes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
err: pkg.ErrNotFound{fmt.Errorf("test error")},
|
err: fmt.Errorf("test error"),
|
||||||
tests: []struct {
|
tests: []struct {
|
||||||
path string
|
path string
|
||||||
val []string
|
val []string
|
||||||
@@ -178,9 +57,11 @@ func TestFetchAttributes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
client := &testHttpClient{s.resources, s.err}
|
service := metadataService{metadata.MetadataService{
|
||||||
|
Client: &test.HttpClient{s.resources, s.err},
|
||||||
|
}}
|
||||||
for _, tt := range s.tests {
|
for _, tt := range s.tests {
|
||||||
attrs, err := fetchAttributes(client, tt.path)
|
attrs, err := service.fetchAttributes(tt.path)
|
||||||
if err != s.err {
|
if err != s.err {
|
||||||
t.Fatalf("bad error for %q (%q): want %q, got %q", tt.path, s.resources, s.err, err)
|
t.Fatalf("bad error for %q (%q): want %q, got %q", tt.path, s.resources, s.err, err)
|
||||||
}
|
}
|
||||||
@@ -221,7 +102,7 @@ func TestFetchAttribute(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
err: pkg.ErrNotFound{fmt.Errorf("test error")},
|
err: fmt.Errorf("test error"),
|
||||||
tests: []struct {
|
tests: []struct {
|
||||||
path string
|
path string
|
||||||
val string
|
val string
|
||||||
@@ -230,9 +111,11 @@ func TestFetchAttribute(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
client := &testHttpClient{s.resources, s.err}
|
service := metadataService{metadata.MetadataService{
|
||||||
|
Client: &test.HttpClient{s.resources, s.err},
|
||||||
|
}}
|
||||||
for _, tt := range s.tests {
|
for _, tt := range s.tests {
|
||||||
attr, err := fetchAttribute(client, tt.path)
|
attr, err := service.fetchAttribute(tt.path)
|
||||||
if err != s.err {
|
if err != s.err {
|
||||||
t.Fatalf("bad error for %q (%q): want %q, got %q", tt.path, s.resources, s.err, err)
|
t.Fatalf("bad error for %q (%q): want %q, got %q", tt.path, s.resources, s.err, err)
|
||||||
}
|
}
|
||||||
@@ -246,6 +129,7 @@ func TestFetchAttribute(t *testing.T) {
|
|||||||
func TestFetchMetadata(t *testing.T) {
|
func TestFetchMetadata(t *testing.T) {
|
||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
root string
|
root string
|
||||||
|
metadataPath string
|
||||||
resources map[string]string
|
resources map[string]string
|
||||||
expect []byte
|
expect []byte
|
||||||
clientErr error
|
clientErr error
|
||||||
@@ -253,6 +137,7 @@ func TestFetchMetadata(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
root: "/",
|
root: "/",
|
||||||
|
metadataPath: "2009-04-04/meta-data",
|
||||||
resources: map[string]string{
|
resources: map[string]string{
|
||||||
"/2009-04-04/meta-data/public-keys": "bad\n",
|
"/2009-04-04/meta-data/public-keys": "bad\n",
|
||||||
},
|
},
|
||||||
@@ -260,6 +145,7 @@ func TestFetchMetadata(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
root: "/",
|
root: "/",
|
||||||
|
metadataPath: "2009-04-04/meta-data",
|
||||||
resources: map[string]string{
|
resources: map[string]string{
|
||||||
"/2009-04-04/meta-data/hostname": "host",
|
"/2009-04-04/meta-data/hostname": "host",
|
||||||
"/2009-04-04/meta-data/local-ipv4": "1.2.3.4",
|
"/2009-04-04/meta-data/local-ipv4": "1.2.3.4",
|
||||||
@@ -276,7 +162,11 @@ func TestFetchMetadata(t *testing.T) {
|
|||||||
expectErr: pkg.ErrTimeout{fmt.Errorf("test error")},
|
expectErr: pkg.ErrTimeout{fmt.Errorf("test error")},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
service := &metadataService{tt.root, &testHttpClient{tt.resources, tt.clientErr}}
|
service := &metadataService{metadata.MetadataService{
|
||||||
|
Root: tt.root,
|
||||||
|
Client: &test.HttpClient{tt.resources, tt.clientErr},
|
||||||
|
MetadataPath: tt.metadataPath,
|
||||||
|
}}
|
||||||
metadata, err := service.FetchMetadata()
|
metadata, err := service.FetchMetadata()
|
||||||
if Error(err) != Error(tt.expectErr) {
|
if Error(err) != Error(tt.expectErr) {
|
||||||
t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err)
|
t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err)
|
||||||
@@ -287,35 +177,6 @@ func TestFetchMetadata(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewDatasource(t *testing.T) {
|
|
||||||
for _, tt := range []struct {
|
|
||||||
root string
|
|
||||||
expectRoot string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
root: "",
|
|
||||||
expectRoot: "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "/",
|
|
||||||
expectRoot: "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "http://169.254.169.254",
|
|
||||||
expectRoot: "http://169.254.169.254/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: "http://169.254.169.254/",
|
|
||||||
expectRoot: "http://169.254.169.254/",
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
service := NewDatasource(tt.root)
|
|
||||||
if service.root != tt.expectRoot {
|
|
||||||
t.Fatalf("bad root (%q): want %q, got %q", tt.root, tt.expectRoot, service.root)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Error(err error) string {
|
func Error(err error) string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err.Error()
|
return err.Error()
|
||||||
|
61
datasource/metadata/metadata.go
Normal file
61
datasource/metadata/metadata.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MetadataService struct {
|
||||||
|
Root string
|
||||||
|
Client pkg.Getter
|
||||||
|
ApiVersion string
|
||||||
|
UserdataPath string
|
||||||
|
MetadataPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDatasource(root, apiVersion, userdataPath, metadataPath string) MetadataService {
|
||||||
|
if !strings.HasSuffix(root, "/") {
|
||||||
|
root += "/"
|
||||||
|
}
|
||||||
|
return MetadataService{root, pkg.NewHttpClient(), apiVersion, userdataPath, metadataPath}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms MetadataService) IsAvailable() bool {
|
||||||
|
_, err := ms.Client.Get(ms.Root + ms.ApiVersion)
|
||||||
|
return (err == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms MetadataService) AvailabilityChanges() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms MetadataService) ConfigRoot() string {
|
||||||
|
return ms.Root
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms MetadataService) FetchUserdata() ([]byte, error) {
|
||||||
|
return ms.FetchData(ms.UserdataUrl())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms MetadataService) FetchNetworkConfig(filename string) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms MetadataService) FetchData(url string) ([]byte, error) {
|
||||||
|
if data, err := ms.Client.GetRetry(url); err == nil {
|
||||||
|
return data, err
|
||||||
|
} else if _, ok := err.(pkg.ErrNotFound); ok {
|
||||||
|
return []byte{}, nil
|
||||||
|
} else {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms MetadataService) MetadataUrl() string {
|
||||||
|
return (ms.Root + ms.MetadataPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms MetadataService) UserdataUrl() string {
|
||||||
|
return (ms.Root + ms.UserdataPath)
|
||||||
|
}
|
171
datasource/metadata/metadata_test.go
Normal file
171
datasource/metadata/metadata_test.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata/test"
|
||||||
|
"github.com/coreos/coreos-cloudinit/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAvailabilityChanges(t *testing.T) {
|
||||||
|
want := true
|
||||||
|
if ac := (MetadataService{}).AvailabilityChanges(); ac != want {
|
||||||
|
t.Fatalf("bad AvailabilityChanges: want %q, got %q", want, ac)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsAvailable(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
root string
|
||||||
|
apiVersion string
|
||||||
|
resources map[string]string
|
||||||
|
expect bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
root: "/",
|
||||||
|
apiVersion: "2009-04-04",
|
||||||
|
resources: map[string]string{
|
||||||
|
"/2009-04-04": "",
|
||||||
|
},
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "/",
|
||||||
|
resources: map[string]string{},
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
service := &MetadataService{
|
||||||
|
Root: tt.root,
|
||||||
|
Client: &test.HttpClient{tt.resources, nil},
|
||||||
|
ApiVersion: tt.apiVersion,
|
||||||
|
}
|
||||||
|
if a := service.IsAvailable(); a != tt.expect {
|
||||||
|
t.Fatalf("bad isAvailable (%q): want %q, got %q", tt.resources, tt.expect, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchUserdata(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
root string
|
||||||
|
userdataPath string
|
||||||
|
resources map[string]string
|
||||||
|
userdata []byte
|
||||||
|
clientErr error
|
||||||
|
expectErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
root: "/",
|
||||||
|
userdataPath: "2009-04-04/user-data",
|
||||||
|
resources: map[string]string{
|
||||||
|
"/2009-04-04/user-data": "hello",
|
||||||
|
},
|
||||||
|
userdata: []byte("hello"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "/",
|
||||||
|
clientErr: pkg.ErrNotFound{fmt.Errorf("test not found error")},
|
||||||
|
userdata: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "/",
|
||||||
|
clientErr: pkg.ErrTimeout{fmt.Errorf("test timeout error")},
|
||||||
|
expectErr: pkg.ErrTimeout{fmt.Errorf("test timeout error")},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
service := &MetadataService{
|
||||||
|
Root: tt.root,
|
||||||
|
Client: &test.HttpClient{tt.resources, tt.clientErr},
|
||||||
|
UserdataPath: tt.userdataPath,
|
||||||
|
}
|
||||||
|
data, err := service.FetchUserdata()
|
||||||
|
if Error(err) != Error(tt.expectErr) {
|
||||||
|
t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(data, tt.userdata) {
|
||||||
|
t.Fatalf("bad userdata (%q): want %q, got %q", tt.resources, tt.userdata, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUrls(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
root string
|
||||||
|
userdataPath string
|
||||||
|
metadataPath string
|
||||||
|
expectRoot string
|
||||||
|
userdata string
|
||||||
|
metadata string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
root: "/",
|
||||||
|
userdataPath: "2009-04-04/user-data",
|
||||||
|
metadataPath: "2009-04-04/meta-data",
|
||||||
|
expectRoot: "/",
|
||||||
|
userdata: "/2009-04-04/user-data",
|
||||||
|
metadata: "/2009-04-04/meta-data",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "http://169.254.169.254/",
|
||||||
|
userdataPath: "2009-04-04/user-data",
|
||||||
|
metadataPath: "2009-04-04/meta-data",
|
||||||
|
expectRoot: "http://169.254.169.254/",
|
||||||
|
userdata: "http://169.254.169.254/2009-04-04/user-data",
|
||||||
|
metadata: "http://169.254.169.254/2009-04-04/meta-data",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
service := &MetadataService{
|
||||||
|
Root: tt.root,
|
||||||
|
UserdataPath: tt.userdataPath,
|
||||||
|
MetadataPath: tt.metadataPath,
|
||||||
|
}
|
||||||
|
if url := service.UserdataUrl(); url != tt.userdata {
|
||||||
|
t.Fatalf("bad url (%q): want %q, got %q", tt.root, tt.userdata, url)
|
||||||
|
}
|
||||||
|
if url := service.MetadataUrl(); url != tt.metadata {
|
||||||
|
t.Fatalf("bad url (%q): want %q, got %q", tt.root, tt.metadata, url)
|
||||||
|
}
|
||||||
|
if url := service.ConfigRoot(); url != tt.expectRoot {
|
||||||
|
t.Fatalf("bad url (%q): want %q, got %q", tt.root, tt.expectRoot, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewDatasource(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
root string
|
||||||
|
expectRoot string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
root: "",
|
||||||
|
expectRoot: "/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "/",
|
||||||
|
expectRoot: "/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "http://169.254.169.254",
|
||||||
|
expectRoot: "http://169.254.169.254/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "http://169.254.169.254/",
|
||||||
|
expectRoot: "http://169.254.169.254/",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
service := NewDatasource(tt.root, "", "", "")
|
||||||
|
if service.Root != tt.expectRoot {
|
||||||
|
t.Fatalf("bad root (%q): want %q, got %q", tt.root, tt.expectRoot, service.Root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(err error) string {
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
27
datasource/metadata/test/test.go
Normal file
27
datasource/metadata/test/test.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HttpClient struct {
|
||||||
|
Resources map[string]string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HttpClient) GetRetry(url string) ([]byte, error) {
|
||||||
|
if t.Err != nil {
|
||||||
|
return nil, t.Err
|
||||||
|
}
|
||||||
|
if val, ok := t.Resources[url]; ok {
|
||||||
|
return []byte(val), nil
|
||||||
|
} else {
|
||||||
|
return nil, pkg.ErrNotFound{fmt.Errorf("not found: %q", url)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HttpClient) Get(url string) ([]byte, error) {
|
||||||
|
return t.GetRetry(url)
|
||||||
|
}
|
@@ -66,6 +66,10 @@ func (c *procCmdline) FetchUserdata() ([]byte, error) {
|
|||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *procCmdline) FetchNetworkConfig(filename string) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *procCmdline) Type() string {
|
func (c *procCmdline) Type() string {
|
||||||
return "proc-cmdline"
|
return "proc-cmdline"
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package url
|
package url
|
||||||
|
|
||||||
import "github.com/coreos/coreos-cloudinit/pkg"
|
import (
|
||||||
|
"github.com/coreos/coreos-cloudinit/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
type remoteFile struct {
|
type remoteFile struct {
|
||||||
url string
|
url string
|
||||||
@@ -33,6 +35,10 @@ func (f *remoteFile) FetchUserdata() ([]byte, error) {
|
|||||||
return client.GetRetry(f.url)
|
return client.GetRetry(f.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *remoteFile) FetchNetworkConfig(filename string) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *remoteFile) Type() string {
|
func (f *remoteFile) Type() string {
|
||||||
return "url"
|
return "url"
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@ package initialize
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
@@ -42,6 +41,7 @@ type CloudConfig struct {
|
|||||||
Users []system.User
|
Users []system.User
|
||||||
ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"`
|
ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"`
|
||||||
NetworkConfigPath string
|
NetworkConfigPath string
|
||||||
|
NetworkConfig string
|
||||||
}
|
}
|
||||||
|
|
||||||
type warner func(format string, v ...interface{})
|
type warner func(format string, v ...interface{})
|
||||||
@@ -258,17 +258,13 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if env.NetconfType() != "" {
|
if env.NetconfType() != "" {
|
||||||
filename := path.Join(env.ConfigRoot(), cfg.NetworkConfigPath)
|
|
||||||
log.Printf("Attempting to read config from %q\n", filename)
|
|
||||||
netconfBytes, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var interfaces []network.InterfaceGenerator
|
var interfaces []network.InterfaceGenerator
|
||||||
|
var err error
|
||||||
switch env.NetconfType() {
|
switch env.NetconfType() {
|
||||||
case "debian":
|
case "debian":
|
||||||
interfaces, err = network.ProcessDebianNetconf(string(netconfBytes))
|
interfaces, err = network.ProcessDebianNetconf(cfg.NetworkConfig)
|
||||||
|
case "digitalocean":
|
||||||
|
interfaces, err = network.ProcessDigitalOceanNetconf(cfg.NetworkConfig)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unsupported network config format %q", env.NetconfType())
|
return fmt.Errorf("Unsupported network config format %q", env.NetconfType())
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
package initialize
|
package initialize
|
||||||
|
|
||||||
import "encoding/json"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
// ParseMetaData parses a JSON blob in the OpenStack metadata service format, and
|
// ParseMetaData parses a JSON blob in the OpenStack metadata service format, and
|
||||||
// converts it to a partially hydrated CloudConfig
|
// converts it to a partially hydrated CloudConfig
|
||||||
@@ -22,8 +25,8 @@ func ParseMetaData(contents string) (*CloudConfig, error) {
|
|||||||
var cfg CloudConfig
|
var cfg CloudConfig
|
||||||
if len(metadata.SSHAuthorizedKeyMap) > 0 {
|
if len(metadata.SSHAuthorizedKeyMap) > 0 {
|
||||||
cfg.SSHAuthorizedKeys = make([]string, 0, len(metadata.SSHAuthorizedKeyMap))
|
cfg.SSHAuthorizedKeys = make([]string, 0, len(metadata.SSHAuthorizedKeyMap))
|
||||||
for _, key := range metadata.SSHAuthorizedKeyMap {
|
for _, name := range sortedKeys(metadata.SSHAuthorizedKeyMap) {
|
||||||
cfg.SSHAuthorizedKeys = append(cfg.SSHAuthorizedKeys, key)
|
cfg.SSHAuthorizedKeys = append(cfg.SSHAuthorizedKeys, metadata.SSHAuthorizedKeyMap[name])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cfg.Hostname = metadata.Hostname
|
cfg.Hostname = metadata.Hostname
|
||||||
@@ -50,3 +53,11 @@ func ExtractIPsFromMetadata(contents []byte) (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sortedKeys(m map[string]string) (keys []string) {
|
||||||
|
for key := range m {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@@ -14,7 +14,7 @@ func TestParseMetadata(t *testing.T) {
|
|||||||
{`{"foo": "bar"}`, &CloudConfig{}, false},
|
{`{"foo": "bar"}`, &CloudConfig{}, false},
|
||||||
{`{"network_config": {"content_path": "asdf"}}`, &CloudConfig{NetworkConfigPath: "asdf"}, false},
|
{`{"network_config": {"content_path": "asdf"}}`, &CloudConfig{NetworkConfigPath: "asdf"}, false},
|
||||||
{`{"hostname": "turkleton"}`, &CloudConfig{Hostname: "turkleton"}, false},
|
{`{"hostname": "turkleton"}`, &CloudConfig{Hostname: "turkleton"}, false},
|
||||||
{`{"public_keys": {"jack": "jill", "bob": "alice"}}`, &CloudConfig{SSHAuthorizedKeys: []string{"jill", "alice"}}, false},
|
{`{"public_keys": {"jack": "jill", "bob": "alice"}}`, &CloudConfig{SSHAuthorizedKeys: []string{"alice", "jill"}}, false},
|
||||||
{`{"unknown": "thing", "hostname": "my_host", "public_keys": {"do": "re", "mi": "fa"}, "network_config": {"content_path": "/root", "blah": "zzz"}}`, &CloudConfig{SSHAuthorizedKeys: []string{"re", "fa"}, Hostname: "my_host", NetworkConfigPath: "/root"}, false},
|
{`{"unknown": "thing", "hostname": "my_host", "public_keys": {"do": "re", "mi": "fa"}, "network_config": {"content_path": "/root", "blah": "zzz"}}`, &CloudConfig{SSHAuthorizedKeys: []string{"re", "fa"}, Hostname: "my_host", NetworkConfigPath: "/root"}, false},
|
||||||
} {
|
} {
|
||||||
got, err := ParseMetaData(tt.in)
|
got, err := ParseMetaData(tt.in)
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) {
|
func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) {
|
||||||
|
log.Println("Processing Debian network config")
|
||||||
lines := formatConfig(config)
|
lines := formatConfig(config)
|
||||||
stanzas, err := parseStanzas(lines)
|
stanzas, err := parseStanzas(lines)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -18,7 +20,9 @@ func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) {
|
|||||||
interfaces = append(interfaces, s)
|
interfaces = append(interfaces, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.Printf("Parsed %d network interfaces\n", len(interfaces))
|
||||||
|
|
||||||
|
log.Println("Processed Debian network config")
|
||||||
return buildInterfaces(interfaces), nil
|
return buildInterfaces(interfaces), nil
|
||||||
}
|
}
|
||||||
|
|
142
network/digitalocean.go
Normal file
142
network/digitalocean.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ProcessDigitalOceanNetconf(config string) ([]InterfaceGenerator, error) {
|
||||||
|
log.Println("Processing DigitalOcean network config")
|
||||||
|
if config == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg digitalocean.Metadata
|
||||||
|
if err := json.Unmarshal([]byte(config), &cfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Parsing nameservers")
|
||||||
|
nameservers, err := parseNameservers(cfg.DNS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Printf("Parsed %d nameservers\n", len(nameservers))
|
||||||
|
|
||||||
|
log.Println("Parsing interfaces")
|
||||||
|
generators, err := parseInterfaces(cfg.Interfaces, nameservers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Printf("Parsed %d network interfaces\n", len(generators))
|
||||||
|
|
||||||
|
log.Println("Processed DigitalOcean network config")
|
||||||
|
return generators, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNameservers(cfg digitalocean.DNS) ([]net.IP, error) {
|
||||||
|
nameservers := make([]net.IP, 0, len(cfg.Nameservers))
|
||||||
|
for _, ns := range cfg.Nameservers {
|
||||||
|
if ip := net.ParseIP(ns); ip == nil {
|
||||||
|
return nil, fmt.Errorf("could not parse %q as nameserver IP address", ns)
|
||||||
|
} else {
|
||||||
|
nameservers = append(nameservers, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nameservers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInterfaces(cfg digitalocean.Interfaces, nameservers []net.IP) ([]InterfaceGenerator, error) {
|
||||||
|
generators := make([]InterfaceGenerator, 0, len(cfg.Public)+len(cfg.Private))
|
||||||
|
for _, iface := range cfg.Public {
|
||||||
|
if generator, err := parseInterface(iface, nameservers, true); err == nil {
|
||||||
|
generators = append(generators, &physicalInterface{*generator})
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, iface := range cfg.Private {
|
||||||
|
if generator, err := parseInterface(iface, []net.IP{}, false); err == nil {
|
||||||
|
generators = append(generators, &physicalInterface{*generator})
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return generators, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInterface(iface digitalocean.Interface, nameservers []net.IP, useRoute bool) (*logicalInterface, error) {
|
||||||
|
routes := make([]route, 0)
|
||||||
|
addresses := make([]net.IPNet, 0)
|
||||||
|
if iface.IPv4 != nil {
|
||||||
|
var ip, mask, gateway net.IP
|
||||||
|
if ip = net.ParseIP(iface.IPv4.IPAddress); ip == nil {
|
||||||
|
return nil, fmt.Errorf("could not parse %q as IPv4 address", iface.IPv4.IPAddress)
|
||||||
|
}
|
||||||
|
if mask = net.ParseIP(iface.IPv4.Netmask); mask == nil {
|
||||||
|
return nil, fmt.Errorf("could not parse %q as IPv4 mask", iface.IPv4.Netmask)
|
||||||
|
}
|
||||||
|
addresses = append(addresses, net.IPNet{
|
||||||
|
IP: ip,
|
||||||
|
Mask: net.IPMask(mask),
|
||||||
|
})
|
||||||
|
|
||||||
|
if useRoute {
|
||||||
|
if gateway = net.ParseIP(iface.IPv4.Gateway); gateway == nil {
|
||||||
|
return nil, fmt.Errorf("could not parse %q as IPv4 gateway", iface.IPv4.Gateway)
|
||||||
|
}
|
||||||
|
routes = append(routes, route{
|
||||||
|
destination: net.IPNet{
|
||||||
|
IP: net.IPv4zero,
|
||||||
|
Mask: net.IPMask(net.IPv4zero),
|
||||||
|
},
|
||||||
|
gateway: gateway,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iface.IPv6 != nil {
|
||||||
|
var ip, gateway net.IP
|
||||||
|
if ip = net.ParseIP(iface.IPv6.IPAddress); ip == nil {
|
||||||
|
return nil, fmt.Errorf("could not parse %q as IPv6 address", iface.IPv6.IPAddress)
|
||||||
|
}
|
||||||
|
addresses = append(addresses, net.IPNet{
|
||||||
|
IP: ip,
|
||||||
|
Mask: net.CIDRMask(iface.IPv6.Cidr, net.IPv6len*8),
|
||||||
|
})
|
||||||
|
|
||||||
|
if useRoute {
|
||||||
|
if gateway = net.ParseIP(iface.IPv6.Gateway); gateway == nil {
|
||||||
|
return nil, fmt.Errorf("could not parse %q as IPv6 gateway", iface.IPv6.Gateway)
|
||||||
|
}
|
||||||
|
routes = append(routes, route{
|
||||||
|
destination: net.IPNet{
|
||||||
|
IP: net.IPv6zero,
|
||||||
|
Mask: net.IPMask(net.IPv6zero),
|
||||||
|
},
|
||||||
|
gateway: gateway,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hwaddr, err := net.ParseMAC(iface.MAC)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if nameservers == nil {
|
||||||
|
nameservers = []net.IP{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &logicalInterface{
|
||||||
|
hwaddr: hwaddr,
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: addresses,
|
||||||
|
nameservers: nameservers,
|
||||||
|
routes: routes,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
367
network/digitalocean_test.go
Normal file
367
network/digitalocean_test.go
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseNameservers(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
dns digitalocean.DNS
|
||||||
|
nss []net.IP
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
dns: digitalocean.DNS{},
|
||||||
|
nss: []net.IP{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dns: digitalocean.DNS{[]string{"1.2.3.4"}},
|
||||||
|
nss: []net.IP{net.ParseIP("1.2.3.4")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dns: digitalocean.DNS{[]string{"bad"}},
|
||||||
|
err: errors.New("could not parse \"bad\" as nameserver IP address"),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
nss, err := parseNameservers(tt.dns)
|
||||||
|
if !errorsEqual(tt.err, err) {
|
||||||
|
t.Fatalf("bad error (%+v): want %q, got %q", tt.dns, tt.err, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.nss, nss) {
|
||||||
|
t.Fatalf("bad nameservers (%+v): want %#v, got %#v", tt.dns, tt.nss, nss)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterface(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
cfg digitalocean.Interface
|
||||||
|
nss []net.IP
|
||||||
|
useRoute bool
|
||||||
|
iface *logicalInterface
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "bad",
|
||||||
|
},
|
||||||
|
err: errors.New("invalid MAC address: bad"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
},
|
||||||
|
nss: []net.IP{},
|
||||||
|
iface: &logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
},
|
||||||
|
useRoute: true,
|
||||||
|
nss: []net.IP{net.ParseIP("1.2.3.4")},
|
||||||
|
iface: &logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{},
|
||||||
|
nameservers: []net.IP{net.ParseIP("1.2.3.4")},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv4: &digitalocean.Address{
|
||||||
|
IPAddress: "bad",
|
||||||
|
Netmask: "255.255.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nss: []net.IP{},
|
||||||
|
err: errors.New("could not parse \"bad\" as IPv4 address"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv4: &digitalocean.Address{
|
||||||
|
IPAddress: "1.2.3.4",
|
||||||
|
Netmask: "bad",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nss: []net.IP{},
|
||||||
|
err: errors.New("could not parse \"bad\" as IPv4 mask"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv4: &digitalocean.Address{
|
||||||
|
IPAddress: "1.2.3.4",
|
||||||
|
Netmask: "255.255.0.0",
|
||||||
|
Gateway: "ignoreme",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nss: []net.IP{},
|
||||||
|
iface: &logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{net.IPNet{net.ParseIP("1.2.3.4"), net.IPMask(net.ParseIP("255.255.0.0"))}},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv4: &digitalocean.Address{
|
||||||
|
IPAddress: "1.2.3.4",
|
||||||
|
Netmask: "255.255.0.0",
|
||||||
|
Gateway: "bad",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useRoute: true,
|
||||||
|
nss: []net.IP{},
|
||||||
|
err: errors.New("could not parse \"bad\" as IPv4 gateway"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv4: &digitalocean.Address{
|
||||||
|
IPAddress: "1.2.3.4",
|
||||||
|
Netmask: "255.255.0.0",
|
||||||
|
Gateway: "5.6.7.8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useRoute: true,
|
||||||
|
nss: []net.IP{},
|
||||||
|
iface: &logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{net.IPNet{net.ParseIP("1.2.3.4"), net.IPMask(net.ParseIP("255.255.0.0"))}},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{route{net.IPNet{net.IPv4zero, net.IPMask(net.IPv4zero)}, net.ParseIP("5.6.7.8")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv6: &digitalocean.Address{
|
||||||
|
IPAddress: "bad",
|
||||||
|
Cidr: 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nss: []net.IP{},
|
||||||
|
err: errors.New("could not parse \"bad\" as IPv6 address"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv6: &digitalocean.Address{
|
||||||
|
IPAddress: "fe00::",
|
||||||
|
Cidr: 16,
|
||||||
|
Gateway: "ignoreme",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nss: []net.IP{},
|
||||||
|
iface: &logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{net.IPNet{net.ParseIP("fe00::"), net.IPMask(net.ParseIP("ffff::"))}},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv6: &digitalocean.Address{
|
||||||
|
IPAddress: "fe00::",
|
||||||
|
Cidr: 16,
|
||||||
|
Gateway: "bad",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useRoute: true,
|
||||||
|
nss: []net.IP{},
|
||||||
|
err: errors.New("could not parse \"bad\" as IPv6 gateway"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interface{
|
||||||
|
MAC: "01:23:45:67:89:AB",
|
||||||
|
IPv6: &digitalocean.Address{
|
||||||
|
IPAddress: "fe00::",
|
||||||
|
Cidr: 16,
|
||||||
|
Gateway: "fe00:1234::",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useRoute: true,
|
||||||
|
nss: []net.IP{},
|
||||||
|
iface: &logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{net.IPNet{net.ParseIP("fe00::"), net.IPMask(net.ParseIP("ffff::"))}},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{route{net.IPNet{net.IPv6zero, net.IPMask(net.IPv6zero)}, net.ParseIP("fe00:1234::")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
iface, err := parseInterface(tt.cfg, tt.nss, tt.useRoute)
|
||||||
|
if !errorsEqual(tt.err, err) {
|
||||||
|
t.Fatalf("bad error (%+v): want %q, got %q", tt.cfg, tt.err, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.iface, iface) {
|
||||||
|
t.Fatalf("bad interface (%+v): want %#v, got %#v", tt.cfg, tt.iface, iface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaces(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
cfg digitalocean.Interfaces
|
||||||
|
nss []net.IP
|
||||||
|
ifaces []InterfaceGenerator
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
ifaces: []InterfaceGenerator{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interfaces{
|
||||||
|
Public: []digitalocean.Interface{{MAC: "01:23:45:67:89:AB"}},
|
||||||
|
},
|
||||||
|
ifaces: []InterfaceGenerator{
|
||||||
|
&physicalInterface{logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interfaces{
|
||||||
|
Private: []digitalocean.Interface{{MAC: "01:23:45:67:89:AB"}},
|
||||||
|
},
|
||||||
|
ifaces: []InterfaceGenerator{
|
||||||
|
&physicalInterface{logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interfaces{
|
||||||
|
Public: []digitalocean.Interface{{MAC: "01:23:45:67:89:AB"}},
|
||||||
|
},
|
||||||
|
nss: []net.IP{net.ParseIP("1.2.3.4")},
|
||||||
|
ifaces: []InterfaceGenerator{
|
||||||
|
&physicalInterface{logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{},
|
||||||
|
nameservers: []net.IP{net.ParseIP("1.2.3.4")},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interfaces{
|
||||||
|
Private: []digitalocean.Interface{{MAC: "01:23:45:67:89:AB"}},
|
||||||
|
},
|
||||||
|
nss: []net.IP{net.ParseIP("1.2.3.4")},
|
||||||
|
ifaces: []InterfaceGenerator{
|
||||||
|
&physicalInterface{logicalInterface{
|
||||||
|
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
|
||||||
|
config: configMethodStatic{
|
||||||
|
addresses: []net.IPNet{},
|
||||||
|
nameservers: []net.IP{},
|
||||||
|
routes: []route{},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interfaces{
|
||||||
|
Public: []digitalocean.Interface{{MAC: "bad"}},
|
||||||
|
},
|
||||||
|
err: errors.New("invalid MAC address: bad"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: digitalocean.Interfaces{
|
||||||
|
Private: []digitalocean.Interface{{MAC: "bad"}},
|
||||||
|
},
|
||||||
|
err: errors.New("invalid MAC address: bad"),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
ifaces, err := parseInterfaces(tt.cfg, tt.nss)
|
||||||
|
if !errorsEqual(tt.err, err) {
|
||||||
|
t.Fatalf("bad error (%+v): want %q, got %q", tt.cfg, tt.err, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.ifaces, ifaces) {
|
||||||
|
t.Fatalf("bad interfaces (%+v): want %#v, got %#v", tt.cfg, tt.ifaces, ifaces)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessDigitalOceanNetconf(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
cfg string
|
||||||
|
ifaces []InterfaceGenerator
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
cfg: ``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: `{"dns":{"nameservers":["bad"]}}`,
|
||||||
|
err: errors.New("could not parse \"bad\" as nameserver IP address"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: `{"interfaces":{"public":[{"ipv4":{"ip_address":"bad"}}]}}`,
|
||||||
|
err: errors.New("could not parse \"bad\" as IPv4 address"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: `{}`,
|
||||||
|
ifaces: []InterfaceGenerator{},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
ifaces, err := ProcessDigitalOceanNetconf(tt.cfg)
|
||||||
|
if !errorsEqual(tt.err, err) {
|
||||||
|
t.Fatalf("bad error (%q): want %q, got %q", tt.cfg, tt.err, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.ifaces, ifaces) {
|
||||||
|
t.Fatalf("bad interfaces (%q): want %#v, got %#v", tt.cfg, tt.ifaces, ifaces)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorsEqual(a, b error) bool {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (a != nil && b == nil) || (a == nil && b != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (a.Error() == b.Error())
|
||||||
|
}
|
@@ -2,6 +2,8 @@ package network
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -24,13 +26,25 @@ type networkInterface interface {
|
|||||||
|
|
||||||
type logicalInterface struct {
|
type logicalInterface struct {
|
||||||
name string
|
name string
|
||||||
|
hwaddr net.HardwareAddr
|
||||||
config configMethod
|
config configMethod
|
||||||
children []networkInterface
|
children []networkInterface
|
||||||
configDepth int
|
configDepth int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *logicalInterface) Name() string {
|
||||||
|
return i.name
|
||||||
|
}
|
||||||
|
|
||||||
func (i *logicalInterface) Network() string {
|
func (i *logicalInterface) Network() string {
|
||||||
config := fmt.Sprintf("[Match]\nName=%s\n\n[Network]\n", i.name)
|
config := fmt.Sprintln("[Match]")
|
||||||
|
if i.name != "" {
|
||||||
|
config += fmt.Sprintf("Name=%s\n", i.name)
|
||||||
|
}
|
||||||
|
if i.hwaddr != nil {
|
||||||
|
config += fmt.Sprintf("MACAddress=%s\n", i.hwaddr)
|
||||||
|
}
|
||||||
|
config += "\n[Network]\n"
|
||||||
|
|
||||||
for _, child := range i.children {
|
for _, child := range i.children {
|
||||||
switch iface := child.(type) {
|
switch iface := child.(type) {
|
||||||
@@ -46,8 +60,8 @@ func (i *logicalInterface) Network() string {
|
|||||||
for _, nameserver := range conf.nameservers {
|
for _, nameserver := range conf.nameservers {
|
||||||
config += fmt.Sprintf("DNS=%s\n", nameserver)
|
config += fmt.Sprintf("DNS=%s\n", nameserver)
|
||||||
}
|
}
|
||||||
if conf.address.IP != nil {
|
for _, addr := range conf.addresses {
|
||||||
config += fmt.Sprintf("\n[Address]\nAddress=%s\n", conf.address.String())
|
config += fmt.Sprintf("\n[Address]\nAddress=%s\n", addr.String())
|
||||||
}
|
}
|
||||||
for _, route := range conf.routes {
|
for _, route := range conf.routes {
|
||||||
config += fmt.Sprintf("\n[Route]\nDestination=%s\nGateway=%s\n", route.destination.String(), route.gateway)
|
config += fmt.Sprintf("\n[Route]\nDestination=%s\nGateway=%s\n", route.destination.String(), route.gateway)
|
||||||
@@ -63,8 +77,16 @@ func (i *logicalInterface) Link() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *logicalInterface) Netdev() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (i *logicalInterface) Filename() string {
|
func (i *logicalInterface) Filename() string {
|
||||||
return fmt.Sprintf("%02x-%s", i.configDepth, i.name)
|
name := i.name
|
||||||
|
if name == "" {
|
||||||
|
name = i.hwaddr.String()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%02x-%s", i.configDepth, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *logicalInterface) Children() []networkInterface {
|
func (i *logicalInterface) Children() []networkInterface {
|
||||||
@@ -83,14 +105,6 @@ type physicalInterface struct {
|
|||||||
logicalInterface
|
logicalInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *physicalInterface) Name() string {
|
|
||||||
return p.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *physicalInterface) Netdev() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *physicalInterface) Type() string {
|
func (p *physicalInterface) Type() string {
|
||||||
return "physical"
|
return "physical"
|
||||||
}
|
}
|
||||||
@@ -101,10 +115,6 @@ type bondInterface struct {
|
|||||||
options map[string]string
|
options map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bondInterface) Name() string {
|
|
||||||
return b.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bondInterface) Netdev() string {
|
func (b *bondInterface) Netdev() string {
|
||||||
return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name)
|
return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name)
|
||||||
}
|
}
|
||||||
@@ -115,8 +125,8 @@ func (b *bondInterface) Type() string {
|
|||||||
|
|
||||||
func (b *bondInterface) ModprobeParams() string {
|
func (b *bondInterface) ModprobeParams() string {
|
||||||
params := ""
|
params := ""
|
||||||
for name, val := range b.options {
|
for _, name := range sortedKeys(b.options) {
|
||||||
params += fmt.Sprintf("%s=%s ", name, val)
|
params += fmt.Sprintf("%s=%s ", name, b.options[name])
|
||||||
}
|
}
|
||||||
params = strings.TrimSuffix(params, " ")
|
params = strings.TrimSuffix(params, " ")
|
||||||
return params
|
return params
|
||||||
@@ -128,10 +138,6 @@ type vlanInterface struct {
|
|||||||
rawDevice string
|
rawDevice string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *vlanInterface) Name() string {
|
|
||||||
return v.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *vlanInterface) Netdev() string {
|
func (v *vlanInterface) Netdev() string {
|
||||||
config := fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n", v.name)
|
config := fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n", v.name)
|
||||||
switch c := v.config.(type) {
|
switch c := v.config.(type) {
|
||||||
@@ -158,8 +164,8 @@ func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator {
|
|||||||
markConfigDepths(interfaceMap)
|
markConfigDepths(interfaceMap)
|
||||||
|
|
||||||
interfaces := make([]InterfaceGenerator, 0, len(interfaceMap))
|
interfaces := make([]InterfaceGenerator, 0, len(interfaceMap))
|
||||||
for _, iface := range interfaceMap {
|
for _, name := range sortedInterfaces(interfaceMap) {
|
||||||
interfaces = append(interfaces, iface)
|
interfaces = append(interfaces, interfaceMap[name])
|
||||||
}
|
}
|
||||||
|
|
||||||
return interfaces
|
return interfaces
|
||||||
@@ -239,7 +245,8 @@ func createInterfaces(stanzas []*stanzaInterface) map[string]networkInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func linkAncestors(interfaceMap map[string]networkInterface) {
|
func linkAncestors(interfaceMap map[string]networkInterface) {
|
||||||
for _, iface := range interfaceMap {
|
for _, name := range sortedInterfaces(interfaceMap) {
|
||||||
|
iface := interfaceMap[name]
|
||||||
switch i := iface.(type) {
|
switch i := iface.(type) {
|
||||||
case *vlanInterface:
|
case *vlanInterface:
|
||||||
if parent, ok := interfaceMap[i.rawDevice]; ok {
|
if parent, ok := interfaceMap[i.rawDevice]; ok {
|
||||||
@@ -291,3 +298,19 @@ func setDepth(iface networkInterface) int {
|
|||||||
iface.setConfigDepth(maxDepth)
|
iface.setConfigDepth(maxDepth)
|
||||||
return (maxDepth + 1)
|
return (maxDepth + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sortedKeys(m map[string]string) (keys []string) {
|
||||||
|
for key := range m {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortedInterfaces(m map[string]networkInterface) (keys []string) {
|
||||||
|
for key := range m {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@@ -6,241 +6,101 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPhysicalInterfaceName(t *testing.T) {
|
func TestInterfaceGenerators(t *testing.T) {
|
||||||
p := physicalInterface{logicalInterface{name: "testname"}}
|
for _, tt := range []struct {
|
||||||
if p.Name() != "testname" {
|
name string
|
||||||
t.FailNow()
|
netdev string
|
||||||
}
|
link string
|
||||||
}
|
network string
|
||||||
|
kind string
|
||||||
func TestPhysicalInterfaceNetdev(t *testing.T) {
|
iface InterfaceGenerator
|
||||||
p := physicalInterface{}
|
}{
|
||||||
if p.Netdev() != "" {
|
{
|
||||||
t.FailNow()
|
name: "",
|
||||||
}
|
network: "[Match]\nMACAddress=00:01:02:03:04:05\n\n[Network]\n",
|
||||||
}
|
kind: "physical",
|
||||||
|
iface: &physicalInterface{logicalInterface{
|
||||||
func TestPhysicalInterfaceLink(t *testing.T) {
|
hwaddr: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5}),
|
||||||
p := physicalInterface{}
|
}},
|
||||||
if p.Link() != "" {
|
},
|
||||||
t.FailNow()
|
{
|
||||||
}
|
name: "testname",
|
||||||
}
|
network: "[Match]\nName=testname\n\n[Network]\nBond=testbond1\nVLAN=testvlan1\nVLAN=testvlan2\n",
|
||||||
|
kind: "physical",
|
||||||
func TestPhysicalInterfaceNetwork(t *testing.T) {
|
iface: &physicalInterface{logicalInterface{
|
||||||
p := physicalInterface{logicalInterface{
|
|
||||||
name: "testname",
|
name: "testname",
|
||||||
children: []networkInterface{
|
children: []networkInterface{
|
||||||
&bondInterface{
|
&bondInterface{logicalInterface: logicalInterface{name: "testbond1"}},
|
||||||
logicalInterface{
|
&vlanInterface{logicalInterface: logicalInterface{name: "testvlan1"}, id: 1},
|
||||||
name: "testbond1",
|
&vlanInterface{logicalInterface: logicalInterface{name: "testvlan2"}, id: 1},
|
||||||
},
|
},
|
||||||
nil,
|
}},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
&vlanInterface{
|
{
|
||||||
logicalInterface{
|
name: "testname",
|
||||||
name: "testvlan1",
|
netdev: "[NetDev]\nKind=bond\nName=testname\n",
|
||||||
},
|
network: "[Match]\nName=testname\n\n[Network]\nBond=testbond1\nVLAN=testvlan1\nVLAN=testvlan2\nDHCP=true\n",
|
||||||
1,
|
kind: "bond",
|
||||||
"",
|
iface: &bondInterface{logicalInterface: logicalInterface{
|
||||||
},
|
|
||||||
&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, nil}
|
|
||||||
if b.Name() != "testname" {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBondInterfaceNetdev(t *testing.T) {
|
|
||||||
b := bondInterface{logicalInterface{name: "testname"}, nil, 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",
|
name: "testname",
|
||||||
config: configMethodDHCP{},
|
config: configMethodDHCP{},
|
||||||
children: []networkInterface{
|
children: []networkInterface{
|
||||||
&bondInterface{
|
&bondInterface{logicalInterface: logicalInterface{name: "testbond1"}},
|
||||||
logicalInterface{
|
&vlanInterface{logicalInterface: logicalInterface{name: "testvlan1"}, id: 1},
|
||||||
name: "testbond1",
|
&vlanInterface{logicalInterface: logicalInterface{name: "testvlan2"}, id: 1},
|
||||||
},
|
},
|
||||||
nil,
|
}},
|
||||||
nil,
|
|
||||||
},
|
|
||||||
&vlanInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "testvlan1",
|
|
||||||
},
|
|
||||||
1,
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
&vlanInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "testvlan2",
|
|
||||||
},
|
|
||||||
1,
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
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) {
|
|
||||||
for _, tt := range []struct {
|
|
||||||
i vlanInterface
|
|
||||||
l string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
vlanInterface{logicalInterface{name: "testname"}, 1, ""},
|
|
||||||
"[NetDev]\nKind=vlan\nName=testname\n\n[VLAN]\nId=1\n",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
vlanInterface{logicalInterface{name: "testname", config: configMethodStatic{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""},
|
name: "testname",
|
||||||
"[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n",
|
netdev: "[NetDev]\nKind=vlan\nName=testname\n\n[VLAN]\nId=1\n",
|
||||||
|
network: "[Match]\nName=testname\n\n[Network]\n",
|
||||||
|
kind: "vlan",
|
||||||
|
iface: &vlanInterface{logicalInterface{name: "testname"}, 1, ""},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
vlanInterface{logicalInterface{name: "testname", config: configMethodDHCP{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""},
|
name: "testname",
|
||||||
"[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n",
|
netdev: "[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n",
|
||||||
|
network: "[Match]\nName=testname\n\n[Network]\n",
|
||||||
|
kind: "vlan",
|
||||||
|
iface: &vlanInterface{logicalInterface{name: "testname", config: configMethodStatic{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""},
|
||||||
},
|
},
|
||||||
} {
|
{
|
||||||
if tt.i.Netdev() != tt.l {
|
name: "testname",
|
||||||
t.Fatalf("bad netdev config (%q): got %q, want %q", tt.i, tt.i.Netdev(), tt.l)
|
netdev: "[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n",
|
||||||
}
|
network: "[Match]\nName=testname\n\n[Network]\nDHCP=true\n",
|
||||||
}
|
kind: "vlan",
|
||||||
}
|
iface: &vlanInterface{logicalInterface{name: "testname", config: configMethodDHCP{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""},
|
||||||
|
},
|
||||||
func TestVLANInterfaceLink(t *testing.T) {
|
{
|
||||||
v := vlanInterface{}
|
name: "testname",
|
||||||
if v.Link() != "" {
|
netdev: "[NetDev]\nKind=vlan\nName=testname\n\n[VLAN]\nId=0\n",
|
||||||
t.FailNow()
|
network: "[Match]\nName=testname\n\n[Network]\nDNS=8.8.8.8\n\n[Address]\nAddress=192.168.1.100/24\n\n[Route]\nDestination=0.0.0.0/0\nGateway=1.2.3.4\n",
|
||||||
}
|
kind: "vlan",
|
||||||
}
|
iface: &vlanInterface{logicalInterface: logicalInterface{
|
||||||
|
|
||||||
func TestVLANInterfaceNetwork(t *testing.T) {
|
|
||||||
v := vlanInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: "testname",
|
name: "testname",
|
||||||
config: configMethodStatic{
|
config: configMethodStatic{
|
||||||
address: net.IPNet{
|
addresses: []net.IPNet{{IP: []byte{192, 168, 1, 100}, Mask: []byte{255, 255, 255, 0}}},
|
||||||
IP: []byte{192, 168, 1, 100},
|
nameservers: []net.IP{[]byte{8, 8, 8, 8}},
|
||||||
Mask: []byte{255, 255, 255, 0},
|
routes: []route{route{destination: net.IPNet{IP: []byte{0, 0, 0, 0}, Mask: []byte{0, 0, 0, 0}}, gateway: []byte{1, 2, 3, 4}}},
|
||||||
},
|
},
|
||||||
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 TestType(t *testing.T) {
|
|
||||||
for _, tt := range []struct {
|
|
||||||
i InterfaceGenerator
|
|
||||||
t string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
i: &physicalInterface{},
|
|
||||||
t: "physical",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
i: &vlanInterface{},
|
|
||||||
t: "vlan",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
i: &bondInterface{},
|
|
||||||
t: "bond",
|
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
if tp := tt.i.Type(); tp != tt.t {
|
if name := tt.iface.Name(); name != tt.name {
|
||||||
t.Fatalf("bad type (%q): got %s, want %s", tt.i, tp, tt.t)
|
t.Fatalf("bad name (%q): want %q, got %q", tt.iface, tt.name, name)
|
||||||
|
}
|
||||||
|
if netdev := tt.iface.Netdev(); netdev != tt.netdev {
|
||||||
|
t.Fatalf("bad netdev (%q): want %q, got %q", tt.iface, tt.netdev, netdev)
|
||||||
|
}
|
||||||
|
if link := tt.iface.Link(); link != tt.link {
|
||||||
|
t.Fatalf("bad link (%q): want %q, got %q", tt.iface, tt.link, link)
|
||||||
|
}
|
||||||
|
if network := tt.iface.Network(); network != tt.network {
|
||||||
|
t.Fatalf("bad network (%q): want %q, got %q", tt.iface, tt.network, network)
|
||||||
|
}
|
||||||
|
if kind := tt.iface.Type(); kind != tt.kind {
|
||||||
|
t.Fatalf("bad type (%q): want %q, got %q", tt.iface, tt.kind, kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -469,7 +329,7 @@ func TestBuildInterfaces(t *testing.T) {
|
|||||||
configDepth: 2,
|
configDepth: 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expect := []InterfaceGenerator{eth0, bond0, bond1, vlan0, vlan1}
|
expect := []InterfaceGenerator{bond0, bond1, eth0, vlan0, vlan1}
|
||||||
if !reflect.DeepEqual(interfaces, expect) {
|
if !reflect.DeepEqual(interfaces, expect) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
@@ -484,6 +344,8 @@ func TestFilename(t *testing.T) {
|
|||||||
{logicalInterface{name: "iface", configDepth: 9}, "09-iface"},
|
{logicalInterface{name: "iface", configDepth: 9}, "09-iface"},
|
||||||
{logicalInterface{name: "iface", configDepth: 10}, "0a-iface"},
|
{logicalInterface{name: "iface", configDepth: 10}, "0a-iface"},
|
||||||
{logicalInterface{name: "iface", configDepth: 53}, "35-iface"},
|
{logicalInterface{name: "iface", configDepth: 53}, "35-iface"},
|
||||||
|
{logicalInterface{hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}), configDepth: 1}, "01-01:23:45:67:89:ab"},
|
||||||
|
{logicalInterface{name: "iface", hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}), configDepth: 1}, "01-iface"},
|
||||||
} {
|
} {
|
||||||
if tt.i.Filename() != tt.f {
|
if tt.i.Filename() != tt.f {
|
||||||
t.Fatalf("bad filename (%q): got %q, want %q", tt.i, tt.i.Filename(), tt.f)
|
t.Fatalf("bad filename (%q): got %q, want %q", tt.i, tt.i.Filename(), tt.f)
|
||||||
|
@@ -37,7 +37,7 @@ type route struct {
|
|||||||
type configMethod interface{}
|
type configMethod interface{}
|
||||||
|
|
||||||
type configMethodStatic struct {
|
type configMethodStatic struct {
|
||||||
address net.IPNet
|
addresses []net.IPNet
|
||||||
nameservers []net.IP
|
nameservers []net.IP
|
||||||
routes []route
|
routes []route
|
||||||
hwaddress net.HardwareAddr
|
hwaddress net.HardwareAddr
|
||||||
@@ -193,20 +193,21 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa
|
|||||||
switch confMethod {
|
switch confMethod {
|
||||||
case "static":
|
case "static":
|
||||||
config := configMethodStatic{
|
config := configMethodStatic{
|
||||||
|
addresses: make([]net.IPNet, 1),
|
||||||
routes: make([]route, 0),
|
routes: make([]route, 0),
|
||||||
nameservers: make([]net.IP, 0),
|
nameservers: make([]net.IP, 0),
|
||||||
}
|
}
|
||||||
if addresses, ok := optionMap["address"]; ok {
|
if addresses, ok := optionMap["address"]; ok {
|
||||||
if len(addresses) == 1 {
|
if len(addresses) == 1 {
|
||||||
config.address.IP = net.ParseIP(addresses[0])
|
config.addresses[0].IP = net.ParseIP(addresses[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if netmasks, ok := optionMap["netmask"]; ok {
|
if netmasks, ok := optionMap["netmask"]; ok {
|
||||||
if len(netmasks) == 1 {
|
if len(netmasks) == 1 {
|
||||||
config.address.Mask = net.IPMask(net.ParseIP(netmasks[0]).To4())
|
config.addresses[0].Mask = net.IPMask(net.ParseIP(netmasks[0]).To4())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if config.address.IP == nil || config.address.Mask == nil {
|
if config.addresses[0].IP == nil || config.addresses[0].Mask == nil {
|
||||||
return nil, fmt.Errorf("malformed static network config for %q", iface)
|
return nil, fmt.Errorf("malformed static network config for %q", iface)
|
||||||
}
|
}
|
||||||
if gateways, ok := optionMap["gateway"]; ok {
|
if gateways, ok := optionMap["gateway"]; ok {
|
||||||
|
@@ -194,9 +194,11 @@ func TestParseVLANStanzas(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseInterfaceStanzaStaticAddress(t *testing.T) {
|
func TestParseInterfaceStanzaStaticAddress(t *testing.T) {
|
||||||
options := []string{"address 192.168.1.100", "netmask 255.255.255.0"}
|
options := []string{"address 192.168.1.100", "netmask 255.255.255.0"}
|
||||||
expect := net.IPNet{
|
expect := []net.IPNet{
|
||||||
|
{
|
||||||
IP: net.IPv4(192, 168, 1, 100),
|
IP: net.IPv4(192, 168, 1, 100),
|
||||||
Mask: net.IPv4Mask(255, 255, 255, 0),
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
|
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
|
||||||
@@ -207,7 +209,7 @@ func TestParseInterfaceStanzaStaticAddress(t *testing.T) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(static.address, expect) {
|
if !reflect.DeepEqual(static.addresses, expect) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -74,15 +74,14 @@ func maybeProbe8012q(interfaces []network.InterfaceGenerator) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func maybeProbeBonding(interfaces []network.InterfaceGenerator) error {
|
func maybeProbeBonding(interfaces []network.InterfaceGenerator) error {
|
||||||
args := []string{"bonding"}
|
|
||||||
for _, iface := range interfaces {
|
for _, iface := range interfaces {
|
||||||
if iface.Type() == "bond" {
|
if iface.Type() == "bond" {
|
||||||
args = append(args, strings.Split(iface.ModprobeParams(), " ")...)
|
args := append([]string{"bonding"}, strings.Split(iface.ModprobeParams(), " ")...)
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Printf("Probing LKM %q (%q)\n", "bonding", args)
|
log.Printf("Probing LKM %q (%q)\n", "bonding", args)
|
||||||
return exec.Command("modprobe", args...).Run()
|
return exec.Command("modprobe", args...).Run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func restartNetworkd() error {
|
func restartNetworkd() error {
|
||||||
|
11
test
11
test
@@ -18,6 +18,9 @@ declare -a TESTPKGS=(initialize
|
|||||||
datasource
|
datasource
|
||||||
datasource/configdrive
|
datasource/configdrive
|
||||||
datasource/file
|
datasource/file
|
||||||
|
datasource/metadata
|
||||||
|
datasource/metadata/cloudsigma
|
||||||
|
datasource/metadata/digitalocean
|
||||||
datasource/metadata/ec2
|
datasource/metadata/ec2
|
||||||
datasource/proc_cmdline
|
datasource/proc_cmdline
|
||||||
datasource/url
|
datasource/url
|
||||||
@@ -25,9 +28,9 @@ declare -a TESTPKGS=(initialize
|
|||||||
network)
|
network)
|
||||||
|
|
||||||
if [ -z "$PKG" ]; then
|
if [ -z "$PKG" ]; then
|
||||||
GOFMTPATH="$TESTPKGS coreos-cloudinit.go"
|
GOFMTPATH="${TESTPKGS[*]} coreos-cloudinit.go"
|
||||||
# prepend repo path to each package
|
# prepend repo path to each package
|
||||||
TESTPKGS="${TESTPKGS[@]/#/${REPO_PATH}/} ./"
|
TESTPKGS="${TESTPKGS[*]/#/${REPO_PATH}/} ./"
|
||||||
else
|
else
|
||||||
GOFMTPATH="$TESTPKGS"
|
GOFMTPATH="$TESTPKGS"
|
||||||
# strip out slashes and dots from PKG=./foo/
|
# strip out slashes and dots from PKG=./foo/
|
||||||
@@ -42,5 +45,9 @@ go test ${COVER} $@ ${TESTPKGS}
|
|||||||
|
|
||||||
echo "Checking gofmt..."
|
echo "Checking gofmt..."
|
||||||
fmtRes=$(gofmt -l $GOFMTPATH)
|
fmtRes=$(gofmt -l $GOFMTPATH)
|
||||||
|
if [ -n "$fmtRes" ]; then
|
||||||
|
echo "$fmtRes"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Success"
|
echo "Success"
|
||||||
|
202
third_party/github.com/cloudsigma/cepgo/LICENSE
vendored
Normal file
202
third_party/github.com/cloudsigma/cepgo/LICENSE
vendored
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
43
third_party/github.com/cloudsigma/cepgo/README.md
vendored
Normal file
43
third_party/github.com/cloudsigma/cepgo/README.md
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
cepgo
|
||||||
|
=====
|
||||||
|
|
||||||
|
Cepko implements easy-to-use communication with CloudSigma's VMs through a
|
||||||
|
virtual serial port without bothering with formatting the messages properly nor
|
||||||
|
parsing the output with the specific and sometimes confusing shell tools for
|
||||||
|
that purpose.
|
||||||
|
|
||||||
|
Having the server definition accessible by the VM can be useful in various
|
||||||
|
ways. For example it is possible to easily determine from within the VM, which
|
||||||
|
network interfaces are connected to public and which to private network.
|
||||||
|
Another use is to pass some data to initial VM setup scripts, like setting the
|
||||||
|
hostname to the VM name or passing ssh public keys through server meta.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cloudsigma/cepgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c := cepgo.NewCepgo()
|
||||||
|
result, err := c.Meta()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%#v", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
map[string]interface {}{
|
||||||
|
"optimize_for":"custom",
|
||||||
|
"ssh_public_key":"ssh-rsa AAA...",
|
||||||
|
"description":"[...]",
|
||||||
|
}
|
||||||
|
|
||||||
|
For more information take a look at the Server Context section of CloudSigma
|
||||||
|
API Docs: http://cloudsigma-docs.readthedocs.org/en/latest/server_context.html
|
186
third_party/github.com/cloudsigma/cepgo/cepgo.go
vendored
Normal file
186
third_party/github.com/cloudsigma/cepgo/cepgo.go
vendored
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
// Cepko implements easy-to-use communication with CloudSigma's VMs through a
|
||||||
|
// virtual serial port without bothering with formatting the messages properly
|
||||||
|
// nor parsing the output with the specific and sometimes confusing shell tools
|
||||||
|
// for that purpose.
|
||||||
|
//
|
||||||
|
// Having the server definition accessible by the VM can be useful in various
|
||||||
|
// ways. For example it is possible to easily determine from within the VM,
|
||||||
|
// which network interfaces are connected to public and which to private
|
||||||
|
// network. Another use is to pass some data to initial VM setup scripts, like
|
||||||
|
// setting the hostname to the VM name or passing ssh public keys through
|
||||||
|
// server meta.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// package main
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "fmt"
|
||||||
|
//
|
||||||
|
// "github.com/cloudsigma/cepgo"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// c := cepgo.NewCepgo()
|
||||||
|
// result, err := c.Meta()
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// fmt.Printf("%#v", result)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Output:
|
||||||
|
//
|
||||||
|
// map[string]string{
|
||||||
|
// "optimize_for":"custom",
|
||||||
|
// "ssh_public_key":"ssh-rsa AAA...",
|
||||||
|
// "description":"[...]",
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// For more information take a look at the Server Context section API Docs:
|
||||||
|
// http://cloudsigma-docs.readthedocs.org/en/latest/server_context.html
|
||||||
|
package cepgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/third_party/github.com/tarm/goserial"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
requestPattern = "<\n%s\n>"
|
||||||
|
EOT = '\x04' // End Of Transmission
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
SerialPort string = "/dev/ttyS1"
|
||||||
|
Baud int = 115200
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sets the serial port. If the operating system is windows CloudSigma's server
|
||||||
|
// context is at COM2 port, otherwise (linux, freebsd, darwin) the port is
|
||||||
|
// being left to the default /dev/ttyS1.
|
||||||
|
func init() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
SerialPort = "COM2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The default fetcher makes the connection to the serial port,
|
||||||
|
// writes given query and reads until the EOT symbol.
|
||||||
|
func fetchViaSerialPort(key string) ([]byte, error) {
|
||||||
|
config := &serial.Config{Name: SerialPort, Baud: Baud}
|
||||||
|
connection, err := serial.OpenPort(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(requestPattern, key)
|
||||||
|
if _, err := connection.Write([]byte(query)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := bufio.NewReader(connection)
|
||||||
|
answer, err := reader.ReadBytes(EOT)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer[0 : len(answer)-1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queries to the serial port can be executed only from instance of this type.
|
||||||
|
// The result from each of them can be either interface{}, map[string]string or
|
||||||
|
// a single in case of single value is returned. There is also a public metod
|
||||||
|
// who directly calls the fetcher and returns raw []byte from the serial port.
|
||||||
|
type Cepgo struct {
|
||||||
|
fetcher func(string) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a Cepgo instance with the default serial port fetcher.
|
||||||
|
func NewCepgo() *Cepgo {
|
||||||
|
cepgo := new(Cepgo)
|
||||||
|
cepgo.fetcher = fetchViaSerialPort
|
||||||
|
return cepgo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a Cepgo instance with custom fetcher.
|
||||||
|
func NewCepgoFetcher(fetcher func(string) ([]byte, error)) *Cepgo {
|
||||||
|
cepgo := new(Cepgo)
|
||||||
|
cepgo.fetcher = fetcher
|
||||||
|
return cepgo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetches raw []byte from the serial port using directly the fetcher member.
|
||||||
|
func (c *Cepgo) FetchRaw(key string) ([]byte, error) {
|
||||||
|
return c.fetcher(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetches a single key and tries to unmarshal the result to json and returns
|
||||||
|
// it. If the unmarshalling fails it's safe to assume the result it's just a
|
||||||
|
// string and returns it.
|
||||||
|
func (c *Cepgo) Key(key string) (interface{}, error) {
|
||||||
|
var result interface{}
|
||||||
|
|
||||||
|
fetched, err := c.FetchRaw(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(fetched, &result)
|
||||||
|
if err != nil {
|
||||||
|
return string(fetched), nil
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetches all the server context. Equivalent of c.Key("")
|
||||||
|
func (c *Cepgo) All() (interface{}, error) {
|
||||||
|
return c.Key("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetches only the object meta field and makes sure to return a proper
|
||||||
|
// map[string]string
|
||||||
|
func (c *Cepgo) Meta() (map[string]string, error) {
|
||||||
|
rawMeta, err := c.Key("/meta/")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeAssertToMapOfStrings(rawMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetches only the global context and makes sure to return a proper
|
||||||
|
// map[string]string
|
||||||
|
func (c *Cepgo) GlobalContext() (map[string]string, error) {
|
||||||
|
rawContext, err := c.Key("/global_context/")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeAssertToMapOfStrings(rawContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just a little helper function that uses type assertions in order to convert
|
||||||
|
// a interface{} to map[string]string if this is possible.
|
||||||
|
func typeAssertToMapOfStrings(raw interface{}) (map[string]string, error) {
|
||||||
|
result := make(map[string]string)
|
||||||
|
|
||||||
|
dictionary, ok := raw.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("Received bytes are formatted badly")
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, rawValue := range dictionary {
|
||||||
|
if value, ok := rawValue.(string); ok {
|
||||||
|
result[key] = value
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Server context metadata is formatted badly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
122
third_party/github.com/cloudsigma/cepgo/cepgo_test.go
vendored
Normal file
122
third_party/github.com/cloudsigma/cepgo/cepgo_test.go
vendored
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package cepgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fetchMock(key string) ([]byte, error) {
|
||||||
|
context := []byte(`{
|
||||||
|
"context": true,
|
||||||
|
"cpu": 4000,
|
||||||
|
"cpu_model": null,
|
||||||
|
"cpus_instead_of_cores": false,
|
||||||
|
"enable_numa": false,
|
||||||
|
"global_context": {
|
||||||
|
"some_global_key": "some_global_val"
|
||||||
|
},
|
||||||
|
"grantees": [],
|
||||||
|
"hv_relaxed": false,
|
||||||
|
"hv_tsc": false,
|
||||||
|
"jobs": [],
|
||||||
|
"mem": 4294967296,
|
||||||
|
"meta": {
|
||||||
|
"base64_fields": "cloudinit-user-data",
|
||||||
|
"cloudinit-user-data": "I2Nsb3VkLWNvbmZpZwoKaG9zdG5hbWU6IGNvcmVvczE=",
|
||||||
|
"ssh_public_key": "ssh-rsa AAAAB2NzaC1yc2E.../hQ5D5 john@doe"
|
||||||
|
},
|
||||||
|
"name": "coreos",
|
||||||
|
"nics": [
|
||||||
|
{
|
||||||
|
"runtime": {
|
||||||
|
"interface_type": "public",
|
||||||
|
"ip_v4": {
|
||||||
|
"uuid": "31.171.251.74"
|
||||||
|
},
|
||||||
|
"ip_v6": null
|
||||||
|
},
|
||||||
|
"vlan": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"smp": 2,
|
||||||
|
"status": "running",
|
||||||
|
"uuid": "20a0059b-041e-4d0c-bcc6-9b2852de48b3"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
if key == "" {
|
||||||
|
return context, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var marshalledContext map[string]interface{}
|
||||||
|
|
||||||
|
err := json.Unmarshal(context, &marshalledContext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if key[0] == '/' {
|
||||||
|
key = key[1:]
|
||||||
|
}
|
||||||
|
if key[len(key)-1] == '/' {
|
||||||
|
key = key[:len(key)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(marshalledContext[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAll(t *testing.T) {
|
||||||
|
cepgo := NewCepgoFetcher(fetchMock)
|
||||||
|
|
||||||
|
result, err := cepgo.All()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range []string{"meta", "name", "uuid", "global_context"} {
|
||||||
|
if _, ok := result.(map[string]interface{})[key]; !ok {
|
||||||
|
t.Errorf("%s not in all keys", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKey(t *testing.T) {
|
||||||
|
cepgo := NewCepgoFetcher(fetchMock)
|
||||||
|
|
||||||
|
result, err := cepgo.Key("uuid")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := result.(string); !ok {
|
||||||
|
t.Errorf("%#v\n", result)
|
||||||
|
|
||||||
|
t.Error("Fetching the uuid did not return a string")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMeta(t *testing.T) {
|
||||||
|
cepgo := NewCepgoFetcher(fetchMock)
|
||||||
|
|
||||||
|
meta, err := cepgo.Meta()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%#v\n", meta)
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := meta["ssh_public_key"]; !ok {
|
||||||
|
t.Error("ssh_public_key is not in the meta")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlobalContext(t *testing.T) {
|
||||||
|
cepgo := NewCepgoFetcher(fetchMock)
|
||||||
|
|
||||||
|
result, err := cepgo.GlobalContext()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := result["some_global_key"]; !ok {
|
||||||
|
t.Error("some_global_key is not in the global context")
|
||||||
|
}
|
||||||
|
}
|
27
third_party/github.com/tarm/goserial/LICENSE
vendored
Normal file
27
third_party/github.com/tarm/goserial/LICENSE
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
63
third_party/github.com/tarm/goserial/README.md
vendored
Normal file
63
third_party/github.com/tarm/goserial/README.md
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
GoSerial
|
||||||
|
========
|
||||||
|
A simple go package to allow you to read and write from the
|
||||||
|
serial port as a stream of bytes.
|
||||||
|
|
||||||
|
Details
|
||||||
|
-------
|
||||||
|
It aims to have the same API on all platforms, including windows. As
|
||||||
|
an added bonus, the windows package does not use cgo, so you can cross
|
||||||
|
compile for windows from another platform. Unfortunately goinstall
|
||||||
|
does not currently let you cross compile so you will have to do it
|
||||||
|
manually:
|
||||||
|
|
||||||
|
GOOS=windows make clean install
|
||||||
|
|
||||||
|
Currently there is very little in the way of configurability. You can
|
||||||
|
set the baud rate. Then you can Read(), Write(), or Close() the
|
||||||
|
connection. Read() will block until at least one byte is returned.
|
||||||
|
Write is the same. There is currently no exposed way to set the
|
||||||
|
timeouts, though patches are welcome.
|
||||||
|
|
||||||
|
Currently all ports are opened with 8 data bits, 1 stop bit, no
|
||||||
|
parity, no hardware flow control, and no software flow control. This
|
||||||
|
works fine for many real devices and many faux serial devices
|
||||||
|
including usb-to-serial converters and bluetooth serial ports.
|
||||||
|
|
||||||
|
You may Read() and Write() simulantiously on the same connection (from
|
||||||
|
different goroutines).
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tarm/goserial"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c := &serial.Config{Name: "COM45", Baud: 115200}
|
||||||
|
s, err := serial.OpenPort(c)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := s.Write([]byte("test"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 128)
|
||||||
|
n, err = s.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Print("%q", buf[:n])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Possible Future Work
|
||||||
|
--------------------
|
||||||
|
- better tests (loopback etc)
|
39
third_party/github.com/tarm/goserial/basic_test.go
vendored
Normal file
39
third_party/github.com/tarm/goserial/basic_test.go
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package serial
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConnection(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c0 := &Config{Name: "COM5", Baud: 115200}
|
||||||
|
|
||||||
|
/*
|
||||||
|
c1 := new(Config)
|
||||||
|
c1.Name = "COM5"
|
||||||
|
c1.Baud = 115200
|
||||||
|
*/
|
||||||
|
|
||||||
|
s, err := OpenPort(c0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.Write([]byte("test"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 128)
|
||||||
|
_, err = s.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUG(tarmigan): Add loopback test
|
||||||
|
func TestLoopback(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
99
third_party/github.com/tarm/goserial/serial.go
vendored
Normal file
99
third_party/github.com/tarm/goserial/serial.go
vendored
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
Goserial is a simple go package to allow you to read and write from
|
||||||
|
the serial port as a stream of bytes.
|
||||||
|
|
||||||
|
It aims to have the same API on all platforms, including windows. As
|
||||||
|
an added bonus, the windows package does not use cgo, so you can cross
|
||||||
|
compile for windows from another platform. Unfortunately goinstall
|
||||||
|
does not currently let you cross compile so you will have to do it
|
||||||
|
manually:
|
||||||
|
|
||||||
|
GOOS=windows make clean install
|
||||||
|
|
||||||
|
Currently there is very little in the way of configurability. You can
|
||||||
|
set the baud rate. Then you can Read(), Write(), or Close() the
|
||||||
|
connection. Read() will block until at least one byte is returned.
|
||||||
|
Write is the same. There is currently no exposed way to set the
|
||||||
|
timeouts, though patches are welcome.
|
||||||
|
|
||||||
|
Currently all ports are opened with 8 data bits, 1 stop bit, no
|
||||||
|
parity, no hardware flow control, and no software flow control. This
|
||||||
|
works fine for many real devices and many faux serial devices
|
||||||
|
including usb-to-serial converters and bluetooth serial ports.
|
||||||
|
|
||||||
|
You may Read() and Write() simulantiously on the same connection (from
|
||||||
|
different goroutines).
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tarm/goserial"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c := &serial.Config{Name: "COM5", Baud: 115200}
|
||||||
|
s, err := serial.OpenPort(c)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := s.Write([]byte("test"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 128)
|
||||||
|
n, err = s.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Print("%q", buf[:n])
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package serial
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// Config contains the information needed to open a serial port.
|
||||||
|
//
|
||||||
|
// Currently few options are implemented, but more may be added in the
|
||||||
|
// future (patches welcome), so it is recommended that you create a
|
||||||
|
// new config addressing the fields by name rather than by order.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// c0 := &serial.Config{Name: "COM45", Baud: 115200}
|
||||||
|
// or
|
||||||
|
// c1 := new(serial.Config)
|
||||||
|
// c1.Name = "/dev/tty.usbserial"
|
||||||
|
// c1.Baud = 115200
|
||||||
|
//
|
||||||
|
type Config struct {
|
||||||
|
Name string
|
||||||
|
Baud int
|
||||||
|
|
||||||
|
// Size int // 0 get translated to 8
|
||||||
|
// Parity SomeNewTypeToGetCorrectDefaultOf_None
|
||||||
|
// StopBits SomeNewTypeToGetCorrectDefaultOf_1
|
||||||
|
|
||||||
|
// RTSFlowControl bool
|
||||||
|
// DTRFlowControl bool
|
||||||
|
// XONFlowControl bool
|
||||||
|
|
||||||
|
// CRLFTranslate bool
|
||||||
|
// TimeoutStuff int
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenPort opens a serial port with the specified configuration
|
||||||
|
func OpenPort(c *Config) (io.ReadWriteCloser, error) {
|
||||||
|
return openPort(c.Name, c.Baud)
|
||||||
|
}
|
||||||
|
|
||||||
|
// func Flush()
|
||||||
|
|
||||||
|
// func SendBreak()
|
||||||
|
|
||||||
|
// func RegisterBreakHandler(func())
|
90
third_party/github.com/tarm/goserial/serial_linux.go
vendored
Normal file
90
third_party/github.com/tarm/goserial/serial_linux.go
vendored
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// +build linux,!cgo
|
||||||
|
|
||||||
|
package serial
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openPort(name string, baud int) (rwc io.ReadWriteCloser, err error) {
|
||||||
|
|
||||||
|
var bauds = map[int]uint32{
|
||||||
|
50: syscall.B50,
|
||||||
|
75: syscall.B75,
|
||||||
|
110: syscall.B110,
|
||||||
|
134: syscall.B134,
|
||||||
|
150: syscall.B150,
|
||||||
|
200: syscall.B200,
|
||||||
|
300: syscall.B300,
|
||||||
|
600: syscall.B600,
|
||||||
|
1200: syscall.B1200,
|
||||||
|
1800: syscall.B1800,
|
||||||
|
2400: syscall.B2400,
|
||||||
|
4800: syscall.B4800,
|
||||||
|
9600: syscall.B9600,
|
||||||
|
19200: syscall.B19200,
|
||||||
|
38400: syscall.B38400,
|
||||||
|
57600: syscall.B57600,
|
||||||
|
115200: syscall.B115200,
|
||||||
|
230400: syscall.B230400,
|
||||||
|
460800: syscall.B460800,
|
||||||
|
500000: syscall.B500000,
|
||||||
|
576000: syscall.B576000,
|
||||||
|
921600: syscall.B921600,
|
||||||
|
1000000: syscall.B1000000,
|
||||||
|
1152000: syscall.B1152000,
|
||||||
|
1500000: syscall.B1500000,
|
||||||
|
2000000: syscall.B2000000,
|
||||||
|
2500000: syscall.B2500000,
|
||||||
|
3000000: syscall.B3000000,
|
||||||
|
3500000: syscall.B3500000,
|
||||||
|
4000000: syscall.B4000000,
|
||||||
|
}
|
||||||
|
|
||||||
|
rate := bauds[baud]
|
||||||
|
|
||||||
|
if rate == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile(name, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil && f != nil {
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
fd := f.Fd()
|
||||||
|
t := syscall.Termios{
|
||||||
|
Iflag: syscall.IGNPAR,
|
||||||
|
Cflag: syscall.CS8 | syscall.CREAD | syscall.CLOCAL | rate,
|
||||||
|
Cc: [32]uint8{syscall.VMIN: 1},
|
||||||
|
Ispeed: rate,
|
||||||
|
Ospeed: rate,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, _, errno := syscall.Syscall6(
|
||||||
|
syscall.SYS_IOCTL,
|
||||||
|
uintptr(fd),
|
||||||
|
uintptr(syscall.TCSETS),
|
||||||
|
uintptr(unsafe.Pointer(&t)),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
); errno != 0 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = syscall.SetNonblock(int(fd), false); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
107
third_party/github.com/tarm/goserial/serial_posix.go
vendored
Normal file
107
third_party/github.com/tarm/goserial/serial_posix.go
vendored
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// +build !windows,cgo
|
||||||
|
|
||||||
|
package serial
|
||||||
|
|
||||||
|
// #include <termios.h>
|
||||||
|
// #include <unistd.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// TODO: Maybe change to using syscall package + ioctl instead of cgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
//"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openPort(name string, baud int) (rwc io.ReadWriteCloser, err error) {
|
||||||
|
f, err := os.OpenFile(name, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fd := C.int(f.Fd())
|
||||||
|
if C.isatty(fd) != 1 {
|
||||||
|
f.Close()
|
||||||
|
return nil, errors.New("File is not a tty")
|
||||||
|
}
|
||||||
|
|
||||||
|
var st C.struct_termios
|
||||||
|
_, err = C.tcgetattr(fd, &st)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var speed C.speed_t
|
||||||
|
switch baud {
|
||||||
|
case 115200:
|
||||||
|
speed = C.B115200
|
||||||
|
case 57600:
|
||||||
|
speed = C.B57600
|
||||||
|
case 38400:
|
||||||
|
speed = C.B38400
|
||||||
|
case 19200:
|
||||||
|
speed = C.B19200
|
||||||
|
case 9600:
|
||||||
|
speed = C.B9600
|
||||||
|
case 4800:
|
||||||
|
speed = C.B4800
|
||||||
|
case 2400:
|
||||||
|
speed = C.B2400
|
||||||
|
default:
|
||||||
|
f.Close()
|
||||||
|
return nil, fmt.Errorf("Unknown baud rate %v", baud)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = C.cfsetispeed(&st, speed)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = C.cfsetospeed(&st, speed)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select local mode
|
||||||
|
st.c_cflag |= (C.CLOCAL | C.CREAD)
|
||||||
|
|
||||||
|
// Select raw mode
|
||||||
|
st.c_lflag &= ^C.tcflag_t(C.ICANON | C.ECHO | C.ECHOE | C.ISIG)
|
||||||
|
st.c_oflag &= ^C.tcflag_t(C.OPOST)
|
||||||
|
|
||||||
|
_, err = C.tcsetattr(fd, C.TCSANOW, &st)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Println("Tweaking", name)
|
||||||
|
r1, _, e := syscall.Syscall(syscall.SYS_FCNTL,
|
||||||
|
uintptr(f.Fd()),
|
||||||
|
uintptr(syscall.F_SETFL),
|
||||||
|
uintptr(0))
|
||||||
|
if e != 0 || r1 != 0 {
|
||||||
|
s := fmt.Sprint("Clearing NONBLOCK syscall error:", e, r1)
|
||||||
|
f.Close()
|
||||||
|
return nil, errors.New(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
r1, _, e = syscall.Syscall(syscall.SYS_IOCTL,
|
||||||
|
uintptr(f.Fd()),
|
||||||
|
uintptr(0x80045402), // IOSSIOSPEED
|
||||||
|
uintptr(unsafe.Pointer(&baud)));
|
||||||
|
if e != 0 || r1 != 0 {
|
||||||
|
s := fmt.Sprint("Baudrate syscall error:", e, r1)
|
||||||
|
f.Close()
|
||||||
|
return nil, os.NewError(s)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
263
third_party/github.com/tarm/goserial/serial_windows.go
vendored
Normal file
263
third_party/github.com/tarm/goserial/serial_windows.go
vendored
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package serial
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type serialPort struct {
|
||||||
|
f *os.File
|
||||||
|
fd syscall.Handle
|
||||||
|
rl sync.Mutex
|
||||||
|
wl sync.Mutex
|
||||||
|
ro *syscall.Overlapped
|
||||||
|
wo *syscall.Overlapped
|
||||||
|
}
|
||||||
|
|
||||||
|
type structDCB struct {
|
||||||
|
DCBlength, BaudRate uint32
|
||||||
|
flags [4]byte
|
||||||
|
wReserved, XonLim, XoffLim uint16
|
||||||
|
ByteSize, Parity, StopBits byte
|
||||||
|
XonChar, XoffChar, ErrorChar, EofChar, EvtChar byte
|
||||||
|
wReserved1 uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type structTimeouts struct {
|
||||||
|
ReadIntervalTimeout uint32
|
||||||
|
ReadTotalTimeoutMultiplier uint32
|
||||||
|
ReadTotalTimeoutConstant uint32
|
||||||
|
WriteTotalTimeoutMultiplier uint32
|
||||||
|
WriteTotalTimeoutConstant uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func openPort(name string, baud int) (rwc io.ReadWriteCloser, err error) {
|
||||||
|
if len(name) > 0 && name[0] != '\\' {
|
||||||
|
name = "\\\\.\\" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := syscall.CreateFile(syscall.StringToUTF16Ptr(name),
|
||||||
|
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
|
||||||
|
0,
|
||||||
|
nil,
|
||||||
|
syscall.OPEN_EXISTING,
|
||||||
|
syscall.FILE_ATTRIBUTE_NORMAL|syscall.FILE_FLAG_OVERLAPPED,
|
||||||
|
0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f := os.NewFile(uintptr(h), name)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = setCommState(h, baud); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = setupComm(h, 64, 64); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = setCommTimeouts(h); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = setCommMask(h); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ro, err := newOverlapped()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wo, err := newOverlapped()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
port := new(serialPort)
|
||||||
|
port.f = f
|
||||||
|
port.fd = h
|
||||||
|
port.ro = ro
|
||||||
|
port.wo = wo
|
||||||
|
|
||||||
|
return port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *serialPort) Close() error {
|
||||||
|
return p.f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *serialPort) Write(buf []byte) (int, error) {
|
||||||
|
p.wl.Lock()
|
||||||
|
defer p.wl.Unlock()
|
||||||
|
|
||||||
|
if err := resetEvent(p.wo.HEvent); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var n uint32
|
||||||
|
err := syscall.WriteFile(p.fd, buf, &n, p.wo)
|
||||||
|
if err != nil && err != syscall.ERROR_IO_PENDING {
|
||||||
|
return int(n), err
|
||||||
|
}
|
||||||
|
return getOverlappedResult(p.fd, p.wo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *serialPort) Read(buf []byte) (int, error) {
|
||||||
|
if p == nil || p.f == nil {
|
||||||
|
return 0, fmt.Errorf("Invalid port on read %v %v", p, p.f)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.rl.Lock()
|
||||||
|
defer p.rl.Unlock()
|
||||||
|
|
||||||
|
if err := resetEvent(p.ro.HEvent); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var done uint32
|
||||||
|
err := syscall.ReadFile(p.fd, buf, &done, p.ro)
|
||||||
|
if err != nil && err != syscall.ERROR_IO_PENDING {
|
||||||
|
return int(done), err
|
||||||
|
}
|
||||||
|
return getOverlappedResult(p.fd, p.ro)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
nSetCommState,
|
||||||
|
nSetCommTimeouts,
|
||||||
|
nSetCommMask,
|
||||||
|
nSetupComm,
|
||||||
|
nGetOverlappedResult,
|
||||||
|
nCreateEvent,
|
||||||
|
nResetEvent uintptr
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
k32, err := syscall.LoadLibrary("kernel32.dll")
|
||||||
|
if err != nil {
|
||||||
|
panic("LoadLibrary " + err.Error())
|
||||||
|
}
|
||||||
|
defer syscall.FreeLibrary(k32)
|
||||||
|
|
||||||
|
nSetCommState = getProcAddr(k32, "SetCommState")
|
||||||
|
nSetCommTimeouts = getProcAddr(k32, "SetCommTimeouts")
|
||||||
|
nSetCommMask = getProcAddr(k32, "SetCommMask")
|
||||||
|
nSetupComm = getProcAddr(k32, "SetupComm")
|
||||||
|
nGetOverlappedResult = getProcAddr(k32, "GetOverlappedResult")
|
||||||
|
nCreateEvent = getProcAddr(k32, "CreateEventW")
|
||||||
|
nResetEvent = getProcAddr(k32, "ResetEvent")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProcAddr(lib syscall.Handle, name string) uintptr {
|
||||||
|
addr, err := syscall.GetProcAddress(lib, name)
|
||||||
|
if err != nil {
|
||||||
|
panic(name + " " + err.Error())
|
||||||
|
}
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCommState(h syscall.Handle, baud int) error {
|
||||||
|
var params structDCB
|
||||||
|
params.DCBlength = uint32(unsafe.Sizeof(params))
|
||||||
|
|
||||||
|
params.flags[0] = 0x01 // fBinary
|
||||||
|
params.flags[0] |= 0x10 // Assert DSR
|
||||||
|
|
||||||
|
params.BaudRate = uint32(baud)
|
||||||
|
params.ByteSize = 8
|
||||||
|
|
||||||
|
r, _, err := syscall.Syscall(nSetCommState, 2, uintptr(h), uintptr(unsafe.Pointer(¶ms)), 0)
|
||||||
|
if r == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCommTimeouts(h syscall.Handle) error {
|
||||||
|
var timeouts structTimeouts
|
||||||
|
const MAXDWORD = 1<<32 - 1
|
||||||
|
timeouts.ReadIntervalTimeout = MAXDWORD
|
||||||
|
timeouts.ReadTotalTimeoutMultiplier = MAXDWORD
|
||||||
|
timeouts.ReadTotalTimeoutConstant = MAXDWORD - 1
|
||||||
|
|
||||||
|
/* From http://msdn.microsoft.com/en-us/library/aa363190(v=VS.85).aspx
|
||||||
|
|
||||||
|
For blocking I/O see below:
|
||||||
|
|
||||||
|
Remarks:
|
||||||
|
|
||||||
|
If an application sets ReadIntervalTimeout and
|
||||||
|
ReadTotalTimeoutMultiplier to MAXDWORD and sets
|
||||||
|
ReadTotalTimeoutConstant to a value greater than zero and
|
||||||
|
less than MAXDWORD, one of the following occurs when the
|
||||||
|
ReadFile function is called:
|
||||||
|
|
||||||
|
If there are any bytes in the input buffer, ReadFile returns
|
||||||
|
immediately with the bytes in the buffer.
|
||||||
|
|
||||||
|
If there are no bytes in the input buffer, ReadFile waits
|
||||||
|
until a byte arrives and then returns immediately.
|
||||||
|
|
||||||
|
If no bytes arrive within the time specified by
|
||||||
|
ReadTotalTimeoutConstant, ReadFile times out.
|
||||||
|
*/
|
||||||
|
|
||||||
|
r, _, err := syscall.Syscall(nSetCommTimeouts, 2, uintptr(h), uintptr(unsafe.Pointer(&timeouts)), 0)
|
||||||
|
if r == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupComm(h syscall.Handle, in, out int) error {
|
||||||
|
r, _, err := syscall.Syscall(nSetupComm, 3, uintptr(h), uintptr(in), uintptr(out))
|
||||||
|
if r == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCommMask(h syscall.Handle) error {
|
||||||
|
const EV_RXCHAR = 0x0001
|
||||||
|
r, _, err := syscall.Syscall(nSetCommMask, 2, uintptr(h), EV_RXCHAR, 0)
|
||||||
|
if r == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetEvent(h syscall.Handle) error {
|
||||||
|
r, _, err := syscall.Syscall(nResetEvent, 1, uintptr(h), 0, 0)
|
||||||
|
if r == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOverlapped() (*syscall.Overlapped, error) {
|
||||||
|
var overlapped syscall.Overlapped
|
||||||
|
r, _, err := syscall.Syscall6(nCreateEvent, 4, 0, 1, 0, 0, 0, 0)
|
||||||
|
if r == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
overlapped.HEvent = syscall.Handle(r)
|
||||||
|
return &overlapped, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOverlappedResult(h syscall.Handle, overlapped *syscall.Overlapped) (int, error) {
|
||||||
|
var n int
|
||||||
|
r, _, err := syscall.Syscall6(nGetOverlappedResult, 4,
|
||||||
|
uintptr(h),
|
||||||
|
uintptr(unsafe.Pointer(overlapped)),
|
||||||
|
uintptr(unsafe.Pointer(&n)), 1, 0, 0)
|
||||||
|
if r == 0 {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
Reference in New Issue
Block a user