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