Skip to content

Commit

Permalink
kubernetes: add clusterlint support (#414)
Browse files Browse the repository at this point in the history
* kubernetes: add clusterlint support

* add clusterlint diagnostics root
  • Loading branch information
varshavaradarajan authored Nov 20, 2020
1 parent 45fcf9e commit 972f638
Show file tree
Hide file tree
Showing 2 changed files with 244 additions and 12 deletions.
84 changes: 84 additions & 0 deletions kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ type KubernetesService interface {
GetOptions(context.Context) (*KubernetesOptions, *Response, error)
AddRegistry(ctx context.Context, req *KubernetesClusterRegistryRequest) (*Response, error)
RemoveRegistry(ctx context.Context, req *KubernetesClusterRegistryRequest) (*Response, error)

RunClusterlint(ctx context.Context, clusterID string, req *KubernetesRunClusterlintRequest) (string, *Response, error)
GetClusterlintResults(ctx context.Context, clusterID string, req *KubernetesGetClusterlintRequest) ([]*ClusterlintDiagnostic, *Response, error)
}

var _ KubernetesService = &KubernetesServiceOp{}
Expand Down Expand Up @@ -153,6 +156,17 @@ type KubernetesClusterRegistryRequest struct {
ClusterUUIDs []string `json:"cluster_uuids,omitempty"`
}

type KubernetesRunClusterlintRequest struct {
IncludeGroups []string `json:"include_groups"`
ExcludeGroups []string `json:"exclude_groups"`
IncludeChecks []string `json:"include_checks"`
ExcludeChecks []string `json:"exclude_checks"`
}

type KubernetesGetClusterlintRequest struct {
RunId string `json:"run_id"`
}

// KubernetesCluster represents a Kubernetes cluster.
type KubernetesCluster struct {
ID string `json:"id,omitempty"`
Expand Down Expand Up @@ -409,6 +423,28 @@ type KubernetesRegion struct {
Slug string `json:"slug"`
}

// ClusterlintDiagnostic is a diagnostic returned from clusterlint.
type ClusterlintDiagnostic struct {
CheckName string `json:"check_name"`
Severity string `json:"severity"`
Message string `json:"message"`
Object *ClusterlintObject `json:"object"`
}

// ClusterlintObject is the object a clusterlint diagnostic refers to.
type ClusterlintObject struct {
Kind string `json:"kind"`
Name string `json:"name"`
Namespace string `json:"namespace"`
Owners []*ClusterlintOwner `json:"owners,omitempty"`
}

// ClusterlintOwner indicates the resource that owns the offending object.
type ClusterlintOwner struct {
Kind string `json:"kind"`
Name string `json:"name"`
}

type kubernetesClustersRoot struct {
Clusters []*KubernetesCluster `json:"kubernetes_clusters,omitempty"`
Links *Links `json:"links,omitempty"`
Expand Down Expand Up @@ -799,3 +835,51 @@ func (svc *KubernetesServiceOp) RemoveRegistry(ctx context.Context, req *Kuberne
}
return resp, nil
}

type runClusterlintRoot struct {
RunID string `json:"run_id"`
}

// RunClusterlint schedules a clusterlint run for the specified cluster
func (svc *KubernetesServiceOp) RunClusterlint(ctx context.Context, clusterID string, req *KubernetesRunClusterlintRequest) (string, *Response, error) {
path := fmt.Sprintf("%s/%s/clusterlint", kubernetesClustersPath, clusterID)
request, err := svc.client.NewRequest(ctx, http.MethodPost, path, req)
if err != nil {
return "", nil, err
}
root := new(runClusterlintRoot)
resp, err := svc.client.Do(ctx, request, root)
if err != nil {
return "", resp, err
}
return root.RunID, resp, nil
}

type clusterlintDiagnosticsRoot struct {
Diagnostics []*ClusterlintDiagnostic
}

// GetClusterlintResults fetches the diagnostics after clusterlint run completes
func (svc *KubernetesServiceOp) GetClusterlintResults(ctx context.Context, clusterID string, req *KubernetesGetClusterlintRequest) ([]*ClusterlintDiagnostic, *Response, error) {
path := fmt.Sprintf("%s/%s/clusterlint", kubernetesClustersPath, clusterID)
if req != nil {
v := make(url.Values)
if req.RunId != "" {
v.Set("run_id", req.RunId)
}
if query := v.Encode(); query != "" {
path = path + "?" + query
}
}

request, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(clusterlintDiagnosticsRoot)
resp, err := svc.client.Do(ctx, request, root)
if err != nil {
return nil, resp, err
}
return root.Diagnostics, resp, nil
}
172 changes: 160 additions & 12 deletions kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1507,11 +1507,6 @@ func TestKubernetesClusterRegistry_Add(t *testing.T) {
ClusterUUIDs: []string{"8d91899c-0739-4a1a-acc5-deadbeefbb8f"},
}

jBlob := `
{
"cluster_uuids": ["8d91899c-0739-4a1a-acc5-deadbeefbb8f"]
}`

mux.HandleFunc("/v2/kubernetes/registry", func(w http.ResponseWriter, r *http.Request) {
v := new(KubernetesClusterRegistryRequest)
err := json.NewDecoder(r.Body).Decode(v)
Expand All @@ -1521,7 +1516,6 @@ func TestKubernetesClusterRegistry_Add(t *testing.T) {

testMethod(t, r, http.MethodPost)
require.Equal(t, v, addRequest)
fmt.Fprint(w, jBlob)
})

_, err := kubeSvc.AddRegistry(ctx, addRequest)
Expand All @@ -1538,11 +1532,6 @@ func TestKubernetesClusterRegistry_Remove(t *testing.T) {
ClusterUUIDs: []string{"8d91899c-0739-4a1a-acc5-deadbeefbb8f"},
}

jBlob := `
{
"cluster_uuids": ["8d91899c-0739-4a1a-acc5-deadbeefbb8f"]
}`

mux.HandleFunc("/v2/kubernetes/registry", func(w http.ResponseWriter, r *http.Request) {
v := new(KubernetesClusterRegistryRequest)
err := json.NewDecoder(r.Body).Decode(v)
Expand All @@ -1552,13 +1541,172 @@ func TestKubernetesClusterRegistry_Remove(t *testing.T) {

testMethod(t, r, http.MethodDelete)
require.Equal(t, v, remove)
fmt.Fprint(w, jBlob)
})

_, err := kubeSvc.RemoveRegistry(ctx, remove)
require.NoError(t, err)
}

func TestKubernetesRunClusterlint_WithRequestBody(t *testing.T) {
setup()
defer teardown()

kubeSvc := client.Kubernetes
request := &KubernetesRunClusterlintRequest{IncludeGroups: []string{"doks"}}
want := "1234"
jBlob := `
{
"run_id": "1234"
}`

mux.HandleFunc("/v2/kubernetes/clusters/8d91899c-0739-4a1a-acc5-deadbeefbb8f/clusterlint", func(w http.ResponseWriter, r *http.Request) {
v := new(KubernetesRunClusterlintRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatal(err)
}

testMethod(t, r, http.MethodPost)
require.Equal(t, v, request)
fmt.Fprint(w, jBlob)
})

runID, _, err := kubeSvc.RunClusterlint(ctx, "8d91899c-0739-4a1a-acc5-deadbeefbb8f", request)
require.NoError(t, err)
assert.Equal(t, want, runID)

}

func TestKubernetesRunClusterlint_WithoutRequestBody(t *testing.T) {
setup()
defer teardown()

kubeSvc := client.Kubernetes
want := "1234"
jBlob := `
{
"run_id": "1234"
}`

mux.HandleFunc("/v2/kubernetes/clusters/8d91899c-0739-4a1a-acc5-deadbeefbb8f/clusterlint", func(w http.ResponseWriter, r *http.Request) {
v := new(KubernetesRunClusterlintRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatal(err)
}

testMethod(t, r, http.MethodPost)
require.Equal(t, v, &KubernetesRunClusterlintRequest{})
fmt.Fprint(w, jBlob)
})

runID, _, err := kubeSvc.RunClusterlint(ctx, "8d91899c-0739-4a1a-acc5-deadbeefbb8f", &KubernetesRunClusterlintRequest{})
require.NoError(t, err)
assert.Equal(t, want, runID)

}

func TestKubernetesGetClusterlint_WithRunID(t *testing.T) {
setup()
defer teardown()

kubeSvc := client.Kubernetes
r := &KubernetesGetClusterlintRequest{RunId: "1234"}
jBlob := `
{
"run_id": "1234",
"requested_at": "2019-10-30T05:34:07Z",
"completed_at": "2019-10-30T05:34:11Z",
"diagnostics": [
{
"check_name": "unused-config-map",
"severity": "warning",
"message": "Unused config map",
"object": {
"kind": "config map",
"name": "foo",
"namespace": "kube-system"
}
}
]
}`

expected := []*ClusterlintDiagnostic{
{
CheckName: "unused-config-map",
Severity: "warning",
Message: "Unused config map",
Object: &ClusterlintObject{
Kind: "config map",
Name: "foo",
Namespace: "kube-system",
Owners: nil,
},
},
}
mux.HandleFunc("/v2/kubernetes/clusters/8d91899c-0739-4a1a-acc5-deadbeefbb8f/clusterlint", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
require.Equal(t, "run_id=1234", r.URL.Query().Encode())
fmt.Fprint(w, jBlob)
})

diagnostics, _, err := kubeSvc.GetClusterlintResults(ctx, "8d91899c-0739-4a1a-acc5-deadbeefbb8f", r)
require.NoError(t, err)
assert.Equal(t, expected, diagnostics)

}

func TestKubernetesGetClusterlint_WithoutRunID(t *testing.T) {
setup()
defer teardown()

kubeSvc := client.Kubernetes
r := &KubernetesGetClusterlintRequest{}
jBlob := `
{
"run_id": "1234",
"requested_at": "2019-10-30T05:34:07Z",
"completed_at": "2019-10-30T05:34:11Z",
"diagnostics": [
{
"check_name": "unused-config-map",
"severity": "warning",
"message": "Unused config map",
"object": {
"kind": "config map",
"name": "foo",
"namespace": "kube-system"
}
}
]
}`

expected := []*ClusterlintDiagnostic{
{
CheckName: "unused-config-map",
Severity: "warning",
Message: "Unused config map",
Object: &ClusterlintObject{
Kind: "config map",
Name: "foo",
Namespace: "kube-system",
Owners: nil,
},
},
}

mux.HandleFunc("/v2/kubernetes/clusters/8d91899c-0739-4a1a-acc5-deadbeefbb8f/clusterlint", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
require.Equal(t, "", r.URL.Query().Encode())
fmt.Fprint(w, jBlob)
})

diagnostics, _, err := kubeSvc.GetClusterlintResults(ctx, "8d91899c-0739-4a1a-acc5-deadbeefbb8f", r)
require.NoError(t, err)
assert.Equal(t, expected, diagnostics)

}

var maintenancePolicyDayTests = []struct {
name string
json string
Expand Down

0 comments on commit 972f638

Please sign in to comment.