github.com/Tyktechnologies/tyk@v2.9.5+incompatible/gateway/service_discovery.go (about) 1 package gateway 2 3 import ( 4 "io/ioutil" 5 "net/http" 6 "strconv" 7 "strings" 8 9 "github.com/Jeffail/gabs" 10 11 "github.com/TykTechnologies/tyk/apidef" 12 ) 13 14 const arrayName = "tyk_array" 15 16 type ServiceDiscovery struct { 17 spec *apidef.ServiceDiscoveryConfiguration 18 isNested bool 19 isTargetList bool 20 endpointReturnsList bool 21 portSeperate bool 22 dataPath string 23 parentPath string 24 portPath string 25 targetPath string 26 } 27 28 func (s *ServiceDiscovery) Init(spec *apidef.ServiceDiscoveryConfiguration) { 29 s.spec = spec 30 s.isNested = spec.UseNestedQuery 31 s.isTargetList = spec.UseTargetList 32 s.endpointReturnsList = spec.EndpointReturnsList 33 s.targetPath = spec.TargetPath 34 35 if spec.PortDataPath != "" { 36 s.portSeperate = true 37 s.portPath = spec.PortDataPath 38 } 39 40 if spec.ParentDataPath != "" { 41 s.parentPath = spec.ParentDataPath 42 } 43 44 s.dataPath = spec.DataPath 45 } 46 47 func (s *ServiceDiscovery) getServiceData(name string) (string, error) { 48 log.Debug("Getting ", name) 49 resp, err := http.Get(name) 50 if err != nil { 51 return "", err 52 } 53 54 defer resp.Body.Close() 55 contents, err := ioutil.ReadAll(resp.Body) 56 if err != nil { 57 return "", err 58 } 59 60 return string(contents), nil 61 } 62 63 func (s *ServiceDiscovery) decodeToNameSpace(namespace string, jsonParsed *gabs.Container) interface{} { 64 log.Debug("Namespace: ", namespace) 65 value := jsonParsed.Path(namespace).Data() 66 return value 67 } 68 69 func (s *ServiceDiscovery) decodeToNameSpaceAsArray(namespace string, jsonParsed *gabs.Container) []*gabs.Container { 70 log.Debug("Array Namespace: ", namespace) 71 log.Debug("Container: ", jsonParsed) 72 value, _ := jsonParsed.Path(namespace).Children() 73 log.Debug("Array value:", value) 74 return value 75 } 76 77 func (s *ServiceDiscovery) addPortFromObject(host string, obj *gabs.Container) string { 78 if !s.portSeperate { 79 return host 80 } 81 // Grab the port object 82 port := s.decodeToNameSpace(s.portPath, obj) 83 84 switch x := port.(type) { 85 case []interface{}: 86 port = x[0] 87 } 88 89 var portToUse string 90 switch x := port.(type) { 91 case string: 92 portToUse = x 93 case float64: 94 portToUse = strconv.Itoa(int(x)) 95 } 96 97 return host + ":" + portToUse 98 } 99 100 func (s *ServiceDiscovery) NestedObject(item *gabs.Container) string { 101 parentData := s.decodeToNameSpace(s.parentPath, item) 102 // Get the data path from the decoded object 103 subContainer := gabs.Container{} 104 switch x := parentData.(type) { 105 case string: 106 s.ParseObject(x, &subContainer) 107 default: 108 log.Debug("Get Nested Object: parentData is not a string") 109 return "" 110 } 111 return s.Object(&subContainer) 112 } 113 114 func (s *ServiceDiscovery) Object(item *gabs.Container) string { 115 hostnameData := s.decodeToNameSpace(s.dataPath, item) 116 if str, ok := hostnameData.(string); ok { 117 // Get the port 118 str = s.addPortFromObject(str, item) 119 return str 120 } 121 log.Warning("Get Object: hostname is not a string") 122 return "" 123 } 124 125 func (s *ServiceDiscovery) Hostname(item *gabs.Container) string { 126 var hostname string 127 // Get a nested object 128 if s.isNested { 129 hostname = s.NestedObject(item) 130 } else { 131 hostname = s.Object(item) 132 } 133 return hostname 134 } 135 136 func (s *ServiceDiscovery) isList(val string) bool { 137 return strings.HasPrefix(val, "[") 138 } 139 140 func (s *ServiceDiscovery) SubObjectFromList(objList *gabs.Container) []string { 141 hostList := []string{} 142 var hostname string 143 var set []*gabs.Container 144 if s.endpointReturnsList { 145 // pre-process the object since we've nested it 146 set = s.decodeToNameSpaceAsArray(arrayName, objList) 147 log.Debug("set: ", set) 148 } else if s.isNested { // It's an object, but the value may be nested 149 // Get the actual raw string object 150 parentData := s.decodeToNameSpace(s.parentPath, objList) 151 // Get the data path from the decoded object 152 subContainer := gabs.Container{} 153 154 // Now check if this string is a list 155 nestedString, ok := parentData.(string) 156 if !ok { 157 log.Debug("parentData is not a string") 158 return hostList 159 } 160 if s.isList(nestedString) { 161 log.Debug("Yup, it's a list") 162 jsonData := s.rawListToObj(nestedString) 163 s.ParseObject(jsonData, &subContainer) 164 set = s.decodeToNameSpaceAsArray(arrayName, &subContainer) 165 166 // Hijack this here because we need to use a non-nested get 167 for _, item := range set { 168 log.Debug("Child in list: ", item) 169 hostname = s.Object(item) + s.targetPath 170 // Add to list 171 hostList = append(hostList, hostname) 172 } 173 return hostList 174 } 175 log.Debug("Not a list") 176 s.ParseObject(nestedString, &subContainer) 177 set = s.decodeToNameSpaceAsArray(s.dataPath, objList) 178 log.Debug("set (object list): ", objList) 179 } else if s.parentPath != "" { 180 set = s.decodeToNameSpaceAsArray(s.parentPath, objList) 181 } 182 183 for _, item := range set { 184 log.Debug("Child in list: ", item) 185 hostname = s.Hostname(item) + s.targetPath 186 // Add to list 187 hostList = append(hostList, hostname) 188 } 189 return hostList 190 } 191 192 func (s *ServiceDiscovery) SubObject(obj *gabs.Container) string { 193 return s.Hostname(obj) + s.targetPath 194 } 195 196 func (s *ServiceDiscovery) rawListToObj(rawData string) string { 197 // Modify to turn a list object into a regular object 198 return `{"` + arrayName + `":` + rawData + `}` 199 } 200 201 func (s *ServiceDiscovery) ParseObject(contents string, jsonParsed *gabs.Container) error { 202 log.Debug("Parsing raw data: ", contents) 203 jp, err := gabs.ParseJSON([]byte(contents)) 204 if err != nil { 205 log.Error(err) 206 } 207 *jsonParsed = *jp 208 log.Debug("Got:", jsonParsed) 209 return err 210 } 211 212 func (s *ServiceDiscovery) ProcessRawData(rawData string) (*apidef.HostList, error) { 213 var jsonParsed gabs.Container 214 215 hostlist := apidef.NewHostList() 216 217 if s.endpointReturnsList { 218 // Convert to an object 219 jsonData := s.rawListToObj(rawData) 220 if err := s.ParseObject(jsonData, &jsonParsed); err != nil { 221 log.Error("Parse object failed: ", err) 222 return nil, err 223 } 224 225 log.Debug("Parsed object list: ", jsonParsed) 226 // Treat JSON as a list and then apply the data path 227 if s.isTargetList { 228 // Get all values 229 asList := s.SubObjectFromList(&jsonParsed) 230 log.Debug("Host list:", asList) 231 hostlist.Set(asList) 232 return hostlist, nil 233 } 234 235 // Get the top value 236 list := s.SubObjectFromList(&jsonParsed) 237 var host string 238 for _, v := range list { 239 host = v 240 break 241 } 242 243 hostlist.Set([]string{host}) 244 return hostlist, nil 245 } 246 247 // It's an object 248 s.ParseObject(rawData, &jsonParsed) 249 if s.isTargetList { 250 // It's a list object 251 log.Debug("It's a target list - getting sub object from list") 252 log.Debug("Passing in: ", jsonParsed) 253 254 asList := s.SubObjectFromList(&jsonParsed) 255 hostlist.Set(asList) 256 log.Debug("Got from object: ", hostlist) 257 return hostlist, nil 258 } 259 260 // It's a single object 261 host := s.SubObject(&jsonParsed) 262 hostlist.Set([]string{host}) 263 264 return hostlist, nil 265 } 266 267 func (s *ServiceDiscovery) Target(serviceURL string) (*apidef.HostList, error) { 268 // Get the data 269 rawData, err := s.getServiceData(serviceURL) 270 if err != nil { 271 return nil, err 272 } 273 274 return s.ProcessRawData(rawData) 275 276 }