Skip to content

Commit

Permalink
feat: Add Terraform generator (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
yufeiminds committed Oct 16, 2023
1 parent 2067f5d commit 62ca979
Show file tree
Hide file tree
Showing 23 changed files with 1,134 additions and 25 deletions.
6 changes: 0 additions & 6 deletions examples/petstore/manifest.cue
Original file line number Diff line number Diff line change
@@ -1,8 +1,2 @@
// Package petstore is a sample package for testing
package petstore

options: templates: {
terraform: {
outdir: ".build/terraform"
}
}
6 changes: 1 addition & 5 deletions examples/simple/manifest.cue
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,9 @@ resources: "Pet": models: "Pet": {
}

// Template definition
templates: "foo": {
templates: "foo": template.#Manifest & {
name: "basic"

inputs: template.#Inputs

diagnostics: [...template.#Diagnostic]

outputs: files: "README.md": {
content: "Hello, World!"
}
Expand Down
20 changes: 8 additions & 12 deletions internal/cue/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,20 +61,16 @@ func GeneratePlan(p *Package, options ...GenerateOption) (*Plan, error) {

color.Magenta("Generate template %q to folder %q", templateName, templateOptions.Outdir)

// Get the template
templateValue := p.value.LookupPath(cue.MakePath(cue.Str("templates"), cue.Str(templateName)))
if err := templateValue.Err(); err != nil {
return nil, fmt.Errorf("lookup path failed: %w", err)
}

// Fill the template with the input RMS data
concretTemplateValue := templateValue.FillPath(cue.MakePath(cue.Str("inputs")), &templateV1.Inputs{
Resources: rms.Resources,
Errors: rms.Errors,
Vars: templateOptions.Vars,
})
concretTemplateValue := p.value.
LookupPath(cue.MakePath(cue.Str("templates"), cue.Str(templateName))).
FillPath(cue.MakePath(cue.Str("inputs")), &templateV1.Inputs{
Resources: rms.Resources,
Errors: rms.Errors,
Vars: templateOptions.Vars,
})
if err := concretTemplateValue.Err(); err != nil {
return nil, fmt.Errorf("fill path failed: %w", err)
return nil, fmt.Errorf("fill inputs failed: %w", err)
}

// Check the template diagnostics
Expand Down
21 changes: 21 additions & 0 deletions pkg/helpers/naming/naming.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package naming

import (
"strings"
)

#Snake: {
name: string
snake: name
uppercamel: strings.Join([ for _, token in strings.Split(snake, "_") {strings.ToTitle(strings.ToLower(token))}], "")
lowercamel: strings.ToCamel(uppercamel)
lower: strings.ToLower(lowercamel)
}

#UpperCamel: {
snake: "unimplemented"
name: string
uppercamel: name
lowercamel: strings.ToCamel(uppercamel)
lower: strings.ToLower(lowercamel)
}
4 changes: 2 additions & 2 deletions pkg/rms/v1/rms.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ syntax = "proto3";
package pkg.rms.v1;

import "pkg/resource/v1/resource.proto";
// import "pkg/template/v1/template.proto";
import "pkg/template/v1/template.proto";

option go_package = "github.com/GuanceCloud/iacker/pkg/rms/v1;v1";

message Manifest {
optional Options options = 1;
map<string, pkg.resource.v1.Resource> resources = 2;
map<string, pkg.resource.v1.Error> errors = 3;
// map<string, pkg.template.v1.Manifest> templates = 4;
map<string, pkg.template.v1.Manifest> templates = 4;
}

message Options {
Expand Down
Empty file removed templates/terraform/.gitkeep
Empty file.
175 changes: 175 additions & 0 deletions templates/terraform/builder/model.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package builder

import (
"strings"
gotemplate "text/template"

resource "github.com/GuanceCloud/iacker/pkg/resource/v1"
naming "github.com/GuanceCloud/iacker/pkg/helpers/naming"
)

#StructBuilder: {
name: string

pkg: string

rs: resource.#Resource

isds: *false | bool

_resource_template: """
// Code generated by Guance Cloud Code Generation Pipeline. DO NOT EDIT.
package {{ .pkg }}
import (
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/GuanceCloud/terraform-provider-guance/internal/consts"
)
// {{ .name.lowercamel }}ResourceModel maps the resource schema data.
type {{ .name.lowercamel }}ResourceModel struct {
ID types.String `tfsdk:"id"`
CreatedAt types.String `tfsdk:"created_at"`
{{- range $v := .properties }}
{{ $v }}
{{- end }}
}
// GetId returns the ID of the resource.
func (m *{{ .name.lowercamel }}ResourceModel) GetId() string {
return m.ID.ValueString()
}
// SetId sets the ID of the resource.
func (m *{{ .name.lowercamel }}ResourceModel) SetId(id string) {
m.ID = types.StringValue(id)
}
// GetResourceType returns the type of the resource.
func (m *{{ .name.lowercamel }}ResourceModel) GetResourceType() string {
return consts.TypeName{{ .name.uppercamel }}
}
// SetCreatedAt sets the creation time of the resource.
func (m *{{ .name.lowercamel }}ResourceModel) SetCreatedAt(t string) {
m.CreatedAt = types.StringValue(t)
}
"""

_data_source_template: _resource_template + """
// {{ .name.lowercamel }}DataSourceModel maps the resource schema data.
type {{ .name.lowercamel }}DataSourceModel struct {
Items []*{{ .name.lowercamel }}ResourceModel `tfsdk:"items"`
Filters []*sdk.Filter `tfsdk:"filters"`
MaxResults types.Int64 `tfsdk:"max_results"`
ID types.String `tfsdk:"id"`
}
"""

_model_template: """
// {{ .name.uppercamel }} maps the resource schema data.
type {{ .name.uppercamel }} struct {
{{- range $v := .properties }}
{{ $v }}
{{- end }}
}
"""

_prop_template: """
{{ .name.uppercamel }} {{ template "type" .v.schema }} `tfsdk:"{{ .name.snake }}"`
{{- define "type" }}
{{- if eq .type "array" -}}
[]{{ template "elem" .elem }}
{{- else if eq .type "object" -}}
*{{ .model }}
{{- else if eq .type "ref" -}}
types.String
{{- else -}}
{{ template "primitive" . }}
{{- end -}}
{{- end }}
{{- define "elem" }}
{{- if eq .type "object" -}}
*{{ .model }}
{{- else if eq .type "ref" -}}
types.String
{{- else -}}
{{ template "primitive" . }}
{{- end -}}
{{- end }}
{{- define "primitive" }}
{{- if eq .type "integer" -}}
types.Int64
{{- else if eq .type "boolean" -}}
types.Bool
{{- else if eq .type "float" -}}
types.Float64
{{- else if eq .type "string" -}}
types.String
{{- else -}}
unsupported type: {{ .type }}
{{- end -}}
{{- end }}
"""

_resource: gotemplate.Execute(_resource_template, {
"name": naming.#UpperCamel & {"name": name}
"pkg": pkg
v: rs
properties: [
for i, pinfo in rs.models[name].properties {
gotemplate.Execute(_prop_template, {
name: naming.#Snake & {"name": pinfo.name}
v: pinfo
index: i + 1
})
},
]
})

_data_source: gotemplate.Execute(_data_source_template, {
"name": naming.#UpperCamel & {"name": name}
"pkg": pkg
v: rs
properties: [
for i, pinfo in rs.models[name].properties {
gotemplate.Execute(_prop_template, {
name: naming.#Snake & {"name": pinfo.name}
v: pinfo
index: i + 1
})
},
]
})

_models: [
for mname, minfo in rs.models if mname != name {
gotemplate.Execute(_model_template, {
name: naming.#UpperCamel & {"name": mname}
v: minfo
properties: [
for i, pinfo in minfo.properties {
gotemplate.Execute(_prop_template, {
name: naming.#Snake & {"name": pinfo.name}
v: pinfo
index: i + 1
})
},
]
})
},
]

_block_sep: "\n" * 2
if !isds {
output: _resource + _block_sep + strings.Join(_models, _block_sep)
}
if isds {
output: _data_source + _block_sep + strings.Join(_models, _block_sep)
}
}
Loading

0 comments on commit 62ca979

Please sign in to comment.