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  }