From c1c6effc15cf5597c72a25c0eae93928a5b9f569 Mon Sep 17 00:00:00 2001 From: Erik Kristensen Date: Sun, 14 Apr 2024 16:15:04 -0600 Subject: [PATCH] feat: generate property documentation for resources --- pkg/docs/generate.go | 75 +++++++++++++++++++++++++++++++++++++++ pkg/docs/generate_test.go | 57 +++++++++++++++++++++++++++++ pkg/registry/registry.go | 6 ++++ 3 files changed, 138 insertions(+) create mode 100644 pkg/docs/generate.go create mode 100644 pkg/docs/generate_test.go diff --git a/pkg/docs/generate.go b/pkg/docs/generate.go new file mode 100644 index 0000000..ef5ac57 --- /dev/null +++ b/pkg/docs/generate.go @@ -0,0 +1,75 @@ +package docs + +import ( + "fmt" + "reflect" + "strings" +) + +func GeneratePropertiesMap(data interface{}) map[string]string { + properties := map[string]string{} + + v := reflect.ValueOf(data) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + t := v.Type() + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + + if !field.IsExported() { + continue + } + + propertyTag := field.Tag.Get("property") + options := strings.Split(propertyTag, ",") + name := field.Name + prefix := "" + + if options[0] == "-" { + continue + } + + for _, option := range options { + parts := strings.Split(option, "=") + if len(parts) != 2 { + continue + } + switch parts[0] { + case "name": + name = parts[1] + case "prefix": + prefix = parts[1] + } + } + + if prefix != "" && name != "Tags" { + name = fmt.Sprintf("%s:%s", prefix, name) + } + + descriptionTag := field.Tag.Get("description") + + if name == "Tags" { + originalName := name + name = "tag::" + tagPrefix := "tag:" + if prefix != "" { + tagPrefix = fmt.Sprintf("tag:%s:", prefix) + } + + descriptionTag = fmt.Sprintf( + "This resource has tags with property `%s`. These are key/value pairs that are\n\t"+ + "added as their own property with the prefix of `%s` (e.g. [%sexample: \"value\"]) ", + originalName, tagPrefix, tagPrefix) + + if prefix != "" { + name = fmt.Sprintf("tag:%s::", prefix) + } + } + + properties[name] = fmt.Sprintf("%s", descriptionTag) + } + + return properties +} diff --git a/pkg/docs/generate_test.go b/pkg/docs/generate_test.go new file mode 100644 index 0000000..e58c0f7 --- /dev/null +++ b/pkg/docs/generate_test.go @@ -0,0 +1,57 @@ +package docs + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGenerateProperties(t *testing.T) { + type TestResource1 struct { + Name string `description:"The name of the resource"` + Region *string `description:"The region in which the resource resides"` + VpcID string `description:"The VPC ID of the resource" property:"prefix=vpc"` + Tags map[string]string `description:"The tags associated with the resource"` + } + + type TestResource2 struct { + Name string `description:"The name of the resource"` + Region *string `description:"The region in which the resource resides"` + Tags map[string]string `description:"The tags associated with the resource" property:"prefix=ee"` + } + + cases := []struct { + name string + in interface{} + want map[string]string + }{ + { + name: "TestResource1", + in: TestResource1{}, + want: map[string]string{ + "Name": "The name of the resource", + "Region": "The region in which the resource resides", + "vpc:VpcID": "The VPC ID of the resource", + "tag::": "This resource has tags with property `Tags`. These are key/value pairs that are\n\t" + + "added as their own property with the prefix of `tag:` (e.g. [tag:example: \"value\"]) ", + }, + }, + { + name: "TestResource2", + in: TestResource2{}, + want: map[string]string{ + "Name": "The name of the resource", + "Region": "The region in which the resource resides", + "tag:ee::": "This resource has tags with property `Tags`. These are key/value pairs that are\n\t" + + "added as their own property with the prefix of `tag:ee:" + + "` (e.g. [tag:ee:example: \"value\"]) ", + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + have := GeneratePropertiesMap(c.in) + assert.Equal(t, c.want, have) + }) + } +} diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 3f9159f..0236812 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -48,6 +48,12 @@ type Registration struct { // different levels, whereas AWS has simply Account level. Scope Scope + // Resource is the resource type that the lister is going to list. This is a struct that implements the Resource + // interface. This is primarily used to generate documentation by parsing the structs properties and generating + // markdown documentation. + // Note: it is a interface{} because we are going to inspect it, we do not need to actually call any methods on it. + Resource interface{} + // Lister is the lister for the resource type, it is a struct with a method called List that returns a slice // of resources. The lister is responsible for filtering out any resources that should not be deleted because they // are ineligible for deletion. For example, built in resources that cannot be deleted.