Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions internal/api/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ func TestFunctionMethodsUseGeneratedRoutes(t *testing.T) {
var batchFilenames []string
var updateBody map[string]bool
var invokeBody map[string]any
var logSearchBody map[string]any
var logSearchBodies []map[string]any
var schedulerCreateBody map[string]any
var schedulerUpdateBody map[string]any
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -360,11 +360,13 @@ func TestFunctionMethodsUseGeneratedRoutes(t *testing.T) {
"total": 1,
})
case r.Method == http.MethodPost && r.URL.Path == "/projects/"+projectIDText+"/logs/search":
require.NoError(t, json.NewDecoder(r.Body).Decode(&logSearchBody))
writeAPIJSON(t, w, http.StatusOK, logsResponse("function runtime"))
case r.Method == http.MethodGet && r.URL.Path == "/projects/"+projectIDText+"/functions/"+functionIDText+"/deployments/"+deploymentIDText+"/logs":
assert.Equal(t, "75", r.URL.Query().Get("limit"))
assert.Equal(t, "dep-next", r.URL.Query().Get("cursor"))
var body map[string]any
require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
logSearchBodies = append(logSearchBodies, body)
if len(logSearchBodies) == 1 {
writeAPIJSON(t, w, http.StatusOK, logsResponse("function runtime"))
return
}
writeAPIJSON(t, w, http.StatusOK, logsResponse("deployment build"))
case r.Method == http.MethodGet && r.URL.Path == "/projects/"+projectIDText+"/functions/"+functionIDText+"/schedulers":
writeAPIJSON(t, w, http.StatusOK, map[string]any{
Expand Down Expand Up @@ -463,14 +465,27 @@ func TestFunctionMethodsUseGeneratedRoutes(t *testing.T) {
runtimeLogs, err := client.GetFunctionLogs(context.Background(), projectID, functionID, 50, "fn-next")
require.NoError(t, err)
assert.Equal(t, "function runtime", runtimeLogs.Data[0].Message)
assert.Equal(t, "function", logSearchBody["resource_type"])
assert.Equal(t, []any{functionIDText}, logSearchBody["resource_ids"])
assert.InEpsilon(t, 50, logSearchBody["limit"], 0)
assert.Equal(t, "fn-next", logSearchBody["cursor"])
require.Len(t, logSearchBodies, 1)
runtimeResource, ok := logSearchBodies[0]["resource"].(map[string]any)
require.True(t, ok)
assert.Equal(t, "function", runtimeResource["type"])
assert.Equal(t, []any{functionIDText}, runtimeResource["ids"])
assert.InEpsilon(t, 50, logSearchBodies[0]["limit"], 0)
assert.Equal(t, "fn-next", logSearchBodies[0]["cursor"])

deploymentLogs, err := client.GetFunctionDeploymentLogs(context.Background(), projectID, functionID, deploymentID, 75, "dep-next")
require.NoError(t, err)
assert.Equal(t, "deployment build", deploymentLogs.Data[0].Message)
require.Len(t, logSearchBodies, 2)
buildResource, ok := logSearchBodies[1]["resource"].(map[string]any)
require.True(t, ok)
buildDeployments, ok := buildResource["deployments"].(map[string]any)
require.True(t, ok)
assert.Equal(t, "function", buildResource["type"])
assert.Equal(t, []any{functionIDText}, buildResource["ids"])
assert.Equal(t, []any{deploymentIDText}, buildDeployments["ids"])
assert.InEpsilon(t, 75, logSearchBodies[1]["limit"], 0)
assert.Equal(t, "dep-next", logSearchBodies[1]["cursor"])

schedulers, err := client.ListFunctionSchedulers(context.Background(), projectID, functionID)
require.NoError(t, err)
Expand Down Expand Up @@ -518,7 +533,7 @@ func TestFunctionMethodsUseGeneratedRoutes(t *testing.T) {
"GET /functions/runtimes",
"GET /projects/" + projectIDText + "/functions/" + functionIDText + "/deployments?page=3&limit=10",
"POST /projects/" + projectIDText + "/logs/search",
"GET /projects/" + projectIDText + "/functions/" + functionIDText + "/deployments/" + deploymentIDText + "/logs?limit=75&cursor=dep-next",
"POST /projects/" + projectIDText + "/logs/search",
"GET /projects/" + projectIDText + "/functions/" + functionIDText + "/schedulers",
"POST /projects/" + projectIDText + "/functions/" + functionIDText + "/schedulers",
"PATCH /projects/" + projectIDText + "/functions/" + functionIDText + "/schedulers/" + schedulerIDText,
Expand Down
42 changes: 18 additions & 24 deletions internal/api/frontends.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,38 +117,32 @@ func (c *Client) ListFrontendDeployments(ctx context.Context, projectID, fronten
return apiResult(resp.StatusCode(), resp.Body, resp.JSON200, resp.JSON400, resp.JSON401, resp.JSON403, resp.JSON404, resp.JSON500)
}

// GetFrontendLogs returns one runtime log page for a frontend.
func (c *Client) GetFrontendLogs(ctx context.Context, projectID, frontendID uuid.UUID, limit int, cursor string) (*apiclient.ListLogsResponse, error) {
params := &apiclient.GetFrontendLogsParams{}
if limit > 0 {
params.Limit = &limit
// GetFrontendLogs returns one runtime log search page for a frontend.
func (c *Client) GetFrontendLogs(ctx context.Context, projectID, frontendID uuid.UUID, limit int, cursor string) (*apiclient.LogSearchResponse, error) {
body := logSearchRequest{
Resource: logResource(logResourceTypeFrontend, frontendID),
}
if cursor = strings.TrimSpace(cursor); cursor != "" {
params.Cursor = &cursor
if limit > 0 {
body.Limit = &limit
}

resp, err := c.client.GetFrontendLogsWithResponse(ctx, projectID, frontendID, params)
if err != nil {
return nil, err
if cursor != "" {
body.Cursor = &cursor
}
return apiResult(resp.StatusCode(), resp.Body, resp.JSON200, resp.JSON400, resp.JSON401, resp.JSON403, resp.JSON404, resp.JSON500, resp.JSON503)
return c.searchProjectLogs(ctx, projectID, body)
}

// GetFrontendDeploymentLogs returns one build log page for a frontend deployment.
func (c *Client) GetFrontendDeploymentLogs(ctx context.Context, projectID, frontendID, deploymentID uuid.UUID, limit int, cursor string) (*apiclient.ListLogsResponse, error) {
params := &apiclient.GetFrontendDeploymentLogsParams{}
if limit > 0 {
params.Limit = &limit
// GetFrontendDeploymentLogs returns one build log search page for a frontend deployment.
func (c *Client) GetFrontendDeploymentLogs(ctx context.Context, projectID, frontendID, deploymentID uuid.UUID, limit int, cursor string) (*apiclient.LogSearchResponse, error) {
body := logSearchRequest{
Resource: logDeploymentResource(logResourceTypeFrontend, frontendID, deploymentID),
}
if cursor = strings.TrimSpace(cursor); cursor != "" {
params.Cursor = &cursor
if limit > 0 {
body.Limit = &limit
}

resp, err := c.client.GetFrontendDeploymentLogsWithResponse(ctx, projectID, frontendID, deploymentID, params)
if err != nil {
return nil, err
if cursor != "" {
body.Cursor = &cursor
}
return apiResult(resp.StatusCode(), resp.Body, resp.JSON200, resp.JSON400, resp.JSON401, resp.JSON403, resp.JSON404, resp.JSON500, resp.JSON503)
return c.searchProjectLogs(ctx, projectID, body)
}

// CreateFrontendCustomDomain attaches a BYOC custom domain to a frontend.
Expand Down
37 changes: 28 additions & 9 deletions internal/api/frontends_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func TestFrontendDomainAndLogsMethodsUseGeneratedRoutes(t *testing.T) {
deploymentID := uuid.MustParse(deploymentIDText)
var requests []string
var createBody map[string]any
var logSearchBodies []map[string]any
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "Bearer token", r.Header.Get("Authorization"))
requests = append(requests, r.Method+" "+r.URL.RequestURI())
Expand All @@ -91,13 +92,14 @@ func TestFrontendDomainAndLogsMethodsUseGeneratedRoutes(t *testing.T) {
"limit": 10,
"total": 1,
})
case r.Method == http.MethodGet && r.URL.Path == "/projects/"+projectIDText+"/frontends/"+frontendIDText+"/logs":
assert.Equal(t, "50", r.URL.Query().Get("limit"))
assert.Equal(t, "fe-next", r.URL.Query().Get("cursor"))
writeAPIJSON(t, w, http.StatusOK, logsResponse("frontend runtime"))
case r.Method == http.MethodGet && r.URL.Path == "/projects/"+projectIDText+"/frontends/"+frontendIDText+"/deployments/"+deploymentIDText+"/logs":
assert.Equal(t, "75", r.URL.Query().Get("limit"))
assert.Equal(t, "dep-next", r.URL.Query().Get("cursor"))
case r.Method == http.MethodPost && r.URL.Path == "/projects/"+projectIDText+"/logs/search":
var body map[string]any
require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
logSearchBodies = append(logSearchBodies, body)
if len(logSearchBodies) == 1 {
writeAPIJSON(t, w, http.StatusOK, logsResponse("frontend runtime"))
return
}
writeAPIJSON(t, w, http.StatusOK, logsResponse("frontend build"))
case r.Method == http.MethodPost && r.URL.Path == "/projects/"+projectIDText+"/frontends/"+frontendIDText+"/domain":
require.NoError(t, json.NewDecoder(r.Body).Decode(&createBody))
Expand All @@ -123,10 +125,27 @@ func TestFrontendDomainAndLogsMethodsUseGeneratedRoutes(t *testing.T) {
runtimeLogs, err := client.GetFrontendLogs(context.Background(), projectID, frontendID, 50, "fe-next")
require.NoError(t, err)
assert.Equal(t, "frontend runtime", runtimeLogs.Data[0].Message)
require.Len(t, logSearchBodies, 1)
runtimeResource, ok := logSearchBodies[0]["resource"].(map[string]any)
require.True(t, ok)
assert.Equal(t, "frontend", runtimeResource["type"])
assert.Equal(t, []any{frontendIDText}, runtimeResource["ids"])
assert.InEpsilon(t, 50, logSearchBodies[0]["limit"], 0)
assert.Equal(t, "fe-next", logSearchBodies[0]["cursor"])

deploymentLogs, err := client.GetFrontendDeploymentLogs(context.Background(), projectID, frontendID, deploymentID, 75, "dep-next")
require.NoError(t, err)
assert.Equal(t, "frontend build", deploymentLogs.Data[0].Message)
require.Len(t, logSearchBodies, 2)
buildResource, ok := logSearchBodies[1]["resource"].(map[string]any)
require.True(t, ok)
buildDeployments, ok := buildResource["deployments"].(map[string]any)
require.True(t, ok)
assert.Equal(t, "frontend", buildResource["type"])
assert.Equal(t, []any{frontendIDText}, buildResource["ids"])
assert.Equal(t, []any{deploymentIDText}, buildDeployments["ids"])
assert.InEpsilon(t, 75, logSearchBodies[1]["limit"], 0)
assert.Equal(t, "dep-next", logSearchBodies[1]["cursor"])

createdDomain, err := client.CreateFrontendCustomDomain(context.Background(), projectID, frontendID, FrontendCustomDomainInput{
Domain: " app.example.com ",
Expand All @@ -151,8 +170,8 @@ func TestFrontendDomainAndLogsMethodsUseGeneratedRoutes(t *testing.T) {
require.NoError(t, client.DeleteFrontendCustomDomain(context.Background(), projectID, frontendID))
assert.Equal(t, []string{
"GET /projects/" + projectIDText + "/frontends/" + frontendIDText + "/deployments?page=3&limit=10",
"GET /projects/" + projectIDText + "/frontends/" + frontendIDText + "/logs?limit=50&cursor=fe-next",
"GET /projects/" + projectIDText + "/frontends/" + frontendIDText + "/deployments/" + deploymentIDText + "/logs?limit=75&cursor=dep-next",
"POST /projects/" + projectIDText + "/logs/search",
"POST /projects/" + projectIDText + "/logs/search",
"POST /projects/" + projectIDText + "/frontends/" + frontendIDText + "/domain",
"GET /projects/" + projectIDText + "/frontends/" + frontendIDText + "/domain",
"DELETE /projects/" + projectIDText + "/frontends/" + frontendIDText + "/domain",
Expand Down
38 changes: 13 additions & 25 deletions internal/api/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import (
"encoding/json"
"fmt"
"mime/multipart"
"strings"

"github.com/google/uuid"

"github.com/Kong/volcano-cli/internal/apiclient"
apicommon "github.com/Kong/volcano-cli/internal/apiclient/common"
"github.com/Kong/volcano-cli/internal/archive"
)

Expand Down Expand Up @@ -158,23 +156,16 @@ func (c *Client) ListFunctionDeployments(ctx context.Context, projectID, functio

// GetFunctionLogs returns one runtime log search page for a function.
func (c *Client) GetFunctionLogs(ctx context.Context, projectID, functionID uuid.UUID, limit int, cursor string) (*apiclient.LogSearchResponse, error) {
resourceID := functionID.String()
body := apiclient.SearchProjectLogsJSONRequestBody{
ResourceType: apicommon.LogSearchRequestResourceTypeFunction,
ResourceIds: &[]string{resourceID},
body := logSearchRequest{
Resource: logResource(logResourceTypeFunction, functionID),
}
if limit > 0 {
body.Limit = &limit
}
if cursor = strings.TrimSpace(cursor); cursor != "" {
if cursor != "" {
body.Cursor = &cursor
}

resp, err := c.client.SearchProjectLogsWithResponse(ctx, projectID, body)
if err != nil {
return nil, err
}
return apiResult(resp.StatusCode(), resp.Body, resp.JSON200, resp.JSON401, resp.JSON403, resp.JSON404)
return c.searchProjectLogs(ctx, projectID, body)
}

func buildFunctionDeployMultipart(fn FunctionDeployInput) (*bytes.Buffer, string, error) {
Expand Down Expand Up @@ -305,19 +296,16 @@ func (c *Client) DeleteFunctionScheduler(ctx context.Context, projectID, functio
return apiOK(resp.StatusCode(), resp.Body)
}

// GetFunctionDeploymentLogs returns one build log page for a function deployment.
func (c *Client) GetFunctionDeploymentLogs(ctx context.Context, projectID, functionID, deploymentID uuid.UUID, limit int, cursor string) (*apiclient.ListLogsResponse, error) {
params := &apiclient.GetFunctionDeploymentLogsParams{}
if limit > 0 {
params.Limit = &limit
// GetFunctionDeploymentLogs returns one build log search page for a function deployment.
func (c *Client) GetFunctionDeploymentLogs(ctx context.Context, projectID, functionID, deploymentID uuid.UUID, limit int, cursor string) (*apiclient.LogSearchResponse, error) {
body := logSearchRequest{
Resource: logDeploymentResource(logResourceTypeFunction, functionID, deploymentID),
}
if cursor = strings.TrimSpace(cursor); cursor != "" {
params.Cursor = &cursor
if limit > 0 {
body.Limit = &limit
}

resp, err := c.client.GetFunctionDeploymentLogsWithResponse(ctx, projectID, functionID, deploymentID, params)
if err != nil {
return nil, err
if cursor != "" {
body.Cursor = &cursor
}
return apiResult(resp.StatusCode(), resp.Body, resp.JSON200, resp.JSON401, resp.JSON403, resp.JSON404)
return c.searchProjectLogs(ctx, projectID, body)
}
74 changes: 74 additions & 0 deletions internal/api/logs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package api

import (
"bytes"
"context"
"encoding/json"
"fmt"
"strings"

"github.com/google/uuid"

"github.com/Kong/volcano-cli/internal/apiclient"
)

const (
logResourceTypeFrontend = "frontend"
logResourceTypeFunction = "function"
)

type logSearchRequest struct {
Resource logRequestResource `json:"resource"`
Limit *int `json:"limit,omitempty"`
Cursor *string `json:"cursor,omitempty"`
}

type logRequestResource struct {
Type string `json:"type"`
IDs []uuid.UUID `json:"ids,omitempty"`
Deployments *logDeploymentRequestSelector `json:"deployments,omitempty"`
}

type logDeploymentRequestSelector struct {
IDs []uuid.UUID `json:"ids,omitempty"`
}

func (c *Client) searchProjectLogs(ctx context.Context, projectID uuid.UUID, body logSearchRequest) (*apiclient.LogSearchResponse, error) {
if body.Limit != nil && *body.Limit <= 0 {
body.Limit = nil
}
if body.Cursor != nil {
cursor := strings.TrimSpace(*body.Cursor)
if cursor == "" {
body.Cursor = nil
} else {
body.Cursor = &cursor
}
}

payload, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("failed to marshal log search request: %w", err)
}

resp, err := c.client.SearchProjectLogsWithBodyWithResponse(ctx, projectID, "application/json", bytes.NewReader(payload))
if err != nil {
return nil, err
}
return apiResult(resp.StatusCode(), resp.Body, resp.JSON200, resp.JSON400, resp.JSON401, resp.JSON403, resp.JSON404, resp.JSON503)
}

func logResource(resourceType string, resourceID uuid.UUID) logRequestResource {
return logRequestResource{
Type: resourceType,
IDs: []uuid.UUID{resourceID},
}
}

func logDeploymentResource(resourceType string, resourceID, deploymentID uuid.UUID) logRequestResource {
resource := logResource(resourceType, resourceID)
resource.Deployments = &logDeploymentRequestSelector{
IDs: []uuid.UUID{deploymentID},
}
return resource
}
Loading
Loading