util/structfs: import https://github.com/unistack-org/go-structfs #186
							
								
								
									
										3
									
								
								util/structfs/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								util/structfs/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | # go-structfs | ||||||
|  |  | ||||||
|  | Expose struct data via http.FileServer | ||||||
							
								
								
									
										67
									
								
								util/structfs/metadata_digitalocean.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								util/structfs/metadata_digitalocean.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | package structfs | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type DigitalOceanMetadata struct { | ||||||
|  | 	Metadata struct { | ||||||
|  | 		V1 struct { | ||||||
|  | 			DropletID  int64    `json:"droplet_id"` | ||||||
|  | 			Hostname   string   `json:"hostname"` | ||||||
|  | 			VendorData string   `json:"vendor_data"` | ||||||
|  | 			PublicKeys []string `json:"public_keys"` | ||||||
|  | 			Region     string   `json:"region"` | ||||||
|  | 			Interfaces struct { | ||||||
|  | 				Private []struct { | ||||||
|  | 					IPv4 struct { | ||||||
|  | 						Address string `json:"ip_address"` | ||||||
|  | 						Netmask string `json:"netmask"` | ||||||
|  | 						Gateway string `json:"gateway"` | ||||||
|  | 					} | ||||||
|  | 					Mac  string `json:"mac"` | ||||||
|  | 					Type string `json:"type"` | ||||||
|  | 				} `json:"private"` | ||||||
|  | 				Public []struct { | ||||||
|  | 					IPv4 struct { | ||||||
|  | 						Address string `json:"ip_address"` | ||||||
|  | 						Netmask string `json:"netmask"` | ||||||
|  | 						Gateway string `json:"gateway"` | ||||||
|  | 					} `json:"ipv4"` | ||||||
|  | 					IPv6 struct { | ||||||
|  | 						Address string `json:"ip_address"` | ||||||
|  | 						CIDR    int    `json:"cidr"` | ||||||
|  | 						Gateway string `json:"gateway"` | ||||||
|  | 					} `json:"ipv6"` | ||||||
|  | 					Mac  string `json:"mac"` | ||||||
|  | 					Type string `json:"type"` | ||||||
|  | 				} `json:"public"` | ||||||
|  | 			} `json:"interfaces"` | ||||||
|  | 			FloatingIP struct { | ||||||
|  | 				IPv4 struct { | ||||||
|  | 					Active bool `json:"active"` | ||||||
|  | 				} `json:"ipv4"` | ||||||
|  | 			} `json:"floating_ip"` | ||||||
|  | 			DNS struct { | ||||||
|  | 				Nameservers []string `json:"nameservers"` | ||||||
|  | 			} `json:"dns"` | ||||||
|  | 			Features map[string]interface{} `json:"features"` | ||||||
|  | 		} `json:"v1"` | ||||||
|  | 	} `json:"metadata"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (stfs *DigitalOceanMetadata) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	switch r.URL.Path { | ||||||
|  | 	case "/metadata/v1.json": | ||||||
|  | 		json.NewEncoder(w).Encode(stfs.Metadata.V1) | ||||||
|  | 	default: | ||||||
|  | 		fs := FileServer(stfs, "json", time.Now()) | ||||||
|  | 		idx := strings.Index(r.URL.Path[1:], "/") | ||||||
|  | 		r.URL.Path = strings.Replace(r.URL.Path[idx+1:], "/metadata/v1/", "", 1) | ||||||
|  | 		r.RequestURI = r.URL.Path | ||||||
|  | 		fs.ServeHTTP(w, r) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								util/structfs/metadata_ec2.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								util/structfs/metadata_ec2.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | package structfs | ||||||
|  |  | ||||||
|  | type EC2Metadata struct { | ||||||
|  | 	Latest struct { | ||||||
|  | 		Metadata struct { | ||||||
|  | 			AMIID              int      `json:"ami-id"` | ||||||
|  | 			AMILaunchIndex     int      `json:"ami-launch-index"` | ||||||
|  | 			AMIManifestPath    string   `json:"ami-manifest-path"` | ||||||
|  | 			AncestorAMIIDs     []int    `json:"ancestor-ami-ids"` | ||||||
|  | 			BlockDeviceMapping []string `json:"block-device-mapping"` | ||||||
|  | 			InstanceID         int      `json:"instance-id"` | ||||||
|  | 			InstanceType       string   `json:"instance-type"` | ||||||
|  | 			LocalHostname      string   `json:"local-hostname"` | ||||||
|  | 			LocalIPv4          string   `json:"local-ipv4"` | ||||||
|  | 			kernelID           int      `json:"kernel-id"` | ||||||
|  | 			Placement          string   `json:"placement"` | ||||||
|  | 			AvailabilityZone   string   `json:"availability-zone"` | ||||||
|  | 			ProductCodes       string   `json:"product-codes"` | ||||||
|  | 			PublicHostname     string   `json:"public-hostname"` | ||||||
|  | 			PublicIPv4         string   `json:"public-ipv4"` | ||||||
|  | 			PublicKeys         []struct { | ||||||
|  | 				Key []string `json:"-"` | ||||||
|  | 			} `json:"public-keys"` | ||||||
|  | 			RamdiskID      int      `json:"ramdisk-id"` | ||||||
|  | 			ReservationID  int      `json:"reservation-id"` | ||||||
|  | 			SecurityGroups []string `json:"security-groups"` | ||||||
|  | 		} `json:"meta-data"` | ||||||
|  | 		Userdata string `json:"user-data"` | ||||||
|  | 	} `json:"latest"` | ||||||
|  | } | ||||||
							
								
								
									
										245
									
								
								util/structfs/structfs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								util/structfs/structfs.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,245 @@ | |||||||
|  | package structfs | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"net/http" | ||||||
|  | 	"os" | ||||||
|  | 	"reflect" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // FileServer creates new file server from the struct iface with specific tag and specific modtime | ||||||
|  | func FileServer(iface interface{}, tag string, modtime time.Time) http.Handler { | ||||||
|  | 	if modtime.IsZero() { | ||||||
|  | 		modtime = time.Now() | ||||||
|  | 	} | ||||||
|  | 	return &fs{iface: iface, tag: tag, modtime: modtime} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (fs *fs) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	upath := r.URL.Path | ||||||
|  | 	if !strings.HasPrefix(upath, "/") { | ||||||
|  | 		upath = "/" + upath | ||||||
|  | 		r.URL.Path = upath | ||||||
|  | 	} | ||||||
|  | 	f, err := fs.Open(r.URL.Path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		w.WriteHeader(http.StatusInternalServerError) | ||||||
|  | 		w.Write([]byte(err.Error())) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	w.Header().Set("Content-Type", "application/octet-stream") | ||||||
|  | 	http.ServeContent(w, r, r.URL.Path, fs.modtime, f) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type fs struct { | ||||||
|  | 	iface   interface{} | ||||||
|  | 	tag     string | ||||||
|  | 	modtime time.Time | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type file struct { | ||||||
|  | 	name    string | ||||||
|  | 	offset  int64 | ||||||
|  | 	data    []byte | ||||||
|  | 	modtime time.Time | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type fileInfo struct { | ||||||
|  | 	name    string | ||||||
|  | 	size    int64 | ||||||
|  | 	modtime time.Time | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (fi *fileInfo) Sys() interface{} { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (fi *fileInfo) Size() int64 { | ||||||
|  | 	return fi.size | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (fi *fileInfo) Name() string { | ||||||
|  | 	return fi.name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (fi *fileInfo) Mode() os.FileMode { | ||||||
|  | 	if strings.HasSuffix(fi.name, "/") { | ||||||
|  | 		return os.FileMode(0755) | os.ModeDir | ||||||
|  | 	} | ||||||
|  | 	return os.FileMode(0644) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (fi *fileInfo) IsDir() bool { | ||||||
|  | 	// disables additional open /index.html | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (fi *fileInfo) ModTime() time.Time { | ||||||
|  | 	return fi.modtime | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *file) Close() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *file) Read(b []byte) (int, error) { | ||||||
|  | 	var err error | ||||||
|  | 	var n int | ||||||
|  |  | ||||||
|  | 	if f.offset >= int64(len(f.data)) { | ||||||
|  | 		return 0, io.EOF | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(f.data) > 0 { | ||||||
|  | 		n = copy(b, f.data[f.offset:]) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if n < len(b) { | ||||||
|  | 		err = io.EOF | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	f.offset += int64(n) | ||||||
|  | 	return n, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *file) Readdir(count int) ([]os.FileInfo, error) { | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *file) Seek(offset int64, whence int) (int64, error) { | ||||||
|  | 	//	log.Printf("seek %d %d %s\n", offset, whence, f.name) | ||||||
|  | 	switch whence { | ||||||
|  | 	case os.SEEK_SET: | ||||||
|  | 		f.offset = offset | ||||||
|  | 	case os.SEEK_CUR: | ||||||
|  | 		f.offset += offset | ||||||
|  | 	case os.SEEK_END: | ||||||
|  | 		f.offset = int64(len(f.data)) + offset | ||||||
|  | 	} | ||||||
|  | 	return f.offset, nil | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *file) Stat() (os.FileInfo, error) { | ||||||
|  | 	return &fileInfo{name: f.name, size: int64(len(f.data)), modtime: f.modtime}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (fs *fs) Open(path string) (http.File, error) { | ||||||
|  | 	return newFile(path, fs.iface, fs.tag, fs.modtime) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func newFile(name string, iface interface{}, tag string, modtime time.Time) (*file, error) { | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	f := &file{name: name, modtime: modtime} | ||||||
|  | 	f.data, err = structItem(name, iface, tag) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return f, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func structItem(path string, iface interface{}, tag string) ([]byte, error) { | ||||||
|  | 	var buf []byte | ||||||
|  | 	var err error | ||||||
|  | 	var curiface interface{} | ||||||
|  |  | ||||||
|  | 	if path == "/" { | ||||||
|  | 		return getNames(iface, tag) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	idx := strings.Index(path[1:], "/") | ||||||
|  | 	switch { | ||||||
|  | 	case idx > 0: | ||||||
|  | 		curiface, err = getStruct(path[1:idx+1], iface, tag) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		buf, err = structItem(path[idx+1:], curiface, tag) | ||||||
|  | 	case idx == 0: | ||||||
|  | 		return getNames(iface, tag) | ||||||
|  | 	case idx < 0: | ||||||
|  | 		return getValue(path[1:], iface, tag) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return buf, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getNames(iface interface{}, tag string) ([]byte, error) { | ||||||
|  | 	var lines []string | ||||||
|  | 	s := reflectValue(iface) | ||||||
|  | 	typeOf := s.Type() | ||||||
|  | 	for i := 0; i < s.NumField(); i++ { | ||||||
|  | 		value := typeOf.Field(i).Tag.Get(tag) | ||||||
|  | 		if value != "" { | ||||||
|  | 			lines = append(lines, value) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(lines) > 0 { | ||||||
|  | 		return []byte(strings.Join(lines, "\n")), nil | ||||||
|  | 	} | ||||||
|  | 	return nil, fmt.Errorf("failed to find names") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getStruct(name string, iface interface{}, tag string) (interface{}, error) { | ||||||
|  | 	s := reflectValue(iface) | ||||||
|  | 	typeOf := s.Type() | ||||||
|  | 	for i := 0; i < s.NumField(); i++ { | ||||||
|  | 		if typeOf.Field(i).Tag.Get(tag) == name { | ||||||
|  | 			return s.Field(i).Interface(), nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil, fmt.Errorf("failed to find iface %T with name %s", iface, name) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getValue(name string, iface interface{}, tag string) ([]byte, error) { | ||||||
|  | 	s := reflectValue(iface) | ||||||
|  | 	typeOf := s.Type() | ||||||
|  | 	switch typeOf.Kind() { | ||||||
|  | 	case reflect.Map: | ||||||
|  | 		return []byte(fmt.Sprintf("%v", s.MapIndex(reflect.ValueOf(name)).Interface())), nil | ||||||
|  | 	default: | ||||||
|  | 		for i := 0; i < s.NumField(); i++ { | ||||||
|  | 			if typeOf.Field(i).Tag.Get(tag) != name { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			ifs := s.Field(i).Interface() | ||||||
|  | 			switch s.Field(i).Kind() { | ||||||
|  | 			case reflect.Slice: | ||||||
|  | 				var lines []string | ||||||
|  | 				for k := 0; k < s.Field(i).Len(); k++ { | ||||||
|  | 					lines = append(lines, fmt.Sprintf("%v", s.Field(i).Index(k))) | ||||||
|  | 				} | ||||||
|  | 				return []byte(strings.Join(lines, "\n")), nil | ||||||
|  | 			default: | ||||||
|  | 				return []byte(fmt.Sprintf("%v", ifs)), nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil, fmt.Errorf("failed to find %s in interface %T", name, iface) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func hasValidType(obj interface{}, types []reflect.Kind) bool { | ||||||
|  | 	for _, t := range types { | ||||||
|  | 		if reflect.TypeOf(obj).Kind() == t { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func reflectValue(obj interface{}) reflect.Value { | ||||||
|  | 	var val reflect.Value | ||||||
|  |  | ||||||
|  | 	if reflect.TypeOf(obj).Kind() == reflect.Ptr { | ||||||
|  | 		val = reflect.ValueOf(obj).Elem() | ||||||
|  | 	} else { | ||||||
|  | 		val = reflect.ValueOf(obj) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return val | ||||||
|  | } | ||||||
							
								
								
									
										133
									
								
								util/structfs/structfs_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								util/structfs/structfs_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | |||||||
|  | package structfs | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  | 	"reflect" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var doOrig = []byte(`{ | ||||||
|  |   "droplet_id":2756294, | ||||||
|  |   "hostname":"sample-droplet", | ||||||
|  |   "vendor_data":"#cloud-config\ndisable_root: false\nmanage_etc_hosts: true\n\ncloud_config_modules:\n - ssh\n - set_hostname\n - [ update_etc_hosts, once-per-instance ]\n\ncloud_final_modules:\n - scripts-vendor\n - scripts-per-once\n - scripts-per-boot\n - scripts-per-instance\n - scripts-user\n", | ||||||
|  |   "public_keys":["ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCcbi6cygCUmuNlB0KqzBpHXf7CFYb3VE4pDOf/RLJ8OFDjOM+fjF83a24QktSVIpQnHYpJJT2pQMBxD+ZmnhTbKv+OjwHSHwAfkBullAojgZKzz+oN35P4Ea4J78AvMrHw0zp5MknS+WKEDCA2c6iDRCq6/hZ13Mn64f6c372JK99X29lj/B4VQpKCQyG8PUSTFkb5DXTETGbzuiVft+vM6SF+0XZH9J6dQ7b4yD3sOder+M0Q7I7CJD4VpdVD/JFa2ycOS4A4dZhjKXzabLQXdkWHvYGgNPGA5lI73TcLUAueUYqdq3RrDRfaQ5Z0PEw0mDllCzhk5dQpkmmqNi0F sammy@digitalocean.com"], | ||||||
|  |   "region":"nyc3", | ||||||
|  |   "interfaces":{ | ||||||
|  |     "private":[ | ||||||
|  |       { | ||||||
|  |         "ipv4":{ | ||||||
|  |           "ip_address":"10.132.255.113", | ||||||
|  |           "netmask":"255.255.0.0", | ||||||
|  |           "gateway":"10.132.0.1" | ||||||
|  |         }, | ||||||
|  |         "mac":"04:01:2a:0f:2a:02", | ||||||
|  |         "type":"private" | ||||||
|  |       } | ||||||
|  |     ], | ||||||
|  |     "public":[ | ||||||
|  |       { | ||||||
|  |         "ipv4":{ | ||||||
|  |           "ip_address":"104.131.20.105", | ||||||
|  |           "netmask":"255.255.192.0", | ||||||
|  |           "gateway":"104.131.0.1" | ||||||
|  |         }, | ||||||
|  |         "ipv6":{ | ||||||
|  |           "ip_address":"2604:A880:0800:0010:0000:0000:017D:2001", | ||||||
|  |           "cidr":64, | ||||||
|  |           "gateway":"2604:A880:0800:0010:0000:0000:0000:0001" | ||||||
|  |         }, | ||||||
|  |         "mac":"04:01:2a:0f:2a:01", | ||||||
|  |         "type":"public"} | ||||||
|  |     ] | ||||||
|  |   }, | ||||||
|  |   "floating_ip": { | ||||||
|  |     "ipv4": { | ||||||
|  |       "active": false | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "dns":{ | ||||||
|  |     "nameservers":[ | ||||||
|  |       "2001:4860:4860::8844", | ||||||
|  |       "2001:4860:4860::8888", | ||||||
|  |       "8.8.8.8" | ||||||
|  |     ] | ||||||
|  |   }, | ||||||
|  |   "features":{ | ||||||
|  |     "dhcp_enabled": true | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | `) | ||||||
|  |  | ||||||
|  | func server(t *testing.T) { | ||||||
|  | 	stfs := DigitalOceanMetadata{} | ||||||
|  | 	err := json.Unmarshal(doOrig, &stfs.Metadata.V1) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	http.Handle("/metadata/v1/", FileServer(&stfs, "json", time.Now())) | ||||||
|  | 	http.Handle("/metadata/v1.json", &stfs) | ||||||
|  | 	go func() { | ||||||
|  | 		t.Fatal(http.ListenAndServe("127.0.0.1:8080", nil)) | ||||||
|  | 	}() | ||||||
|  | 	time.Sleep(2 * time.Second) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func get(path string) ([]byte, error) { | ||||||
|  | 	res, err := http.Get(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	defer res.Body.Close() | ||||||
|  | 	return ioutil.ReadAll(res.Body) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestAll(t *testing.T) { | ||||||
|  | 	server(t) | ||||||
|  |  | ||||||
|  | 	var tests = []struct { | ||||||
|  | 		in  string | ||||||
|  | 		out string | ||||||
|  | 	}{ | ||||||
|  | 		{"http://127.0.0.1:8080/metadata/v1/", "droplet_id\nhostname\nvendor_data\npublic_keys\nregion\ninterfaces\nfloating_ip\ndns\nfeatures"}, | ||||||
|  | 		{"http://127.0.0.1:8080/metadata/v1/droplet_id", "2756294"}, | ||||||
|  | 		{"http://127.0.0.1:8080/metadata/v1/dns/", "nameservers"}, | ||||||
|  | 		{"http://127.0.0.1:8080/metadata/v1/dns/nameservers", "2001:4860:4860::8844\n2001:4860:4860::8888\n8.8.8.8"}, | ||||||
|  | 		{"http://127.0.0.1:8080/metadata/v1/features/dhcp_enabled", "true"}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		buf, err := get(tt.in) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		if string(buf) != tt.out { | ||||||
|  | 			t.Errorf("req %s output %s not match requested %s", tt.in, string(buf), tt.out) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	doTest, err := get("http://127.0.0.1:8080/metadata/v1.json") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	oSt := DigitalOceanMetadata{} | ||||||
|  | 	err = json.Unmarshal(doOrig, &oSt.Metadata.V1) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	nSt := DigitalOceanMetadata{} | ||||||
|  |  | ||||||
|  | 	err = json.Unmarshal(doTest, &nSt.Metadata.V1) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !reflect.DeepEqual(oSt, nSt) { | ||||||
|  | 		t.Fatalf("%v not match %v", oSt, nSt) | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user