From 3bda3b5fea4d77e61fdbe04700ba8faf50d91f82 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 6 May 2022 14:33:17 -0500 Subject: [PATCH 1/3] feat: add codegen for audit.AuditableResources entries --- coderd/audit/audit_gen.sh | 6 ++ scripts/auditgen/main.go | 118 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100755 coderd/audit/audit_gen.sh create mode 100644 scripts/auditgen/main.go diff --git a/coderd/audit/audit_gen.sh b/coderd/audit/audit_gen.sh new file mode 100755 index 0000000000000..494b84df6c0fe --- /dev/null +++ b/coderd/audit/audit_gen.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" +go run ./scripts/auditgen ./coderd/database "$@" diff --git a/scripts/auditgen/main.go b/scripts/auditgen/main.go new file mode 100644 index 0000000000000..73ff662c5d7aa --- /dev/null +++ b/scripts/auditgen/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "context" + "fmt" + "go/types" + "io" + "os" + "reflect" + "strings" + + "golang.org/x/tools/go/packages" + "golang.org/x/xerrors" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/sloghuman" +) + +func main() { + ctx := context.Background() + log := slog.Make(sloghuman.Sink(os.Stderr)) + code, err := GenerateFromDirectory(ctx, os.Args[1], os.Args[2:]...) + if err != nil { + log.Fatal(ctx, "generate", slog.Error(err)) + } + + _, _ = fmt.Print(code) +} + +// GenerateFromDirectory will return all the typescript code blocks for a directory +func GenerateFromDirectory(ctx context.Context, directory string, typeNames ...string) (string, error) { + g := Generator{} + err := g.parsePackage(ctx, directory) + if err != nil { + return "", xerrors.Errorf("parse package %q: %w", directory, err) + } + + str, err := g.generate(typeNames...) + if err != nil { + return "", xerrors.Errorf("parse package %q: %w", directory, err) + } + + return str, nil +} + +type Generator struct { + // Package we are scanning. + pkg *packages.Package +} + +// parsePackage takes a list of patterns such as a directory, and parses them. +func (g *Generator) parsePackage(ctx context.Context, patterns ...string) error { + cfg := &packages.Config{ + // Just accept the fact we need these flags for what we want. Feel free to add + // more, it'll just increase the time it takes to parse. + Mode: packages.NeedTypes | packages.NeedName | packages.NeedTypesInfo | + packages.NeedTypesSizes | packages.NeedSyntax, + Tests: false, + Context: ctx, + } + + pkgs, err := packages.Load(cfg, patterns...) + if err != nil { + return xerrors.Errorf("load package: %w", err) + } + + // Only support 1 package for now. We can expand it if we need later, we + // just need to hook up multiple packages in the generator. + if len(pkgs) != 1 { + return xerrors.Errorf("expected 1 package, found %d", len(pkgs)) + } + + g.pkg = pkgs[0] + return nil +} + +func (g *Generator) generate(typeNames ...string) (string, error) { + sb := strings.Builder{} + + _, _ = fmt.Fprint(&sb, "Copy the following code into the audit.AuditableResources table\n\n") + + for _, typName := range typeNames { + obj := g.pkg.Types.Scope().Lookup(typName) + if obj == nil || obj.Type() == nil { + return "", xerrors.Errorf("type doesn't exist %q", typName) + } + + switch obj := obj.(type) { + case *types.TypeName: + named, ok := obj.Type().(*types.Named) + if !ok { + panic("all typenames should be named types") + } + + switch typ := named.Underlying().(type) { + case *types.Struct: + g.writeStruct(&sb, typ, typName) + + default: + return "", xerrors.Errorf("invalid type %T", obj) + } + default: + return "", xerrors.Errorf("invalid type %T", obj) + } + } + + return sb.String(), nil +} + +func (*Generator) writeStruct(w io.Writer, st *types.Struct, name string) { + _, _ = fmt.Fprintf(w, "\t&database.%s{}: {\n", name) + + for i := 0; i < st.NumFields(); i++ { + _, _ = fmt.Fprintf(w, "\t\t\"%s\": ActionIgnore, // TODO: why\n", reflect.StructTag(st.Tag(i)).Get("json")) + } + + _, _ = fmt.Fprint(w, "\t},\n") +} From 66e810b6ca7789ec6cca571b556c9780d0a857f9 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 10 May 2022 16:03:57 -0500 Subject: [PATCH 2/3] audit_gen.sh -> generate.sh --- coderd/audit/audit_gen.sh | 6 ------ coderd/audit/generate.sh | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) delete mode 100755 coderd/audit/audit_gen.sh create mode 100755 coderd/audit/generate.sh diff --git a/coderd/audit/audit_gen.sh b/coderd/audit/audit_gen.sh deleted file mode 100755 index 494b84df6c0fe..0000000000000 --- a/coderd/audit/audit_gen.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -cd "$(git rev-parse --show-toplevel)" -go run ./scripts/auditgen ./coderd/database "$@" diff --git a/coderd/audit/generate.sh b/coderd/audit/generate.sh new file mode 100755 index 0000000000000..caea324c3ce4b --- /dev/null +++ b/coderd/audit/generate.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# This script facilitates code generation for auditing types. It outputs code +# that can be copied and pasted into the audit.AuditableResources table. By +# default, every field is ignored. It is your responsiblity to go through each field and document why each field should or should not be audited. +# +# Usage: +# ./generate.sh ... + + +set -euo pipefail + +cd "$(dirname "$0")" && cd "$(git rev-parse --show-toplevel)" +go run ./scripts/auditgen ./coderd/database "$@" From 3641f3dc1cd268f5e3ab4decb7871cd5884d2f28 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 10 May 2022 16:17:50 -0500 Subject: [PATCH 3/3] fixup! audit_gen.sh -> generate.sh --- coderd/audit/generate.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coderd/audit/generate.sh b/coderd/audit/generate.sh index caea324c3ce4b..d6763c7b9780c 100755 --- a/coderd/audit/generate.sh +++ b/coderd/audit/generate.sh @@ -2,7 +2,8 @@ # This script facilitates code generation for auditing types. It outputs code # that can be copied and pasted into the audit.AuditableResources table. By -# default, every field is ignored. It is your responsiblity to go through each field and document why each field should or should not be audited. +# default, every field is ignored. It is your responsiblity to go through each +# field and document why each field should or should not be audited. # # Usage: # ./generate.sh ...