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  }