forked from coder/coder
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
165 lines (133 loc) · 4.33 KB
/
main.go
File metadata and controls
165 lines (133 loc) · 4.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package main
import (
"bytes"
"flag"
"log"
"os"
"sort"
"strconv"
"strings"
"golang.org/x/xerrors"
"github.com/coder/coder/enterprise/audit"
)
var (
auditDocFile string
dryRun bool
generatorPrefix = []byte("<!-- Code generated by 'make docs/admin/audit-logs.md'. DO NOT EDIT -->")
generatorSuffix = []byte("<!-- End generated by 'make docs/admin/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/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 := sortKeys(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 := sortKeys(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 {
// G306: Expect WriteFile permissions to be 0600 or less
/* #nosec G306 */
return os.WriteFile(auditDocFile, doc, 0644)
}
func sortKeys[T any](stringMap map[string]T) []string {
var keyNames []string
for key := range stringMap {
keyNames = append(keyNames, key)
}
sort.Strings(keyNames)
return keyNames
}