Skip to content

Commit

Permalink
update terragen to use per-object directory in code generation
Browse files Browse the repository at this point in the history
  • Loading branch information
jlarfors committed Apr 11, 2024
1 parent d6e1fef commit 302599c
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 73 deletions.
9 changes: 8 additions & 1 deletion cmd/terragen/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Usage:
The flags are:
-force
override any existing generated Go files (required)
override any existing generated Go files
-out string
directory to generate Go files in (required)
-pkg string
Expand Down Expand Up @@ -49,6 +49,7 @@ func main() {
pkgPath string
providerStr string
force bool
clean bool
v bool
)

Expand All @@ -72,6 +73,12 @@ func main() {
false,
"override any existing generated Go files",
)
flag.BoolVar(
&clean,
"clean",
false,
"clean the out directory before generating Go files",
)
flag.BoolVar(&v, "v", false, "show version")

flag.Parse()
Expand Down
9 changes: 7 additions & 2 deletions pkg/internal/terrajen/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func funcAttributes(s *Schema) *jen.Statement {
}

func funcResourceImportState(s *Schema) *jen.Statement {
attributesArgs := jen.Id("av").Clone
attributesArgs := jen.Id("state").Clone
return jen.Comment(
fmt.Sprintf(
"%s imports the given attribute values into [%s]'s state.",
Expand All @@ -207,7 +207,12 @@ func funcResourceImportState(s *Schema) *jen.Statement {
// Body
Block(
// Initialise the state
jen.Id(s.Receiver).Dot(idFieldState).Op("=").Op("&").Id(s.StateStructName).Block(),
jen.Id(s.Receiver).
Dot(idFieldState).
Op("=").
Op("&").
Id(s.StateStructName).
Block(),
jen.If(
jen.Id("err").Op(":=").Qual(
"encoding/json",
Expand Down
79 changes: 53 additions & 26 deletions pkg/internal/terrajen/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ func (a *ProviderGenerator) SchemaProvider(sb *tfjson.SchemaBlock) *Schema {
ShortName: "provider",
Type: "provider",
StructName: "Provider",
ArgumentStructName: "ProviderArgs",
ArgumentStructName: "Provider", // Args struct *is* the provider struct
Receiver: structReceiverFromName("provider"),

NewFuncName: "NewProvider",
SubPackageName: "provider",
SubPackageName: a.ProviderName,
FilePath: filepath.Join(
a.GeneratedPackageLocation,
"provider"+fileExtension,
Expand All @@ -87,9 +87,26 @@ func (a *ProviderGenerator) SchemaResource(
name string,
sb *tfjson.SchemaBlock,
) *Schema {
shortName := providerShortName(name)
spn := strings.ReplaceAll(shortName, "_", "")
fp := filepath.Join(a.GeneratedPackageLocation, shortName+fileExtension)
shortName := name
// shortName := providerShortName(name)
// split := strings.SplitN(shortName, "_", 2)
pkgName := shortName
subPkgName := shortName
fileName := shortName
fp := filepath.Join(
a.GeneratedPackageLocation,
pkgName,
fileName+fileExtension,
)
// If the pkgName is a keyword prefix it with the provider name.
// E.g. aws_default_vpc => awsdefault
// if token.IsKeyword(pkgName) {
// pkgName = a.ProviderName + pkgName
// }
// if token.IsKeyword(subPkgName) {
// subPkgName = a.ProviderName + subPkgName
// }

rs := &Schema{
SchemaType: SchemaTypeResource,
GoProviderPkgPath: a.GoProviderPkgPath, // github.com/golingon/lingon/gen/aws
Expand All @@ -98,15 +115,13 @@ func (a *ProviderGenerator) SchemaResource(
ProviderSource: a.ProviderSource, // hashicorp/aws
ProviderVersion: a.ProviderVersion, // 4.49.0
ShortName: shortName, // aws_iam_role => iam_role
PackageName: a.ProviderName, // aws
PackageName: pkgName, // aws
Type: name, // aws

StructName: strcase.Pascal(
shortName,
), // iam_role => IamRole
ArgumentStructName: strcase.Pascal(
shortName,
) + suffixArgs, // iam_role => IamRoleArgs
ArgumentStructName: suffixArgs, // iam_role => IamRoleArgs
AttributesStructName: strcase.Camel(
shortName,
) + suffixAttributes, // iam_role => iamRoleAttributes
Expand All @@ -117,8 +132,8 @@ func (a *ProviderGenerator) SchemaResource(
shortName,
), // iam_role => ir

NewFuncName: "New" + strcase.Pascal(shortName),
SubPackageName: spn, // iam_role => iamrole
NewFuncName: "New",
SubPackageName: subPkgName, // iam_role => iam
FilePath: fp,
graph: newGraph(sb),
}
Expand All @@ -131,12 +146,18 @@ func (a *ProviderGenerator) SchemaData(
name string,
sb *tfjson.SchemaBlock,
) *Schema {
shortName := providerShortName(name)
spn := strings.ReplaceAll(shortName, "_", "")
dataName := "data_" + shortName
fp := filepath.Join(a.GeneratedPackageLocation, dataName+fileExtension)
pn := strcase.Pascal(shortName)

shortName := name
// shortName := providerShortName(name)
pkgName := "data_" + shortName
subPkgName := "data_" + shortName
// spn := strings.ReplaceAll(shortName, "_", "")
dataName := shortName
// dataName := "data_" + shortName
fp := filepath.Join(
a.GeneratedPackageLocation,
pkgName,
dataName+fileExtension,
)
ds := &Schema{
SchemaType: SchemaTypeData,
GoProviderPkgPath: a.GoProviderPkgPath, // github.com/golingon/lingon/gen/aws
Expand All @@ -145,18 +166,25 @@ func (a *ProviderGenerator) SchemaData(
ProviderSource: a.ProviderSource, // hashicorp/aws
ProviderVersion: a.ProviderVersion, // 4.49.0
ShortName: shortName, // aws_iam_role => iam_role
PackageName: a.ProviderName, // aws
PackageName: pkgName, // aws
Type: name, // aws_iam_role

StructName: "Data" + pn, // iam_role => DataIamRole
ArgumentStructName: "Data" + pn + suffixArgs, // iam_role => DataIamRoleArgs
AttributesStructName: "data" + pn + suffixAttributes, // iam_role => dataIamRoleAttributes
StructName: strcase.Pascal(
shortName,
), // iam_role => IamRole
ArgumentStructName: suffixArgs, // iam_role => Args
AttributesStructName: strcase.Camel(
shortName,
) + suffixAttributes, // iam_role => iamRoleAttributes
StateStructName: strcase.Camel(
shortName,
) + suffixState, // iam_role => IamRoleOut
Receiver: structReceiverFromName(
shortName,
), // iam_role => ir

NewFuncName: "NewData" + pn, // iam_role => NewDataIamRole
SubPackageName: "data" + spn, // iam_role => dataiamrole
NewFuncName: "New", // iam_role => New
SubPackageName: subPkgName, // iam_role => iamrole
FilePath: fp,
graph: newGraph(sb),
}
Expand All @@ -165,8 +193,7 @@ func (a *ProviderGenerator) SchemaData(
}

// providerShortName takes a name like "aws_iam_role" and returns the name
// without
// the leading provider prefix, i.e. it returns "iam_role"
// without the leading provider prefix, i.e. it returns "iam_role"
func providerShortName(name string) string {
underscoreIndex := strings.Index(name, "_")
if underscoreIndex == -1 {
Expand Down Expand Up @@ -229,6 +256,6 @@ func (s *Schema) SubPkgPath() string {
return filepath.Join(
s.GeneratedPackageLocation,
s.SubPackageName,
s.ShortName+fileExtension,
"sub_"+s.ShortName+fileExtension,
)
}
9 changes: 6 additions & 3 deletions pkg/internal/terrajen/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ func newGraph(schema *tfjson.SchemaBlock) *graph {
return &g
}

// graph is used to decouple the tfjson.SchemaBlock type from the code generator.
// graph is used to decouple the tfjson.SchemaBlock type from the code
// generator.
// A graph is created by supplying a tfjson.SchemaBlock.
type graph struct {
// attributes are the top-level attributes for a terraform configuration
Expand Down Expand Up @@ -255,7 +256,8 @@ func (g *graph) traverseCtyType(
obj, _ := ctyTypeElementObject(ct)
for _, atName := range sortMapKeys(obj.AttributeTypes()) {
at := obj.AttributeType(atName)
// If there are objects within the attributes of this object, traverse those objects
// If there are objects within the attributes of this object, traverse
// those objects
// and make them children of this object
if _, ok := ctyTypeElementObject(at); ok {
n.children = append(
Expand Down Expand Up @@ -326,7 +328,8 @@ func blockNodeNestingMode(block *tfjson.SchemaBlockType) []nodeNestingMode {
case tfjson.SchemaNestingModeSingle, tfjson.SchemaNestingModeGroup:
return nil
case tfjson.SchemaNestingModeList, tfjson.SchemaNestingModeMap:
// Unintuitively, tfjson.SchemaNestingModeMap is not actually a map, just a list,
// Unintuitively, tfjson.SchemaNestingModeMap is not actually a map,
// just a list,
// but they get keyed by the block labels into a Map.
// For our use case, we therefore treat it like a list.
return []nodeNestingMode{nodeNestingModeList}
Expand Down
65 changes: 39 additions & 26 deletions pkg/internal/terrajen/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,23 @@
package terrajen

import (
"fmt"

"github.com/dave/jennifer/jen"
)

// ProviderFile generates a Go file for a Terraform provider configuration based on the given Schema
// ProviderFile generates a Go file for a Terraform provider configuration based
// on the given Schema
func ProviderFile(s *Schema) *jen.File {
f := jen.NewFile(s.ProviderName)
f.ImportName(pkgTerra, pkgTerraAlias)
f.HeaderComment(HeaderComment)
f.Add(providerNewFunc(s))
f.Add(providerStructCompileCheck(s))
f.Add(providerStruct(s))
f.Add(argsStruct(s))

return f
}

func providerNewFunc(s *Schema) *jen.Statement {
return jen.Func().Id(s.NewFuncName).Params(
jen.Id("args").Id(s.ArgumentStructName),
).
// Return
Op("*").Id(s.StructName).
// Block
Block(
jen.Return(
jen.Op("&").Id(s.StructName).Values(
jen.Dict{
jen.Id(idFieldArgs): jen.Id("args"),
},
),
),
)
}

func providerStructCompileCheck(s *Schema) *jen.Statement {
return jen.Var().Op("_").Qual(pkgTerra, "Provider").Op("=").
Params(
Expand All @@ -48,10 +31,16 @@ func providerStructCompileCheck(s *Schema) *jen.Statement {
}

func providerStruct(s *Schema) *jen.Statement {
stmt := jen.Type().Id(s.StructName).Struct(
jen.Id(idFieldArgs).Id(s.ArgumentStructName),
jen.Line(),
)
// stmt := jen.Type().Id(s.StructName).Struct(
// jen.Id(idFieldArgs).Id(s.ArgumentStructName),
// jen.Line(),
// )
// stmt.Line()
// stmt.Line()

// Use the args struct as the main struct, because there is nothing else to
// go in the provider.
stmt := argsStruct(s)
stmt.Line()
stmt.Line()

Expand All @@ -68,9 +57,33 @@ func providerStruct(s *Schema) *jen.Statement {
stmt.Line()
stmt.Line()
// Configuration
stmt.Add(funcConfiguration(s))
stmt.Add(funcProviderConfiguration(s))
stmt.Line()
stmt.Line()

return stmt
}

func funcProviderConfiguration(s *Schema) *jen.Statement {
return jen.Comment(
fmt.Sprintf(
"%s returns the provider configuration for [%s].",
idFuncConfiguration,
s.StructName,
),
).
Line().
Func().
// Receiver
Params(jen.Id(s.Receiver).Op("*").Id(s.StructName)).
// Name
Id(idFuncConfiguration).Call().
// Return type
Interface().
// Body
Block(
jen.Return(
jen.Id(s.Receiver),
),
)
}
6 changes: 4 additions & 2 deletions pkg/internal/terrajen/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import (
"github.com/veggiemonk/strcase"
)

// ResourceFile generates a Go file for a Terraform resource configuration based on the given Schema
// ResourceFile generates a Go file for a Terraform resource configuration based
// on the given Schema
func ResourceFile(s *Schema) *jen.File {
f := jen.NewFile(s.PackageName)
f.ImportAlias(pkgHCL, pkgHCLAlias)
Expand Down Expand Up @@ -152,7 +153,8 @@ func resourceStateStruct(s *Schema) *jen.Statement {
stmt.Index()
}

stmt.Qual(s.SubPkgQualPath(), strcase.Pascal(child.name)+suffixState)
stmt.Id(strcase.Pascal(child.uniqueName) + suffixState)
// stmt.Qual(s.SubPkgQualPath(), strcase.Pascal(child.name)+suffixState)
stmt.Tag(
map[string]string{
tagJSON: child.name,
Expand Down
16 changes: 10 additions & 6 deletions pkg/internal/terrajen/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import (
"github.com/veggiemonk/strcase"
)

// argsStruct takes a schema and generates the Args struct that is used by the user to specify the arguments
// for the object that the schema represents (e.g. provider, resource, data resource)
// argsStruct takes a schema and generates the Args struct that is used by the
// user to specify the arguments for the object that the schema represents (e.g.
// provider, resource, data resource)
func argsStruct(s *Schema) *jen.Statement {
fields := make([]jen.Code, 0)
for _, attr := range s.graph.attributes {
Expand Down Expand Up @@ -50,7 +51,8 @@ func argsStruct(s *Schema) *jen.Statement {
}
tags[tagValidate] = nodeBlockListValidateTags(child)
}
stmt.Qual(s.SubPkgQualPath(), strcase.Pascal(child.uniqueName))
stmt.Id(strcase.Pascal(child.uniqueName))
// stmt.Qual(s.SubPkgQualPath(), strcase.Pascal(child.uniqueName))
stmt.Tag(tags)
fields = append(fields, stmt)
}
Expand All @@ -68,8 +70,9 @@ func argsStruct(s *Schema) *jen.Statement {
Struct(fields...)
}

// attributesStruct takes a schema and generates the Attributes struct that is used by the user to creates references to
// attributes for the object that the schema represents (e.g. provider, resource, data resource)
// attributesStruct takes a schema and generates the Attributes struct that is
// used by the user to creates references to attributes for the object that the
// schema represents (e.g. provider, resource, data resource)
func attributesStruct(s *Schema) *jen.Statement {
var stmt jen.Statement

Expand Down Expand Up @@ -125,7 +128,8 @@ func attributesStruct(s *Schema) *jen.Statement {

for _, child := range s.graph.children {
structName := strcase.Pascal(child.uniqueName) + suffixAttributes
qualStruct := jen.Qual(s.SubPkgQualPath(), structName).Clone
qualStruct := jen.Id(structName).Clone
// qualStruct := jen.Qual(s.SubPkgQualPath(), structName).Clone
stmt.Add(
jen.Func().
// Receiver
Expand Down
Loading

0 comments on commit 302599c

Please sign in to comment.