187 lines
5.0 KiB
Go
187 lines
5.0 KiB
Go
// 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
|
|
}
|