Skip to content

Commit

Permalink
feat: Render files from templates (first step) (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianczech authored Mar 4, 2024
1 parent 813a4d9 commit b40e3b4
Show file tree
Hide file tree
Showing 14 changed files with 302 additions and 53 deletions.
25 changes: 25 additions & 0 deletions pkg/content/unmarshal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package content

import (
"encoding/json"
"fmt"
"gopkg.in/yaml.v3"
"strings"
)

func Unmarshal(v []byte, o interface{}) error {
var err error

runes := []byte(strings.TrimSpace(string(v)))
if len(runes) == 0 {
return fmt.Errorf("no data in file")
}

if runes[0] == '{' && runes[len(runes)-1] == '}' {
err = json.Unmarshal(v, o)
} else {
err = yaml.Unmarshal(v, o)
}

return err
}
123 changes: 123 additions & 0 deletions pkg/generate/generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package generate

import (
"fmt"
"github.com/paloaltonetworks/pan-os-codegen/pkg/properties"
"github.com/paloaltonetworks/pan-os-codegen/pkg/translate"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"text/template"
)

type Creator struct {
GoOutputDir string
TemplatesDir string
Spec *properties.Normalization
}

func NewCreator(goOutputDir string, templatesDir string, spec *properties.Normalization) *Creator {
return &Creator{
GoOutputDir: goOutputDir,
TemplatesDir: templatesDir,
Spec: spec,
}
}

func (c *Creator) RenderTemplate() error {
templates := make([]string, 0, 100)
templates, err := c.listOfTemplates(templates)
if err != nil {
return err
}

for _, templateName := range templates {
filePath := c.createFullFilePath(c.GoOutputDir, c.Spec, templateName)
fmt.Printf("Create file %s\n", filePath)

if err := c.makeAllDirs(filePath, err); err != nil {
return err
}

outputFile, err := c.createFile(filePath)
if err != nil {
return err
}
defer func(outputFile *os.File) {
err := outputFile.Close()
if err != nil {

}
}(outputFile)

tmpl, err := c.parseTemplate(templateName)
if err != nil {
return err
}

err = c.generateOutputFileFromTemplate(tmpl, outputFile, c.Spec)
if err != nil {
return err
}
}
return nil
}

func (c *Creator) generateOutputFileFromTemplate(tmpl *template.Template, output io.Writer, spec *properties.Normalization) error {
if err := tmpl.Execute(output, spec); err != nil {
return err
}
return nil
}

func (c *Creator) parseTemplate(templateName string) (*template.Template, error) {
templatePath := fmt.Sprintf("%s/%s", c.TemplatesDir, templateName)
funcMap := template.FuncMap{
"packageName": translate.PackageName,
}
tmpl, err := template.New(templateName).Funcs(funcMap).ParseFiles(templatePath)
if err != nil {
return nil, err
}
return tmpl, nil
}

func (c *Creator) createFullFilePath(goOutputDir string, spec *properties.Normalization, templateName string) string {
return fmt.Sprintf("%s/%s/%s.go", goOutputDir, strings.Join(spec.GoSdkPath, "/"), strings.Split(templateName, ".")[0])
}

func (c *Creator) listOfTemplates(files []string) ([]string, error) {
err := filepath.WalkDir(c.TemplatesDir, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}

if strings.HasSuffix(entry.Name(), ".tmpl") {
files = append(files, filepath.Base(path))
}

return nil
})
if err != nil {
return nil, err
}
return files, nil
}

func (c *Creator) createFile(filePath string) (*os.File, error) {
outputFile, err := os.Create(filePath)
if err != nil {
return nil, err
}
return outputFile, nil
}

func (c *Creator) makeAllDirs(filePath string, err error) error {
dirPath := filepath.Dir(filePath)
if err = os.MkdirAll(dirPath, os.ModePerm); err != nil {
return err
}
return nil
}
68 changes: 68 additions & 0 deletions pkg/generate/generator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package generate

import (
"bytes"
"github.com/paloaltonetworks/pan-os-codegen/pkg/properties"
"github.com/stretchr/testify/assert"
"testing"
)

func TestCreateFullFilePath(t *testing.T) {
// given
spec := properties.Normalization{
GoSdkPath: []string{"go"},
}
generator := NewCreator("test", "../../templates/sdk", &spec)

// when
fullFilePath := generator.createFullFilePath("output", &spec, "template.tmpl")

// then
assert.NotNil(t, fullFilePath)
assert.Equal(t, "output/go/template.go", fullFilePath)
}

// NOTE - unit tests should only touch code inside package, do not reference external resources.
// Nevertheless, below tests ARE REFERENCING to text templates, because template ARE NOT EMBEDDED into Go files.
// Technically we could embed them, but then:
// 1 - we are losing clarity of the code,
// 2 - we are mixing Golang with templates expressions.
// Testing generator is crucial, so below tests we can br treated more as integration tests, not unit one.

func TestListOfTemplates(t *testing.T) {
// given
spec := properties.Normalization{
GoSdkPath: []string{"go"},
}
generator := NewCreator("test", "../../templates/sdk", &spec)

// when
var templates []string
templates, _ = generator.listOfTemplates(templates)

// then
assert.Equal(t, 4, len(templates))
}

