Skip to content

Commit

Permalink
m
Browse files Browse the repository at this point in the history
  • Loading branch information
moduli committed May 9, 2024
1 parent f9bc226 commit 694cb47
Show file tree
Hide file tree
Showing 2 changed files with 233 additions and 36 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ test-database-down:
docker stop boundary-sql-tests || true
docker rm -v boundary-sql-tests || true

datasources:
go run scripts/generate_data_sources.go

.PHONY: testacc tools docs test-database-up test-database-down

.PHONY: copywrite
Expand All @@ -89,4 +92,4 @@ fmt:
gofumpt -w $$(find . -name '*.go')

.PHONY: gen
gen: docs copywrite fmt
gen: docs copywrite fmt datasources
264 changes: 229 additions & 35 deletions scripts/generate_data_sources.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,21 @@ import (
// This script is used to generate data sources for this terraform provider
//
// Usage:
// go run ../../scripts/generate_data_source.go
// go run ../../scripts/generate_data_source.go -resource auth-methods
// go run ../../scripts/generate_data_source.go -resource credential-libraries
// go run ../../scripts/generate_data_sources.go
// go run ../../scripts/generate_data_sources.go -resource auth-methods
// go run ../../scripts/generate_data_sources.go -resource credential-libraries
// !!
// will need to update provider.go to include the new data sources
// will need to update docs

// !! add to make-gen-delta

// SCRIPT BEHAVIOR
// 3. Generate an example in the examples doc so that you can make docs with it

var (
// !! do we want to hardcode version here?
boundaryVersion = "v0.16.0"
swaggerFile = fmt.Sprintf("https://raw.githubusercontent.com/hashicorp/boundary/%s/internal/gen/controller.swagger.json", boundaryVersion)
outputDir = "internal/provider"
swaggerFile = "https://raw.githubusercontent.com/hashicorp/boundary/%s/internal/gen/controller.swagger.json"
outputDir = "internal/provider"
)

func main() {
Expand All @@ -51,35 +54,59 @@ func main() {
}
}

// SCRIPT BEHAVIOR
// 3. Generate an example in the examples doc so that you can make docs with it
func getBoundaryVersion() (string, error) {
gomod, err := os.ReadFile("go.mod")
if err != nil {
return "", err
}

// DATA SOURCE OUTPUT
// 1. Do we want plural variants of the provider?
// - singular: you can specify individual parameters to try to find something
// - plural: if you don't provide anything, it will list all
// 2. Figure out how to structure data source
for _, line := range strings.Split(string(gomod), "\n") {
if strings.Contains(line, "github.com/hashicorp/boundary") {
parts := strings.Split(line, " ")
if len(parts) < 2 {
return "", fmt.Errorf("unexpected go.mod format")
}
return parts[1], nil
}
}

func Main() error {
// Optional input "resource" to specify a single resource to generate
// !! Could remove this later
var r string
flag.StringVar(&r, "resource", "", "")
flag.Parse()
return "", fmt.Errorf("boundary version not found in go.mod")
}

// Load swagger file
document, err := loads.JSONSpec(swaggerFile)
func loadSwaggerFile(version string) (*spec.Swagger, error) {
document, err := loads.JSONSpec(fmt.Sprintf(swaggerFile, version))
if err != nil {
return fmt.Errorf("failed to load spec: %w", err)
return nil, fmt.Errorf("failed to load spec: %w", err)
}

swagger := document.Spec()
// ExpandSpec is not careful to merge the attributes so we lose some
// descriptions here
if err = spec.ExpandSpec(swagger, nil); err != nil {
return fmt.Errorf("failed to expand spec")
return nil, fmt.Errorf("failed to expand spec")
}

// Get all resources
return swagger, nil
}

func Main() error {
// Optional flag to specify a single resource to generate
var r string
flag.StringVar(&r, "resource", "", "")
flag.Parse()

version, err := getBoundaryVersion()
if err != nil {
return fmt.Errorf("failed to get boundary version: %w", err)
}
fmt.Printf("Using Boundary version %s...\n", version)

swagger, err := loadSwaggerFile(version)
if err != nil {
return fmt.Errorf("failed to load swagger file: %w", err)
}

// Get all resources if no resource was provided in a flag
var rs []string
if r == "" {
for path := range swagger.Paths.Paths {
Expand All @@ -92,8 +119,9 @@ func Main() error {
}

// Process resources
for _, rr := range rs {
resource, err := NewResourceFromSwagger(swagger, "/v1/", rr)
for _, r := range rs {
fmt.Printf("Processing resource: %s...\n", r)
resource, err := NewResourceFromSwagger(swagger, "/v1/", r)
if err != nil {
return err
}
Expand Down Expand Up @@ -281,6 +309,155 @@ func getSchemaFromProperty(name string, p spec.Schema) (*schema.Schema, error) {
return s, nil
}

func (r *Resource) RenderSingle() (string, error) {
items := &Resource{
schema: r.schema["items"].Elem.(*schema.Resource).Schema,
}
// Find the field that is used to list the items for this resource. This
// is done by finding the parameter that is a also a field in the items list
// Example: For auth methods, you need the scope id
for name, schema := range items.schema {
if _, ok := r.schema[name]; ok {
schema.Optional = true
}
}
if _, ok := items.schema["name"]; ok {
items.schema["name"].Optional = true
}

// Find all input parameters
attrs := map[string]schema.ValueType{}
for name, schema := range items.schema {
fmt.Printf("%s: %t\n", name, schema.Optional)
if schema.Optional {
attrs[name] = schema.Type
}
}
attrs_ := []string{}
for a := range attrs {
attrs_ = append(attrs_, a)
}
sort.Strings(attrs_)

imports := map[string]struct{}{
"context": {},
"net/url": {},
}

// For each input parameter, construct a query parameter for it
var queryParams []string
for _, attr := range attrs_ {
switch ty := attrs[attr]; ty {
case schema.TypeBool:
imports["strconv"] = struct{}{}
queryParams = append(queryParams, fmt.Sprintf(`%s := d.Get(%q).(bool)`, attr, attr))
queryParams = append(queryParams, fmt.Sprintf(`if %s {`, attr))
queryParams = append(queryParams, fmt.Sprintf(`q.Add(%q, strconv.FormatBool(%s))`, attr, attr))
queryParams = append(queryParams, `}`)
case schema.TypeString:
queryParams = append(queryParams, fmt.Sprintf(`q.Add(%q, d.Get(%q).(string))`, attr, attr))
case schema.TypeInt:
imports["strconv"] = struct{}{}
queryParams = append(queryParams, fmt.Sprintf(`q.Add(%q, strconv.Itoa(d.Get(%q).(int)))`, attr, attr))
default:
panic(fmt.Sprintf("unknown type %q for %q", ty, attr))
}
}

imports_ := []string{}
for i := range imports {
imports_ = append(imports_, i)
}
sort.Strings(imports_)

t := template.Must(template.New("datasource_single").Parse(`
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Code generated by scripts/generate_data_sources.go. DO NOT EDIT.
//go:{{.GenerateLine}}
package provider
import (
{{range .Imports}}"{{.}}"
{{end}}
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
var dataSource{{.Name}}Schema = {{.Schema}}
func dataSource{{.Name}}() *schema.Resource{
return &schema.Resource{
Description: "Gets {{.Path}}",
ReadContext: dataSource{{.Name}}Read,
Schema: dataSource{{.Name}}Schema,
}
}
func dataSource{{.Name}}Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*metaData).client
req, err := client.NewRequest(ctx, "GET", "{{.Path}}", nil)
if err != nil {
return diag.FromErr(err)
}
q := url.Values{}
{{.Query}}
req.URL.RawQuery = q.Encode()
resp, err := client.Do(req)
if err != nil {
diag.FromErr(err)
}
apiError, err := resp.Decode(nil)
if err != nil {
return diag.FromErr(err)
}
if apiError != nil {
return apiErr(apiError)
}
err = set(dataSource{{.Name}}Schema, d, resp.Map)
if err != nil {
return diag.FromErr(err)
}
d.SetId("asdfasdf")
return nil
}
`))

// !! q.Add("filter", fmt.Sprintf("\"/item/name\" == \"%s\"", d.Get("name").(string)))
// !! set id to id of item
// !! map other attributes correctly

w := bytes.NewBuffer(nil)
data := map[string]interface{}{
"GenerateLine": fmt.Sprintf("generate go run ../../scripts/generate_data_sources.go %s", shellquote.Join("-name", r.name, "-path", r.path)),
"Imports": imports_,
"Name": strings.TrimSuffix(r.name, "s"), // Example:
"Package": strings.ToLower(r.name),
"Path": strings.TrimSuffix(r.path, "s"),
"Schema": template.HTML(items.schema.String()),
"Description": template.HTML(fmt.Sprintf("%q", r.description)),
"Query": template.HTML(strings.Join(queryParams, "\n")),
}
if err := t.Execute(w, data); err != nil {
return "", err
}

source, err := format.Source(w.Bytes())
if err != nil {
log.Fatalf("the generated go code is incorrect: %s", err)
}

return string(source), nil
}

func (r *Resource) Render() (string, error) {
attrs := map[string]schema.ValueType{}
for name, schema := range r.schema {
Expand Down Expand Up @@ -316,7 +493,6 @@ func (r *Resource) Render() (string, error) {
default:
panic(fmt.Sprintf("unknown type %q for %q", ty, attr))
}

}

imports_ := []string{}
Expand All @@ -331,8 +507,6 @@ func (r *Resource) Render() (string, error) {
// Code generated by scripts/generate_data_sources.go. DO NOT EDIT.
//go:{{.GenerateLine}}
// This file was generated based on Boundary {{.Version}}
package provider
import (
Expand All @@ -356,7 +530,7 @@ func dataSource{{.Name}}() *schema.Resource{
func dataSource{{.Name}}Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*metaData).client
req, err := client.NewRequest(ctx, "GET", "{{.Path}}", nil)
req, err := client.NewRequest(ctx, "GET", "{{.Path}}s", nil)
if err != nil {
return diag.FromErr(err)
}
Expand Down Expand Up @@ -390,7 +564,6 @@ func dataSource{{.Name}}Read(ctx context.Context, d *schema.ResourceData, meta i
w := bytes.NewBuffer(nil)
data := map[string]interface{}{
"GenerateLine": fmt.Sprintf("generate go run ../../scripts/generate_data_sources.go %s", shellquote.Join("-name", r.name, "-path", r.path)),
"Version": boundaryVersion,
"Imports": imports_,
"Name": r.name,
"Schema": template.HTML(r.schema.String()),
Expand All @@ -413,23 +586,44 @@ func dataSource{{.Name}}Read(ctx context.Context, d *schema.ResourceData, meta i
func write(resource *Resource) error {
_, b, _, _ := runtime.Caller(0)
basepath := filepath.Dir(b)

// Create "multiple" variant
filename := filepath.Join(
basepath,
"..",
fmt.Sprintf("%s/data_source_%s.go", outputDir, strings.Replace(resource.path, "-", "_", -1)),
)

f, err := os.Create(filename)
fm, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
defer fm.Close()

source, err := resource.Render()
if err != nil {
return err
}
f.Write([]byte(source))
fm.Write([]byte(source))

// Create "singular" variant
filename = filepath.Join(
basepath,
"..",
fmt.Sprintf("%s/data_source_%s.go", outputDir, strings.TrimSuffix(strings.Replace(resource.path, "-", "_", -1), "s")),
)

fs, err := os.Create(filename)
if err != nil {
return err
}
defer fs.Close()

source, err = resource.RenderSingle()
if err != nil {
return err
}
fs.Write([]byte(source))

return nil
}

0 comments on commit 694cb47

Please sign in to comment.