Skip to content

Commit

Permalink
feat: Render entry.go (second stage) (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianczech authored Mar 15, 2024
1 parent 5a18280 commit 25ec702
Show file tree
Hide file tree
Showing 17 changed files with 729 additions and 93 deletions.
5 changes: 3 additions & 2 deletions cmd/codegen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import (
"github.com/paloaltonetworks/pan-os-codegen/pkg/commands/codegen"
)

// Config holds the configuration values for the application
// Config holds the configuration values for the application.
type Config struct {
ConfigFile string
OpType string
}

// parseFlags parses the command line flags
// parseFlags parses the command line flags.
func parseFlags() Config {
var cfg Config
flag.StringVar(&cfg.ConfigFile, "config", "./cmd/codegen/config.yaml", "Path to the configuration file")
Expand All @@ -24,6 +24,7 @@ func parseFlags() Config {
return cfg
}

// runCommand executed command to generate code for SDK or Terraform.
func runCommand(ctx context.Context, cmdType codegen.CommandType, cfg string) {
cmd, err := codegen.NewCommand(ctx, cmdType, cfg)
if err != nil {
Expand Down
7 changes: 5 additions & 2 deletions pkg/generate/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package generate

import (
"bytes"
"fmt"
"github.com/paloaltonetworks/pan-os-codegen/pkg/properties"
"io"
"io/fs"
Expand All @@ -10,6 +11,7 @@ import (
"path/filepath"
)

// CopyAssets copy assets (static files) according to configuration.
func CopyAssets(config *properties.Config) error {
for _, asset := range config.Assets {
files, err := listAssets(asset)
Expand All @@ -32,10 +34,10 @@ func CopyAssets(config *properties.Config) error {
return nil
}

// listAssets walk through directory and get list of all assets (static files).
func listAssets(asset *properties.Asset) ([]string, error) {
var files []string

// Walk through directory and get list of all files
err := filepath.WalkDir(asset.Source, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
Expand All @@ -52,9 +54,10 @@ func listAssets(asset *properties.Asset) ([]string, error) {
return files, nil
}

// copyAsset copy single asset, which may contain multiple files.
func copyAsset(target string, asset *properties.Asset, files []string) error {
// Prepare destination path
destinationDir := target + "/" + asset.Destination
destinationDir := fmt.Sprintf("%s/%s", target, asset.Destination)

// Create the destination directory if it doesn't exist
if err := os.MkdirAll(destinationDir, os.ModePerm); err != nil {
Expand Down
56 changes: 38 additions & 18 deletions pkg/generate/generator.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package generate

import (
"bytes"
"fmt"
"io"
"log"
"os"
"path/filepath"
Expand All @@ -18,6 +20,7 @@ type Creator struct {
Spec *properties.Normalization
}

// NewCreator initialize Creator instance.
func NewCreator(goOutputDir, templatesDir string, spec *properties.Normalization) *Creator {
return &Creator{
GoOutputDir: goOutputDir,
Expand All @@ -26,6 +29,7 @@ func NewCreator(goOutputDir, templatesDir string, spec *properties.Normalization
}
}

// RenderTemplate loop through all templates, parse them and render content, which is saved to output file.
func (c *Creator) RenderTemplate() error {
log.Println("Start rendering templates")

Expand All @@ -42,30 +46,40 @@ func (c *Creator) RenderTemplate() error {
return fmt.Errorf("error creating directories for %s: %w", filePath, err)
}

outputFile, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("error creating file %s: %w", filePath, err)
}
defer outputFile.Close()

tmpl, err := c.parseTemplate(templateName)
if err != nil {
return fmt.Errorf("error parsing template %s: %w", templateName, err)
}

if err := tmpl.Execute(outputFile, c.Spec); err != nil {
var data bytes.Buffer
if err := tmpl.Execute(&data, c.Spec); err != nil {
return fmt.Errorf("error executing template %s: %w", templateName, err)
}
// If from template no data was rendered (e.g. for DNS spec entry should not be created),
// then we don't need to create empty file (e.g. `entry.go`) with no content
if data.Len() > 0 {
outputFile, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("error creating file %s: %w", filePath, err)
}
defer outputFile.Close()

_, err = io.Copy(outputFile, &data)
if err != nil {
return err
}
}
}
return nil
}


// createFullFilePath returns a full path for output file generated from template passed as argument to function.
func (c *Creator) createFullFilePath(templateName string) string {
fileBaseName := strings.TrimSuffix(templateName, filepath.Ext(templateName))
return filepath.Join(c.GoOutputDir, filepath.Join(c.Spec.GoSdkPath...), fileBaseName+".go")
return filepath.Join(c.GoOutputDir, filepath.Join(c.Spec.GoSdkPath...), fmt.Sprintf("%s.go", fileBaseName))
}

// listOfTemplates return list of templates defined in TemplatesDir.
func (c *Creator) listOfTemplates() ([]string, error) {
var files []string
err := filepath.WalkDir(c.TemplatesDir, func(path string, entry os.DirEntry, err error) error {
Expand All @@ -86,11 +100,13 @@ func (c *Creator) listOfTemplates() ([]string, error) {
return files, nil
}

// makeAllDirs creates all required directories, which are in the file path.
func (c *Creator) makeAllDirs(filePath string) error {
dirPath := filepath.Dir(filePath)
return os.MkdirAll(dirPath, os.ModePerm)
}

// createFile just create a file and return it.
func (c *Creator) createFile(filePath string) (*os.File, error) {
outputFile, err := os.Create(filePath)
if err != nil {
Expand All @@ -99,21 +115,25 @@ func (c *Creator) createFile(filePath string) (*os.File, error) {
return outputFile, nil
}


// parseTemplate parse template passed as argument and with function map defined below.
func (c *Creator) parseTemplate(templateName string) (*template.Template, error) {
templatePath := filepath.Join(c.TemplatesDir, templateName)
funcMap := template.FuncMap{
"packageName": translate.PackageName,
"locationType": translate.LocationType,
"specParamType": translate.SpecParamType,
"omitEmpty": translate.OmitEmpty,
"contains": func(full, part string) bool {
return strings.Contains(full, part)
},
"packageName": translate.PackageName,
"locationType": translate.LocationType,
"specParamType": translate.SpecParamType,
"xmlParamType": translate.XmlParamType,
"xmlTag": translate.XmlTag,
"specifyEntryAssignment": translate.SpecifyEntryAssignment,
"normalizeAssignment": translate.NormalizeAssignment,
"specMatchesFunction": translate.SpecMatchesFunction,
"omitEmpty": translate.OmitEmpty,
"contains": strings.Contains,
"subtract": func(a, b int) int {
return a - b
},
"asEntryXpath": translate.AsEntryXpath,
"generateEntryXpath": translate.GenerateEntryXpathForLocation,
"nestedSpecs": translate.NestedSpecs,
}
return template.New(templateName).Funcs(funcMap).ParseFiles(templatePath)
}
1 change: 1 addition & 0 deletions pkg/load/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ func File(path string) ([]byte, error) {
if err != nil {
return nil, err
}

return content, err
}
1 change: 1 addition & 0 deletions pkg/properties/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Target struct {
TerraformProvider bool `json:"terraform_provider" yaml:"terraform_provider"`
}

// ParseConfig initialize Config instance using input data from YAML file.
func ParseConfig(input []byte) (*Config, error) {
var ans Config
err := content.Unmarshal(input, &ans)
Expand Down
4 changes: 2 additions & 2 deletions pkg/properties/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ assets:
assert.NotEmptyf(t, config.Output.GoSdk, "Config Go SDK path cannot be empty")
assert.NotEmptyf(t, config.Output.TerraformProvider, "Config Terraform provider path cannot be empty")
assert.NotEmpty(t, config.Assets)
assert.Equal(t, 1, len(config.Assets))
assert.Equal(t, 1, len(config.Assets))
assert.Len(t, config.Assets, 1)
assert.Len(t, config.Assets, 1)
assert.Equal(t, "assets/util", config.Assets["util_package"].Source)
assert.True(t, config.Assets["util_package"].Target.GoSdk)
assert.False(t, config.Assets["util_package"].Target.TerraformProvider)
Expand Down
81 changes: 59 additions & 22 deletions pkg/properties/normalized.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package properties

import (
"errors"
"fmt"
"github.com/paloaltonetworks/pan-os-codegen/pkg/content"
"github.com/paloaltonetworks/pan-os-codegen/pkg/naming"
Expand Down Expand Up @@ -97,6 +96,7 @@ type SpecParamCount struct {
type SpecParamItems struct {
Type string `json:"type" yaml:"type"`
Length *SpecParamItemsLength `json:"length" yaml:"length"`
Ref []*string `json:"ref" yaml:"ref"`
}

type SpecParamItemsLength struct {
Expand All @@ -105,10 +105,13 @@ type SpecParamItemsLength struct {
}

type SpecParamProfile struct {
Xpath []string `json:"xpath" yaml:"xpath"`
Type string `json:"type" yaml:"type,omitempty"`
Xpath []string `json:"xpath" yaml:"xpath"`
Type string `json:"type" yaml:"type,omitempty"`
NotPresent bool `json:"not_present" yaml:"not_present"`
FromVersion string `json:"from_version" yaml:"from_version"`
}

// GetNormalizations get list of all specs (normalizations).
func GetNormalizations() ([]string, error) {
_, loc, _, ok := runtime.Caller(0)
if !ok {
Expand Down Expand Up @@ -137,18 +140,34 @@ func GetNormalizations() ([]string, error) {
return files, nil
}

// ParseSpec parse single spec (unmarshal file), add name variants for locations and params, add default types for params.
func ParseSpec(input []byte) (*Normalization, error) {
var spec Normalization

err := content.Unmarshal(input, &spec)
if err != nil {
return nil, err
}

err = spec.AddNameVariantsForLocation()
if err != nil {
return nil, err
}

err = spec.AddNameVariantsForParams()
if err != nil {
return nil, err
}

err = spec.AddDefaultTypesForParams()
if err != nil {
return nil, err
}

return &spec, err
}

// AddNameVariantsForLocation add name variants for location (under_score and CamelCase).
func (spec *Normalization) AddNameVariantsForLocation() error {
for key, location := range spec.Locations {
location.Name = &NameVariant{
Expand All @@ -167,6 +186,7 @@ func (spec *Normalization) AddNameVariantsForLocation() error {
return nil
}

// AddNameVariantsForParams recursively add name variants for params for nested specs.
func AddNameVariantsForParams(name string, param *SpecParam) error {
param.Name = &NameVariant{
Underscore: name,
Expand All @@ -187,6 +207,7 @@ func AddNameVariantsForParams(name string, param *SpecParam) error {
return nil
}

// AddNameVariantsForParams add name variants for params (under_score and CamelCase).
func (spec *Normalization) AddNameVariantsForParams() error {
if spec.Spec != nil {
for key, param := range spec.Spec.Params {
Expand All @@ -203,57 +224,73 @@ func (spec *Normalization) AddNameVariantsForParams() error {
return nil
}

// AddDefaultTypesForParams ensures all SpecParams within Spec have a default type if not specified.
func (spec *Normalization) AddDefaultTypesForParams() error {
if spec.Spec == nil {
return nil
}
// addDefaultTypesForParams recursively add default types for params for nested specs.
func addDefaultTypesForParams(params map[string]*SpecParam) error {
for _, param := range params {
if param.Type == "" {
param.Type = "string"
}

setDefaultParamTypeForMap(spec.Spec.Params)
setDefaultParamTypeForMap(spec.Spec.OneOf)
if param.Spec != nil {
if err := addDefaultTypesForParams(param.Spec.Params); err != nil {
return err
}
if err := addDefaultTypesForParams(param.Spec.OneOf); err != nil {
return err
}
}
}

return nil
}

// setDefaultParamTypeForMap iterates over a map of SpecParam pointers, setting their Type to "string" if not specified.
func setDefaultParamTypeForMap(params map[string]*SpecParam) {
for _, param := range params {
if param.Type == "" {
param.Type = "string"
// AddDefaultTypesForParams ensures all params within Spec have a default type if not specified.
func (spec *Normalization) AddDefaultTypesForParams() error {
if spec.Spec != nil {
if err := addDefaultTypesForParams(spec.Spec.Params); err != nil {
return err
}
if err := addDefaultTypesForParams(spec.Spec.OneOf); err != nil {
return err
}
return nil
} else {
return nil
}
}

// Sanity basic checks for specification (normalization) e.g. check if at least 1 location is defined.
func (spec *Normalization) Sanity() error {
if spec.Name == "" {
return errors.New("name is required")
return fmt.Errorf("name is required")
}
if spec.Locations == nil {
return errors.New("at least 1 location is required")
return fmt.Errorf("at least 1 location is required")
}
if spec.GoSdkPath == nil {
return errors.New("golang SDK path is required")
return fmt.Errorf("golang SDK path is required")
}

return nil
}

// Validate validations for specification (normalization) e.g. check if XPath contain /.
func (spec *Normalization) Validate() []error {
var checks []error

if strings.Contains(spec.TerraformProviderSuffix, "panos_") {
checks = append(checks, errors.New("suffix for Terraform provider cannot contain `panos_`"))
checks = append(checks, fmt.Errorf("suffix for Terraform provider cannot contain `panos_`"))
}
for _, suffix := range spec.XpathSuffix {
if strings.Contains(suffix, "/") {
checks = append(checks, errors.New("XPath cannot contain /"))
checks = append(checks, fmt.Errorf("XPath cannot contain /"))
}
}
if len(spec.Locations) < 1 {
checks = append(checks, errors.New("at least 1 location is required"))
checks = append(checks, fmt.Errorf("at least 1 location is required"))
}
if len(spec.GoSdkPath) < 2 {
checks = append(checks, errors.New("golang SDK path should contain at least 2 elements of the path"))
checks = append(checks, fmt.Errorf("golang SDK path should contain at least 2 elements of the path"))
}

return checks
Expand Down
Loading

0 comments on commit 25ec702

Please sign in to comment.