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
|
||||
}
|
||||
|
||||
type AdditionalFields struct {
|
||||
WorkspaceName string
|
||||
BuildNumber string
|
||||
}
|
||||
|
||||
func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogsOffsetRow) codersdk.AuditLog {
|
||||
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 resourceLink string
|
||||
var (
|
||||
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 {
|
||||
resourceLink = ""
|
||||
} else {
|
||||
resourceLink = api.auditLogResourceLink(ctx, dblog)
|
||||
resourceLink = auditLogResourceLink(dblog, additionalFields)
|
||||
}
|
||||
|
||||
return codersdk.AuditLog{
|
||||
@ -209,23 +231,28 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
|
||||
StatusCode: dblog.StatusCode,
|
||||
AdditionalFields: dblog.AdditionalFields,
|
||||
User: user,
|
||||
Description: auditLogDescription(dblog),
|
||||
Description: auditLogDescription(dblog, additionalFields),
|
||||
ResourceLink: resourceLink,
|
||||
IsDeleted: isDeleted,
|
||||
}
|
||||
}
|
||||
|
||||
func auditLogDescription(alog database.GetAuditLogsOffsetRow) string {
|
||||
func auditLogDescription(alog database.GetAuditLogsOffsetRow, additionalFields AdditionalFields) string {
|
||||
str := fmt.Sprintf("{user} %s",
|
||||
codersdk.AuditAction(alog.Action).FriendlyString(),
|
||||
)
|
||||
|
||||
// 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
|
||||
// passed in on the FE via AuditLog.AdditionalFields rather than derived in request.go:35
|
||||
if alog.ResourceType == database.ResourceTypeWorkspaceBuild && alog.Action != database.AuditActionDelete {
|
||||
str += " build for"
|
||||
if len(additionalFields.BuildNumber) == 0 {
|
||||
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
|
||||
@ -295,12 +322,7 @@ func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.Get
|
||||
}
|
||||
}
|
||||
|
||||
type AdditionalFields struct {
|
||||
WorkspaceName string
|
||||
BuildNumber string
|
||||
}
|
||||
|
||||
func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAuditLogsOffsetRow) string {
|
||||
func auditLogResourceLink(alog database.GetAuditLogsOffsetRow, additionalFields AdditionalFields) string {
|
||||
switch alog.ResourceType {
|
||||
case database.ResourceTypeTemplate:
|
||||
return fmt.Sprintf("/templates/%s",
|
||||
@ -312,11 +334,8 @@ func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAudit
|
||||
return fmt.Sprintf("/@%s/%s",
|
||||
alog.UserUsername.String, alog.ResourceTarget)
|
||||
case database.ResourceTypeWorkspaceBuild:
|
||||
additionalFieldsBytes := []byte(alog.AdditionalFields)
|
||||
var additionalFields AdditionalFields
|
||||
err := json.Unmarshal(additionalFieldsBytes, &additionalFields)
|
||||
if err != nil {
|
||||
api.Logger.Error(ctx, "unmarshal workspace name", slog.Error(err))
|
||||
if len(additionalFields.WorkspaceName) == 0 || len(additionalFields.BuildNumber) == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("/@%s/%s/builds/%s",
|
||||
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()
|
||||
build, getBuildErr := server.Database.GetWorkspaceBuildByJobID(ctx, job.ID)
|
||||
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 {
|
||||
auditAction := auditActionFromTransition(build.Transition)
|
||||
workspace, getWorkspaceErr := server.Database.GetWorkspaceByID(ctx, build.WorkspaceID)
|
||||
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 {
|
||||
// We pass the below information to the Auditor so that it
|
||||
// can form a friendly string for the user to view in the UI.
|
||||
workspaceResourceInfo := map[string]string{
|
||||
buildResourceInfo := map[string]string{
|
||||
"workspaceName": workspace.Name,
|
||||
"buildNumber": strconv.FormatInt(int64(build.BuildNumber), 10),
|
||||
}
|
||||
|
||||
wriBytes, err := json.Marshal(workspaceResourceInfo)
|
||||
wriBytes, err := json.Marshal(buildResourceInfo)
|
||||
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]{
|
||||
@ -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
|
||||
// can form a friendly string for the user to view in the UI.
|
||||
workspaceResourceInfo := map[string]string{
|
||||
buildResourceInfo := map[string]string{
|
||||
"workspaceName": workspace.Name,
|
||||
"buildNumber": strconv.FormatInt(int64(workspaceBuild.BuildNumber), 10),
|
||||
}
|
||||
|
||||
wriBytes, err := json.Marshal(workspaceResourceInfo)
|
||||
wriBytes, err := json.Marshal(buildResourceInfo)
|
||||
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]{
|
||||
|
Reference in New Issue
Block a user