feat: add audit log filter for autostarted and autostopped workspace builds (#5830)

* added query

* fixed query

* added example to dropdown

* added documentation

* added test

* fixed formatting

* fixed format
This commit is contained in:
Kira Pilot
2023-01-24 15:34:29 -05:00
committed by GitHub
parent 36384aa3c1
commit 322a4d93e1
13 changed files with 130 additions and 11 deletions

12
coderd/apidoc/docs.go generated
View File

@ -5583,6 +5583,18 @@ const docTemplate = `{
}
]
},
"build_reason": {
"enum": [
"autostart",
"autostop",
"initiator"
],
"allOf": [
{
"$ref": "#/definitions/codersdk.BuildReason"
}
]
},
"resource_id": {
"type": "string",
"format": "uuid"

View File

@ -4943,6 +4943,14 @@
}
]
},
"build_reason": {
"enum": ["autostart", "autostop", "initiator"],
"allOf": [
{
"$ref": "#/definitions/codersdk.BuildReason"
}
]
},
"resource_id": {
"type": "string",
"format": "uuid"

View File

@ -67,6 +67,7 @@ func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) {
Email: filter.Email,
DateFrom: filter.DateFrom,
DateTo: filter.DateTo,
BuildReason: filter.BuildReason,
})
if err != nil {
httpapi.InternalServerError(rw, err)
@ -443,6 +444,7 @@ func auditSearchQuery(query string) (database.GetAuditLogsOffsetParams, []coders
Email: parser.String(searchParams, "", "email"),
DateFrom: parsedDateFrom,
DateTo: parsedDateTo,
BuildReason: buildReasonFromString(parser.String(searchParams, "", "build_reason")),
}
return filter, parser.Errors
@ -488,3 +490,16 @@ func actionFromString(actionString string) string {
}
return ""
}
func buildReasonFromString(buildReasonString string) string {
switch codersdk.BuildReason(buildReasonString) {
case codersdk.BuildReasonInitiator:
return buildReasonString
case codersdk.BuildReasonAutostart:
return buildReasonString
case codersdk.BuildReasonAutostop:
return buildReasonString
default:
}
return ""
}

View File

@ -179,6 +179,11 @@ func TestAuditLogsFilter(t *testing.T) {
SearchQuery: "resource_type:workspace_build action:stop",
ExpectedResult: 1,
},
{
Name: "FilterOnWorkspaceBuildStartByInitiator",
SearchQuery: "resource_type:workspace_build action:start build_reason:start",
ExpectedResult: 1,
},
}
for _, testCase := range testCases {

View File

@ -3680,6 +3680,12 @@ func (q *fakeQuerier) GetAuditLogsOffset(ctx context.Context, arg database.GetAu
continue
}
}
if arg.BuildReason != "" {
workspaceBuild, err := q.GetWorkspaceBuildByID(context.Background(), alog.ResourceID)
if err == nil && !strings.EqualFold(arg.BuildReason, string(workspaceBuild.Reason)) {
continue
}
}
user, err := q.GetUserByID(ctx, alog.UserID)
userValid := err == nil

View File

@ -367,18 +367,41 @@ func (q *sqlQuerier) UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDP
const getAuditLogsOffset = `-- name: GetAuditLogsOffset :many
SELECT
audit_logs.id, audit_logs.time, audit_logs.user_id, audit_logs.organization_id, audit_logs.ip, audit_logs.user_agent, audit_logs.resource_type, audit_logs.resource_id, audit_logs.resource_target, audit_logs.action, audit_logs.diff, audit_logs.status_code, audit_logs.additional_fields, audit_logs.request_id, audit_logs.resource_icon,
audit_logs.id, audit_logs.time, audit_logs.user_id, audit_logs.organization_id, audit_logs.ip, audit_logs.user_agent, audit_logs.resource_type, audit_logs.resource_id, audit_logs.resource_target, audit_logs.action, audit_logs.diff, audit_logs.status_code, audit_logs.additional_fields, audit_logs.request_id, audit_logs.resource_icon,
users.username AS user_username,
users.email AS user_email,
users.created_at AS user_created_at,
users.status AS user_status,
users.rbac_roles AS user_roles,
users.avatar_url AS user_avatar_url,
COUNT(audit_logs.*) OVER() AS count
COUNT(audit_logs.*) OVER () AS count
FROM
audit_logs
LEFT JOIN
users ON audit_logs.user_id = users.id
audit_logs
LEFT JOIN users ON audit_logs.user_id = users.id
LEFT JOIN
-- First join on workspaces to get the initial workspace create
-- to workspace build 1 id. This is because the first create is
-- is a different audit log than subsequent starts.
workspaces ON
audit_logs.resource_type = 'workspace' AND
audit_logs.resource_id = workspaces.id
LEFT JOIN
workspace_builds ON
-- Get the reason from the build if the resource type
-- is a workspace_build
(
audit_logs.resource_type = 'workspace_build'
AND audit_logs.resource_id = workspace_builds.id
)
OR
-- Get the reason from the build #1 if this is the first
-- workspace create.
(
audit_logs.resource_type = 'workspace' AND
audit_logs.action = 'create' AND
workspaces.id = workspace_builds.workspace_id AND
workspace_builds.build_number = 1
)
WHERE
-- Filter resource_type
CASE
@ -428,6 +451,12 @@ WHERE
"time" <= $10
ELSE true
END
-- Filter by build_reason
AND CASE
WHEN $11::text != '' THEN
workspace_builds.reason::text = $11
ELSE true
END
ORDER BY
"time" DESC
LIMIT
@ -447,6 +476,7 @@ type GetAuditLogsOffsetParams struct {
Email string `db:"email" json:"email"`
DateFrom time.Time `db:"date_from" json:"date_from"`
DateTo time.Time `db:"date_to" json:"date_to"`
BuildReason string `db:"build_reason" json:"build_reason"`
}
type GetAuditLogsOffsetRow struct {
@ -488,6 +518,7 @@ func (q *sqlQuerier) GetAuditLogsOffset(ctx context.Context, arg GetAuditLogsOff
arg.Email,
arg.DateFrom,
arg.DateTo,
arg.BuildReason,
)
if err != nil {
return nil, err

View File

@ -2,18 +2,41 @@
-- ID.
-- name: GetAuditLogsOffset :many
SELECT
audit_logs.*,
audit_logs.*,
users.username AS user_username,
users.email AS user_email,
users.created_at AS user_created_at,
users.status AS user_status,
users.rbac_roles AS user_roles,
users.avatar_url AS user_avatar_url,
COUNT(audit_logs.*) OVER() AS count
COUNT(audit_logs.*) OVER () AS count
FROM
audit_logs
LEFT JOIN
users ON audit_logs.user_id = users.id
audit_logs
LEFT JOIN users ON audit_logs.user_id = users.id
LEFT JOIN
-- First join on workspaces to get the initial workspace create
-- to workspace build 1 id. This is because the first create is
-- is a different audit log than subsequent starts.
workspaces ON
audit_logs.resource_type = 'workspace' AND
audit_logs.resource_id = workspaces.id
LEFT JOIN
workspace_builds ON
-- Get the reason from the build if the resource type
-- is a workspace_build
(
audit_logs.resource_type = 'workspace_build'
AND audit_logs.resource_id = workspace_builds.id
)
OR
-- Get the reason from the build #1 if this is the first
-- workspace create.
(
audit_logs.resource_type = 'workspace' AND
audit_logs.action = 'create' AND
workspaces.id = workspace_builds.workspace_id AND
workspace_builds.build_number = 1
)
WHERE
-- Filter resource_type
CASE
@ -63,6 +86,12 @@ WHERE
"time" <= @date_to
ELSE true
END
-- Filter by build_reason
AND CASE
WHEN @build_reason::text != '' THEN
workspace_builds.reason::text = @build_reason
ELSE true
END
ORDER BY
"time" DESC
LIMIT