Files
Cian Johnston f1d333f0e6 refactor: deduplicate utility helpers across the codebase (#23338)
Audited exported helpers in `coderd/util/*`, `testutil`, `cryptorand`,
and friends, then replaced duplicated implementations with canonical
versions.

- **fix: `maps.SortedKeys` generic signature** — value type was
hardcoded to `any`, making it impossible to actually call. Added second
type parameter `V any`. Added table-driven tests with `cmp.Diff`.
- **refactor: replace ad-hoc ptr helpers with `ptr.Ref`** — removed
`int64Ptr`, `stringPtr`, `boolPtr`, `i64ptr`, `strPtr`, `PtrInt32`
across 6 files.
- **refactor: replace local `sortedKeys`/`sortKeys` with
`maps.SortedKeys`** — now that the signature is fixed, scripts can use
it.
- **refactor: replace hand-rolled `capitalize` with
`strings.Capitalize`** — the typegen version was also not UTF-8 safe.

> 🤖 This PR was created with the help of Coder Agents, and was reviewed
by my human. 🧑‍💻
2026-03-20 15:12:41 +00:00

156 lines
4.2 KiB
Go

package main
import (
"bytes"
"flag"
"log"
"os"
"strconv"
"strings"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/util/maps"
"github.com/coder/coder/v2/enterprise/audit"
"github.com/coder/coder/v2/scripts/atomicwrite"
)
var (
auditDocFile string
dryRun bool
generatorPrefix = []byte("<!-- Code generated by 'make docs/admin/security/audit-logs.md'. DO NOT EDIT -->")
generatorSuffix = []byte("<!-- End generated by 'make docs/admin/security/audit-logs.md'. -->")
)
/*
*
AuditableResourcesMap is derived from audit.AuditableResources
and has the following structure:
{
friendlyResourceName: {
fieldName1: isTracked,
fieldName2: isTracked,
...
},
...
}
*/
type AuditableResourcesMap map[string]map[string]bool
func main() {
flag.StringVar(&auditDocFile, "audit-doc-file", "docs/admin/security/audit-logs.md", "Path to audit log doc file")
flag.BoolVar(&dryRun, "dry-run", false, "Dry run")
flag.Parse()
auditableResourcesMap := readAuditableResources()
doc, err := readAuditDoc()
if err != nil {
log.Fatal("can't read audit doc: ", err)
}
doc, err = updateAuditDoc(doc, auditableResourcesMap)
if err != nil {
log.Fatal("can't update audit doc: ", err)
}
if dryRun {
log.Println(string(doc))
return
}
err = writeAuditDoc(doc)
if err != nil {
log.Fatal("can't write updated audit doc: ", err)
}
}
// Transforms audit.AuditableResources to AuditableResourcesMap,
// which uses friendlier language.
func readAuditableResources() AuditableResourcesMap {
auditableResourcesMap := make(AuditableResourcesMap)
for resourceName, resourceFields := range audit.AuditableResources {
friendlyResourceName := strings.Split(resourceName, ".")[2]
fieldNameMap := make(map[string]bool)
for fieldName, action := range resourceFields {
fieldNameMap[fieldName] = action != audit.ActionIgnore
auditableResourcesMap[friendlyResourceName] = fieldNameMap
}
}
return auditableResourcesMap
}
func readAuditDoc() ([]byte, error) {
doc, err := os.ReadFile(auditDocFile)
if err != nil {
return nil, err
}
return doc, nil
}
// Writes a markdown table of audit log resources to a buffer
func updateAuditDoc(doc []byte, auditableResourcesMap AuditableResourcesMap) ([]byte, error) {
// We must sort the resources to ensure table ordering
sortedResourceNames := maps.SortedKeys(auditableResourcesMap)
i := bytes.Index(doc, generatorPrefix)
if i < 0 {
return nil, xerrors.New("generator prefix tag not found")
}
tableStartIndex := i + len(generatorPrefix) + 1
j := bytes.Index(doc[tableStartIndex:], generatorSuffix)
if j < 0 {
return nil, xerrors.New("generator suffix tag not found")
}
tableEndIndex := tableStartIndex + j
var buffer bytes.Buffer
_, _ = buffer.Write(doc[:tableStartIndex])
_ = buffer.WriteByte('\n')
_, _ = buffer.WriteString("|<b>Resource<b>||\n")
_, _ = buffer.WriteString("|--|-----------------|\n")
for _, resourceName := range sortedResourceNames {
readableResourceName := resourceName
// AuditableGroup is really a combination of Group and GroupMember resources
// but we use the label 'Group' in our docs to avoid confusion.
if resourceName == "AuditableGroup" {
readableResourceName = "Group"
}
// Create a string of audit actions for each resource
var auditActions []string
for _, action := range audit.AuditActionMap[readableResourceName] {
auditActions = append(auditActions, string(action))
}
auditActionsString := strings.Join(auditActions, ", ")
_, _ = buffer.WriteString("|" + readableResourceName + "<br><i>" + auditActionsString + "</i>|<table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody>" + "|")
// We must sort the field names to ensure sub-table ordering
sortedFieldNames := maps.SortedKeys(auditableResourcesMap[resourceName])
for _, fieldName := range sortedFieldNames {
isTracked := auditableResourcesMap[resourceName][fieldName]
_, _ = buffer.WriteString("<tr><td>" + fieldName + "</td><td>" + strconv.FormatBool(isTracked) + "</td></tr>")
}
_, _ = buffer.WriteString("</tbody></table>\n")
}
_, _ = buffer.WriteString("\n")
_, _ = buffer.Write(doc[tableEndIndex:])
return buffer.Bytes(), nil
}
func writeAuditDoc(doc []byte) error {
return atomicwrite.File(auditDocFile, doc)
}