mirror of
				https://github.com/coredns/coredns.git
				synced 2025-11-03 18:53:13 -05:00 
			
		
		
		
	* Removing unnecessary gitignore pattern
* Updating Makefile to run unittests for subpackages
* Adding Corefile validation to ignore overlapping zones
* Fixing SRV query handling
* Updating README.md now that SRV works
* Fixing debug message, adding code comment
* Clarifying implementation of zone normalization
* "Overlapping zones" is ill-defined. Reimplemented zone overlap/subzone
  checking to contain these functions in k8s middleware and provide
  better code comments explaining the normalization.
* Separate build verbosity from test verbosity
* Cleaning up comments to match repo code style
* Merging warning messages into single message
* Moving function docs to before function declaration
* Adding test cases for k8sclient connector
* Tests cover connector create and setting base url
* Fixed bugs in connector create and setting base url functions
* Updaing README to group and order development work
* Priority focused on achieving functional parity with SkyDNS.
* Adding work items to README and cleaning up formatting
* More README format cleaning
* List formating
* Refactoring k8s API call to allow dependency injection
* Add test cases for data parsing from k8s into dataobject structures
* URL is dependency-injected to allow replacement with a mock http
  server during test execution
* Adding more data validation for JSON parsing tests
* Adding test case for GetResourceList()
* Adding notes about SkyDNS embedded IP and port record names
* Marked test case implemented.
* Fixing formatting for example command.
* Fixing formatting
* Adding notes about Docker image building.
* Adding SkyDNS work item
* Updating TODO list
* Adding name template to Corefile to specify how k8s record names are assembled
* Adding template support for multi-segment zones
* Updating example CoreFile for k8s with template comment
* Misc whitespace cleanup
* Adding SkyDNS naming notes
* Adding namespace filtering to CoreFile config
* Updating example k8sCoreFile to specify namespaces
* Removing unused codepath
* Adding check for valid namespace
* More README TODO restructuring to focus effort
* Adding template validation while parsing CoreFile
* Record name template is considered invalid if it contains a symbol of the form ${bar} where the symbol
  "${bar}" is not an accepted template symbol.
* Refactoring generation of answer records
* Parse typeName out of query string
* Refactor answer record creation as operation over list of ServiceItems
* Moving k8s API caching into SkyDNS equivalency segment
* Adding function to assemble record names from template
* Warning: This commit may be broken. Syncing to get laptop code over to dev machine.
* More todo notes
* Adding comment describing sample test data.
* Update k8sCorefile
* Adding comment
* Adding filtering support for kubernetes "type"
* Required refactoring to support reuse of the StringInSlice function.
* Cleaning up formatting
* Adding note about SkyDNS supporting word "any".
* baseUrl -> baseURL
* Also removed debug statement from core/setup/kubernetes.go
* Fixing test breaking from Url -> URL naming changes
* Changing record name template language ${...} -> {...}
* Fix formatting with go fmt
* Updating all k8sclient data getters to return error value
* Adding error message to k8sclient data accessors
* Cleaning up setup for kubernetes
* Removed verbose nils in initial k8s middleware instance
* Set reasonable defaults if CoreFile has no parameters in the
kubernetes block. (k8s endpoint, and name template)
* Formatting cleanup -- go fmt
		
	
		
			
				
	
	
		
			681 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			681 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package k8sclient
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"net/http"
 | 
						|
	"net/http/httptest"
 | 
						|
	"testing"
 | 
						|
)
 | 
						|
 | 
						|
var validURLs = []string{
 | 
						|
	"http://www.github.com",
 | 
						|
	"http://www.github.com:8080",
 | 
						|
	"http://8.8.8.8",
 | 
						|
	"http://8.8.8.8:9090",
 | 
						|
	"www.github.com:8080",
 | 
						|
}
 | 
						|
 | 
						|
var invalidURLs = []string{
 | 
						|
	"www.github.com",
 | 
						|
	"8.8.8.8",
 | 
						|
	"8.8.8.8:1010",
 | 
						|
	"8.8`8.8",
 | 
						|
}
 | 
						|
 | 
						|
