mirror of https://github.com/coder/coder.git
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:
parent
36384aa3c1
commit
322a4d93e1
|
@ -5583,6 +5583,18 @@ const docTemplate = `{
|
|||
}
|
||||
]
|
||||
},
|
||||
"build_reason": {
|
||||
"enum": [
|
||||
"autostart",
|
||||
"autostop",
|
||||
"initiator"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.BuildReason"
|
||||
}
|
||||
]
|
||||
},
|
||||
"resource_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
|
|
|
@ -4943,6 +4943,14 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"build_reason": {
|
||||
"enum": ["autostart", "autostop", "initiator"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.BuildReason"
|
||||
}
|
||||
]
|
||||
},
|
||||
"resource_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
|
|
|
@ -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 ""
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -125,6 +125,7 @@ type CreateTestAuditLogRequest struct {
|
|||
ResourceType ResourceType `json:"resource_type,omitempty" enums:"organization,template,template_version,user,workspace,workspace_build,git_ssh_key,api_key,group"`
|
||||
ResourceID uuid.UUID `json:"resource_id,omitempty" format:"uuid"`
|
||||
Time time.Time `json:"time,omitempty" format:"date-time"`
|
||||
BuildReason BuildReason `json:"build_reason,omitempty" enums:"autostart,autostop,initiator"`
|
||||
}
|
||||
|
||||
// AuditLogs retrieves audit logs from the given page.
|
||||
|
|
|
@ -31,7 +31,8 @@ The supported filters are:
|
|||
- `username` - The username of the user who triggered the action.
|
||||
- `email` - The email of the user who triggered the action.
|
||||
- `date_from` - The inclusive start date with format `YYYY-MM-DD`.
|
||||
- `date_to ` - the inclusive end date with format `YYYY-MM-DD`.
|
||||
- `date_to` - The inclusive end date with format `YYYY-MM-DD`.
|
||||
- `build_reason` - To be used with `resource_type:workspace_build`, the [initiator](https://pkg.go.dev/github.com/coder/coder/codersdk#BuildReason) behind the build start or stop.
|
||||
|
||||
## Enabling this feature
|
||||
|
||||
|
|
|
@ -106,6 +106,7 @@ curl -X POST http://coder-server:8080/api/v2/audit/testgenerate \
|
|||
```json
|
||||
{
|
||||
"action": "create",
|
||||
"build_reason": "autostart",
|
||||
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
|
||||
"resource_type": "organization",
|
||||
"time": "2019-08-24T14:15:22Z"
|
||||
|
|
|
@ -783,6 +783,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
|||
```json
|
||||
{
|
||||
"action": "create",
|
||||
"build_reason": "autostart",
|
||||
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
|
||||
"resource_type": "organization",
|
||||
"time": "2019-08-24T14:15:22Z"
|
||||
|
@ -794,6 +795,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
|||
| Name | Type | Required | Restrictions | Description |
|
||||
| --------------- | ---------------------------------------------- | -------- | ------------ | ----------- |
|
||||
| `action` | [codersdk.AuditAction](#codersdkauditaction) | false | | |
|
||||
| `build_reason` | [codersdk.BuildReason](#codersdkbuildreason) | false | | |
|
||||
| `resource_id` | string | false | | |
|
||||
| `resource_type` | [codersdk.ResourceType](#codersdkresourcetype) | false | | |
|
||||
| `time` | string | false | | |
|
||||
|
@ -807,6 +809,9 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
|||
| `action` | `delete` |
|
||||
| `action` | `start` |
|
||||
| `action` | `stop` |
|
||||
| `build_reason` | `autostart` |
|
||||
| `build_reason` | `autostop` |
|
||||
| `build_reason` | `initiator` |
|
||||
| `resource_type` | `organization` |
|
||||
| `resource_type` | `template` |
|
||||
| `resource_type` | `template_version` |
|
||||
|
|
|
@ -209,6 +209,7 @@ export interface CreateTestAuditLogRequest {
|
|||
readonly resource_type?: ResourceType
|
||||
readonly resource_id?: string
|
||||
readonly time?: string
|
||||
readonly build_reason?: BuildReason
|
||||
}
|
||||
|
||||
// From codersdk/apikey.go
|
||||
|
|
|
@ -40,6 +40,10 @@ const presetFilters = [
|
|||
query: "resource_type:workspace_build action:start",
|
||||
name: "Started builds",
|
||||
},
|
||||
{
|
||||
query: "resource_type:workspace_build action:start build_reason:initiator",
|
||||
name: "Builds started by a user",
|
||||
},
|
||||
]
|
||||
|
||||
export interface AuditPageViewProps {
|
||||
|
|
Loading…
Reference in New Issue