feat(datasource/metadata): Add datasource for CloudSigma
This commit is contained in:
parent
57950b3ed9
commit
7320a2cbf2
@ -10,6 +10,7 @@ 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/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"
|
||||||
@ -29,12 +30,13 @@ var (
|
|||||||
printVersion bool
|
printVersion bool
|
||||||
ignoreFailure bool
|
ignoreFailure bool
|
||||||
sources struct {
|
sources struct {
|
||||||
file string
|
file string
|
||||||
configDrive string
|
configDrive string
|
||||||
metadataService bool
|
metadataService bool
|
||||||
ec2MetadataService string
|
ec2MetadataService string
|
||||||
url string
|
cloudSigmaMetadataService bool
|
||||||
procCmdLine bool
|
url string
|
||||||
|
procCmdLine bool
|
||||||
}
|
}
|
||||||
convertNetconf string
|
convertNetconf string
|
||||||
workspace string
|
workspace string
|
||||||
@ -48,6 +50,7 @@ func init() {
|
|||||||
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 data from the provided metadata service")
|
||||||
|
flag.BoolVar(&sources.cloudSigmaMetadataService, "from-cloudsigma-metadata", false, "Download data from CloudSigma server context")
|
||||||
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 (requires the -from-configdrive flag)")
|
||||||
@ -85,7 +88,7 @@ func main() {
|
|||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,6 +220,9 @@ 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.procCmdLine {
|
if sources.procCmdLine {
|
||||||
dss = append(dss, proc_cmdline.NewDatasource())
|
dss = append(dss, proc_cmdline.NewDatasource())
|
||||||
}
|
}
|
||||||
|
141
datasource/metadata/cloudsigma/server_context.go
Normal file
141
datasource/metadata/cloudsigma/server_context.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
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 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user