From be1063d1684b3b448e5f2c70fc2e8b83ba14ce25 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Mon, 4 May 2020 11:23:35 -0400 Subject: [PATCH] Clean up and reorganize API generation --- Makefile | 7 +- api/internal/genapi/Makefile | 9 + api/internal/genapi/input.go | 97 +++++++++ api/internal/genapi/main.go | 8 + api/internal/genapi/makeimports.go | 7 - api/internal/genapi/{genapi.go => parse.go} | 230 +------------------- api/internal/genapi/templates.go | 132 +++++++++++ 7 files changed, 255 insertions(+), 235 deletions(-) create mode 100644 api/internal/genapi/Makefile create mode 100644 api/internal/genapi/input.go create mode 100644 api/internal/genapi/main.go delete mode 100644 api/internal/genapi/makeimports.go rename api/internal/genapi/{genapi.go => parse.go} (54%) create mode 100644 api/internal/genapi/templates.go diff --git a/Makefile b/Makefile index 7db9e7dd3c..2be1fefb4d 100644 --- a/Makefile +++ b/Makefile @@ -5,11 +5,13 @@ THIS_FILE := $(lastword $(MAKEFILE_LIST)) TMP_DIR := $(shell mktemp -d) REPO_PATH := github.com/hashicorp/watchtower +export APIGEN_BASEPATH := $(shell pwd) + bootstrap: go generate -tags tools tools/tools.go api: - APIGEN_BASEPATH=$(shell pwd) go generate -tags genapi api/internal/genapi/makeimports.go + $(MAKE) --environment-overrides -C api/internal/genapi api ### oplog requires protoc-gen-go v1.20.0 or later # GO111MODULE=on go get -u github.com/golang/protobuf/protoc-gen-go@v1.40 @@ -19,7 +21,8 @@ protolint: @buf check lint protobuild: - # To add a new directory containing a proto pass the proto's root path in through the --proto_path flag. + # To add a new directory containing a proto pass the proto's root path in + # through the --proto_path flag. @bash make/protoc_gen_plugin.bash \ "--proto_path=internal/proto/local" \ "--proto_include_path=internal/proto/third_party" \ diff --git a/api/internal/genapi/Makefile b/api/internal/genapi/Makefile new file mode 100644 index 0000000000..7124d1b5d6 --- /dev/null +++ b/api/internal/genapi/Makefile @@ -0,0 +1,9 @@ +# Determine this makefile's path. +# Be sure to place this BEFORE `include` directives, if any. +THIS_FILE := $(lastword $(MAKEFILE_LIST)) + +api: + go run -tags genapi . + goimports -w ${APIGEN_BASEPATH}/api + +.PHONY: api diff --git a/api/internal/genapi/input.go b/api/internal/genapi/input.go new file mode 100644 index 0000000000..b35fd988ac --- /dev/null +++ b/api/internal/genapi/input.go @@ -0,0 +1,97 @@ +// +build genapi + +package main + +import "os" + +type generateInfo struct { + inFile string + inName string + outFile string + outName string + outPkg string + structFields string + parentName string + detailName string + templateType templateType +} + +var inputStructs = []*generateInfo{ + { + os.Getenv("APIGEN_BASEPATH") + "/internal/gen/controller/api/error.pb.go", + "Error", + os.Getenv("APIGEN_BASEPATH") + "/api/error.go", + "Error", + "api", + "", + "", + "", + templateTypeResource, + }, + { + os.Getenv("APIGEN_BASEPATH") + "/internal/gen/controller/api/error.pb.go", + "ErrorDetails", + os.Getenv("APIGEN_BASEPATH") + "/api/error_details.go", + "ErrorDetails", + "api", + "", + "", + "", + templateTypeResource, + }, + { + os.Getenv("APIGEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host.pb.go", + "Host", + os.Getenv("APIGEN_BASEPATH") + "/api/hosts/host.go", + "Host", + "hosts", + "", + "", + "", + templateTypeResource, + }, + { + os.Getenv("APIGEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_set.pb.go", + "HostSet", + os.Getenv("APIGEN_BASEPATH") + "/api/hosts/host_set.go", + "HostSet", + "hosts", + "", + "", + "", + templateTypeResource, + }, + { + os.Getenv("APIGEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_catalog.pb.go", + "HostCatalog", + os.Getenv("APIGEN_BASEPATH") + "/api/hosts/host_catalog.go", + "HostCatalog", + "hosts", + "", + "", + "", + templateTypeResource, + }, + { + os.Getenv("APIGEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_catalog.pb.go", + "StaticHostCatalogDetails", + os.Getenv("APIGEN_BASEPATH") + "/api/hosts/static_host_catalog.go", + "StaticHostCatalogDetails", + "hosts", + "", + "HostCatalog", + "StaticHostCatalog", + templateTypeDetail, + }, + { + os.Getenv("APIGEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_catalog.pb.go", + "AwsEc2HostCatalogDetails", + os.Getenv("APIGEN_BASEPATH") + "/api/hosts/awsec2_host_catalog.go", + "AwsEc2HostCatalogDetails", + "hosts", + "", + "HostCatalog", + "AwsEc2HostCatalog", + templateTypeDetail, + }, +} diff --git a/api/internal/genapi/main.go b/api/internal/genapi/main.go new file mode 100644 index 0000000000..d5b1ff3e20 --- /dev/null +++ b/api/internal/genapi/main.go @@ -0,0 +1,8 @@ +// +build genapi + +package main + +func main() { + parsePBs() + writeTemplates() +} diff --git a/api/internal/genapi/makeimports.go b/api/internal/genapi/makeimports.go deleted file mode 100644 index 83c9e9434d..0000000000 --- a/api/internal/genapi/makeimports.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -// +build genapi - -//go:generate go run genapi.go -//go:generate goimports -w ../../hosts -//go:generate goimports -w ../.. diff --git a/api/internal/genapi/genapi.go b/api/internal/genapi/parse.go similarity index 54% rename from api/internal/genapi/genapi.go rename to api/internal/genapi/parse.go index 6060da39c6..75abfbe671 100644 --- a/api/internal/genapi/genapi.go +++ b/api/internal/genapi/parse.go @@ -1,7 +1,7 @@ -package main - // +build genapi +package main + import ( "bufio" "bytes" @@ -10,117 +10,13 @@ import ( "go/format" "go/parser" "go/token" - "io/ioutil" "os" "path/filepath" "regexp" "strings" - "text/template" - "time" -) - -type templateType int - -const ( - templateTypeResource templateType = iota - templateTypeDetail ) -type generateInfo struct { - inFile string - inName string - outFile string - outName string - outPkg string - structFields string - parentName string - detailName string - templateType templateType -} - -var ( - regex = regexp.MustCompile(`(json:".*")`) - - inputStructs = []*generateInfo{ - { - os.Getenv("APIGEN_BASEPATH") + "/internal/gen/controller/api/error.pb.go", - "Error", - os.Getenv("APIGEN_BASEPATH") + "/api/error.go", - "Error", - "api", - "", - "", - "", - templateTypeResource, - }, - { - os.Getenv("APIGEN_BASEPATH") + "/internal/gen/controller/api/error.pb.go", - "ErrorDetails", - os.Getenv("APIGEN_BASEPATH") + "/api/error_details.go", - "ErrorDetails", - "api", - "", - "", - "", - templateTypeResource, - }, - { - os.Getenv("APIGEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host.pb.go", - "Host", - os.Getenv("APIGEN_BASEPATH") + "/api/hosts/host.go", - "Host", - "hosts", - "", - "", - "", - templateTypeResource, - }, - { - os.Getenv("APIGEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_set.pb.go", - "HostSet", - os.Getenv("APIGEN_BASEPATH") + "/api/hosts/host_set.go", - "HostSet", - "hosts", - "", - "", - "", - templateTypeResource, - }, - { - os.Getenv("APIGEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_catalog.pb.go", - "HostCatalog", - os.Getenv("APIGEN_BASEPATH") + "/api/hosts/host_catalog.go", - "HostCatalog", - "hosts", - "", - "", - "", - templateTypeResource, - }, - { - os.Getenv("APIGEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_catalog.pb.go", - "StaticHostCatalogDetails", - os.Getenv("APIGEN_BASEPATH") + "/api/hosts/static_host_catalog.go", - "StaticHostCatalogDetails", - "hosts", - "", - "HostCatalog", - "StaticHostCatalog", - templateTypeDetail, - }, - { - os.Getenv("APIGEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_catalog.pb.go", - "AwsEc2HostCatalogDetails", - os.Getenv("APIGEN_BASEPATH") + "/api/hosts/awsec2_host_catalog.go", - "AwsEc2HostCatalogDetails", - "hosts", - "", - "HostCatalog", - "AwsEc2HostCatalog", - templateTypeDetail, - }, - } -) +var regex = regexp.MustCompile(`(json:".*")`) type visitFn func(node ast.Node) @@ -129,12 +25,7 @@ func (fn visitFn) Visit(node ast.Node) ast.Visitor { return fn } -func main() { - createStructs() - createUtilFuncs() -} - -func createStructs() { +func parsePBs() { for _, inputStruct := range inputStructs { inFile, err := filepath.Abs(inputStruct.inFile) if err != nil { @@ -367,116 +258,3 @@ func createStructs() { inputStruct.structFields = strings.Join(outBuf, "\n") } } - -func createUtilFuncs() { - for _, inputStruct := range inputStructs { - outBuf := new(bytes.Buffer) - switch inputStruct.templateType { - case templateTypeResource: - utilFuncsTemplate.Execute(outBuf, struct { - Timestamp time.Time - Name string - Package string - StructFields string - }{ - Name: inputStruct.outName, - Package: inputStruct.outPkg, - StructFields: inputStruct.structFields, - }) - - case templateTypeDetail: - detailTemplate.Execute(outBuf, struct { - Timestamp time.Time - Package string - StructFields string - ParentName string - DetailName string - }{ - Package: inputStruct.outPkg, - StructFields: inputStruct.structFields, - ParentName: inputStruct.parentName, - DetailName: inputStruct.detailName, - }) - - } - - outFile, err := filepath.Abs(inputStruct.outFile) - if err != nil { - fmt.Printf("error opening file %q: %v\n", inputStruct.outFile, err) - os.Exit(1) - } - if err := ioutil.WriteFile(outFile, outBuf.Bytes(), 0644); err != nil { - fmt.Printf("error writing file %q: %v\n", outFile, err) - os.Exit(1) - } - } -} - -var utilFuncsTemplate = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT. -package {{ .Package }} - -import ( - "encoding/json" - - "github.com/fatih/structs" - "github.com/hashicorp/watchtower/api/internal/strutil" -) - -type {{ .Name }} struct { - {{ .StructFields }} -} - -func (s *{{ .Name }}) SetDefault(key string) { - s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key) -} - -func (s *{{ .Name }}) UnsetDefault(key string) { - s.defaultFields = strutil.StrListDelete(s.defaultFields, key) -} - -func (s {{ .Name }}) MarshalJSON() ([]byte, error) { - m := structs.Map(s) - if m == nil { - m = make(map[string]interface{}) - } - for _, k := range s.defaultFields { - m[k] = nil - } - return json.Marshal(m) -} -`)) - -// TODO: Figure out the right way to write out the specific fields -var detailTemplate = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT. -package {{ .Package }} - -import ( - "fmt" - - "github.com/mitchellh/mapstructure" -) - -type {{ .DetailName }} struct { - *{{ .ParentName }} - {{ .StructFields }} -} - -func (s {{ .ParentName }}) As{{ .DetailName }}() (*{{ .DetailName }}, error) { - out := &{{ .DetailName }}{ - {{ .ParentName }}: &s, - } - decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: out, - TagName: "json", - }) - if err != nil { - return nil, fmt.Errorf("error creating map decoder: %w", err) - } - - if err := decoder.Decode(s.Attributes); err != nil { - return nil, fmt.Errorf("error decoding attributes map: %w", err) - } - - return out, nil -} -`)) diff --git a/api/internal/genapi/templates.go b/api/internal/genapi/templates.go new file mode 100644 index 0000000000..90be782ffe --- /dev/null +++ b/api/internal/genapi/templates.go @@ -0,0 +1,132 @@ +// +build genapi + +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "text/template" + "time" +) + +type templateType int + +const ( + templateTypeResource templateType = iota + templateTypeDetail +) + +func writeTemplates() { + for _, inputStruct := range inputStructs { + outBuf := new(bytes.Buffer) + switch inputStruct.templateType { + case templateTypeResource: + utilFuncsTemplate.Execute(outBuf, struct { + Timestamp time.Time + Name string + Package string + StructFields string + }{ + Name: inputStruct.outName, + Package: inputStruct.outPkg, + StructFields: inputStruct.structFields, + }) + + case templateTypeDetail: + detailTemplate.Execute(outBuf, struct { + Timestamp time.Time + Package string + StructFields string + ParentName string + DetailName string + }{ + Package: inputStruct.outPkg, + StructFields: inputStruct.structFields, + ParentName: inputStruct.parentName, + DetailName: inputStruct.detailName, + }) + + } + + outFile, err := filepath.Abs(inputStruct.outFile) + if err != nil { + fmt.Printf("error opening file %q: %v\n", inputStruct.outFile, err) + os.Exit(1) + } + if err := ioutil.WriteFile(outFile, outBuf.Bytes(), 0644); err != nil { + fmt.Printf("error writing file %q: %v\n", outFile, err) + os.Exit(1) + } + } +} + +var utilFuncsTemplate = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT. +package {{ .Package }} + +import ( + "encoding/json" + + "github.com/fatih/structs" + "github.com/hashicorp/watchtower/api/internal/strutil" +) + +type {{ .Name }} struct { + {{ .StructFields }} +} + +func (s *{{ .Name }}) SetDefault(key string) { + s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key) +} + +func (s *{{ .Name }}) UnsetDefault(key string) { + s.defaultFields = strutil.StrListDelete(s.defaultFields, key) +} + +func (s {{ .Name }}) MarshalJSON() ([]byte, error) { + m := structs.Map(s) + if m == nil { + m = make(map[string]interface{}) + } + for _, k := range s.defaultFields { + m[k] = nil + } + return json.Marshal(m) +} +`)) + +var detailTemplate = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT. +package {{ .Package }} + +import ( + "fmt" + + "github.com/mitchellh/mapstructure" +) + +type {{ .DetailName }} struct { + *{{ .ParentName }} + {{ .StructFields }} +} + +func (s {{ .ParentName }}) As{{ .DetailName }}() (*{{ .DetailName }}, error) { + out := &{{ .DetailName }}{ + {{ .ParentName }}: &s, + } + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Result: out, + TagName: "json", + }) + if err != nil { + return nil, fmt.Errorf("error creating map decoder: %w", err) + } + + if err := decoder.Decode(s.Attributes); err != nil { + return nil, fmt.Errorf("error decoding attributes map: %w", err) + } + + return out, nil +} +`))