func TestParseTemplate(t *testing.T) {
// given
spec := properties.Normalization{
GoSdkPath: []string{"object", "address"},
}
generator := NewCreator("test", "../../templates/sdk", &spec)
expectedFileContent := `package address
type Specifier func(Entry) (any, error)
type Normalizer interface {
Normalize() ([]Entry, error)
}`

// when
template, _ := generator.parseTemplate("interfaces.tmpl")
var output bytes.Buffer
_ = generator.generateOutputFileFromTemplate(template, &output, generator.Spec)

// then
assert.Equal(t, expectedFileContent, output.String())
}
21 changes: 0 additions & 21 deletions pkg/load/file.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,9 @@
package load

import (
"encoding/json"
"fmt"
"gopkg.in/yaml.v3"
"os"
"strings"
)

func Unmarshal(v []byte, o interface{}) error {
var err error

runes := []byte(strings.TrimSpace(string(v)))
if len(runes) == 0 {
return fmt.Errorf("no data in file")
}

if runes[0] == '{' && runes[len(runes)-1] == '}' {
err = json.Unmarshal(v, o)
} else {
err = yaml.Unmarshal(v, o)
}

return err
}

func File(path string) ([]byte, error) {
content, err := os.ReadFile(path)
if err != nil {
Expand Down
45 changes: 29 additions & 16 deletions pkg/mktp/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mktp
import (
"context"
"fmt"
"github.com/paloaltonetworks/pan-os-codegen/pkg/generate"
"github.com/paloaltonetworks/pan-os-codegen/pkg/load"
"io"
"os"
Expand Down Expand Up @@ -61,37 +62,49 @@ func (c *Cmd) Execute() error {
//providerDataSources := make([]string, 0, 200)
//providerResources := make([]string, 0, 100)

fmt.Fprintf(c.Stdout, "Reading configuration file: %s...\n", c.args[0])
content, err := load.File(c.args[0])
config, err := properties.ParseConfig(content)
if err != nil {
return fmt.Errorf("error parsing %s - %s", c.args[0], err)
// Check if path to configuration file is passed as argument
if len(c.args) == 0 {
return fmt.Errorf("path to configuration file is required")
}
configPath := c.args[0]

fmt.Fprintf(c.Stdout, "Output directory for Go SDK: %s\n", config.Output.GoSdk)
if err = os.MkdirAll(config.Output.GoSdk, 0755); err != nil && !os.IsExist(err) {
return err
// Load configuration file
content, err := load.File(configPath)
if err != nil {
return fmt.Errorf("error loading %s - %s", configPath, err)
}

fmt.Fprintf(c.Stdout, "Output directory for Terraform provider: %s\n", config.Output.TerraformProvider)
if err = os.MkdirAll(config.Output.TerraformProvider, 0755); err != nil && !os.IsExist(err) {
return err
// Parse configuration file
config, err := properties.ParseConfig(content)
if err != nil {
return fmt.Errorf("error parsing %s - %s", configPath, err)
}

for _, configPath := range c.specs {
fmt.Fprintf(c.Stdout, "Parsing %s...\n", configPath)
content, err := load.File(configPath)
for _, specPath := range c.specs {
fmt.Fprintf(c.Stdout, "Parsing %s...\n", specPath)

// Load YAML file
content, err := load.File(specPath)
if err != nil {
return fmt.Errorf("error loading %s - %s", specPath, err)
}

// Parse content
spec, err := properties.ParseSpec(content)
if err != nil {
return fmt.Errorf("error parsing %s - %s", configPath, err)
return fmt.Errorf("error parsing %s - %s", specPath, err)
}

// Sanity check.
if err = spec.Sanity(); err != nil {
return fmt.Errorf("%s sanity failed: %s", configPath, err)
return fmt.Errorf("%s sanity failed: %s", specPath, err)
}

// Output normalization as pango code.
generator := generate.NewCreator(config.Output.GoSdk, "templates/sdk", spec)
if err = generator.RenderTemplate(); err != nil {
return fmt.Errorf("error rendering %s - %s", specPath, err)
}

// Output as Terraform code.
}
Expand Down
8 changes: 3 additions & 5 deletions pkg/properties/config.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package properties

import (
"github.com/paloaltonetworks/pan-os-codegen/pkg/load"
)
import "github.com/paloaltonetworks/pan-os-codegen/pkg/content"

type Config struct {
Output OutputPaths `json:"output" yaml:"output"`
Expand All @@ -13,8 +11,8 @@ type OutputPaths struct {
TerraformProvider string `json:"terraform_provider" yaml:"terraform_provider"`
}

func ParseConfig(content []byte) (*Config, error) {
func ParseConfig(input []byte) (*Config, error) {
var ans Config
err := load.Unmarshal(content, &ans)
err := content.Unmarshal(input, &ans)
return &ans, err
}
7 changes: 3 additions & 4 deletions pkg/properties/normalized.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ package properties
import (
"errors"
"fmt"
"github.com/paloaltonetworks/pan-os-codegen/pkg/content"
"io/fs"
"path/filepath"
"runtime"
"strings"

"github.com/paloaltonetworks/pan-os-codegen/pkg/load"
)

type Normalization struct {
Expand Down Expand Up @@ -128,9 +127,9 @@ func GetNormalizations() ([]string, error) {
return files, nil
}

func ParseSpec(content []byte) (*Normalization, error) {
func ParseSpec(input []byte) (*Normalization, error) {
var ans Normalization
err := load.Unmarshal(content, &ans)
err := content.Unmarshal(input, &ans)
return &ans, err
}

Expand Down
Loading

0 comments on commit b40e3b4

Please sign in to comment.