diff --git a/docs/data-sources/network_group_policies.md b/docs/data-sources/network_group_policies.md index 085a212..09984f3 100644 --- a/docs/data-sources/network_group_policies.md +++ b/docs/data-sources/network_group_policies.md @@ -33,8 +33,16 @@ data "meraki_network_group_policies" "example" { ### Read-Only +- `bandwidth_bandwidth_limits_limit_down` (Number) +- `bandwidth_bandwidth_limits_limit_up` (Number) +- `bandwidth_settings` (String) - `bonjour_forwarding_rules` (Attributes List) (see [below for nested schema](#nestedatt--bonjour_forwarding_rules)) - `bonjour_forwarding_settings` (String) +- `content_filtering_allowed_url_patterns_settings` (String) +- `content_filtering_blocked_url_categories_categories` (List of String) +- `content_filtering_blocked_url_categories_settings` (String) +- `content_filtering_blocked_url_patterns_patterns` (List of String) +- `content_filtering_blocked_url_patterns_settings` (String) ### Nested Schema for `bonjour_forwarding_rules` diff --git a/docs/resources/network_group_policies.md b/docs/resources/network_group_policies.md index c91faad..577c6f4 100644 --- a/docs/resources/network_group_policies.md +++ b/docs/resources/network_group_policies.md @@ -24,6 +24,14 @@ resource "meraki_network_group_policies" "example" { vlan_id = "2" } ] + bandwidth_bandwidth_limits_limit_down = 1000000 + bandwidth_bandwidth_limits_limit_up = 1000000 + bandwidth_settings = "custom" + content_filtering_allowed_url_patterns_settings = "network default" + content_filtering_blocked_url_categories_categories = ["meraki:contentFiltering/category/1"] + content_filtering_blocked_url_categories_settings = "override" + content_filtering_blocked_url_patterns_patterns = ["http://www.betting.com"] + content_filtering_blocked_url_patterns_settings = "append" } ``` @@ -37,8 +45,16 @@ resource "meraki_network_group_policies" "example" { ### Optional +- `bandwidth_bandwidth_limits_limit_down` (Number) +- `bandwidth_bandwidth_limits_limit_up` (Number) +- `bandwidth_settings` (String) - `bonjour_forwarding_rules` (Attributes List) (see [below for nested schema](#nestedatt--bonjour_forwarding_rules)) - `bonjour_forwarding_settings` (String) +- `content_filtering_allowed_url_patterns_settings` (String) +- `content_filtering_blocked_url_categories_categories` (List of String) +- `content_filtering_blocked_url_categories_settings` (String) +- `content_filtering_blocked_url_patterns_patterns` (List of String) +- `content_filtering_blocked_url_patterns_settings` (String) ### Read-Only diff --git a/examples/resources/meraki_network_group_policies/resource.tf b/examples/resources/meraki_network_group_policies/resource.tf index 290b95e..59abd94 100644 --- a/examples/resources/meraki_network_group_policies/resource.tf +++ b/examples/resources/meraki_network_group_policies/resource.tf @@ -9,4 +9,12 @@ resource "meraki_network_group_policies" "example" { vlan_id = "2" } ] + bandwidth_bandwidth_limits_limit_down = 1000000 + bandwidth_bandwidth_limits_limit_up = 1000000 + bandwidth_settings = "custom" + content_filtering_allowed_url_patterns_settings = "network default" + content_filtering_blocked_url_categories_categories = ["meraki:contentFiltering/category/1"] + content_filtering_blocked_url_categories_settings = "override" + content_filtering_blocked_url_patterns_patterns = ["http://www.betting.com"] + content_filtering_blocked_url_patterns_settings = "append" } diff --git a/gen/definitions/networks_group_policies.yaml b/gen/definitions/networks_group_policies.yaml index e20426e..fe012d6 100644 --- a/gen/definitions/networks_group_policies.yaml +++ b/gen/definitions/networks_group_policies.yaml @@ -34,10 +34,45 @@ attributes: - model_name: vlanId type: String example: "2" - - - - + - model_name: limitDown + data_path: [bandwidth, bandwidthLimits] + type: Int64 + example: 1000000 + - model_name: limitUp + data_path: [bandwidth, bandwidthLimits] + type: Int64 + example: 1000000 + - model_name: settings + data_path: [bandwidth] + type: String + example: custom + - model_name: settings + data_path: [contentFiltering, allowedUrlPatterns] + exclude_test: true + type: String + example: network default + - model_name: categories + data_path: [contentFiltering, blockedUrlCategories] + exclude_test: true + type: List + element_type: String + example: meraki:contentFiltering/category/1 + - model_name: settings + data_path: [contentFiltering, blockedUrlCategories] + exclude_test: true + example: override + type: String + - model_name: patterns + type: List + data_path: [contentFiltering, blockedUrlPatterns] + exclude_test: true + element_type: String + example: http://www.betting.com + - model_name: settings + data_path: [contentFiltering, blockedUrlPatterns] + exclude_test: true + example: append + type: String test_prerequisites: | data "meraki_organization" "test" { diff --git a/gen/generator.go b/gen/generator.go index 9404c6b..defa78e 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -30,8 +30,8 @@ import ( "regexp" "strings" "text/template" - "unicode" + "github.com/netascode/terraform-provider-meraki/gen/generator" "gopkg.in/yaml.v3" ) @@ -93,360 +93,16 @@ var templates = []t{ }, } -type YamlConfig struct { - Name string `yaml:"name"` - TfName string `yaml:"tf_name"` - RestEndpoint string `yaml:"rest_endpoint"` - PutCreate bool `yaml:"put_create"` - GetFromAll bool `yaml:"get_from_all"` - NoUpdate bool `yaml:"no_update"` - NoDelete bool `yaml:"no_delete"` - DataSourceNameQuery bool `yaml:"data_source_name_query"` - MinimumVersion string `yaml:"minimum_version"` - DsDescription string `yaml:"ds_description"` - ResDescription string `yaml:"res_description"` - DocCategory string `yaml:"doc_category"` - ExcludeTest bool `yaml:"exclude_test"` - SkipMinimumTest bool `yaml:"skip_minimum_test"` - Attributes []YamlConfigAttribute `yaml:"attributes"` - TestTags []string `yaml:"test_tags"` - TestPrerequisites string `yaml:"test_prerequisites"` - IdName string `yaml:"id_name"` -} - -type YamlConfigAttribute struct { - ModelName string `yaml:"model_name"` - TfName string `yaml:"tf_name"` - Type string `yaml:"type"` - ElementType string `yaml:"element_type"` - DataPath []string `yaml:"data_path"` - Id bool `yaml:"id"` - ResourceId bool `yaml:"resource_id"` - Reference bool `yaml:"reference"` - RequiresReplace bool `yaml:"requires_replace"` - Mandatory bool `yaml:"mandatory"` - WriteOnly bool `yaml:"write_only"` - WriteChangesOnly bool `yaml:"write_changes_only"` - ExcludeTest bool `yaml:"exclude_test"` - ExcludeExample bool `yaml:"exclude_example"` - Description string `yaml:"description"` - Example string `yaml:"example"` - EnumValues []string `yaml:"enum_values"` - MinList int64 `yaml:"min_list"` - MaxList int64 `yaml:"max_list"` - MinInt int64 `yaml:"min_int"` - MaxInt int64 `yaml:"max_int"` - MinFloat float64 `yaml:"min_float"` - MaxFloat float64 `yaml:"max_float"` - MapKeyExample string `yaml:"map_key_example"` - OrderedList bool `yaml:"ordered_list"` - StringPatterns []string `yaml:"string_patterns"` - StringMinLength int64 `yaml:"string_min_length"` - StringMaxLength int64 `yaml:"string_max_length"` - DefaultValue string `yaml:"default_value"` - Value string `yaml:"value"` - TestValue string `yaml:"test_value"` - MinimumTestValue string `yaml:"minimum_test_value"` - TestTags []string `yaml:"test_tags"` - Attributes []YamlConfigAttribute `yaml:"attributes"` - GoTypeName string -} - -// Templating helper function to convert TF name to GO name -func ToGoName(s string) string { - var g []string - - p := strings.Split(s, "_") - - for _, value := range p { - g = append(g, strings.Title(value)) - } - s = strings.Join(g, "") - return s -} - -// Templating helper function to convert string to camel case -func CamelCase(s string) string { - var g []string - - s = strings.ReplaceAll(s, "-", " ") - p := strings.Fields(s) - - for _, value := range p { - g = append(g, strings.Title(value)) - } - return strings.Join(g, "") -} - -func DromedaryCase(s string) string { - s = ToGoName(s) - return strings.ToLower(string(s[0])) + s[1:] -} - -// Templating helper function to convert string to snake case -func SnakeCase(s string) string { - var g []string - - s = strings.ReplaceAll(s, "-", " ") - p := strings.Fields(s) - - for _, value := range p { - g = append(g, strings.ToLower(value)) - } - return strings.Join(g, "_") -} - -// Templating helper function to fail a template mid-way -func Errorf(s string, args ...any) (struct{}, error) { - return struct{}{}, fmt.Errorf(s, args...) -} - -// Templating helper function to build a SJSON path -func BuildPath(s []string) string { - return strings.Join(s, ".") -} - -func contains(s []string, str string) bool { - for _, v := range s { - if v == str { - return true - } - } - return false -} - -// Templating helper function to return true if id included in attributes -func HasId(attributes []YamlConfigAttribute) bool { - for _, attr := range attributes { - if attr.Id { - return true - } - } - return false -} - -// Templating helper function to return true if reference included in attributes -func HasReference(attributes []YamlConfigAttribute) bool { - for _, attr := range attributes { - if attr.Reference { - return true - } - } - return false -} - -// Templating helper function to return true if reference included in attributes -func HasResourceId(attributes []YamlConfigAttribute) bool { - for _, attr := range attributes { - if attr.ResourceId { - return true - } - if len(attr.Attributes) > 0 { - if HasResourceId(attr.Attributes) { - return true - } - } - } - return false -} - -// Templating helper function to return true if type is a list or set without nested elements -func IsListSet(attribute YamlConfigAttribute) bool { - if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType != "" { - return true - } - return false -} - -// Templating helper function to return true if type is a list without nested elements -func IsList(attribute YamlConfigAttribute) bool { - if attribute.Type == "List" && attribute.ElementType != "" { - return true - } - return false -} - -// Templating helper function to return true if type is a set without nested elements -func IsSet(attribute YamlConfigAttribute) bool { - if attribute.Type == "Set" && attribute.ElementType != "" { - return true - } - return false -} - -// Templating helper function to return true if type is a list or set of strings without nested elements -func IsStringListSet(attribute YamlConfigAttribute) bool { - if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType == "String" { - return true - } - return false -} - -// Templating helper function to return true if type is a list or set of integers without nested elements -func IsInt64ListSet(attribute YamlConfigAttribute) bool { - if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType == "Int64" { - return true - } - return false -} - -// Templating helper function to return true if type is a list or a map or a set, anyway with nested elements -func IsNestedListMapSet(attribute YamlConfigAttribute) bool { - if (attribute.Type == "List" || attribute.Type == "Map" || attribute.Type == "Set") && attribute.ElementType == "" { - return true - } - return false -} - -// Templating helper function to return true if type is a list or set with nested elements -func IsNestedListSet(attribute YamlConfigAttribute) bool { - if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType == "" { - return true - } - return false -} - -// Templating helper function to return true if type is a list with nested elements -func IsNestedList(attribute YamlConfigAttribute) bool { - if attribute.Type == "List" && attribute.ElementType == "" { - return true - } - return false -} - -// Templating helper function to return true if type is a map with nested elements -func IsNestedMap(attribute YamlConfigAttribute) bool { - if attribute.Type == "Map" && attribute.ElementType == "" { - return true - } - return false -} - -// Templating helper function to return true if type is a set with nested elements -func IsNestedSet(attribute YamlConfigAttribute) bool { - if attribute.Type == "Set" && attribute.ElementType == "" { - return true - } - return false -} - -// Templating helper function to return number of import parts -func ImportParts(attributes []YamlConfigAttribute) int { - parts := 1 - for _, attr := range attributes { - if attr.Reference { - parts += 1 - } else if attr.Id { - parts += 1 - } - } - return parts -} - -// Templating helper function to subtract one number from another -func Subtract(a, b int) int { - return a - b -} - -// Map of templating functions -var functions = template.FuncMap{ - "toGoName": ToGoName, - "dromedaryCase": DromedaryCase, - "camelCase": CamelCase, - "snakeCase": SnakeCase, - "sprintf": fmt.Sprintf, - "errorf": Errorf, - "toLower": strings.ToLower, - "path": BuildPath, - "hasId": HasId, - "hasReference": HasReference, - "hasResourceId": HasResourceId, - "isListSet": IsListSet, - "isList": IsList, - "isSet": IsSet, - "isStringListSet": IsStringListSet, - "isInt64ListSet": IsInt64ListSet, - "isNestedListMapSet": IsNestedListMapSet, - "isNestedListSet": IsNestedListSet, - "isNestedList": IsNestedList, - "isNestedMap": IsNestedMap, - "isNestedSet": IsNestedSet, - "importParts": ImportParts, - "subtract": Subtract, -} - -func (attr *YamlConfigAttribute) init(parentGoTypeName string) error { - // Augument - if attr.TfName == "" { - var words []string - fullString := "" - for _, s := range attr.DataPath { - fullString += strings.ToUpper(string(s[0])) + s[1:] - } - fullString += strings.ToUpper(string(attr.ModelName[0])) + attr.ModelName[1:] - l := 0 - for s := fullString; s != ""; s = s[l:] { - l = strings.IndexFunc(s[1:], unicode.IsUpper) + 1 - if l <= 0 { - l = len(s) - } - words = append(words, strings.ToLower(s[:l])) - } - attr.TfName = strings.Join(words, "_") - } - - attr.GoTypeName = parentGoTypeName + ToGoName(attr.TfName) - - // Validate - if len(attr.Attributes) > 0 && attr.Type != "List" && attr.Type != "Map" && attr.Type != "Set" { - return fmt.Errorf("%q has type %q which cannot have `attributes`: instead use type List, Map, Set", - attr.TfName, attr.Type) - } - - if len(attr.Attributes) > 0 && attr.ElementType != "" { - return fmt.Errorf("%q: either `attributes` or `element_type` can be specified, but not both", attr.TfName) - } - - if attr.Type == "Map" && attr.ElementType != "" { - return fmt.Errorf("%q: the `element_type` is not yet implemented for type Map", attr.TfName) - } - - if attr.OrderedList { - if attr.Type != "List" { - return fmt.Errorf("%q has type %q which cannot use `ordered_list`: instead use type List", - attr.TfName, attr.Type) - } - if HasId(attr.Attributes) { - return fmt.Errorf("%q: the `ordered_list: true` conflicts with sub-attributes having `id: true`, as it treats list index ([i]) as the only unique id", - attr.TfName) - } - } - - if attr.Type == "Map" && HasId(attr.Attributes) { - return fmt.Errorf("Map %q cannot contain sub-attributes with `id: true`, as it treats map key ([k]) as the only unique id", - attr.TfName) - } - - // Recurse - for i := range attr.Attributes { - if err := attr.Attributes[i].init(attr.GoTypeName); err != nil { - return err - } - } - - return nil -} - -func NewYamlConfig(bytes []byte) (YamlConfig, error) { - var config YamlConfig +func NewYamlConfig(bytes []byte) (generator.YamlConfig, error) { + var config generator.YamlConfig if err := yaml.Unmarshal(bytes, &config); err != nil { return config, err } for i := range config.Attributes { - if err := config.Attributes[i].init(CamelCase(config.Name)); err != nil { - return YamlConfig{}, err + if err := config.Attributes[i].Init(generator.CamelCase(config.Name)); err != nil { + return generator.YamlConfig{}, err } } if config.DsDescription == "" { @@ -507,7 +163,7 @@ func renderTemplate(templatePath, outputPath string, config interface{}) { temp = temp + scanner.Text() + "\n" } - template, err := template.New(path.Base(templatePath)).Funcs(functions).Parse(temp) + template, err := template.New(path.Base(templatePath)).Funcs(generator.Functions).Parse(temp) if err != nil { log.Fatalf("Error parsing template: %v", err) } @@ -558,7 +214,7 @@ func renderTemplate(templatePath, outputPath string, config interface{}) { func main() { // Load configs - var configs []YamlConfig + var configs []generator.YamlConfig files, _ := os.ReadDir(definitionsPath) for _, filename := range files { @@ -579,7 +235,7 @@ func main() { for i := range configs { // Iterate over templates and render files for _, t := range templates { - renderTemplate(t.path, t.prefix+SnakeCase(configs[i].Name)+t.suffix, configs[i]) + renderTemplate(t.path, t.prefix+generator.SnakeCase(configs[i].Name)+t.suffix, configs[i]) } providerConfig = append(providerConfig, configs[i].Name) } diff --git a/gen/generator/model.go b/gen/generator/model.go new file mode 100644 index 0000000..59c7f1b --- /dev/null +++ b/gen/generator/model.go @@ -0,0 +1,128 @@ +package generator + +import ( + "fmt" + "strings" + "unicode" +) + +type YamlConfig struct { + Name string `yaml:"name"` + TfName string `yaml:"tf_name"` + RestEndpoint string `yaml:"rest_endpoint"` + PutCreate bool `yaml:"put_create"` + GetFromAll bool `yaml:"get_from_all"` + NoUpdate bool `yaml:"no_update"` + NoDelete bool `yaml:"no_delete"` + DataSourceNameQuery bool `yaml:"data_source_name_query"` + MinimumVersion string `yaml:"minimum_version"` + DsDescription string `yaml:"ds_description"` + ResDescription string `yaml:"res_description"` + DocCategory string `yaml:"doc_category"` + ExcludeTest bool `yaml:"exclude_test"` + SkipMinimumTest bool `yaml:"skip_minimum_test"` + Attributes []YamlConfigAttribute `yaml:"attributes"` + TestTags []string `yaml:"test_tags"` + TestPrerequisites string `yaml:"test_prerequisites"` + IdName string `yaml:"id_name"` +} + +type YamlConfigAttribute struct { + ModelName string `yaml:"model_name"` + TfName string `yaml:"tf_name"` + Type string `yaml:"type"` + ElementType string `yaml:"element_type"` + DataPath []string `yaml:"data_path"` + Id bool `yaml:"id"` + ResourceId bool `yaml:"resource_id"` + Reference bool `yaml:"reference"` + RequiresReplace bool `yaml:"requires_replace"` + Mandatory bool `yaml:"mandatory"` + WriteOnly bool `yaml:"write_only"` + WriteChangesOnly bool `yaml:"write_changes_only"` + ExcludeTest bool `yaml:"exclude_test"` + ExcludeExample bool `yaml:"exclude_example"` + Description string `yaml:"description"` + Example string `yaml:"example"` + EnumValues []string `yaml:"enum_values"` + MinList int64 `yaml:"min_list"` + MaxList int64 `yaml:"max_list"` + MinInt int64 `yaml:"min_int"` + MaxInt int64 `yaml:"max_int"` + MinFloat float64 `yaml:"min_float"` + MaxFloat float64 `yaml:"max_float"` + MapKeyExample string `yaml:"map_key_example"` + OrderedList bool `yaml:"ordered_list"` + StringPatterns []string `yaml:"string_patterns"` + StringMinLength int64 `yaml:"string_min_length"` + StringMaxLength int64 `yaml:"string_max_length"` + DefaultValue string `yaml:"default_value"` + Value string `yaml:"value"` + TestValue string `yaml:"test_value"` + MinimumTestValue string `yaml:"minimum_test_value"` + TestTags []string `yaml:"test_tags"` + Attributes []YamlConfigAttribute `yaml:"attributes"` + GoTypeName string +} + +func (attr *YamlConfigAttribute) Init(parentGoTypeName string) error { + // Augument + if attr.TfName == "" { + var words []string + fullString := "" + for _, s := range attr.DataPath { + fullString += strings.ToUpper(string(s[0])) + s[1:] + } + fullString += strings.ToUpper(string(attr.ModelName[0])) + attr.ModelName[1:] + l := 0 + for s := fullString; s != ""; s = s[l:] { + l = strings.IndexFunc(s[1:], unicode.IsUpper) + 1 + if l <= 0 { + l = len(s) + } + words = append(words, strings.ToLower(s[:l])) + } + attr.TfName = strings.Join(words, "_") + } + + attr.GoTypeName = parentGoTypeName + ToGoName(attr.TfName) + + // Validate + if len(attr.Attributes) > 0 && attr.Type != "List" && attr.Type != "Map" && attr.Type != "Set" { + return fmt.Errorf("%q has type %q which cannot have `attributes`: instead use type List, Map, Set", + attr.TfName, attr.Type) + } + + if len(attr.Attributes) > 0 && attr.ElementType != "" { + return fmt.Errorf("%q: either `attributes` or `element_type` can be specified, but not both", attr.TfName) + } + + if attr.Type == "Map" && attr.ElementType != "" { + return fmt.Errorf("%q: the `element_type` is not yet implemented for type Map", attr.TfName) + } + + if attr.OrderedList { + if attr.Type != "List" { + return fmt.Errorf("%q has type %q which cannot use `ordered_list`: instead use type List", + attr.TfName, attr.Type) + } + if HasId(attr.Attributes) { + return fmt.Errorf("%q: the `ordered_list: true` conflicts with sub-attributes having `id: true`, as it treats list index ([i]) as the only unique id", + attr.TfName) + } + } + + if attr.Type == "Map" && HasId(attr.Attributes) { + return fmt.Errorf("Map %q cannot contain sub-attributes with `id: true`, as it treats map key ([k]) as the only unique id", + attr.TfName) + } + + // Recurse + for i := range attr.Attributes { + if err := attr.Attributes[i].Init(attr.GoTypeName); err != nil { + return err + } + } + + return nil +} diff --git a/gen/generator/template_functions.go b/gen/generator/template_functions.go new file mode 100644 index 0000000..a5aa560 --- /dev/null +++ b/gen/generator/template_functions.go @@ -0,0 +1,230 @@ +package generator + +import ( + "fmt" + "strings" + "text/template" +) + +// Templating helper function to convert TF name to GO name +func ToGoName(s string) string { + var g []string + + p := strings.Split(s, "_") + + for _, value := range p { + g = append(g, strings.Title(value)) + } + s = strings.Join(g, "") + return s +} + +// Templating helper function to convert string to camel case +func CamelCase(s string) string { + var g []string + + s = strings.ReplaceAll(s, "-", " ") + p := strings.Fields(s) + + for _, value := range p { + g = append(g, strings.Title(value)) + } + return strings.Join(g, "") +} + +func DromedaryCase(s string) string { + s = ToGoName(s) + return strings.ToLower(string(s[0])) + s[1:] +} + +// Templating helper function to convert string to snake case +func SnakeCase(s string) string { + var g []string + + s = strings.ReplaceAll(s, "-", " ") + p := strings.Fields(s) + + for _, value := range p { + g = append(g, strings.ToLower(value)) + } + return strings.Join(g, "_") +} + +// Templating helper function to fail a template mid-way +func Errorf(s string, args ...any) (struct{}, error) { + return struct{}{}, fmt.Errorf(s, args...) +} + +// Templating helper function to build a SJSON path +func BuildPath(s []string) string { + return strings.Join(s, ".") +} + +func contains(s []string, str string) bool { + for _, v := range s { + if v == str { + return true + } + } + return false +} + +// Templating helper function to return true if id included in attributes +func HasId(attributes []YamlConfigAttribute) bool { + for _, attr := range attributes { + if attr.Id { + return true + } + } + return false +} + +// Templating helper function to return true if reference included in attributes +func HasReference(attributes []YamlConfigAttribute) bool { + for _, attr := range attributes { + if attr.Reference { + return true + } + } + return false +} + +// Templating helper function to return true if reference included in attributes +func HasResourceId(attributes []YamlConfigAttribute) bool { + for _, attr := range attributes { + if attr.ResourceId { + return true + } + if len(attr.Attributes) > 0 { + if HasResourceId(attr.Attributes) { + return true + } + } + } + return false +} + +// Templating helper function to return true if type is a list or set without nested elements +func IsListSet(attribute YamlConfigAttribute) bool { + if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType != "" { + return true + } + return false +} + +// Templating helper function to return true if type is a list without nested elements +func IsList(attribute YamlConfigAttribute) bool { + if attribute.Type == "List" && attribute.ElementType != "" { + return true + } + return false +} + +// Templating helper function to return true if type is a set without nested elements +func IsSet(attribute YamlConfigAttribute) bool { + if attribute.Type == "Set" && attribute.ElementType != "" { + return true + } + return false +} + +// Templating helper function to return true if type is a list or set of strings without nested elements +func IsStringListSet(attribute YamlConfigAttribute) bool { + if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType == "String" { + return true + } + return false +} + +// Templating helper function to return true if type is a list or set of integers without nested elements +func IsInt64ListSet(attribute YamlConfigAttribute) bool { + if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType == "Int64" { + return true + } + return false +} + +// Templating helper function to return true if type is a list or a map or a set, anyway with nested elements +func IsNestedListMapSet(attribute YamlConfigAttribute) bool { + if (attribute.Type == "List" || attribute.Type == "Map" || attribute.Type == "Set") && attribute.ElementType == "" { + return true + } + return false +} + +// Templating helper function to return true if type is a list or set with nested elements +func IsNestedListSet(attribute YamlConfigAttribute) bool { + if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType == "" { + return true + } + return false +} + +// Templating helper function to return true if type is a list with nested elements +func IsNestedList(attribute YamlConfigAttribute) bool { + if attribute.Type == "List" && attribute.ElementType == "" { + return true + } + return false +} + +// Templating helper function to return true if type is a map with nested elements +func IsNestedMap(attribute YamlConfigAttribute) bool { + if attribute.Type == "Map" && attribute.ElementType == "" { + return true + } + return false +} + +// Templating helper function to return true if type is a set with nested elements +func IsNestedSet(attribute YamlConfigAttribute) bool { + if attribute.Type == "Set" && attribute.ElementType == "" { + return true + } + return false +} + +// Templating helper function to return number of import parts +func ImportParts(attributes []YamlConfigAttribute) int { + parts := 1 + for _, attr := range attributes { + if attr.Reference { + parts += 1 + } else if attr.Id { + parts += 1 + } + } + return parts +} + +// Templating helper function to subtract one number from another +func Subtract(a, b int) int { + return a - b +} + +// Map of templating functions +var Functions = template.FuncMap{ + "toGoName": ToGoName, + "dromedaryCase": DromedaryCase, + "camelCase": CamelCase, + "snakeCase": SnakeCase, + "sprintf": fmt.Sprintf, + "errorf": Errorf, + "toLower": strings.ToLower, + "path": BuildPath, + "hasId": HasId, + "hasReference": HasReference, + "hasResourceId": HasResourceId, + "isListSet": IsListSet, + "isList": IsList, + "isSet": IsSet, + "isStringListSet": IsStringListSet, + "isInt64ListSet": IsInt64ListSet, + "isNestedListMapSet": IsNestedListMapSet, + "isNestedListSet": IsNestedListSet, + "isNestedList": IsNestedList, + "isNestedMap": IsNestedMap, + "isNestedSet": IsNestedSet, + "importParts": ImportParts, + "subtract": Subtract, +} diff --git a/internal/provider/data_source_meraki_network_group_policies.go b/internal/provider/data_source_meraki_network_group_policies.go index 124caff..40e5302 100644 --- a/internal/provider/data_source_meraki_network_group_policies.go +++ b/internal/provider/data_source_meraki_network_group_policies.go @@ -100,6 +100,40 @@ func (d *NetworkGroupPoliciesDataSource) Schema(ctx context.Context, req datasou }, }, }, + "bandwidth_bandwidth_limits_limit_down": schema.Int64Attribute{ + MarkdownDescription: "", + Computed: true, + }, + "bandwidth_bandwidth_limits_limit_up": schema.Int64Attribute{ + MarkdownDescription: "", + Computed: true, + }, + "bandwidth_settings": schema.StringAttribute{ + MarkdownDescription: "", + Computed: true, + }, + "content_filtering_allowed_url_patterns_settings": schema.StringAttribute{ + MarkdownDescription: "", + Computed: true, + }, + "content_filtering_blocked_url_categories_categories": schema.ListAttribute{ + MarkdownDescription: "", + ElementType: types.StringType, + Computed: true, + }, + "content_filtering_blocked_url_categories_settings": schema.StringAttribute{ + MarkdownDescription: "", + Computed: true, + }, + "content_filtering_blocked_url_patterns_patterns": schema.ListAttribute{ + MarkdownDescription: "", + ElementType: types.StringType, + Computed: true, + }, + "content_filtering_blocked_url_patterns_settings": schema.StringAttribute{ + MarkdownDescription: "", + Computed: true, + }, }, } } diff --git a/internal/provider/data_source_meraki_network_group_policies_test.go b/internal/provider/data_source_meraki_network_group_policies_test.go index be20ce6..144d552 100644 --- a/internal/provider/data_source_meraki_network_group_policies_test.go +++ b/internal/provider/data_source_meraki_network_group_policies_test.go @@ -35,6 +35,9 @@ func TestAccDataSourceMerakiNetworkGroupPolicies(t *testing.T) { checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policies.test", "bonjour_forwarding_rules.0.description", "a simple bonjour rule")) checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policies.test", "bonjour_forwarding_rules.0.services.0", "All Services")) checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policies.test", "bonjour_forwarding_rules.0.vlan_id", "2")) + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policies.test", "bandwidth_bandwidth_limits_limit_down", "1000000")) + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policies.test", "bandwidth_bandwidth_limits_limit_up", "1000000")) + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policies.test", "bandwidth_settings", "custom")) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, @@ -81,6 +84,9 @@ func testAccDataSourceMerakiNetworkGroupPoliciesConfig() string { config += ` services = ["All Services"]` + "\n" config += ` vlan_id = "2"` + "\n" config += ` }]` + "\n" + config += ` bandwidth_bandwidth_limits_limit_down = 1000000` + "\n" + config += ` bandwidth_bandwidth_limits_limit_up = 1000000` + "\n" + config += ` bandwidth_settings = "custom"` + "\n" config += `}` + "\n" config += ` @@ -102,6 +108,9 @@ func testAccNamedDataSourceMerakiNetworkGroupPoliciesConfig() string { config += ` services = ["All Services"]` + "\n" config += ` vlan_id = "2"` + "\n" config += ` }]` + "\n" + config += ` bandwidth_bandwidth_limits_limit_down = 1000000` + "\n" + config += ` bandwidth_bandwidth_limits_limit_up = 1000000` + "\n" + config += ` bandwidth_settings = "custom"` + "\n" config += `}` + "\n" config += ` diff --git a/internal/provider/model_meraki_network_group_policies.go b/internal/provider/model_meraki_network_group_policies.go index 730119b..6078580 100644 --- a/internal/provider/model_meraki_network_group_policies.go +++ b/internal/provider/model_meraki_network_group_policies.go @@ -36,11 +36,19 @@ import ( // Section below is generated&owned by "gen/generator.go". //template:begin types type NetworkGroupPolicies struct { - GroupPolicyId types.String `tfsdk:"group_policy_id"` - NetworkId types.String `tfsdk:"network_id"` - Name types.String `tfsdk:"name"` - BonjourForwardingSettings types.String `tfsdk:"bonjour_forwarding_settings"` - BonjourForwardingRules []NetworkGroupPoliciesBonjourForwardingRules `tfsdk:"bonjour_forwarding_rules"` + GroupPolicyId types.String `tfsdk:"group_policy_id"` + NetworkId types.String `tfsdk:"network_id"` + Name types.String `tfsdk:"name"` + BonjourForwardingSettings types.String `tfsdk:"bonjour_forwarding_settings"` + BonjourForwardingRules []NetworkGroupPoliciesBonjourForwardingRules `tfsdk:"bonjour_forwarding_rules"` + BandwidthBandwidthLimitsLimitDown types.Int64 `tfsdk:"bandwidth_bandwidth_limits_limit_down"` + BandwidthBandwidthLimitsLimitUp types.Int64 `tfsdk:"bandwidth_bandwidth_limits_limit_up"` + BandwidthSettings types.String `tfsdk:"bandwidth_settings"` + ContentFilteringAllowedUrlPatternsSettings types.String `tfsdk:"content_filtering_allowed_url_patterns_settings"` + ContentFilteringBlockedUrlCategoriesCategories types.List `tfsdk:"content_filtering_blocked_url_categories_categories"` + ContentFilteringBlockedUrlCategoriesSettings types.String `tfsdk:"content_filtering_blocked_url_categories_settings"` + ContentFilteringBlockedUrlPatternsPatterns types.List `tfsdk:"content_filtering_blocked_url_patterns_patterns"` + ContentFilteringBlockedUrlPatternsSettings types.String `tfsdk:"content_filtering_blocked_url_patterns_settings"` } type NetworkGroupPoliciesBonjourForwardingRules struct { @@ -90,6 +98,34 @@ func (data NetworkGroupPolicies) toBody(ctx context.Context, state NetworkGroupP body, _ = sjson.SetRaw(body, "bonjourForwarding.rules.-1", itemBody) } } + if !data.BandwidthBandwidthLimitsLimitDown.IsNull() { + body, _ = sjson.Set(body, "bandwidth.bandwidthLimits.limitDown", data.BandwidthBandwidthLimitsLimitDown.ValueInt64()) + } + if !data.BandwidthBandwidthLimitsLimitUp.IsNull() { + body, _ = sjson.Set(body, "bandwidth.bandwidthLimits.limitUp", data.BandwidthBandwidthLimitsLimitUp.ValueInt64()) + } + if !data.BandwidthSettings.IsNull() { + body, _ = sjson.Set(body, "bandwidth.settings", data.BandwidthSettings.ValueString()) + } + if !data.ContentFilteringAllowedUrlPatternsSettings.IsNull() { + body, _ = sjson.Set(body, "contentFiltering.allowedUrlPatterns.settings", data.ContentFilteringAllowedUrlPatternsSettings.ValueString()) + } + if !data.ContentFilteringBlockedUrlCategoriesCategories.IsNull() { + var values []string + data.ContentFilteringBlockedUrlCategoriesCategories.ElementsAs(ctx, &values, false) + body, _ = sjson.Set(body, "contentFiltering.blockedUrlCategories.categories", values) + } + if !data.ContentFilteringBlockedUrlCategoriesSettings.IsNull() { + body, _ = sjson.Set(body, "contentFiltering.blockedUrlCategories.settings", data.ContentFilteringBlockedUrlCategoriesSettings.ValueString()) + } + if !data.ContentFilteringBlockedUrlPatternsPatterns.IsNull() { + var values []string + data.ContentFilteringBlockedUrlPatternsPatterns.ElementsAs(ctx, &values, false) + body, _ = sjson.Set(body, "contentFiltering.blockedUrlPatterns.patterns", values) + } + if !data.ContentFilteringBlockedUrlPatternsSettings.IsNull() { + body, _ = sjson.Set(body, "contentFiltering.blockedUrlPatterns.settings", data.ContentFilteringBlockedUrlPatternsSettings.ValueString()) + } return body } @@ -132,6 +168,46 @@ func (data *NetworkGroupPolicies) fromBody(ctx context.Context, res gjson.Result return true }) } + if value := res.Get("bandwidth.bandwidthLimits.limitDown"); value.Exists() { + data.BandwidthBandwidthLimitsLimitDown = types.Int64Value(value.Int()) + } else { + data.BandwidthBandwidthLimitsLimitDown = types.Int64Null() + } + if value := res.Get("bandwidth.bandwidthLimits.limitUp"); value.Exists() { + data.BandwidthBandwidthLimitsLimitUp = types.Int64Value(value.Int()) + } else { + data.BandwidthBandwidthLimitsLimitUp = types.Int64Null() + } + if value := res.Get("bandwidth.settings"); value.Exists() { + data.BandwidthSettings = types.StringValue(value.String()) + } else { + data.BandwidthSettings = types.StringNull() + } + if value := res.Get("contentFiltering.allowedUrlPatterns.settings"); value.Exists() { + data.ContentFilteringAllowedUrlPatternsSettings = types.StringValue(value.String()) + } else { + data.ContentFilteringAllowedUrlPatternsSettings = types.StringNull() + } + if value := res.Get("contentFiltering.blockedUrlCategories.categories"); value.Exists() { + data.ContentFilteringBlockedUrlCategoriesCategories = helpers.GetStringList(value.Array()) + } else { + data.ContentFilteringBlockedUrlCategoriesCategories = types.ListNull(types.StringType) + } + if value := res.Get("contentFiltering.blockedUrlCategories.settings"); value.Exists() { + data.ContentFilteringBlockedUrlCategoriesSettings = types.StringValue(value.String()) + } else { + data.ContentFilteringBlockedUrlCategoriesSettings = types.StringNull() + } + if value := res.Get("contentFiltering.blockedUrlPatterns.patterns"); value.Exists() { + data.ContentFilteringBlockedUrlPatternsPatterns = helpers.GetStringList(value.Array()) + } else { + data.ContentFilteringBlockedUrlPatternsPatterns = types.ListNull(types.StringType) + } + if value := res.Get("contentFiltering.blockedUrlPatterns.settings"); value.Exists() { + data.ContentFilteringBlockedUrlPatternsSettings = types.StringValue(value.String()) + } else { + data.ContentFilteringBlockedUrlPatternsSettings = types.StringNull() + } } // End of section. //template:end fromBody @@ -206,6 +282,46 @@ func (data *NetworkGroupPolicies) fromBodyPartial(ctx context.Context, res gjson } (*parent).BonjourForwardingRules[i] = data } + if value := res.Get("bandwidth.bandwidthLimits.limitDown"); value.Exists() && !data.BandwidthBandwidthLimitsLimitDown.IsNull() { + data.BandwidthBandwidthLimitsLimitDown = types.Int64Value(value.Int()) + } else { + data.BandwidthBandwidthLimitsLimitDown = types.Int64Null() + } + if value := res.Get("bandwidth.bandwidthLimits.limitUp"); value.Exists() && !data.BandwidthBandwidthLimitsLimitUp.IsNull() { + data.BandwidthBandwidthLimitsLimitUp = types.Int64Value(value.Int()) + } else { + data.BandwidthBandwidthLimitsLimitUp = types.Int64Null() + } + if value := res.Get("bandwidth.settings"); value.Exists() && !data.BandwidthSettings.IsNull() { + data.BandwidthSettings = types.StringValue(value.String()) + } else { + data.BandwidthSettings = types.StringNull() + } + if value := res.Get("contentFiltering.allowedUrlPatterns.settings"); value.Exists() && !data.ContentFilteringAllowedUrlPatternsSettings.IsNull() { + data.ContentFilteringAllowedUrlPatternsSettings = types.StringValue(value.String()) + } else { + data.ContentFilteringAllowedUrlPatternsSettings = types.StringNull() + } + if value := res.Get("contentFiltering.blockedUrlCategories.categories"); value.Exists() && !data.ContentFilteringBlockedUrlCategoriesCategories.IsNull() { + data.ContentFilteringBlockedUrlCategoriesCategories = helpers.GetStringList(value.Array()) + } else { + data.ContentFilteringBlockedUrlCategoriesCategories = types.ListNull(types.StringType) + } + if value := res.Get("contentFiltering.blockedUrlCategories.settings"); value.Exists() && !data.ContentFilteringBlockedUrlCategoriesSettings.IsNull() { + data.ContentFilteringBlockedUrlCategoriesSettings = types.StringValue(value.String()) + } else { + data.ContentFilteringBlockedUrlCategoriesSettings = types.StringNull() + } + if value := res.Get("contentFiltering.blockedUrlPatterns.patterns"); value.Exists() && !data.ContentFilteringBlockedUrlPatternsPatterns.IsNull() { + data.ContentFilteringBlockedUrlPatternsPatterns = helpers.GetStringList(value.Array()) + } else { + data.ContentFilteringBlockedUrlPatternsPatterns = types.ListNull(types.StringType) + } + if value := res.Get("contentFiltering.blockedUrlPatterns.settings"); value.Exists() && !data.ContentFilteringBlockedUrlPatternsSettings.IsNull() { + data.ContentFilteringBlockedUrlPatternsSettings = types.StringValue(value.String()) + } else { + data.ContentFilteringBlockedUrlPatternsSettings = types.StringNull() + } } // End of section. //template:end fromBodyPartial diff --git a/internal/provider/resource_meraki_network_group_policies.go b/internal/provider/resource_meraki_network_group_policies.go index 11db57f..172d9b1 100644 --- a/internal/provider/resource_meraki_network_group_policies.go +++ b/internal/provider/resource_meraki_network_group_policies.go @@ -106,6 +106,40 @@ func (r *NetworkGroupPoliciesResource) Schema(ctx context.Context, req resource. }, }, }, + "bandwidth_bandwidth_limits_limit_down": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, + "bandwidth_bandwidth_limits_limit_up": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, + "bandwidth_settings": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, + "content_filtering_allowed_url_patterns_settings": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, + "content_filtering_blocked_url_categories_categories": schema.ListAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + ElementType: types.StringType, + Optional: true, + }, + "content_filtering_blocked_url_categories_settings": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, + "content_filtering_blocked_url_patterns_patterns": schema.ListAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + ElementType: types.StringType, + Optional: true, + }, + "content_filtering_blocked_url_patterns_settings": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, }, } } diff --git a/internal/provider/resource_meraki_network_group_policies_test.go b/internal/provider/resource_meraki_network_group_policies_test.go index 11c83b1..dc77c11 100644 --- a/internal/provider/resource_meraki_network_group_policies_test.go +++ b/internal/provider/resource_meraki_network_group_policies_test.go @@ -36,6 +36,9 @@ func TestAccMerakiNetworkGroupPolicies(t *testing.T) { checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policies.test", "bonjour_forwarding_rules.0.description", "a simple bonjour rule")) checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policies.test", "bonjour_forwarding_rules.0.services.0", "All Services")) checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policies.test", "bonjour_forwarding_rules.0.vlan_id", "2")) + checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policies.test", "bandwidth_bandwidth_limits_limit_down", "1000000")) + checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policies.test", "bandwidth_bandwidth_limits_limit_up", "1000000")) + checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policies.test", "bandwidth_settings", "custom")) var steps []resource.TestStep if os.Getenv("SKIP_MINIMUM_TEST") == "" { @@ -97,6 +100,9 @@ func testAccMerakiNetworkGroupPoliciesConfig_all() string { config += ` services = ["All Services"]` + "\n" config += ` vlan_id = "2"` + "\n" config += ` }]` + "\n" + config += ` bandwidth_bandwidth_limits_limit_down = 1000000` + "\n" + config += ` bandwidth_bandwidth_limits_limit_up = 1000000` + "\n" + config += ` bandwidth_settings = "custom"` + "\n" config += `}` + "\n" return config } diff --git a/openapiconverter/main.go b/openapiconverter/main.go new file mode 100644 index 0000000..99bd0d6 --- /dev/null +++ b/openapiconverter/main.go @@ -0,0 +1,104 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "gopkg.in/yaml.v2" +) + +const usage = ` +Usage: openapi-schema-parser + +Arguments: + openapi_spec Path to the file containing the OpenAPI specification (YAML or JSON) + schema_path Path to the schema within the OpenAPI specification file, using spaces as separators + +Example: + openapi-schema-parser ./api-spec.yaml "# components schemas User" + +Note: The schema_path should start with '#' and use spaces as separators. +` + +func main() { + if len(os.Args) < 3 { + fmt.Println("Error: Insufficient number of arguments") + fmt.Println(usage) + os.Exit(1) + } + + specPath := os.Args[1] + schemaPath := strings.Join(os.Args[2:], " ") // Join all remaining arguments + + if !strings.HasPrefix(schemaPath, "# ") { + fmt.Println("Error: schema_path must start with '# '") + fmt.Println(usage) + os.Exit(1) + } + + specData, err := os.ReadFile(specPath) + if err != nil { + fmt.Printf("Error reading OpenAPI spec file: %v\n", err) + os.Exit(1) + } + + var spec interface{} + if strings.HasSuffix(specPath, ".json") { + err = json.Unmarshal(specData, &spec) + } else { + err = yaml.Unmarshal(specData, &spec) + } + if err != nil { + fmt.Printf("Error parsing OpenAPI spec: %v\n", err) + os.Exit(1) + } + + schema, err := getSchemaFromPath(spec, schemaPath) + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + // jsonSchema, err := json.MarshalIndent(schema, "", " ") + // if err != nil { + // fmt.Printf("Error converting schema to JSON: %v\n", err) + // os.Exit(1) + // } + + traverseProperties(schema.(map[string]interface{})["schema"].(map[string]interface{})["properties"].(map[string]interface{}), []string{}) +} + +func traverseProperties(m map[string]interface{}, path []string) { + for propName, v := range m { + propMap := v.(map[string]interface{}) + fmt.Println(propName, propMap["type"]) + } +} + +func getSchemaFromPath(spec interface{}, path string) (interface{}, error) { + components := strings.Split(strings.TrimPrefix(path, "# "), " ") + current := spec + + for _, component := range components { + switch v := current.(type) { + case map[interface{}]interface{}: + if next, ok := v[component]; ok { + current = next + } else { + return nil, fmt.Errorf("path component '%s' not found", component) + } + case map[string]interface{}: + if next, ok := v[component]; ok { + current = next + } else { + return nil, fmt.Errorf("path component '%s' not found", component) + } + default: + return nil, fmt.Errorf("invalid path: '%s' is not an object", component) + } + } + + return current, nil +} diff --git a/openapiconverter/openapiconverter b/openapiconverter/openapiconverter new file mode 100755 index 0000000..b4a0942 Binary files /dev/null and b/openapiconverter/openapiconverter differ