-
Notifications
You must be signed in to change notification settings - Fork 132
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reimplement listing instance endpoint
- Loading branch information
Showing
3 changed files
with
175 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package routes | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"strconv" | ||
"time" | ||
|
||
"github.com/juice-shop/multi-juicer/balancer/pkg/bundle" | ||
"github.com/juice-shop/multi-juicer/balancer/pkg/teamcookie" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
type AdminListInstancesResponse struct { | ||
Instances []AdminListJuiceShopInstance `json:"instances"` | ||
} | ||
|
||
type AdminListJuiceShopInstance struct { | ||
Team string `json:"team"` | ||
Ready bool `json:"ready"` | ||
CreatedAt int64 `json:"createdAt"` | ||
LastConnect int64 `json:"lastConnect"` | ||
} | ||
|
||
func handleAdminListInstances(bundle *bundle.Bundle) http.Handler { | ||
return http.HandlerFunc( | ||
func(responseWriter http.ResponseWriter, req *http.Request) { | ||
team, err := teamcookie.GetTeamFromRequest(bundle, req) | ||
if err != nil || team != "admin" { | ||
http.Error(responseWriter, "", http.StatusUnauthorized) | ||
return | ||
} | ||
|
||
deployments, err := bundle.ClientSet.AppsV1().Deployments(bundle.RuntimeEnvironment.Namespace).List(req.Context(), metav1.ListOptions{ | ||
LabelSelector: "app.kubernetes.io/name=juice-shop,app.kubernetes.io/part-of=multi-juicer", | ||
}) | ||
if err != nil { | ||
bundle.Log.Printf("Failed to list deployments: %s", err) | ||
http.Error(responseWriter, "unable to get instances", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
instances := []AdminListJuiceShopInstance{} | ||
for _, teamDeployment := range deployments.Items { | ||
|
||
lastConnectAnnotation := teamDeployment.Annotations["multi-juicer.owasp-juice.shop/lastRequest"] | ||
lastConnection := time.UnixMilli(0) | ||
|
||
if lastConnectAnnotation != "" { | ||
millis, err := strconv.ParseInt(lastConnectAnnotation, 10, 64) | ||
if err != nil { | ||
millis = 0 | ||
} | ||
lastConnection = time.UnixMilli(millis) | ||
} | ||
|
||
instances = append(instances, AdminListJuiceShopInstance{ | ||
Team: teamDeployment.Labels["team"], | ||
Ready: teamDeployment.Status.ReadyReplicas == 1, | ||
CreatedAt: teamDeployment.CreationTimestamp.UnixMilli(), | ||
LastConnect: lastConnection.UnixMilli(), | ||
}) | ||
} | ||
|
||
response := AdminListInstancesResponse{ | ||
Instances: instances, | ||
} | ||
|
||
responseBody, _ := json.Marshal(response) | ||
responseWriter.Header().Set("Content-Type", "application/json") | ||
responseWriter.WriteHeader(http.StatusOK) | ||
responseWriter.Write(responseBody) | ||
}, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package routes | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
"time" | ||
|
||
"github.com/juice-shop/multi-juicer/balancer/pkg/testutil" | ||
"github.com/stretchr/testify/assert" | ||
appsv1 "k8s.io/api/apps/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/client-go/kubernetes/fake" | ||
) | ||
|
||
func TestAdminListInstanceshandler(t *testing.T) { | ||
createTeam := func(team string, createdAt time.Time, lastRequest time.Time, readyReplicas int32) *appsv1.Deployment { | ||
return &appsv1.Deployment{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: fmt.Sprintf("juiceshop-%s", team), | ||
Namespace: "test-namespace", | ||
CreationTimestamp: metav1.Time{ | ||
Time: createdAt, | ||
}, | ||
Annotations: map[string]string{ | ||
"multi-juicer.owasp-juice.shop/challenges": "[]", | ||
"multi-juicer.owasp-juice.shop/challengesSolved": "0", | ||
"multi-juicer.owasp-juice.shop/lastRequest": fmt.Sprintf("%d", lastRequest.UnixMilli()), | ||
"multi-juicer.owasp-juice.shop/lastRequestReadable": "2024-10-18 13:55:18.08198884+0000 UTC m=+11.556786174", | ||
"multi-juicer.owasp-juice.shop/passcode": "$2a$10$wnxvqClPk/13SbdowdJtu.2thGxrZe4qrsaVdTVUsYIrVVClhPMfS", | ||
}, | ||
Labels: map[string]string{ | ||
"app.kubernetes.io/name": "juice-shop", | ||
"app.kubernetes.io/part-of": "multi-juicer", | ||
"team": team, | ||
}, | ||
}, | ||
Status: appsv1.DeploymentStatus{ | ||
ReadyReplicas: readyReplicas, | ||
}, | ||
} | ||
} | ||
|
||
t.Run("listing instances requires admin login", func(t *testing.T) { | ||
req, _ := http.NewRequest("GET", "/balancer/admin/all", nil) | ||
req.Header.Set("Cookie", fmt.Sprintf("team=%s", testutil.SignTestTeamname("some team"))) | ||
rr := httptest.NewRecorder() | ||
|
||
server := http.NewServeMux() | ||
|
||
clientset := fake.NewSimpleClientset() | ||
bundle := testutil.NewTestBundleWithCustomFakeClient(clientset) | ||
AddRoutes(server, bundle) | ||
|
||
server.ServeHTTP(rr, req) | ||
|
||
assert.Equal(t, http.StatusUnauthorized, rr.Code) | ||
assert.Equal(t, "\n", rr.Body.String()) | ||
}) | ||
|
||
t.Run("lists all juice shop instances", func(t *testing.T) { | ||
req, _ := http.NewRequest("GET", "/balancer/admin/all", nil) | ||
req.Header.Set("Cookie", fmt.Sprintf("team=%s", testutil.SignTestTeamname("admin"))) | ||
rr := httptest.NewRecorder() | ||
|
||
server := http.NewServeMux() | ||
|
||
clientset := fake.NewSimpleClientset( | ||
createTeam("foobar", time.UnixMilli(1_700_000_000_000), time.UnixMilli(1_729_259_666_123), 1), | ||
createTeam("test-team", time.UnixMilli(1_600_000_000_000), time.UnixMilli(1_729_259_333_123), 0), | ||
) | ||
bundle := testutil.NewTestBundleWithCustomFakeClient(clientset) | ||
AddRoutes(server, bundle) | ||
|
||
server.ServeHTTP(rr, req) | ||
assert.Equal(t, http.StatusOK, rr.Code) | ||
|
||
var response AdminListInstancesResponse | ||
err := json.Unmarshal(rr.Body.Bytes(), &response) | ||
assert.Nil(t, err) | ||
|
||
assert.Equal(t, []AdminListJuiceShopInstance{ | ||
{ | ||
Team: "foobar", | ||
Ready: true, | ||
CreatedAt: 1_700_000_000_000, | ||
LastConnect: 1_729_259_666_123, | ||
}, | ||
{ | ||
Team: "test-team", | ||
Ready: false, | ||
CreatedAt: 1_600_000_000_000, | ||
LastConnect: 1_729_259_333_123, | ||
}, | ||
}, response.Instances) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters