mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
Add build number to workspace_build audit logs (#5267)
* got links working * added translations * fixed translation * added translation for unavailable ip * added support for group, template, user links * cleaned up string * added deleted label * querying for workspace id * remove prints * fix/write tests * added build number * checking for existence of additional fields * adjust documentation * PR feedback
This commit is contained in:
@ -160,6 +160,11 @@ func (api *API) convertAuditLogs(ctx context.Context, dblogs []database.GetAudit
|
|||||||
return alogs
|
return alogs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AdditionalFields struct {
|
||||||
|
WorkspaceName string
|
||||||
|
BuildNumber string
|
||||||
|
}
|
||||||
|
|
||||||
func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogsOffsetRow) codersdk.AuditLog {
|
func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogsOffsetRow) codersdk.AuditLog {
|
||||||
ip, _ := netip.AddrFromSlice(dblog.Ip.IPNet.IP)
|
ip, _ := netip.AddrFromSlice(dblog.Ip.IPNet.IP)
|
||||||
|
|
||||||
@ -185,12 +190,29 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isDeleted := api.auditLogIsResourceDeleted(ctx, dblog)
|
var (
|
||||||
var resourceLink string
|
additionalFieldsBytes = []byte(dblog.AdditionalFields)
|
||||||
|
additionalFields AdditionalFields
|
||||||
|
err = json.Unmarshal(additionalFieldsBytes, &additionalFields)
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
api.Logger.Error(ctx, "unmarshal additional fields", slog.Error(err))
|
||||||
|
resourceInfo := map[string]string{
|
||||||
|
"workspaceName": "unknown",
|
||||||
|
"buildNumber": "unknown",
|
||||||
|
}
|
||||||
|
dblog.AdditionalFields, err = json.Marshal(resourceInfo)
|
||||||
|
api.Logger.Error(ctx, "marshal additional fields", slog.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
isDeleted = api.auditLogIsResourceDeleted(ctx, dblog)
|
||||||
|
resourceLink string
|
||||||
|
)
|
||||||
if isDeleted {
|
if isDeleted {
|
||||||
resourceLink = ""
|
resourceLink = ""
|
||||||
} else {
|
} else {
|
||||||
resourceLink = api.auditLogResourceLink(ctx, dblog)
|
resourceLink = auditLogResourceLink(dblog, additionalFields)
|
||||||
}
|
}
|
||||||
|
|
||||||
return codersdk.AuditLog{
|
return codersdk.AuditLog{
|
||||||
@ -209,23 +231,28 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
|
|||||||
StatusCode: dblog.StatusCode,
|
StatusCode: dblog.StatusCode,
|
||||||
AdditionalFields: dblog.AdditionalFields,
|
AdditionalFields: dblog.AdditionalFields,
|
||||||
User: user,
|
User: user,
|
||||||
Description: auditLogDescription(dblog),
|
Description: auditLogDescription(dblog, additionalFields),
|
||||||
ResourceLink: resourceLink,
|
ResourceLink: resourceLink,
|
||||||
IsDeleted: isDeleted,
|
IsDeleted: isDeleted,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func auditLogDescription(alog database.GetAuditLogsOffsetRow) string {
|
func auditLogDescription(alog database.GetAuditLogsOffsetRow, additionalFields AdditionalFields) string {
|
||||||
str := fmt.Sprintf("{user} %s",
|
str := fmt.Sprintf("{user} %s",
|
||||||
codersdk.AuditAction(alog.Action).FriendlyString(),
|
codersdk.AuditAction(alog.Action).FriendlyString(),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Strings for starting/stopping workspace builds follow the below format:
|
// Strings for starting/stopping workspace builds follow the below format:
|
||||||
// "{user} started build for workspace {target}"
|
// "{user} started build #{build_number} for workspace {target}"
|
||||||
// where target is a workspace instead of a workspace build
|
// where target is a workspace instead of a workspace build
|
||||||
// passed in on the FE via AuditLog.AdditionalFields rather than derived in request.go:35
|
// passed in on the FE via AuditLog.AdditionalFields rather than derived in request.go:35
|
||||||
if alog.ResourceType == database.ResourceTypeWorkspaceBuild && alog.Action != database.AuditActionDelete {
|
if alog.ResourceType == database.ResourceTypeWorkspaceBuild && alog.Action != database.AuditActionDelete {
|
||||||
|
if len(additionalFields.BuildNumber) == 0 {
|
||||||
str += " build for"
|
str += " build for"
|
||||||
|
} else {
|
||||||
|
str += fmt.Sprintf(" build #%s for",
|
||||||
|
additionalFields.BuildNumber)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't display the name (target) for git ssh keys. It's fairly long and doesn't
|
// We don't display the name (target) for git ssh keys. It's fairly long and doesn't
|
||||||
@ -295,12 +322,7 @@ func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.Get
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdditionalFields struct {
|
func auditLogResourceLink(alog database.GetAuditLogsOffsetRow, additionalFields AdditionalFields) string {
|
||||||
WorkspaceName string
|
|
||||||
BuildNumber string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAuditLogsOffsetRow) string {
|
|
||||||
switch alog.ResourceType {
|
switch alog.ResourceType {
|
||||||
case database.ResourceTypeTemplate:
|
case database.ResourceTypeTemplate:
|
||||||
return fmt.Sprintf("/templates/%s",
|
return fmt.Sprintf("/templates/%s",
|
||||||
@ -312,11 +334,8 @@ func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAudit
|
|||||||
return fmt.Sprintf("/@%s/%s",
|
return fmt.Sprintf("/@%s/%s",
|
||||||
alog.UserUsername.String, alog.ResourceTarget)
|
alog.UserUsername.String, alog.ResourceTarget)
|
||||||
case database.ResourceTypeWorkspaceBuild:
|
case database.ResourceTypeWorkspaceBuild:
|
||||||
additionalFieldsBytes := []byte(alog.AdditionalFields)
|
if len(additionalFields.WorkspaceName) == 0 || len(additionalFields.BuildNumber) == 0 {
|
||||||
var additionalFields AdditionalFields
|
return ""
|
||||||
err := json.Unmarshal(additionalFieldsBytes, &additionalFields)
|
|
||||||
if err != nil {
|
|
||||||
api.Logger.Error(ctx, "unmarshal workspace name", slog.Error(err))
|
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("/@%s/%s/builds/%s",
|
return fmt.Sprintf("/@%s/%s/builds/%s",
|
||||||
alog.UserUsername.String, additionalFields.WorkspaceName, additionalFields.BuildNumber)
|
alog.UserUsername.String, additionalFields.WorkspaceName, additionalFields.BuildNumber)
|
||||||
|
@ -531,23 +531,23 @@ func (server *Server) FailJob(ctx context.Context, failJob *proto.FailedJob) (*p
|
|||||||
auditor := server.Auditor.Load()
|
auditor := server.Auditor.Load()
|
||||||
build, getBuildErr := server.Database.GetWorkspaceBuildByJobID(ctx, job.ID)
|
build, getBuildErr := server.Database.GetWorkspaceBuildByJobID(ctx, job.ID)
|
||||||
if getBuildErr != nil {
|
if getBuildErr != nil {
|
||||||
server.Logger.Error(ctx, "failed to create audit log - get build err", slog.Error(err))
|
server.Logger.Error(ctx, "audit log - get build", slog.Error(err))
|
||||||
} else {
|
} else {
|
||||||
auditAction := auditActionFromTransition(build.Transition)
|
auditAction := auditActionFromTransition(build.Transition)
|
||||||
workspace, getWorkspaceErr := server.Database.GetWorkspaceByID(ctx, build.WorkspaceID)
|
workspace, getWorkspaceErr := server.Database.GetWorkspaceByID(ctx, build.WorkspaceID)
|
||||||
if getWorkspaceErr != nil {
|
if getWorkspaceErr != nil {
|
||||||
server.Logger.Error(ctx, "failed to create audit log - get workspace err", slog.Error(err))
|
server.Logger.Error(ctx, "audit log - get workspace", slog.Error(err))
|
||||||
} else {
|
} else {
|
||||||
// We pass the below information to the Auditor so that it
|
// We pass the below information to the Auditor so that it
|
||||||
// can form a friendly string for the user to view in the UI.
|
// can form a friendly string for the user to view in the UI.
|
||||||
workspaceResourceInfo := map[string]string{
|
buildResourceInfo := map[string]string{
|
||||||
"workspaceName": workspace.Name,
|
"workspaceName": workspace.Name,
|
||||||
"buildNumber": strconv.FormatInt(int64(build.BuildNumber), 10),
|
"buildNumber": strconv.FormatInt(int64(build.BuildNumber), 10),
|
||||||
}
|
}
|
||||||
|
|
||||||
wriBytes, err := json.Marshal(workspaceResourceInfo)
|
wriBytes, err := json.Marshal(buildResourceInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.Logger.Error(ctx, "could not marshal workspace name", slog.Error(err))
|
server.Logger.Error(ctx, "marshal workspace resource info for failed job", slog.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
audit.BuildAudit(ctx, &audit.BuildAuditParams[database.WorkspaceBuild]{
|
audit.BuildAudit(ctx, &audit.BuildAuditParams[database.WorkspaceBuild]{
|
||||||
@ -756,14 +756,14 @@ func (server *Server) CompleteJob(ctx context.Context, completed *proto.Complete
|
|||||||
|
|
||||||
// We pass the below information to the Auditor so that it
|
// We pass the below information to the Auditor so that it
|
||||||
// can form a friendly string for the user to view in the UI.
|
// can form a friendly string for the user to view in the UI.
|
||||||
workspaceResourceInfo := map[string]string{
|
buildResourceInfo := map[string]string{
|
||||||
"workspaceName": workspace.Name,
|
"workspaceName": workspace.Name,
|
||||||
"buildNumber": strconv.FormatInt(int64(workspaceBuild.BuildNumber), 10),
|
"buildNumber": strconv.FormatInt(int64(workspaceBuild.BuildNumber), 10),
|
||||||
}
|
}
|
||||||
|
|
||||||
wriBytes, err := json.Marshal(workspaceResourceInfo)
|
wriBytes, err := json.Marshal(buildResourceInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.Logger.Error(ctx, "marshal resource info", slog.Error(err))
|
server.Logger.Error(ctx, "marshal resource info for successful job", slog.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
audit.BuildAudit(ctx, &audit.BuildAuditParams[database.WorkspaceBuild]{
|
audit.BuildAudit(ctx, &audit.BuildAuditParams[database.WorkspaceBuild]{
|
||||||
|
@ -11,7 +11,7 @@ We track **create, update and delete** events for the following resources:
|
|||||||
- Template
|
- Template
|
||||||
- TemplateVersion
|
- TemplateVersion
|
||||||
- Workspace
|
- Workspace
|
||||||
- Workspace start/stop
|
- WorkspaceBuild
|
||||||
- User
|
- User
|
||||||
- Group
|
- Group
|
||||||
|
|
||||||
|
@ -14,10 +14,7 @@ export const AuditLogDescription: FC<{ auditLog: AuditLog }> = ({
|
|||||||
let target = auditLog.resource_target.trim()
|
let target = auditLog.resource_target.trim()
|
||||||
|
|
||||||
// audit logs with a resource_type of workspace build use workspace name as a target
|
// audit logs with a resource_type of workspace build use workspace name as a target
|
||||||
if (
|
if (auditLog.resource_type === "workspace_build") {
|
||||||
auditLog.resource_type === "workspace_build" &&
|
|
||||||
auditLog.additional_fields.workspaceName
|
|
||||||
) {
|
|
||||||
target = auditLog.additional_fields.workspaceName.trim()
|
target = auditLog.additional_fields.workspaceName.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user