func TestNewK8sConnector(t *testing.T) {
 | 
						|
	var conn *K8sConnector
 | 
						|
	var url string
 | 
						|
 | 
						|
	// Create with empty URL
 | 
						|
	conn = nil
 | 
						|
	url = ""
 | 
						|
 | 
						|
	conn = NewK8sConnector("")
 | 
						|
	if conn == nil {
 | 
						|
		t.Errorf("Expected K8sConnector instance. Instead got '%v'", conn)
 | 
						|
	}
 | 
						|
	url = conn.GetBaseURL()
 | 
						|
	if url != defaultBaseURL {
 | 
						|
		t.Errorf("Expected K8sConnector instance to be initialized with defaultBaseURL. Instead got '%v'", url)
 | 
						|
	}
 | 
						|
 | 
						|
	// Create with valid URL
 | 
						|
	for _, validURL := range validURLs {
 | 
						|
		conn = nil
 | 
						|
		url = ""
 | 
						|
 | 
						|
		conn = NewK8sConnector(validURL)
 | 
						|
		if conn == nil {
 | 
						|
			t.Errorf("Expected K8sConnector instance. Instead got '%v'", conn)
 | 
						|
		}
 | 
						|
		url = conn.GetBaseURL()
 | 
						|
		if url != validURL {
 | 
						|
			t.Errorf("Expected K8sConnector instance to be initialized with supplied url '%v'. Instead got '%v'", validURL, url)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Create with invalid URL
 | 
						|
	for _, invalidURL := range invalidURLs {
 | 
						|
		conn = nil
 | 
						|
		url = ""
 | 
						|
 | 
						|
		conn = NewK8sConnector(invalidURL)
 | 
						|
		if conn != nil {
 | 
						|
			t.Errorf("Expected to not get K8sConnector instance. Instead got '%v'", conn)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestSetBaseURL(t *testing.T) {
 | 
						|
	// SetBaseURL with valid URLs should work...
 | 
						|
	for _, validURL := range validURLs {
 | 
						|
		conn := NewK8sConnector(defaultBaseURL)
 | 
						|
		err := conn.SetBaseURL(validURL)
 | 
						|
		if err != nil {
 | 
						|
			t.Errorf("Expected to receive nil, instead got error '%v'", err)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		url := conn.GetBaseURL()
 | 
						|
		if url != validURL {
 | 
						|
			t.Errorf("Expected to connector url to be set to value '%v', instead set to '%v'", validURL, url)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// SetBaseURL with invalid or non absolute URLs should not change state...
 | 
						|
	for _, invalidURL := range invalidURLs {
 | 
						|
		conn := NewK8sConnector(defaultBaseURL)
 | 
						|
		originalURL := conn.GetBaseURL()
 | 
						|
 | 
						|
		err := conn.SetBaseURL(invalidURL)
 | 
						|
		if err == nil {
 | 
						|
			t.Errorf("Expected to receive an error value, instead got nil")
 | 
						|
		}
 | 
						|
		url := conn.GetBaseURL()
 | 
						|
		if url != originalURL {
 | 
						|
			t.Errorf("Expected base url to not change, instead it changed to '%v'", url)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestGetNamespaceList(t *testing.T) {
 | 
						|
	// Set up a test http server
 | 
						|
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
						|
		fmt.Fprintln(w, namespaceListJsonData)
 | 
						|
	}))
 | 
						|
	defer testServer.Close()
 | 
						|
 | 
						|
	// Overwrite URL constructor to access testServer
 | 
						|
	makeURL = func(parts []string) string {
 | 
						|
		return testServer.URL
 | 
						|
	}
 | 
						|
 | 
						|
	expectedNamespaces := []string{"default", "demo", "test"}
 | 
						|
	apiConn := NewK8sConnector("")
 | 
						|
	namespaceList, err := apiConn.GetNamespaceList()
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("Expected no error from from GetNamespaceList(), instead got %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if namespaceList == nil {
 | 
						|
		t.Errorf("Expected data from GetNamespaceList(), instead got nil")
 | 
						|
	}
 | 
						|
 | 
						|
	kind := namespaceList.Kind
 | 
						|
	if kind != "NamespaceList" {
 | 
						|
		t.Errorf("Expected data from GetNamespaceList() to have Kind='NamespaceList', instead got Kind='%v'", kind)
 | 
						|
	}
 | 
						|
 | 
						|
	// Ensure correct number of namespaces found
 | 
						|
	expectedCount := len(expectedNamespaces)
 | 
						|
	namespaceCount := len(namespaceList.Items)
 | 
						|
	if namespaceCount != expectedCount {
 | 
						|
		t.Errorf("Expected '%v' namespaces from GetNamespaceList(), instead found '%v' namespaces", expectedCount, namespaceCount)
 | 
						|
	}
 | 
						|
 | 
						|
	// Check that all expectedNamespaces are found in the parsed data
 | 
						|
	for _, ns := range expectedNamespaces {
 | 
						|
		found := false
 | 
						|
		for _, item := range namespaceList.Items {
 | 
						|
			if item.Metadata.Name == ns {
 | 
						|
				found = true
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !found {
 | 
						|
			t.Errorf("Expected '%v' namespace is not in the parsed data from GetServicesByNamespace()", ns)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestGetServiceList(t *testing.T) {
 | 
						|
	// Set up a test http server
 | 
						|
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
						|
		fmt.Fprintln(w, serviceListJsonData)
 | 
						|
	}))
 | 
						|
	defer testServer.Close()
 | 
						|
 | 
						|
	// Overwrite URL constructor to access testServer
 | 
						|
	makeURL = func(parts []string) string {
 | 
						|
		return testServer.URL
 | 
						|
	}
 | 
						|
 | 
						|
	expectedServices := []string{"kubernetes", "mynginx", "mywebserver"}
 | 
						|
	apiConn := NewK8sConnector("")
 | 
						|
	serviceList, err := apiConn.GetServiceList()
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("Expected no error from from GetNamespaceList(), instead got %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if serviceList == nil {
 | 
						|
		t.Errorf("Expected data from GetServiceList(), instead got nil")
 | 
						|
	}
 | 
						|
 | 
						|
	kind := serviceList.Kind
 | 
						|
	if kind != "ServiceList" {
 | 
						|
		t.Errorf("Expected data from GetServiceList() to have Kind='ServiceList', instead got Kind='%v'", kind)
 | 
						|
	}
 | 
						|
 | 
						|
	// Ensure correct number of services found
 | 
						|
	expectedCount := len(expectedServices)
 | 
						|
	serviceCount := len(serviceList.Items)
 | 
						|
	if serviceCount != expectedCount {
 | 
						|
		t.Errorf("Expected '%v' services from GetServiceList(), instead found '%v' services", expectedCount, serviceCount)
 | 
						|
	}
 | 
						|
 | 
						|
	// Check that all expectedServices are found in the parsed data
 | 
						|
	for _, s := range expectedServices {
 | 
						|
		found := false
 | 
						|
		for _, item := range serviceList.Items {
 | 
						|
			if item.Metadata.Name == s {
 | 
						|
				found = true
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !found {
 | 
						|
			t.Errorf("Expected '%v' service is not in the parsed data from GetServiceList()", s)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestGetServicesByNamespace(t *testing.T) {
 | 
						|
	// Set up a test http server
 | 
						|
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
						|
		fmt.Fprintln(w, serviceListJsonData)
 | 
						|
	}))
 | 
						|
	defer testServer.Close()
 | 
						|
 | 
						|
	// Overwrite URL constructor to access testServer
 | 
						|
	makeURL = func(parts []string) string {
 | 
						|
		return testServer.URL
 | 
						|
	}
 | 
						|
 | 
						|
	expectedNamespaces := []string{"default", "demo"}
 | 
						|
	apiConn := NewK8sConnector("")
 | 
						|
	servicesByNamespace, err := apiConn.GetServicesByNamespace()
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("Expected no error from from GetServicesByNamespace(), instead got %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Ensure correct number of namespaces found
 | 
						|
	expectedCount := len(expectedNamespaces)
 | 
						|
	namespaceCount := len(servicesByNamespace)
 | 
						|
	if namespaceCount != expectedCount {
 | 
						|
		t.Errorf("Expected '%v' namespaces from GetServicesByNamespace(), instead found '%v' namespaces", expectedCount, namespaceCount)
 | 
						|
	}
 | 
						|
 | 
						|
	// Check that all expectedNamespaces are found in the parsed data
 | 
						|
	for _, ns := range expectedNamespaces {
 | 
						|
		_, ok := servicesByNamespace[ns]
 | 
						|
		if !ok {
 | 
						|
			t.Errorf("Expected '%v' namespace is not in the parsed data from GetServicesByNamespace()", ns)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestGetResourceList(t *testing.T) {
 | 
						|
	// Set up a test http server
 | 
						|
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
						|
		fmt.Fprintln(w, resourceListJsonData)
 | 
						|
	}))
 | 
						|
	defer testServer.Close()
 | 
						|
 | 
						|
	// Overwrite URL constructor to access testServer
 | 
						|
	makeURL = func(parts []string) string {
 | 
						|
		return testServer.URL
 | 
						|
	}
 | 
						|
 | 
						|
	expectedResources := []string{"bindings",
 | 
						|
		"componentstatuses",
 | 
						|
		"configmaps",
 | 
						|
		"endpoints",
 | 
						|
		"events",
 | 
						|
		"limitranges",
 | 
						|
		"namespaces",
 | 
						|
		"namespaces/finalize",
 | 
						|
		"namespaces/status",
 | 
						|
		"nodes",
 | 
						|
		"nodes/proxy",
 | 
						|
		"nodes/status",
 | 
						|
		"persistentvolumeclaims",
 | 
						|
		"persistentvolumeclaims/status",
 | 
						|
		"persistentvolumes",
 | 
						|
		"persistentvolumes/status",
 | 
						|
		"pods",
 | 
						|
		"pods/attach",
 | 
						|
		"pods/binding",
 | 
						|
		"pods/exec",
 | 
						|
		"pods/log",
 | 
						|
		"pods/portforward",
 | 
						|
		"pods/proxy",
 | 
						|
		"pods/status",
 | 
						|
		"podtemplates",
 | 
						|
		"replicationcontrollers",
 | 
						|
		"replicationcontrollers/scale",
 | 
						|
		"replicationcontrollers/status",
 | 
						|
		"resourcequotas",
 | 
						|
		"resourcequotas/status",
 | 
						|
		"secrets",
 | 
						|
		"serviceaccounts",
 | 
						|
		"services",
 | 
						|
		"services/proxy",
 | 
						|
		"services/status",
 | 
						|
	}
 | 
						|
	apiConn := NewK8sConnector("")
 | 
						|
	resourceList, err := apiConn.GetResourceList()
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("Expected no error from from GetResourceList(), instead got %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if resourceList == nil {
 | 
						|
		t.Errorf("Expected data from GetResourceList(), instead got nil")
 | 
						|
	}
 | 
						|
 | 
						|
	kind := resourceList.Kind
 | 
						|
	if kind != "APIResourceList" {
 | 
						|
		t.Errorf("Expected data from GetResourceList() to have Kind='ResourceList', instead got Kind='%v'", kind)
 | 
						|
	}
 | 
						|
 | 
						|
	// Ensure correct number of resources found
 | 
						|
	expectedCount := len(expectedResources)
 | 
						|
	resourceCount := len(resourceList.Resources)
 | 
						|
	if resourceCount != expectedCount {
 | 
						|
		t.Errorf("Expected '%v' resources from GetResourceList(), instead found '%v' resources", expectedCount, resourceCount)
 | 
						|
	}
 | 
						|
 | 
						|
	// Check that all expectedResources are found in the parsed data
 | 
						|
	for _, r := range expectedResources {
 | 
						|
		found := false
 | 
						|
		for _, item := range resourceList.Resources {
 | 
						|
			if item.Name == r {
 | 
						|
				found = true
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !found {
 | 
						|
			t.Errorf("Expected '%v' resource is not in the parsed data from GetResourceList()", r)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Sample namespace data for kubernetes with 3 namespaces:
 | 
						|
// "default", "demo", and "test".
 | 
						|
const namespaceListJsonData string = `{
 | 
						|
  "kind": "NamespaceList",
 | 
						|
  "apiVersion": "v1",
 | 
						|
  "metadata": {
 | 
						|
    "selfLink": "/api/v1/namespaces/",
 | 
						|
    "resourceVersion": "121279"
 | 
						|
  },
 | 
						|
  "items": [
 | 
						|
    {
 | 
						|
      "metadata": {
 | 
						|
        "name": "default",
 | 
						|
        "selfLink": "/api/v1/namespaces/default",
 | 
						|
        "uid": "fb1c92d1-2f39-11e6-b9db-0800279930f6",
 | 
						|
        "resourceVersion": "6",
 | 
						|
        "creationTimestamp": "2016-06-10T18:34:35Z"
 | 
						|
      },
 | 
						|
      "spec": {
 | 
						|
        "finalizers": [
 | 
						|
          "kubernetes"
 | 
						|
        ]
 | 
						|
      },
 | 
						|
      "status": {
 | 
						|
        "phase": "Active"
 | 
						|
      }
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "metadata": {
 | 
						|
        "name": "demo",
 | 
						|
        "selfLink": "/api/v1/namespaces/demo",
 | 
						|
        "uid": "73be8ffd-2f3a-11e6-b9db-0800279930f6",
 | 
						|
        "resourceVersion": "111",
 | 
						|
        "creationTimestamp": "2016-06-10T18:37:57Z"
 | 
						|
      },
 | 
						|
      "spec": {
 | 
						|
        "finalizers": [
 | 
						|
          "kubernetes"
 | 
						|
        ]
 | 
						|
      },
 | 
						|
      "status": {
 | 
						|
        "phase": "Active"
 | 
						|
      }
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "metadata": {
 | 
						|
        "name": "test",
 | 
						|
        "selfLink": "/api/v1/namespaces/test",
 | 
						|
        "uid": "c0be05fa-3352-11e6-b9db-0800279930f6",
 | 
						|
        "resourceVersion": "121276",
 | 
						|
        "creationTimestamp": "2016-06-15T23:41:59Z"
 | 
						|
      },
 | 
						|
      "spec": {
 | 
						|
        "finalizers": [
 | 
						|
          "kubernetes"
 | 
						|
        ]
 | 
						|
      },
 | 
						|
      "status": {
 | 
						|
        "phase": "Active"
 | 
						|
      }
 | 
						|
    }
 | 
						|
  ]
 | 
						|
}`
 | 
						|
 | 
						|
// Sample service data for kubernetes with 3 services:
 | 
						|
//	* "kubernetes" (in "default" namespace)
 | 
						|
//	* "mynginx" (in "demo" namespace)
 | 
						|
//	* "webserver" (in "demo" namespace)
 | 
						|
const serviceListJsonData string = `
 | 
						|
{
 | 
						|
  "kind": "ServiceList",
 | 
						|
  "apiVersion": "v1",
 | 
						|
  "metadata": {
 | 
						|
    "selfLink": "/api/v1/services",
 | 
						|
    "resourceVersion": "147965"
 | 
						|
  },
 | 
						|
  "items": [
 | 
						|
    {
 | 
						|
      "metadata": {
 | 
						|
        "name": "kubernetes",
 | 
						|
        "namespace": "default",
 | 
						|
        "selfLink": "/api/v1/namespaces/default/services/kubernetes",
 | 
						|
        "uid": "fb1cb0d3-2f39-11e6-b9db-0800279930f6",
 | 
						|
        "resourceVersion": "7",
 | 
						|
        "creationTimestamp": "2016-06-10T18:34:35Z",
 | 
						|
        "labels": {
 | 
						|
          "component": "apiserver",
 | 
						|
          "provider": "kubernetes"
 | 
						|
        }
 | 
						|
      },
 | 
						|
      "spec": {
 | 
						|
        "ports": [
 | 
						|
          {
 | 
						|
            "name": "https",
 | 
						|
            "protocol": "TCP",
 | 
						|
            "port": 443,
 | 
						|
            "targetPort": 443
 | 
						|
          }
 | 
						|
        ],
 | 
						|
        "clusterIP": "10.0.0.1",
 | 
						|
        "type": "ClusterIP",
 | 
						|
        "sessionAffinity": "None"
 | 
						|
      },
 | 
						|
      "status": {
 | 
						|
        "loadBalancer": {}
 | 
						|
      }
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "metadata": {
 | 
						|
        "name": "mynginx",
 | 
						|
        "namespace": "demo",
 | 
						|
        "selfLink": "/api/v1/namespaces/demo/services/mynginx",
 | 
						|
        "uid": "93c117ac-2f3a-11e6-b9db-0800279930f6",
 | 
						|
        "resourceVersion": "147",
 | 
						|
        "creationTimestamp": "2016-06-10T18:38:51Z",
 | 
						|
        "labels": {
 | 
						|
          "run": "mynginx"
 | 
						|
        }
 | 
						|
      },
 | 
						|
      "spec": {
 | 
						|
        "ports": [
 | 
						|
          {
 | 
						|
            "protocol": "TCP",
 | 
						|
            "port": 80,
 | 
						|
            "targetPort": 80
 | 
						|
          }
 | 
						|
        ],
 | 
						|
        "selector": {
 | 
						|
          "run": "mynginx"
 | 
						|
        },
 | 
						|
        "clusterIP": "10.0.0.132",
 | 
						|
        "type": "ClusterIP",
 | 
						|
        "sessionAffinity": "None"
 | 
						|
      },
 | 
						|
      "status": {
 | 
						|
        "loadBalancer": {}
 | 
						|
      }
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "metadata": {
 | 
						|
        "name": "mywebserver",
 | 
						|
        "namespace": "demo",
 | 
						|
        "selfLink": "/api/v1/namespaces/demo/services/mywebserver",
 | 
						|
        "uid": "aed62187-33e5-11e6-a224-0800279930f6",
 | 
						|
        "resourceVersion": "138185",
 | 
						|
        "creationTimestamp": "2016-06-16T17:13:45Z",
 | 
						|
        "labels": {
 | 
						|
          "run": "mywebserver"
 | 
						|
        }
 | 
						|
      },
 | 
						|
      "spec": {
 | 
						|
        "ports": [
 | 
						|
          {
 | 
						|
            "protocol": "TCP",
 | 
						|
            "port": 443,
 | 
						|
            "targetPort": 443
 | 
						|
          }
 | 
						|
        ],
 | 
						|
        "selector": {
 | 
						|
          "run": "mywebserver"
 | 
						|
        },
 | 
						|
        "clusterIP": "10.0.0.63",
 | 
						|
        "type": "ClusterIP",
 | 
						|
        "sessionAffinity": "None"
 | 
						|
      },
 | 
						|
      "status": {
 | 
						|
        "loadBalancer": {}
 | 
						|
      }
 | 
						|
    }
 | 
						|
  ]
 | 
						|
}
 | 
						|
`
 | 
						|
 | 
						|
// Sample resource data for kubernetes.
 | 
						|
const resourceListJsonData string = `{
 | 
						|
  "kind": "APIResourceList",
 | 
						|
  "groupVersion": "v1",
 | 
						|
  "resources": [
 | 
						|
    {
 | 
						|
      "name": "bindings",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Binding"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "componentstatuses",
 | 
						|
      "namespaced": false,
 | 
						|
      "kind": "ComponentStatus"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "configmaps",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "ConfigMap"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "endpoints",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Endpoints"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "events",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Event"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "limitranges",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "LimitRange"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "namespaces",
 | 
						|
      "namespaced": false,
 | 
						|
      "kind": "Namespace"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "namespaces/finalize",
 | 
						|
      "namespaced": false,
 | 
						|
      "kind": "Namespace"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "namespaces/status",
 | 
						|
      "namespaced": false,
 | 
						|
      "kind": "Namespace"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "nodes",
 | 
						|
      "namespaced": false,
 | 
						|
      "kind": "Node"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "nodes/proxy",
 | 
						|
      "namespaced": false,
 | 
						|
      "kind": "Node"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "nodes/status",
 | 
						|
      "namespaced": false,
 | 
						|
      "kind": "Node"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "persistentvolumeclaims",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "PersistentVolumeClaim"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "persistentvolumeclaims/status",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "PersistentVolumeClaim"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "persistentvolumes",
 | 
						|
      "namespaced": false,
 | 
						|
      "kind": "PersistentVolume"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "persistentvolumes/status",
 | 
						|
      "namespaced": false,
 | 
						|
      "kind": "PersistentVolume"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "pods",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Pod"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "pods/attach",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Pod"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "pods/binding",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Binding"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "pods/exec",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Pod"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "pods/log",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Pod"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "pods/portforward",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Pod"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "pods/proxy",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Pod"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "pods/status",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Pod"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "podtemplates",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "PodTemplate"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "replicationcontrollers",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "ReplicationController"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "replicationcontrollers/scale",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Scale"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "replicationcontrollers/status",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "ReplicationController"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "resourcequotas",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "ResourceQuota"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "resourcequotas/status",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "ResourceQuota"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "secrets",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Secret"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "serviceaccounts",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "ServiceAccount"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "services",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Service"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "services/proxy",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Service"
 | 
						|
    },
 | 
						|
    {
 | 
						|
      "name": "services/status",
 | 
						|
      "namespaced": true,
 | 
						|
      "kind": "Service"
 | 
						|
    }
 | 
						|
  ]
 | 
						|
}`
 |