Skip to content

Commit 9e68769

Browse files
committed
feat: add codegen for audit.AuditableResources entries
1 parent c96d439 commit 9e68769

File tree

2 files changed

+122
-0
lines changed

2 files changed

+122
-0
lines changed

coderd/audit/audit_gen.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
cd "$(git rev-parse --show-toplevel)"
6+
go run ./scripts/auditgen ./coderd/database "$1"

scripts/auditgen/main.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"go/types"
7+
"io"
8+
"os"
9+
"reflect"
10+
"strings"
11+
12+
"golang.org/x/tools/go/packages"
13+
"golang.org/x/xerrors"
14+
15+
"cdr.dev/slog"
16+
"cdr.dev/slog/sloggers/sloghuman"
17+
)
18+
19+
func main() {
20+
ctx := context.Background()
21+
log := slog.Make(sloghuman.Sink(os.Stderr))
22+
code, err := GenerateFromDirectory(ctx, os.Args[1], os.Args[2])
23+
if err != nil {
24+
log.Fatal(ctx, "generate", slog.Error(err))
25+
}
26+
27+
_, _ = fmt.Print(code)
28+
}
29+
30+
// GenerateFromDirectory will return all the typescript code blocks for a directory
31+
func GenerateFromDirectory(ctx context.Context, directory, typName string) (string, error) {
32+
g := Generator{}
33+
err := g.parsePackage(ctx, directory)
34+
if err != nil {
35+
return "", xerrors.Errorf("parse package %q: %w", directory, err)
36+
}
37+
38+
str, err := g.generate(typName)
39+
if err != nil {
40+
return "", xerrors.Errorf("parse package %q: %w", directory, err)
41+
}
42+
43+
return str, nil
44+
}
45+
46+
type Generator struct {
47+
// Package we are scanning.
48+
pkg *packages.Package
49+
}
50+
51+
// parsePackage takes a list of patterns such as a directory, and parses them.
52+
func (g *Generator) parsePackage(ctx context.Context, patterns ...string) error {
53+
cfg := &packages.Config{
54+
// Just accept the fact we need these flags for what we want. Feel free to add
55+
// more, it'll just increase the time it takes to parse.
56+
Mode: packages.NeedTypes | packages.NeedName | packages.NeedTypesInfo |
57+
packages.NeedTypesSizes | packages.NeedSyntax,
58+
Tests: false,
59+
Context: ctx,
60+
}
61+
62+
pkgs, err := packages.Load(cfg, patterns...)
63+
if err != nil {
64+
return xerrors.Errorf("load package: %w", err)
65+
}
66+
67+
// Only support 1 package for now. We can expand it if we need later, we
68+
// just need to hook up multiple packages in the generator.
69+
if len(pkgs) != 1 {
70+
return xerrors.Errorf("expected 1 package, found %d", len(pkgs))
71+
}
72+
73+
g.pkg = pkgs[0]
74+
return nil
75+
}
76+
77+
func (g *Generator) generate(typName string) (string, error) {
78+
sb := strings.Builder{}
79+
80+
_, _ = fmt.Fprint(&sb, "Copy the following code into the audit.AuditableResources table\n\n")
81+
82+
obj := g.pkg.Types.Scope().Lookup(typName)
83+
if obj == nil || obj.Type() == nil {
84+
return "", xerrors.Errorf("type doesn't exist %q", typName)
85+
}
86+
87+
switch obj := obj.(type) {
88+
case *types.TypeName:
89+
named, ok := obj.Type().(*types.Named)
90+
if !ok {
91+
panic("all typenames should be named types")
92+
}
93+
94+
switch typ := named.Underlying().(type) {
95+
case *types.Struct:
96+
g.writeStruct(&sb, typ, typName)
97+
98+
default:
99+
return "", xerrors.Errorf("invalid type %T", obj)
100+
}
101+
default:
102+
return "", xerrors.Errorf("invalid type %T", obj)
103+
}
104+
105+
return sb.String(), nil
106+
}
107+
108+
func (g *Generator) writeStruct(w io.Writer, st *types.Struct, name string) {
109+
_, _ = fmt.Fprintf(w, "\t&database.%s{}: {\n", name)
110+
111+
for i := 0; i < st.NumFields(); i++ {
112+
_, _ = fmt.Fprintf(w, "\t\t\"%s\": ActionIgnore, // TODO: why\n", reflect.StructTag(st.Tag(i)).Get("json"))
113+
}
114+
115+
_, _ = fmt.Fprint(w, "\t},\n")
116+
}

0 commit comments

Comments
 (0)