From c95fc8e922ad75f15f9d58ebcb7c1e37b2600a3f Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Fri, 19 Jul 2024 14:43:56 +0200 Subject: [PATCH 1/8] added GenerateQueryParamString templating helper function --- gen/generator.go | 209 ++++++++++++++++++++++++++------------ gen/templates/resource.go | 13 ++- 2 files changed, 151 insertions(+), 71 deletions(-) diff --git a/gen/generator.go b/gen/generator.go index a522c7c8..31b1e94a 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -137,44 +137,52 @@ type YamlConfig struct { } type YamlConfigAttribute struct { - ModelName string `yaml:"model_name"` - ResponseModelName string `yaml:"response_model_name"` - TfName string `yaml:"tf_name"` - Type string `yaml:"type"` - ElementType string `yaml:"element_type"` - DataPath string `yaml:"data_path"` - ResponseDataPath string `yaml:"response_data_path"` - Id bool `yaml:"id"` - MatchId bool `yaml:"match_id"` - Reference bool `yaml:"reference"` - RequiresReplace bool `yaml:"requires_replace"` - QueryParam bool `yaml:"query_param"` - DeleteQueryParam bool `yaml:"delete_query_param"` - DataSourceQuery bool `yaml:"data_source_query"` - Mandatory bool `yaml:"mandatory"` - WriteOnly bool `yaml:"write_only"` - ExcludeFromPut bool `yaml:"exclude_from_put"` - 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"` - 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"` - ValueCondition string `yaml:"value_condition"` - TestValue string `yaml:"test_value"` - MinimumTestValue string `yaml:"minimum_test_value"` - TestTags []string `yaml:"test_tags"` - Attributes []YamlConfigAttribute `yaml:"attributes"` + ModelName string `yaml:"model_name"` + ResponseModelName string `yaml:"response_model_name"` + TfName string `yaml:"tf_name"` + Type string `yaml:"type"` + ElementType string `yaml:"element_type"` + DataPath string `yaml:"data_path"` + ResponseDataPath string `yaml:"response_data_path"` + Id bool `yaml:"id"` + MatchId bool `yaml:"match_id"` + Reference bool `yaml:"reference"` + RequiresReplace bool `yaml:"requires_replace"` + QueryParam bool `yaml:"query_param"` + DeleteQueryParam bool `yaml:"delete_query_param"` + GetQueryParam bool `yaml:"get_query_param"` + PutQueryParam bool `yaml:"put_query_param"` + PostQueryParam bool `yaml:"post_query_param"` + QueryParamName string `yaml:"query_param_name"` + DeleteQueryParamName string `yaml:"delete_query_param_name"` + GetQueryParamName string `yaml:"get_query_param_name"` + PutQueryParamName string `yaml:"put_query_param_name"` + PostQueryParamName string `yaml:"post_query_param_name"` + DataSourceQuery bool `yaml:"data_source_query"` + Mandatory bool `yaml:"mandatory"` + WriteOnly bool `yaml:"write_only"` + ExcludeFromPut bool `yaml:"exclude_from_put"` + 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"` + 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"` + ValueCondition string `yaml:"value_condition"` + TestValue string `yaml:"test_value"` + MinimumTestValue string `yaml:"minimum_test_value"` + TestTags []string `yaml:"test_tags"` + Attributes []YamlConfigAttribute `yaml:"attributes"` } // Templating helper function to convert TF name to GO name @@ -416,6 +424,72 @@ func IsNestedSet(attribute YamlConfigAttribute) bool { return false } +// Templating helper function to return a query parameter string based on the HTTP method input source (plan, state) and provided attributes. +// By default, it uses attr.QueryParam if specified, and for method-specific parameters like DeleteQueryParamName, GetQueryParamName, etc., +// it uses those if available for the respective HTTP method. If no specific query parameter is provided for a method, it defaults to attr.ModelName. +// Returns the constructed query parameter string. +func GenerateQueryParamString(method string, inputSource string, attributes []YamlConfigAttribute) string { + var params []string + first := true + + for _, attr := range attributes { + var queryParamName string + includeParam := false + + // Determine the appropriate query parameter name based on the method + switch method { + case "DELETE": + if attr.DeleteQueryParam { + queryParamName = attr.DeleteQueryParamName + includeParam = true + } + case "GET": + if attr.GetQueryParam { + queryParamName = attr.GetQueryParamName + includeParam = true + } + case "POST": + if attr.PostQueryParam { + queryParamName = attr.PostQueryParamName + includeParam = true + } + case "PUT": + if attr.PutQueryParam { + queryParamName = attr.PutQueryParamName + includeParam = true + } + } + + // If no method-specific query parameter is set, fall back to default query parameter + if !includeParam && attr.QueryParam { + queryParamName = attr.QueryParamName + includeParam = true + } + + // Use model name if queryParamName is still empty + if queryParamName == "" { + queryParamName = attr.ModelName + } + + // Construct the query parameter string if includeParam is true + if includeParam { + if first { + params = append(params, `"?`+queryParamName+`=" + url.QueryEscape(`+inputSource+`.`+ToGoName(attr.TfName)+`.Value`+attr.Type+`())`) + first = false + } else { + params = append(params, `"&`+queryParamName+`=" + url.QueryEscape(`+inputSource+`.`+ToGoName(attr.TfName)+`.Value`+attr.Type+`())`) + } + } + } + + // Return the appropriate string based on whether params is empty or not + if len(params) == 0 { + return "" + } else { + return strings.Join(params, "+") + } +} + // Templating helper function to return a list of import attributes func ImportAttributes(config YamlConfig) []YamlConfigAttribute { r := []YamlConfigAttribute{} @@ -442,35 +516,36 @@ func Subtract(a, b int) int { // Map of templating functions var functions = template.FuncMap{ - "toGoName": ToGoName, - "camelCase": CamelCase, - "strContains": strings.Contains, - "snakeCase": SnakeCase, - "sprintf": fmt.Sprintf, - "toLower": strings.ToLower, - "path": BuildPath, - "hasId": HasId, - "hasReference": HasReference, - "hasQueryParam": HasQueryParam, - "hasDeleteQueryParam": HasDeleteQueryParam, - "getId": GetId, - "getMatchId": GetMatchId, - "getQueryParam": GetQueryParam, - "getDeleteQueryParam": GetDeleteQueryParam, - "hasDataSourceQuery": HasDataSourceQuery, - "firstPathElement": FirstPathElement, - "remainingPathElements": RemainingPathElements, - "getFromAllPath": GetFromAllPath, - "isListSet": IsListSet, - "isList": IsList, - "isSet": IsSet, - "isStringListSet": IsStringListSet, - "isInt64ListSet": IsInt64ListSet, - "isNestedListSet": IsNestedListSet, - "isNestedList": IsNestedList, - "isNestedSet": IsNestedSet, - "importAttributes": ImportAttributes, - "subtract": Subtract, + "toGoName": ToGoName, + "camelCase": CamelCase, + "strContains": strings.Contains, + "snakeCase": SnakeCase, + "sprintf": fmt.Sprintf, + "toLower": strings.ToLower, + "path": BuildPath, + "hasId": HasId, + "hasReference": HasReference, + "hasQueryParam": HasQueryParam, + "hasDeleteQueryParam": HasDeleteQueryParam, + "generateQueryParamString": GenerateQueryParamString, + "getId": GetId, + "getMatchId": GetMatchId, + "getQueryParam": GetQueryParam, + "getDeleteQueryParam": GetDeleteQueryParam, + "hasDataSourceQuery": HasDataSourceQuery, + "firstPathElement": FirstPathElement, + "remainingPathElements": RemainingPathElements, + "getFromAllPath": GetFromAllPath, + "isListSet": IsListSet, + "isList": IsList, + "isSet": IsSet, + "isStringListSet": IsStringListSet, + "isInt64ListSet": IsInt64ListSet, + "isNestedListSet": IsNestedListSet, + "isNestedList": IsNestedList, + "isNestedSet": IsNestedSet, + "importAttributes": ImportAttributes, + "subtract": Subtract, } func augmentAttribute(attr *YamlConfigAttribute) { diff --git a/gen/templates/resource.go b/gen/templates/resource.go index 89f43134..62d0117c 100644 --- a/gen/templates/resource.go +++ b/gen/templates/resource.go @@ -446,8 +446,10 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C {{- $id := getMatchId .Attributes}} params = "" {{- if hasQueryParam .Attributes}} - {{- $queryParam := getQueryParam .Attributes}} - params += "?{{$queryParam.ModelName}}=" + url.QueryEscape(plan.{{toGoName $queryParam.TfName}}.Value{{$queryParam.Type}}()) + {{- $queryParams := generateQueryParamString "GET" "plan" .Attributes }} + {{- if $queryParams }} + params += {{$queryParams}} + {{- end}} {{- end}} res, err = r.client.Get({{if .GetRestEndpoint}}"{{.GetRestEndpoint}}"{{else}}plan.getPath(){{end}} + params) if err != nil { @@ -483,11 +485,14 @@ func (r *{{camelCase .Name}}Resource) Read(ctx context.Context, req resource.Rea {{- if not .NoRead}} params := "" + {{- $queryParams := generateQueryParamString "GET" "state" .Attributes }} + {{- if .IdQueryParam}} params += "?{{.IdQueryParam}}=" + url.QueryEscape(state.Id.ValueString()) {{- else if and (hasQueryParam .Attributes) (not .GetRequiresId)}} - {{- $queryParam := getQueryParam .Attributes}} - params += "?{{$queryParam.ModelName}}=" + url.QueryEscape(state.{{toGoName $queryParam.TfName}}.Value{{$queryParam.Type}}()) + {{- if $queryParams }} + params += {{$queryParams}} + {{- end}} {{- else if and (not .GetNoId) (not .GetFromAll)}} params += "/" + url.QueryEscape(state.Id.ValueString()) {{- end}} From b853a94e84b58c6241cfd795aef4842f9f53c138 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Fri, 19 Jul 2024 15:00:48 +0200 Subject: [PATCH 2/8] use create_query_path instead of query_param in Create and Update function (api/) --- gen/definitions/assign_credentials.yaml | 1 + gen/definitions/ip_pool_reservation.yaml | 1 + gen/definitions/network.yaml | 1 + gen/generator.go | 23 +++++++++++++++++++++++ gen/schema/schema.yaml | 9 +++++++++ gen/templates/resource.go | 6 +++--- 6 files changed, 38 insertions(+), 3 deletions(-) diff --git a/gen/definitions/assign_credentials.yaml b/gen/definitions/assign_credentials.yaml index bd231bff..07bf3e52 100644 --- a/gen/definitions/assign_credentials.yaml +++ b/gen/definitions/assign_credentials.yaml @@ -13,6 +13,7 @@ attributes: type: String query_param: true id: true + create_query_path: true description: The site ID example: 5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1 test_value: catalystcenter_area.test.id diff --git a/gen/definitions/ip_pool_reservation.yaml b/gen/definitions/ip_pool_reservation.yaml index e78e1fef..782d0dc1 100644 --- a/gen/definitions/ip_pool_reservation.yaml +++ b/gen/definitions/ip_pool_reservation.yaml @@ -9,6 +9,7 @@ attributes: - model_name: siteId type: String query_param: true + create_query_path: true description: The site ID example: 5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1 test_value: catalystcenter_area.test.id diff --git a/gen/definitions/network.yaml b/gen/definitions/network.yaml index b9dc5950..cb27c1b9 100644 --- a/gen/definitions/network.yaml +++ b/gen/definitions/network.yaml @@ -9,6 +9,7 @@ attributes: - model_name: siteId type: String query_param: true + create_query_path: true id: true description: The site ID example: 5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1 diff --git a/gen/generator.go b/gen/generator.go index 31b1e94a..2409a322 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -158,6 +158,7 @@ type YamlConfigAttribute struct { GetQueryParamName string `yaml:"get_query_param_name"` PutQueryParamName string `yaml:"put_query_param_name"` PostQueryParamName string `yaml:"post_query_param_name"` + CreateQueryPath bool `yaml:"create_query_path"` DataSourceQuery bool `yaml:"data_source_query"` Mandatory bool `yaml:"mandatory"` WriteOnly bool `yaml:"write_only"` @@ -424,6 +425,26 @@ func IsNestedSet(attribute YamlConfigAttribute) bool { return false } +// Templating helper function to return true if create query path included in attributes +func HasCreateQueryPath(attributes []YamlConfigAttribute) bool { + for _, attr := range attributes { + if attr.CreateQueryPath { + return true + } + } + return false +} + +// Templating helper function to return the create query path attribute +func GetCreateQueryPath(attributes []YamlConfigAttribute) YamlConfigAttribute { + for _, attr := range attributes { + if attr.CreateQueryPath { + return attr + } + } + return YamlConfigAttribute{} +} + // Templating helper function to return a query parameter string based on the HTTP method input source (plan, state) and provided attributes. // By default, it uses attr.QueryParam if specified, and for method-specific parameters like DeleteQueryParamName, GetQueryParamName, etc., // it uses those if available for the respective HTTP method. If no specific query parameter is provided for a method, it defaults to attr.ModelName. @@ -530,6 +551,8 @@ var functions = template.FuncMap{ "generateQueryParamString": GenerateQueryParamString, "getId": GetId, "getMatchId": GetMatchId, + "hasCreateQueryPath": HasCreateQueryPath, + "getCreateQueryPath": GetCreateQueryPath, "getQueryParam": GetQueryParam, "getDeleteQueryParam": GetDeleteQueryParam, "hasDataSourceQuery": HasDataSourceQuery, diff --git a/gen/schema/schema.yaml b/gen/schema/schema.yaml index 7fb223d3..4f0d5b12 100644 --- a/gen/schema/schema.yaml +++ b/gen/schema/schema.yaml @@ -51,8 +51,17 @@ attribute: match_id: bool(required=False) # Set to true if the attribute is used to identify the right element using "get_from_all" option reference: bool(required=False) # Set to true if the attribute is a reference being used in the path (URL) of the REST endpoint requires_replace: bool(required=False) # Set to true if the attribute update forces Terraform to destroy/recreate the entire resource + create_query_path: bool(required=False) # Set to true if the attribute is a query path and being used for POST or PUT request query_param: bool(required=False) # Set to true if the attribute is a query parameter and being used for GET, POST and PUT requests + query_param_name: str(required=False) # Name of query param used in request, by default derived from model_name delete_query_param: bool(required=False) # Set to true if the attribute is a query parameter and being used for DELETE request. Note: This cannot be used in conjunction with 'delete_id_query_param' + delete_query_param_name: str(required=False) # Name of query param used in DELETE request, by default derived from model_name + post_query_param: bool(required=False) # Set to true if the attribute is a query parameter and being used for POST request + post_query_param_name: str(required=False) # Name of query param used in POST request, by default derived from model_name + put_query_param: bool(required=False) # Set to true if the attribute is a query parameter and being used for PUT request + put_query_param_name: str(required=False) # Name of query param used in PUT request, by default derived from model_name + get_query_param: bool(required=False) # Set to true if the attribute is a query parameter and being used for GET request + get_query_param_name: str(required=False) # Name of query param used in GET request, by default derived from model_name data_source_query: bool(required=False) # Set to true if the attribute is an alternative query parameter for the data source mandatory: bool(required=False) # Set to true if the attribute is mandatory write_only: bool(required=False) # Set to true if the attribute is write-only, meaning we cannot read the value diff --git a/gen/templates/resource.go b/gen/templates/resource.go index 62d0117c..fe492663 100644 --- a/gen/templates/resource.go +++ b/gen/templates/resource.go @@ -425,9 +425,9 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C body := plan.toBody(ctx, {{camelCase .Name}}{}) params := "" - {{- if hasQueryParam .Attributes}} - {{- $queryParam := getQueryParam .Attributes}} - params += "/" + url.QueryEscape(plan.{{toGoName $queryParam.TfName}}.Value{{$queryParam.Type}}()) + {{- if hasCreateQueryPath .Attributes}} + {{- $createQueryPath := getCreateQueryPath .Attributes}} + params += "/" + url.QueryEscape(plan.{{toGoName $createQueryPath.TfName}}.Value{{$createQueryPath.Type}}()) {{- end}} {{- if .PutCreate}} res, err := r.client.Put(plan.getPath() + params, body {{- if .MaxAsyncWaitTime }}, func(r *cc.Req) { r.MaxAsyncWaitTime={{.MaxAsyncWaitTime}} }{{end}}) From d0e2c6f16173b751b1adb11702bc0281102d4944 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Sat, 20 Jul 2024 21:40:25 +0200 Subject: [PATCH 3/8] add query_param_no_body and data_source_no_id attributes query_param_no_body: bool(required=False) # Set to true if the attribute is a query parameter and not part of the body data_source_no_id: bool(required=False) # Set to true if id in data source should be optional --- gen/definitions/image.yaml | 1 + gen/generator.go | 2 ++ gen/schema/schema.yaml | 2 ++ gen/templates/data-source.tf | 2 ++ gen/templates/data_source.go | 10 +++++++--- gen/templates/model.go | 14 +++++++++++--- gen/templates/resource.go | 10 +++++++--- 7 files changed, 32 insertions(+), 9 deletions(-) diff --git a/gen/definitions/image.yaml b/gen/definitions/image.yaml index b9302fbd..b734fc81 100644 --- a/gen/definitions/image.yaml +++ b/gen/definitions/image.yaml @@ -44,6 +44,7 @@ attributes: mandatory: true id: true query_param: true + query_param_no_body: true response_data_path: response.0.name data_path: '0' requires_replace: true diff --git a/gen/generator.go b/gen/generator.go index 2409a322..59e55da9 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -106,6 +106,7 @@ type YamlConfig struct { GetRequiresId bool `yaml:"get_requires_id"` GetExtraQueryParams string `yaml:"get_extra_query_params"` NoDelete bool `yaml:"no_delete"` + DataSourceNoId bool `yaml:"data_source_no_id"` DeleteNoId bool `yaml:"delete_no_id"` NoUpdate bool `yaml:"no_update"` NoRead bool `yaml:"no_read"` @@ -160,6 +161,7 @@ type YamlConfigAttribute struct { PostQueryParamName string `yaml:"post_query_param_name"` CreateQueryPath bool `yaml:"create_query_path"` DataSourceQuery bool `yaml:"data_source_query"` + QueryParamNoBody bool `yaml:"query_param_no_body"` Mandatory bool `yaml:"mandatory"` WriteOnly bool `yaml:"write_only"` ExcludeFromPut bool `yaml:"exclude_from_put"` diff --git a/gen/schema/schema.yaml b/gen/schema/schema.yaml index 4f0d5b12..98f3019f 100644 --- a/gen/schema/schema.yaml +++ b/gen/schema/schema.yaml @@ -7,6 +7,7 @@ get_rest_endpoint: str(required=False) # Override GET REST endpoint path put_rest_endpoint: str(required=False) # Override PUT REST endpoint path delete_rest_endpoint: str(required=False) # Override DELETE REST endpoint path get_no_id: bool(required=False) # Set to true if the GET request does not require an ID +data_source_no_id: bool(required=False) # Set to true if id in data source should be optional get_from_all: bool(required=False) # Set to true if GET does not support querying individual objects get_requires_id: bool(required=False) # Set to true if the GET request requires an ID in the URL path get_extra_query_params: str(required=False) # Additional query parameters for GET request @@ -62,6 +63,7 @@ attribute: put_query_param_name: str(required=False) # Name of query param used in PUT request, by default derived from model_name get_query_param: bool(required=False) # Set to true if the attribute is a query parameter and being used for GET request get_query_param_name: str(required=False) # Name of query param used in GET request, by default derived from model_name + query_param_no_body: bool(required=False) # Set to true if the attribute is a query parameter and not part of the body data_source_query: bool(required=False) # Set to true if the attribute is an alternative query parameter for the data source mandatory: bool(required=False) # Set to true if the attribute is mandatory write_only: bool(required=False) # Set to true if the attribute is write-only, meaning we cannot read the value diff --git a/gen/templates/data-source.tf b/gen/templates/data-source.tf index 59bcf763..9bc8b0de 100644 --- a/gen/templates/data-source.tf +++ b/gen/templates/data-source.tf @@ -1,5 +1,7 @@ data "catalystcenter_{{snakeCase .Name}}" "example" { + {{- if not .DataSourceNoId}} id = "{{$id := false}}{{range .Attributes}}{{if .Id}}{{$id = true}}{{.Example}}{{end}}{{end}}{{if not $id}}76d24097-41c4-4558-a4d0-a8c07ac08470{{end}}" + {{- end}} {{- range .Attributes}} {{- if or .Reference .QueryParam}} {{.TfName}} = {{if eq .Type "String"}}"{{.Example}}"{{else if isStringListSet .}}["{{.Example}}"]{{else if isInt64ListSet .}}[{{.Example}}]{{else}}{{.Example}}{{end}} diff --git a/gen/templates/data_source.go b/gen/templates/data_source.go index 2d3dea02..a0147202 100644 --- a/gen/templates/data_source.go +++ b/gen/templates/data_source.go @@ -65,7 +65,9 @@ func (d *{{camelCase .Name}}DataSource) Schema(ctx context.Context, req datasour Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ MarkdownDescription: "The id of the object", - {{- if not $dataSourceQuery}} + {{- if .DataSourceNoId}} + Computed: true, + {{- else if not $dataSourceQuery}} Required: true, {{- else}} Optional: true, @@ -227,8 +229,10 @@ func (d *{{camelCase .Name}}DataSource) Read(ctx context.Context, req datasource {{- if .IdQueryParam}} params += "?{{.IdQueryParam}}=" + url.QueryEscape(config.Id.ValueString()) {{- else if and (hasQueryParam .Attributes) (not .GetRequiresId)}} - {{- $queryParam := getQueryParam .Attributes}} - params += "?{{$queryParam.ModelName}}=" + url.QueryEscape(config.{{toGoName $queryParam.TfName}}.Value{{$queryParam.Type}}()) + {{- $queryParams := generateQueryParamString "GET" "config" .Attributes }} + {{- if $queryParams }} + params += {{$queryParams}} + {{- end}} {{- else if and (not .GetNoId) (not .GetFromAll)}} params += "/" + url.QueryEscape(config.Id.ValueString()) {{- end}} diff --git a/gen/templates/model.go b/gen/templates/model.go index e4554464..7c9d0fc3 100644 --- a/gen/templates/model.go +++ b/gen/templates/model.go @@ -168,7 +168,7 @@ func (data {{camelCase .Name}}) toBody(ctx context.Context, state {{camelCase .N {{- else}} body, _ = sjson.Set(body, "{{if .DataPath}}{{.DataPath}}.{{end}}{{.ModelName}}", {{if eq .Type "String"}}"{{end}}{{.Value}}{{if eq .Type "String"}}"{{end}}) {{- end}} - {{- else if and (not .Reference) (not .QueryParam)}} + {{- else if and (not .Reference) (not .CreateQueryPath) (not .QueryParamNoBody)}} {{- if or (eq .Type "String") (eq .Type "Int64") (eq .Type "Float64") (eq .Type "Bool")}} if !data.{{toGoName .TfName}}.IsNull() {{if .ExcludeFromPut}}&& put == false{{end}} { body, _ = sjson.Set(body, "{{if .DataPath}}{{.DataPath}}.{{end}}{{.ModelName}}", data.{{toGoName .TfName}}.Value{{.Type}}()) @@ -305,8 +305,16 @@ func (data {{camelCase .Name}}) toBody(ctx context.Context, state {{camelCase .N // Section below is generated&owned by "gen/generator.go". //template:begin fromBody func (data *{{camelCase .Name}}) fromBody(ctx context.Context, res gjson.Result) { + {{- if .DataSourceNoId}} + // Retrieve the 'id' attribute, if Data Source doesn't require id + if value := res.Get("{{if .IdFromQueryPath}}{{.IdFromQueryPath}}.{{if .IdFromQueryPathAttribute}}{{.IdFromQueryPathAttribute}}{{else}}id{{end}}{{end}}"); value.Exists() { + data.Id = types.StringValue(value.String()) + } else { + data.Id = types.StringNull() + } + {{- end}} {{- range .Attributes}} - {{- if and (not .Value) (not .WriteOnly) (not .Reference) (not .QueryParam)}} + {{- if and (not .Value) (not .WriteOnly) (not .Reference) (not .CreateQueryPath) (not .QueryParamNoBody)}} {{- $cname := toGoName .TfName}} {{- if or (eq .Type "String") (eq .Type "Int64") (eq .Type "Float64") (eq .Type "Bool")}} if value := res.Get("{{if .ResponseDataPath}}{{.ResponseDataPath}}{{else}}{{if .DataPath}}{{.DataPath}}.{{end}}{{.ModelName}}{{end}}"); value.Exists() { @@ -448,7 +456,7 @@ func (data *{{camelCase .Name}}) fromBody(ctx context.Context, res gjson.Result) // Section below is generated&owned by "gen/generator.go". //template:begin updateFromBody func (data *{{camelCase .Name}}) updateFromBody(ctx context.Context, res gjson.Result) { {{- range .Attributes}} - {{- if and (not .Value) (not .WriteOnly) (not .Reference) (not .QueryParam)}} + {{- if and (not .Value) (not .WriteOnly) (not .Reference) (not .CreateQueryPath) (not .QueryParamNoBody)}} {{- if or (eq .Type "String") (eq .Type "Int64") (eq .Type "Float64") (eq .Type "Bool")}} if value := res.Get("{{if .ResponseDataPath}}{{.ResponseDataPath}}{{else}}{{if .DataPath}}{{.DataPath}}.{{end}}{{.ModelName}}{{end}}"); value.Exists() && !data.{{toGoName .TfName}}.IsNull() { data.{{toGoName .TfName}} = types.{{.Type}}Value(value.{{if eq .Type "Int64"}}Int{{else if eq .Type "Float64"}}Float{{else}}{{.Type}}{{end}}()) diff --git a/gen/templates/resource.go b/gen/templates/resource.go index fe492663..da28f6a6 100644 --- a/gen/templates/resource.go +++ b/gen/templates/resource.go @@ -456,7 +456,11 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) return } + {{- if and .IdFromQueryPathAttribute .IdFromQueryPath (not .GetExtraQueryParams) (not .GetFromAll) }} + plan.Id = types.StringValue(res.Get("{{if .IdFromQueryPath}}{{.IdFromQueryPath}}.{{end}}{{.IdFromQueryPathAttribute}}").String()) + {{- else}} plan.Id = types.StringValue(res.Get("{{.IdFromQueryPath}}.#({{if $id.ResponseModelName}}{{$id.ResponseModelName}}{{else}}{{$id.ModelName}}{{end}}==\""+ plan.{{toGoName $id.TfName}}.Value{{$id.Type}}() +"\").{{if .IdFromQueryPathAttribute}}{{.IdFromQueryPathAttribute}}{{else}}id{{end}}").String()) + {{- end}} {{- /* If we have an id attribute we will use that as id */}} {{- else if hasId .Attributes}} {{- $id := getId .Attributes}} @@ -554,9 +558,9 @@ func (r *{{camelCase .Name}}Resource) Update(ctx context.Context, req resource.U body := plan.toBody(ctx, state) params := "" - {{- if hasQueryParam .Attributes}} - {{- $queryParam := getQueryParam .Attributes}} - params += "/" + url.QueryEscape(plan.{{toGoName $queryParam.TfName}}.Value{{$queryParam.Type}}()) + {{- if hasCreateQueryPath .Attributes}} + {{- $createQueryPath := getCreateQueryPath .Attributes}} + params += "/" + url.QueryEscape(plan.{{toGoName $createQueryPath.TfName}}.Value{{$createQueryPath.Type}}()) {{- end}} {{- if .PutIdQueryParam}} params += "?{{.PutIdQueryParam}}=" + url.QueryEscape(plan.Id.ValueString()) From c1381fde37b04cf10994d6084cdca30eee1d54bd Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Sat, 20 Jul 2024 22:08:34 +0200 Subject: [PATCH 4/8] add import_no_id attribute, if import does not require and ID --- gen/generator.go | 3 ++- gen/schema/schema.yaml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gen/generator.go b/gen/generator.go index 59e55da9..891522f8 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -111,6 +111,7 @@ type YamlConfig struct { NoUpdate bool `yaml:"no_update"` NoRead bool `yaml:"no_read"` NoImport bool `yaml:"no_import"` + ImportNoId bool `yaml:"import_no_id"` PostUpdate bool `yaml:"post_update"` PutCreate bool `yaml:"put_create"` RootList bool `yaml:"root_list"` @@ -521,7 +522,7 @@ func ImportAttributes(config YamlConfig) []YamlConfigAttribute { r = append(r, attr) } } - if !config.IdFromAttribute { + if !config.IdFromAttribute && !config.ImportNoId { attr := YamlConfigAttribute{} attr.ModelName = "id" attr.TfName = "id" diff --git a/gen/schema/schema.yaml b/gen/schema/schema.yaml index 98f3019f..43e167f6 100644 --- a/gen/schema/schema.yaml +++ b/gen/schema/schema.yaml @@ -16,6 +16,7 @@ delete_no_id: bool(required=False) # Set to true if the DELETE request does not no_update: bool(required=False) # Set to true if the PUT request is not supported no_read: bool(required=False) # Set to true if the GET request is not supported no_import: bool(required=False) # Set to true if the resource does not support importing +import_no_id: bool(required=False) # Set to true if import does not require an ID post_update: bool(required=False) # Set to true if the POST request is used for update put_create: bool(required=False) # Set to true if the PUT request is used for create root_list: bool(required=False) # Set to true if the root element of the data structure is a list From a98ad26ddfed153261e15001e23386195a7c6705 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Sat, 20 Jul 2024 22:24:06 +0200 Subject: [PATCH 5/8] add anycast_gateway --- docs/data-sources/anycast_gateway.md | 48 +++ docs/resources/anycast_gateway.md | 72 ++++ .../data-source.tf | 5 + .../catalystcenter_anycast_gateway/import.sh | 1 + .../resource.tf | 15 + gen/definitions/anycast_gateway.yaml | 186 +++++++++ ...a_source_catalystcenter_anycast_gateway.go | 175 +++++++++ ...rce_catalystcenter_anycast_gateway_test.go | 126 ++++++ .../model_catalystcenter_anycast_gateway.go | 366 ++++++++++++++++++ internal/provider/provider.go | 2 + ...resource_catalystcenter_anycast_gateway.go | 342 ++++++++++++++++ ...rce_catalystcenter_anycast_gateway_test.go | 145 +++++++ 12 files changed, 1483 insertions(+) create mode 100644 docs/data-sources/anycast_gateway.md create mode 100644 docs/resources/anycast_gateway.md create mode 100644 examples/data-sources/catalystcenter_anycast_gateway/data-source.tf create mode 100644 examples/resources/catalystcenter_anycast_gateway/import.sh create mode 100644 examples/resources/catalystcenter_anycast_gateway/resource.tf create mode 100644 gen/definitions/anycast_gateway.yaml create mode 100644 internal/provider/data_source_catalystcenter_anycast_gateway.go create mode 100644 internal/provider/data_source_catalystcenter_anycast_gateway_test.go create mode 100644 internal/provider/model_catalystcenter_anycast_gateway.go create mode 100644 internal/provider/resource_catalystcenter_anycast_gateway.go create mode 100644 internal/provider/resource_catalystcenter_anycast_gateway_test.go diff --git a/docs/data-sources/anycast_gateway.md b/docs/data-sources/anycast_gateway.md new file mode 100644 index 00000000..7ec0a291 --- /dev/null +++ b/docs/data-sources/anycast_gateway.md @@ -0,0 +1,48 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_anycast_gateway Data Source - terraform-provider-catalystcenter" +subcategory: "SDA" +description: |- + This data source can read the Anycast Gateway. +--- + +# catalystcenter_anycast_gateway (Data Source) + +This data source can read the Anycast Gateway. + +## Example Usage + +```terraform +data "catalystcenter_anycast_gateway" "example" { + fabric_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + virtual_network_name = "SDA_VN1" + ip_pool_name = "MyRes1" +} +``` + + +## Schema + +### Required + +- `fabric_id` (String) ID of the fabric to contain this anycast gateway +- `ip_pool_name` (String) Name of the IP pool associated with the anycast gateway +- `virtual_network_name` (String) Name of the layer 3 virtual network associated with the anycast gateway. the virtual network must have already been added to the site before creating an anycast gateway with it + +### Read-Only + +- `auto_generate_vlan_name` (Boolean) This field cannot be true when vlanName is provided. the vlanName will be generated as ipPoolGroupV4Cidr-virtualNetworkName for non-critical VLANs. for critical VLANs with DATA trafficType, vlanName will be CRITICAL_VLAN. for critical VLANs with VOICE trafficType, vlanName will be VOICE_VLAN +- `critical_pool` (Boolean) Enable/disable critical VLAN. if true, autoGenerateVlanName must also be true. (isCriticalPool is not applicable to INFRA_VN) +- `id` (String) The id of the object +- `intra_subnet_routing_enabled` (Boolean) Enable/disable Intra-Subnet Routing (not applicable to INFRA_VN) +- `ip_directed_broadcast` (Boolean) Enable/disable IP-directed broadcast (not applicable to INFRA_VN) +- `l2_flooding_enabled` (Boolean) Enable/disable layer 2 flooding (not applicable to INFRA_VN) +- `multiple_ip_to_mac_addresses` (Boolean) Enable/disable multiple IP-to-MAC Addresses (Wireless Bridged-Network Virtual Machine; not applicable to INFRA_VN) +- `pool_type` (String) The pool type of the anycast gateway (required for & applicable only to INFRA_VN) +- `security_group_name` (String) Name of the associated Security Group (not applicable to INFRA_VN) +- `supplicant_based_extended_node_onboarding` (Boolean) Enable/disable Supplicant-Based Extended Node Onboarding (applicable only to INFRA_VN) +- `tcp_mss_adjustment` (Number) TCP maximum segment size adjustment +- `traffic_type` (String) The type of traffic the anycast gateway serves +- `vlan_id` (Number) ID of the VLAN of the anycast gateway. allowed VLAN range is 2-4093 except for reserved VLANs 1002-1005, 2046, and 4094. if deploying an anycast gateway on a fabric zone, this vlanId must match the vlanId of the corresponding anycast gateway on the fabric site +- `vlan_name` (String) Name of the VLAN of the anycast gateway +- `wireless_pool` (Boolean) Enable/disable fabric-enabled wireless (not applicable to INFRA_VN) diff --git a/docs/resources/anycast_gateway.md b/docs/resources/anycast_gateway.md new file mode 100644 index 00000000..16884c8a --- /dev/null +++ b/docs/resources/anycast_gateway.md @@ -0,0 +1,72 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_anycast_gateway Resource - terraform-provider-catalystcenter" +subcategory: "SDA" +description: |- + Manages Anycast Gateways +--- + +# catalystcenter_anycast_gateway (Resource) + +Manages Anycast Gateways + +## Example Usage + +```terraform +resource "catalystcenter_anycast_gateway" "example" { + fabric_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + virtual_network_name = "SDA_VN1" + ip_pool_name = "MyRes1" + tcp_mss_adjustment = 1400 + vlan_name = "VLAN401" + vlan_id = 401 + traffic_type = "DATA" + critical_pool = false + l2_flooding_enabled = false + wireless_pool = false + ip_directed_broadcast = false + intra_subnet_routing_enabled = false + multiple_ip_to_mac_addresses = false +} +``` + + +## Schema + +### Required + +- `critical_pool` (Boolean) Enable/disable critical VLAN. if true, autoGenerateVlanName must also be true. (isCriticalPool is not applicable to INFRA_VN) +- `fabric_id` (String) ID of the fabric to contain this anycast gateway +- `intra_subnet_routing_enabled` (Boolean) Enable/disable Intra-Subnet Routing (not applicable to INFRA_VN) +- `ip_directed_broadcast` (Boolean) Enable/disable IP-directed broadcast (not applicable to INFRA_VN) +- `ip_pool_name` (String) Name of the IP pool associated with the anycast gateway +- `l2_flooding_enabled` (Boolean) Enable/disable layer 2 flooding (not applicable to INFRA_VN) +- `multiple_ip_to_mac_addresses` (Boolean) Enable/disable multiple IP-to-MAC Addresses (Wireless Bridged-Network Virtual Machine; not applicable to INFRA_VN) +- `traffic_type` (String) The type of traffic the anycast gateway serves + - Choices: `DATA`, `VOICE` +- `virtual_network_name` (String) Name of the layer 3 virtual network associated with the anycast gateway. the virtual network must have already been added to the site before creating an anycast gateway with it +- `vlan_name` (String) Name of the VLAN of the anycast gateway +- `wireless_pool` (Boolean) Enable/disable fabric-enabled wireless (not applicable to INFRA_VN) + +### Optional + +- `auto_generate_vlan_name` (Boolean) This field cannot be true when vlanName is provided. the vlanName will be generated as ipPoolGroupV4Cidr-virtualNetworkName for non-critical VLANs. for critical VLANs with DATA trafficType, vlanName will be CRITICAL_VLAN. for critical VLANs with VOICE trafficType, vlanName will be VOICE_VLAN +- `pool_type` (String) The pool type of the anycast gateway (required for & applicable only to INFRA_VN) + - Choices: `EXTENDED_NODE`, `FABRIC_AP` +- `security_group_name` (String) Name of the associated Security Group (not applicable to INFRA_VN) +- `supplicant_based_extended_node_onboarding` (Boolean) Enable/disable Supplicant-Based Extended Node Onboarding (applicable only to INFRA_VN) +- `tcp_mss_adjustment` (Number) TCP maximum segment size adjustment + - Range: `500`-`1440` +- `vlan_id` (Number) ID of the VLAN of the anycast gateway. allowed VLAN range is 2-4093 except for reserved VLANs 1002-1005, 2046, and 4094. if deploying an anycast gateway on a fabric zone, this vlanId must match the vlanId of the corresponding anycast gateway on the fabric site + +### Read-Only + +- `id` (String) The id of the object + +## Import + +Import is supported using the following syntax: + +```shell +terraform import catalystcenter_anycast_gateway.example ",," +``` diff --git a/examples/data-sources/catalystcenter_anycast_gateway/data-source.tf b/examples/data-sources/catalystcenter_anycast_gateway/data-source.tf new file mode 100644 index 00000000..575b8dc5 --- /dev/null +++ b/examples/data-sources/catalystcenter_anycast_gateway/data-source.tf @@ -0,0 +1,5 @@ +data "catalystcenter_anycast_gateway" "example" { + fabric_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + virtual_network_name = "SDA_VN1" + ip_pool_name = "MyRes1" +} diff --git a/examples/resources/catalystcenter_anycast_gateway/import.sh b/examples/resources/catalystcenter_anycast_gateway/import.sh new file mode 100644 index 00000000..a27b7b2c --- /dev/null +++ b/examples/resources/catalystcenter_anycast_gateway/import.sh @@ -0,0 +1 @@ +terraform import catalystcenter_anycast_gateway.example ",," diff --git a/examples/resources/catalystcenter_anycast_gateway/resource.tf b/examples/resources/catalystcenter_anycast_gateway/resource.tf new file mode 100644 index 00000000..6920b961 --- /dev/null +++ b/examples/resources/catalystcenter_anycast_gateway/resource.tf @@ -0,0 +1,15 @@ +resource "catalystcenter_anycast_gateway" "example" { + fabric_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + virtual_network_name = "SDA_VN1" + ip_pool_name = "MyRes1" + tcp_mss_adjustment = 1400 + vlan_name = "VLAN401" + vlan_id = 401 + traffic_type = "DATA" + critical_pool = false + l2_flooding_enabled = false + wireless_pool = false + ip_directed_broadcast = false + intra_subnet_routing_enabled = false + multiple_ip_to_mac_addresses = false +} diff --git a/gen/definitions/anycast_gateway.yaml b/gen/definitions/anycast_gateway.yaml new file mode 100644 index 00000000..68064ee8 --- /dev/null +++ b/gen/definitions/anycast_gateway.yaml @@ -0,0 +1,186 @@ +--- +name: Anycast Gateway +rest_endpoint: /dna/intent/api/v1/sda/anycastGateways +res_description: Manages Anycast Gateways +id_from_query_path: response.0 +id_from_query_path_attribute: id +import_no_id: true +data_source_no_id: true +put_id_include_path: "0.id" +put_no_id: true +max_async_wait_time: 120 +doc_category: SDA +attributes: + - model_name: fabricId + requires_replace: true + data_path: '0' + query_param: true + response_data_path: response.0.fabricId + mandatory: true + description: ID of the fabric to contain this anycast gateway + type: String + example: 5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1 + test_value: catalystcenter_fabric_site.test.id + - model_name: virtualNetworkName + requires_replace: true + data_path: '0' + query_param: true + response_data_path: response.0.virtualNetworkName + type: String + mandatory: true + description: Name of the layer 3 virtual network associated with the anycast gateway. the virtual network must have already been added to the site before creating an anycast gateway with it + example: SDA_VN1 + test_value: catalystcenter_virtual_network_to_fabric_site.test.virtual_network_name + - model_name: ipPoolName + requires_replace: true + query_param: true + data_path: '0' + response_data_path: response.0.ipPoolName + type: String + mandatory: true + description: Name of the IP pool associated with the anycast gateway + example: MyRes1 + test_value: catalystcenter_ip_pool_reservation.test.name + - model_name: tcpMssAdjustment + data_path: '0' + response_data_path: response.0.tcpMssAdjustment + type: Int64 + min_int: 500 + max_int: 1440 + description: TCP maximum segment size adjustment + example: 1400 + - model_name: vlanName + requires_replace: true + data_path: '0' + response_data_path: response.0.vlanName + type: String + description: Name of the VLAN of the anycast gateway + mandatory: true + example: VLAN401 + - model_name: vlanId + requires_replace: true + data_path: '0' + response_data_path: response.0.vlanId + type: Int64 + description: ID of the VLAN of the anycast gateway. allowed VLAN range is 2-4093 except for reserved VLANs 1002-1005, 2046, and 4094. if deploying an anycast gateway on a fabric zone, this vlanId must match the vlanId of the corresponding anycast gateway on the fabric site + example: 401 + - model_name: trafficType + data_path: '0' + response_data_path: response.0.trafficType + type: String + enum_values: [DATA, VOICE] + mandatory: true + description: The type of traffic the anycast gateway serves + example: DATA + - model_name: poolType + data_path: '0' + response_data_path: response.0.poolType + type: String + enum_values: [EXTENDED_NODE, FABRIC_AP] + description: The pool type of the anycast gateway (required for & applicable only to INFRA_VN) + exclude_test: true + - model_name: securityGroupName + data_path: '0' + response_data_path: response.0.securityGroupNames + type: String + description: Name of the associated Security Group (not applicable to INFRA_VN) + exclude_test: true + - model_name: isCriticalPool + requires_replace: true + data_path: '0' + response_data_path: response.0.isCriticalPool + tf_name: critical_pool + type: Bool + mandatory: true + description: Enable/disable critical VLAN. if true, autoGenerateVlanName must also be true. (isCriticalPool is not applicable to INFRA_VN) + example: false + - model_name: isLayer2FloodingEnabled + data_path: '0' + response_data_path: response.0.isLayer2FloodingEnabled + tf_name: l2_flooding_enabled + type: Bool + mandatory: true + description: Enable/disable layer 2 flooding (not applicable to INFRA_VN) + example: false + - model_name: isWirelessPool + data_path: '0' + response_data_path: response.0.isWirelessPool + tf_name: wireless_pool + type: Bool + mandatory: true + description: Enable/disable fabric-enabled wireless (not applicable to INFRA_VN) + example: false + - model_name: isIpDirectedBroadcast + data_path: '0' + response_data_path: response.0.isIpDirectedBroadcast + tf_name: ip_directed_broadcast + type: Bool + mandatory: true + description: Enable/disable IP-directed broadcast (not applicable to INFRA_VN) + example: false + - model_name: isIntraSubnetRoutingEnabled + requires_replace: true + data_path: '0' + response_data_path: response.0.isIntraSubnetRoutingEnabled + tf_name: intra_subnet_routing_enabled + type: Bool + mandatory: true + description: Enable/disable Intra-Subnet Routing (not applicable to INFRA_VN) + example: false + - model_name: isMultipleIpToMacAddresses + data_path: '0' + response_data_path: response.0.isMultipleIpToMacAddresses + tf_name: multiple_ip_to_mac_addresses + type: Bool + mandatory: true + description: Enable/disable multiple IP-to-MAC Addresses (Wireless Bridged-Network Virtual Machine; not applicable to INFRA_VN) + example: false + - model_name: isSupplicantBasedExtendedNodeOnboarding + data_path: '0' + response_data_path: response.0.isSupplicantBasedExtendedNodeOnboarding + tf_name: supplicant_based_extended_node_onboarding + type: Bool + description: Enable/disable Supplicant-Based Extended Node Onboarding (applicable only to INFRA_VN) + exclude_test: true + - model_name: autoGenerateVlanName + data_path: '0' + response_data_path: response.0.autoGenerateVlanName + type: Bool + description: 'This field cannot be true when vlanName is provided. the vlanName will be generated as ipPoolGroupV4Cidr-virtualNetworkName for non-critical VLANs. for critical VLANs with DATA trafficType, vlanName will be CRITICAL_VLAN. for critical VLANs with VOICE trafficType, vlanName will be VOICE_VLAN' + exclude_test: true +test_prerequisites: | + resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" + depends_on = [catalystcenter_ip_pool.test] + } + resource "catalystcenter_ip_pool" "test" { + name = "MyPool1" + ip_subnet = "172.32.0.0/16" + } + resource "catalystcenter_ip_pool_reservation" "test" { + site_id = catalystcenter_area.test.id + name = "MyRes1" + type = "Generic" + ipv4_global_pool = catalystcenter_ip_pool.test.ip_subnet + ipv4_prefix = true + ipv4_prefix_length = 24 + ipv4_subnet = "172.32.1.0" + depends_on = [catalystcenter_ip_pool.test] + } + resource "catalystcenter_fabric_site" "test" { + site_id = catalystcenter_area.test.id + pub_sub_enabled = false + authentication_profile_name = "No Authentication" + depends_on = [catalystcenter_area.test] + } + resource "catalystcenter_fabric_virtual_network" "test" { + virtual_network_name = "SDA_VN1" + is_guest = false + sg_names = ["Employees"] + } + resource "catalystcenter_virtual_network_to_fabric_site" "test" { + virtual_network_name = "SDA_VN1" + site_name_hierarchy = "Global/Area1" + depends_on = [catalystcenter_fabric_virtual_network.test, catalystcenter_fabric_site.test] + } \ No newline at end of file diff --git a/internal/provider/data_source_catalystcenter_anycast_gateway.go b/internal/provider/data_source_catalystcenter_anycast_gateway.go new file mode 100644 index 00000000..6768c007 --- /dev/null +++ b/internal/provider/data_source_catalystcenter_anycast_gateway.go @@ -0,0 +1,175 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "context" + "fmt" + "net/url" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-log/tflog" + cc "github.com/netascode/go-catalystcenter" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin model + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &AnycastGatewayDataSource{} + _ datasource.DataSourceWithConfigure = &AnycastGatewayDataSource{} +) + +func NewAnycastGatewayDataSource() datasource.DataSource { + return &AnycastGatewayDataSource{} +} + +type AnycastGatewayDataSource struct { + client *cc.Client +} + +func (d *AnycastGatewayDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_anycast_gateway" +} + +func (d *AnycastGatewayDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "This data source can read the Anycast Gateway.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + }, + "fabric_id": schema.StringAttribute{ + MarkdownDescription: "ID of the fabric to contain this anycast gateway", + Required: true, + }, + "virtual_network_name": schema.StringAttribute{ + MarkdownDescription: "Name of the layer 3 virtual network associated with the anycast gateway. the virtual network must have already been added to the site before creating an anycast gateway with it", + Required: true, + }, + "ip_pool_name": schema.StringAttribute{ + MarkdownDescription: "Name of the IP pool associated with the anycast gateway", + Required: true, + }, + "tcp_mss_adjustment": schema.Int64Attribute{ + MarkdownDescription: "TCP maximum segment size adjustment", + Computed: true, + }, + "vlan_name": schema.StringAttribute{ + MarkdownDescription: "Name of the VLAN of the anycast gateway", + Computed: true, + }, + "vlan_id": schema.Int64Attribute{ + MarkdownDescription: "ID of the VLAN of the anycast gateway. allowed VLAN range is 2-4093 except for reserved VLANs 1002-1005, 2046, and 4094. if deploying an anycast gateway on a fabric zone, this vlanId must match the vlanId of the corresponding anycast gateway on the fabric site", + Computed: true, + }, + "traffic_type": schema.StringAttribute{ + MarkdownDescription: "The type of traffic the anycast gateway serves", + Computed: true, + }, + "pool_type": schema.StringAttribute{ + MarkdownDescription: "The pool type of the anycast gateway (required for & applicable only to INFRA_VN)", + Computed: true, + }, + "security_group_name": schema.StringAttribute{ + MarkdownDescription: "Name of the associated Security Group (not applicable to INFRA_VN)", + Computed: true, + }, + "critical_pool": schema.BoolAttribute{ + MarkdownDescription: "Enable/disable critical VLAN. if true, autoGenerateVlanName must also be true. (isCriticalPool is not applicable to INFRA_VN)", + Computed: true, + }, + "l2_flooding_enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable/disable layer 2 flooding (not applicable to INFRA_VN)", + Computed: true, + }, + "wireless_pool": schema.BoolAttribute{ + MarkdownDescription: "Enable/disable fabric-enabled wireless (not applicable to INFRA_VN)", + Computed: true, + }, + "ip_directed_broadcast": schema.BoolAttribute{ + MarkdownDescription: "Enable/disable IP-directed broadcast (not applicable to INFRA_VN)", + Computed: true, + }, + "intra_subnet_routing_enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable/disable Intra-Subnet Routing (not applicable to INFRA_VN)", + Computed: true, + }, + "multiple_ip_to_mac_addresses": schema.BoolAttribute{ + MarkdownDescription: "Enable/disable multiple IP-to-MAC Addresses (Wireless Bridged-Network Virtual Machine; not applicable to INFRA_VN)", + Computed: true, + }, + "supplicant_based_extended_node_onboarding": schema.BoolAttribute{ + MarkdownDescription: "Enable/disable Supplicant-Based Extended Node Onboarding (applicable only to INFRA_VN)", + Computed: true, + }, + "auto_generate_vlan_name": schema.BoolAttribute{ + MarkdownDescription: "This field cannot be true when vlanName is provided. the vlanName will be generated as ipPoolGroupV4Cidr-virtualNetworkName for non-critical VLANs. for critical VLANs with DATA trafficType, vlanName will be CRITICAL_VLAN. for critical VLANs with VOICE trafficType, vlanName will be VOICE_VLAN", + Computed: true, + }, + }, + } +} + +func (d *AnycastGatewayDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + d.client = req.ProviderData.(*CcProviderData).Client +} + +// End of section. //template:end model + +// Section below is generated&owned by "gen/generator.go". //template:begin read +func (d *AnycastGatewayDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config AnycastGateway + + // Read config + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.Id.String())) + + params := "" + params += "?fabricId=" + url.QueryEscape(config.FabricId.ValueString()) + "&virtualNetworkName=" + url.QueryEscape(config.VirtualNetworkName.ValueString()) + "&ipPoolName=" + url.QueryEscape(config.IpPoolName.ValueString()) + res, err := d.client.Get(config.getPath() + params) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object, got error: %s", err)) + return + } + + config.fromBody(ctx, res) + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", config.Id.ValueString())) + + diags = resp.State.Set(ctx, &config) + resp.Diagnostics.Append(diags...) +} + +// End of section. //template:end read diff --git a/internal/provider/data_source_catalystcenter_anycast_gateway_test.go b/internal/provider/data_source_catalystcenter_anycast_gateway_test.go new file mode 100644 index 00000000..1acab680 --- /dev/null +++ b/internal/provider/data_source_catalystcenter_anycast_gateway_test.go @@ -0,0 +1,126 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSource +func TestAccDataSourceCcAnycastGateway(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_anycast_gateway.test", "tcp_mss_adjustment", "1400")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_anycast_gateway.test", "vlan_name", "VLAN401")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_anycast_gateway.test", "vlan_id", "401")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_anycast_gateway.test", "traffic_type", "DATA")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_anycast_gateway.test", "critical_pool", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_anycast_gateway.test", "l2_flooding_enabled", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_anycast_gateway.test", "wireless_pool", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_anycast_gateway.test", "ip_directed_broadcast", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_anycast_gateway.test", "intra_subnet_routing_enabled", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_anycast_gateway.test", "multiple_ip_to_mac_addresses", "false")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceCcAnycastGatewayPrerequisitesConfig + testAccDataSourceCcAnycastGatewayConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAccDataSource + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites +const testAccDataSourceCcAnycastGatewayPrerequisitesConfig = ` +resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" + depends_on = [catalystcenter_ip_pool.test] +} +resource "catalystcenter_ip_pool" "test" { + name = "MyPool1" + ip_subnet = "172.32.0.0/16" +} +resource "catalystcenter_ip_pool_reservation" "test" { + site_id = catalystcenter_area.test.id + name = "MyRes1" + type = "Generic" + ipv4_global_pool = catalystcenter_ip_pool.test.ip_subnet + ipv4_prefix = true + ipv4_prefix_length = 24 + ipv4_subnet = "172.32.1.0" + depends_on = [catalystcenter_ip_pool.test] +} +resource "catalystcenter_fabric_site" "test" { + site_id = catalystcenter_area.test.id + pub_sub_enabled = false + authentication_profile_name = "No Authentication" + depends_on = [catalystcenter_area.test] +} +resource "catalystcenter_fabric_virtual_network" "test" { + virtual_network_name = "SDA_VN1" + is_guest = false + sg_names = ["Employees"] +} +resource "catalystcenter_virtual_network_to_fabric_site" "test" { + virtual_network_name = "SDA_VN1" + site_name_hierarchy = "Global/Area1" + depends_on = [catalystcenter_fabric_virtual_network.test, catalystcenter_fabric_site.test] +} +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig +func testAccDataSourceCcAnycastGatewayConfig() string { + config := `resource "catalystcenter_anycast_gateway" "test" {` + "\n" + config += ` fabric_id = catalystcenter_fabric_site.test.id` + "\n" + config += ` virtual_network_name = catalystcenter_virtual_network_to_fabric_site.test.virtual_network_name` + "\n" + config += ` ip_pool_name = catalystcenter_ip_pool_reservation.test.name` + "\n" + config += ` tcp_mss_adjustment = 1400` + "\n" + config += ` vlan_name = "VLAN401"` + "\n" + config += ` vlan_id = 401` + "\n" + config += ` traffic_type = "DATA"` + "\n" + config += ` critical_pool = false` + "\n" + config += ` l2_flooding_enabled = false` + "\n" + config += ` wireless_pool = false` + "\n" + config += ` ip_directed_broadcast = false` + "\n" + config += ` intra_subnet_routing_enabled = false` + "\n" + config += ` multiple_ip_to_mac_addresses = false` + "\n" + config += `}` + "\n" + + config += ` + data "catalystcenter_anycast_gateway" "test" { + id = catalystcenter_anycast_gateway.test.id + fabric_id = catalystcenter_fabric_site.test.id + virtual_network_name = catalystcenter_virtual_network_to_fabric_site.test.virtual_network_name + ip_pool_name = catalystcenter_ip_pool_reservation.test.name + } + ` + return config +} + +// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/model_catalystcenter_anycast_gateway.go b/internal/provider/model_catalystcenter_anycast_gateway.go new file mode 100644 index 00000000..6ed8574f --- /dev/null +++ b/internal/provider/model_catalystcenter_anycast_gateway.go @@ -0,0 +1,366 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin types +type AnycastGateway struct { + Id types.String `tfsdk:"id"` + FabricId types.String `tfsdk:"fabric_id"` + VirtualNetworkName types.String `tfsdk:"virtual_network_name"` + IpPoolName types.String `tfsdk:"ip_pool_name"` + TcpMssAdjustment types.Int64 `tfsdk:"tcp_mss_adjustment"` + VlanName types.String `tfsdk:"vlan_name"` + VlanId types.Int64 `tfsdk:"vlan_id"` + TrafficType types.String `tfsdk:"traffic_type"` + PoolType types.String `tfsdk:"pool_type"` + SecurityGroupName types.String `tfsdk:"security_group_name"` + CriticalPool types.Bool `tfsdk:"critical_pool"` + L2FloodingEnabled types.Bool `tfsdk:"l2_flooding_enabled"` + WirelessPool types.Bool `tfsdk:"wireless_pool"` + IpDirectedBroadcast types.Bool `tfsdk:"ip_directed_broadcast"` + IntraSubnetRoutingEnabled types.Bool `tfsdk:"intra_subnet_routing_enabled"` + MultipleIpToMacAddresses types.Bool `tfsdk:"multiple_ip_to_mac_addresses"` + SupplicantBasedExtendedNodeOnboarding types.Bool `tfsdk:"supplicant_based_extended_node_onboarding"` + AutoGenerateVlanName types.Bool `tfsdk:"auto_generate_vlan_name"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath +func (data AnycastGateway) getPath() string { + return "/dna/intent/api/v1/sda/anycastGateways" +} + +// End of section. //template:end getPath + +// Section below is generated&owned by "gen/generator.go". //template:begin getPathDelete + +// End of section. //template:end getPathDelete + +// Section below is generated&owned by "gen/generator.go". //template:begin toBody +func (data AnycastGateway) toBody(ctx context.Context, state AnycastGateway) string { + body := "" + put := false + if state.Id.ValueString() != "" { + put = true + body, _ = sjson.Set(body, "0.id", state.Id.ValueString()) + } + _ = put + if !data.FabricId.IsNull() { + body, _ = sjson.Set(body, "0.fabricId", data.FabricId.ValueString()) + } + if !data.VirtualNetworkName.IsNull() { + body, _ = sjson.Set(body, "0.virtualNetworkName", data.VirtualNetworkName.ValueString()) + } + if !data.IpPoolName.IsNull() { + body, _ = sjson.Set(body, "0.ipPoolName", data.IpPoolName.ValueString()) + } + if !data.TcpMssAdjustment.IsNull() { + body, _ = sjson.Set(body, "0.tcpMssAdjustment", data.TcpMssAdjustment.ValueInt64()) + } + if !data.VlanName.IsNull() { + body, _ = sjson.Set(body, "0.vlanName", data.VlanName.ValueString()) + } + if !data.VlanId.IsNull() { + body, _ = sjson.Set(body, "0.vlanId", data.VlanId.ValueInt64()) + } + if !data.TrafficType.IsNull() { + body, _ = sjson.Set(body, "0.trafficType", data.TrafficType.ValueString()) + } + if !data.PoolType.IsNull() { + body, _ = sjson.Set(body, "0.poolType", data.PoolType.ValueString()) + } + if !data.SecurityGroupName.IsNull() { + body, _ = sjson.Set(body, "0.securityGroupName", data.SecurityGroupName.ValueString()) + } + if !data.CriticalPool.IsNull() { + body, _ = sjson.Set(body, "0.isCriticalPool", data.CriticalPool.ValueBool()) + } + if !data.L2FloodingEnabled.IsNull() { + body, _ = sjson.Set(body, "0.isLayer2FloodingEnabled", data.L2FloodingEnabled.ValueBool()) + } + if !data.WirelessPool.IsNull() { + body, _ = sjson.Set(body, "0.isWirelessPool", data.WirelessPool.ValueBool()) + } + if !data.IpDirectedBroadcast.IsNull() { + body, _ = sjson.Set(body, "0.isIpDirectedBroadcast", data.IpDirectedBroadcast.ValueBool()) + } + if !data.IntraSubnetRoutingEnabled.IsNull() { + body, _ = sjson.Set(body, "0.isIntraSubnetRoutingEnabled", data.IntraSubnetRoutingEnabled.ValueBool()) + } + if !data.MultipleIpToMacAddresses.IsNull() { + body, _ = sjson.Set(body, "0.isMultipleIpToMacAddresses", data.MultipleIpToMacAddresses.ValueBool()) + } + if !data.SupplicantBasedExtendedNodeOnboarding.IsNull() { + body, _ = sjson.Set(body, "0.isSupplicantBasedExtendedNodeOnboarding", data.SupplicantBasedExtendedNodeOnboarding.ValueBool()) + } + if !data.AutoGenerateVlanName.IsNull() { + body, _ = sjson.Set(body, "0.autoGenerateVlanName", data.AutoGenerateVlanName.ValueBool()) + } + return body +} + +// End of section. //template:end toBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBody +func (data *AnycastGateway) fromBody(ctx context.Context, res gjson.Result) { + // Retrieve the 'id' attribute, if Data Source doesn't require id + if value := res.Get("response.0.id"); value.Exists() { + data.Id = types.StringValue(value.String()) + } else { + data.Id = types.StringNull() + } + if value := res.Get("response.0.fabricId"); value.Exists() { + data.FabricId = types.StringValue(value.String()) + } else { + data.FabricId = types.StringNull() + } + if value := res.Get("response.0.virtualNetworkName"); value.Exists() { + data.VirtualNetworkName = types.StringValue(value.String()) + } else { + data.VirtualNetworkName = types.StringNull() + } + if value := res.Get("response.0.ipPoolName"); value.Exists() { + data.IpPoolName = types.StringValue(value.String()) + } else { + data.IpPoolName = types.StringNull() + } + if value := res.Get("response.0.tcpMssAdjustment"); value.Exists() { + data.TcpMssAdjustment = types.Int64Value(value.Int()) + } else { + data.TcpMssAdjustment = types.Int64Null() + } + if value := res.Get("response.0.vlanName"); value.Exists() { + data.VlanName = types.StringValue(value.String()) + } else { + data.VlanName = types.StringNull() + } + if value := res.Get("response.0.vlanId"); value.Exists() { + data.VlanId = types.Int64Value(value.Int()) + } else { + data.VlanId = types.Int64Null() + } + if value := res.Get("response.0.trafficType"); value.Exists() { + data.TrafficType = types.StringValue(value.String()) + } else { + data.TrafficType = types.StringNull() + } + if value := res.Get("response.0.poolType"); value.Exists() { + data.PoolType = types.StringValue(value.String()) + } else { + data.PoolType = types.StringNull() + } + if value := res.Get("response.0.securityGroupNames"); value.Exists() { + data.SecurityGroupName = types.StringValue(value.String()) + } else { + data.SecurityGroupName = types.StringNull() + } + if value := res.Get("response.0.isCriticalPool"); value.Exists() { + data.CriticalPool = types.BoolValue(value.Bool()) + } else { + data.CriticalPool = types.BoolNull() + } + if value := res.Get("response.0.isLayer2FloodingEnabled"); value.Exists() { + data.L2FloodingEnabled = types.BoolValue(value.Bool()) + } else { + data.L2FloodingEnabled = types.BoolNull() + } + if value := res.Get("response.0.isWirelessPool"); value.Exists() { + data.WirelessPool = types.BoolValue(value.Bool()) + } else { + data.WirelessPool = types.BoolNull() + } + if value := res.Get("response.0.isIpDirectedBroadcast"); value.Exists() { + data.IpDirectedBroadcast = types.BoolValue(value.Bool()) + } else { + data.IpDirectedBroadcast = types.BoolNull() + } + if value := res.Get("response.0.isIntraSubnetRoutingEnabled"); value.Exists() { + data.IntraSubnetRoutingEnabled = types.BoolValue(value.Bool()) + } else { + data.IntraSubnetRoutingEnabled = types.BoolNull() + } + if value := res.Get("response.0.isMultipleIpToMacAddresses"); value.Exists() { + data.MultipleIpToMacAddresses = types.BoolValue(value.Bool()) + } else { + data.MultipleIpToMacAddresses = types.BoolNull() + } + if value := res.Get("response.0.isSupplicantBasedExtendedNodeOnboarding"); value.Exists() { + data.SupplicantBasedExtendedNodeOnboarding = types.BoolValue(value.Bool()) + } else { + data.SupplicantBasedExtendedNodeOnboarding = types.BoolNull() + } + if value := res.Get("response.0.autoGenerateVlanName"); value.Exists() { + data.AutoGenerateVlanName = types.BoolValue(value.Bool()) + } else { + data.AutoGenerateVlanName = types.BoolNull() + } +} + +// End of section. //template:end fromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin updateFromBody +func (data *AnycastGateway) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.0.fabricId"); value.Exists() && !data.FabricId.IsNull() { + data.FabricId = types.StringValue(value.String()) + } else { + data.FabricId = types.StringNull() + } + if value := res.Get("response.0.virtualNetworkName"); value.Exists() && !data.VirtualNetworkName.IsNull() { + data.VirtualNetworkName = types.StringValue(value.String()) + } else { + data.VirtualNetworkName = types.StringNull() + } + if value := res.Get("response.0.ipPoolName"); value.Exists() && !data.IpPoolName.IsNull() { + data.IpPoolName = types.StringValue(value.String()) + } else { + data.IpPoolName = types.StringNull() + } + if value := res.Get("response.0.tcpMssAdjustment"); value.Exists() && !data.TcpMssAdjustment.IsNull() { + data.TcpMssAdjustment = types.Int64Value(value.Int()) + } else { + data.TcpMssAdjustment = types.Int64Null() + } + if value := res.Get("response.0.vlanName"); value.Exists() && !data.VlanName.IsNull() { + data.VlanName = types.StringValue(value.String()) + } else { + data.VlanName = types.StringNull() + } + if value := res.Get("response.0.vlanId"); value.Exists() && !data.VlanId.IsNull() { + data.VlanId = types.Int64Value(value.Int()) + } else { + data.VlanId = types.Int64Null() + } + if value := res.Get("response.0.trafficType"); value.Exists() && !data.TrafficType.IsNull() { + data.TrafficType = types.StringValue(value.String()) + } else { + data.TrafficType = types.StringNull() + } + if value := res.Get("response.0.poolType"); value.Exists() && !data.PoolType.IsNull() { + data.PoolType = types.StringValue(value.String()) + } else { + data.PoolType = types.StringNull() + } + if value := res.Get("response.0.securityGroupNames"); value.Exists() && !data.SecurityGroupName.IsNull() { + data.SecurityGroupName = types.StringValue(value.String()) + } else { + data.SecurityGroupName = types.StringNull() + } + if value := res.Get("response.0.isCriticalPool"); value.Exists() && !data.CriticalPool.IsNull() { + data.CriticalPool = types.BoolValue(value.Bool()) + } else { + data.CriticalPool = types.BoolNull() + } + if value := res.Get("response.0.isLayer2FloodingEnabled"); value.Exists() && !data.L2FloodingEnabled.IsNull() { + data.L2FloodingEnabled = types.BoolValue(value.Bool()) + } else { + data.L2FloodingEnabled = types.BoolNull() + } + if value := res.Get("response.0.isWirelessPool"); value.Exists() && !data.WirelessPool.IsNull() { + data.WirelessPool = types.BoolValue(value.Bool()) + } else { + data.WirelessPool = types.BoolNull() + } + if value := res.Get("response.0.isIpDirectedBroadcast"); value.Exists() && !data.IpDirectedBroadcast.IsNull() { + data.IpDirectedBroadcast = types.BoolValue(value.Bool()) + } else { + data.IpDirectedBroadcast = types.BoolNull() + } + if value := res.Get("response.0.isIntraSubnetRoutingEnabled"); value.Exists() && !data.IntraSubnetRoutingEnabled.IsNull() { + data.IntraSubnetRoutingEnabled = types.BoolValue(value.Bool()) + } else { + data.IntraSubnetRoutingEnabled = types.BoolNull() + } + if value := res.Get("response.0.isMultipleIpToMacAddresses"); value.Exists() && !data.MultipleIpToMacAddresses.IsNull() { + data.MultipleIpToMacAddresses = types.BoolValue(value.Bool()) + } else { + data.MultipleIpToMacAddresses = types.BoolNull() + } + if value := res.Get("response.0.isSupplicantBasedExtendedNodeOnboarding"); value.Exists() && !data.SupplicantBasedExtendedNodeOnboarding.IsNull() { + data.SupplicantBasedExtendedNodeOnboarding = types.BoolValue(value.Bool()) + } else { + data.SupplicantBasedExtendedNodeOnboarding = types.BoolNull() + } + if value := res.Get("response.0.autoGenerateVlanName"); value.Exists() && !data.AutoGenerateVlanName.IsNull() { + data.AutoGenerateVlanName = types.BoolValue(value.Bool()) + } else { + data.AutoGenerateVlanName = types.BoolNull() + } +} + +// End of section. //template:end updateFromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin isNull +func (data *AnycastGateway) isNull(ctx context.Context, res gjson.Result) bool { + if !data.TcpMssAdjustment.IsNull() { + return false + } + if !data.VlanName.IsNull() { + return false + } + if !data.VlanId.IsNull() { + return false + } + if !data.TrafficType.IsNull() { + return false + } + if !data.PoolType.IsNull() { + return false + } + if !data.SecurityGroupName.IsNull() { + return false + } + if !data.CriticalPool.IsNull() { + return false + } + if !data.L2FloodingEnabled.IsNull() { + return false + } + if !data.WirelessPool.IsNull() { + return false + } + if !data.IpDirectedBroadcast.IsNull() { + return false + } + if !data.IntraSubnetRoutingEnabled.IsNull() { + return false + } + if !data.MultipleIpToMacAddresses.IsNull() { + return false + } + if !data.SupplicantBasedExtendedNodeOnboarding.IsNull() { + return false + } + if !data.AutoGenerateVlanName.IsNull() { + return false + } + return true +} + +// End of section. //template:end isNull diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 6ebd5a97..df1d9aa0 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -238,6 +238,7 @@ func (p *CcProvider) Configure(ctx context.Context, req provider.ConfigureReques func (p *CcProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ + NewAnycastGatewayResource, NewAreaResource, NewAssignCredentialsResource, NewAssociateSiteToNetworkProfileResource, @@ -285,6 +286,7 @@ func (p *CcProvider) Resources(ctx context.Context) []func() resource.Resource { func (p *CcProvider) DataSources(ctx context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ NewNetworkDevicesDataSource, // manually maintained + NewAnycastGatewayDataSource, NewAreaDataSource, NewAssignCredentialsDataSource, NewBuildingDataSource, diff --git a/internal/provider/resource_catalystcenter_anycast_gateway.go b/internal/provider/resource_catalystcenter_anycast_gateway.go new file mode 100644 index 00000000..24dcdfae --- /dev/null +++ b/internal/provider/resource_catalystcenter_anycast_gateway.go @@ -0,0 +1,342 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/CiscoDevNet/terraform-provider-catalystcenter/internal/provider/helpers" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + cc "github.com/netascode/go-catalystcenter" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin model + +// Ensure provider defined types fully satisfy framework interfaces +var _ resource.Resource = &AnycastGatewayResource{} +var _ resource.ResourceWithImportState = &AnycastGatewayResource{} + +func NewAnycastGatewayResource() resource.Resource { + return &AnycastGatewayResource{} +} + +type AnycastGatewayResource struct { + client *cc.Client +} + +func (r *AnycastGatewayResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_anycast_gateway" +} + +func (r *AnycastGatewayResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: helpers.NewAttributeDescription("Manages Anycast Gateways").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "fabric_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("ID of the fabric to contain this anycast gateway").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "virtual_network_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Name of the layer 3 virtual network associated with the anycast gateway. the virtual network must have already been added to the site before creating an anycast gateway with it").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "ip_pool_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Name of the IP pool associated with the anycast gateway").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "tcp_mss_adjustment": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("TCP maximum segment size adjustment").AddIntegerRangeDescription(500, 1440).String, + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(500, 1440), + }, + }, + "vlan_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Name of the VLAN of the anycast gateway").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "vlan_id": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("ID of the VLAN of the anycast gateway. allowed VLAN range is 2-4093 except for reserved VLANs 1002-1005, 2046, and 4094. if deploying an anycast gateway on a fabric zone, this vlanId must match the vlanId of the corresponding anycast gateway on the fabric site").String, + Optional: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "traffic_type": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("The type of traffic the anycast gateway serves").AddStringEnumDescription("DATA", "VOICE").String, + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("DATA", "VOICE"), + }, + }, + "pool_type": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("The pool type of the anycast gateway (required for & applicable only to INFRA_VN)").AddStringEnumDescription("EXTENDED_NODE", "FABRIC_AP").String, + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("EXTENDED_NODE", "FABRIC_AP"), + }, + }, + "security_group_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Name of the associated Security Group (not applicable to INFRA_VN)").String, + Optional: true, + }, + "critical_pool": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Enable/disable critical VLAN. if true, autoGenerateVlanName must also be true. (isCriticalPool is not applicable to INFRA_VN)").String, + Required: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "l2_flooding_enabled": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Enable/disable layer 2 flooding (not applicable to INFRA_VN)").String, + Required: true, + }, + "wireless_pool": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Enable/disable fabric-enabled wireless (not applicable to INFRA_VN)").String, + Required: true, + }, + "ip_directed_broadcast": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Enable/disable IP-directed broadcast (not applicable to INFRA_VN)").String, + Required: true, + }, + "intra_subnet_routing_enabled": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Enable/disable Intra-Subnet Routing (not applicable to INFRA_VN)").String, + Required: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "multiple_ip_to_mac_addresses": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Enable/disable multiple IP-to-MAC Addresses (Wireless Bridged-Network Virtual Machine; not applicable to INFRA_VN)").String, + Required: true, + }, + "supplicant_based_extended_node_onboarding": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Enable/disable Supplicant-Based Extended Node Onboarding (applicable only to INFRA_VN)").String, + Optional: true, + }, + "auto_generate_vlan_name": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("This field cannot be true when vlanName is provided. the vlanName will be generated as ipPoolGroupV4Cidr-virtualNetworkName for non-critical VLANs. for critical VLANs with DATA trafficType, vlanName will be CRITICAL_VLAN. for critical VLANs with VOICE trafficType, vlanName will be VOICE_VLAN").String, + Optional: true, + }, + }, + } +} + +func (r *AnycastGatewayResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*CcProviderData).Client +} + +// End of section. //template:end model + +// Section below is generated&owned by "gen/generator.go". //template:begin create +func (r *AnycastGatewayResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan AnycastGateway + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + + // Create object + body := plan.toBody(ctx, AnycastGateway{}) + + params := "" + res, err := r.client.Post(plan.getPath()+params, body, func(r *cc.Req) { r.MaxAsyncWaitTime = 120 }) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST), got error: %s, %s", err, res.String())) + return + } + params = "" + params += "?fabricId=" + url.QueryEscape(plan.FabricId.ValueString()) + "&virtualNetworkName=" + url.QueryEscape(plan.VirtualNetworkName.ValueString()) + "&ipPoolName=" + url.QueryEscape(plan.IpPoolName.ValueString()) + res, err = r.client.Get(plan.getPath() + params) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + plan.Id = types.StringValue(res.Get("response.0.id").String()) + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +// End of section. //template:end create + +// Section below is generated&owned by "gen/generator.go". //template:begin read +func (r *AnycastGatewayResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state AnycastGateway + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) + + params := "" + params += "?fabricId=" + url.QueryEscape(state.FabricId.ValueString()) + "&virtualNetworkName=" + url.QueryEscape(state.VirtualNetworkName.ValueString()) + "&ipPoolName=" + url.QueryEscape(state.IpPoolName.ValueString()) + res, err := r.client.Get(state.getPath() + params) + if err != nil && strings.Contains(err.Error(), "StatusCode 404") { + resp.State.RemoveResource(ctx) + return + } else if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + + // If every attribute is set to null we are dealing with an import operation and therefore reading all attributes + if state.isNull(ctx, res) { + state.fromBody(ctx, res) + } else { + state.updateFromBody(ctx, res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +// End of section. //template:end read + +// Section below is generated&owned by "gen/generator.go". //template:begin update +func (r *AnycastGatewayResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state AnycastGateway + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Read state + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + body := plan.toBody(ctx, state) + params := "" + res, err := r.client.Put(plan.getPath()+params, body, func(r *cc.Req) { r.MaxAsyncWaitTime = 120 }) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +// End of section. //template:end update + +// Section below is generated&owned by "gen/generator.go". //template:begin delete +func (r *AnycastGatewayResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state AnycastGateway + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + res, err := r.client.Delete(state.getPath() + "/" + url.QueryEscape(state.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object (DELETE), got error: %s, %s", err, res.String())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +// End of section. //template:end delete + +// Section below is generated&owned by "gen/generator.go". //template:begin import +func (r *AnycastGatewayResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, ",") + + if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: ,,. Got: %q", req.ID), + ) + return + } + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("fabric_id"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("virtual_network_name"), idParts[1])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("ip_pool_name"), idParts[2])...) +} + +// End of section. //template:end import diff --git a/internal/provider/resource_catalystcenter_anycast_gateway_test.go b/internal/provider/resource_catalystcenter_anycast_gateway_test.go new file mode 100644 index 00000000..94825d68 --- /dev/null +++ b/internal/provider/resource_catalystcenter_anycast_gateway_test.go @@ -0,0 +1,145 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin testAcc +func TestAccCcAnycastGateway(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_anycast_gateway.test", "tcp_mss_adjustment", "1400")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_anycast_gateway.test", "vlan_name", "VLAN401")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_anycast_gateway.test", "vlan_id", "401")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_anycast_gateway.test", "traffic_type", "DATA")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_anycast_gateway.test", "critical_pool", "false")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_anycast_gateway.test", "l2_flooding_enabled", "false")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_anycast_gateway.test", "wireless_pool", "false")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_anycast_gateway.test", "ip_directed_broadcast", "false")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_anycast_gateway.test", "intra_subnet_routing_enabled", "false")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_anycast_gateway.test", "multiple_ip_to_mac_addresses", "false")) + + var steps []resource.TestStep + if os.Getenv("SKIP_MINIMUM_TEST") == "" { + steps = append(steps, resource.TestStep{ + Config: testAccCcAnycastGatewayPrerequisitesConfig + testAccCcAnycastGatewayConfig_minimum(), + }) + } + steps = append(steps, resource.TestStep{ + Config: testAccCcAnycastGatewayPrerequisitesConfig + testAccCcAnycastGatewayConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: steps, + }) +} + +// End of section. //template:end testAcc + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites +const testAccCcAnycastGatewayPrerequisitesConfig = ` +resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" + depends_on = [catalystcenter_ip_pool.test] +} +resource "catalystcenter_ip_pool" "test" { + name = "MyPool1" + ip_subnet = "172.32.0.0/16" +} +resource "catalystcenter_ip_pool_reservation" "test" { + site_id = catalystcenter_area.test.id + name = "MyRes1" + type = "Generic" + ipv4_global_pool = catalystcenter_ip_pool.test.ip_subnet + ipv4_prefix = true + ipv4_prefix_length = 24 + ipv4_subnet = "172.32.1.0" + depends_on = [catalystcenter_ip_pool.test] +} +resource "catalystcenter_fabric_site" "test" { + site_id = catalystcenter_area.test.id + pub_sub_enabled = false + authentication_profile_name = "No Authentication" + depends_on = [catalystcenter_area.test] +} +resource "catalystcenter_fabric_virtual_network" "test" { + virtual_network_name = "SDA_VN1" + is_guest = false + sg_names = ["Employees"] +} +resource "catalystcenter_virtual_network_to_fabric_site" "test" { + virtual_network_name = "SDA_VN1" + site_name_hierarchy = "Global/Area1" + depends_on = [catalystcenter_fabric_virtual_network.test, catalystcenter_fabric_site.test] +} +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal +func testAccCcAnycastGatewayConfig_minimum() string { + config := `resource "catalystcenter_anycast_gateway" "test" {` + "\n" + config += ` fabric_id = catalystcenter_fabric_site.test.id` + "\n" + config += ` virtual_network_name = catalystcenter_virtual_network_to_fabric_site.test.virtual_network_name` + "\n" + config += ` ip_pool_name = catalystcenter_ip_pool_reservation.test.name` + "\n" + config += ` vlan_name = "VLAN401"` + "\n" + config += ` traffic_type = "DATA"` + "\n" + config += ` critical_pool = false` + "\n" + config += ` l2_flooding_enabled = false` + "\n" + config += ` wireless_pool = false` + "\n" + config += ` ip_directed_broadcast = false` + "\n" + config += ` intra_subnet_routing_enabled = false` + "\n" + config += ` multiple_ip_to_mac_addresses = false` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll +func testAccCcAnycastGatewayConfig_all() string { + config := `resource "catalystcenter_anycast_gateway" "test" {` + "\n" + config += ` fabric_id = catalystcenter_fabric_site.test.id` + "\n" + config += ` virtual_network_name = catalystcenter_virtual_network_to_fabric_site.test.virtual_network_name` + "\n" + config += ` ip_pool_name = catalystcenter_ip_pool_reservation.test.name` + "\n" + config += ` tcp_mss_adjustment = 1400` + "\n" + config += ` vlan_name = "VLAN401"` + "\n" + config += ` vlan_id = 401` + "\n" + config += ` traffic_type = "DATA"` + "\n" + config += ` critical_pool = false` + "\n" + config += ` l2_flooding_enabled = false` + "\n" + config += ` wireless_pool = false` + "\n" + config += ` ip_directed_broadcast = false` + "\n" + config += ` intra_subnet_routing_enabled = false` + "\n" + config += ` multiple_ip_to_mac_addresses = false` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll From 5545db089c1e87fdb85387e90f3f75d436959637 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Sat, 20 Jul 2024 22:29:54 +0200 Subject: [PATCH 6/8] update changelog --- CHANGELOG.md | 1 + docs/guides/changelog.md | 1 + templates/guides/changelog.md.tmpl | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4d1f472..e712bbbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.1.10 (unreleased) +- Add `anycast_gateway` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/anycastGateways` - BREAKING CHANGE: Modified `fabric_site` resource to use `/dna/intent/api/v1/sda/fabricSites` API endpoint, this resource now only works with Catalyst Center version 2.3.7.5+ - Fix issue with mandatory attributes in `transit_peer_network` resource, [link](https://github.com/CiscoDevNet/terraform-provider-catalystcenter/issues/92) - BREAKING CHANGE: Fix `ip_pool` update if more than 25 pools are registered diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index 109b28ee..3940ca6f 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -9,6 +9,7 @@ description: |- ## 0.1.10 (unreleased) +- Add `anycast_gateway` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/anycastGateways` - BREAKING CHANGE: Modified `fabric_site` resource to use `/dna/intent/api/v1/sda/fabricSites` API endpoint, this resource now only works with Catalyst Center version 2.3.7.5+ - Fix issue with mandatory attributes in `transit_peer_network` resource, [link](https://github.com/CiscoDevNet/terraform-provider-catalystcenter/issues/92) - BREAKING CHANGE: Fix `ip_pool` update if more than 25 pools are registered diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index 109b28ee..3940ca6f 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -9,6 +9,7 @@ description: |- ## 0.1.10 (unreleased) +- Add `anycast_gateway` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/anycastGateways` - BREAKING CHANGE: Modified `fabric_site` resource to use `/dna/intent/api/v1/sda/fabricSites` API endpoint, this resource now only works with Catalyst Center version 2.3.7.5+ - Fix issue with mandatory attributes in `transit_peer_network` resource, [link](https://github.com/CiscoDevNet/terraform-provider-catalystcenter/issues/92) - BREAKING CHANGE: Fix `ip_pool` update if more than 25 pools are registered From 9c69bd540586db7102509715dcc3a5d418f58990 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Sat, 20 Jul 2024 23:41:25 +0200 Subject: [PATCH 7/8] add transitPeerNetworkId as id to transit_peer_network resource --- docs/data-sources/transit_peer_network.md | 6 +-- docs/resources/transit_peer_network.md | 6 --- .../data-source.tf | 2 +- .../resource.tf | 6 --- gen/definitions/transit_peer_network.yaml | 14 ++++--- gen/definitions/wireless_profile.yaml | 1 + gen/templates/model.go | 2 +- gen/templates/resource.go | 11 +++-- ...rce_catalystcenter_transit_peer_network.go | 40 +------------------ ...atalystcenter_transit_peer_network_test.go | 5 +-- ...del_catalystcenter_transit_peer_network.go | 6 +++ ...rce_catalystcenter_transit_peer_network.go | 15 +++++-- ...atalystcenter_transit_peer_network_test.go | 8 ---- ...esource_catalystcenter_wireless_profile.go | 3 +- 14 files changed, 44 insertions(+), 81 deletions(-) diff --git a/docs/data-sources/transit_peer_network.md b/docs/data-sources/transit_peer_network.md index 5d295e77..892ca67a 100644 --- a/docs/data-sources/transit_peer_network.md +++ b/docs/data-sources/transit_peer_network.md @@ -14,21 +14,21 @@ This data source can read the Transit Peer Network. ```terraform data "catalystcenter_transit_peer_network" "example" { - id = "TRANSIT_1" + transit_peer_network_name = "TRANSIT_1" } ``` ## Schema -### Optional +### Required -- `id` (String) The id of the object - `transit_peer_network_name` (String) Transit Peer Network Name ### Read-Only - `autonomous_system_number` (String) Autonomous System Number +- `id` (String) The id of the object - `routing_protocol_name` (String) Routing Protocol Name - `transit_control_plane_settings` (Attributes List) Transit Control Plane Settings info (see [below for nested schema](#nestedatt--transit_control_plane_settings)) - `transit_peer_network_type` (String) Transit Peer Network Type diff --git a/docs/resources/transit_peer_network.md b/docs/resources/transit_peer_network.md index fa9f1bb9..381af689 100644 --- a/docs/resources/transit_peer_network.md +++ b/docs/resources/transit_peer_network.md @@ -18,12 +18,6 @@ resource "catalystcenter_transit_peer_network" "example" { transit_peer_network_type = "ip_transit" routing_protocol_name = "BGP" autonomous_system_number = "65010" - transit_control_plane_settings = [ - { - site_name_hierarchy = "Global/Area1" - device_management_ip_address = "10.0.0.1" - } - ] } ``` diff --git a/examples/data-sources/catalystcenter_transit_peer_network/data-source.tf b/examples/data-sources/catalystcenter_transit_peer_network/data-source.tf index 1b461c67..c4ae4e17 100644 --- a/examples/data-sources/catalystcenter_transit_peer_network/data-source.tf +++ b/examples/data-sources/catalystcenter_transit_peer_network/data-source.tf @@ -1,3 +1,3 @@ data "catalystcenter_transit_peer_network" "example" { - id = "TRANSIT_1" + transit_peer_network_name = "TRANSIT_1" } diff --git a/examples/resources/catalystcenter_transit_peer_network/resource.tf b/examples/resources/catalystcenter_transit_peer_network/resource.tf index bc950635..655cd66c 100644 --- a/examples/resources/catalystcenter_transit_peer_network/resource.tf +++ b/examples/resources/catalystcenter_transit_peer_network/resource.tf @@ -3,10 +3,4 @@ resource "catalystcenter_transit_peer_network" "example" { transit_peer_network_type = "ip_transit" routing_protocol_name = "BGP" autonomous_system_number = "65010" - transit_control_plane_settings = [ - { - site_name_hierarchy = "Global/Area1" - device_management_ip_address = "10.0.0.1" - } - ] } diff --git a/gen/definitions/transit_peer_network.yaml b/gen/definitions/transit_peer_network.yaml index 58a02562..dff8cee5 100644 --- a/gen/definitions/transit_peer_network.yaml +++ b/gen/definitions/transit_peer_network.yaml @@ -1,17 +1,20 @@ --- name: Transit Peer Network rest_endpoint: /dna/intent/api/v1/business/sda/transit-peer-network -id_from_attribute: true -id_query_param: transitPeerNetworkName -delete_id_query_param: transitPeerNetworkName +id_from_query_path: . +id_from_query_path_attribute: transitPeerNetworkId +import_no_id: true +data_source_no_id: true no_update: true skip_minimum_test: true doc_category: SDA attributes: - model_name: transitPeerNetworkName type: String - id: true - data_source_query: true + query_param: true + delete_query_param: true + mandatory: true + requires_replace: true description: Transit Peer Network Name example: TRANSIT_1 - model_name: transitPeerNetworkType @@ -37,6 +40,7 @@ attributes: - model_name: transitControlPlaneSettings data_path: sdaTransitSettings write_only: true + exclude_test: true type: List description: Transit Control Plane Settings info attributes: diff --git a/gen/definitions/wireless_profile.yaml b/gen/definitions/wireless_profile.yaml index b1f635ca..2e8f4990 100644 --- a/gen/definitions/wireless_profile.yaml +++ b/gen/definitions/wireless_profile.yaml @@ -13,6 +13,7 @@ doc_category: Wireless attributes: - model_name: wirelessProfileName tf_name: name + delete_query_param_name: name delete_query_param: true type: String match_id: true diff --git a/gen/templates/model.go b/gen/templates/model.go index 7c9d0fc3..5a11aa3b 100644 --- a/gen/templates/model.go +++ b/gen/templates/model.go @@ -307,7 +307,7 @@ func (data {{camelCase .Name}}) toBody(ctx context.Context, state {{camelCase .N func (data *{{camelCase .Name}}) fromBody(ctx context.Context, res gjson.Result) { {{- if .DataSourceNoId}} // Retrieve the 'id' attribute, if Data Source doesn't require id - if value := res.Get("{{if .IdFromQueryPath}}{{.IdFromQueryPath}}.{{if .IdFromQueryPathAttribute}}{{.IdFromQueryPathAttribute}}{{else}}id{{end}}{{end}}"); value.Exists() { + if value := res.Get("{{if .IdFromQueryPath}}{{if eq .IdFromQueryPath "." }}{{else}}{{.IdFromQueryPath}}.{{end}}{{if .IdFromQueryPathAttribute}}{{.IdFromQueryPathAttribute}}{{else}}id{{end}}{{end}}"); value.Exists() { data.Id = types.StringValue(value.String()) } else { data.Id = types.StringNull() diff --git a/gen/templates/resource.go b/gen/templates/resource.go index da28f6a6..d2dfcc54 100644 --- a/gen/templates/resource.go +++ b/gen/templates/resource.go @@ -457,7 +457,7 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C return } {{- if and .IdFromQueryPathAttribute .IdFromQueryPath (not .GetExtraQueryParams) (not .GetFromAll) }} - plan.Id = types.StringValue(res.Get("{{if .IdFromQueryPath}}{{.IdFromQueryPath}}.{{end}}{{.IdFromQueryPathAttribute}}").String()) + plan.Id = types.StringValue(res.Get("{{if eq .IdFromQueryPath "." }}{{else}}{{.IdFromQueryPath}}.{{end}}{{.IdFromQueryPathAttribute}}").String()) {{- else}} plan.Id = types.StringValue(res.Get("{{.IdFromQueryPath}}.#({{if $id.ResponseModelName}}{{$id.ResponseModelName}}{{else}}{{$id.ModelName}}{{end}}==\""+ plan.{{toGoName $id.TfName}}.Value{{$id.Type}}() +"\").{{if .IdFromQueryPathAttribute}}{{.IdFromQueryPathAttribute}}{{else}}id{{end}}").String()) {{- end}} @@ -611,9 +611,12 @@ func (r *{{camelCase .Name}}Resource) Delete(ctx context.Context, req resource.D res, err := r.client.Delete({{if .DeleteRestEndpoint}}state.getPathDelete(){{else}}state.getPath(){{end}}) {{- else if .DeleteIdQueryParam}} res, err := r.client.Delete({{if .DeleteRestEndpoint}}state.getPathDelete(){{else}}state.getPath(){{end}} + "?{{.DeleteIdQueryParam}}=" + url.QueryEscape(state.Id.ValueString())) - {{- else if hasDeleteQueryParam .Attributes}} - {{- $deleteQueryParam := getDeleteQueryParam .Attributes}} - res, err := r.client.Delete({{if .DeleteRestEndpoint}}state.getPathDelete(){{else}}state.getPath(){{end}} + "?{{$deleteQueryParam.TfName}}=" + url.QueryEscape(state.{{toGoName $deleteQueryParam.TfName}}.Value{{$deleteQueryParam.Type}}())) + {{- else if hasDeleteQueryParam .Attributes }} + {{- $queryParams := generateQueryParamString "DELETE" "state" .Attributes }} + {{- if $queryParams }} + params := {{$queryParams}} + {{- end}} + res, err := r.client.Delete({{if .DeleteRestEndpoint}}state.getPathDelete(){{else}}state.getPath(){{end}} + params) {{- else}} res, err := r.client.Delete({{if .DeleteRestEndpoint}}state.getPathDelete(){{else}}state.getPath(){{end}} + "/" + url.QueryEscape(state.Id.ValueString())) {{- end}} diff --git a/internal/provider/data_source_catalystcenter_transit_peer_network.go b/internal/provider/data_source_catalystcenter_transit_peer_network.go index 4f47e083..60fa9a44 100644 --- a/internal/provider/data_source_catalystcenter_transit_peer_network.go +++ b/internal/provider/data_source_catalystcenter_transit_peer_network.go @@ -23,14 +23,10 @@ import ( "fmt" "net/url" - "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" cc "github.com/netascode/go-catalystcenter" - "github.com/tidwall/gjson" ) // End of section. //template:end imports @@ -63,13 +59,11 @@ func (d *TransitPeerNetworkDataSource) Schema(ctx context.Context, req datasourc Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ MarkdownDescription: "The id of the object", - Optional: true, Computed: true, }, "transit_peer_network_name": schema.StringAttribute{ MarkdownDescription: "Transit Peer Network Name", - Optional: true, - Computed: true, + Required: true, }, "transit_peer_network_type": schema.StringAttribute{ MarkdownDescription: "Transit Peer Network Type", @@ -102,14 +96,6 @@ func (d *TransitPeerNetworkDataSource) Schema(ctx context.Context, req datasourc }, } } -func (d *TransitPeerNetworkDataSource) ConfigValidators(ctx context.Context) []datasource.ConfigValidator { - return []datasource.ConfigValidator{ - datasourcevalidator.ExactlyOneOf( - path.MatchRoot("id"), - path.MatchRoot("transit_peer_network_name"), - ), - } -} func (d *TransitPeerNetworkDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { if req.ProviderData == nil { @@ -133,31 +119,9 @@ func (d *TransitPeerNetworkDataSource) Read(ctx context.Context, req datasource. } tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.Id.String())) - if config.Id.IsNull() && !config.TransitPeerNetworkName.IsNull() { - res, err := d.client.Get(config.getPath()) - if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) - return - } - if value := res; len(value.Array()) > 0 { - value.ForEach(func(k, v gjson.Result) bool { - if config.TransitPeerNetworkName.ValueString() == v.Get("transitPeerNetworkName").String() { - config.Id = types.StringValue(v.Get("id").String()) - tflog.Debug(ctx, fmt.Sprintf("%s: Found object with transitPeerNetworkName '%v', id: %v", config.Id.String(), config.TransitPeerNetworkName.ValueString(), config.Id.String())) - return false - } - return true - }) - } - - if config.Id.IsNull() { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to find object with transitPeerNetworkName: %s", config.TransitPeerNetworkName.ValueString())) - return - } - } params := "" - params += "?transitPeerNetworkName=" + url.QueryEscape(config.Id.ValueString()) + params += "?transitPeerNetworkName=" + url.QueryEscape(config.TransitPeerNetworkName.ValueString()) res, err := d.client.Get(config.getPath() + params) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object, got error: %s", err)) diff --git a/internal/provider/data_source_catalystcenter_transit_peer_network_test.go b/internal/provider/data_source_catalystcenter_transit_peer_network_test.go index 90afdc8b..f5b7c58c 100644 --- a/internal/provider/data_source_catalystcenter_transit_peer_network_test.go +++ b/internal/provider/data_source_catalystcenter_transit_peer_network_test.go @@ -57,15 +57,12 @@ func testAccDataSourceCcTransitPeerNetworkConfig() string { config += ` transit_peer_network_type = "ip_transit"` + "\n" config += ` routing_protocol_name = "BGP"` + "\n" config += ` autonomous_system_number = "65010"` + "\n" - config += ` transit_control_plane_settings = [{` + "\n" - config += ` site_name_hierarchy = "Global/Area1"` + "\n" - config += ` device_management_ip_address = "10.0.0.1"` + "\n" - config += ` }]` + "\n" config += `}` + "\n" config += ` data "catalystcenter_transit_peer_network" "test" { id = catalystcenter_transit_peer_network.test.id + transit_peer_network_name = "TRANSIT_1" } ` return config diff --git a/internal/provider/model_catalystcenter_transit_peer_network.go b/internal/provider/model_catalystcenter_transit_peer_network.go index 0a071d0d..ad90a6eb 100644 --- a/internal/provider/model_catalystcenter_transit_peer_network.go +++ b/internal/provider/model_catalystcenter_transit_peer_network.go @@ -96,6 +96,12 @@ func (data TransitPeerNetwork) toBody(ctx context.Context, state TransitPeerNetw // Section below is generated&owned by "gen/generator.go". //template:begin fromBody func (data *TransitPeerNetwork) fromBody(ctx context.Context, res gjson.Result) { + // Retrieve the 'id' attribute, if Data Source doesn't require id + if value := res.Get("transitPeerNetworkId"); value.Exists() { + data.Id = types.StringValue(value.String()) + } else { + data.Id = types.StringNull() + } if value := res.Get("transitPeerNetworkName"); value.Exists() { data.TransitPeerNetworkName = types.StringValue(value.String()) } else { diff --git a/internal/provider/resource_catalystcenter_transit_peer_network.go b/internal/provider/resource_catalystcenter_transit_peer_network.go index c19e9601..1ee101a7 100644 --- a/internal/provider/resource_catalystcenter_transit_peer_network.go +++ b/internal/provider/resource_catalystcenter_transit_peer_network.go @@ -162,7 +162,14 @@ func (r *TransitPeerNetworkResource) Create(ctx context.Context, req resource.Cr resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST), got error: %s, %s", err, res.String())) return } - plan.Id = types.StringValue(fmt.Sprint(plan.TransitPeerNetworkName.ValueString())) + params = "" + params += "?transitPeerNetworkName=" + url.QueryEscape(plan.TransitPeerNetworkName.ValueString()) + res, err = r.client.Get(plan.getPath() + params) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + plan.Id = types.StringValue(res.Get("transitPeerNetworkId").String()) tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) @@ -186,7 +193,7 @@ func (r *TransitPeerNetworkResource) Read(ctx context.Context, req resource.Read tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) params := "" - params += "?transitPeerNetworkName=" + url.QueryEscape(state.Id.ValueString()) + params += "?transitPeerNetworkName=" + url.QueryEscape(state.TransitPeerNetworkName.ValueString()) res, err := r.client.Get(state.getPath() + params) if err != nil && strings.Contains(err.Error(), "StatusCode 404") { resp.State.RemoveResource(ctx) @@ -250,7 +257,8 @@ func (r *TransitPeerNetworkResource) Delete(ctx context.Context, req resource.De } tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) - res, err := r.client.Delete(state.getPath() + "?transitPeerNetworkName=" + url.QueryEscape(state.Id.ValueString())) + params := "?transitPeerNetworkName=" + url.QueryEscape(state.TransitPeerNetworkName.ValueString()) + res, err := r.client.Delete(state.getPath() + params) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object (DELETE), got error: %s, %s", err, res.String())) return @@ -275,7 +283,6 @@ func (r *TransitPeerNetworkResource) ImportState(ctx context.Context, req resour return } resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("transit_peer_network_name"), idParts[0])...) - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), idParts[0])...) } // End of section. //template:end import diff --git a/internal/provider/resource_catalystcenter_transit_peer_network_test.go b/internal/provider/resource_catalystcenter_transit_peer_network_test.go index 56149c64..62fa7c4e 100644 --- a/internal/provider/resource_catalystcenter_transit_peer_network_test.go +++ b/internal/provider/resource_catalystcenter_transit_peer_network_test.go @@ -39,10 +39,6 @@ func TestAccCcTransitPeerNetwork(t *testing.T) { Config: testAccCcTransitPeerNetworkConfig_all(), Check: resource.ComposeTestCheckFunc(checks...), }) - steps = append(steps, resource.TestStep{ - ResourceName: "catalystcenter_transit_peer_network.test", - ImportState: true, - }) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -74,10 +70,6 @@ func testAccCcTransitPeerNetworkConfig_all() string { config += ` transit_peer_network_type = "ip_transit"` + "\n" config += ` routing_protocol_name = "BGP"` + "\n" config += ` autonomous_system_number = "65010"` + "\n" - config += ` transit_control_plane_settings = [{` + "\n" - config += ` site_name_hierarchy = "Global/Area1"` + "\n" - config += ` device_management_ip_address = "10.0.0.1"` + "\n" - config += ` }]` + "\n" config += `}` + "\n" return config } diff --git a/internal/provider/resource_catalystcenter_wireless_profile.go b/internal/provider/resource_catalystcenter_wireless_profile.go index 0a2e8878..a830f167 100644 --- a/internal/provider/resource_catalystcenter_wireless_profile.go +++ b/internal/provider/resource_catalystcenter_wireless_profile.go @@ -249,7 +249,8 @@ func (r *WirelessProfileResource) Delete(ctx context.Context, req resource.Delet } tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) - res, err := r.client.Delete(state.getPath() + "?name=" + url.QueryEscape(state.Name.ValueString())) + params := "?name=" + url.QueryEscape(state.Name.ValueString()) + res, err := r.client.Delete(state.getPath() + params) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object (DELETE), got error: %s, %s", err, res.String())) return From c70bd2a0a1d9d601361e537572b62c7830715dcf Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Sat, 20 Jul 2024 23:45:00 +0200 Subject: [PATCH 8/8] updated changelog --- CHANGELOG.md | 1 + docs/guides/changelog.md | 1 + templates/guides/changelog.md.tmpl | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e712bbbc..004335c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.1.10 (unreleased) +- Add `transitPeerNetworkId` as `id` to `transit_peer_network` resource - Add `anycast_gateway` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/anycastGateways` - BREAKING CHANGE: Modified `fabric_site` resource to use `/dna/intent/api/v1/sda/fabricSites` API endpoint, this resource now only works with Catalyst Center version 2.3.7.5+ - Fix issue with mandatory attributes in `transit_peer_network` resource, [link](https://github.com/CiscoDevNet/terraform-provider-catalystcenter/issues/92) diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index 3940ca6f..e1fa80bf 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -9,6 +9,7 @@ description: |- ## 0.1.10 (unreleased) +- Add `transitPeerNetworkId` as `id` to `transit_peer_network` resource - Add `anycast_gateway` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/anycastGateways` - BREAKING CHANGE: Modified `fabric_site` resource to use `/dna/intent/api/v1/sda/fabricSites` API endpoint, this resource now only works with Catalyst Center version 2.3.7.5+ - Fix issue with mandatory attributes in `transit_peer_network` resource, [link](https://github.com/CiscoDevNet/terraform-provider-catalystcenter/issues/92) diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index 3940ca6f..e1fa80bf 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -9,6 +9,7 @@ description: |- ## 0.1.10 (unreleased) +- Add `transitPeerNetworkId` as `id` to `transit_peer_network` resource - Add `anycast_gateway` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/anycastGateways` - BREAKING CHANGE: Modified `fabric_site` resource to use `/dna/intent/api/v1/sda/fabricSites` API endpoint, this resource now only works with Catalyst Center version 2.3.7.5+ - Fix issue with mandatory attributes in `transit_peer_network` resource, [link](https://github.com/CiscoDevNet/terraform-provider-catalystcenter/issues/92)