Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codegen mikrotik resource #163

Merged
merged 2 commits into from
Jun 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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