Skip to content

Commit

Permalink
Merge pull request #163 from maksym-nazarenko/codegen-mikrotik-resource
Browse files Browse the repository at this point in the history
Codegen mikrotik resource
  • Loading branch information
ddelnano authored Jun 10, 2023
2 parents de902dc + 1e74a06 commit a5426b1
Show file tree
Hide file tree
Showing 8 changed files with 285 additions and 86 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ $ export TF_CLI_CONFIG_FILE=path/to/custom.tfrc
- RouterOS. See which versions are supported by what is tested in [CI](.github/workflows/continuous-integration.yml)
- Terraform 0.12+


For code generation of boilerplate code, see [codegen Readme](./cmd/mikrotik-codegen/internal/codegen/README.md)

### Testing

The provider is tested with Terraform's acceptance testing framework. As long as you have a RouterOS device you should be able to run them. Please be aware it will create resources on your device! Code that is accepted by the project will not be destructive for anything existing on your router but be careful when changing test code!
Expand Down Expand Up @@ -113,5 +116,5 @@ $ make routeros ROUTEROS_VERSION=latest

To cleanup everything, just run:
```sh
$ make routeros clean
$ make routeros-clean
```
17 changes: 14 additions & 3 deletions cmd/mikrotik-codegen/internal/codegen/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
MikroTik code generation
========================

This tool allows generating MikroTik resources based on client's struct definition.
This tool allows generating MikroTik resources for API client and Terraform resources based on Mikrotik struct definition.

## Quickstart
## MikroTik client resource
To generate new MikroTik resource definition, simply run
```sh
$ go run ./cmd/mikrotik-codegen mikrotik -name BridgeVlan -commandBase "/interface/bridge/vlan"
```
where

`name` - a name of MikroTik resource to generate.

`commandBase` - base path to craft commands for CRUD operations.

## Terraform resource
Just add a `codegen` tag key to struct fields:
```go
type MikrotikResource struct{
Expand All @@ -20,7 +31,7 @@ type MikrotikResource struct{

and run:
```sh
$ go run ./cmd/mikrotik-codegen -src client/resource.go -struct MikrotikResource > mikrotik/resource_new.go
$ go run ./cmd/mikrotik-codegen terraform -src client/resource.go -struct MikrotikResource > mikrotik/resource_new.go
```


Expand Down
26 changes: 26 additions & 0 deletions cmd/mikrotik-codegen/internal/codegen/generator_mikrotik.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package codegen

import (
"io"
"text/template"
)

func GenerateMikrotikResource(resourceName, commandBasePath string, w io.Writer) error {
if err := writeWrapper(w, []byte(generatedNotice)); err != nil {
return err
}
t := template.New("resource")
if _, err := t.Parse(mikrotikResourceDefinitionTemplate); err != nil {
return err
}

data := struct {
CommandBasePath string
ResourceName string
}{
CommandBasePath: commandBasePath,
ResourceName: resourceName,
}

return t.Execute(w, data)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package codegen

import (
"bytes"
"errors"
"io"
"strings"
Expand Down Expand Up @@ -55,24 +54,8 @@ type (
)

// GenerateResource generates Terraform resource and writes it to specified output
func GenerateResource(s *Struct, w io.Writer, beforeWriteHooks ...SourceWriteHookFunc) error {
var result []byte
var buf bytes.Buffer
var err error

if err := generateResource(&buf, *s); err != nil {
return err
}
result = buf.Bytes()
for _, h := range beforeWriteHooks {
result, err = h(result)
if err != nil {
return err
}
}

_, err = w.Write(result)
if err != nil {
func GenerateResource(s *Struct, w io.Writer) error {
if err := generateResource(w, *s); err != nil {
return err
}

Expand All @@ -90,18 +73,9 @@ func generateResource(w sourceWriter, s Struct) error {

t := template.New("resource")
t.Funcs(template.FuncMap{
"lowercase": strings.ToLower,
"snakeCase": utils.ToSnakeCase,
"firstLower": func(s string) string {
if len(s) < 1 {
return s
}
if len(s) == 1 {
return strings.ToLower(s)
}

return strings.ToLower(s[:1]) + s[1:]
},
"lowercase": strings.ToLower,
"snakeCase": utils.ToSnakeCase,
"firstLower": utils.FirstLower,
})
if _, err := t.Parse(resourceDefinitionTemplate); err != nil {
return err
Expand Down
96 changes: 96 additions & 0 deletions cmd/mikrotik-codegen/internal/codegen/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,5 +185,101 @@ func modelTo{{.ResourceName}}(m *{{$resourceStructName}}Model) *client.{{.Resour
{{end}}
}
}
`

