aboutsummaryrefslogtreecommitdiff
path: root/middleware/kubernetes/k8sclient
diff options
context:
space:
mode:
Diffstat (limited to 'middleware/kubernetes/k8sclient')
-rw-r--r--middleware/kubernetes/k8sclient/dataobjects.go117
-rw-r--r--middleware/kubernetes/k8sclient/k8sclient.go172
-rw-r--r--middleware/kubernetes/k8sclient/k8sclient_test.go680
3 files changed, 846 insertions, 123 deletions
diff --git a/middleware/kubernetes/k8sclient/dataobjects.go b/middleware/kubernetes/k8sclient/dataobjects.go
index a5ab4f19c..b17adeba4 100644
--- a/middleware/kubernetes/k8sclient/dataobjects.go
+++ b/middleware/kubernetes/k8sclient/dataobjects.go
@@ -1,110 +1,113 @@
package k8sclient
import (
- "encoding/json"
- "net/http"
+ "encoding/json"
+ "net/http"
)
+// getK8sAPIResponse wraps the http.Get(url) function to provide dependency
+// injection for unit testing.
+var getK8sAPIResponse = func(url string) (resp *http.Response, err error) {
+ resp, err = http.Get(url)
+ return resp, err
+}
-func getJson(url string, target interface{}) error {
- r, err := http.Get(url)
- if err != nil {
- return err
- }
- defer r.Body.Close()
+func parseJson(url string, target interface{}) error {
+ r, err := getK8sAPIResponse(url)
+ if err != nil {
+ return err
+ }
+ defer r.Body.Close()
- return json.NewDecoder(r.Body).Decode(target)
+ return json.NewDecoder(r.Body).Decode(target)
}
-
// Kubernetes Resource List
type ResourceList struct {
- Kind string `json:"kind"`
- GroupVersion string `json:"groupVersion"`
- Resources []resource `json:"resources"`
+ Kind string `json:"kind"`
+ GroupVersion string `json:"groupVersion"`
+ Resources []resource `json:"resources"`
}
type resource struct {
- Name string `json:"name"`
- Namespaced bool `json:"namespaced"`
- Kind string `json:"kind"`
+ Name string `json:"name"`
+ Namespaced bool `json:"namespaced"`
+ Kind string `json:"kind"`
}
-
// Kubernetes NamespaceList
type NamespaceList struct {
- Kind string `json:"kind"`
- APIVersion string `json:"apiVersion"`
- Metadata apiListMetadata `json:"metadata"`
- Items []nsItems `json:"items"`
+ Kind string `json:"kind"`
+ APIVersion string `json:"apiVersion"`
+ Metadata apiListMetadata `json:"metadata"`
+ Items []nsItems `json:"items"`
}
type apiListMetadata struct {
- SelfLink string `json:"selfLink"`
- resourceVersion string `json:"resourceVersion"`
+ SelfLink string `json:"selfLink"`
+ ResourceVersion string `json:"resourceVersion"`
}
type nsItems struct {
- Metadata nsMetadata `json:"metadata"`
- Spec nsSpec `json:"spec"`
- Status nsStatus `json:"status"`
+ Metadata nsMetadata `json:"metadata"`
+ Spec nsSpec `json:"spec"`
+ Status nsStatus `json:"status"`
}
type nsMetadata struct {
- Name string `json:"name"`
- SelfLink string `json:"selfLink"`
- Uid string `json:"uid"`
- ResourceVersion string `json:"resourceVersion"`
- CreationTimestamp string `json:"creationTimestamp"`
+ Name string `json:"name"`
+ SelfLink string `json:"selfLink"`
+ Uid string `json:"uid"`
+ ResourceVersion string `json:"resourceVersion"`
+ CreationTimestamp string `json:"creationTimestamp"`
}
type nsSpec struct {
- Finalizers []string `json:"finalizers"`
+ Finalizers []string `json:"finalizers"`
}
type nsStatus struct {
- Phase string `json:"phase"`
+ Phase string `json:"phase"`
}
-
// Kubernetes ServiceList
type ServiceList struct {
- Kind string `json:"kind"`
- APIVersion string `json:"apiVersion"`
- Metadata apiListMetadata `json:"metadata"`
- Items []ServiceItem `json:"items"`
+ Kind string `json:"kind"`
+ APIVersion string `json:"apiVersion"`
+ Metadata apiListMetadata `json:"metadata"`
+ Items []ServiceItem `json:"items"`
}
type ServiceItem struct {
- Metadata serviceMetadata `json:"metadata"`
- Spec serviceSpec `json:"spec"`
-// Status serviceStatus `json:"status"`
+ Metadata serviceMetadata `json:"metadata"`
+ Spec serviceSpec `json:"spec"`
+ // Status serviceStatus `json:"status"`
}
type serviceMetadata struct {
- Name string `json:"name"`
- Namespace string `json:"namespace"`
- SelfLink string `json:"selfLink"`
- Uid string `json:"uid"`
- ResourceVersion string `json:"resourceVersion"`
- CreationTimestamp string `json:"creationTimestamp"`
- // labels
+ Name string `json:"name"`
+ Namespace string `json:"namespace"`
+ SelfLink string `json:"selfLink"`
+ Uid string `json:"uid"`
+ ResourceVersion string `json:"resourceVersion"`
+ CreationTimestamp string `json:"creationTimestamp"`
+ // labels
}
type serviceSpec struct {
- Ports []servicePort `json:"ports"`
- ClusterIP string `json:"clusterIP"`
- Type string `json:"type"`
- SessionAffinity string `json:"sessionAffinity"`
+ Ports []servicePort `json:"ports"`
+ ClusterIP string `json:"clusterIP"`
+ Type string `json:"type"`
+ SessionAffinity string `json:"sessionAffinity"`
}
type servicePort struct {
- Name string `json:"name"`
- Protocol string `json:"protocol"`
- Port int `json:"port"`
- TargetPort int `json:"targetPort"`
+ Name string `json:"name"`
+ Protocol string `json:"protocol"`
+ Port int `json:"port"`
+ TargetPort int `json:"targetPort"`
}
type serviceStatus struct {
- LoadBalancer string `json:"loadBalancer"`
+ LoadBalancer string `json:"loadBalancer"`
}
diff --git a/middleware/kubernetes/k8sclient/k8sclient.go b/middleware/kubernetes/k8sclient/k8sclient.go
index a05ef8905..95300f3b9 100644
--- a/middleware/kubernetes/k8sclient/k8sclient.go
+++ b/middleware/kubernetes/k8sclient/k8sclient.go
@@ -1,117 +1,157 @@
package k8sclient
import (
-// "fmt"
- "net/url"
+ "errors"
+ "fmt"
+ "net/url"
+ "strings"
)
// API strings
const (
- apiBase = "/api/v1"
- apiNamespaces = "/namespaces"
- apiServices = "/services"
+ apiBase = "/api/v1"
+ apiNamespaces = "/namespaces"
+ apiServices = "/services"
)
// Defaults
const (
- defaultBaseUrl = "http://localhost:8080"
+ defaultBaseURL = "http://localhost:8080"
)
-
type K8sConnector struct {
- baseUrl string
+ baseURL string
}
-func (c *K8sConnector) SetBaseUrl(u string) error {
- validUrl, error := url.Parse(u)
+func (c *K8sConnector) SetBaseURL(u string) error {
+ url, error := url.Parse(u)
+
+ if error != nil {
+ return error
+ }
- if error != nil {
- return error
- }
- c.baseUrl = validUrl.String()
+ if !url.IsAbs() {
+ return errors.New("k8sclient: Kubernetes endpoint url must be an absolute URL")
+ }
- return nil
+ c.baseURL = url.String()
+ return nil
}
-func (c *K8sConnector) GetBaseUrl() string {
- return c.baseUrl
+func (c *K8sConnector) GetBaseURL() string {
+ return c.baseURL
}
+// URL constructor separated from code to support dependency injection
+// for unit tests.
+var makeURL = func(parts []string) string {
+ return strings.Join(parts, "")
+}
-func (c *K8sConnector) GetResourceList() *ResourceList {
- resources := new(ResourceList)
-
- error := getJson((c.baseUrl + apiBase), resources)
- if error != nil {
- return nil
- }
+func (c *K8sConnector) GetResourceList() (*ResourceList, error) {
+ resources := new(ResourceList)
- return resources
-}
+ url := makeURL([]string{c.baseURL, apiBase})
+ err := parseJson(url, resources)
+ // TODO: handle no response from k8s
+ if err != nil {
+ fmt.Printf("[ERROR] Response from kubernetes API for GetResourceList() is: %v\n", err)
+ return nil, err
+ }
+ return resources, nil
+}
-func (c *K8sConnector) GetNamespaceList() *NamespaceList {
- namespaces := new(NamespaceList)
+func (c *K8sConnector) GetNamespaceList() (*NamespaceList, error) {
+ namespaces := new(NamespaceList)
- error := getJson((c.baseUrl + apiBase + apiNamespaces), namespaces)
- if error != nil {
- return nil
- }
+ url := makeURL([]string{c.baseURL, apiBase, apiNamespaces})
+ err := parseJson(url, namespaces)
+ if err != nil {
+ fmt.Printf("[ERROR] Response from kubernetes API for GetNamespaceList() is: %v\n", err)
+ return nil, err
+ }
- return namespaces
+ return namespaces, nil
}
+func (c *K8sConnector) GetServiceList() (*ServiceList, error) {
+ services := new(ServiceList)
-func (c *K8sConnector) GetServiceList() *ServiceList {
- services := new(ServiceList)
+ url := makeURL([]string{c.baseURL, apiBase, apiServices})
+ err := parseJson(url, services)
+ // TODO: handle no response from k8s
+ if err != nil {
+ fmt.Printf("[ERROR] Response from kubernetes API for GetServiceList() is: %v\n", err)
+ return nil, err
+ }
- error := getJson((c.baseUrl + apiBase + apiServices), services)
- if error != nil {
- return nil
- }
-
- return services
+ return services, nil
}
+// GetServicesByNamespace returns a map of
+// namespacename :: [ kubernetesServiceItem ]
+func (c *K8sConnector) GetServicesByNamespace() (map[string][]ServiceItem, error) {
+
+ items := make(map[string][]ServiceItem)
+
+ k8sServiceList, err := c.GetServiceList()
-func (c *K8sConnector) GetServicesByNamespace() map[string][]ServiceItem {
- // GetServicesByNamespace returns a map of namespacename :: [ kubernetesServiceItem ]
+ if err != nil {
+ fmt.Printf("[ERROR] Getting service list produced error: %v", err)
+ return nil, err
+ }
- items := make(map[string][]ServiceItem)
+ // TODO: handle no response from k8s
+ if k8sServiceList == nil {
+ return nil, nil
+ }
- k8sServiceList := c.GetServiceList()
- k8sItemList := k8sServiceList.Items
+ k8sItemList := k8sServiceList.Items
- for _, i := range k8sItemList {
- namespace := i.Metadata.Namespace
- items[namespace] = append(items[namespace], i)
- }
+ for _, i := range k8sItemList {
+ namespace := i.Metadata.Namespace
+ items[namespace] = append(items[namespace], i)
+ }
- return items
+ return items, nil
}
+// GetServiceItemsInNamespace returns the ServiceItems that match
+// servicename in the namespace
+func (c *K8sConnector) GetServiceItemsInNamespace(namespace string, servicename string) ([]*ServiceItem, error) {
-func (c *K8sConnector) GetServiceItemInNamespace(namespace string, servicename string) *ServiceItem {
- // GetServiceItemInNamespace returns the ServiceItem that matches servicename in the namespace
+ itemMap, err := c.GetServicesByNamespace()
- itemMap := c.GetServicesByNamespace()
+ if err != nil {
+ fmt.Printf("[ERROR] Getting service list produced error: %v", err)
+ return nil, err
+ }
- // TODO: Handle case where namesapce == nil
+ // TODO: Handle case where namespace == nil
- for _, x := range itemMap[namespace] {
- if x.Metadata.Name == servicename {
- return &x
- }
- }
+ var serviceItems []*ServiceItem
- // No matching item found in namespace
- return nil
+ for _, x := range itemMap[namespace] {
+ if x.Metadata.Name == servicename {
+ serviceItems = append(serviceItems, &x)
+ }
+ }
+
+ return serviceItems, nil
}
+func NewK8sConnector(baseURL string) *K8sConnector {
+ k := new(K8sConnector)
+
+ if baseURL == "" {
+ baseURL = defaultBaseURL
+ }
-func NewK8sConnector(baseurl string) *K8sConnector {
- k := new(K8sConnector)
- k.SetBaseUrl(baseurl)
+ err := k.SetBaseURL(baseURL)
+ if err != nil {
+ return nil
+ }
- return k
+ return k
}
diff --git a/middleware/kubernetes/k8sclient/k8sclient_test.go b/middleware/kubernetes/k8sclient/k8sclient_test.go
new file mode 100644
index 000000000..eded61b92
--- /dev/null
+++ b/middleware/kubernetes/k8sclient/k8sclient_test.go
@@ -0,0 +1,680 @@
+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"
+ }
+ ]
+}`