mikrotikResourceDefinitionTemplate = `
package client
import (
"github.com/ddelnano/terraform-provider-mikrotik/client/internal/types"
"github.com/go-routeros/routeros"
)
// {{.ResourceName}} defines resource
type {{.ResourceName}} struct {
Id string ` + "`" + `mikrotik:".id"` + "`" + `
}
var _ Resource = (*{{.ResourceName}})(nil)
func (b *{{.ResourceName}}) ActionToCommand(a Action) string {
return map[Action]string{
Add: "{{.CommandBasePath}}/add",
Find: "{{.CommandBasePath}}/print",
Update: "{{.CommandBasePath}}/set",
Delete: "{{.CommandBasePath}}/remove",
}[a]
}
func (b *{{.ResourceName}}) IDField() string {
return ".id"
}
func (b *{{.ResourceName}}) ID() string {
return b.Id
}
func (b *{{.ResourceName}}) SetID(id string) {
b.Id = id
}
// Uncomment extra methods to satisfy more interfaces
// Adder
// func (b *{{.ResourceName}}) AfterAddHook(r *routeros.Reply) {
// b.Id = r.Done.Map["ret"]
// }
// Finder
// func (b *{{.ResourceName}}) FindField() string {
// return "name"
// }
// func (b *{{.ResourceName}}) FindFieldValue() string {
// return b.Name
// }
// Deleter
// func (b *{{.ResourceName}}) DeleteField() string {
// return "numbers"
// }
// func (b *{{.ResourceName}}) DeleteFieldValue() string {
// return b.Id
// }
// Typed wrappers
func (c Mikrotik) Add{{.ResourceName}}(r *{{.ResourceName}}) (*{{.ResourceName}}, error) {
res, err := c.Add(r)
if err != nil {
return nil, err
}
return res.(*{{.ResourceName}}), nil
}
func (c Mikrotik) Update{{.ResourceName}}(r *{{.ResourceName}}) (*{{.ResourceName}}, error) {
res, err := c.Update(r)
if err != nil {
return nil, err
}
return res.(*{{.ResourceName}}), nil
}
func (c Mikrotik) Find{{.ResourceName}}(id string) (*{{.ResourceName}}, error) {
res, err := c.Find(&{{.ResourceName}}{Id: id})
if err != nil {
return nil, err
}
return res.(*{{.ResourceName}}), nil
}
func (c Mikrotik) Delete{{.ResourceName}}(id string) error {
return c.Delete(&{{.ResourceName}}{Id: id})
}
`
)
12 changes: 12 additions & 0 deletions cmd/mikrotik-codegen/internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,15 @@ func ToSnakeCase(in string) string {

return buf.String()
}

// FirstLower makes first symbol lowercase in the string
func FirstLower(s string) string {
if len(s) < 1 {
return s
}
if len(s) == 1 {
return strings.ToLower(s)
}

return strings.ToLower(s[:1]) + s[1:]
}
30 changes: 29 additions & 1 deletion cmd/mikrotik-codegen/internal/utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package utils
import "testing"

func TestToSnakeCase(t *testing.T) {

testCases := []struct {
name string
input string
Expand Down Expand Up @@ -46,3 +45,32 @@ func TestToSnakeCase(t *testing.T) {
})
}
}

func TestFirstLower(t *testing.T) {
testCases := []struct {
name string
input string
expected string
}{
{
name: "title case",
input: "ClientResourceName",
expected: "clientResourceName",
},
{
name: "kebab case",
input: "clientResourceName",
expected: "clientResourceName",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := FirstLower(tc.input)
if result != tc.expected {
t.Errorf(`
expected %s,
got %s`, tc.expected, result)
}
})
}
}
Loading

0 comments on commit a5426b1

Please sign in to comment.