From c95fc8e922ad75f15f9d58ebcb7c1e37b2600a3f Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Fri, 19 Jul 2024 14:43:56 +0200 Subject: [PATCH 01/31] 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 02/31] 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 03/31] 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 04/31] 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 05/31] 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 06/31] 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 07/31] 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 08/31] 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) From 38d93bf0c98374f7589d13225f2068c7cfabfa95 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Sun, 21 Jul 2024 19:51:54 +0200 Subject: [PATCH 09/31] add fabric_l3_handoff_ip_transit resource and data source --- CHANGELOG.md | 2 +- .../fabric_l3_handoff_ip_transit.md | 42 +++ docs/guides/changelog.md | 2 +- .../resources/fabric_l3_handoff_ip_transit.md | 61 ++++ .../data-source.tf | 4 + .../import.sh | 1 + .../resource.tf | 11 + .../fabric_l3_handoff_ip_transit.yaml | 125 +++++++ ...lystcenter_fabric_l3_handoff_ip_transit.go | 155 +++++++++ ...enter_fabric_l3_handoff_ip_transit_test.go | 103 ++++++ ...lystcenter_fabric_l3_handoff_ip_transit.go | 284 +++++++++++++++ internal/provider/provider.go | 2 + ...lystcenter_fabric_l3_handoff_ip_transit.go | 325 ++++++++++++++++++ ...enter_fabric_l3_handoff_ip_transit_test.go | 116 +++++++ templates/guides/changelog.md.tmpl | 2 +- 15 files changed, 1232 insertions(+), 3 deletions(-) create mode 100644 docs/data-sources/fabric_l3_handoff_ip_transit.md create mode 100644 docs/resources/fabric_l3_handoff_ip_transit.md create mode 100644 examples/data-sources/catalystcenter_fabric_l3_handoff_ip_transit/data-source.tf create mode 100644 examples/resources/catalystcenter_fabric_l3_handoff_ip_transit/import.sh create mode 100644 examples/resources/catalystcenter_fabric_l3_handoff_ip_transit/resource.tf create mode 100644 gen/definitions/fabric_l3_handoff_ip_transit.yaml create mode 100644 internal/provider/data_source_catalystcenter_fabric_l3_handoff_ip_transit.go create mode 100644 internal/provider/data_source_catalystcenter_fabric_l3_handoff_ip_transit_test.go create mode 100644 internal/provider/model_catalystcenter_fabric_l3_handoff_ip_transit.go create mode 100644 internal/provider/resource_catalystcenter_fabric_l3_handoff_ip_transit.go create mode 100644 internal/provider/resource_catalystcenter_fabric_l3_handoff_ip_transit_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 004335c5..147e9c33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## 0.1.10 (unreleased) - +- Add `fabric_l3_handoff_ip_transit` resource and data source - 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+ diff --git a/docs/data-sources/fabric_l3_handoff_ip_transit.md b/docs/data-sources/fabric_l3_handoff_ip_transit.md new file mode 100644 index 00000000..ab618279 --- /dev/null +++ b/docs/data-sources/fabric_l3_handoff_ip_transit.md @@ -0,0 +1,42 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_fabric_l3_handoff_ip_transit Data Source - terraform-provider-catalystcenter" +subcategory: "SDA" +description: |- + This data source can read the Fabric L3 Handoff IP Transit. +--- + +# catalystcenter_fabric_l3_handoff_ip_transit (Data Source) + +This data source can read the Fabric L3 Handoff IP Transit. + +## Example Usage + +```terraform +data "catalystcenter_fabric_l3_handoff_ip_transit" "example" { + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = "c4b85bb2-ce3f-4db9-a32b-e439a388ac2f" +} +``` + + +## Schema + +### Required + +- `fabric_id` (String) ID of the fabric this device belongs to +- `network_device_id` (String) Network device ID of the fabric device + +### Read-Only + +- `external_connectivity_ip_pool_name` (String) External connectivity ip pool will be used by Catalyst Center to allocate IP address for the connection between the border node and peer +- `id` (String) The id of the object +- `interface_name` (String) Interface name of the layer 3 handoff ip transit +- `local_ip_address` (String) Local ipv4 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name +- `local_ipv6_address` (String) Local ipv6 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name +- `remote_ip_address` (String) Remote ipv4 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name +- `remote_ipv6_address` (String) Remote ipv6 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name +- `tcp_mss_adjustment` (Number) TCP maximum segment size (mss) value for the layer 3 handoff. Allowed range is [500-1440]. TCP MSS Adjustment value is applicable for the TCP sessions over both IPv4 and IPv6 +- `transit_network_id` (String) ID of the transit network of the layer 3 handoff ip transit +- `virtual_network_name` (String) SName of the virtual network associated with this fabric site +- `vlan_id` (Number) VLAN number for the Switch Virtual Interface (SVI) used to establish BGP peering with the external domain for the virtual network. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094) diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index e1fa80bf..4dbc9e98 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -8,7 +8,7 @@ description: |- # Changelog ## 0.1.10 (unreleased) - +- Add `fabric_l3_handoff_ip_transit` resource and data source - 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+ diff --git a/docs/resources/fabric_l3_handoff_ip_transit.md b/docs/resources/fabric_l3_handoff_ip_transit.md new file mode 100644 index 00000000..fa008196 --- /dev/null +++ b/docs/resources/fabric_l3_handoff_ip_transit.md @@ -0,0 +1,61 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_fabric_l3_handoff_ip_transit Resource - terraform-provider-catalystcenter" +subcategory: "SDA" +description: |- + Manages Layer 3 Handoffs with IP Transit in Fabric Devices +--- + +# catalystcenter_fabric_l3_handoff_ip_transit (Resource) + +Manages Layer 3 Handoffs with IP Transit in Fabric Devices + +## Example Usage + +```terraform +resource "catalystcenter_fabric_l3_handoff_ip_transit" "example" { + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = "c4b85bb2-ce3f-4db9-a32b-e439a388ac2f" + transit_network_id = "d71c847b-e9c2-4f13-928c-223372b72b06" + interface_name = "TenGigabitEthernet1/0/2" + virtual_network_name = "SDA_VN1" + vlan_id = 205 + tcp_mss_adjustment = 1400 + local_ip_address = "10.0.0.1/24" + remote_ip_address = "10.0.0.2/24" +} +``` + + +## Schema + +### Required + +- `fabric_id` (String) ID of the fabric this device belongs to +- `network_device_id` (String) Network device ID of the fabric device +- `transit_network_id` (String) ID of the transit network of the layer 3 handoff ip transit +- `virtual_network_name` (String) SName of the virtual network associated with this fabric site +- `vlan_id` (Number) VLAN number for the Switch Virtual Interface (SVI) used to establish BGP peering with the external domain for the virtual network. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094) + +### Optional + +- `external_connectivity_ip_pool_name` (String) External connectivity ip pool will be used by Catalyst Center to allocate IP address for the connection between the border node and peer +- `interface_name` (String) Interface name of the layer 3 handoff ip transit +- `local_ip_address` (String) Local ipv4 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name +- `local_ipv6_address` (String) Local ipv6 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name +- `remote_ip_address` (String) Remote ipv4 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name +- `remote_ipv6_address` (String) Remote ipv6 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name +- `tcp_mss_adjustment` (Number) TCP maximum segment size (mss) value for the layer 3 handoff. Allowed range is [500-1440]. TCP MSS Adjustment value is applicable for the TCP sessions over both IPv4 and IPv6 + - Range: `500`-`1440` + +### Read-Only + +- `id` (String) The id of the object + +## Import + +Import is supported using the following syntax: + +```shell +terraform import catalystcenter_fabric_l3_handoff_ip_transit.example "," +``` diff --git a/examples/data-sources/catalystcenter_fabric_l3_handoff_ip_transit/data-source.tf b/examples/data-sources/catalystcenter_fabric_l3_handoff_ip_transit/data-source.tf new file mode 100644 index 00000000..cdd99cba --- /dev/null +++ b/examples/data-sources/catalystcenter_fabric_l3_handoff_ip_transit/data-source.tf @@ -0,0 +1,4 @@ +data "catalystcenter_fabric_l3_handoff_ip_transit" "example" { + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = "c4b85bb2-ce3f-4db9-a32b-e439a388ac2f" +} diff --git a/examples/resources/catalystcenter_fabric_l3_handoff_ip_transit/import.sh b/examples/resources/catalystcenter_fabric_l3_handoff_ip_transit/import.sh new file mode 100644 index 00000000..2cdee40c --- /dev/null +++ b/examples/resources/catalystcenter_fabric_l3_handoff_ip_transit/import.sh @@ -0,0 +1 @@ +terraform import catalystcenter_fabric_l3_handoff_ip_transit.example "," diff --git a/examples/resources/catalystcenter_fabric_l3_handoff_ip_transit/resource.tf b/examples/resources/catalystcenter_fabric_l3_handoff_ip_transit/resource.tf new file mode 100644 index 00000000..a8b09a6d --- /dev/null +++ b/examples/resources/catalystcenter_fabric_l3_handoff_ip_transit/resource.tf @@ -0,0 +1,11 @@ +resource "catalystcenter_fabric_l3_handoff_ip_transit" "example" { + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = "c4b85bb2-ce3f-4db9-a32b-e439a388ac2f" + transit_network_id = "d71c847b-e9c2-4f13-928c-223372b72b06" + interface_name = "TenGigabitEthernet1/0/2" + virtual_network_name = "SDA_VN1" + vlan_id = 205 + tcp_mss_adjustment = 1400 + local_ip_address = "10.0.0.1/24" + remote_ip_address = "10.0.0.2/24" +} diff --git a/gen/definitions/fabric_l3_handoff_ip_transit.yaml b/gen/definitions/fabric_l3_handoff_ip_transit.yaml new file mode 100644 index 00000000..81ccfb5d --- /dev/null +++ b/gen/definitions/fabric_l3_handoff_ip_transit.yaml @@ -0,0 +1,125 @@ +--- +name: Fabric L3 Handoff IP Transit +rest_endpoint: /dna/intent/api/v1/sda/fabricDevices/layer3Handoffs/ipTransits +res_description: Manages Layer 3 Handoffs with IP Transit in Fabric Devices +id_from_query_path: response.0 +id_from_query_path_attribute: id +put_id_include_path: 0.id +import_no_id: true +data_source_no_id: true +put_no_id: true +doc_category: SDA +test_tags: [SDA] +attributes: + - model_name: networkDeviceId + query_param: true + requires_replace: true + data_path: '0' + response_data_path: response.0.networkDeviceId + mandatory: true + description: Network device ID of the fabric device + type: String + example: 5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1 + - model_name: fabricId + requires_replace: true + query_param: true + data_path: '0' + response_data_path: response.0.fabricId + type: String + mandatory: true + description: ID of the fabric this device belongs to + example: c4b85bb2-ce3f-4db9-a32b-e439a388ac2f + test_value: catalystcenter_fabric_site.test.id + - model_name: transitNetworkId + requires_replace: true + data_path: '0' + response_data_path: response.0.transitNetworkId + type: String + mandatory: true + description: ID of the transit network of the layer 3 handoff ip transit + example: d71c847b-e9c2-4f13-928c-223372b72b06 + test_value: catalystcenter_transit_peer_network.test.id + - model_name: interfaceName + data_path: '0' + requires_replace: true + response_data_path: response.0.interfaceName + type: String + description: Interface name of the layer 3 handoff ip transit + example: TenGigabitEthernet1/0/2 + - model_name: externalConnectivityIpPoolName + data_path: '0' + requires_replace: true + response_data_path: response.0.externalConnectivityIpPoolName + type: String + description: External connectivity ip pool will be used by Catalyst Center to allocate IP address for the connection between the border node and peer + example: "MyPool1" + exclude_test: true + - model_name: virtualNetworkName + data_path: '0' + requires_replace: true + response_data_path: response.0.virtualNetworkName + mandatory: true + type: String + description: SName of the virtual network associated with this fabric site + example: SDA_VN1 + - model_name: vlanId + data_path: '0' + requires_replace: true + response_data_path: response.0.vlanId + type: Int64 + mandatory: true + description: VLAN number for the Switch Virtual Interface (SVI) used to establish BGP peering with the external domain for the virtual network. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094) + example: 205 + - 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 (mss) value for the layer 3 handoff. Allowed range is [500-1440]. TCP MSS Adjustment value is applicable for the TCP sessions over both IPv4 and IPv6 + example: 1400 + - model_name: localIpAddress + data_path: '0' + requires_replace: true + response_data_path: response.0.localIpAddress + type: String + description: Local ipv4 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name + example: "10.0.0.1/24" + - model_name: remoteIpAddress + data_path: '0' + requires_replace: true + response_data_path: response.0.remoteIpAddress + type: String + description: Remote ipv4 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name + example: "10.0.0.2/24" + - model_name: localIpv6Address + data_path: '0' + requires_replace: true + response_data_path: response.0.localIpv6Address + type: String + description: Local ipv6 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name + exclude_test: true + - model_name: remoteIpv6Address + data_path: '0' + requires_replace: true + response_data_path: response.0.remoteIpv6Address + type: String + description: Remote ipv6 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name + exclude_test: true +test_prerequisites: | + resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" + } + 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_transit_peer_network" "test" { + transit_peer_network_name = "TRANSIT_1" + transit_peer_network_type = "ip_transit" + routing_protocol_name = "BGP" + autonomous_system_number = "65010" + } \ No newline at end of file diff --git a/internal/provider/data_source_catalystcenter_fabric_l3_handoff_ip_transit.go b/internal/provider/data_source_catalystcenter_fabric_l3_handoff_ip_transit.go new file mode 100644 index 00000000..5e34d57b --- /dev/null +++ b/internal/provider/data_source_catalystcenter_fabric_l3_handoff_ip_transit.go @@ -0,0 +1,155 @@ +// 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 = &FabricL3HandoffIPTransitDataSource{} + _ datasource.DataSourceWithConfigure = &FabricL3HandoffIPTransitDataSource{} +) + +func NewFabricL3HandoffIPTransitDataSource() datasource.DataSource { + return &FabricL3HandoffIPTransitDataSource{} +} + +type FabricL3HandoffIPTransitDataSource struct { + client *cc.Client +} + +func (d *FabricL3HandoffIPTransitDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_fabric_l3_handoff_ip_transit" +} + +func (d *FabricL3HandoffIPTransitDataSource) 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 Fabric L3 Handoff IP Transit.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + }, + "network_device_id": schema.StringAttribute{ + MarkdownDescription: "Network device ID of the fabric device", + Required: true, + }, + "fabric_id": schema.StringAttribute{ + MarkdownDescription: "ID of the fabric this device belongs to", + Required: true, + }, + "transit_network_id": schema.StringAttribute{ + MarkdownDescription: "ID of the transit network of the layer 3 handoff ip transit", + Computed: true, + }, + "interface_name": schema.StringAttribute{ + MarkdownDescription: "Interface name of the layer 3 handoff ip transit", + Computed: true, + }, + "external_connectivity_ip_pool_name": schema.StringAttribute{ + MarkdownDescription: "External connectivity ip pool will be used by Catalyst Center to allocate IP address for the connection between the border node and peer", + Computed: true, + }, + "virtual_network_name": schema.StringAttribute{ + MarkdownDescription: "SName of the virtual network associated with this fabric site", + Computed: true, + }, + "vlan_id": schema.Int64Attribute{ + MarkdownDescription: "VLAN number for the Switch Virtual Interface (SVI) used to establish BGP peering with the external domain for the virtual network. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094)", + Computed: true, + }, + "tcp_mss_adjustment": schema.Int64Attribute{ + MarkdownDescription: "TCP maximum segment size (mss) value for the layer 3 handoff. Allowed range is [500-1440]. TCP MSS Adjustment value is applicable for the TCP sessions over both IPv4 and IPv6", + Computed: true, + }, + "local_ip_address": schema.StringAttribute{ + MarkdownDescription: "Local ipv4 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name", + Computed: true, + }, + "remote_ip_address": schema.StringAttribute{ + MarkdownDescription: "Remote ipv4 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name", + Computed: true, + }, + "local_ipv6_address": schema.StringAttribute{ + MarkdownDescription: "Local ipv6 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name", + Computed: true, + }, + "remote_ipv6_address": schema.StringAttribute{ + MarkdownDescription: "Remote ipv6 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name", + Computed: true, + }, + }, + } +} + +func (d *FabricL3HandoffIPTransitDataSource) 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 *FabricL3HandoffIPTransitDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config FabricL3HandoffIPTransit + + // 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 += "?networkDeviceId=" + url.QueryEscape(config.NetworkDeviceId.ValueString()) + "&fabricId=" + url.QueryEscape(config.FabricId.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_fabric_l3_handoff_ip_transit_test.go b/internal/provider/data_source_catalystcenter_fabric_l3_handoff_ip_transit_test.go new file mode 100644 index 00000000..11cd64f7 --- /dev/null +++ b/internal/provider/data_source_catalystcenter_fabric_l3_handoff_ip_transit_test.go @@ -0,0 +1,103 @@ +// 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 testAccDataSource +func TestAccDataSourceCcFabricL3HandoffIPTransit(t *testing.T) { + if os.Getenv("SDA") == "" { + t.Skip("skipping test, set environment variable SDA") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_l3_handoff_ip_transit.test", "network_device_id", "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_l3_handoff_ip_transit.test", "interface_name", "TenGigabitEthernet1/0/2")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_l3_handoff_ip_transit.test", "virtual_network_name", "SDA_VN1")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_l3_handoff_ip_transit.test", "vlan_id", "205")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_l3_handoff_ip_transit.test", "tcp_mss_adjustment", "1400")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_l3_handoff_ip_transit.test", "local_ip_address", "10.0.0.1/24")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_l3_handoff_ip_transit.test", "remote_ip_address", "10.0.0.2/24")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceCcFabricL3HandoffIPTransitPrerequisitesConfig + testAccDataSourceCcFabricL3HandoffIPTransitConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAccDataSource + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites +const testAccDataSourceCcFabricL3HandoffIPTransitPrerequisitesConfig = ` +resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" +} +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_transit_peer_network" "test" { + transit_peer_network_name = "TRANSIT_1" + transit_peer_network_type = "ip_transit" + routing_protocol_name = "BGP" + autonomous_system_number = "65010" +} +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig +func testAccDataSourceCcFabricL3HandoffIPTransitConfig() string { + config := `resource "catalystcenter_fabric_l3_handoff_ip_transit" "test" {` + "\n" + config += ` network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1"` + "\n" + config += ` fabric_id = catalystcenter_fabric_site.test.id` + "\n" + config += ` transit_network_id = catalystcenter_transit_peer_network.test.id` + "\n" + config += ` interface_name = "TenGigabitEthernet1/0/2"` + "\n" + config += ` virtual_network_name = "SDA_VN1"` + "\n" + config += ` vlan_id = 205` + "\n" + config += ` tcp_mss_adjustment = 1400` + "\n" + config += ` local_ip_address = "10.0.0.1/24"` + "\n" + config += ` remote_ip_address = "10.0.0.2/24"` + "\n" + config += `}` + "\n" + + config += ` + data "catalystcenter_fabric_l3_handoff_ip_transit" "test" { + id = catalystcenter_fabric_l3_handoff_ip_transit.test.id + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = catalystcenter_fabric_site.test.id + } + ` + return config +} + +// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/model_catalystcenter_fabric_l3_handoff_ip_transit.go b/internal/provider/model_catalystcenter_fabric_l3_handoff_ip_transit.go new file mode 100644 index 00000000..36767ee7 --- /dev/null +++ b/internal/provider/model_catalystcenter_fabric_l3_handoff_ip_transit.go @@ -0,0 +1,284 @@ +// 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 FabricL3HandoffIPTransit struct { + Id types.String `tfsdk:"id"` + NetworkDeviceId types.String `tfsdk:"network_device_id"` + FabricId types.String `tfsdk:"fabric_id"` + TransitNetworkId types.String `tfsdk:"transit_network_id"` + InterfaceName types.String `tfsdk:"interface_name"` + ExternalConnectivityIpPoolName types.String `tfsdk:"external_connectivity_ip_pool_name"` + VirtualNetworkName types.String `tfsdk:"virtual_network_name"` + VlanId types.Int64 `tfsdk:"vlan_id"` + TcpMssAdjustment types.Int64 `tfsdk:"tcp_mss_adjustment"` + LocalIpAddress types.String `tfsdk:"local_ip_address"` + RemoteIpAddress types.String `tfsdk:"remote_ip_address"` + LocalIpv6Address types.String `tfsdk:"local_ipv6_address"` + RemoteIpv6Address types.String `tfsdk:"remote_ipv6_address"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath +func (data FabricL3HandoffIPTransit) getPath() string { + return "/dna/intent/api/v1/sda/fabricDevices/layer3Handoffs/ipTransits" +} + +// 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 FabricL3HandoffIPTransit) toBody(ctx context.Context, state FabricL3HandoffIPTransit) string { + body := "" + put := false + if state.Id.ValueString() != "" { + put = true + body, _ = sjson.Set(body, "0.id", state.Id.ValueString()) + } + _ = put + if !data.NetworkDeviceId.IsNull() { + body, _ = sjson.Set(body, "0.networkDeviceId", data.NetworkDeviceId.ValueString()) + } + if !data.FabricId.IsNull() { + body, _ = sjson.Set(body, "0.fabricId", data.FabricId.ValueString()) + } + if !data.TransitNetworkId.IsNull() { + body, _ = sjson.Set(body, "0.transitNetworkId", data.TransitNetworkId.ValueString()) + } + if !data.InterfaceName.IsNull() { + body, _ = sjson.Set(body, "0.interfaceName", data.InterfaceName.ValueString()) + } + if !data.ExternalConnectivityIpPoolName.IsNull() { + body, _ = sjson.Set(body, "0.externalConnectivityIpPoolName", data.ExternalConnectivityIpPoolName.ValueString()) + } + if !data.VirtualNetworkName.IsNull() { + body, _ = sjson.Set(body, "0.virtualNetworkName", data.VirtualNetworkName.ValueString()) + } + if !data.VlanId.IsNull() { + body, _ = sjson.Set(body, "0.vlanId", data.VlanId.ValueInt64()) + } + if !data.TcpMssAdjustment.IsNull() { + body, _ = sjson.Set(body, "0.tcpMssAdjustment", data.TcpMssAdjustment.ValueInt64()) + } + if !data.LocalIpAddress.IsNull() { + body, _ = sjson.Set(body, "0.localIpAddress", data.LocalIpAddress.ValueString()) + } + if !data.RemoteIpAddress.IsNull() { + body, _ = sjson.Set(body, "0.remoteIpAddress", data.RemoteIpAddress.ValueString()) + } + if !data.LocalIpv6Address.IsNull() { + body, _ = sjson.Set(body, "0.localIpv6Address", data.LocalIpv6Address.ValueString()) + } + if !data.RemoteIpv6Address.IsNull() { + body, _ = sjson.Set(body, "0.remoteIpv6Address", data.RemoteIpv6Address.ValueString()) + } + return body +} + +// End of section. //template:end toBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBody +func (data *FabricL3HandoffIPTransit) 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.networkDeviceId"); value.Exists() { + data.NetworkDeviceId = types.StringValue(value.String()) + } else { + data.NetworkDeviceId = 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.transitNetworkId"); value.Exists() { + data.TransitNetworkId = types.StringValue(value.String()) + } else { + data.TransitNetworkId = types.StringNull() + } + if value := res.Get("response.0.interfaceName"); value.Exists() { + data.InterfaceName = types.StringValue(value.String()) + } else { + data.InterfaceName = types.StringNull() + } + if value := res.Get("response.0.externalConnectivityIpPoolName"); value.Exists() { + data.ExternalConnectivityIpPoolName = types.StringValue(value.String()) + } else { + data.ExternalConnectivityIpPoolName = 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.vlanId"); value.Exists() { + data.VlanId = types.Int64Value(value.Int()) + } else { + data.VlanId = types.Int64Null() + } + 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.localIpAddress"); value.Exists() { + data.LocalIpAddress = types.StringValue(value.String()) + } else { + data.LocalIpAddress = types.StringNull() + } + if value := res.Get("response.0.remoteIpAddress"); value.Exists() { + data.RemoteIpAddress = types.StringValue(value.String()) + } else { + data.RemoteIpAddress = types.StringNull() + } + if value := res.Get("response.0.localIpv6Address"); value.Exists() { + data.LocalIpv6Address = types.StringValue(value.String()) + } else { + data.LocalIpv6Address = types.StringNull() + } + if value := res.Get("response.0.remoteIpv6Address"); value.Exists() { + data.RemoteIpv6Address = types.StringValue(value.String()) + } else { + data.RemoteIpv6Address = types.StringNull() + } +} + +// End of section. //template:end fromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin updateFromBody +func (data *FabricL3HandoffIPTransit) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.0.networkDeviceId"); value.Exists() && !data.NetworkDeviceId.IsNull() { + data.NetworkDeviceId = types.StringValue(value.String()) + } else { + data.NetworkDeviceId = types.StringNull() + } + 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.transitNetworkId"); value.Exists() && !data.TransitNetworkId.IsNull() { + data.TransitNetworkId = types.StringValue(value.String()) + } else { + data.TransitNetworkId = types.StringNull() + } + if value := res.Get("response.0.interfaceName"); value.Exists() && !data.InterfaceName.IsNull() { + data.InterfaceName = types.StringValue(value.String()) + } else { + data.InterfaceName = types.StringNull() + } + if value := res.Get("response.0.externalConnectivityIpPoolName"); value.Exists() && !data.ExternalConnectivityIpPoolName.IsNull() { + data.ExternalConnectivityIpPoolName = types.StringValue(value.String()) + } else { + data.ExternalConnectivityIpPoolName = 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.vlanId"); value.Exists() && !data.VlanId.IsNull() { + data.VlanId = types.Int64Value(value.Int()) + } else { + data.VlanId = types.Int64Null() + } + 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.localIpAddress"); value.Exists() && !data.LocalIpAddress.IsNull() { + data.LocalIpAddress = types.StringValue(value.String()) + } else { + data.LocalIpAddress = types.StringNull() + } + if value := res.Get("response.0.remoteIpAddress"); value.Exists() && !data.RemoteIpAddress.IsNull() { + data.RemoteIpAddress = types.StringValue(value.String()) + } else { + data.RemoteIpAddress = types.StringNull() + } + if value := res.Get("response.0.localIpv6Address"); value.Exists() && !data.LocalIpv6Address.IsNull() { + data.LocalIpv6Address = types.StringValue(value.String()) + } else { + data.LocalIpv6Address = types.StringNull() + } + if value := res.Get("response.0.remoteIpv6Address"); value.Exists() && !data.RemoteIpv6Address.IsNull() { + data.RemoteIpv6Address = types.StringValue(value.String()) + } else { + data.RemoteIpv6Address = types.StringNull() + } +} + +// End of section. //template:end updateFromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin isNull +func (data *FabricL3HandoffIPTransit) isNull(ctx context.Context, res gjson.Result) bool { + if !data.TransitNetworkId.IsNull() { + return false + } + if !data.InterfaceName.IsNull() { + return false + } + if !data.ExternalConnectivityIpPoolName.IsNull() { + return false + } + if !data.VirtualNetworkName.IsNull() { + return false + } + if !data.VlanId.IsNull() { + return false + } + if !data.TcpMssAdjustment.IsNull() { + return false + } + if !data.LocalIpAddress.IsNull() { + return false + } + if !data.RemoteIpAddress.IsNull() { + return false + } + if !data.LocalIpv6Address.IsNull() { + return false + } + if !data.RemoteIpv6Address.IsNull() { + return false + } + return true +} + +// End of section. //template:end isNull diff --git a/internal/provider/provider.go b/internal/provider/provider.go index df1d9aa0..636d9aae 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -254,6 +254,7 @@ func (p *CcProvider) Resources(ctx context.Context) []func() resource.Resource { NewDeviceRoleResource, NewDiscoveryResource, NewFabricAuthenticationProfileResource, + NewFabricL3HandoffIPTransitResource, NewFabricSiteResource, NewFabricVirtualNetworkResource, NewFloorResource, @@ -299,6 +300,7 @@ func (p *CcProvider) DataSources(ctx context.Context) []func() datasource.DataSo NewDeviceDetailDataSource, NewDiscoveryDataSource, NewFabricAuthenticationProfileDataSource, + NewFabricL3HandoffIPTransitDataSource, NewFabricSiteDataSource, NewFabricVirtualNetworkDataSource, NewFloorDataSource, diff --git a/internal/provider/resource_catalystcenter_fabric_l3_handoff_ip_transit.go b/internal/provider/resource_catalystcenter_fabric_l3_handoff_ip_transit.go new file mode 100644 index 00000000..94fce82e --- /dev/null +++ b/internal/provider/resource_catalystcenter_fabric_l3_handoff_ip_transit.go @@ -0,0 +1,325 @@ +// 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/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "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 = &FabricL3HandoffIPTransitResource{} +var _ resource.ResourceWithImportState = &FabricL3HandoffIPTransitResource{} + +func NewFabricL3HandoffIPTransitResource() resource.Resource { + return &FabricL3HandoffIPTransitResource{} +} + +type FabricL3HandoffIPTransitResource struct { + client *cc.Client +} + +func (r *FabricL3HandoffIPTransitResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_fabric_l3_handoff_ip_transit" +} + +func (r *FabricL3HandoffIPTransitResource) 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 Layer 3 Handoffs with IP Transit in Fabric Devices").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "network_device_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Network device ID of the fabric device").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "fabric_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("ID of the fabric this device belongs to").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "transit_network_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("ID of the transit network of the layer 3 handoff ip transit").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "interface_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Interface name of the layer 3 handoff ip transit").String, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "external_connectivity_ip_pool_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("External connectivity ip pool will be used by Catalyst Center to allocate IP address for the connection between the border node and peer").String, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "virtual_network_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("SName of the virtual network associated with this fabric site").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "vlan_id": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("VLAN number for the Switch Virtual Interface (SVI) used to establish BGP peering with the external domain for the virtual network. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094)").String, + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "tcp_mss_adjustment": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("TCP maximum segment size (mss) value for the layer 3 handoff. Allowed range is [500-1440]. TCP MSS Adjustment value is applicable for the TCP sessions over both IPv4 and IPv6").AddIntegerRangeDescription(500, 1440).String, + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(500, 1440), + }, + }, + "local_ip_address": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Local ipv4 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name").String, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "remote_ip_address": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Remote ipv4 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name").String, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "local_ipv6_address": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Local ipv6 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name").String, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "remote_ipv6_address": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Remote ipv6 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name").String, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (r *FabricL3HandoffIPTransitResource) 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 *FabricL3HandoffIPTransitResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan FabricL3HandoffIPTransit + + // 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, FabricL3HandoffIPTransit{}) + + params := "" + res, err := r.client.Post(plan.getPath()+params, body) + 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 += "?networkDeviceId=" + url.QueryEscape(plan.NetworkDeviceId.ValueString()) + "&fabricId=" + url.QueryEscape(plan.FabricId.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 *FabricL3HandoffIPTransitResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state FabricL3HandoffIPTransit + + // 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 += "?networkDeviceId=" + url.QueryEscape(state.NetworkDeviceId.ValueString()) + "&fabricId=" + url.QueryEscape(state.FabricId.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 *FabricL3HandoffIPTransitResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state FabricL3HandoffIPTransit + + // 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) + 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 *FabricL3HandoffIPTransitResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state FabricL3HandoffIPTransit + + // 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 *FabricL3HandoffIPTransitResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, ",") + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + 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("network_device_id"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("fabric_id"), idParts[1])...) +} + +// End of section. //template:end import diff --git a/internal/provider/resource_catalystcenter_fabric_l3_handoff_ip_transit_test.go b/internal/provider/resource_catalystcenter_fabric_l3_handoff_ip_transit_test.go new file mode 100644 index 00000000..47cf3946 --- /dev/null +++ b/internal/provider/resource_catalystcenter_fabric_l3_handoff_ip_transit_test.go @@ -0,0 +1,116 @@ +// 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 TestAccCcFabricL3HandoffIPTransit(t *testing.T) { + if os.Getenv("SDA") == "" { + t.Skip("skipping test, set environment variable SDA") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_l3_handoff_ip_transit.test", "network_device_id", "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_l3_handoff_ip_transit.test", "interface_name", "TenGigabitEthernet1/0/2")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_l3_handoff_ip_transit.test", "virtual_network_name", "SDA_VN1")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_l3_handoff_ip_transit.test", "vlan_id", "205")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_l3_handoff_ip_transit.test", "tcp_mss_adjustment", "1400")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_l3_handoff_ip_transit.test", "local_ip_address", "10.0.0.1/24")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_l3_handoff_ip_transit.test", "remote_ip_address", "10.0.0.2/24")) + + var steps []resource.TestStep + if os.Getenv("SKIP_MINIMUM_TEST") == "" { + steps = append(steps, resource.TestStep{ + Config: testAccCcFabricL3HandoffIPTransitPrerequisitesConfig + testAccCcFabricL3HandoffIPTransitConfig_minimum(), + }) + } + steps = append(steps, resource.TestStep{ + Config: testAccCcFabricL3HandoffIPTransitPrerequisitesConfig + testAccCcFabricL3HandoffIPTransitConfig_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 testAccCcFabricL3HandoffIPTransitPrerequisitesConfig = ` +resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" +} +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_transit_peer_network" "test" { + transit_peer_network_name = "TRANSIT_1" + transit_peer_network_type = "ip_transit" + routing_protocol_name = "BGP" + autonomous_system_number = "65010" +} +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal +func testAccCcFabricL3HandoffIPTransitConfig_minimum() string { + config := `resource "catalystcenter_fabric_l3_handoff_ip_transit" "test" {` + "\n" + config += ` network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1"` + "\n" + config += ` fabric_id = catalystcenter_fabric_site.test.id` + "\n" + config += ` transit_network_id = catalystcenter_transit_peer_network.test.id` + "\n" + config += ` virtual_network_name = "SDA_VN1"` + "\n" + config += ` vlan_id = 205` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll +func testAccCcFabricL3HandoffIPTransitConfig_all() string { + config := `resource "catalystcenter_fabric_l3_handoff_ip_transit" "test" {` + "\n" + config += ` network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1"` + "\n" + config += ` fabric_id = catalystcenter_fabric_site.test.id` + "\n" + config += ` transit_network_id = catalystcenter_transit_peer_network.test.id` + "\n" + config += ` interface_name = "TenGigabitEthernet1/0/2"` + "\n" + config += ` virtual_network_name = "SDA_VN1"` + "\n" + config += ` vlan_id = 205` + "\n" + config += ` tcp_mss_adjustment = 1400` + "\n" + config += ` local_ip_address = "10.0.0.1/24"` + "\n" + config += ` remote_ip_address = "10.0.0.2/24"` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index e1fa80bf..4dbc9e98 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -8,7 +8,7 @@ description: |- # Changelog ## 0.1.10 (unreleased) - +- Add `fabric_l3_handoff_ip_transit` resource and data source - 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+ From 59173f31d772b3b4b955e3140b4f43e3453d2705 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Sun, 21 Jul 2024 20:46:13 +0200 Subject: [PATCH 10/31] add fabric_device resource and data source --- CHANGELOG.md | 2 + docs/data-sources/fabric_device.md | 39 +++ docs/guides/changelog.md | 2 + docs/resources/fabric_device.md | 60 ++++ .../data-source.tf | 4 + .../catalystcenter_fabric_device/import.sh | 1 + .../catalystcenter_fabric_device/resource.tf | 11 + gen/definitions/fabric_device.yaml | 96 ++++++ ...ata_source_catalystcenter_fabric_device.go | 146 +++++++++ ...ource_catalystcenter_fabric_device_test.go | 98 ++++++ .../model_catalystcenter_fabric_device.go | 238 ++++++++++++++ internal/provider/provider.go | 2 + .../resource_catalystcenter_fabric_device.go | 297 ++++++++++++++++++ ...ource_catalystcenter_fabric_device_test.go | 109 +++++++ templates/guides/changelog.md.tmpl | 2 + 15 files changed, 1107 insertions(+) create mode 100644 docs/data-sources/fabric_device.md create mode 100644 docs/resources/fabric_device.md create mode 100644 examples/data-sources/catalystcenter_fabric_device/data-source.tf create mode 100644 examples/resources/catalystcenter_fabric_device/import.sh create mode 100644 examples/resources/catalystcenter_fabric_device/resource.tf create mode 100644 gen/definitions/fabric_device.yaml create mode 100644 internal/provider/data_source_catalystcenter_fabric_device.go create mode 100644 internal/provider/data_source_catalystcenter_fabric_device_test.go create mode 100644 internal/provider/model_catalystcenter_fabric_device.go create mode 100644 internal/provider/resource_catalystcenter_fabric_device.go create mode 100644 internal/provider/resource_catalystcenter_fabric_device_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 147e9c33..b01083a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ ## 0.1.10 (unreleased) + +- Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` - Add `fabric_l3_handoff_ip_transit` resource and data source - 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` diff --git a/docs/data-sources/fabric_device.md b/docs/data-sources/fabric_device.md new file mode 100644 index 00000000..db05c843 --- /dev/null +++ b/docs/data-sources/fabric_device.md @@ -0,0 +1,39 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_fabric_device Data Source - terraform-provider-catalystcenter" +subcategory: "SDA" +description: |- + This data source can read the Fabric Device. +--- + +# catalystcenter_fabric_device (Data Source) + +This data source can read the Fabric Device. + +## Example Usage + +```terraform +data "catalystcenter_fabric_device" "example" { + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = "c4b85bb2-ce3f-4db9-a32b-e439a388ac2f" +} +``` + + +## Schema + +### Required + +- `fabric_id` (String) ID of the fabric site/zone of this fabric device +- `network_device_id` (String) Network device ID of the fabric device + +### Read-Only + +- `border_priority` (Number) Border priority of the fabric border device. A lower value indicates higher priority +- `border_types` (List of String) List of the border types of the fabric device. Allowed values are [LAYER_2, LAYER_3] +- `default_exit` (Boolean) Set this to make the fabric border device the gateway of last resort for this site. Any unknown traffic will be sent to this fabric border device from edge nodes +- `device_roles` (List of String) List of the roles of the fabric device. Allowed values are [CONTROL_PLANE_NODE, EDGE_NODE, BORDER_NODE] +- `id` (String) The id of the object +- `import_external_routes` (Boolean) Set this to import external routes from other routing protocols (such as BGP) to the fabric control plane +- `local_autonomous_system_number` (String) BGP Local autonomous system number of the fabric border device +- `prepend_autonomous_system_count` (Number) Prepend autonomous system count of the fabric border device diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index 4dbc9e98..b3fd9c72 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -8,6 +8,8 @@ description: |- # Changelog ## 0.1.10 (unreleased) + +- Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` - Add `fabric_l3_handoff_ip_transit` resource and data source - 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` diff --git a/docs/resources/fabric_device.md b/docs/resources/fabric_device.md new file mode 100644 index 00000000..21a96e4c --- /dev/null +++ b/docs/resources/fabric_device.md @@ -0,0 +1,60 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_fabric_device Resource - terraform-provider-catalystcenter" +subcategory: "SDA" +description: |- + Manages Fabric Devices +--- + +# catalystcenter_fabric_device (Resource) + +Manages Fabric Devices + +## Example Usage + +```terraform +resource "catalystcenter_fabric_device" "example" { + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = "c4b85bb2-ce3f-4db9-a32b-e439a388ac2f" + device_roles = ["CONTROL_PLANE_NODE"] + border_types = ["LAYER_3"] + local_autonomous_system_number = "65000" + default_exit = true + import_external_routes = false + border_priority = 5 + prepend_autonomous_system_count = 1 +} +``` + + +## Schema + +### Required + +- `device_roles` (List of String) List of the roles of the fabric device. Allowed values are [CONTROL_PLANE_NODE, EDGE_NODE, BORDER_NODE] +- `fabric_id` (String) ID of the fabric site/zone of this fabric device +- `network_device_id` (String) Network device ID of the fabric device + +### Optional + +- `border_priority` (Number) Border priority of the fabric border device. A lower value indicates higher priority + - Range: `1`-`9` + - Default value: `10` +- `border_types` (List of String) List of the border types of the fabric device. Allowed values are [LAYER_2, LAYER_3] +- `default_exit` (Boolean) Set this to make the fabric border device the gateway of last resort for this site. Any unknown traffic will be sent to this fabric border device from edge nodes +- `import_external_routes` (Boolean) Set this to import external routes from other routing protocols (such as BGP) to the fabric control plane +- `local_autonomous_system_number` (String) BGP Local autonomous system number of the fabric border device +- `prepend_autonomous_system_count` (Number) Prepend autonomous system count of the fabric border device + - Range: `1`-`10` + +### Read-Only + +- `id` (String) The id of the object + +## Import + +Import is supported using the following syntax: + +```shell +terraform import catalystcenter_fabric_device.example "," +``` diff --git a/examples/data-sources/catalystcenter_fabric_device/data-source.tf b/examples/data-sources/catalystcenter_fabric_device/data-source.tf new file mode 100644 index 00000000..fd501f24 --- /dev/null +++ b/examples/data-sources/catalystcenter_fabric_device/data-source.tf @@ -0,0 +1,4 @@ +data "catalystcenter_fabric_device" "example" { + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = "c4b85bb2-ce3f-4db9-a32b-e439a388ac2f" +} diff --git a/examples/resources/catalystcenter_fabric_device/import.sh b/examples/resources/catalystcenter_fabric_device/import.sh new file mode 100644 index 00000000..4196a556 --- /dev/null +++ b/examples/resources/catalystcenter_fabric_device/import.sh @@ -0,0 +1 @@ +terraform import catalystcenter_fabric_device.example "," diff --git a/examples/resources/catalystcenter_fabric_device/resource.tf b/examples/resources/catalystcenter_fabric_device/resource.tf new file mode 100644 index 00000000..a5f1c1d8 --- /dev/null +++ b/examples/resources/catalystcenter_fabric_device/resource.tf @@ -0,0 +1,11 @@ +resource "catalystcenter_fabric_device" "example" { + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = "c4b85bb2-ce3f-4db9-a32b-e439a388ac2f" + device_roles = ["CONTROL_PLANE_NODE"] + border_types = ["LAYER_3"] + local_autonomous_system_number = "65000" + default_exit = true + import_external_routes = false + border_priority = 5 + prepend_autonomous_system_count = 1 +} diff --git a/gen/definitions/fabric_device.yaml b/gen/definitions/fabric_device.yaml new file mode 100644 index 00000000..41b5c34d --- /dev/null +++ b/gen/definitions/fabric_device.yaml @@ -0,0 +1,96 @@ +--- +name: Fabric Device +rest_endpoint: /dna/intent/api/v1/sda/fabricDevices +res_description: Manages Fabric Devices +id_from_query_path: response.0 +id_from_query_path_attribute: id +put_id_include_path: 0.id +import_no_id: true +data_source_no_id: true +put_no_id: true +max_async_wait_time: 120 +doc_category: SDA +test_tags: [SDA] +attributes: + - model_name: networkDeviceId + query_param: true + requires_replace: true + data_path: '0' + response_data_path: response.0.networkDeviceId + mandatory: true + description: Network device ID of the fabric device + type: String + example: 5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1 + - model_name: fabricId + requires_replace: true + query_param: true + data_path: '0' + response_data_path: response.0.fabricId + type: String + mandatory: true + description: ID of the fabric site/zone of this fabric device + example: c4b85bb2-ce3f-4db9-a32b-e439a388ac2f + test_value: catalystcenter_fabric_site.test.id + - model_name: deviceRoles + requires_replace: true + data_path: '0' + response_data_path: response.0.deviceRoles + type: List + element_type: String + mandatory: true + description: List of the roles of the fabric device. Allowed values are [CONTROL_PLANE_NODE, EDGE_NODE, BORDER_NODE] + example: CONTROL_PLANE_NODE + - model_name: borderTypes + data_path: '0.borderDeviceSettings' + response_data_path: response.0.borderDeviceSettings.borderTypes + type: List + element_type: String + description: List of the border types of the fabric device. Allowed values are [LAYER_2, LAYER_3] + example: LAYER_3 + - model_name: localAutonomousSystemNumber + data_path: '0.borderDeviceSettings.layer3Settings' + response_data_path: response.0.borderDeviceSettings.layer3Settings.localAutonomousSystemNumber + type: String + description: BGP Local autonomous system number of the fabric border device + example: "65000" + - model_name: isDefaultExit + tf_name: default_exit + data_path: '0.borderDeviceSettings.layer3Settings' + response_data_path: response.0.borderDeviceSettings.layer3Settings.isDefaultExit + type: Bool + description: Set this to make the fabric border device the gateway of last resort for this site. Any unknown traffic will be sent to this fabric border device from edge nodes + example: true + - model_name: importExternalRoutes + data_path: '0.borderDeviceSettings.layer3Settings' + response_data_path: response.0.borderDeviceSettings.layer3Settings.importExternalRoutes + type: Bool + description: Set this to import external routes from other routing protocols (such as BGP) to the fabric control plane + example: false + - model_name: borderPriority + data_path: '0.borderDeviceSettings.layer3Settings' + response_data_path: response.0.borderDeviceSettings.layer3Settings.borderPriority + type: Int64 + min_int: 1 + max_int: 9 + description: Border priority of the fabric border device. A lower value indicates higher priority + example: 5 + default_value: 10 + - model_name: prependAutonomousSystemCount + data_path: '0.borderDeviceSettings.layer3Settings' + response_data_path: response.0.borderDeviceSettings.layer3Settings.prependAutonomousSystemCount + type: Int64 + description: Prepend autonomous system count of the fabric border device + example: 1 + min_int: 1 + max_int: 10 +test_prerequisites: | + resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" + } + 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] + } \ No newline at end of file diff --git a/internal/provider/data_source_catalystcenter_fabric_device.go b/internal/provider/data_source_catalystcenter_fabric_device.go new file mode 100644 index 00000000..336e4c48 --- /dev/null +++ b/internal/provider/data_source_catalystcenter_fabric_device.go @@ -0,0 +1,146 @@ +// 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-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 the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &FabricDeviceDataSource{} + _ datasource.DataSourceWithConfigure = &FabricDeviceDataSource{} +) + +func NewFabricDeviceDataSource() datasource.DataSource { + return &FabricDeviceDataSource{} +} + +type FabricDeviceDataSource struct { + client *cc.Client +} + +func (d *FabricDeviceDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_fabric_device" +} + +func (d *FabricDeviceDataSource) 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 Fabric Device.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + }, + "network_device_id": schema.StringAttribute{ + MarkdownDescription: "Network device ID of the fabric device", + Required: true, + }, + "fabric_id": schema.StringAttribute{ + MarkdownDescription: "ID of the fabric site/zone of this fabric device", + Required: true, + }, + "device_roles": schema.ListAttribute{ + MarkdownDescription: "List of the roles of the fabric device. Allowed values are [CONTROL_PLANE_NODE, EDGE_NODE, BORDER_NODE]", + ElementType: types.StringType, + Computed: true, + }, + "border_types": schema.ListAttribute{ + MarkdownDescription: "List of the border types of the fabric device. Allowed values are [LAYER_2, LAYER_3]", + ElementType: types.StringType, + Computed: true, + }, + "local_autonomous_system_number": schema.StringAttribute{ + MarkdownDescription: "BGP Local autonomous system number of the fabric border device", + Computed: true, + }, + "default_exit": schema.BoolAttribute{ + MarkdownDescription: "Set this to make the fabric border device the gateway of last resort for this site. Any unknown traffic will be sent to this fabric border device from edge nodes", + Computed: true, + }, + "import_external_routes": schema.BoolAttribute{ + MarkdownDescription: "Set this to import external routes from other routing protocols (such as BGP) to the fabric control plane", + Computed: true, + }, + "border_priority": schema.Int64Attribute{ + MarkdownDescription: "Border priority of the fabric border device. A lower value indicates higher priority", + Computed: true, + }, + "prepend_autonomous_system_count": schema.Int64Attribute{ + MarkdownDescription: "Prepend autonomous system count of the fabric border device", + Computed: true, + }, + }, + } +} + +func (d *FabricDeviceDataSource) 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 *FabricDeviceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config FabricDevice + + // 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 += "?networkDeviceId=" + url.QueryEscape(config.NetworkDeviceId.ValueString()) + "&fabricId=" + url.QueryEscape(config.FabricId.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_fabric_device_test.go b/internal/provider/data_source_catalystcenter_fabric_device_test.go new file mode 100644 index 00000000..8260e73f --- /dev/null +++ b/internal/provider/data_source_catalystcenter_fabric_device_test.go @@ -0,0 +1,98 @@ +// 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 testAccDataSource +func TestAccDataSourceCcFabricDevice(t *testing.T) { + if os.Getenv("SDA") == "" { + t.Skip("skipping test, set environment variable SDA") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_device.test", "network_device_id", "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_device.test", "device_roles.0", "CONTROL_PLANE_NODE")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_device.test", "border_types.0", "LAYER_3")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_device.test", "local_autonomous_system_number", "65000")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_device.test", "default_exit", "true")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_device.test", "import_external_routes", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_device.test", "border_priority", "5")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_device.test", "prepend_autonomous_system_count", "1")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceCcFabricDevicePrerequisitesConfig + testAccDataSourceCcFabricDeviceConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAccDataSource + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites +const testAccDataSourceCcFabricDevicePrerequisitesConfig = ` +resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" +} +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] +} +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig +func testAccDataSourceCcFabricDeviceConfig() string { + config := `resource "catalystcenter_fabric_device" "test" {` + "\n" + config += ` network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1"` + "\n" + config += ` fabric_id = catalystcenter_fabric_site.test.id` + "\n" + config += ` device_roles = ["CONTROL_PLANE_NODE"]` + "\n" + config += ` border_types = ["LAYER_3"]` + "\n" + config += ` local_autonomous_system_number = "65000"` + "\n" + config += ` default_exit = true` + "\n" + config += ` import_external_routes = false` + "\n" + config += ` border_priority = 5` + "\n" + config += ` prepend_autonomous_system_count = 1` + "\n" + config += `}` + "\n" + + config += ` + data "catalystcenter_fabric_device" "test" { + id = catalystcenter_fabric_device.test.id + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = catalystcenter_fabric_site.test.id + } + ` + return config +} + +// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/model_catalystcenter_fabric_device.go b/internal/provider/model_catalystcenter_fabric_device.go new file mode 100644 index 00000000..5544a987 --- /dev/null +++ b/internal/provider/model_catalystcenter_fabric_device.go @@ -0,0 +1,238 @@ +// 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/CiscoDevNet/terraform-provider-catalystcenter/internal/provider/helpers" + "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 FabricDevice struct { + Id types.String `tfsdk:"id"` + NetworkDeviceId types.String `tfsdk:"network_device_id"` + FabricId types.String `tfsdk:"fabric_id"` + DeviceRoles types.List `tfsdk:"device_roles"` + BorderTypes types.List `tfsdk:"border_types"` + LocalAutonomousSystemNumber types.String `tfsdk:"local_autonomous_system_number"` + DefaultExit types.Bool `tfsdk:"default_exit"` + ImportExternalRoutes types.Bool `tfsdk:"import_external_routes"` + BorderPriority types.Int64 `tfsdk:"border_priority"` + PrependAutonomousSystemCount types.Int64 `tfsdk:"prepend_autonomous_system_count"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath +func (data FabricDevice) getPath() string { + return "/dna/intent/api/v1/sda/fabricDevices" +} + +// 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 FabricDevice) toBody(ctx context.Context, state FabricDevice) string { + body := "" + put := false + if state.Id.ValueString() != "" { + put = true + body, _ = sjson.Set(body, "0.id", state.Id.ValueString()) + } + _ = put + if !data.NetworkDeviceId.IsNull() { + body, _ = sjson.Set(body, "0.networkDeviceId", data.NetworkDeviceId.ValueString()) + } + if !data.FabricId.IsNull() { + body, _ = sjson.Set(body, "0.fabricId", data.FabricId.ValueString()) + } + if !data.DeviceRoles.IsNull() { + var values []string + data.DeviceRoles.ElementsAs(ctx, &values, false) + body, _ = sjson.Set(body, "0.deviceRoles", values) + } + if !data.BorderTypes.IsNull() { + var values []string + data.BorderTypes.ElementsAs(ctx, &values, false) + body, _ = sjson.Set(body, "0.borderDeviceSettings.borderTypes", values) + } + if !data.LocalAutonomousSystemNumber.IsNull() { + body, _ = sjson.Set(body, "0.borderDeviceSettings.layer3Settings.localAutonomousSystemNumber", data.LocalAutonomousSystemNumber.ValueString()) + } + if !data.DefaultExit.IsNull() { + body, _ = sjson.Set(body, "0.borderDeviceSettings.layer3Settings.isDefaultExit", data.DefaultExit.ValueBool()) + } + if !data.ImportExternalRoutes.IsNull() { + body, _ = sjson.Set(body, "0.borderDeviceSettings.layer3Settings.importExternalRoutes", data.ImportExternalRoutes.ValueBool()) + } + if !data.BorderPriority.IsNull() { + body, _ = sjson.Set(body, "0.borderDeviceSettings.layer3Settings.borderPriority", data.BorderPriority.ValueInt64()) + } + if !data.PrependAutonomousSystemCount.IsNull() { + body, _ = sjson.Set(body, "0.borderDeviceSettings.layer3Settings.prependAutonomousSystemCount", data.PrependAutonomousSystemCount.ValueInt64()) + } + return body +} + +// End of section. //template:end toBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBody +func (data *FabricDevice) 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.networkDeviceId"); value.Exists() { + data.NetworkDeviceId = types.StringValue(value.String()) + } else { + data.NetworkDeviceId = 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.deviceRoles"); value.Exists() && len(value.Array()) > 0 { + data.DeviceRoles = helpers.GetStringList(value.Array()) + } else { + data.DeviceRoles = types.ListNull(types.StringType) + } + if value := res.Get("response.0.borderDeviceSettings.borderTypes"); value.Exists() && len(value.Array()) > 0 { + data.BorderTypes = helpers.GetStringList(value.Array()) + } else { + data.BorderTypes = types.ListNull(types.StringType) + } + if value := res.Get("response.0.borderDeviceSettings.layer3Settings.localAutonomousSystemNumber"); value.Exists() { + data.LocalAutonomousSystemNumber = types.StringValue(value.String()) + } else { + data.LocalAutonomousSystemNumber = types.StringNull() + } + if value := res.Get("response.0.borderDeviceSettings.layer3Settings.isDefaultExit"); value.Exists() { + data.DefaultExit = types.BoolValue(value.Bool()) + } else { + data.DefaultExit = types.BoolNull() + } + if value := res.Get("response.0.borderDeviceSettings.layer3Settings.importExternalRoutes"); value.Exists() { + data.ImportExternalRoutes = types.BoolValue(value.Bool()) + } else { + data.ImportExternalRoutes = types.BoolNull() + } + if value := res.Get("response.0.borderDeviceSettings.layer3Settings.borderPriority"); value.Exists() { + data.BorderPriority = types.Int64Value(value.Int()) + } else { + data.BorderPriority = types.Int64Value(10) + } + if value := res.Get("response.0.borderDeviceSettings.layer3Settings.prependAutonomousSystemCount"); value.Exists() { + data.PrependAutonomousSystemCount = types.Int64Value(value.Int()) + } else { + data.PrependAutonomousSystemCount = types.Int64Null() + } +} + +// End of section. //template:end fromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin updateFromBody +func (data *FabricDevice) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.0.networkDeviceId"); value.Exists() && !data.NetworkDeviceId.IsNull() { + data.NetworkDeviceId = types.StringValue(value.String()) + } else { + data.NetworkDeviceId = types.StringNull() + } + 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.deviceRoles"); value.Exists() && !data.DeviceRoles.IsNull() { + data.DeviceRoles = helpers.GetStringList(value.Array()) + } else { + data.DeviceRoles = types.ListNull(types.StringType) + } + if value := res.Get("response.0.borderDeviceSettings.borderTypes"); value.Exists() && !data.BorderTypes.IsNull() { + data.BorderTypes = helpers.GetStringList(value.Array()) + } else { + data.BorderTypes = types.ListNull(types.StringType) + } + if value := res.Get("response.0.borderDeviceSettings.layer3Settings.localAutonomousSystemNumber"); value.Exists() && !data.LocalAutonomousSystemNumber.IsNull() { + data.LocalAutonomousSystemNumber = types.StringValue(value.String()) + } else { + data.LocalAutonomousSystemNumber = types.StringNull() + } + if value := res.Get("response.0.borderDeviceSettings.layer3Settings.isDefaultExit"); value.Exists() && !data.DefaultExit.IsNull() { + data.DefaultExit = types.BoolValue(value.Bool()) + } else { + data.DefaultExit = types.BoolNull() + } + if value := res.Get("response.0.borderDeviceSettings.layer3Settings.importExternalRoutes"); value.Exists() && !data.ImportExternalRoutes.IsNull() { + data.ImportExternalRoutes = types.BoolValue(value.Bool()) + } else { + data.ImportExternalRoutes = types.BoolNull() + } + if value := res.Get("response.0.borderDeviceSettings.layer3Settings.borderPriority"); value.Exists() && !data.BorderPriority.IsNull() { + data.BorderPriority = types.Int64Value(value.Int()) + } else if data.BorderPriority.ValueInt64() != 10 { + data.BorderPriority = types.Int64Null() + } + if value := res.Get("response.0.borderDeviceSettings.layer3Settings.prependAutonomousSystemCount"); value.Exists() && !data.PrependAutonomousSystemCount.IsNull() { + data.PrependAutonomousSystemCount = types.Int64Value(value.Int()) + } else { + data.PrependAutonomousSystemCount = types.Int64Null() + } +} + +// End of section. //template:end updateFromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin isNull +func (data *FabricDevice) isNull(ctx context.Context, res gjson.Result) bool { + if !data.DeviceRoles.IsNull() { + return false + } + if !data.BorderTypes.IsNull() { + return false + } + if !data.LocalAutonomousSystemNumber.IsNull() { + return false + } + if !data.DefaultExit.IsNull() { + return false + } + if !data.ImportExternalRoutes.IsNull() { + return false + } + if !data.BorderPriority.IsNull() { + return false + } + if !data.PrependAutonomousSystemCount.IsNull() { + return false + } + return true +} + +// End of section. //template:end isNull diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 636d9aae..95bf6933 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -254,6 +254,7 @@ func (p *CcProvider) Resources(ctx context.Context) []func() resource.Resource { NewDeviceRoleResource, NewDiscoveryResource, NewFabricAuthenticationProfileResource, + NewFabricDeviceResource, NewFabricL3HandoffIPTransitResource, NewFabricSiteResource, NewFabricVirtualNetworkResource, @@ -300,6 +301,7 @@ func (p *CcProvider) DataSources(ctx context.Context) []func() datasource.DataSo NewDeviceDetailDataSource, NewDiscoveryDataSource, NewFabricAuthenticationProfileDataSource, + NewFabricDeviceDataSource, NewFabricL3HandoffIPTransitDataSource, NewFabricSiteDataSource, NewFabricVirtualNetworkDataSource, diff --git a/internal/provider/resource_catalystcenter_fabric_device.go b/internal/provider/resource_catalystcenter_fabric_device.go new file mode 100644 index 00000000..a6b7c212 --- /dev/null +++ b/internal/provider/resource_catalystcenter_fabric_device.go @@ -0,0 +1,297 @@ +// 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/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "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 = &FabricDeviceResource{} +var _ resource.ResourceWithImportState = &FabricDeviceResource{} + +func NewFabricDeviceResource() resource.Resource { + return &FabricDeviceResource{} +} + +type FabricDeviceResource struct { + client *cc.Client +} + +func (r *FabricDeviceResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_fabric_device" +} + +func (r *FabricDeviceResource) 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 Fabric Devices").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "network_device_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Network device ID of the fabric device").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "fabric_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("ID of the fabric site/zone of this fabric device").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "device_roles": schema.ListAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("List of the roles of the fabric device. Allowed values are [CONTROL_PLANE_NODE, EDGE_NODE, BORDER_NODE]").String, + ElementType: types.StringType, + Required: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplace(), + }, + }, + "border_types": schema.ListAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("List of the border types of the fabric device. Allowed values are [LAYER_2, LAYER_3]").String, + ElementType: types.StringType, + Optional: true, + }, + "local_autonomous_system_number": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("BGP Local autonomous system number of the fabric border device").String, + Optional: true, + }, + "default_exit": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Set this to make the fabric border device the gateway of last resort for this site. Any unknown traffic will be sent to this fabric border device from edge nodes").String, + Optional: true, + }, + "import_external_routes": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Set this to import external routes from other routing protocols (such as BGP) to the fabric control plane").String, + Optional: true, + }, + "border_priority": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Border priority of the fabric border device. A lower value indicates higher priority").AddIntegerRangeDescription(1, 9).AddDefaultValueDescription("10").String, + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.Between(1, 9), + }, + Default: int64default.StaticInt64(10), + }, + "prepend_autonomous_system_count": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Prepend autonomous system count of the fabric border device").AddIntegerRangeDescription(1, 10).String, + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(1, 10), + }, + }, + }, + } +} + +func (r *FabricDeviceResource) 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 *FabricDeviceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan FabricDevice + + // 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, FabricDevice{}) + + 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 += "?networkDeviceId=" + url.QueryEscape(plan.NetworkDeviceId.ValueString()) + "&fabricId=" + url.QueryEscape(plan.FabricId.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 *FabricDeviceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state FabricDevice + + // 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 += "?networkDeviceId=" + url.QueryEscape(state.NetworkDeviceId.ValueString()) + "&fabricId=" + url.QueryEscape(state.FabricId.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 *FabricDeviceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state FabricDevice + + // 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 *FabricDeviceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state FabricDevice + + // 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 *FabricDeviceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, ",") + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + 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("network_device_id"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("fabric_id"), idParts[1])...) +} + +// End of section. //template:end import diff --git a/internal/provider/resource_catalystcenter_fabric_device_test.go b/internal/provider/resource_catalystcenter_fabric_device_test.go new file mode 100644 index 00000000..a75d4d0c --- /dev/null +++ b/internal/provider/resource_catalystcenter_fabric_device_test.go @@ -0,0 +1,109 @@ +// 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 TestAccCcFabricDevice(t *testing.T) { + if os.Getenv("SDA") == "" { + t.Skip("skipping test, set environment variable SDA") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_device.test", "network_device_id", "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_device.test", "device_roles.0", "CONTROL_PLANE_NODE")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_device.test", "border_types.0", "LAYER_3")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_device.test", "local_autonomous_system_number", "65000")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_device.test", "default_exit", "true")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_device.test", "import_external_routes", "false")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_device.test", "border_priority", "5")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_device.test", "prepend_autonomous_system_count", "1")) + + var steps []resource.TestStep + if os.Getenv("SKIP_MINIMUM_TEST") == "" { + steps = append(steps, resource.TestStep{ + Config: testAccCcFabricDevicePrerequisitesConfig + testAccCcFabricDeviceConfig_minimum(), + }) + } + steps = append(steps, resource.TestStep{ + Config: testAccCcFabricDevicePrerequisitesConfig + testAccCcFabricDeviceConfig_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 testAccCcFabricDevicePrerequisitesConfig = ` +resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" +} +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] +} +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal +func testAccCcFabricDeviceConfig_minimum() string { + config := `resource "catalystcenter_fabric_device" "test" {` + "\n" + config += ` network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1"` + "\n" + config += ` fabric_id = catalystcenter_fabric_site.test.id` + "\n" + config += ` device_roles = ["CONTROL_PLANE_NODE"]` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll +func testAccCcFabricDeviceConfig_all() string { + config := `resource "catalystcenter_fabric_device" "test" {` + "\n" + config += ` network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1"` + "\n" + config += ` fabric_id = catalystcenter_fabric_site.test.id` + "\n" + config += ` device_roles = ["CONTROL_PLANE_NODE"]` + "\n" + config += ` border_types = ["LAYER_3"]` + "\n" + config += ` local_autonomous_system_number = "65000"` + "\n" + config += ` default_exit = true` + "\n" + config += ` import_external_routes = false` + "\n" + config += ` border_priority = 5` + "\n" + config += ` prepend_autonomous_system_count = 1` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index 4dbc9e98..b3fd9c72 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -8,6 +8,8 @@ description: |- # Changelog ## 0.1.10 (unreleased) + +- Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` - Add `fabric_l3_handoff_ip_transit` resource and data source - 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` From 522d49c1b091b2b89eedf8a80cea85a8c868152e Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Mon, 22 Jul 2024 14:56:21 +0200 Subject: [PATCH 11/31] pnp bulk import --- docs/resources/pnp_device_import.md | 47 ++++ .../resource.tf | 10 + gen/definitions/pnp_device_import.yaml | 39 ++++ .../model_catalystcenter_pnp_device_import.go | 182 ++++++++++++++++ internal/provider/provider.go | 1 + ...source_catalystcenter_pnp_device_import.go | 206 ++++++++++++++++++ ...e_catalystcenter_pnp_device_import_test.go | 88 ++++++++ 7 files changed, 573 insertions(+) create mode 100644 docs/resources/pnp_device_import.md create mode 100644 examples/resources/catalystcenter_pnp_device_import/resource.tf create mode 100644 gen/definitions/pnp_device_import.yaml create mode 100644 internal/provider/model_catalystcenter_pnp_device_import.go create mode 100644 internal/provider/resource_catalystcenter_pnp_device_import.go create mode 100644 internal/provider/resource_catalystcenter_pnp_device_import_test.go diff --git a/docs/resources/pnp_device_import.md b/docs/resources/pnp_device_import.md new file mode 100644 index 00000000..38a50645 --- /dev/null +++ b/docs/resources/pnp_device_import.md @@ -0,0 +1,47 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_pnp_device_import Resource - terraform-provider-catalystcenter" +subcategory: "Plug and Play" +description: |- + Add devices to PnP in bulk +--- + +# catalystcenter_pnp_device_import (Resource) + +Add devices to PnP in bulk + +## Example Usage + +```terraform +resource "catalystcenter_pnp_device_import" "example" { + devices = [ + { + serial_number = "FOC12345678" + stack = false + pid = "C9300-24P" + hostname = "switch1" + } + ] +} +``` + + +## Schema + +### Required + +- `devices` (Attributes List) List of devices to add (see [below for nested schema](#nestedatt--devices)) + +### Read-Only + +- `id` (String) The id of the object + + +### Nested Schema for `devices` + +Optional: + +- `hostname` (String) Device hostname +- `pid` (String) Device product ID +- `serial_number` (String) Device serial number +- `stack` (Boolean) Device is a stacked switch diff --git a/examples/resources/catalystcenter_pnp_device_import/resource.tf b/examples/resources/catalystcenter_pnp_device_import/resource.tf new file mode 100644 index 00000000..c7452a70 --- /dev/null +++ b/examples/resources/catalystcenter_pnp_device_import/resource.tf @@ -0,0 +1,10 @@ +resource "catalystcenter_pnp_device_import" "example" { + devices = [ + { + serial_number = "FOC12345678" + stack = false + pid = "C9300-24P" + hostname = "switch1" + } + ] +} diff --git a/gen/definitions/pnp_device_import.yaml b/gen/definitions/pnp_device_import.yaml new file mode 100644 index 00000000..1f71c7a8 --- /dev/null +++ b/gen/definitions/pnp_device_import.yaml @@ -0,0 +1,39 @@ +--- +name: PnP Device Import +rest_endpoint: /dna/intent/api/v1/onboarding/pnp-device/import +res_description: Add devices to PnP in bulk +no_data_source: true +no_read: true +no_delete: true +no_import: true +no_update: true +id_path: id +root_list: true +doc_category: Plug and Play +test_tags: [PNP] +attributes: + - tf_name: devices + type: List + mandatory: true + description: List of devices to add + attributes: + - model_name: serialNumber + data_path: deviceInfo + type: String + description: Device serial number + example: FOC12345678 + - model_name: stack + data_path: deviceInfo + type: Bool + description: Device is a stacked switch + example: false + - model_name: pid + data_path: deviceInfo + type: String + description: Device product ID + example: C9300-24P + - model_name: hostname + data_path: deviceInfo + type: String + description: Device hostname + example: switch1 \ No newline at end of file diff --git a/internal/provider/model_catalystcenter_pnp_device_import.go b/internal/provider/model_catalystcenter_pnp_device_import.go new file mode 100644 index 00000000..1ff33061 --- /dev/null +++ b/internal/provider/model_catalystcenter_pnp_device_import.go @@ -0,0 +1,182 @@ +// 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" + "strconv" + + "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 PnPDeviceImport struct { + Id types.String `tfsdk:"id"` + Devices []PnPDeviceImportDevices `tfsdk:"devices"` +} + +type PnPDeviceImportDevices struct { + SerialNumber types.String `tfsdk:"serial_number"` + Stack types.Bool `tfsdk:"stack"` + Pid types.String `tfsdk:"pid"` + Hostname types.String `tfsdk:"hostname"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath +func (data PnPDeviceImport) getPath() string { + return "/dna/intent/api/v1/onboarding/pnp-device/import" +} + +// 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 PnPDeviceImport) toBody(ctx context.Context, state PnPDeviceImport) string { + body := "[]" + put := false + if state.Id.ValueString() != "" { + put = true + } + _ = put + if len(data.Devices) > 0 { + body, _ = sjson.Set(body, "", []interface{}{}) + for _, item := range data.Devices { + itemBody := "" + if !item.SerialNumber.IsNull() { + itemBody, _ = sjson.Set(itemBody, "deviceInfo.serialNumber", item.SerialNumber.ValueString()) + } + if !item.Stack.IsNull() { + itemBody, _ = sjson.Set(itemBody, "deviceInfo.stack", item.Stack.ValueBool()) + } + if !item.Pid.IsNull() { + itemBody, _ = sjson.Set(itemBody, "deviceInfo.pid", item.Pid.ValueString()) + } + if !item.Hostname.IsNull() { + itemBody, _ = sjson.Set(itemBody, "deviceInfo.hostname", item.Hostname.ValueString()) + } + body, _ = sjson.SetRaw(body, "-1", itemBody) + } + } + return body +} + +// End of section. //template:end toBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBody +func (data *PnPDeviceImport) fromBody(ctx context.Context, res gjson.Result) { + if value := res; value.Exists() && len(value.Array()) > 0 { + data.Devices = make([]PnPDeviceImportDevices, 0) + value.ForEach(func(k, v gjson.Result) bool { + item := PnPDeviceImportDevices{} + if cValue := v.Get("deviceInfo.serialNumber"); cValue.Exists() { + item.SerialNumber = types.StringValue(cValue.String()) + } else { + item.SerialNumber = types.StringNull() + } + if cValue := v.Get("deviceInfo.stack"); cValue.Exists() { + item.Stack = types.BoolValue(cValue.Bool()) + } else { + item.Stack = types.BoolNull() + } + if cValue := v.Get("deviceInfo.pid"); cValue.Exists() { + item.Pid = types.StringValue(cValue.String()) + } else { + item.Pid = types.StringNull() + } + if cValue := v.Get("deviceInfo.hostname"); cValue.Exists() { + item.Hostname = types.StringValue(cValue.String()) + } else { + item.Hostname = types.StringNull() + } + data.Devices = append(data.Devices, item) + return true + }) + } +} + +// End of section. //template:end fromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin updateFromBody +func (data *PnPDeviceImport) updateFromBody(ctx context.Context, res gjson.Result) { + for i := range data.Devices { + keys := [...]string{"deviceInfo.serialNumber", "deviceInfo.stack", "deviceInfo.pid", "deviceInfo.hostname"} + keyValues := [...]string{data.Devices[i].SerialNumber.ValueString(), strconv.FormatBool(data.Devices[i].Stack.ValueBool()), data.Devices[i].Pid.ValueString(), data.Devices[i].Hostname.ValueString()} + + var r gjson.Result + res.ForEach( + func(_, v gjson.Result) bool { + found := false + for ik := range keys { + if v.Get(keys[ik]).String() == keyValues[ik] { + found = true + continue + } + found = false + break + } + if found { + r = v + return false + } + return true + }, + ) + if value := r.Get("deviceInfo.serialNumber"); value.Exists() && !data.Devices[i].SerialNumber.IsNull() { + data.Devices[i].SerialNumber = types.StringValue(value.String()) + } else { + data.Devices[i].SerialNumber = types.StringNull() + } + if value := r.Get("deviceInfo.stack"); value.Exists() && !data.Devices[i].Stack.IsNull() { + data.Devices[i].Stack = types.BoolValue(value.Bool()) + } else { + data.Devices[i].Stack = types.BoolNull() + } + if value := r.Get("deviceInfo.pid"); value.Exists() && !data.Devices[i].Pid.IsNull() { + data.Devices[i].Pid = types.StringValue(value.String()) + } else { + data.Devices[i].Pid = types.StringNull() + } + if value := r.Get("deviceInfo.hostname"); value.Exists() && !data.Devices[i].Hostname.IsNull() { + data.Devices[i].Hostname = types.StringValue(value.String()) + } else { + data.Devices[i].Hostname = types.StringNull() + } + } +} + +// End of section. //template:end updateFromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin isNull +func (data *PnPDeviceImport) isNull(ctx context.Context, res gjson.Result) bool { + if len(data.Devices) > 0 { + return false + } + return true +} + +// End of section. //template:end isNull diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 95bf6933..e1059d82 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -270,6 +270,7 @@ func (p *CcProvider) Resources(ctx context.Context) []func() resource.Resource { NewPNPConfigPreviewResource, NewPnPDeviceResource, NewPnPDeviceClaimSiteResource, + NewPnPDeviceImportResource, NewProjectResource, NewRoleResource, NewSPProfileResource, diff --git a/internal/provider/resource_catalystcenter_pnp_device_import.go b/internal/provider/resource_catalystcenter_pnp_device_import.go new file mode 100644 index 00000000..47062907 --- /dev/null +++ b/internal/provider/resource_catalystcenter_pnp_device_import.go @@ -0,0 +1,206 @@ +// 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" + + "github.com/CiscoDevNet/terraform-provider-catalystcenter/internal/provider/helpers" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "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 = &PnPDeviceImportResource{} + +func NewPnPDeviceImportResource() resource.Resource { + return &PnPDeviceImportResource{} +} + +type PnPDeviceImportResource struct { + client *cc.Client +} + +func (r *PnPDeviceImportResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_pnp_device_import" +} + +func (r *PnPDeviceImportResource) 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("Add devices to PnP in bulk").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "devices": schema.ListNestedAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("List of devices to add").String, + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "serial_number": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Device serial number").String, + Optional: true, + }, + "stack": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Device is a stacked switch").String, + Optional: true, + }, + "pid": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Device product ID").String, + Optional: true, + }, + "hostname": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Device hostname").String, + Optional: true, + }, + }, + }, + }, + }, + } +} + +func (r *PnPDeviceImportResource) 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 *PnPDeviceImportResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan PnPDeviceImport + + // 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, PnPDeviceImport{}) + + params := "" + res, err := r.client.Post(plan.getPath()+params, body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST), got error: %s, %s", err, res.String())) + return + } + plan.Id = types.StringValue(res.Get("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 *PnPDeviceImportResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state PnPDeviceImport + + // 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())) + + 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 *PnPDeviceImportResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state PnPDeviceImport + + // 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())) + + 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 *PnPDeviceImportResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state PnPDeviceImport + + // 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())) + + 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 +// End of section. //template:end import diff --git a/internal/provider/resource_catalystcenter_pnp_device_import_test.go b/internal/provider/resource_catalystcenter_pnp_device_import_test.go new file mode 100644 index 00000000..ebfb1e83 --- /dev/null +++ b/internal/provider/resource_catalystcenter_pnp_device_import_test.go @@ -0,0 +1,88 @@ +// 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 TestAccCcPnPDeviceImport(t *testing.T) { + if os.Getenv("PNP") == "" { + t.Skip("skipping test, set environment variable PNP") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_pnp_device_import.test", "devices.0.serial_number", "FOC12345678")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_pnp_device_import.test", "devices.0.stack", "false")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_pnp_device_import.test", "devices.0.pid", "C9300-24P")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_pnp_device_import.test", "devices.0.hostname", "switch1")) + + var steps []resource.TestStep + if os.Getenv("SKIP_MINIMUM_TEST") == "" { + steps = append(steps, resource.TestStep{ + Config: testAccCcPnPDeviceImportConfig_minimum(), + }) + } + steps = append(steps, resource.TestStep{ + Config: testAccCcPnPDeviceImportConfig_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 +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal +func testAccCcPnPDeviceImportConfig_minimum() string { + config := `resource "catalystcenter_pnp_device_import" "test" {` + "\n" + config += ` devices = [{` + "\n" + config += ` }]` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll +func testAccCcPnPDeviceImportConfig_all() string { + config := `resource "catalystcenter_pnp_device_import" "test" {` + "\n" + config += ` devices = [{` + "\n" + config += ` serial_number = "FOC12345678"` + "\n" + config += ` stack = false` + "\n" + config += ` pid = "C9300-24P"` + "\n" + config += ` hostname = "switch1"` + "\n" + config += ` }]` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll From 30854add10057cc652560f3c937fdf090f3d62d0 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Mon, 22 Jul 2024 14:59:43 +0200 Subject: [PATCH 12/31] 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 b01083a9..73da6ee2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.1.10 (unreleased) +- Add `pnp_device_import` resource - Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` - Add `fabric_l3_handoff_ip_transit` resource and data source - Add `transitPeerNetworkId` as `id` to `transit_peer_network` resource diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index b3fd9c72..f31434ac 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -9,6 +9,7 @@ description: |- ## 0.1.10 (unreleased) +- Add `pnp_device_import` resource - Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` - Add `fabric_l3_handoff_ip_transit` resource and data source - Add `transitPeerNetworkId` as `id` to `transit_peer_network` resource diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index b3fd9c72..f31434ac 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -9,6 +9,7 @@ description: |- ## 0.1.10 (unreleased) +- Add `pnp_device_import` resource - Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` - Add `fabric_l3_handoff_ip_transit` resource and data source - Add `transitPeerNetworkId` as `id` to `transit_peer_network` resource From 9a6533c49b64ca8da8093a2c2935c65295203170 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Wed, 24 Jul 2024 14:49:19 +0200 Subject: [PATCH 13/31] rename pnp_device_import to pnp_import_devices --- CHANGELOG.md | 2 +- docs/guides/changelog.md | 2 +- docs/resources/pnp_device_import.md | 47 ------------------- docs/resources/pnp_import_devices.md | 47 +++++++++++++++++++ .../resource.tf | 2 +- ...ce_import.yaml => pnp_import_devices.yaml} | 6 ++- ...odel_catalystcenter_pnp_import_devices.go} | 22 ++++----- internal/provider/provider.go | 2 +- ...urce_catalystcenter_pnp_import_devices.go} | 36 +++++++------- ...catalystcenter_pnp_import_devices_test.go} | 22 ++++----- templates/guides/changelog.md.tmpl | 2 +- 11 files changed, 96 insertions(+), 94 deletions(-) delete mode 100644 docs/resources/pnp_device_import.md create mode 100644 docs/resources/pnp_import_devices.md rename examples/resources/{catalystcenter_pnp_device_import => catalystcenter_pnp_import_devices}/resource.tf (73%) rename gen/definitions/{pnp_device_import.yaml => pnp_import_devices.yaml} (70%) rename internal/provider/{model_catalystcenter_pnp_device_import.go => model_catalystcenter_pnp_import_devices.go} (89%) rename internal/provider/{resource_catalystcenter_pnp_device_import.go => resource_catalystcenter_pnp_import_devices.go} (76%) rename internal/provider/{resource_catalystcenter_pnp_device_import_test.go => resource_catalystcenter_pnp_import_devices_test.go} (79%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73da6ee2..884020c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.1.10 (unreleased) -- Add `pnp_device_import` resource +- Add `pnp_import_devices` resource - Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` - Add `fabric_l3_handoff_ip_transit` resource and data source - Add `transitPeerNetworkId` as `id` to `transit_peer_network` resource diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index f31434ac..55461b43 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -9,7 +9,7 @@ description: |- ## 0.1.10 (unreleased) -- Add `pnp_device_import` resource +- Add `pnp_import_devices` resource - Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` - Add `fabric_l3_handoff_ip_transit` resource and data source - Add `transitPeerNetworkId` as `id` to `transit_peer_network` resource diff --git a/docs/resources/pnp_device_import.md b/docs/resources/pnp_device_import.md deleted file mode 100644 index 38a50645..00000000 --- a/docs/resources/pnp_device_import.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "catalystcenter_pnp_device_import Resource - terraform-provider-catalystcenter" -subcategory: "Plug and Play" -description: |- - Add devices to PnP in bulk ---- - -# catalystcenter_pnp_device_import (Resource) - -Add devices to PnP in bulk - -## Example Usage - -```terraform -resource "catalystcenter_pnp_device_import" "example" { - devices = [ - { - serial_number = "FOC12345678" - stack = false - pid = "C9300-24P" - hostname = "switch1" - } - ] -} -``` - - -## Schema - -### Required - -- `devices` (Attributes List) List of devices to add (see [below for nested schema](#nestedatt--devices)) - -### Read-Only - -- `id` (String) The id of the object - - -### Nested Schema for `devices` - -Optional: - -- `hostname` (String) Device hostname -- `pid` (String) Device product ID -- `serial_number` (String) Device serial number -- `stack` (Boolean) Device is a stacked switch diff --git a/docs/resources/pnp_import_devices.md b/docs/resources/pnp_import_devices.md new file mode 100644 index 00000000..52c6e416 --- /dev/null +++ b/docs/resources/pnp_import_devices.md @@ -0,0 +1,47 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_pnp_import_devices Resource - terraform-provider-catalystcenter" +subcategory: "Plug and Play" +description: |- + This resource adds devices to PNP in bulk file on the chosen network device (upgrade the software on the device). Every time this resource is created or re-created, the Catalyst Center considers adding new devices to PNP. When this resource is destroyed or updated or refreshed, no actions are done either on CatalystCenter or on devices +--- + +# catalystcenter_pnp_import_devices (Resource) + +This resource adds devices to PNP in bulk file on the chosen network device (upgrade the software on the device). Every time this resource is created or re-created, the Catalyst Center considers adding new devices to PNP. When this resource is destroyed or updated or refreshed, no actions are done either on CatalystCenter or on devices + +## Example Usage + +```terraform +resource "catalystcenter_pnp_import_devices" "example" { + devices = [ + { + serial_number = "FOC12345678" + stack = false + pid = "C9300-24P" + hostname = "switch1" + } + ] +} +``` + + +## Schema + +### Required + +- `devices` (Attributes List) List of devices to add (see [below for nested schema](#nestedatt--devices)) + +### Read-Only + +- `id` (String) The id of the object + + +### Nested Schema for `devices` + +Optional: + +- `hostname` (String) Device hostname +- `pid` (String) Device product ID +- `serial_number` (String) Device serial number +- `stack` (Boolean) Device is a stacked switch diff --git a/examples/resources/catalystcenter_pnp_device_import/resource.tf b/examples/resources/catalystcenter_pnp_import_devices/resource.tf similarity index 73% rename from examples/resources/catalystcenter_pnp_device_import/resource.tf rename to examples/resources/catalystcenter_pnp_import_devices/resource.tf index c7452a70..1bf9a9fe 100644 --- a/examples/resources/catalystcenter_pnp_device_import/resource.tf +++ b/examples/resources/catalystcenter_pnp_import_devices/resource.tf @@ -1,4 +1,4 @@ -resource "catalystcenter_pnp_device_import" "example" { +resource "catalystcenter_pnp_import_devices" "example" { devices = [ { serial_number = "FOC12345678" diff --git a/gen/definitions/pnp_device_import.yaml b/gen/definitions/pnp_import_devices.yaml similarity index 70% rename from gen/definitions/pnp_device_import.yaml rename to gen/definitions/pnp_import_devices.yaml index 1f71c7a8..00fb5d5a 100644 --- a/gen/definitions/pnp_device_import.yaml +++ b/gen/definitions/pnp_import_devices.yaml @@ -1,7 +1,9 @@ --- -name: PnP Device Import +name: PnP Import Devices rest_endpoint: /dna/intent/api/v1/onboarding/pnp-device/import -res_description: Add devices to PnP in bulk +res_description: 'This resource adds devices to PNP in bulk file on the chosen network device (upgrade the software on the device). + Every time this resource is created or re-created, the Catalyst Center considers adding new devices to PNP. + When this resource is destroyed or updated or refreshed, no actions are done either on CatalystCenter or on devices' no_data_source: true no_read: true no_delete: true diff --git a/internal/provider/model_catalystcenter_pnp_device_import.go b/internal/provider/model_catalystcenter_pnp_import_devices.go similarity index 89% rename from internal/provider/model_catalystcenter_pnp_device_import.go rename to internal/provider/model_catalystcenter_pnp_import_devices.go index 1ff33061..d3c9cca7 100644 --- a/internal/provider/model_catalystcenter_pnp_device_import.go +++ b/internal/provider/model_catalystcenter_pnp_import_devices.go @@ -30,12 +30,12 @@ import ( // End of section. //template:end imports // Section below is generated&owned by "gen/generator.go". //template:begin types -type PnPDeviceImport struct { - Id types.String `tfsdk:"id"` - Devices []PnPDeviceImportDevices `tfsdk:"devices"` +type PnPImportDevices struct { + Id types.String `tfsdk:"id"` + Devices []PnPImportDevicesDevices `tfsdk:"devices"` } -type PnPDeviceImportDevices struct { +type PnPImportDevicesDevices struct { SerialNumber types.String `tfsdk:"serial_number"` Stack types.Bool `tfsdk:"stack"` Pid types.String `tfsdk:"pid"` @@ -45,7 +45,7 @@ type PnPDeviceImportDevices struct { // End of section. //template:end types // Section below is generated&owned by "gen/generator.go". //template:begin getPath -func (data PnPDeviceImport) getPath() string { +func (data PnPImportDevices) getPath() string { return "/dna/intent/api/v1/onboarding/pnp-device/import" } @@ -56,7 +56,7 @@ func (data PnPDeviceImport) getPath() string { // End of section. //template:end getPathDelete // Section below is generated&owned by "gen/generator.go". //template:begin toBody -func (data PnPDeviceImport) toBody(ctx context.Context, state PnPDeviceImport) string { +func (data PnPImportDevices) toBody(ctx context.Context, state PnPImportDevices) string { body := "[]" put := false if state.Id.ValueString() != "" { @@ -88,11 +88,11 @@ func (data PnPDeviceImport) toBody(ctx context.Context, state PnPDeviceImport) s // End of section. //template:end toBody // Section below is generated&owned by "gen/generator.go". //template:begin fromBody -func (data *PnPDeviceImport) fromBody(ctx context.Context, res gjson.Result) { +func (data *PnPImportDevices) fromBody(ctx context.Context, res gjson.Result) { if value := res; value.Exists() && len(value.Array()) > 0 { - data.Devices = make([]PnPDeviceImportDevices, 0) + data.Devices = make([]PnPImportDevicesDevices, 0) value.ForEach(func(k, v gjson.Result) bool { - item := PnPDeviceImportDevices{} + item := PnPImportDevicesDevices{} if cValue := v.Get("deviceInfo.serialNumber"); cValue.Exists() { item.SerialNumber = types.StringValue(cValue.String()) } else { @@ -122,7 +122,7 @@ func (data *PnPDeviceImport) fromBody(ctx context.Context, res gjson.Result) { // End of section. //template:end fromBody // Section below is generated&owned by "gen/generator.go". //template:begin updateFromBody -func (data *PnPDeviceImport) updateFromBody(ctx context.Context, res gjson.Result) { +func (data *PnPImportDevices) updateFromBody(ctx context.Context, res gjson.Result) { for i := range data.Devices { keys := [...]string{"deviceInfo.serialNumber", "deviceInfo.stack", "deviceInfo.pid", "deviceInfo.hostname"} keyValues := [...]string{data.Devices[i].SerialNumber.ValueString(), strconv.FormatBool(data.Devices[i].Stack.ValueBool()), data.Devices[i].Pid.ValueString(), data.Devices[i].Hostname.ValueString()} @@ -172,7 +172,7 @@ func (data *PnPDeviceImport) updateFromBody(ctx context.Context, res gjson.Resul // End of section. //template:end updateFromBody // Section below is generated&owned by "gen/generator.go". //template:begin isNull -func (data *PnPDeviceImport) isNull(ctx context.Context, res gjson.Result) bool { +func (data *PnPImportDevices) isNull(ctx context.Context, res gjson.Result) bool { if len(data.Devices) > 0 { return false } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e1059d82..0245adef 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -270,7 +270,7 @@ func (p *CcProvider) Resources(ctx context.Context) []func() resource.Resource { NewPNPConfigPreviewResource, NewPnPDeviceResource, NewPnPDeviceClaimSiteResource, - NewPnPDeviceImportResource, + NewPnPImportDevicesResource, NewProjectResource, NewRoleResource, NewSPProfileResource, diff --git a/internal/provider/resource_catalystcenter_pnp_device_import.go b/internal/provider/resource_catalystcenter_pnp_import_devices.go similarity index 76% rename from internal/provider/resource_catalystcenter_pnp_device_import.go rename to internal/provider/resource_catalystcenter_pnp_import_devices.go index 47062907..bfdfb18e 100644 --- a/internal/provider/resource_catalystcenter_pnp_device_import.go +++ b/internal/provider/resource_catalystcenter_pnp_import_devices.go @@ -37,24 +37,24 @@ import ( // Section below is generated&owned by "gen/generator.go". //template:begin model // Ensure provider defined types fully satisfy framework interfaces -var _ resource.Resource = &PnPDeviceImportResource{} +var _ resource.Resource = &PnPImportDevicesResource{} -func NewPnPDeviceImportResource() resource.Resource { - return &PnPDeviceImportResource{} +func NewPnPImportDevicesResource() resource.Resource { + return &PnPImportDevicesResource{} } -type PnPDeviceImportResource struct { +type PnPImportDevicesResource struct { client *cc.Client } -func (r *PnPDeviceImportResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_pnp_device_import" +func (r *PnPImportDevicesResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_pnp_import_devices" } -func (r *PnPDeviceImportResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *PnPImportDevicesResource) 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("Add devices to PnP in bulk").String, + MarkdownDescription: helpers.NewAttributeDescription("This resource adds devices to PNP in bulk file on the chosen network device (upgrade the software on the device). Every time this resource is created or re-created, the Catalyst Center considers adding new devices to PNP. When this resource is destroyed or updated or refreshed, no actions are done either on CatalystCenter or on devices").String, Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ @@ -92,7 +92,7 @@ func (r *PnPDeviceImportResource) Schema(ctx context.Context, req resource.Schem } } -func (r *PnPDeviceImportResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { +func (r *PnPImportDevicesResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { if req.ProviderData == nil { return } @@ -103,8 +103,8 @@ func (r *PnPDeviceImportResource) Configure(_ context.Context, req resource.Conf // End of section. //template:end model // Section below is generated&owned by "gen/generator.go". //template:begin create -func (r *PnPDeviceImportResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var plan PnPDeviceImport +func (r *PnPImportDevicesResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan PnPImportDevices // Read plan diags := req.Plan.Get(ctx, &plan) @@ -116,7 +116,7 @@ func (r *PnPDeviceImportResource) Create(ctx context.Context, req resource.Creat tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) // Create object - body := plan.toBody(ctx, PnPDeviceImport{}) + body := plan.toBody(ctx, PnPImportDevices{}) params := "" res, err := r.client.Post(plan.getPath()+params, body) @@ -135,8 +135,8 @@ func (r *PnPDeviceImportResource) Create(ctx context.Context, req resource.Creat // End of section. //template:end create // Section below is generated&owned by "gen/generator.go". //template:begin read -func (r *PnPDeviceImportResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var state PnPDeviceImport +func (r *PnPImportDevicesResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state PnPImportDevices // Read state diags := req.State.Get(ctx, &state) @@ -156,8 +156,8 @@ func (r *PnPDeviceImportResource) Read(ctx context.Context, req resource.ReadReq // End of section. //template:end read // Section below is generated&owned by "gen/generator.go". //template:begin update -func (r *PnPDeviceImportResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var plan, state PnPDeviceImport +func (r *PnPImportDevicesResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state PnPImportDevices // Read plan diags := req.Plan.Get(ctx, &plan) @@ -183,8 +183,8 @@ func (r *PnPDeviceImportResource) Update(ctx context.Context, req resource.Updat // End of section. //template:end update // Section below is generated&owned by "gen/generator.go". //template:begin delete -func (r *PnPDeviceImportResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var state PnPDeviceImport +func (r *PnPImportDevicesResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state PnPImportDevices // Read state diags := req.State.Get(ctx, &state) diff --git a/internal/provider/resource_catalystcenter_pnp_device_import_test.go b/internal/provider/resource_catalystcenter_pnp_import_devices_test.go similarity index 79% rename from internal/provider/resource_catalystcenter_pnp_device_import_test.go rename to internal/provider/resource_catalystcenter_pnp_import_devices_test.go index ebfb1e83..8543bd77 100644 --- a/internal/provider/resource_catalystcenter_pnp_device_import_test.go +++ b/internal/provider/resource_catalystcenter_pnp_import_devices_test.go @@ -28,24 +28,24 @@ import ( // End of section. //template:end imports // Section below is generated&owned by "gen/generator.go". //template:begin testAcc -func TestAccCcPnPDeviceImport(t *testing.T) { +func TestAccCcPnPImportDevices(t *testing.T) { if os.Getenv("PNP") == "" { t.Skip("skipping test, set environment variable PNP") } var checks []resource.TestCheckFunc - checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_pnp_device_import.test", "devices.0.serial_number", "FOC12345678")) - checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_pnp_device_import.test", "devices.0.stack", "false")) - checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_pnp_device_import.test", "devices.0.pid", "C9300-24P")) - checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_pnp_device_import.test", "devices.0.hostname", "switch1")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_pnp_import_devices.test", "devices.0.serial_number", "FOC12345678")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_pnp_import_devices.test", "devices.0.stack", "false")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_pnp_import_devices.test", "devices.0.pid", "C9300-24P")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_pnp_import_devices.test", "devices.0.hostname", "switch1")) var steps []resource.TestStep if os.Getenv("SKIP_MINIMUM_TEST") == "" { steps = append(steps, resource.TestStep{ - Config: testAccCcPnPDeviceImportConfig_minimum(), + Config: testAccCcPnPImportDevicesConfig_minimum(), }) } steps = append(steps, resource.TestStep{ - Config: testAccCcPnPDeviceImportConfig_all(), + Config: testAccCcPnPImportDevicesConfig_all(), Check: resource.ComposeTestCheckFunc(checks...), }) @@ -62,8 +62,8 @@ func TestAccCcPnPDeviceImport(t *testing.T) { // End of section. //template:end testPrerequisites // Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal -func testAccCcPnPDeviceImportConfig_minimum() string { - config := `resource "catalystcenter_pnp_device_import" "test" {` + "\n" +func testAccCcPnPImportDevicesConfig_minimum() string { + config := `resource "catalystcenter_pnp_import_devices" "test" {` + "\n" config += ` devices = [{` + "\n" config += ` }]` + "\n" config += `}` + "\n" @@ -73,8 +73,8 @@ func testAccCcPnPDeviceImportConfig_minimum() string { // End of section. //template:end testAccConfigMinimal // Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll -func testAccCcPnPDeviceImportConfig_all() string { - config := `resource "catalystcenter_pnp_device_import" "test" {` + "\n" +func testAccCcPnPImportDevicesConfig_all() string { + config := `resource "catalystcenter_pnp_import_devices" "test" {` + "\n" config += ` devices = [{` + "\n" config += ` serial_number = "FOC12345678"` + "\n" config += ` stack = false` + "\n" diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index f31434ac..55461b43 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -9,7 +9,7 @@ description: |- ## 0.1.10 (unreleased) -- Add `pnp_device_import` resource +- Add `pnp_import_devices` resource - Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` - Add `fabric_l3_handoff_ip_transit` resource and data source - Add `transitPeerNetworkId` as `id` to `transit_peer_network` resource From 35565f0cc091d934af44d75f1f711382f52bad1e Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Fri, 26 Jul 2024 16:06:14 +0200 Subject: [PATCH 14/31] add tag and assign_templates_to_tag --- CHANGELOG.md | 2 + docs/data-sources/assign_templates_to_tag.md | 31 ++ docs/data-sources/tag.md | 54 +++ docs/guides/changelog.md | 2 + docs/resources/assign_templates_to_tag.md | 35 ++ docs/resources/tag.md | 67 ++++ .../data-source.tf | 3 + .../catalystcenter_tag/data-source.tf | 3 + .../resource.tf | 4 + .../resources/catalystcenter_tag/import.sh | 1 + .../resources/catalystcenter_tag/resource.tf | 5 + gen/definitions/assign_templates_to_tag.yaml | 56 +++ gen/definitions/tag.yaml | 71 ++++ ..._catalystcenter_assign_templates_to_tag.go | 116 ++++++ ...lystcenter_assign_templates_to_tag_test.go | 96 +++++ .../data_source_catalystcenter_tag.go | 169 +++++++++ .../data_source_catalystcenter_tag_test.go | 69 ++++ ..._catalystcenter_assign_templates_to_tag.go | 120 +++++++ internal/provider/model_catalystcenter_tag.go | 338 ++++++++++++++++++ internal/provider/provider.go | 4 + ..._catalystcenter_assign_templates_to_tag.go | 234 ++++++++++++ ...lystcenter_assign_templates_to_tag_test.go | 101 ++++++ .../provider/resource_catalystcenter_tag.go | 298 +++++++++++++++ .../resource_catalystcenter_tag_test.go | 80 +++++ templates/guides/changelog.md.tmpl | 2 + 25 files changed, 1961 insertions(+) create mode 100644 docs/data-sources/assign_templates_to_tag.md create mode 100644 docs/data-sources/tag.md create mode 100644 docs/resources/assign_templates_to_tag.md create mode 100644 docs/resources/tag.md create mode 100644 examples/data-sources/catalystcenter_assign_templates_to_tag/data-source.tf create mode 100644 examples/data-sources/catalystcenter_tag/data-source.tf create mode 100644 examples/resources/catalystcenter_assign_templates_to_tag/resource.tf create mode 100644 examples/resources/catalystcenter_tag/import.sh create mode 100644 examples/resources/catalystcenter_tag/resource.tf create mode 100644 gen/definitions/assign_templates_to_tag.yaml create mode 100644 gen/definitions/tag.yaml create mode 100644 internal/provider/data_source_catalystcenter_assign_templates_to_tag.go create mode 100644 internal/provider/data_source_catalystcenter_assign_templates_to_tag_test.go create mode 100644 internal/provider/data_source_catalystcenter_tag.go create mode 100644 internal/provider/data_source_catalystcenter_tag_test.go create mode 100644 internal/provider/model_catalystcenter_assign_templates_to_tag.go create mode 100644 internal/provider/model_catalystcenter_tag.go create mode 100644 internal/provider/resource_catalystcenter_assign_templates_to_tag.go create mode 100644 internal/provider/resource_catalystcenter_assign_templates_to_tag_test.go create mode 100644 internal/provider/resource_catalystcenter_tag.go create mode 100644 internal/provider/resource_catalystcenter_tag_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 884020c5..9feb2617 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## 0.1.10 (unreleased) +- Add `assign_templates_to_tag` resource +- Add `tag` resource and data source - Add `pnp_import_devices` resource - Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` - Add `fabric_l3_handoff_ip_transit` resource and data source diff --git a/docs/data-sources/assign_templates_to_tag.md b/docs/data-sources/assign_templates_to_tag.md new file mode 100644 index 00000000..3a0c335c --- /dev/null +++ b/docs/data-sources/assign_templates_to_tag.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_assign_templates_to_tag Data Source - terraform-provider-catalystcenter" +subcategory: "Tags" +description: |- + This data source can read the Assign Templates to Tag. +--- + +# catalystcenter_assign_templates_to_tag (Data Source) + +This data source can read the Assign Templates to Tag. + +## Example Usage + +```terraform +data "catalystcenter_assign_templates_to_tag" "example" { + tag_id = "ea505070-6bb8-493f-bff0-8058e8e03ee5" +} +``` + + +## Schema + +### Required + +- `tag_id` (String) Tag Id to be associated with the template + +### Read-Only + +- `id` (String) The id of the object +- `template_ids` (List of String) Template Ids List diff --git a/docs/data-sources/tag.md b/docs/data-sources/tag.md new file mode 100644 index 00000000..50af16de --- /dev/null +++ b/docs/data-sources/tag.md @@ -0,0 +1,54 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_tag Data Source - terraform-provider-catalystcenter" +subcategory: "Tags" +description: |- + This data source can read the Tag. +--- + +# catalystcenter_tag (Data Source) + +This data source can read the Tag. + +## Example Usage + +```terraform +data "catalystcenter_tag" "example" { + name = "Tag1" +} +``` + + +## Schema + +### Required + +- `name` (String) + +### Read-Only + +- `description` (String) Description of the tag +- `dynamic_rules` (Attributes List) Dynamic rules details (see [below for nested schema](#nestedatt--dynamic_rules)) +- `id` (String) The id of the object +- `system_tag` (Boolean) true for system created tags, false for user defined tag + + +### Nested Schema for `dynamic_rules` + +Read-Only: + +- `member_type` (String) memberType of the tag (e.g. networkdevice, interface) +- `rule_items` (Attributes List) items details, multiple rules can be defined by items (see [below for nested schema](#nestedatt--dynamic_rules--rule_items)) +- `rule_name` (String) Name of the parameter (e.g. for interface:portName,adminStatus,speed,status,description. for networkdevice:family,series,hostname,managementIpAddress,groupNameHierarchy,softwareVersion) +- `rule_operation` (String) Operation of the rule (e.g. OR,IN,EQ,LIKE,ILIKE,AND) +- `rule_value` (String) Value of the parameter (e.g. for portName:1/0/1,for adminStatus,status:up/down, for speed: any integer value, for description: any valid string, for family:switches, for series:C3650, for managementIpAddress:10.197.124.90, groupNameHierarchy:Global, softwareVersion: 16.9.1) +- `rule_values` (List of String) values of the parameter,Only one of the value or values can be used for the given parameter. (for managementIpAddress e.g. ["10.197.124.90","10.197.124.91"]) + + +### Nested Schema for `dynamic_rules.rule_items` + +Read-Only: + +- `name` (String) Name of the parameter (e.g. managementIpAddress,hostname) +- `operation` (String) Operation of the rule (e.g. OR,IN,EQ,LIKE,ILIKE,AND) +- `value` (String) Value of the parameter (e.g. %10%,%NA%) diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index 55461b43..b2078c8b 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -9,6 +9,8 @@ description: |- ## 0.1.10 (unreleased) +- Add `assign_templates_to_tag` resource +- Add `tag` resource and data source - Add `pnp_import_devices` resource - Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` - Add `fabric_l3_handoff_ip_transit` resource and data source diff --git a/docs/resources/assign_templates_to_tag.md b/docs/resources/assign_templates_to_tag.md new file mode 100644 index 00000000..e98ff040 --- /dev/null +++ b/docs/resources/assign_templates_to_tag.md @@ -0,0 +1,35 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_assign_templates_to_tag Resource - terraform-provider-catalystcenter" +subcategory: "Tags" +description: |- + This resource is responsible for assigning templates to a specified tag during creation and removing the template from the tag during destruction +--- + +# catalystcenter_assign_templates_to_tag (Resource) + +This resource is responsible for assigning templates to a specified tag during creation and removing the template from the tag during destruction + +## Example Usage + +```terraform +resource "catalystcenter_assign_templates_to_tag" "example" { + tag_id = "ea505070-6bb8-493f-bff0-8058e8e03ee5" + template_ids = ["75b0f85a-8157-4db3-ae2d-9807c893319a"] +} +``` + + +## Schema + +### Required + +- `tag_id` (String) Tag Id to be associated with the template + +### Optional + +- `template_ids` (List of String) Template Ids List + +### Read-Only + +- `id` (String) The id of the object diff --git a/docs/resources/tag.md b/docs/resources/tag.md new file mode 100644 index 00000000..7438cc3f --- /dev/null +++ b/docs/resources/tag.md @@ -0,0 +1,67 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_tag Resource - terraform-provider-catalystcenter" +subcategory: "Tags" +description: |- + This resource can manage a Tag. +--- + +# catalystcenter_tag (Resource) + +This resource can manage a Tag. + +## Example Usage + +```terraform +resource "catalystcenter_tag" "example" { + name = "Tag1" + description = "Tag1 Description" + system_tag = false +} +``` + + +## Schema + +### Required + +- `name` (String) + +### Optional + +- `description` (String) Description of the tag +- `dynamic_rules` (Attributes List) Dynamic rules details (see [below for nested schema](#nestedatt--dynamic_rules)) +- `system_tag` (Boolean) true for system created tags, false for user defined tag + +### Read-Only + +- `id` (String) The id of the object + + +### Nested Schema for `dynamic_rules` + +Optional: + +- `member_type` (String) memberType of the tag (e.g. networkdevice, interface) +- `rule_items` (Attributes List) items details, multiple rules can be defined by items (see [below for nested schema](#nestedatt--dynamic_rules--rule_items)) +- `rule_name` (String) Name of the parameter (e.g. for interface:portName,adminStatus,speed,status,description. for networkdevice:family,series,hostname,managementIpAddress,groupNameHierarchy,softwareVersion) +- `rule_operation` (String) Operation of the rule (e.g. OR,IN,EQ,LIKE,ILIKE,AND) +- `rule_value` (String) Value of the parameter (e.g. for portName:1/0/1,for adminStatus,status:up/down, for speed: any integer value, for description: any valid string, for family:switches, for series:C3650, for managementIpAddress:10.197.124.90, groupNameHierarchy:Global, softwareVersion: 16.9.1) +- `rule_values` (List of String) values of the parameter,Only one of the value or values can be used for the given parameter. (for managementIpAddress e.g. ["10.197.124.90","10.197.124.91"]) + + +### Nested Schema for `dynamic_rules.rule_items` + +Optional: + +- `name` (String) Name of the parameter (e.g. managementIpAddress,hostname) +- `operation` (String) Operation of the rule (e.g. OR,IN,EQ,LIKE,ILIKE,AND) +- `value` (String) Value of the parameter (e.g. %10%,%NA%) + +## Import + +Import is supported using the following syntax: + +```shell +terraform import catalystcenter_tag.example "" +``` diff --git a/examples/data-sources/catalystcenter_assign_templates_to_tag/data-source.tf b/examples/data-sources/catalystcenter_assign_templates_to_tag/data-source.tf new file mode 100644 index 00000000..26459eba --- /dev/null +++ b/examples/data-sources/catalystcenter_assign_templates_to_tag/data-source.tf @@ -0,0 +1,3 @@ +data "catalystcenter_assign_templates_to_tag" "example" { + tag_id = "ea505070-6bb8-493f-bff0-8058e8e03ee5" +} diff --git a/examples/data-sources/catalystcenter_tag/data-source.tf b/examples/data-sources/catalystcenter_tag/data-source.tf new file mode 100644 index 00000000..c9f70e37 --- /dev/null +++ b/examples/data-sources/catalystcenter_tag/data-source.tf @@ -0,0 +1,3 @@ +data "catalystcenter_tag" "example" { + name = "Tag1" +} diff --git a/examples/resources/catalystcenter_assign_templates_to_tag/resource.tf b/examples/resources/catalystcenter_assign_templates_to_tag/resource.tf new file mode 100644 index 00000000..42c2d6dd --- /dev/null +++ b/examples/resources/catalystcenter_assign_templates_to_tag/resource.tf @@ -0,0 +1,4 @@ +resource "catalystcenter_assign_templates_to_tag" "example" { + tag_id = "ea505070-6bb8-493f-bff0-8058e8e03ee5" + template_ids = ["75b0f85a-8157-4db3-ae2d-9807c893319a"] +} diff --git a/examples/resources/catalystcenter_tag/import.sh b/examples/resources/catalystcenter_tag/import.sh new file mode 100644 index 00000000..0c07851d --- /dev/null +++ b/examples/resources/catalystcenter_tag/import.sh @@ -0,0 +1 @@ +terraform import catalystcenter_tag.example "" diff --git a/examples/resources/catalystcenter_tag/resource.tf b/examples/resources/catalystcenter_tag/resource.tf new file mode 100644 index 00000000..7408f75a --- /dev/null +++ b/examples/resources/catalystcenter_tag/resource.tf @@ -0,0 +1,5 @@ +resource "catalystcenter_tag" "example" { + name = "Tag1" + description = "Tag1 Description" + system_tag = false +} diff --git a/gen/definitions/assign_templates_to_tag.yaml b/gen/definitions/assign_templates_to_tag.yaml new file mode 100644 index 00000000..4d89625e --- /dev/null +++ b/gen/definitions/assign_templates_to_tag.yaml @@ -0,0 +1,56 @@ +--- +name: Assign Templates to Tag +rest_endpoint: /dna/intent/api/v1/tag/%v/member +get_extra_query_params: '?memberType=template' +get_no_id: true +# Manual updates in Delete function to handle removal of templates from tag +res_description: 'This resource is responsible for assigning templates to a specified tag during creation + and removing the template from the tag during destruction' +post_update: true +no_import: true +data_source_no_id: true +skip_minimum_test: true +doc_category: Tags +attributes: + - model_name: tagId + type: String + reference: true + id: true + description: Tag Id to be associated with the template + example: ea505070-6bb8-493f-bff0-8058e8e03ee5 + test_value: catalystcenter_tag.test.id + - model_name: template + tf_name: template_ids + type: List + element_type: String + description: Template Ids List + example: 75b0f85a-8157-4db3-ae2d-9807c893319a + test_value: catalystcenter_template.test.id +test_prerequisites: | + resource "catalystcenter_tag" "test" { + name = "Tag1" + description = "Tag1 Description" + system_tag = false + } + + resource "catalystcenter_project" "test" { + name = "Project1" + } + + resource "catalystcenter_template" "test" { + project_id = catalystcenter_project.test.id + name = "Template1" + description = "My description" + device_types = [ + { + product_family = "Switches and Hubs" + product_series = "Cisco Catalyst 9300 Series Switches" + product_type = "Cisco Catalyst 9300 Switch" + } + ] + language = "JINJA" + software_type = "IOS-XE" + software_variant = "XE" + software_version = "16.12.1a" + template_content = "hostname SW1" + } diff --git a/gen/definitions/tag.yaml b/gen/definitions/tag.yaml new file mode 100644 index 00000000..0f13347d --- /dev/null +++ b/gen/definitions/tag.yaml @@ -0,0 +1,71 @@ +--- +name: Tag +rest_endpoint: /dna/intent/api/v1/tag +id_from_query_path: response.0 +id_from_query_path_attribute: id +put_id_include_path: id +import_no_id: true +data_source_no_id: true +doc_category: Tags +attributes: + - model_name: name + response_data_path: response.0.name + type: String + mandatory: true + query_param: true + example: Tag1 + - model_name: description + response_data_path: response.0.description + type: String + description: Description of the tag + example: Tag1 Description + - model_name: systemTag + response_data_path: response.0.systemTag + type: Bool + description: true for system created tags, false for user defined tag + example: false + - model_name: dynamicRules + response_data_path: response.0.dynamicRules + type: List + description: Dynamic rules details + exclude_test: true + attributes: + - model_name: memberType + type: String + description: memberType of the tag (e.g. networkdevice, interface) + - model_name: values + tf_name: rule_values + data_path: rules + type: List + element_type: String + description: 'values of the parameter,Only one of the value or values can be used for the given parameter. (for managementIpAddress e.g. [\"10.197.124.90\",\"10.197.124.91\"])' + - model_name: items + tf_name: rule_items + data_path: rules + type: List + description: 'items details, multiple rules can be defined by items' + attributes: + - model_name: operation + type: String + description: Operation of the rule (e.g. OR,IN,EQ,LIKE,ILIKE,AND) + - model_name: name + type: String + description: Name of the parameter (e.g. managementIpAddress,hostname) + - model_name: value + type: String + description: Value of the parameter (e.g. %10%,%NA%) + - model_name: operation + tf_name: rule_operation + data_path: rules + type: String + description: Operation of the rule (e.g. OR,IN,EQ,LIKE,ILIKE,AND) + - model_name: name + tf_name: rule_name + data_path: rules + type: String + description: 'Name of the parameter (e.g. for interface:portName,adminStatus,speed,status,description. for networkdevice:family,series,hostname,managementIpAddress,groupNameHierarchy,softwareVersion)' + - model_name: value + tf_name: rule_value + data_path: rules + type: String + description: 'Value of the parameter (e.g. for portName:1/0/1,for adminStatus,status:up/down, for speed: any integer value, for description: any valid string, for family:switches, for series:C3650, for managementIpAddress:10.197.124.90, groupNameHierarchy:Global, softwareVersion: 16.9.1)' \ No newline at end of file diff --git a/internal/provider/data_source_catalystcenter_assign_templates_to_tag.go b/internal/provider/data_source_catalystcenter_assign_templates_to_tag.go new file mode 100644 index 00000000..110f46cb --- /dev/null +++ b/internal/provider/data_source_catalystcenter_assign_templates_to_tag.go @@ -0,0 +1,116 @@ +// 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" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "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 the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &AssignTemplatesToTagDataSource{} + _ datasource.DataSourceWithConfigure = &AssignTemplatesToTagDataSource{} +) + +func NewAssignTemplatesToTagDataSource() datasource.DataSource { + return &AssignTemplatesToTagDataSource{} +} + +type AssignTemplatesToTagDataSource struct { + client *cc.Client +} + +func (d *AssignTemplatesToTagDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_assign_templates_to_tag" +} + +func (d *AssignTemplatesToTagDataSource) 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 Assign Templates to Tag.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + }, + "tag_id": schema.StringAttribute{ + MarkdownDescription: "Tag Id to be associated with the template", + Required: true, + }, + "template_ids": schema.ListAttribute{ + MarkdownDescription: "Template Ids List", + ElementType: types.StringType, + Computed: true, + }, + }, + } +} + +func (d *AssignTemplatesToTagDataSource) 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 *AssignTemplatesToTagDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config AssignTemplatesToTag + + // 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 += "?memberType=template" + 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_assign_templates_to_tag_test.go b/internal/provider/data_source_catalystcenter_assign_templates_to_tag_test.go new file mode 100644 index 00000000..84dad71a --- /dev/null +++ b/internal/provider/data_source_catalystcenter_assign_templates_to_tag_test.go @@ -0,0 +1,96 @@ +// 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 TestAccDataSourceCcAssignTemplatesToTag(t *testing.T) { + var checks []resource.TestCheckFunc + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceCcAssignTemplatesToTagPrerequisitesConfig + testAccDataSourceCcAssignTemplatesToTagConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAccDataSource + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites +const testAccDataSourceCcAssignTemplatesToTagPrerequisitesConfig = ` +resource "catalystcenter_tag" "test" { + name = "Tag1" + description = "Tag1 Description" + system_tag = false +} + +resource "catalystcenter_project" "test" { + name = "Project1" +} + +resource "catalystcenter_template" "test" { + project_id = catalystcenter_project.test.id + name = "Template1" + description = "My description" + device_types = [ + { + product_family = "Switches and Hubs" + product_series = "Cisco Catalyst 9300 Series Switches" + product_type = "Cisco Catalyst 9300 Switch" + } + ] + language = "JINJA" + software_type = "IOS-XE" + software_variant = "XE" + software_version = "16.12.1a" + template_content = "hostname SW1" +} + +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig +func testAccDataSourceCcAssignTemplatesToTagConfig() string { + config := `resource "catalystcenter_assign_templates_to_tag" "test" {` + "\n" + config += ` tag_id = catalystcenter_tag.test.id` + "\n" + config += ` template_ids = catalystcenter_template.test.id` + "\n" + config += `}` + "\n" + + config += ` + data "catalystcenter_assign_templates_to_tag" "test" { + id = catalystcenter_assign_templates_to_tag.test.id + tag_id = catalystcenter_tag.test.id + } + ` + return config +} + +// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/data_source_catalystcenter_tag.go b/internal/provider/data_source_catalystcenter_tag.go new file mode 100644 index 00000000..d5383689 --- /dev/null +++ b/internal/provider/data_source_catalystcenter_tag.go @@ -0,0 +1,169 @@ +// 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-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 the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &TagDataSource{} + _ datasource.DataSourceWithConfigure = &TagDataSource{} +) + +func NewTagDataSource() datasource.DataSource { + return &TagDataSource{} +} + +type TagDataSource struct { + client *cc.Client +} + +func (d *TagDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_tag" +} + +func (d *TagDataSource) 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 Tag.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "", + Required: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: "Description of the tag", + Computed: true, + }, + "system_tag": schema.BoolAttribute{ + MarkdownDescription: "true for system created tags, false for user defined tag", + Computed: true, + }, + "dynamic_rules": schema.ListNestedAttribute{ + MarkdownDescription: "Dynamic rules details", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "member_type": schema.StringAttribute{ + MarkdownDescription: "memberType of the tag (e.g. networkdevice, interface)", + Computed: true, + }, + "rule_values": schema.ListAttribute{ + MarkdownDescription: "values of the parameter,Only one of the value or values can be used for the given parameter. (for managementIpAddress e.g. [\"10.197.124.90\",\"10.197.124.91\"])", + ElementType: types.StringType, + Computed: true, + }, + "rule_items": schema.ListNestedAttribute{ + MarkdownDescription: "items details, multiple rules can be defined by items", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "operation": schema.StringAttribute{ + MarkdownDescription: "Operation of the rule (e.g. OR,IN,EQ,LIKE,ILIKE,AND)", + Computed: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the parameter (e.g. managementIpAddress,hostname)", + Computed: true, + }, + "value": schema.StringAttribute{ + MarkdownDescription: "Value of the parameter (e.g. %10%,%NA%)", + Computed: true, + }, + }, + }, + }, + "rule_operation": schema.StringAttribute{ + MarkdownDescription: "Operation of the rule (e.g. OR,IN,EQ,LIKE,ILIKE,AND)", + Computed: true, + }, + "rule_name": schema.StringAttribute{ + MarkdownDescription: "Name of the parameter (e.g. for interface:portName,adminStatus,speed,status,description. for networkdevice:family,series,hostname,managementIpAddress,groupNameHierarchy,softwareVersion)", + Computed: true, + }, + "rule_value": schema.StringAttribute{ + MarkdownDescription: "Value of the parameter (e.g. for portName:1/0/1,for adminStatus,status:up/down, for speed: any integer value, for description: any valid string, for family:switches, for series:C3650, for managementIpAddress:10.197.124.90, groupNameHierarchy:Global, softwareVersion: 16.9.1)", + Computed: true, + }, + }, + }, + }, + }, + } +} + +func (d *TagDataSource) 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 *TagDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config Tag + + // 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 += "?name=" + url.QueryEscape(config.Name.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_tag_test.go b/internal/provider/data_source_catalystcenter_tag_test.go new file mode 100644 index 00000000..15d13275 --- /dev/null +++ b/internal/provider/data_source_catalystcenter_tag_test.go @@ -0,0 +1,69 @@ +// 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 TestAccDataSourceCcTag(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_tag.test", "name", "Tag1")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_tag.test", "description", "Tag1 Description")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_tag.test", "system_tag", "false")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceCcTagConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAccDataSource + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig +func testAccDataSourceCcTagConfig() string { + config := `resource "catalystcenter_tag" "test" {` + "\n" + config += ` name = "Tag1"` + "\n" + config += ` description = "Tag1 Description"` + "\n" + config += ` system_tag = false` + "\n" + config += `}` + "\n" + + config += ` + data "catalystcenter_tag" "test" { + id = catalystcenter_tag.test.id + name = "Tag1" + } + ` + return config +} + +// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/model_catalystcenter_assign_templates_to_tag.go b/internal/provider/model_catalystcenter_assign_templates_to_tag.go new file mode 100644 index 00000000..f0264dbe --- /dev/null +++ b/internal/provider/model_catalystcenter_assign_templates_to_tag.go @@ -0,0 +1,120 @@ +// 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/attr" + "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 AssignTemplatesToTag struct { + Id types.String `tfsdk:"id"` + TagId types.String `tfsdk:"tag_id"` + TemplateIds types.List `tfsdk:"template_ids"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath +func (data AssignTemplatesToTag) getPath() string { + return fmt.Sprintf("/dna/intent/api/v1/tag/%v/member", url.QueryEscape(data.TagId.ValueString())) +} + +// 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 AssignTemplatesToTag) toBody(ctx context.Context, state AssignTemplatesToTag) string { + body := "" + put := false + if state.Id.ValueString() != "" { + put = true + } + _ = put + if !data.TemplateIds.IsNull() { + var values []string + data.TemplateIds.ElementsAs(ctx, &values, false) + body, _ = sjson.Set(body, "template", values) + } + return body +} + +// End of section. //template:end toBody + +// Helper function to extract and convert instanceUuids +func extractInstanceUuids(res gjson.Result) []attr.Value { + instanceUuids := []attr.Value{} + + // Iterate over each item in the "response" array + res.Get("response").ForEach(func(key, value gjson.Result) bool { + // Check if "instanceUuid" exists and append it to the list + if uuid := value.Get("instanceUuid"); uuid.Exists() { + instanceUuids = append(instanceUuids, types.StringValue(uuid.String())) + } + return true // Continue iterating + }) + + return instanceUuids +} + +func (data *AssignTemplatesToTag) fromBody(ctx context.Context, res gjson.Result) { + // Extract and convert the list of instanceUuids + instanceUuidAttrValues := extractInstanceUuids(res) + + // Check if instanceUuidAttrValues is not empty, assign it to TemplateIds, else assign a null list + if len(instanceUuidAttrValues) > 0 { + data.TemplateIds = types.ListValueMust(types.StringType, instanceUuidAttrValues) + } else { + data.TemplateIds = types.ListNull(types.StringType) + } +} + +func (data *AssignTemplatesToTag) updateFromBody(ctx context.Context, res gjson.Result) { + // Extract and convert the list of instanceUuids + instanceUuidAttrValues := extractInstanceUuids(res) + + // Check if instanceUuidAttrValues is not empty, assign it to TemplateIds, else assign a null list + if len(instanceUuidAttrValues) > 0 { + data.TemplateIds = types.ListValueMust(types.StringType, instanceUuidAttrValues) + } else { + data.TemplateIds = types.ListNull(types.StringType) + } +} + +// Section below is generated&owned by "gen/generator.go". //template:begin isNull +func (data *AssignTemplatesToTag) isNull(ctx context.Context, res gjson.Result) bool { + if !data.TemplateIds.IsNull() { + return false + } + return true +} + +// End of section. //template:end isNull diff --git a/internal/provider/model_catalystcenter_tag.go b/internal/provider/model_catalystcenter_tag.go new file mode 100644 index 00000000..21bef8cf --- /dev/null +++ b/internal/provider/model_catalystcenter_tag.go @@ -0,0 +1,338 @@ +// 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/CiscoDevNet/terraform-provider-catalystcenter/internal/provider/helpers" + "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 Tag struct { + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + SystemTag types.Bool `tfsdk:"system_tag"` + DynamicRules []TagDynamicRules `tfsdk:"dynamic_rules"` +} + +type TagDynamicRules struct { + MemberType types.String `tfsdk:"member_type"` + RuleValues types.List `tfsdk:"rule_values"` + RuleItems []TagDynamicRulesRuleItems `tfsdk:"rule_items"` + RuleOperation types.String `tfsdk:"rule_operation"` + RuleName types.String `tfsdk:"rule_name"` + RuleValue types.String `tfsdk:"rule_value"` +} + +type TagDynamicRulesRuleItems struct { + Operation types.String `tfsdk:"operation"` + Name types.String `tfsdk:"name"` + Value types.String `tfsdk:"value"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath +func (data Tag) getPath() string { + return "/dna/intent/api/v1/tag" +} + +// 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 Tag) toBody(ctx context.Context, state Tag) string { + body := "" + put := false + if state.Id.ValueString() != "" { + put = true + body, _ = sjson.Set(body, "id", state.Id.ValueString()) + } + _ = put + if !data.Name.IsNull() { + body, _ = sjson.Set(body, "name", data.Name.ValueString()) + } + if !data.Description.IsNull() { + body, _ = sjson.Set(body, "description", data.Description.ValueString()) + } + if !data.SystemTag.IsNull() { + body, _ = sjson.Set(body, "systemTag", data.SystemTag.ValueBool()) + } + if len(data.DynamicRules) > 0 { + body, _ = sjson.Set(body, "dynamicRules", []interface{}{}) + for _, item := range data.DynamicRules { + itemBody := "" + if !item.MemberType.IsNull() { + itemBody, _ = sjson.Set(itemBody, "memberType", item.MemberType.ValueString()) + } + if !item.RuleValues.IsNull() { + var values []string + item.RuleValues.ElementsAs(ctx, &values, false) + itemBody, _ = sjson.Set(itemBody, "rules.values", values) + } + if len(item.RuleItems) > 0 { + itemBody, _ = sjson.Set(itemBody, "rules.items", []interface{}{}) + for _, childItem := range item.RuleItems { + itemChildBody := "" + if !childItem.Operation.IsNull() { + itemChildBody, _ = sjson.Set(itemChildBody, "operation", childItem.Operation.ValueString()) + } + if !childItem.Name.IsNull() { + itemChildBody, _ = sjson.Set(itemChildBody, "name", childItem.Name.ValueString()) + } + if !childItem.Value.IsNull() { + itemChildBody, _ = sjson.Set(itemChildBody, "value", childItem.Value.ValueString()) + } + itemBody, _ = sjson.SetRaw(itemBody, "rules.items.-1", itemChildBody) + } + } + if !item.RuleOperation.IsNull() { + itemBody, _ = sjson.Set(itemBody, "rules.operation", item.RuleOperation.ValueString()) + } + if !item.RuleName.IsNull() { + itemBody, _ = sjson.Set(itemBody, "rules.name", item.RuleName.ValueString()) + } + if !item.RuleValue.IsNull() { + itemBody, _ = sjson.Set(itemBody, "rules.value", item.RuleValue.ValueString()) + } + body, _ = sjson.SetRaw(body, "dynamicRules.-1", itemBody) + } + } + return body +} + +// End of section. //template:end toBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBody +func (data *Tag) 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.name"); value.Exists() { + data.Name = types.StringValue(value.String()) + } else { + data.Name = types.StringNull() + } + if value := res.Get("response.0.description"); value.Exists() { + data.Description = types.StringValue(value.String()) + } else { + data.Description = types.StringNull() + } + if value := res.Get("response.0.systemTag"); value.Exists() { + data.SystemTag = types.BoolValue(value.Bool()) + } else { + data.SystemTag = types.BoolNull() + } + if value := res.Get("response.0.dynamicRules"); value.Exists() && len(value.Array()) > 0 { + data.DynamicRules = make([]TagDynamicRules, 0) + value.ForEach(func(k, v gjson.Result) bool { + item := TagDynamicRules{} + if cValue := v.Get("memberType"); cValue.Exists() { + item.MemberType = types.StringValue(cValue.String()) + } else { + item.MemberType = types.StringNull() + } + if cValue := v.Get("rules.values"); cValue.Exists() && len(cValue.Array()) > 0 { + item.RuleValues = helpers.GetStringList(cValue.Array()) + } else { + item.RuleValues = types.ListNull(types.StringType) + } + if cValue := v.Get("rules.items"); cValue.Exists() && len(cValue.Array()) > 0 { + item.RuleItems = make([]TagDynamicRulesRuleItems, 0) + cValue.ForEach(func(ck, cv gjson.Result) bool { + cItem := TagDynamicRulesRuleItems{} + if ccValue := cv.Get("operation"); ccValue.Exists() { + cItem.Operation = types.StringValue(ccValue.String()) + } else { + cItem.Operation = types.StringNull() + } + if ccValue := cv.Get("name"); ccValue.Exists() { + cItem.Name = types.StringValue(ccValue.String()) + } else { + cItem.Name = types.StringNull() + } + if ccValue := cv.Get("value"); ccValue.Exists() { + cItem.Value = types.StringValue(ccValue.String()) + } else { + cItem.Value = types.StringNull() + } + item.RuleItems = append(item.RuleItems, cItem) + return true + }) + } + if cValue := v.Get("rules.operation"); cValue.Exists() { + item.RuleOperation = types.StringValue(cValue.String()) + } else { + item.RuleOperation = types.StringNull() + } + if cValue := v.Get("rules.name"); cValue.Exists() { + item.RuleName = types.StringValue(cValue.String()) + } else { + item.RuleName = types.StringNull() + } + if cValue := v.Get("rules.value"); cValue.Exists() { + item.RuleValue = types.StringValue(cValue.String()) + } else { + item.RuleValue = types.StringNull() + } + data.DynamicRules = append(data.DynamicRules, item) + return true + }) + } +} + +// End of section. //template:end fromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin updateFromBody +func (data *Tag) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.0.name"); value.Exists() && !data.Name.IsNull() { + data.Name = types.StringValue(value.String()) + } else { + data.Name = types.StringNull() + } + if value := res.Get("response.0.description"); value.Exists() && !data.Description.IsNull() { + data.Description = types.StringValue(value.String()) + } else { + data.Description = types.StringNull() + } + if value := res.Get("response.0.systemTag"); value.Exists() && !data.SystemTag.IsNull() { + data.SystemTag = types.BoolValue(value.Bool()) + } else { + data.SystemTag = types.BoolNull() + } + for i := range data.DynamicRules { + keys := [...]string{"memberType", "rules.operation", "rules.name", "rules.value"} + keyValues := [...]string{data.DynamicRules[i].MemberType.ValueString(), data.DynamicRules[i].RuleOperation.ValueString(), data.DynamicRules[i].RuleName.ValueString(), data.DynamicRules[i].RuleValue.ValueString()} + + var r gjson.Result + res.Get("response.0.dynamicRules").ForEach( + func(_, v gjson.Result) bool { + found := false + for ik := range keys { + if v.Get(keys[ik]).String() == keyValues[ik] { + found = true + continue + } + found = false + break + } + if found { + r = v + return false + } + return true + }, + ) + if value := r.Get("memberType"); value.Exists() && !data.DynamicRules[i].MemberType.IsNull() { + data.DynamicRules[i].MemberType = types.StringValue(value.String()) + } else { + data.DynamicRules[i].MemberType = types.StringNull() + } + if value := r.Get("rules.values"); value.Exists() && !data.DynamicRules[i].RuleValues.IsNull() { + data.DynamicRules[i].RuleValues = helpers.GetStringList(value.Array()) + } else { + data.DynamicRules[i].RuleValues = types.ListNull(types.StringType) + } + for ci := range data.DynamicRules[i].RuleItems { + keys := [...]string{"operation", "name", "value"} + keyValues := [...]string{data.DynamicRules[i].RuleItems[ci].Operation.ValueString(), data.DynamicRules[i].RuleItems[ci].Name.ValueString(), data.DynamicRules[i].RuleItems[ci].Value.ValueString()} + + var cr gjson.Result + r.Get("rules.items").ForEach( + func(_, v gjson.Result) bool { + found := false + for ik := range keys { + if v.Get(keys[ik]).String() == keyValues[ik] { + found = true + continue + } + found = false + break + } + if found { + cr = v + return false + } + return true + }, + ) + if value := cr.Get("operation"); value.Exists() && !data.DynamicRules[i].RuleItems[ci].Operation.IsNull() { + data.DynamicRules[i].RuleItems[ci].Operation = types.StringValue(value.String()) + } else { + data.DynamicRules[i].RuleItems[ci].Operation = types.StringNull() + } + if value := cr.Get("name"); value.Exists() && !data.DynamicRules[i].RuleItems[ci].Name.IsNull() { + data.DynamicRules[i].RuleItems[ci].Name = types.StringValue(value.String()) + } else { + data.DynamicRules[i].RuleItems[ci].Name = types.StringNull() + } + if value := cr.Get("value"); value.Exists() && !data.DynamicRules[i].RuleItems[ci].Value.IsNull() { + data.DynamicRules[i].RuleItems[ci].Value = types.StringValue(value.String()) + } else { + data.DynamicRules[i].RuleItems[ci].Value = types.StringNull() + } + } + if value := r.Get("rules.operation"); value.Exists() && !data.DynamicRules[i].RuleOperation.IsNull() { + data.DynamicRules[i].RuleOperation = types.StringValue(value.String()) + } else { + data.DynamicRules[i].RuleOperation = types.StringNull() + } + if value := r.Get("rules.name"); value.Exists() && !data.DynamicRules[i].RuleName.IsNull() { + data.DynamicRules[i].RuleName = types.StringValue(value.String()) + } else { + data.DynamicRules[i].RuleName = types.StringNull() + } + if value := r.Get("rules.value"); value.Exists() && !data.DynamicRules[i].RuleValue.IsNull() { + data.DynamicRules[i].RuleValue = types.StringValue(value.String()) + } else { + data.DynamicRules[i].RuleValue = types.StringNull() + } + } +} + +// End of section. //template:end updateFromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin isNull +func (data *Tag) isNull(ctx context.Context, res gjson.Result) bool { + if !data.Description.IsNull() { + return false + } + if !data.SystemTag.IsNull() { + return false + } + if len(data.DynamicRules) > 0 { + return false + } + return true +} + +// End of section. //template:end isNull diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 0245adef..913ca4f8 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -241,6 +241,7 @@ func (p *CcProvider) Resources(ctx context.Context) []func() resource.Resource { NewAnycastGatewayResource, NewAreaResource, NewAssignCredentialsResource, + NewAssignTemplatesToTagResource, NewAssociateSiteToNetworkProfileResource, NewBuildingResource, NewCredentialsCLIResource, @@ -274,6 +275,7 @@ func (p *CcProvider) Resources(ctx context.Context) []func() resource.Resource { NewProjectResource, NewRoleResource, NewSPProfileResource, + NewTagResource, NewTemplateResource, NewTemplateVersionResource, NewTransitPeerNetworkResource, @@ -292,6 +294,7 @@ func (p *CcProvider) DataSources(ctx context.Context) []func() datasource.DataSo NewAnycastGatewayDataSource, NewAreaDataSource, NewAssignCredentialsDataSource, + NewAssignTemplatesToTagDataSource, NewBuildingDataSource, NewCredentialsCLIDataSource, NewCredentialsHTTPSReadDataSource, @@ -316,6 +319,7 @@ func (p *CcProvider) DataSources(ctx context.Context) []func() datasource.DataSo NewProjectDataSource, NewRoleDataSource, NewSPProfileDataSource, + NewTagDataSource, NewTemplateDataSource, NewTemplateVersionDataSource, NewTransitPeerNetworkDataSource, diff --git a/internal/provider/resource_catalystcenter_assign_templates_to_tag.go b/internal/provider/resource_catalystcenter_assign_templates_to_tag.go new file mode 100644 index 00000000..7b3971e1 --- /dev/null +++ b/internal/provider/resource_catalystcenter_assign_templates_to_tag.go @@ -0,0 +1,234 @@ +// 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/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "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 = &AssignTemplatesToTagResource{} + +func NewAssignTemplatesToTagResource() resource.Resource { + return &AssignTemplatesToTagResource{} +} + +type AssignTemplatesToTagResource struct { + client *cc.Client +} + +func (r *AssignTemplatesToTagResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_assign_templates_to_tag" +} + +func (r *AssignTemplatesToTagResource) 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("This resource is responsible for assigning templates to a specified tag during creation and removing the template from the tag during destruction").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "tag_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Tag Id to be associated with the template").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "template_ids": schema.ListAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Template Ids List").String, + ElementType: types.StringType, + Optional: true, + }, + }, + } +} + +func (r *AssignTemplatesToTagResource) 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 *AssignTemplatesToTagResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan AssignTemplatesToTag + + // 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, AssignTemplatesToTag{}) + + params := "" + res, err := r.client.Post(plan.getPath()+params, body) + if err != nil { + 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.TagId.ValueString())) + + 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 *AssignTemplatesToTagResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state AssignTemplatesToTag + + // 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 += "?memberType=template" + 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 *AssignTemplatesToTagResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state AssignTemplatesToTag + + // 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.Post(plan.getPath()+params, body) + 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 + +func (r *AssignTemplatesToTagResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state AssignTemplatesToTag + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Extract TemplateIds into a slice of strings + var templateIds []string + diags = state.TemplateIds.ElementsAs(ctx, &templateIds, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Iterate over TemplateIds and delete each one + for _, templateId := range templateIds { + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", templateId)) + res, err := r.client.Delete(state.getPath() + "/" + url.QueryEscape(templateId)) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete template with ID %s (DELETE), got error: %s, %s", templateId, err, res.String())) + return + } + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", templateId)) + } + + resp.State.RemoveResource(ctx) +} + +// Section below is generated&owned by "gen/generator.go". //template:begin import +// End of section. //template:end import diff --git a/internal/provider/resource_catalystcenter_assign_templates_to_tag_test.go b/internal/provider/resource_catalystcenter_assign_templates_to_tag_test.go new file mode 100644 index 00000000..cdb300cd --- /dev/null +++ b/internal/provider/resource_catalystcenter_assign_templates_to_tag_test.go @@ -0,0 +1,101 @@ +// 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 testAcc +func TestAccCcAssignTemplatesToTag(t *testing.T) { + var checks []resource.TestCheckFunc + + var steps []resource.TestStep + steps = append(steps, resource.TestStep{ + Config: testAccCcAssignTemplatesToTagPrerequisitesConfig + testAccCcAssignTemplatesToTagConfig_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 testAccCcAssignTemplatesToTagPrerequisitesConfig = ` +resource "catalystcenter_tag" "test" { + name = "Tag1" + description = "Tag1 Description" + system_tag = false +} + +resource "catalystcenter_project" "test" { + name = "Project1" +} + +resource "catalystcenter_template" "test" { + project_id = catalystcenter_project.test.id + name = "Template1" + description = "My description" + device_types = [ + { + product_family = "Switches and Hubs" + product_series = "Cisco Catalyst 9300 Series Switches" + product_type = "Cisco Catalyst 9300 Switch" + } + ] + language = "JINJA" + software_type = "IOS-XE" + software_variant = "XE" + software_version = "16.12.1a" + template_content = "hostname SW1" +} + +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal +func testAccCcAssignTemplatesToTagConfig_minimum() string { + config := `resource "catalystcenter_assign_templates_to_tag" "test" {` + "\n" + config += ` tag_id = catalystcenter_tag.test.id` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll +func testAccCcAssignTemplatesToTagConfig_all() string { + config := `resource "catalystcenter_assign_templates_to_tag" "test" {` + "\n" + config += ` tag_id = catalystcenter_tag.test.id` + "\n" + config += ` template_ids = catalystcenter_template.test.id` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll diff --git a/internal/provider/resource_catalystcenter_tag.go b/internal/provider/resource_catalystcenter_tag.go new file mode 100644 index 00000000..3dca6a32 --- /dev/null +++ b/internal/provider/resource_catalystcenter_tag.go @@ -0,0 +1,298 @@ +// 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/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "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 = &TagResource{} +var _ resource.ResourceWithImportState = &TagResource{} + +func NewTagResource() resource.Resource { + return &TagResource{} +} + +type TagResource struct { + client *cc.Client +} + +func (r *TagResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_tag" +} + +func (r *TagResource) 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("This resource can manage a Tag.").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Required: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Description of the tag").String, + Optional: true, + }, + "system_tag": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("true for system created tags, false for user defined tag").String, + Optional: true, + }, + "dynamic_rules": schema.ListNestedAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Dynamic rules details").String, + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "member_type": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("memberType of the tag (e.g. networkdevice, interface)").String, + Optional: true, + }, + "rule_values": schema.ListAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("values of the parameter,Only one of the value or values can be used for the given parameter. (for managementIpAddress e.g. [\"10.197.124.90\",\"10.197.124.91\"])").String, + ElementType: types.StringType, + Optional: true, + }, + "rule_items": schema.ListNestedAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("items details, multiple rules can be defined by items").String, + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "operation": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Operation of the rule (e.g. OR,IN,EQ,LIKE,ILIKE,AND)").String, + Optional: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Name of the parameter (e.g. managementIpAddress,hostname)").String, + Optional: true, + }, + "value": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Value of the parameter (e.g. %10%,%NA%)").String, + Optional: true, + }, + }, + }, + }, + "rule_operation": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Operation of the rule (e.g. OR,IN,EQ,LIKE,ILIKE,AND)").String, + Optional: true, + }, + "rule_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Name of the parameter (e.g. for interface:portName,adminStatus,speed,status,description. for networkdevice:family,series,hostname,managementIpAddress,groupNameHierarchy,softwareVersion)").String, + Optional: true, + }, + "rule_value": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Value of the parameter (e.g. for portName:1/0/1,for adminStatus,status:up/down, for speed: any integer value, for description: any valid string, for family:switches, for series:C3650, for managementIpAddress:10.197.124.90, groupNameHierarchy:Global, softwareVersion: 16.9.1)").String, + Optional: true, + }, + }, + }, + }, + }, + } +} + +func (r *TagResource) 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 *TagResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan Tag + + // 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, Tag{}) + + params := "" + res, err := r.client.Post(plan.getPath()+params, body) + 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 += "?name=" + url.QueryEscape(plan.Name.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 *TagResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state Tag + + // 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 += "?name=" + url.QueryEscape(state.Name.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 *TagResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state Tag + + // 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) + 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 *TagResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state Tag + + // 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 *TagResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, ",") + + if len(idParts) != 1 || idParts[0] == "" { + 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("name"), idParts[0])...) +} + +// End of section. //template:end import diff --git a/internal/provider/resource_catalystcenter_tag_test.go b/internal/provider/resource_catalystcenter_tag_test.go new file mode 100644 index 00000000..0a413989 --- /dev/null +++ b/internal/provider/resource_catalystcenter_tag_test.go @@ -0,0 +1,80 @@ +// 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 TestAccCcTag(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_tag.test", "name", "Tag1")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_tag.test", "description", "Tag1 Description")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_tag.test", "system_tag", "false")) + + var steps []resource.TestStep + if os.Getenv("SKIP_MINIMUM_TEST") == "" { + steps = append(steps, resource.TestStep{ + Config: testAccCcTagConfig_minimum(), + }) + } + steps = append(steps, resource.TestStep{ + Config: testAccCcTagConfig_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 +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal +func testAccCcTagConfig_minimum() string { + config := `resource "catalystcenter_tag" "test" {` + "\n" + config += ` name = "Tag1"` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll +func testAccCcTagConfig_all() string { + config := `resource "catalystcenter_tag" "test" {` + "\n" + config += ` name = "Tag1"` + "\n" + config += ` description = "Tag1 Description"` + "\n" + config += ` system_tag = false` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index 55461b43..b2078c8b 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -9,6 +9,8 @@ description: |- ## 0.1.10 (unreleased) +- Add `assign_templates_to_tag` resource +- Add `tag` resource and data source - Add `pnp_import_devices` resource - Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` - Add `fabric_l3_handoff_ip_transit` resource and data source From 241fe0a49b0d76c9d42d070b08032384164872cf Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Fri, 26 Jul 2024 16:33:24 +0200 Subject: [PATCH 15/31] changed template_ids from List to Set --- docs/data-sources/assign_templates_to_tag.md | 2 +- docs/resources/assign_templates_to_tag.md | 2 +- gen/definitions/assign_templates_to_tag.yaml | 4 ++-- ...ta_source_catalystcenter_assign_templates_to_tag.go | 2 +- ...urce_catalystcenter_assign_templates_to_tag_test.go | 2 +- .../model_catalystcenter_assign_templates_to_tag.go | 10 +++++----- .../resource_catalystcenter_assign_templates_to_tag.go | 2 +- ...urce_catalystcenter_assign_templates_to_tag_test.go | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/data-sources/assign_templates_to_tag.md b/docs/data-sources/assign_templates_to_tag.md index 3a0c335c..9863755e 100644 --- a/docs/data-sources/assign_templates_to_tag.md +++ b/docs/data-sources/assign_templates_to_tag.md @@ -28,4 +28,4 @@ data "catalystcenter_assign_templates_to_tag" "example" { ### Read-Only - `id` (String) The id of the object -- `template_ids` (List of String) Template Ids List +- `template_ids` (Set of String) Template Ids List diff --git a/docs/resources/assign_templates_to_tag.md b/docs/resources/assign_templates_to_tag.md index e98ff040..7f7097e5 100644 --- a/docs/resources/assign_templates_to_tag.md +++ b/docs/resources/assign_templates_to_tag.md @@ -28,7 +28,7 @@ resource "catalystcenter_assign_templates_to_tag" "example" { ### Optional -- `template_ids` (List of String) Template Ids List +- `template_ids` (Set of String) Template Ids List ### Read-Only diff --git a/gen/definitions/assign_templates_to_tag.yaml b/gen/definitions/assign_templates_to_tag.yaml index 4d89625e..da0df89b 100644 --- a/gen/definitions/assign_templates_to_tag.yaml +++ b/gen/definitions/assign_templates_to_tag.yaml @@ -21,11 +21,11 @@ attributes: test_value: catalystcenter_tag.test.id - model_name: template tf_name: template_ids - type: List + type: Set element_type: String description: Template Ids List example: 75b0f85a-8157-4db3-ae2d-9807c893319a - test_value: catalystcenter_template.test.id + test_value: '[catalystcenter_template.test.id]' test_prerequisites: | resource "catalystcenter_tag" "test" { name = "Tag1" diff --git a/internal/provider/data_source_catalystcenter_assign_templates_to_tag.go b/internal/provider/data_source_catalystcenter_assign_templates_to_tag.go index 110f46cb..8aca05a5 100644 --- a/internal/provider/data_source_catalystcenter_assign_templates_to_tag.go +++ b/internal/provider/data_source_catalystcenter_assign_templates_to_tag.go @@ -65,7 +65,7 @@ func (d *AssignTemplatesToTagDataSource) Schema(ctx context.Context, req datasou MarkdownDescription: "Tag Id to be associated with the template", Required: true, }, - "template_ids": schema.ListAttribute{ + "template_ids": schema.SetAttribute{ MarkdownDescription: "Template Ids List", ElementType: types.StringType, Computed: true, diff --git a/internal/provider/data_source_catalystcenter_assign_templates_to_tag_test.go b/internal/provider/data_source_catalystcenter_assign_templates_to_tag_test.go index 84dad71a..251916dc 100644 --- a/internal/provider/data_source_catalystcenter_assign_templates_to_tag_test.go +++ b/internal/provider/data_source_catalystcenter_assign_templates_to_tag_test.go @@ -81,7 +81,7 @@ resource "catalystcenter_template" "test" { func testAccDataSourceCcAssignTemplatesToTagConfig() string { config := `resource "catalystcenter_assign_templates_to_tag" "test" {` + "\n" config += ` tag_id = catalystcenter_tag.test.id` + "\n" - config += ` template_ids = catalystcenter_template.test.id` + "\n" + config += ` template_ids = [catalystcenter_template.test.id]` + "\n" config += `}` + "\n" config += ` diff --git a/internal/provider/model_catalystcenter_assign_templates_to_tag.go b/internal/provider/model_catalystcenter_assign_templates_to_tag.go index f0264dbe..483c14a7 100644 --- a/internal/provider/model_catalystcenter_assign_templates_to_tag.go +++ b/internal/provider/model_catalystcenter_assign_templates_to_tag.go @@ -35,7 +35,7 @@ import ( type AssignTemplatesToTag struct { Id types.String `tfsdk:"id"` TagId types.String `tfsdk:"tag_id"` - TemplateIds types.List `tfsdk:"template_ids"` + TemplateIds types.Set `tfsdk:"template_ids"` } // End of section. //template:end types @@ -91,9 +91,9 @@ func (data *AssignTemplatesToTag) fromBody(ctx context.Context, res gjson.Result // Check if instanceUuidAttrValues is not empty, assign it to TemplateIds, else assign a null list if len(instanceUuidAttrValues) > 0 { - data.TemplateIds = types.ListValueMust(types.StringType, instanceUuidAttrValues) + data.TemplateIds = types.SetValueMust(types.StringType, instanceUuidAttrValues) } else { - data.TemplateIds = types.ListNull(types.StringType) + data.TemplateIds = types.SetNull(types.StringType) } } @@ -103,9 +103,9 @@ func (data *AssignTemplatesToTag) updateFromBody(ctx context.Context, res gjson. // Check if instanceUuidAttrValues is not empty, assign it to TemplateIds, else assign a null list if len(instanceUuidAttrValues) > 0 { - data.TemplateIds = types.ListValueMust(types.StringType, instanceUuidAttrValues) + data.TemplateIds = types.SetValueMust(types.StringType, instanceUuidAttrValues) } else { - data.TemplateIds = types.ListNull(types.StringType) + data.TemplateIds = types.SetNull(types.StringType) } } diff --git a/internal/provider/resource_catalystcenter_assign_templates_to_tag.go b/internal/provider/resource_catalystcenter_assign_templates_to_tag.go index 7b3971e1..c458a890 100644 --- a/internal/provider/resource_catalystcenter_assign_templates_to_tag.go +++ b/internal/provider/resource_catalystcenter_assign_templates_to_tag.go @@ -73,7 +73,7 @@ func (r *AssignTemplatesToTagResource) Schema(ctx context.Context, req resource. stringplanmodifier.RequiresReplace(), }, }, - "template_ids": schema.ListAttribute{ + "template_ids": schema.SetAttribute{ MarkdownDescription: helpers.NewAttributeDescription("Template Ids List").String, ElementType: types.StringType, Optional: true, diff --git a/internal/provider/resource_catalystcenter_assign_templates_to_tag_test.go b/internal/provider/resource_catalystcenter_assign_templates_to_tag_test.go index cdb300cd..e3401df4 100644 --- a/internal/provider/resource_catalystcenter_assign_templates_to_tag_test.go +++ b/internal/provider/resource_catalystcenter_assign_templates_to_tag_test.go @@ -93,7 +93,7 @@ func testAccCcAssignTemplatesToTagConfig_minimum() string { func testAccCcAssignTemplatesToTagConfig_all() string { config := `resource "catalystcenter_assign_templates_to_tag" "test" {` + "\n" config += ` tag_id = catalystcenter_tag.test.id` + "\n" - config += ` template_ids = catalystcenter_template.test.id` + "\n" + config += ` template_ids = [catalystcenter_template.test.id]` + "\n" config += `}` + "\n" return config } From 2db5bce297967262f0ed037988866ce7da62c1cc Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Fri, 26 Jul 2024 16:36:00 +0200 Subject: [PATCH 16/31] update changelog --- CHANGELOG.md | 2 +- docs/guides/changelog.md | 2 +- gen/definitions/assign_templates_to_tag.yaml | 2 +- templates/guides/changelog.md.tmpl | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9feb2617..44393a5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.1.10 (unreleased) -- Add `assign_templates_to_tag` resource +- Add `assign_templates_to_tag` resource and data source - Add `tag` resource and data source - Add `pnp_import_devices` resource - Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index b2078c8b..9239f00b 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -9,7 +9,7 @@ description: |- ## 0.1.10 (unreleased) -- Add `assign_templates_to_tag` resource +- Add `assign_templates_to_tag` resource and data source - Add `tag` resource and data source - Add `pnp_import_devices` resource - Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` diff --git a/gen/definitions/assign_templates_to_tag.yaml b/gen/definitions/assign_templates_to_tag.yaml index da0df89b..c749f38e 100644 --- a/gen/definitions/assign_templates_to_tag.yaml +++ b/gen/definitions/assign_templates_to_tag.yaml @@ -3,7 +3,7 @@ name: Assign Templates to Tag rest_endpoint: /dna/intent/api/v1/tag/%v/member get_extra_query_params: '?memberType=template' get_no_id: true -# Manual updates in Delete function to handle removal of templates from tag +# Manual updates in Delete function to handle removal of templates from tag in resource file, and fromBody, updateFromBody functions in model file res_description: 'This resource is responsible for assigning templates to a specified tag during creation and removing the template from the tag during destruction' post_update: true diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index b2078c8b..9239f00b 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -9,7 +9,7 @@ description: |- ## 0.1.10 (unreleased) -- Add `assign_templates_to_tag` resource +- Add `assign_templates_to_tag` resource and data source - Add `tag` resource and data source - Add `pnp_import_devices` resource - Add `fabric_device` resource and data source, this resource now only works with Catalyst Center version 2.3.7.5+ `/sda/fabricDevices` From b10aeaa328032e7ad46dcc481de7516dae1ce535 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Tue, 30 Jul 2024 13:45:05 +0200 Subject: [PATCH 17/31] add fabric_provision_device resource and data source --- CHANGELOG.md | 1 + docs/data-sources/fabric_provision_device.md | 32 +++ docs/guides/changelog.md | 1 + docs/resources/fabric_provision_device.md | 40 +++ .../data-source.tf | 4 + .../import.sh | 1 + .../resource.tf | 4 + gen/definitions/fabric_provision_device.yaml | 43 +++ ..._catalystcenter_fabric_provision_device.go | 115 ++++++++ ...lystcenter_fabric_provision_device_test.go | 84 ++++++ ..._catalystcenter_fabric_provision_device.go | 114 ++++++++ internal/provider/provider.go | 2 + ..._catalystcenter_fabric_provision_device.go | 252 ++++++++++++++++++ ...lystcenter_fabric_provision_device_test.go | 94 +++++++ templates/guides/changelog.md.tmpl | 1 + 15 files changed, 788 insertions(+) create mode 100644 docs/data-sources/fabric_provision_device.md create mode 100644 docs/resources/fabric_provision_device.md create mode 100644 examples/data-sources/catalystcenter_fabric_provision_device/data-source.tf create mode 100644 examples/resources/catalystcenter_fabric_provision_device/import.sh create mode 100644 examples/resources/catalystcenter_fabric_provision_device/resource.tf create mode 100644 gen/definitions/fabric_provision_device.yaml create mode 100644 internal/provider/data_source_catalystcenter_fabric_provision_device.go create mode 100644 internal/provider/data_source_catalystcenter_fabric_provision_device_test.go create mode 100644 internal/provider/model_catalystcenter_fabric_provision_device.go create mode 100644 internal/provider/resource_catalystcenter_fabric_provision_device.go create mode 100644 internal/provider/resource_catalystcenter_fabric_provision_device_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 44393a5d..4c1c7970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.1.10 (unreleased) +- Add `fabric_provision_device` resource and data source - Add `assign_templates_to_tag` resource and data source - Add `tag` resource and data source - Add `pnp_import_devices` resource diff --git a/docs/data-sources/fabric_provision_device.md b/docs/data-sources/fabric_provision_device.md new file mode 100644 index 00000000..8dd71a0c --- /dev/null +++ b/docs/data-sources/fabric_provision_device.md @@ -0,0 +1,32 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_fabric_provision_device Data Source - terraform-provider-catalystcenter" +subcategory: "SDA" +description: |- + This data source can read the Fabric Provision Device. +--- + +# catalystcenter_fabric_provision_device (Data Source) + +This data source can read the Fabric Provision Device. + +## Example Usage + +```terraform +data "catalystcenter_fabric_provision_device" "example" { + site_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + network_device_id = "4cb565d3-1944-42be-be9f-a87cff79e831" +} +``` + + +## Schema + +### Required + +- `network_device_id` (String) ID of network device to be provisioned +- `site_id` (String) ID of the site this network device needs to be provisioned + +### Read-Only + +- `id` (String) The id of the object diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index 9239f00b..e14ef348 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -9,6 +9,7 @@ description: |- ## 0.1.10 (unreleased) +- Add `fabric_provision_device` resource and data source - Add `assign_templates_to_tag` resource and data source - Add `tag` resource and data source - Add `pnp_import_devices` resource diff --git a/docs/resources/fabric_provision_device.md b/docs/resources/fabric_provision_device.md new file mode 100644 index 00000000..63a085b2 --- /dev/null +++ b/docs/resources/fabric_provision_device.md @@ -0,0 +1,40 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_fabric_provision_device Resource - terraform-provider-catalystcenter" +subcategory: "SDA" +description: |- + This resource can manage a Fabric Provision Device. +--- + +# catalystcenter_fabric_provision_device (Resource) + +This resource can manage a Fabric Provision Device. + +## Example Usage + +```terraform +resource "catalystcenter_fabric_provision_device" "example" { + site_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + network_device_id = "4cb565d3-1944-42be-be9f-a87cff79e831" +} +``` + + +## Schema + +### Required + +- `network_device_id` (String) ID of network device to be provisioned +- `site_id` (String) ID of the site this network device needs to be provisioned + +### Read-Only + +- `id` (String) The id of the object + +## Import + +Import is supported using the following syntax: + +```shell +terraform import catalystcenter_fabric_provision_device.example "," +``` diff --git a/examples/data-sources/catalystcenter_fabric_provision_device/data-source.tf b/examples/data-sources/catalystcenter_fabric_provision_device/data-source.tf new file mode 100644 index 00000000..56d58998 --- /dev/null +++ b/examples/data-sources/catalystcenter_fabric_provision_device/data-source.tf @@ -0,0 +1,4 @@ +data "catalystcenter_fabric_provision_device" "example" { + site_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + network_device_id = "4cb565d3-1944-42be-be9f-a87cff79e831" +} diff --git a/examples/resources/catalystcenter_fabric_provision_device/import.sh b/examples/resources/catalystcenter_fabric_provision_device/import.sh new file mode 100644 index 00000000..d8075c43 --- /dev/null +++ b/examples/resources/catalystcenter_fabric_provision_device/import.sh @@ -0,0 +1 @@ +terraform import catalystcenter_fabric_provision_device.example "," diff --git a/examples/resources/catalystcenter_fabric_provision_device/resource.tf b/examples/resources/catalystcenter_fabric_provision_device/resource.tf new file mode 100644 index 00000000..de849cbc --- /dev/null +++ b/examples/resources/catalystcenter_fabric_provision_device/resource.tf @@ -0,0 +1,4 @@ +resource "catalystcenter_fabric_provision_device" "example" { + site_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + network_device_id = "4cb565d3-1944-42be-be9f-a87cff79e831" +} diff --git a/gen/definitions/fabric_provision_device.yaml b/gen/definitions/fabric_provision_device.yaml new file mode 100644 index 00000000..3fba8022 --- /dev/null +++ b/gen/definitions/fabric_provision_device.yaml @@ -0,0 +1,43 @@ +--- +name: Fabric Provision Device +rest_endpoint: /dna/intent/api/v1/sda/provisionDevices +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 +test_tags: [SDA] +attributes: + - model_name: siteId + requires_replace: true + data_path: '0' + query_param: true + response_data_path: response.0.siteId + mandatory: true + description: ID of the site this network device needs to be provisioned + type: String + example: 5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1 + test_value: catalystcenter_area.test.id + - model_name: networkDeviceId + requires_replace: true + data_path: '0' + query_param: true + response_data_path: response.0.networkDeviceId + mandatory: true + description: ID of network device to be provisioned + type: String + example: 4cb565d3-1944-42be-be9f-a87cff79e831 +test_prerequisites: | + resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" + } + 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] + } \ No newline at end of file diff --git a/internal/provider/data_source_catalystcenter_fabric_provision_device.go b/internal/provider/data_source_catalystcenter_fabric_provision_device.go new file mode 100644 index 00000000..1014b180 --- /dev/null +++ b/internal/provider/data_source_catalystcenter_fabric_provision_device.go @@ -0,0 +1,115 @@ +// 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 = &FabricProvisionDeviceDataSource{} + _ datasource.DataSourceWithConfigure = &FabricProvisionDeviceDataSource{} +) + +func NewFabricProvisionDeviceDataSource() datasource.DataSource { + return &FabricProvisionDeviceDataSource{} +} + +type FabricProvisionDeviceDataSource struct { + client *cc.Client +} + +func (d *FabricProvisionDeviceDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_fabric_provision_device" +} + +func (d *FabricProvisionDeviceDataSource) 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 Fabric Provision Device.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + }, + "site_id": schema.StringAttribute{ + MarkdownDescription: "ID of the site this network device needs to be provisioned", + Required: true, + }, + "network_device_id": schema.StringAttribute{ + MarkdownDescription: "ID of network device to be provisioned", + Required: true, + }, + }, + } +} + +func (d *FabricProvisionDeviceDataSource) 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 *FabricProvisionDeviceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config FabricProvisionDevice + + // 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 += "?siteId=" + url.QueryEscape(config.SiteId.ValueString()) + "&networkDeviceId=" + url.QueryEscape(config.NetworkDeviceId.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_fabric_provision_device_test.go b/internal/provider/data_source_catalystcenter_fabric_provision_device_test.go new file mode 100644 index 00000000..51f9cd8b --- /dev/null +++ b/internal/provider/data_source_catalystcenter_fabric_provision_device_test.go @@ -0,0 +1,84 @@ +// 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 testAccDataSource +func TestAccDataSourceCcFabricProvisionDevice(t *testing.T) { + if os.Getenv("SDA") == "" { + t.Skip("skipping test, set environment variable SDA") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_provision_device.test", "network_device_id", "4cb565d3-1944-42be-be9f-a87cff79e831")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceCcFabricProvisionDevicePrerequisitesConfig + testAccDataSourceCcFabricProvisionDeviceConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAccDataSource + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites +const testAccDataSourceCcFabricProvisionDevicePrerequisitesConfig = ` +resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" +} +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] +} +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig +func testAccDataSourceCcFabricProvisionDeviceConfig() string { + config := `resource "catalystcenter_fabric_provision_device" "test" {` + "\n" + config += ` site_id = catalystcenter_area.test.id` + "\n" + config += ` network_device_id = "4cb565d3-1944-42be-be9f-a87cff79e831"` + "\n" + config += `}` + "\n" + + config += ` + data "catalystcenter_fabric_provision_device" "test" { + id = catalystcenter_fabric_provision_device.test.id + site_id = catalystcenter_area.test.id + network_device_id = "4cb565d3-1944-42be-be9f-a87cff79e831" + } + ` + return config +} + +// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/model_catalystcenter_fabric_provision_device.go b/internal/provider/model_catalystcenter_fabric_provision_device.go new file mode 100644 index 00000000..c510b123 --- /dev/null +++ b/internal/provider/model_catalystcenter_fabric_provision_device.go @@ -0,0 +1,114 @@ +// 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 FabricProvisionDevice struct { + Id types.String `tfsdk:"id"` + SiteId types.String `tfsdk:"site_id"` + NetworkDeviceId types.String `tfsdk:"network_device_id"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath +func (data FabricProvisionDevice) getPath() string { + return "/dna/intent/api/v1/sda/provisionDevices" +} + +// 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 FabricProvisionDevice) toBody(ctx context.Context, state FabricProvisionDevice) string { + body := "" + put := false + if state.Id.ValueString() != "" { + put = true + body, _ = sjson.Set(body, "0.id", state.Id.ValueString()) + } + _ = put + if !data.SiteId.IsNull() { + body, _ = sjson.Set(body, "0.siteId", data.SiteId.ValueString()) + } + if !data.NetworkDeviceId.IsNull() { + body, _ = sjson.Set(body, "0.networkDeviceId", data.NetworkDeviceId.ValueString()) + } + return body +} + +// End of section. //template:end toBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBody +func (data *FabricProvisionDevice) 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.siteId"); value.Exists() { + data.SiteId = types.StringValue(value.String()) + } else { + data.SiteId = types.StringNull() + } + if value := res.Get("response.0.networkDeviceId"); value.Exists() { + data.NetworkDeviceId = types.StringValue(value.String()) + } else { + data.NetworkDeviceId = types.StringNull() + } +} + +// End of section. //template:end fromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin updateFromBody +func (data *FabricProvisionDevice) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.0.siteId"); value.Exists() && !data.SiteId.IsNull() { + data.SiteId = types.StringValue(value.String()) + } else { + data.SiteId = types.StringNull() + } + if value := res.Get("response.0.networkDeviceId"); value.Exists() && !data.NetworkDeviceId.IsNull() { + data.NetworkDeviceId = types.StringValue(value.String()) + } else { + data.NetworkDeviceId = types.StringNull() + } +} + +// End of section. //template:end updateFromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin isNull +func (data *FabricProvisionDevice) isNull(ctx context.Context, res gjson.Result) bool { + return true +} + +// End of section. //template:end isNull diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 913ca4f8..d26117fb 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -257,6 +257,7 @@ func (p *CcProvider) Resources(ctx context.Context) []func() resource.Resource { NewFabricAuthenticationProfileResource, NewFabricDeviceResource, NewFabricL3HandoffIPTransitResource, + NewFabricProvisionDeviceResource, NewFabricSiteResource, NewFabricVirtualNetworkResource, NewFloorResource, @@ -307,6 +308,7 @@ func (p *CcProvider) DataSources(ctx context.Context) []func() datasource.DataSo NewFabricAuthenticationProfileDataSource, NewFabricDeviceDataSource, NewFabricL3HandoffIPTransitDataSource, + NewFabricProvisionDeviceDataSource, NewFabricSiteDataSource, NewFabricVirtualNetworkDataSource, NewFloorDataSource, diff --git a/internal/provider/resource_catalystcenter_fabric_provision_device.go b/internal/provider/resource_catalystcenter_fabric_provision_device.go new file mode 100644 index 00000000..90c3283f --- /dev/null +++ b/internal/provider/resource_catalystcenter_fabric_provision_device.go @@ -0,0 +1,252 @@ +// 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/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "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 = &FabricProvisionDeviceResource{} +var _ resource.ResourceWithImportState = &FabricProvisionDeviceResource{} + +func NewFabricProvisionDeviceResource() resource.Resource { + return &FabricProvisionDeviceResource{} +} + +type FabricProvisionDeviceResource struct { + client *cc.Client +} + +func (r *FabricProvisionDeviceResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_fabric_provision_device" +} + +func (r *FabricProvisionDeviceResource) 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("This resource can manage a Fabric Provision Device.").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "site_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("ID of the site this network device needs to be provisioned").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "network_device_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("ID of network device to be provisioned").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (r *FabricProvisionDeviceResource) 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 *FabricProvisionDeviceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan FabricProvisionDevice + + // 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, FabricProvisionDevice{}) + + 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 += "?siteId=" + url.QueryEscape(plan.SiteId.ValueString()) + "&networkDeviceId=" + url.QueryEscape(plan.NetworkDeviceId.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 *FabricProvisionDeviceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state FabricProvisionDevice + + // 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 += "?siteId=" + url.QueryEscape(state.SiteId.ValueString()) + "&networkDeviceId=" + url.QueryEscape(state.NetworkDeviceId.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 *FabricProvisionDeviceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state FabricProvisionDevice + + // 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 *FabricProvisionDeviceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state FabricProvisionDevice + + // 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 *FabricProvisionDeviceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, ",") + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + 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("site_id"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_device_id"), idParts[1])...) +} + +// End of section. //template:end import diff --git a/internal/provider/resource_catalystcenter_fabric_provision_device_test.go b/internal/provider/resource_catalystcenter_fabric_provision_device_test.go new file mode 100644 index 00000000..891a0032 --- /dev/null +++ b/internal/provider/resource_catalystcenter_fabric_provision_device_test.go @@ -0,0 +1,94 @@ +// 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 TestAccCcFabricProvisionDevice(t *testing.T) { + if os.Getenv("SDA") == "" { + t.Skip("skipping test, set environment variable SDA") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_provision_device.test", "network_device_id", "4cb565d3-1944-42be-be9f-a87cff79e831")) + + var steps []resource.TestStep + if os.Getenv("SKIP_MINIMUM_TEST") == "" { + steps = append(steps, resource.TestStep{ + Config: testAccCcFabricProvisionDevicePrerequisitesConfig + testAccCcFabricProvisionDeviceConfig_minimum(), + }) + } + steps = append(steps, resource.TestStep{ + Config: testAccCcFabricProvisionDevicePrerequisitesConfig + testAccCcFabricProvisionDeviceConfig_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 testAccCcFabricProvisionDevicePrerequisitesConfig = ` +resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" +} +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] +} +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal +func testAccCcFabricProvisionDeviceConfig_minimum() string { + config := `resource "catalystcenter_fabric_provision_device" "test" {` + "\n" + config += ` site_id = catalystcenter_area.test.id` + "\n" + config += ` network_device_id = "4cb565d3-1944-42be-be9f-a87cff79e831"` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll +func testAccCcFabricProvisionDeviceConfig_all() string { + config := `resource "catalystcenter_fabric_provision_device" "test" {` + "\n" + config += ` site_id = catalystcenter_area.test.id` + "\n" + config += ` network_device_id = "4cb565d3-1944-42be-be9f-a87cff79e831"` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index 9239f00b..e14ef348 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -9,6 +9,7 @@ description: |- ## 0.1.10 (unreleased) +- Add `fabric_provision_device` resource and data source - Add `assign_templates_to_tag` resource and data source - Add `tag` resource and data source - Add `pnp_import_devices` resource From f478bfba4b0d08f2672f199e78a12ec591b437d0 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Wed, 31 Jul 2024 12:49:33 +0200 Subject: [PATCH 18/31] modified max_async_wait_time in fabric_device resource --- gen/definitions/fabric_device.yaml | 2 +- internal/provider/resource_catalystcenter_fabric_device.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gen/definitions/fabric_device.yaml b/gen/definitions/fabric_device.yaml index 41b5c34d..42934cc9 100644 --- a/gen/definitions/fabric_device.yaml +++ b/gen/definitions/fabric_device.yaml @@ -8,7 +8,7 @@ put_id_include_path: 0.id import_no_id: true data_source_no_id: true put_no_id: true -max_async_wait_time: 120 +max_async_wait_time: 300 doc_category: SDA test_tags: [SDA] attributes: diff --git a/internal/provider/resource_catalystcenter_fabric_device.go b/internal/provider/resource_catalystcenter_fabric_device.go index a6b7c212..446fe92c 100644 --- a/internal/provider/resource_catalystcenter_fabric_device.go +++ b/internal/provider/resource_catalystcenter_fabric_device.go @@ -158,7 +158,7 @@ func (r *FabricDeviceResource) Create(ctx context.Context, req resource.CreateRe body := plan.toBody(ctx, FabricDevice{}) params := "" - res, err := r.client.Post(plan.getPath()+params, body, func(r *cc.Req) { r.MaxAsyncWaitTime = 120 }) + res, err := r.client.Post(plan.getPath()+params, body, func(r *cc.Req) { r.MaxAsyncWaitTime = 300 }) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST), got error: %s, %s", err, res.String())) return @@ -240,7 +240,7 @@ func (r *FabricDeviceResource) Update(ctx context.Context, req resource.UpdateRe body := plan.toBody(ctx, state) params := "" - res, err := r.client.Put(plan.getPath()+params, body, func(r *cc.Req) { r.MaxAsyncWaitTime = 120 }) + res, err := r.client.Put(plan.getPath()+params, body, func(r *cc.Req) { r.MaxAsyncWaitTime = 300 }) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) return From 25435accd048cd0d1f44512147e4c2f08298ba3a Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Wed, 31 Jul 2024 12:51:05 +0200 Subject: [PATCH 19/31] modified max_async_wait_time in fabric_provision_device resource --- gen/definitions/fabric_provision_device.yaml | 2 +- .../resource_catalystcenter_fabric_provision_device.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gen/definitions/fabric_provision_device.yaml b/gen/definitions/fabric_provision_device.yaml index 3fba8022..bd5d5edd 100644 --- a/gen/definitions/fabric_provision_device.yaml +++ b/gen/definitions/fabric_provision_device.yaml @@ -7,7 +7,7 @@ import_no_id: true data_source_no_id: true put_id_include_path: "0.id" put_no_id: true -max_async_wait_time: 120 +max_async_wait_time: 300 doc_category: SDA test_tags: [SDA] attributes: diff --git a/internal/provider/resource_catalystcenter_fabric_provision_device.go b/internal/provider/resource_catalystcenter_fabric_provision_device.go index 90c3283f..05c0fac3 100644 --- a/internal/provider/resource_catalystcenter_fabric_provision_device.go +++ b/internal/provider/resource_catalystcenter_fabric_provision_device.go @@ -113,7 +113,7 @@ func (r *FabricProvisionDeviceResource) Create(ctx context.Context, req resource body := plan.toBody(ctx, FabricProvisionDevice{}) params := "" - res, err := r.client.Post(plan.getPath()+params, body, func(r *cc.Req) { r.MaxAsyncWaitTime = 120 }) + res, err := r.client.Post(plan.getPath()+params, body, func(r *cc.Req) { r.MaxAsyncWaitTime = 300 }) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST), got error: %s, %s", err, res.String())) return @@ -195,7 +195,7 @@ func (r *FabricProvisionDeviceResource) Update(ctx context.Context, req resource body := plan.toBody(ctx, state) params := "" - res, err := r.client.Put(plan.getPath()+params, body, func(r *cc.Req) { r.MaxAsyncWaitTime = 120 }) + res, err := r.client.Put(plan.getPath()+params, body, func(r *cc.Req) { r.MaxAsyncWaitTime = 300 }) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) return From 01fefd8956c34ecedc369e8e20fe9896853f6a9a Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Thu, 1 Aug 2024 14:06:15 +0200 Subject: [PATCH 20/31] add wireless_device_provision, add additional params in enterprise_ssid, increase timeout in fabric_device --- docs/data-sources/wireless_enterprise_ssid.md | 6 + docs/resources/fabric_device.md | 1 - docs/resources/wireless_device_provision.md | 50 ++++ docs/resources/wireless_enterprise_ssid.md | 7 + .../resource.tf | 5 + gen/definitions/fabric_device.yaml | 1 - .../wireless_device_provision.yaml | 67 +++++ gen/definitions/wireless_enterprise_ssid.yaml | 34 +++ ...catalystcenter_wireless_enterprise_ssid.go | 25 ++ .../model_catalystcenter_fabric_device.go | 4 +- ...atalystcenter_wireless_device_provision.go | 261 ++++++++++++++++++ ...catalystcenter_wireless_enterprise_ssid.go | 104 +++++++ internal/provider/provider.go | 1 + .../resource_catalystcenter_fabric_device.go | 5 +- ...atalystcenter_wireless_device_provision.go | 249 +++++++++++++++++ ...stcenter_wireless_device_provision_test.go | 84 ++++++ ...catalystcenter_wireless_enterprise_ssid.go | 28 ++ 17 files changed, 924 insertions(+), 8 deletions(-) create mode 100644 docs/resources/wireless_device_provision.md create mode 100644 examples/resources/catalystcenter_wireless_device_provision/resource.tf create mode 100644 gen/definitions/wireless_device_provision.yaml create mode 100644 internal/provider/model_catalystcenter_wireless_device_provision.go create mode 100644 internal/provider/resource_catalystcenter_wireless_device_provision.go create mode 100644 internal/provider/resource_catalystcenter_wireless_device_provision_test.go diff --git a/docs/data-sources/wireless_enterprise_ssid.md b/docs/data-sources/wireless_enterprise_ssid.md index 491ed9f5..79514ce6 100644 --- a/docs/data-sources/wireless_enterprise_ssid.md +++ b/docs/data-sources/wireless_enterprise_ssid.md @@ -28,6 +28,7 @@ data "catalystcenter_wireless_enterprise_ssid" "example" { ### Read-Only - `aaa_override` (Boolean) AAA Override +- `auth_key_mgmt` (Set of String) Takes string inputs for the AKMs that should be set true. Possible AKM values : dot1x,dot1x_ft, dot1x_sha, psk, psk_ft, psk_sha, owe, sae, sae_ft - `basic_service_set_client_idle_timeout` (Number) Basic Service Set Client Idle Timeout - `client_exclusion_timeout` (Number) Client Exclusion Timeout - `client_rate_limit` (Number) Client Rate Limit (in bits per second) @@ -41,6 +42,8 @@ data "catalystcenter_wireless_enterprise_ssid" "example" { - `enable_neighbor_list` (Boolean) Enable Neighbor List - `enable_session_time_out` (Boolean) Enable Session Timeout - `fast_transition` (String) Fast Transition +- `ghz24_policy` (String) Ghz24 Policy +- `ghz6_policy_client_steering` (Boolean) Ghz6 Policy Client Steering - `mfp_client_protection` (String) Mfp Client Protection - `multi_psk_settings` (Attributes List) Multi PSK Settings (Only applicable for SSID with PERSONAL auth type and PSK) (see [below for nested schema](#nestedatt--multi_psk_settings)) - `name` (String) SSID Name @@ -50,6 +53,9 @@ data "catalystcenter_wireless_enterprise_ssid" "example" { - `profile_name` (String) Profile Name - `protected_management_frame` (String) Protected Management Frame - `radio_policy` (String) Radio Policy +- `rsn_cipher_suite_ccmp256` (Boolean) Rsn Cipher Suite Ccmp256 +- `rsn_cipher_suite_gcmp128` (Boolean) Rsn Cipher Suite Gcmp 128 +- `rsn_cipher_suite_gcmp256` (Boolean) Rsn Cipher Suite Gcmp256 - `security_level` (String) Security Level - `session_time_out` (Number) Session Time Out - `traffic_type` (String) Traffic Type diff --git a/docs/resources/fabric_device.md b/docs/resources/fabric_device.md index 21a96e4c..4bf1be37 100644 --- a/docs/resources/fabric_device.md +++ b/docs/resources/fabric_device.md @@ -39,7 +39,6 @@ resource "catalystcenter_fabric_device" "example" { - `border_priority` (Number) Border priority of the fabric border device. A lower value indicates higher priority - Range: `1`-`9` - - Default value: `10` - `border_types` (List of String) List of the border types of the fabric device. Allowed values are [LAYER_2, LAYER_3] - `default_exit` (Boolean) Set this to make the fabric border device the gateway of last resort for this site. Any unknown traffic will be sent to this fabric border device from edge nodes - `import_external_routes` (Boolean) Set this to import external routes from other routing protocols (such as BGP) to the fabric control plane diff --git a/docs/resources/wireless_device_provision.md b/docs/resources/wireless_device_provision.md new file mode 100644 index 00000000..1be1a654 --- /dev/null +++ b/docs/resources/wireless_device_provision.md @@ -0,0 +1,50 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_wireless_device_provision Resource - terraform-provider-catalystcenter" +subcategory: "Wireless" +description: |- + This resource provision wireless device. Every time this resource is created or re-created, the Catalyst Center considers provisioning new wireless device. When this resource is destroyed or updated or refreshed, no actions are done either on CatalystCenter or on devices +--- + +# catalystcenter_wireless_device_provision (Resource) + +This resource provision wireless device. Every time this resource is created or re-created, the Catalyst Center considers provisioning new wireless device. When this resource is destroyed or updated or refreshed, no actions are done either on CatalystCenter or on devices + +## Example Usage + +```terraform +resource "catalystcenter_wireless_device_provision" "example" { + device_name = "WLC_01" + site = "Global/Area1" + managed_ap_locations = ["Global/Area1"] +} +``` + + +## Schema + +### Required + +- `device_name` (String) Controller Name +- `managed_ap_locations` (Set of String) List of managed AP locations +- `site` (String) Full Site Hierarchy where device has to be assigned + +### Optional + +- `dynamic_interfaces` (Attributes List) Dynamic Interface Details. The required attributes depend on the device type (see [below for nested schema](#nestedatt--dynamic_interfaces)) + +### Read-Only + +- `id` (String) The id of the object + + +### Nested Schema for `dynamic_interfaces` + +Optional: + +- `interface_gateway` (String) Interface Gateway. Required for AireOS +- `interface_ip_address` (String) Interface IP Address. Required for AireOS +- `interface_name` (String) Interface Name. Required for both AireOS and EWLC. +- `interface_netmask` (Number) Interface Netmask In CIDR. Required for AireOS +- `lag_or_port_number` (String) LAG or Port Number. Required for AireOS +- `vlan_id` (Number) VLAN ID. Required for both AireOS and EWLC diff --git a/docs/resources/wireless_enterprise_ssid.md b/docs/resources/wireless_enterprise_ssid.md index c5c0155e..7f57d917 100644 --- a/docs/resources/wireless_enterprise_ssid.md +++ b/docs/resources/wireless_enterprise_ssid.md @@ -48,6 +48,7 @@ resource "catalystcenter_wireless_enterprise_ssid" "example" { ### Optional - `aaa_override` (Boolean) AAA Override +- `auth_key_mgmt` (Set of String) Takes string inputs for the AKMs that should be set true. Possible AKM values : dot1x,dot1x_ft, dot1x_sha, psk, psk_ft, psk_sha, owe, sae, sae_ft - `basic_service_set_client_idle_timeout` (Number) Basic Service Set Client Idle Timeout - `client_exclusion_timeout` (Number) Client Exclusion Timeout - `client_rate_limit` (Number) Client Rate Limit (in bits per second) @@ -62,6 +63,9 @@ resource "catalystcenter_wireless_enterprise_ssid" "example" { - `enable_session_time_out` (Boolean) Enable Session Timeout - `fast_transition` (String) Fast Transition - Choices: `Adaptive`, `Enable`, `Disable` +- `ghz24_policy` (String) Ghz24 Policy + - Choices: `dot11-g-only`, `dot11-bg-only` +- `ghz6_policy_client_steering` (Boolean) Ghz6 Policy Client Steering - `mfp_client_protection` (String) Mfp Client Protection - Choices: `Optional`, `Disabled`, `Required` - `multi_psk_settings` (Attributes List) Multi PSK Settings (Only applicable for SSID with PERSONAL auth type and PSK) (see [below for nested schema](#nestedatt--multi_psk_settings)) @@ -73,6 +77,9 @@ resource "catalystcenter_wireless_enterprise_ssid" "example" { - Choices: `Optional`, `Disabled`, `Required` - `radio_policy` (String) Radio Policy - Choices: `Triple band operation(2.4GHz, 5GHz and 6GHz)`, `Triple band operation with band select`, `5GHz only`, `2.4GHz only`, `6GHz only` +- `rsn_cipher_suite_ccmp256` (Boolean) Rsn Cipher Suite Ccmp256 +- `rsn_cipher_suite_gcmp128` (Boolean) Rsn Cipher Suite Gcmp 128 +- `rsn_cipher_suite_gcmp256` (Boolean) Rsn Cipher Suite Gcmp256 - `session_time_out` (Number) Session Time Out - `traffic_type` (String) Traffic Type - Choices: `voicedata`, `data` diff --git a/examples/resources/catalystcenter_wireless_device_provision/resource.tf b/examples/resources/catalystcenter_wireless_device_provision/resource.tf new file mode 100644 index 00000000..a18da9e5 --- /dev/null +++ b/examples/resources/catalystcenter_wireless_device_provision/resource.tf @@ -0,0 +1,5 @@ +resource "catalystcenter_wireless_device_provision" "example" { + device_name = "WLC_01" + site = "Global/Area1" + managed_ap_locations = ["Global/Area1"] +} diff --git a/gen/definitions/fabric_device.yaml b/gen/definitions/fabric_device.yaml index 42934cc9..f65cadc3 100644 --- a/gen/definitions/fabric_device.yaml +++ b/gen/definitions/fabric_device.yaml @@ -74,7 +74,6 @@ attributes: max_int: 9 description: Border priority of the fabric border device. A lower value indicates higher priority example: 5 - default_value: 10 - model_name: prependAutonomousSystemCount data_path: '0.borderDeviceSettings.layer3Settings' response_data_path: response.0.borderDeviceSettings.layer3Settings.prependAutonomousSystemCount diff --git a/gen/definitions/wireless_device_provision.yaml b/gen/definitions/wireless_device_provision.yaml new file mode 100644 index 00000000..f256c1a6 --- /dev/null +++ b/gen/definitions/wireless_device_provision.yaml @@ -0,0 +1,67 @@ +--- +name: Wireless Device Provision +rest_endpoint: /dna/intent/api/v1/wireless/provision +res_description: 'This resource provision wireless device. Every time this resource is created or re-created, the Catalyst Center considers provisioning new wireless device. + When this resource is destroyed or updated or refreshed, no actions are done either on CatalystCenter or on devices' +no_data_source: true +no_read: true +no_delete: true +no_import: true +put_no_id: true +id_from_attribute: true +doc_category: Wireless +max_async_wait_time: 300 +test_tags: [WIRELESS] +attributes: + - model_name: deviceName + requires_replace: true + data_path: '0' + id: true + mandatory: true + description: Controller Name + type: String + example: WLC_01 + - model_name: site + requires_replace: true + data_path: '0' + mandatory: true + description: Full Site Hierarchy where device has to be assigned + type: String + example: Global/Area1 + - model_name: managedAPLocations + tf_name: managed_ap_locations + requires_replace: true + data_path: '0' + mandatory: true + description: List of managed AP locations + type: Set + element_type: String + example: Global/Area1 + - model_name: dynamicInterfaces + type: List + description: Dynamic Interface Details. The required attributes depend on the device type + requires_replace: true + data_path: '0' + exclude_test: true + attributes: + - model_name: interfaceIPAddress + tf_name: interface_ip_address + type: String + description: Interface IP Address. Required for AireOS + - model_name: interfaceNetmaskInCIDR + tf_name: interface_netmask + type: Int64 + description: Interface Netmask In CIDR. Required for AireOS + - model_name: interfaceGateway + type: String + description: Interface Gateway. Required for AireOS + - model_name: lagOrPortNumber + type: String + description: LAG or Port Number. Required for AireOS + - model_name: vlanId + type: Int64 + description: VLAN ID. Required for both AireOS and EWLC + - model_name: interfaceName + type: String + description: Interface Name. Required for both AireOS and EWLC. + \ No newline at end of file diff --git a/gen/definitions/wireless_enterprise_ssid.yaml b/gen/definitions/wireless_enterprise_ssid.yaml index d0d524ed..44f34547 100644 --- a/gen/definitions/wireless_enterprise_ssid.yaml +++ b/gen/definitions/wireless_enterprise_ssid.yaml @@ -185,3 +185,37 @@ attributes: response_data_path: 0.ssidDetails.0.clientRateLimit description: Client Rate Limit (in bits per second) exclude_test: true + - model_name: authKeyMgmt + type: Set + element_type: String + response_data_path: 0.ssidDetails.0.authKeyMgmt + description: 'Takes string inputs for the AKMs that should be set true. Possible AKM values : dot1x,dot1x_ft, dot1x_sha, psk, psk_ft, psk_sha, owe, sae, sae_ft' + exclude_test: true + - model_name: rsnCipherSuiteGcmp256 + type: Bool + response_data_path: 0.ssidDetails.0.rsnCipherSuiteGcmp256 + description: Rsn Cipher Suite Gcmp256 + exclude_test: true + - model_name: rsnCipherSuiteCcmp256 + type: Bool + response_data_path: 0.ssidDetails.0.rsnCipherSuiteCcmp256 + description: Rsn Cipher Suite Ccmp256 + exclude_test: true + - model_name: rsnCipherSuiteGcmp128 + type: Bool + response_data_path: 0.ssidDetails.0.rsnCipherSuiteGcmp128 + description: Rsn Cipher Suite Gcmp 128 + exclude_test: true + - model_name: ghz6PolicyClientSteering + type: Bool + response_data_path: 0.ssidDetails.0.ghz6PolicyClientSteering + description: Ghz6 Policy Client Steering + exclude_test: true + - model_name: ghz24Policy + type: String + enum_values: + - dot11-g-only + - dot11-bg-only + response_data_path: 0.ssidDetails.0.ghz24Policy + description: Ghz24 Policy + exclude_test: true \ No newline at end of file diff --git a/internal/provider/data_source_catalystcenter_wireless_enterprise_ssid.go b/internal/provider/data_source_catalystcenter_wireless_enterprise_ssid.go index f7b90f8a..b7cd5052 100644 --- a/internal/provider/data_source_catalystcenter_wireless_enterprise_ssid.go +++ b/internal/provider/data_source_catalystcenter_wireless_enterprise_ssid.go @@ -183,6 +183,31 @@ func (d *WirelessEnterpriseSSIDDataSource) Schema(ctx context.Context, req datas MarkdownDescription: "Client Rate Limit (in bits per second)", Computed: true, }, + "auth_key_mgmt": schema.SetAttribute{ + MarkdownDescription: "Takes string inputs for the AKMs that should be set true. Possible AKM values : dot1x,dot1x_ft, dot1x_sha, psk, psk_ft, psk_sha, owe, sae, sae_ft", + ElementType: types.StringType, + Computed: true, + }, + "rsn_cipher_suite_gcmp256": schema.BoolAttribute{ + MarkdownDescription: "Rsn Cipher Suite Gcmp256", + Computed: true, + }, + "rsn_cipher_suite_ccmp256": schema.BoolAttribute{ + MarkdownDescription: "Rsn Cipher Suite Ccmp256", + Computed: true, + }, + "rsn_cipher_suite_gcmp128": schema.BoolAttribute{ + MarkdownDescription: "Rsn Cipher Suite Gcmp 128", + Computed: true, + }, + "ghz6_policy_client_steering": schema.BoolAttribute{ + MarkdownDescription: "Ghz6 Policy Client Steering", + Computed: true, + }, + "ghz24_policy": schema.StringAttribute{ + MarkdownDescription: "Ghz24 Policy", + Computed: true, + }, }, } } diff --git a/internal/provider/model_catalystcenter_fabric_device.go b/internal/provider/model_catalystcenter_fabric_device.go index 5544a987..518c8a6f 100644 --- a/internal/provider/model_catalystcenter_fabric_device.go +++ b/internal/provider/model_catalystcenter_fabric_device.go @@ -147,7 +147,7 @@ func (data *FabricDevice) fromBody(ctx context.Context, res gjson.Result) { if value := res.Get("response.0.borderDeviceSettings.layer3Settings.borderPriority"); value.Exists() { data.BorderPriority = types.Int64Value(value.Int()) } else { - data.BorderPriority = types.Int64Value(10) + data.BorderPriority = types.Int64Null() } if value := res.Get("response.0.borderDeviceSettings.layer3Settings.prependAutonomousSystemCount"); value.Exists() { data.PrependAutonomousSystemCount = types.Int64Value(value.Int()) @@ -197,7 +197,7 @@ func (data *FabricDevice) updateFromBody(ctx context.Context, res gjson.Result) } if value := res.Get("response.0.borderDeviceSettings.layer3Settings.borderPriority"); value.Exists() && !data.BorderPriority.IsNull() { data.BorderPriority = types.Int64Value(value.Int()) - } else if data.BorderPriority.ValueInt64() != 10 { + } else { data.BorderPriority = types.Int64Null() } if value := res.Get("response.0.borderDeviceSettings.layer3Settings.prependAutonomousSystemCount"); value.Exists() && !data.PrependAutonomousSystemCount.IsNull() { diff --git a/internal/provider/model_catalystcenter_wireless_device_provision.go b/internal/provider/model_catalystcenter_wireless_device_provision.go new file mode 100644 index 00000000..8de15949 --- /dev/null +++ b/internal/provider/model_catalystcenter_wireless_device_provision.go @@ -0,0 +1,261 @@ +// 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" + "strconv" + + "github.com/CiscoDevNet/terraform-provider-catalystcenter/internal/provider/helpers" + "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 WirelessDeviceProvision struct { + Id types.String `tfsdk:"id"` + DeviceName types.String `tfsdk:"device_name"` + Site types.String `tfsdk:"site"` + ManagedApLocations types.Set `tfsdk:"managed_ap_locations"` + DynamicInterfaces []WirelessDeviceProvisionDynamicInterfaces `tfsdk:"dynamic_interfaces"` +} + +type WirelessDeviceProvisionDynamicInterfaces struct { + InterfaceIpAddress types.String `tfsdk:"interface_ip_address"` + InterfaceNetmask types.Int64 `tfsdk:"interface_netmask"` + InterfaceGateway types.String `tfsdk:"interface_gateway"` + LagOrPortNumber types.String `tfsdk:"lag_or_port_number"` + VlanId types.Int64 `tfsdk:"vlan_id"` + InterfaceName types.String `tfsdk:"interface_name"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath +func (data WirelessDeviceProvision) getPath() string { + return "/dna/intent/api/v1/wireless/provision" +} + +// 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 WirelessDeviceProvision) toBody(ctx context.Context, state WirelessDeviceProvision) string { + body := "" + put := false + if state.Id.ValueString() != "" { + put = true + } + _ = put + if !data.DeviceName.IsNull() { + body, _ = sjson.Set(body, "0.deviceName", data.DeviceName.ValueString()) + } + if !data.Site.IsNull() { + body, _ = sjson.Set(body, "0.site", data.Site.ValueString()) + } + if !data.ManagedApLocations.IsNull() { + var values []string + data.ManagedApLocations.ElementsAs(ctx, &values, false) + body, _ = sjson.Set(body, "0.managedAPLocations", values) + } + if len(data.DynamicInterfaces) > 0 { + body, _ = sjson.Set(body, "0.dynamicInterfaces", []interface{}{}) + for _, item := range data.DynamicInterfaces { + itemBody := "" + if !item.InterfaceIpAddress.IsNull() { + itemBody, _ = sjson.Set(itemBody, "interfaceIPAddress", item.InterfaceIpAddress.ValueString()) + } + if !item.InterfaceNetmask.IsNull() { + itemBody, _ = sjson.Set(itemBody, "interfaceNetmaskInCIDR", item.InterfaceNetmask.ValueInt64()) + } + if !item.InterfaceGateway.IsNull() { + itemBody, _ = sjson.Set(itemBody, "interfaceGateway", item.InterfaceGateway.ValueString()) + } + if !item.LagOrPortNumber.IsNull() { + itemBody, _ = sjson.Set(itemBody, "lagOrPortNumber", item.LagOrPortNumber.ValueString()) + } + if !item.VlanId.IsNull() { + itemBody, _ = sjson.Set(itemBody, "vlanId", item.VlanId.ValueInt64()) + } + if !item.InterfaceName.IsNull() { + itemBody, _ = sjson.Set(itemBody, "interfaceName", item.InterfaceName.ValueString()) + } + body, _ = sjson.SetRaw(body, "0.dynamicInterfaces.-1", itemBody) + } + } + return body +} + +// End of section. //template:end toBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBody +func (data *WirelessDeviceProvision) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("0.deviceName"); value.Exists() { + data.DeviceName = types.StringValue(value.String()) + } else { + data.DeviceName = types.StringNull() + } + if value := res.Get("0.site"); value.Exists() { + data.Site = types.StringValue(value.String()) + } else { + data.Site = types.StringNull() + } + if value := res.Get("0.managedAPLocations"); value.Exists() && len(value.Array()) > 0 { + data.ManagedApLocations = helpers.GetStringSet(value.Array()) + } else { + data.ManagedApLocations = types.SetNull(types.StringType) + } + if value := res.Get("0.dynamicInterfaces"); value.Exists() && len(value.Array()) > 0 { + data.DynamicInterfaces = make([]WirelessDeviceProvisionDynamicInterfaces, 0) + value.ForEach(func(k, v gjson.Result) bool { + item := WirelessDeviceProvisionDynamicInterfaces{} + if cValue := v.Get("interfaceIPAddress"); cValue.Exists() { + item.InterfaceIpAddress = types.StringValue(cValue.String()) + } else { + item.InterfaceIpAddress = types.StringNull() + } + if cValue := v.Get("interfaceNetmaskInCIDR"); cValue.Exists() { + item.InterfaceNetmask = types.Int64Value(cValue.Int()) + } else { + item.InterfaceNetmask = types.Int64Null() + } + if cValue := v.Get("interfaceGateway"); cValue.Exists() { + item.InterfaceGateway = types.StringValue(cValue.String()) + } else { + item.InterfaceGateway = types.StringNull() + } + if cValue := v.Get("lagOrPortNumber"); cValue.Exists() { + item.LagOrPortNumber = types.StringValue(cValue.String()) + } else { + item.LagOrPortNumber = types.StringNull() + } + if cValue := v.Get("vlanId"); cValue.Exists() { + item.VlanId = types.Int64Value(cValue.Int()) + } else { + item.VlanId = types.Int64Null() + } + if cValue := v.Get("interfaceName"); cValue.Exists() { + item.InterfaceName = types.StringValue(cValue.String()) + } else { + item.InterfaceName = types.StringNull() + } + data.DynamicInterfaces = append(data.DynamicInterfaces, item) + return true + }) + } +} + +// End of section. //template:end fromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin updateFromBody +func (data *WirelessDeviceProvision) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("0.deviceName"); value.Exists() && !data.DeviceName.IsNull() { + data.DeviceName = types.StringValue(value.String()) + } else { + data.DeviceName = types.StringNull() + } + if value := res.Get("0.site"); value.Exists() && !data.Site.IsNull() { + data.Site = types.StringValue(value.String()) + } else { + data.Site = types.StringNull() + } + if value := res.Get("0.managedAPLocations"); value.Exists() && !data.ManagedApLocations.IsNull() { + data.ManagedApLocations = helpers.GetStringSet(value.Array()) + } else { + data.ManagedApLocations = types.SetNull(types.StringType) + } + for i := range data.DynamicInterfaces { + keys := [...]string{"interfaceIPAddress", "interfaceNetmaskInCIDR", "interfaceGateway", "lagOrPortNumber", "vlanId", "interfaceName"} + keyValues := [...]string{data.DynamicInterfaces[i].InterfaceIpAddress.ValueString(), strconv.FormatInt(data.DynamicInterfaces[i].InterfaceNetmask.ValueInt64(), 10), data.DynamicInterfaces[i].InterfaceGateway.ValueString(), data.DynamicInterfaces[i].LagOrPortNumber.ValueString(), strconv.FormatInt(data.DynamicInterfaces[i].VlanId.ValueInt64(), 10), data.DynamicInterfaces[i].InterfaceName.ValueString()} + + var r gjson.Result + res.Get("0.dynamicInterfaces").ForEach( + func(_, v gjson.Result) bool { + found := false + for ik := range keys { + if v.Get(keys[ik]).String() == keyValues[ik] { + found = true + continue + } + found = false + break + } + if found { + r = v + return false + } + return true + }, + ) + if value := r.Get("interfaceIPAddress"); value.Exists() && !data.DynamicInterfaces[i].InterfaceIpAddress.IsNull() { + data.DynamicInterfaces[i].InterfaceIpAddress = types.StringValue(value.String()) + } else { + data.DynamicInterfaces[i].InterfaceIpAddress = types.StringNull() + } + if value := r.Get("interfaceNetmaskInCIDR"); value.Exists() && !data.DynamicInterfaces[i].InterfaceNetmask.IsNull() { + data.DynamicInterfaces[i].InterfaceNetmask = types.Int64Value(value.Int()) + } else { + data.DynamicInterfaces[i].InterfaceNetmask = types.Int64Null() + } + if value := r.Get("interfaceGateway"); value.Exists() && !data.DynamicInterfaces[i].InterfaceGateway.IsNull() { + data.DynamicInterfaces[i].InterfaceGateway = types.StringValue(value.String()) + } else { + data.DynamicInterfaces[i].InterfaceGateway = types.StringNull() + } + if value := r.Get("lagOrPortNumber"); value.Exists() && !data.DynamicInterfaces[i].LagOrPortNumber.IsNull() { + data.DynamicInterfaces[i].LagOrPortNumber = types.StringValue(value.String()) + } else { + data.DynamicInterfaces[i].LagOrPortNumber = types.StringNull() + } + if value := r.Get("vlanId"); value.Exists() && !data.DynamicInterfaces[i].VlanId.IsNull() { + data.DynamicInterfaces[i].VlanId = types.Int64Value(value.Int()) + } else { + data.DynamicInterfaces[i].VlanId = types.Int64Null() + } + if value := r.Get("interfaceName"); value.Exists() && !data.DynamicInterfaces[i].InterfaceName.IsNull() { + data.DynamicInterfaces[i].InterfaceName = types.StringValue(value.String()) + } else { + data.DynamicInterfaces[i].InterfaceName = types.StringNull() + } + } +} + +// End of section. //template:end updateFromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin isNull +func (data *WirelessDeviceProvision) isNull(ctx context.Context, res gjson.Result) bool { + if !data.Site.IsNull() { + return false + } + if !data.ManagedApLocations.IsNull() { + return false + } + if len(data.DynamicInterfaces) > 0 { + return false + } + return true +} + +// End of section. //template:end isNull diff --git a/internal/provider/model_catalystcenter_wireless_enterprise_ssid.go b/internal/provider/model_catalystcenter_wireless_enterprise_ssid.go index 1f60d0ea..587fc4c4 100644 --- a/internal/provider/model_catalystcenter_wireless_enterprise_ssid.go +++ b/internal/provider/model_catalystcenter_wireless_enterprise_ssid.go @@ -58,6 +58,12 @@ type WirelessEnterpriseSSID struct { ProtectedManagementFrame types.String `tfsdk:"protected_management_frame"` MultiPskSettings []WirelessEnterpriseSSIDMultiPskSettings `tfsdk:"multi_psk_settings"` ClientRateLimit types.Int64 `tfsdk:"client_rate_limit"` + AuthKeyMgmt types.Set `tfsdk:"auth_key_mgmt"` + RsnCipherSuiteGcmp256 types.Bool `tfsdk:"rsn_cipher_suite_gcmp256"` + RsnCipherSuiteCcmp256 types.Bool `tfsdk:"rsn_cipher_suite_ccmp256"` + RsnCipherSuiteGcmp128 types.Bool `tfsdk:"rsn_cipher_suite_gcmp128"` + Ghz6PolicyClientSteering types.Bool `tfsdk:"ghz6_policy_client_steering"` + Ghz24Policy types.String `tfsdk:"ghz24_policy"` } type WirelessEnterpriseSSIDMultiPskSettings struct { @@ -180,6 +186,26 @@ func (data WirelessEnterpriseSSID) toBody(ctx context.Context, state WirelessEnt if !data.ClientRateLimit.IsNull() { body, _ = sjson.Set(body, "clientRateLimit", data.ClientRateLimit.ValueInt64()) } + if !data.AuthKeyMgmt.IsNull() { + var values []string + data.AuthKeyMgmt.ElementsAs(ctx, &values, false) + body, _ = sjson.Set(body, "authKeyMgmt", values) + } + if !data.RsnCipherSuiteGcmp256.IsNull() { + body, _ = sjson.Set(body, "rsnCipherSuiteGcmp256", data.RsnCipherSuiteGcmp256.ValueBool()) + } + if !data.RsnCipherSuiteCcmp256.IsNull() { + body, _ = sjson.Set(body, "rsnCipherSuiteCcmp256", data.RsnCipherSuiteCcmp256.ValueBool()) + } + if !data.RsnCipherSuiteGcmp128.IsNull() { + body, _ = sjson.Set(body, "rsnCipherSuiteGcmp128", data.RsnCipherSuiteGcmp128.ValueBool()) + } + if !data.Ghz6PolicyClientSteering.IsNull() { + body, _ = sjson.Set(body, "ghz6PolicyClientSteering", data.Ghz6PolicyClientSteering.ValueBool()) + } + if !data.Ghz24Policy.IsNull() { + body, _ = sjson.Set(body, "ghz24Policy", data.Ghz24Policy.ValueString()) + } return body } @@ -325,6 +351,36 @@ func (data *WirelessEnterpriseSSID) fromBody(ctx context.Context, res gjson.Resu } else { data.ClientRateLimit = types.Int64Null() } + if value := res.Get("0.ssidDetails.0.authKeyMgmt"); value.Exists() && len(value.Array()) > 0 { + data.AuthKeyMgmt = helpers.GetStringSet(value.Array()) + } else { + data.AuthKeyMgmt = types.SetNull(types.StringType) + } + if value := res.Get("0.ssidDetails.0.rsnCipherSuiteGcmp256"); value.Exists() { + data.RsnCipherSuiteGcmp256 = types.BoolValue(value.Bool()) + } else { + data.RsnCipherSuiteGcmp256 = types.BoolNull() + } + if value := res.Get("0.ssidDetails.0.rsnCipherSuiteCcmp256"); value.Exists() { + data.RsnCipherSuiteCcmp256 = types.BoolValue(value.Bool()) + } else { + data.RsnCipherSuiteCcmp256 = types.BoolNull() + } + if value := res.Get("0.ssidDetails.0.rsnCipherSuiteGcmp128"); value.Exists() { + data.RsnCipherSuiteGcmp128 = types.BoolValue(value.Bool()) + } else { + data.RsnCipherSuiteGcmp128 = types.BoolNull() + } + if value := res.Get("0.ssidDetails.0.ghz6PolicyClientSteering"); value.Exists() { + data.Ghz6PolicyClientSteering = types.BoolValue(value.Bool()) + } else { + data.Ghz6PolicyClientSteering = types.BoolNull() + } + if value := res.Get("0.ssidDetails.0.ghz24Policy"); value.Exists() { + data.Ghz24Policy = types.StringValue(value.String()) + } else { + data.Ghz24Policy = types.StringNull() + } } // End of section. //template:end fromBody @@ -485,6 +541,36 @@ func (data *WirelessEnterpriseSSID) updateFromBody(ctx context.Context, res gjso } else { data.ClientRateLimit = types.Int64Null() } + if value := res.Get("0.ssidDetails.0.authKeyMgmt"); value.Exists() && !data.AuthKeyMgmt.IsNull() { + data.AuthKeyMgmt = helpers.GetStringSet(value.Array()) + } else { + data.AuthKeyMgmt = types.SetNull(types.StringType) + } + if value := res.Get("0.ssidDetails.0.rsnCipherSuiteGcmp256"); value.Exists() && !data.RsnCipherSuiteGcmp256.IsNull() { + data.RsnCipherSuiteGcmp256 = types.BoolValue(value.Bool()) + } else { + data.RsnCipherSuiteGcmp256 = types.BoolNull() + } + if value := res.Get("0.ssidDetails.0.rsnCipherSuiteCcmp256"); value.Exists() && !data.RsnCipherSuiteCcmp256.IsNull() { + data.RsnCipherSuiteCcmp256 = types.BoolValue(value.Bool()) + } else { + data.RsnCipherSuiteCcmp256 = types.BoolNull() + } + if value := res.Get("0.ssidDetails.0.rsnCipherSuiteGcmp128"); value.Exists() && !data.RsnCipherSuiteGcmp128.IsNull() { + data.RsnCipherSuiteGcmp128 = types.BoolValue(value.Bool()) + } else { + data.RsnCipherSuiteGcmp128 = types.BoolNull() + } + if value := res.Get("0.ssidDetails.0.ghz6PolicyClientSteering"); value.Exists() && !data.Ghz6PolicyClientSteering.IsNull() { + data.Ghz6PolicyClientSteering = types.BoolValue(value.Bool()) + } else { + data.Ghz6PolicyClientSteering = types.BoolNull() + } + if value := res.Get("0.ssidDetails.0.ghz24Policy"); value.Exists() && !data.Ghz24Policy.IsNull() { + data.Ghz24Policy = types.StringValue(value.String()) + } else { + data.Ghz24Policy = types.StringNull() + } } // End of section. //template:end updateFromBody @@ -566,6 +652,24 @@ func (data *WirelessEnterpriseSSID) isNull(ctx context.Context, res gjson.Result if !data.ClientRateLimit.IsNull() { return false } + if !data.AuthKeyMgmt.IsNull() { + return false + } + if !data.RsnCipherSuiteGcmp256.IsNull() { + return false + } + if !data.RsnCipherSuiteCcmp256.IsNull() { + return false + } + if !data.RsnCipherSuiteGcmp128.IsNull() { + return false + } + if !data.Ghz6PolicyClientSteering.IsNull() { + return false + } + if !data.Ghz24Policy.IsNull() { + return false + } return true } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index d26117fb..ac73e51e 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -283,6 +283,7 @@ func (p *CcProvider) Resources(ctx context.Context) []func() resource.Resource { NewUserResource, NewVirtualNetworkIPPoolResource, NewVirtualNetworkToFabricSiteResource, + NewWirelessDeviceProvisionResource, NewWirelessEnterpriseSSIDResource, NewWirelessProfileResource, NewWirelessRFProfileResource, diff --git a/internal/provider/resource_catalystcenter_fabric_device.go b/internal/provider/resource_catalystcenter_fabric_device.go index 446fe92c..c54a2e9f 100644 --- a/internal/provider/resource_catalystcenter_fabric_device.go +++ b/internal/provider/resource_catalystcenter_fabric_device.go @@ -29,7 +29,6 @@ import ( "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/int64default" "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" @@ -112,13 +111,11 @@ func (r *FabricDeviceResource) Schema(ctx context.Context, req resource.SchemaRe Optional: true, }, "border_priority": schema.Int64Attribute{ - MarkdownDescription: helpers.NewAttributeDescription("Border priority of the fabric border device. A lower value indicates higher priority").AddIntegerRangeDescription(1, 9).AddDefaultValueDescription("10").String, + MarkdownDescription: helpers.NewAttributeDescription("Border priority of the fabric border device. A lower value indicates higher priority").AddIntegerRangeDescription(1, 9).String, Optional: true, - Computed: true, Validators: []validator.Int64{ int64validator.Between(1, 9), }, - Default: int64default.StaticInt64(10), }, "prepend_autonomous_system_count": schema.Int64Attribute{ MarkdownDescription: helpers.NewAttributeDescription("Prepend autonomous system count of the fabric border device").AddIntegerRangeDescription(1, 10).String, diff --git a/internal/provider/resource_catalystcenter_wireless_device_provision.go b/internal/provider/resource_catalystcenter_wireless_device_provision.go new file mode 100644 index 00000000..0e5db014 --- /dev/null +++ b/internal/provider/resource_catalystcenter_wireless_device_provision.go @@ -0,0 +1,249 @@ +// 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" + + "github.com/CiscoDevNet/terraform-provider-catalystcenter/internal/provider/helpers" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "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 = &WirelessDeviceProvisionResource{} + +func NewWirelessDeviceProvisionResource() resource.Resource { + return &WirelessDeviceProvisionResource{} +} + +type WirelessDeviceProvisionResource struct { + client *cc.Client +} + +func (r *WirelessDeviceProvisionResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_wireless_device_provision" +} + +func (r *WirelessDeviceProvisionResource) 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("This resource provision wireless device. Every time this resource is created or re-created, the Catalyst Center considers provisioning new wireless device. When this resource is destroyed or updated or refreshed, no actions are done either on CatalystCenter or on devices").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "device_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Controller Name").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "site": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Full Site Hierarchy where device has to be assigned").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "managed_ap_locations": schema.SetAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("List of managed AP locations").String, + ElementType: types.StringType, + Required: true, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplace(), + }, + }, + "dynamic_interfaces": schema.ListNestedAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Dynamic Interface Details. The required attributes depend on the device type").String, + Optional: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplace(), + }, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "interface_ip_address": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Interface IP Address. Required for AireOS").String, + Optional: true, + }, + "interface_netmask": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Interface Netmask In CIDR. Required for AireOS").String, + Optional: true, + }, + "interface_gateway": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Interface Gateway. Required for AireOS").String, + Optional: true, + }, + "lag_or_port_number": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("LAG or Port Number. Required for AireOS").String, + Optional: true, + }, + "vlan_id": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("VLAN ID. Required for both AireOS and EWLC").String, + Optional: true, + }, + "interface_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Interface Name. Required for both AireOS and EWLC.").String, + Optional: true, + }, + }, + }, + }, + }, + } +} + +func (r *WirelessDeviceProvisionResource) 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 *WirelessDeviceProvisionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan WirelessDeviceProvision + + // 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, WirelessDeviceProvision{}) + + params := "" + res, err := r.client.Post(plan.getPath()+params, body, func(r *cc.Req) { r.MaxAsyncWaitTime = 300 }) + if err != nil { + 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.DeviceName.ValueString())) + + 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 *WirelessDeviceProvisionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state WirelessDeviceProvision + + // 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())) + + 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 *WirelessDeviceProvisionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state WirelessDeviceProvision + + // 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 = 300 }) + 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 *WirelessDeviceProvisionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state WirelessDeviceProvision + + // 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())) + + 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 +// End of section. //template:end import diff --git a/internal/provider/resource_catalystcenter_wireless_device_provision_test.go b/internal/provider/resource_catalystcenter_wireless_device_provision_test.go new file mode 100644 index 00000000..ebcfbf5b --- /dev/null +++ b/internal/provider/resource_catalystcenter_wireless_device_provision_test.go @@ -0,0 +1,84 @@ +// 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 TestAccCcWirelessDeviceProvision(t *testing.T) { + if os.Getenv("WIRELESS") == "" { + t.Skip("skipping test, set environment variable WIRELESS") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_wireless_device_provision.test", "device_name", "WLC_01")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_wireless_device_provision.test", "site", "Global/Area1")) + + var steps []resource.TestStep + if os.Getenv("SKIP_MINIMUM_TEST") == "" { + steps = append(steps, resource.TestStep{ + Config: testAccCcWirelessDeviceProvisionConfig_minimum(), + }) + } + steps = append(steps, resource.TestStep{ + Config: testAccCcWirelessDeviceProvisionConfig_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 +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal +func testAccCcWirelessDeviceProvisionConfig_minimum() string { + config := `resource "catalystcenter_wireless_device_provision" "test" {` + "\n" + config += ` device_name = "WLC_01"` + "\n" + config += ` site = "Global/Area1"` + "\n" + config += ` managed_ap_locations = ["Global/Area1"]` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll +func testAccCcWirelessDeviceProvisionConfig_all() string { + config := `resource "catalystcenter_wireless_device_provision" "test" {` + "\n" + config += ` device_name = "WLC_01"` + "\n" + config += ` site = "Global/Area1"` + "\n" + config += ` managed_ap_locations = ["Global/Area1"]` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll diff --git a/internal/provider/resource_catalystcenter_wireless_enterprise_ssid.go b/internal/provider/resource_catalystcenter_wireless_enterprise_ssid.go index 8cb9776a..1657ec19 100644 --- a/internal/provider/resource_catalystcenter_wireless_enterprise_ssid.go +++ b/internal/provider/resource_catalystcenter_wireless_enterprise_ssid.go @@ -215,6 +215,34 @@ func (r *WirelessEnterpriseSSIDResource) Schema(ctx context.Context, req resourc MarkdownDescription: helpers.NewAttributeDescription("Client Rate Limit (in bits per second)").String, Optional: true, }, + "auth_key_mgmt": schema.SetAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Takes string inputs for the AKMs that should be set true. Possible AKM values : dot1x,dot1x_ft, dot1x_sha, psk, psk_ft, psk_sha, owe, sae, sae_ft").String, + ElementType: types.StringType, + Optional: true, + }, + "rsn_cipher_suite_gcmp256": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Rsn Cipher Suite Gcmp256").String, + Optional: true, + }, + "rsn_cipher_suite_ccmp256": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Rsn Cipher Suite Ccmp256").String, + Optional: true, + }, + "rsn_cipher_suite_gcmp128": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Rsn Cipher Suite Gcmp 128").String, + Optional: true, + }, + "ghz6_policy_client_steering": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Ghz6 Policy Client Steering").String, + Optional: true, + }, + "ghz24_policy": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Ghz24 Policy").AddStringEnumDescription("dot11-g-only", "dot11-bg-only").String, + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("dot11-g-only", "dot11-bg-only"), + }, + }, }, } } From 4906ccbcca5c529da55eeb0d4e9b02c0fa9a37fe Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Thu, 1 Aug 2024 14:22:48 +0200 Subject: [PATCH 21/31] 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 4c1c7970..a750452a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.1.10 (unreleased) +- Add `wireless_device_provision` resource - Add `fabric_provision_device` resource and data source - Add `assign_templates_to_tag` resource and data source - Add `tag` resource and data source diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index e14ef348..0f9d98a3 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -9,6 +9,7 @@ description: |- ## 0.1.10 (unreleased) +- Add `wireless_device_provision` resource - Add `fabric_provision_device` resource and data source - Add `assign_templates_to_tag` resource and data source - Add `tag` resource and data source diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index e14ef348..0f9d98a3 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -9,6 +9,7 @@ description: |- ## 0.1.10 (unreleased) +- Add `wireless_device_provision` resource - Add `fabric_provision_device` resource and data source - Add `assign_templates_to_tag` resource and data source - Add `tag` resource and data source From df90953d583717848d87d9636e07a3619c0ec989 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Mon, 5 Aug 2024 12:14:42 +0200 Subject: [PATCH 22/31] update changelog --- docs/guides/changelog.md | 20 ++++++++++---------- templates/guides/changelog.md.tmpl | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index b6466f46..9239f00b 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -1,12 +1,12 @@ ---- -subcategory: "Guides" -page_title: "Changelog" -description: |- - Changelog ---- - -# Changelog - +--- +subcategory: "Guides" +page_title: "Changelog" +description: |- + Changelog +--- + +# Changelog + ## 0.1.10 (unreleased) - Add `assign_templates_to_tag` resource and data source @@ -102,4 +102,4 @@ description: |- ## 0.1.0 - Initial release - + diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index b6466f46..9239f00b 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -1,12 +1,12 @@ ---- -subcategory: "Guides" -page_title: "Changelog" -description: |- - Changelog ---- - -# Changelog - +--- +subcategory: "Guides" +page_title: "Changelog" +description: |- + Changelog +--- + +# Changelog + ## 0.1.10 (unreleased) - Add `assign_templates_to_tag` resource and data source @@ -102,4 +102,4 @@ description: |- ## 0.1.0 - Initial release - + From c8291fdbaf466e7e704bfcfe319308489545f1a5 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Mon, 5 Aug 2024 12:18:51 +0200 Subject: [PATCH 23/31] modified res_description --- docs/resources/assign_templates_to_tag.md | 4 ++-- gen/definitions/assign_templates_to_tag.yaml | 2 +- .../resource_catalystcenter_assign_templates_to_tag.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/resources/assign_templates_to_tag.md b/docs/resources/assign_templates_to_tag.md index 7f7097e5..2d04ab6e 100644 --- a/docs/resources/assign_templates_to_tag.md +++ b/docs/resources/assign_templates_to_tag.md @@ -3,12 +3,12 @@ page_title: "catalystcenter_assign_templates_to_tag Resource - terraform-provider-catalystcenter" subcategory: "Tags" description: |- - This resource is responsible for assigning templates to a specified tag during creation and removing the template from the tag during destruction + This resource is responsible for assigning templates to a specified tag during creation and removing the template from the tag during destroy operation. --- # catalystcenter_assign_templates_to_tag (Resource) -This resource is responsible for assigning templates to a specified tag during creation and removing the template from the tag during destruction +This resource is responsible for assigning templates to a specified tag during creation and removing the template from the tag during destroy operation. ## Example Usage diff --git a/gen/definitions/assign_templates_to_tag.yaml b/gen/definitions/assign_templates_to_tag.yaml index c749f38e..a41de28c 100644 --- a/gen/definitions/assign_templates_to_tag.yaml +++ b/gen/definitions/assign_templates_to_tag.yaml @@ -5,7 +5,7 @@ get_extra_query_params: '?memberType=template' get_no_id: true # Manual updates in Delete function to handle removal of templates from tag in resource file, and fromBody, updateFromBody functions in model file res_description: 'This resource is responsible for assigning templates to a specified tag during creation - and removing the template from the tag during destruction' + and removing the template from the tag during destroy operation.' post_update: true no_import: true data_source_no_id: true diff --git a/internal/provider/resource_catalystcenter_assign_templates_to_tag.go b/internal/provider/resource_catalystcenter_assign_templates_to_tag.go index c458a890..fe663636 100644 --- a/internal/provider/resource_catalystcenter_assign_templates_to_tag.go +++ b/internal/provider/resource_catalystcenter_assign_templates_to_tag.go @@ -56,7 +56,7 @@ func (r *AssignTemplatesToTagResource) Metadata(ctx context.Context, req resourc func (r *AssignTemplatesToTagResource) 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("This resource is responsible for assigning templates to a specified tag during creation and removing the template from the tag during destruction").String, + MarkdownDescription: helpers.NewAttributeDescription("This resource is responsible for assigning templates to a specified tag during creation and removing the template from the tag during destroy operation.").String, Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ From 9a02a97e34b8e8c85c42f5b1313034b85e61358c Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Mon, 5 Aug 2024 15:17:04 +0200 Subject: [PATCH 24/31] fix data source test for resources with data_source_no_id: true --- gen/templates/data_source_test.go | 2 ++ .../provider/data_source_catalystcenter_anycast_gateway_test.go | 1 - .../data_source_catalystcenter_assign_templates_to_tag_test.go | 1 - .../provider/data_source_catalystcenter_fabric_device_test.go | 1 - .../data_source_catalystcenter_fabric_provision_device_test.go | 1 - internal/provider/data_source_catalystcenter_tag_test.go | 1 - .../data_source_catalystcenter_transit_peer_network_test.go | 1 - 7 files changed, 2 insertions(+), 6 deletions(-) diff --git a/gen/templates/data_source_test.go b/gen/templates/data_source_test.go index 14314faf..b46c3f4e 100644 --- a/gen/templates/data_source_test.go +++ b/gen/templates/data_source_test.go @@ -214,7 +214,9 @@ func testAccDataSourceCc{{camelCase .Name}}Config() string { config += ` data "catalystcenter_{{snakeCase .Name}}" "test" { + {{- if not .DataSourceNoId}} id = catalystcenter_{{snakeCase $name}}.test.id + {{- end}} {{- range .Attributes}} {{- if or .Reference .QueryParam}} {{.TfName}} = {{if .TestValue}}{{.TestValue}}{{else}}{{if eq .Type "String"}}"{{.Example}}"{{else if isStringListSet .}}["{{.Example}}"]{{else if isInt64ListSet .}}[{{.Example}}]{{else}}{{.Example}}{{end}}{{end}} diff --git a/internal/provider/data_source_catalystcenter_anycast_gateway_test.go b/internal/provider/data_source_catalystcenter_anycast_gateway_test.go index 1acab680..1569fa29 100644 --- a/internal/provider/data_source_catalystcenter_anycast_gateway_test.go +++ b/internal/provider/data_source_catalystcenter_anycast_gateway_test.go @@ -114,7 +114,6 @@ func testAccDataSourceCcAnycastGatewayConfig() string { 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 diff --git a/internal/provider/data_source_catalystcenter_assign_templates_to_tag_test.go b/internal/provider/data_source_catalystcenter_assign_templates_to_tag_test.go index 251916dc..09c41b25 100644 --- a/internal/provider/data_source_catalystcenter_assign_templates_to_tag_test.go +++ b/internal/provider/data_source_catalystcenter_assign_templates_to_tag_test.go @@ -86,7 +86,6 @@ func testAccDataSourceCcAssignTemplatesToTagConfig() string { config += ` data "catalystcenter_assign_templates_to_tag" "test" { - id = catalystcenter_assign_templates_to_tag.test.id tag_id = catalystcenter_tag.test.id } ` diff --git a/internal/provider/data_source_catalystcenter_fabric_device_test.go b/internal/provider/data_source_catalystcenter_fabric_device_test.go index 8260e73f..1efded46 100644 --- a/internal/provider/data_source_catalystcenter_fabric_device_test.go +++ b/internal/provider/data_source_catalystcenter_fabric_device_test.go @@ -87,7 +87,6 @@ func testAccDataSourceCcFabricDeviceConfig() string { config += ` data "catalystcenter_fabric_device" "test" { - id = catalystcenter_fabric_device.test.id network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" fabric_id = catalystcenter_fabric_site.test.id } diff --git a/internal/provider/data_source_catalystcenter_fabric_provision_device_test.go b/internal/provider/data_source_catalystcenter_fabric_provision_device_test.go index 51f9cd8b..94c31280 100644 --- a/internal/provider/data_source_catalystcenter_fabric_provision_device_test.go +++ b/internal/provider/data_source_catalystcenter_fabric_provision_device_test.go @@ -73,7 +73,6 @@ func testAccDataSourceCcFabricProvisionDeviceConfig() string { config += ` data "catalystcenter_fabric_provision_device" "test" { - id = catalystcenter_fabric_provision_device.test.id site_id = catalystcenter_area.test.id network_device_id = "4cb565d3-1944-42be-be9f-a87cff79e831" } diff --git a/internal/provider/data_source_catalystcenter_tag_test.go b/internal/provider/data_source_catalystcenter_tag_test.go index 15d13275..c3233572 100644 --- a/internal/provider/data_source_catalystcenter_tag_test.go +++ b/internal/provider/data_source_catalystcenter_tag_test.go @@ -59,7 +59,6 @@ func testAccDataSourceCcTagConfig() string { config += ` data "catalystcenter_tag" "test" { - id = catalystcenter_tag.test.id name = "Tag1" } ` 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 f5b7c58c..c6afe8e3 100644 --- a/internal/provider/data_source_catalystcenter_transit_peer_network_test.go +++ b/internal/provider/data_source_catalystcenter_transit_peer_network_test.go @@ -61,7 +61,6 @@ func testAccDataSourceCcTransitPeerNetworkConfig() string { config += ` data "catalystcenter_transit_peer_network" "test" { - id = catalystcenter_transit_peer_network.test.id transit_peer_network_name = "TRANSIT_1" } ` From a7d936f579532d04f9faa9e574274b0fa98f748c Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Mon, 5 Aug 2024 21:27:04 +0200 Subject: [PATCH 25/31] fix typo in virtualNetworkName description in fabric_l3_handoff_ip_transit --- docs/data-sources/fabric_l3_handoff_ip_transit.md | 2 +- docs/resources/fabric_l3_handoff_ip_transit.md | 2 +- gen/definitions/fabric_l3_handoff_ip_transit.yaml | 2 +- .../data_source_catalystcenter_fabric_l3_handoff_ip_transit.go | 2 +- .../resource_catalystcenter_fabric_l3_handoff_ip_transit.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/data-sources/fabric_l3_handoff_ip_transit.md b/docs/data-sources/fabric_l3_handoff_ip_transit.md index beb4dd93..cc2fb76d 100644 --- a/docs/data-sources/fabric_l3_handoff_ip_transit.md +++ b/docs/data-sources/fabric_l3_handoff_ip_transit.md @@ -39,5 +39,5 @@ data "catalystcenter_fabric_l3_handoff_ip_transit" "example" { - `remote_ipv6_address` (String) Remote ipv6 address for the selected virtual network. Enter the IP addresses and subnet mask in the CIDR notation (IP address/prefix-length). Not applicable if you have already provided an external connectivity ip pool name - `tcp_mss_adjustment` (Number) TCP maximum segment size (mss) value for the layer 3 handoff. Allowed range is [500-1440]. TCP MSS Adjustment value is applicable for the TCP sessions over both IPv4 and IPv6 - `transit_network_id` (String) ID of the transit network of the layer 3 handoff ip transit -- `virtual_network_name` (String) SName of the virtual network associated with this fabric site +- `virtual_network_name` (String) Name of the virtual network associated with this fabric site - `vlan_id` (Number) VLAN number for the Switch Virtual Interface (SVI) used to establish BGP peering with the external domain for the virtual network. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094) diff --git a/docs/resources/fabric_l3_handoff_ip_transit.md b/docs/resources/fabric_l3_handoff_ip_transit.md index 5e4aa3f0..fafd207d 100644 --- a/docs/resources/fabric_l3_handoff_ip_transit.md +++ b/docs/resources/fabric_l3_handoff_ip_transit.md @@ -34,7 +34,7 @@ resource "catalystcenter_fabric_l3_handoff_ip_transit" "example" { - `fabric_id` (String) ID of the fabric this device belongs to - `network_device_id` (String) Network device ID of the fabric device - `transit_network_id` (String) ID of the transit network of the layer 3 handoff ip transit -- `virtual_network_name` (String) SName of the virtual network associated with this fabric site +- `virtual_network_name` (String) Name of the virtual network associated with this fabric site - `vlan_id` (Number) VLAN number for the Switch Virtual Interface (SVI) used to establish BGP peering with the external domain for the virtual network. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094) ### Optional diff --git a/gen/definitions/fabric_l3_handoff_ip_transit.yaml b/gen/definitions/fabric_l3_handoff_ip_transit.yaml index 08f2b3d2..4741cd23 100644 --- a/gen/definitions/fabric_l3_handoff_ip_transit.yaml +++ b/gen/definitions/fabric_l3_handoff_ip_transit.yaml @@ -60,7 +60,7 @@ attributes: response_data_path: virtualNetworkName mandatory: true type: String - description: SName of the virtual network associated with this fabric site + description: Name of the virtual network associated with this fabric site example: SDA_VN1 - model_name: vlanId data_path: '0' diff --git a/internal/provider/data_source_catalystcenter_fabric_l3_handoff_ip_transit.go b/internal/provider/data_source_catalystcenter_fabric_l3_handoff_ip_transit.go index 2f70b274..bdaa1d16 100644 --- a/internal/provider/data_source_catalystcenter_fabric_l3_handoff_ip_transit.go +++ b/internal/provider/data_source_catalystcenter_fabric_l3_handoff_ip_transit.go @@ -82,7 +82,7 @@ func (d *FabricL3HandoffIPTransitDataSource) Schema(ctx context.Context, req dat Computed: true, }, "virtual_network_name": schema.StringAttribute{ - MarkdownDescription: "SName of the virtual network associated with this fabric site", + MarkdownDescription: "Name of the virtual network associated with this fabric site", Computed: true, }, "vlan_id": schema.Int64Attribute{ diff --git a/internal/provider/resource_catalystcenter_fabric_l3_handoff_ip_transit.go b/internal/provider/resource_catalystcenter_fabric_l3_handoff_ip_transit.go index 39e0d882..905534b0 100644 --- a/internal/provider/resource_catalystcenter_fabric_l3_handoff_ip_transit.go +++ b/internal/provider/resource_catalystcenter_fabric_l3_handoff_ip_transit.go @@ -107,7 +107,7 @@ func (r *FabricL3HandoffIPTransitResource) Schema(ctx context.Context, req resou }, }, "virtual_network_name": schema.StringAttribute{ - MarkdownDescription: helpers.NewAttributeDescription("SName of the virtual network associated with this fabric site").String, + MarkdownDescription: helpers.NewAttributeDescription("Name of the virtual network associated with this fabric site").String, Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), From d3c42c26a23f4dc79a53567e36ca5edacde9dbfd Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Tue, 6 Aug 2024 11:05:28 +0200 Subject: [PATCH 26/31] modified wireless_profile to use new endpoint /intent/api/v1/wirelessProfile --- CHANGELOG.md | 1 + docs/data-sources/wireless_profile.md | 44 ++++++ docs/guides/changelog.md | 1 + docs/resources/wireless_profile.md | 17 +- .../data-source.tf | 3 + .../catalystcenter_wireless_profile/import.sh | 2 +- .../resource.tf | 4 +- gen/definitions/wireless_profile.yaml | 27 ++-- ..._source_catalystcenter_wireless_profile.go | 146 ++++++++++++++++++ ...ce_catalystcenter_wireless_profile_test.go | 80 ++++++++++ .../model_catalystcenter_wireless_profile.go | 72 +++++---- internal/provider/provider.go | 1 + ...esource_catalystcenter_wireless_profile.go | 41 ++--- ...ce_catalystcenter_wireless_profile_test.go | 8 +- templates/guides/changelog.md.tmpl | 1 + 15 files changed, 366 insertions(+), 82 deletions(-) create mode 100644 docs/data-sources/wireless_profile.md create mode 100644 examples/data-sources/catalystcenter_wireless_profile/data-source.tf create mode 100644 internal/provider/data_source_catalystcenter_wireless_profile.go create mode 100644 internal/provider/data_source_catalystcenter_wireless_profile_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index a750452a..f9a3c01a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.1.10 (unreleased) +- BREAKING CHANGE: Modified `wireless_profile` resource to use `/intent/api/v1/wirelessProfiles` API endpoint, this resource now only works with Catalyst Center version 2.3.7.6+ - Add `wireless_device_provision` resource - Add `fabric_provision_device` resource and data source - Add `assign_templates_to_tag` resource and data source diff --git a/docs/data-sources/wireless_profile.md b/docs/data-sources/wireless_profile.md new file mode 100644 index 00000000..56fb7068 --- /dev/null +++ b/docs/data-sources/wireless_profile.md @@ -0,0 +1,44 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_wireless_profile Data Source - terraform-provider-catalystcenter" +subcategory: "Wireless" +description: |- + This data source can read the Wireless Profile. +--- + +# catalystcenter_wireless_profile (Data Source) + +This data source can read the Wireless Profile. + +## Example Usage + +```terraform +data "catalystcenter_wireless_profile" "example" { + id = "76d24097-41c4-4558-a4d0-a8c07ac08470" +} +``` + + +## Schema + +### Required + +- `id` (String) The id of the object + +### Read-Only + +- `ssid_details` (Attributes List) SSID Details (see [below for nested schema](#nestedatt--ssid_details)) +- `wireless_profile_name` (String) Wireless Network Profile Name + + +### Nested Schema for `ssid_details` + +Read-Only: + +- `dot11be_profile_id` (String) 802.11be Profile Id. Applicable to IOS controllers with version 17.15 and higher. 802.11be Profiles if passed, should be same across all SSIDs in network profile being configured +- `enable_fabric` (Boolean) True if fabric is enabled, else False. Flex and fabric cannot be enabled simultaneously and a profile can only contain either flex SSIDs or fabric SSIDs and not both at the same time +- `enable_flex_connect` (Boolean) True if flex connect is enabled, else False. Flex and fabric cannot be enabled simultaneously and a profile can only contain either flex SSIDs or fabric SSIDs and not both at the same time +- `interface_name` (String) Interface Name +- `local_to_vlan` (Number) Local To Vlan Id +- `ssid_name` (String) SSID Name +- `wlan_profile_name` (String) WLAN Profile Name diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index 0f9d98a3..f399dd51 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -9,6 +9,7 @@ description: |- ## 0.1.10 (unreleased) +- BREAKING CHANGE: Modified `wireless_profile` resource to use `/intent/api/v1/wirelessProfiles` API endpoint, this resource now only works with Catalyst Center version 2.3.7.6+ - Add `wireless_device_provision` resource - Add `fabric_provision_device` resource and data source - Add `assign_templates_to_tag` resource and data source diff --git a/docs/resources/wireless_profile.md b/docs/resources/wireless_profile.md index b6649d3c..e394f666 100644 --- a/docs/resources/wireless_profile.md +++ b/docs/resources/wireless_profile.md @@ -14,10 +14,10 @@ This resource creates a wireless network profile. To associate a wireless networ ```terraform resource "catalystcenter_wireless_profile" "example" { - name = "Wireless_Profile_1" + wireless_profile_name = "Wireless_Profile_1" ssid_details = [ { - name = "mySSID1" + ssid_name = "mySSID1" enable_fabric = true enable_flex_connect = false } @@ -30,7 +30,7 @@ resource "catalystcenter_wireless_profile" "example" { ### Required -- `name` (String) Profile Name +- `wireless_profile_name` (String) Wireless Network Profile Name ### Optional @@ -45,15 +45,16 @@ resource "catalystcenter_wireless_profile" "example" { Required: -- `name` (String) Ssid Name +- `ssid_name` (String) SSID Name Optional: -- `enable_fabric` (Boolean) `true` if ssid is fabric else `false` -- `enable_flex_connect` (Boolean) `true` if flex connect is enabled else `false` +- `dot11be_profile_id` (String) 802.11be Profile Id. Applicable to IOS controllers with version 17.15 and higher. 802.11be Profiles if passed, should be same across all SSIDs in network profile being configured +- `enable_fabric` (Boolean) True if fabric is enabled, else False. Flex and fabric cannot be enabled simultaneously and a profile can only contain either flex SSIDs or fabric SSIDs and not both at the same time +- `enable_flex_connect` (Boolean) True if flex connect is enabled, else False. Flex and fabric cannot be enabled simultaneously and a profile can only contain either flex SSIDs or fabric SSIDs and not both at the same time - `interface_name` (String) Interface Name + - Default value: `management` - `local_to_vlan` (Number) Local To Vlan Id -- `policy_profile_name` (String) Policy Profile Name - `wlan_profile_name` (String) WLAN Profile Name ## Import @@ -61,5 +62,5 @@ Optional: Import is supported using the following syntax: ```shell -terraform import catalystcenter_wireless_profile.example "" +terraform import catalystcenter_wireless_profile.example "4b0b7a80-44c0-4bf2-bab5-fc24b4e0a17e" ``` diff --git a/examples/data-sources/catalystcenter_wireless_profile/data-source.tf b/examples/data-sources/catalystcenter_wireless_profile/data-source.tf new file mode 100644 index 00000000..13a3e678 --- /dev/null +++ b/examples/data-sources/catalystcenter_wireless_profile/data-source.tf @@ -0,0 +1,3 @@ +data "catalystcenter_wireless_profile" "example" { + id = "76d24097-41c4-4558-a4d0-a8c07ac08470" +} diff --git a/examples/resources/catalystcenter_wireless_profile/import.sh b/examples/resources/catalystcenter_wireless_profile/import.sh index 7bc209be..ef76597b 100644 --- a/examples/resources/catalystcenter_wireless_profile/import.sh +++ b/examples/resources/catalystcenter_wireless_profile/import.sh @@ -1 +1 @@ -terraform import catalystcenter_wireless_profile.example "" +terraform import catalystcenter_wireless_profile.example "4b0b7a80-44c0-4bf2-bab5-fc24b4e0a17e" diff --git a/examples/resources/catalystcenter_wireless_profile/resource.tf b/examples/resources/catalystcenter_wireless_profile/resource.tf index c473cc50..ff3ee7c6 100644 --- a/examples/resources/catalystcenter_wireless_profile/resource.tf +++ b/examples/resources/catalystcenter_wireless_profile/resource.tf @@ -1,8 +1,8 @@ resource "catalystcenter_wireless_profile" "example" { - name = "Wireless_Profile_1" + wireless_profile_name = "Wireless_Profile_1" ssid_details = [ { - name = "mySSID1" + ssid_name = "mySSID1" enable_fabric = true enable_flex_connect = false } diff --git a/gen/definitions/wireless_profile.yaml b/gen/definitions/wireless_profile.yaml index 2e8f4990..4e9fde23 100644 --- a/gen/definitions/wireless_profile.yaml +++ b/gen/definitions/wireless_profile.yaml @@ -1,43 +1,38 @@ --- name: Wireless Profile -rest_endpoint: /dna/intent/api/v2/wireless/profile +rest_endpoint: /intent/api/v1/wirelessProfiles id_from_query_path: response +id_from_query_path_attribute: id +import_no_id: true get_from_all: true -no_data_source: true -put_no_id: true -id_from_query_path_attribute: instanceUuid res_description: 'This resource creates a wireless network profile. To associate a wireless network profile with a site, use the `catalystcenter_associate_site_to_network_profile` resource.' doc_category: Wireless attributes: - model_name: wirelessProfileName - tf_name: name - delete_query_param_name: name - delete_query_param: true type: String match_id: true - description: Profile Name + description: Wireless Network Profile Name example: Wireless_Profile_1 - model_name: ssidDetails type: List description: SSID Details attributes: - model_name: ssidName - tf_name: name type: String id: true - description: Ssid Name + description: SSID Name example: mySSID1 test_value: catalystcenter_wireless_enterprise_ssid.test.name - model_name: enableFabric type: Bool - description: "`true` if ssid is fabric else `false`" + description: "True if fabric is enabled, else False. Flex and fabric cannot be enabled simultaneously and a profile can only contain either flex SSIDs or fabric SSIDs and not both at the same time" example: true - model_name: enableFlexConnect data_path: flexConnect type: Bool - description: "`true` if flex connect is enabled else `false`" + description: "True if flex connect is enabled, else False. Flex and fabric cannot be enabled simultaneously and a profile can only contain either flex SSIDs or fabric SSIDs and not both at the same time" example: false - model_name: localToVlan data_path: flexConnect @@ -49,19 +44,17 @@ attributes: type: String description: Interface Name exclude_test: true + default_value: management example: management - model_name: wlanProfileName - write_only: true type: String description: WLAN Profile Name exclude_test: true example: mySSID1_profile - - model_name: policyProfileName - write_only: true + - model_name: dot11beProfileId type: String - description: Policy Profile Name + description: 802.11be Profile Id. Applicable to IOS controllers with version 17.15 and higher. 802.11be Profiles if passed, should be same across all SSIDs in network profile being configured exclude_test: true - example: mySSID1_profile test_prerequisites: | resource "catalystcenter_wireless_enterprise_ssid" "test" { name = "mySSID1" diff --git a/internal/provider/data_source_catalystcenter_wireless_profile.go b/internal/provider/data_source_catalystcenter_wireless_profile.go new file mode 100644 index 00000000..2e56d46b --- /dev/null +++ b/internal/provider/data_source_catalystcenter_wireless_profile.go @@ -0,0 +1,146 @@ +// 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" + + "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 = &WirelessProfileDataSource{} + _ datasource.DataSourceWithConfigure = &WirelessProfileDataSource{} +) + +func NewWirelessProfileDataSource() datasource.DataSource { + return &WirelessProfileDataSource{} +} + +type WirelessProfileDataSource struct { + client *cc.Client +} + +func (d *WirelessProfileDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_wireless_profile" +} + +func (d *WirelessProfileDataSource) 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 Wireless Profile.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Required: true, + }, + "wireless_profile_name": schema.StringAttribute{ + MarkdownDescription: "Wireless Network Profile Name", + Computed: true, + }, + "ssid_details": schema.ListNestedAttribute{ + MarkdownDescription: "SSID Details", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "ssid_name": schema.StringAttribute{ + MarkdownDescription: "SSID Name", + Computed: true, + }, + "enable_fabric": schema.BoolAttribute{ + MarkdownDescription: "True if fabric is enabled, else False. Flex and fabric cannot be enabled simultaneously and a profile can only contain either flex SSIDs or fabric SSIDs and not both at the same time", + Computed: true, + }, + "enable_flex_connect": schema.BoolAttribute{ + MarkdownDescription: "True if flex connect is enabled, else False. Flex and fabric cannot be enabled simultaneously and a profile can only contain either flex SSIDs or fabric SSIDs and not both at the same time", + Computed: true, + }, + "local_to_vlan": schema.Int64Attribute{ + MarkdownDescription: "Local To Vlan Id", + Computed: true, + }, + "interface_name": schema.StringAttribute{ + MarkdownDescription: "Interface Name", + Computed: true, + }, + "wlan_profile_name": schema.StringAttribute{ + MarkdownDescription: "WLAN Profile Name", + Computed: true, + }, + "dot11be_profile_id": schema.StringAttribute{ + MarkdownDescription: "802.11be Profile Id. Applicable to IOS controllers with version 17.15 and higher. 802.11be Profiles if passed, should be same across all SSIDs in network profile being configured", + Computed: true, + }, + }, + }, + }, + }, + } +} + +func (d *WirelessProfileDataSource) 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 *WirelessProfileDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config WirelessProfile + + // 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 := "" + 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 + } + res = res.Get("response.#(id==\"" + config.Id.ValueString() + "\")") + + 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_wireless_profile_test.go b/internal/provider/data_source_catalystcenter_wireless_profile_test.go new file mode 100644 index 00000000..7537e88d --- /dev/null +++ b/internal/provider/data_source_catalystcenter_wireless_profile_test.go @@ -0,0 +1,80 @@ +// 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 TestAccDataSourceCcWirelessProfile(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_wireless_profile.test", "wireless_profile_name", "Wireless_Profile_1")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_wireless_profile.test", "ssid_details.0.enable_fabric", "true")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_wireless_profile.test", "ssid_details.0.enable_flex_connect", "false")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceCcWirelessProfilePrerequisitesConfig + testAccDataSourceCcWirelessProfileConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAccDataSource + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites +const testAccDataSourceCcWirelessProfilePrerequisitesConfig = ` +resource "catalystcenter_wireless_enterprise_ssid" "test" { + name = "mySSID1" + security_level = "wpa3_enterprise" + passphrase = "Cisco123" +} + +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig +func testAccDataSourceCcWirelessProfileConfig() string { + config := `resource "catalystcenter_wireless_profile" "test" {` + "\n" + config += ` wireless_profile_name = "Wireless_Profile_1"` + "\n" + config += ` ssid_details = [{` + "\n" + config += ` ssid_name = catalystcenter_wireless_enterprise_ssid.test.name` + "\n" + config += ` enable_fabric = true` + "\n" + config += ` enable_flex_connect = false` + "\n" + config += ` }]` + "\n" + config += `}` + "\n" + + config += ` + data "catalystcenter_wireless_profile" "test" { + id = catalystcenter_wireless_profile.test.id + } + ` + return config +} + +// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/model_catalystcenter_wireless_profile.go b/internal/provider/model_catalystcenter_wireless_profile.go index 8e8113cb..e5523684 100644 --- a/internal/provider/model_catalystcenter_wireless_profile.go +++ b/internal/provider/model_catalystcenter_wireless_profile.go @@ -30,26 +30,26 @@ import ( // Section below is generated&owned by "gen/generator.go". //template:begin types type WirelessProfile struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - SsidDetails []WirelessProfileSsidDetails `tfsdk:"ssid_details"` + Id types.String `tfsdk:"id"` + WirelessProfileName types.String `tfsdk:"wireless_profile_name"` + SsidDetails []WirelessProfileSsidDetails `tfsdk:"ssid_details"` } type WirelessProfileSsidDetails struct { - Name types.String `tfsdk:"name"` + SsidName types.String `tfsdk:"ssid_name"` EnableFabric types.Bool `tfsdk:"enable_fabric"` EnableFlexConnect types.Bool `tfsdk:"enable_flex_connect"` LocalToVlan types.Int64 `tfsdk:"local_to_vlan"` InterfaceName types.String `tfsdk:"interface_name"` WlanProfileName types.String `tfsdk:"wlan_profile_name"` - PolicyProfileName types.String `tfsdk:"policy_profile_name"` + Dot11beProfileId types.String `tfsdk:"dot11be_profile_id"` } // End of section. //template:end types // Section below is generated&owned by "gen/generator.go". //template:begin getPath func (data WirelessProfile) getPath() string { - return "/dna/intent/api/v2/wireless/profile" + return "/intent/api/v1/wirelessProfiles" } // End of section. //template:end getPath @@ -66,15 +66,15 @@ func (data WirelessProfile) toBody(ctx context.Context, state WirelessProfile) s put = true } _ = put - if !data.Name.IsNull() { - body, _ = sjson.Set(body, "wirelessProfileName", data.Name.ValueString()) + if !data.WirelessProfileName.IsNull() { + body, _ = sjson.Set(body, "wirelessProfileName", data.WirelessProfileName.ValueString()) } if len(data.SsidDetails) > 0 { body, _ = sjson.Set(body, "ssidDetails", []interface{}{}) for _, item := range data.SsidDetails { itemBody := "" - if !item.Name.IsNull() { - itemBody, _ = sjson.Set(itemBody, "ssidName", item.Name.ValueString()) + if !item.SsidName.IsNull() { + itemBody, _ = sjson.Set(itemBody, "ssidName", item.SsidName.ValueString()) } if !item.EnableFabric.IsNull() { itemBody, _ = sjson.Set(itemBody, "enableFabric", item.EnableFabric.ValueBool()) @@ -91,8 +91,8 @@ func (data WirelessProfile) toBody(ctx context.Context, state WirelessProfile) s if !item.WlanProfileName.IsNull() { itemBody, _ = sjson.Set(itemBody, "wlanProfileName", item.WlanProfileName.ValueString()) } - if !item.PolicyProfileName.IsNull() { - itemBody, _ = sjson.Set(itemBody, "policyProfileName", item.PolicyProfileName.ValueString()) + if !item.Dot11beProfileId.IsNull() { + itemBody, _ = sjson.Set(itemBody, "dot11beProfileId", item.Dot11beProfileId.ValueString()) } body, _ = sjson.SetRaw(body, "ssidDetails.-1", itemBody) } @@ -105,18 +105,18 @@ func (data WirelessProfile) toBody(ctx context.Context, state WirelessProfile) s // Section below is generated&owned by "gen/generator.go". //template:begin fromBody func (data *WirelessProfile) fromBody(ctx context.Context, res gjson.Result) { if value := res.Get("wirelessProfileName"); value.Exists() { - data.Name = types.StringValue(value.String()) + data.WirelessProfileName = types.StringValue(value.String()) } else { - data.Name = types.StringNull() + data.WirelessProfileName = types.StringNull() } if value := res.Get("ssidDetails"); value.Exists() && len(value.Array()) > 0 { data.SsidDetails = make([]WirelessProfileSsidDetails, 0) value.ForEach(func(k, v gjson.Result) bool { item := WirelessProfileSsidDetails{} if cValue := v.Get("ssidName"); cValue.Exists() { - item.Name = types.StringValue(cValue.String()) + item.SsidName = types.StringValue(cValue.String()) } else { - item.Name = types.StringNull() + item.SsidName = types.StringNull() } if cValue := v.Get("enableFabric"); cValue.Exists() { item.EnableFabric = types.BoolValue(cValue.Bool()) @@ -136,7 +136,17 @@ func (data *WirelessProfile) fromBody(ctx context.Context, res gjson.Result) { if cValue := v.Get("interfaceName"); cValue.Exists() { item.InterfaceName = types.StringValue(cValue.String()) } else { - item.InterfaceName = types.StringNull() + item.InterfaceName = types.StringValue("management") + } + if cValue := v.Get("wlanProfileName"); cValue.Exists() { + item.WlanProfileName = types.StringValue(cValue.String()) + } else { + item.WlanProfileName = types.StringNull() + } + if cValue := v.Get("dot11beProfileId"); cValue.Exists() { + item.Dot11beProfileId = types.StringValue(cValue.String()) + } else { + item.Dot11beProfileId = types.StringNull() } data.SsidDetails = append(data.SsidDetails, item) return true @@ -148,14 +158,14 @@ func (data *WirelessProfile) fromBody(ctx context.Context, res gjson.Result) { // Section below is generated&owned by "gen/generator.go". //template:begin updateFromBody func (data *WirelessProfile) updateFromBody(ctx context.Context, res gjson.Result) { - if value := res.Get("wirelessProfileName"); value.Exists() && !data.Name.IsNull() { - data.Name = types.StringValue(value.String()) + if value := res.Get("wirelessProfileName"); value.Exists() && !data.WirelessProfileName.IsNull() { + data.WirelessProfileName = types.StringValue(value.String()) } else { - data.Name = types.StringNull() + data.WirelessProfileName = types.StringNull() } for i := range data.SsidDetails { keys := [...]string{"ssidName"} - keyValues := [...]string{data.SsidDetails[i].Name.ValueString()} + keyValues := [...]string{data.SsidDetails[i].SsidName.ValueString()} var r gjson.Result res.Get("ssidDetails").ForEach( @@ -176,10 +186,10 @@ func (data *WirelessProfile) updateFromBody(ctx context.Context, res gjson.Resul return true }, ) - if value := r.Get("ssidName"); value.Exists() && !data.SsidDetails[i].Name.IsNull() { - data.SsidDetails[i].Name = types.StringValue(value.String()) + if value := r.Get("ssidName"); value.Exists() && !data.SsidDetails[i].SsidName.IsNull() { + data.SsidDetails[i].SsidName = types.StringValue(value.String()) } else { - data.SsidDetails[i].Name = types.StringNull() + data.SsidDetails[i].SsidName = types.StringNull() } if value := r.Get("enableFabric"); value.Exists() && !data.SsidDetails[i].EnableFabric.IsNull() { data.SsidDetails[i].EnableFabric = types.BoolValue(value.Bool()) @@ -198,9 +208,19 @@ func (data *WirelessProfile) updateFromBody(ctx context.Context, res gjson.Resul } if value := r.Get("interfaceName"); value.Exists() && !data.SsidDetails[i].InterfaceName.IsNull() { data.SsidDetails[i].InterfaceName = types.StringValue(value.String()) - } else { + } else if data.SsidDetails[i].InterfaceName.ValueString() != "management" { data.SsidDetails[i].InterfaceName = types.StringNull() } + if value := r.Get("wlanProfileName"); value.Exists() && !data.SsidDetails[i].WlanProfileName.IsNull() { + data.SsidDetails[i].WlanProfileName = types.StringValue(value.String()) + } else { + data.SsidDetails[i].WlanProfileName = types.StringNull() + } + if value := r.Get("dot11beProfileId"); value.Exists() && !data.SsidDetails[i].Dot11beProfileId.IsNull() { + data.SsidDetails[i].Dot11beProfileId = types.StringValue(value.String()) + } else { + data.SsidDetails[i].Dot11beProfileId = types.StringNull() + } } } @@ -208,7 +228,7 @@ func (data *WirelessProfile) updateFromBody(ctx context.Context, res gjson.Resul // Section below is generated&owned by "gen/generator.go". //template:begin isNull func (data *WirelessProfile) isNull(ctx context.Context, res gjson.Result) bool { - if !data.Name.IsNull() { + if !data.WirelessProfileName.IsNull() { return false } if len(data.SsidDetails) > 0 { diff --git a/internal/provider/provider.go b/internal/provider/provider.go index ac73e51e..10c126e2 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -328,6 +328,7 @@ func (p *CcProvider) DataSources(ctx context.Context) []func() datasource.DataSo NewTransitPeerNetworkDataSource, NewUserDataSource, NewWirelessEnterpriseSSIDDataSource, + NewWirelessProfileDataSource, NewWirelessRFProfileDataSource, } } diff --git a/internal/provider/resource_catalystcenter_wireless_profile.go b/internal/provider/resource_catalystcenter_wireless_profile.go index a830f167..161ab3ec 100644 --- a/internal/provider/resource_catalystcenter_wireless_profile.go +++ b/internal/provider/resource_catalystcenter_wireless_profile.go @@ -29,6 +29,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" @@ -68,8 +69,8 @@ func (r *WirelessProfileResource) Schema(ctx context.Context, req resource.Schem stringplanmodifier.UseStateForUnknown(), }, }, - "name": schema.StringAttribute{ - MarkdownDescription: helpers.NewAttributeDescription("Profile Name").String, + "wireless_profile_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Wireless Network Profile Name").String, Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), @@ -80,16 +81,16 @@ func (r *WirelessProfileResource) Schema(ctx context.Context, req resource.Schem Optional: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ - "name": schema.StringAttribute{ - MarkdownDescription: helpers.NewAttributeDescription("Ssid Name").String, + "ssid_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("SSID Name").String, Required: true, }, "enable_fabric": schema.BoolAttribute{ - MarkdownDescription: helpers.NewAttributeDescription("`true` if ssid is fabric else `false`").String, + MarkdownDescription: helpers.NewAttributeDescription("True if fabric is enabled, else False. Flex and fabric cannot be enabled simultaneously and a profile can only contain either flex SSIDs or fabric SSIDs and not both at the same time").String, Optional: true, }, "enable_flex_connect": schema.BoolAttribute{ - MarkdownDescription: helpers.NewAttributeDescription("`true` if flex connect is enabled else `false`").String, + MarkdownDescription: helpers.NewAttributeDescription("True if flex connect is enabled, else False. Flex and fabric cannot be enabled simultaneously and a profile can only contain either flex SSIDs or fabric SSIDs and not both at the same time").String, Optional: true, }, "local_to_vlan": schema.Int64Attribute{ @@ -97,15 +98,17 @@ func (r *WirelessProfileResource) Schema(ctx context.Context, req resource.Schem Optional: true, }, "interface_name": schema.StringAttribute{ - MarkdownDescription: helpers.NewAttributeDescription("Interface Name").String, + MarkdownDescription: helpers.NewAttributeDescription("Interface Name").AddDefaultValueDescription("management").String, Optional: true, + Computed: true, + Default: stringdefault.StaticString("management"), }, "wlan_profile_name": schema.StringAttribute{ MarkdownDescription: helpers.NewAttributeDescription("WLAN Profile Name").String, Optional: true, }, - "policy_profile_name": schema.StringAttribute{ - MarkdownDescription: helpers.NewAttributeDescription("Policy Profile Name").String, + "dot11be_profile_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("802.11be Profile Id. Applicable to IOS controllers with version 17.15 and higher. 802.11be Profiles if passed, should be same across all SSIDs in network profile being configured").String, Optional: true, }, }, @@ -153,7 +156,7 @@ func (r *WirelessProfileResource) Create(ctx context.Context, req resource.Creat 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.#(wirelessProfileName==\"" + plan.Name.ValueString() + "\").instanceUuid").String()) + plan.Id = types.StringValue(res.Get("response.#(wirelessProfileName==\"" + plan.WirelessProfileName.ValueString() + "\").id").String()) tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) @@ -185,7 +188,7 @@ func (r *WirelessProfileResource) Read(ctx context.Context, req resource.ReadReq resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) return } - res = res.Get("response.#(instanceUuid==\"" + state.Id.ValueString() + "\")") + res = res.Get("response.#(id==\"" + state.Id.ValueString() + "\")") // If every attribute is set to null we are dealing with an import operation and therefore reading all attributes if state.isNull(ctx, res) { @@ -223,7 +226,7 @@ func (r *WirelessProfileResource) Update(ctx context.Context, req resource.Updat body := plan.toBody(ctx, state) params := "" - res, err := r.client.Put(plan.getPath()+params, body) + res, err := r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString())+params, body) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) return @@ -249,8 +252,7 @@ func (r *WirelessProfileResource) Delete(ctx context.Context, req resource.Delet } tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) - params := "?name=" + url.QueryEscape(state.Name.ValueString()) - res, err := r.client.Delete(state.getPath() + params) + 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 @@ -265,16 +267,7 @@ func (r *WirelessProfileResource) Delete(ctx context.Context, req resource.Delet // Section below is generated&owned by "gen/generator.go". //template:begin import func (r *WirelessProfileResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - idParts := strings.Split(req.ID, ",") - - if len(idParts) != 1 || idParts[0] == "" { - 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("id"), idParts[0])...) + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } // End of section. //template:end import diff --git a/internal/provider/resource_catalystcenter_wireless_profile_test.go b/internal/provider/resource_catalystcenter_wireless_profile_test.go index b83e12bf..c6bbdccd 100644 --- a/internal/provider/resource_catalystcenter_wireless_profile_test.go +++ b/internal/provider/resource_catalystcenter_wireless_profile_test.go @@ -30,7 +30,7 @@ import ( // Section below is generated&owned by "gen/generator.go". //template:begin testAcc func TestAccCcWirelessProfile(t *testing.T) { var checks []resource.TestCheckFunc - checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_wireless_profile.test", "name", "Wireless_Profile_1")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_wireless_profile.test", "wireless_profile_name", "Wireless_Profile_1")) checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_wireless_profile.test", "ssid_details.0.enable_fabric", "true")) checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_wireless_profile.test", "ssid_details.0.enable_flex_connect", "false")) @@ -73,7 +73,7 @@ resource "catalystcenter_wireless_enterprise_ssid" "test" { // Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal func testAccCcWirelessProfileConfig_minimum() string { config := `resource "catalystcenter_wireless_profile" "test" {` + "\n" - config += ` name = "Wireless_Profile_1"` + "\n" + config += ` wireless_profile_name = "Wireless_Profile_1"` + "\n" config += `}` + "\n" return config } @@ -83,9 +83,9 @@ func testAccCcWirelessProfileConfig_minimum() string { // Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll func testAccCcWirelessProfileConfig_all() string { config := `resource "catalystcenter_wireless_profile" "test" {` + "\n" - config += ` name = "Wireless_Profile_1"` + "\n" + config += ` wireless_profile_name = "Wireless_Profile_1"` + "\n" config += ` ssid_details = [{` + "\n" - config += ` name = catalystcenter_wireless_enterprise_ssid.test.name` + "\n" + config += ` ssid_name = catalystcenter_wireless_enterprise_ssid.test.name` + "\n" config += ` enable_fabric = true` + "\n" config += ` enable_flex_connect = false` + "\n" config += ` }]` + "\n" diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index 0f9d98a3..f399dd51 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -9,6 +9,7 @@ description: |- ## 0.1.10 (unreleased) +- BREAKING CHANGE: Modified `wireless_profile` resource to use `/intent/api/v1/wirelessProfiles` API endpoint, this resource now only works with Catalyst Center version 2.3.7.6+ - Add `wireless_device_provision` resource - Add `fabric_provision_device` resource and data source - Add `assign_templates_to_tag` resource and data source From ce879d826d31a58e0d34eda440197e13d229a18b Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Tue, 6 Aug 2024 11:11:30 +0200 Subject: [PATCH 27/31] add test tag CC2376 to wireles_profile resource --- gen/definitions/wireless_profile.yaml | 1 + .../data_source_catalystcenter_wireless_profile_test.go | 4 ++++ .../provider/resource_catalystcenter_wireless_profile_test.go | 3 +++ 3 files changed, 8 insertions(+) diff --git a/gen/definitions/wireless_profile.yaml b/gen/definitions/wireless_profile.yaml index 4e9fde23..a6431074 100644 --- a/gen/definitions/wireless_profile.yaml +++ b/gen/definitions/wireless_profile.yaml @@ -9,6 +9,7 @@ res_description: 'This resource creates a wireless network profile. To associate a wireless network profile with a site, use the `catalystcenter_associate_site_to_network_profile` resource.' doc_category: Wireless +test_tags: [CC2376] attributes: - model_name: wirelessProfileName type: String diff --git a/internal/provider/data_source_catalystcenter_wireless_profile_test.go b/internal/provider/data_source_catalystcenter_wireless_profile_test.go index 7537e88d..13d13997 100644 --- a/internal/provider/data_source_catalystcenter_wireless_profile_test.go +++ b/internal/provider/data_source_catalystcenter_wireless_profile_test.go @@ -19,6 +19,7 @@ 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" @@ -28,6 +29,9 @@ import ( // Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSource func TestAccDataSourceCcWirelessProfile(t *testing.T) { + if os.Getenv("CC2376") == "" { + t.Skip("skipping test, set environment variable CC2376") + } var checks []resource.TestCheckFunc checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_wireless_profile.test", "wireless_profile_name", "Wireless_Profile_1")) checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_wireless_profile.test", "ssid_details.0.enable_fabric", "true")) diff --git a/internal/provider/resource_catalystcenter_wireless_profile_test.go b/internal/provider/resource_catalystcenter_wireless_profile_test.go index c6bbdccd..2c578a5a 100644 --- a/internal/provider/resource_catalystcenter_wireless_profile_test.go +++ b/internal/provider/resource_catalystcenter_wireless_profile_test.go @@ -29,6 +29,9 @@ import ( // Section below is generated&owned by "gen/generator.go". //template:begin testAcc func TestAccCcWirelessProfile(t *testing.T) { + if os.Getenv("CC2376") == "" { + t.Skip("skipping test, set environment variable CC2376") + } var checks []resource.TestCheckFunc checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_wireless_profile.test", "wireless_profile_name", "Wireless_Profile_1")) checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_wireless_profile.test", "ssid_details.0.enable_fabric", "true")) From 686672af12cea95db4ccdcd95f8c568246e641e1 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Wed, 7 Aug 2024 11:19:02 +0200 Subject: [PATCH 28/31] added fabric_l2_handoff --- docs/data-sources/fabric_l2_handoff.md | 36 +++ docs/resources/fabric_l2_handoff.md | 46 +++ .../data-source.tf | 5 + .../import.sh | 1 + .../resource.tf | 7 + gen/definitions/fabric_l2_handoff.yaml | 67 +++++ gen/templates/resource.go | 2 +- ...source_catalystcenter_fabric_l2_handoff.go | 128 ++++++++ ...e_catalystcenter_fabric_l2_handoff_test.go | 91 ++++++ .../model_catalystcenter_fabric_l2_handoff.go | 159 ++++++++++ internal/provider/provider.go | 2 + ...source_catalystcenter_fabric_l2_handoff.go | 277 ++++++++++++++++++ ...e_catalystcenter_fabric_l2_handoff_test.go | 104 +++++++ 13 files changed, 924 insertions(+), 1 deletion(-) create mode 100644 docs/data-sources/fabric_l2_handoff.md create mode 100644 docs/resources/fabric_l2_handoff.md create mode 100644 examples/data-sources/catalystcenter_fabric_l2_handoff/data-source.tf create mode 100644 examples/resources/catalystcenter_fabric_l2_handoff/import.sh create mode 100644 examples/resources/catalystcenter_fabric_l2_handoff/resource.tf create mode 100644 gen/definitions/fabric_l2_handoff.yaml create mode 100644 internal/provider/data_source_catalystcenter_fabric_l2_handoff.go create mode 100644 internal/provider/data_source_catalystcenter_fabric_l2_handoff_test.go create mode 100644 internal/provider/model_catalystcenter_fabric_l2_handoff.go create mode 100644 internal/provider/resource_catalystcenter_fabric_l2_handoff.go create mode 100644 internal/provider/resource_catalystcenter_fabric_l2_handoff_test.go diff --git a/docs/data-sources/fabric_l2_handoff.md b/docs/data-sources/fabric_l2_handoff.md new file mode 100644 index 00000000..2418ad63 --- /dev/null +++ b/docs/data-sources/fabric_l2_handoff.md @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_fabric_l2_handoff Data Source - terraform-provider-catalystcenter" +subcategory: "SDA" +description: |- + This data source can read the Fabric L2 Handoff. +--- + +# catalystcenter_fabric_l2_handoff (Data Source) + +This data source can read the Fabric L2 Handoff. + +## Example Usage + +```terraform +data "catalystcenter_fabric_l2_handoff" "example" { + id = "76d24097-41c4-4558-a4d0-a8c07ac08470" + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = "c4b85bb2-ce3f-4db9-a32b-e439a388ac2f" +} +``` + + +## Schema + +### Required + +- `fabric_id` (String) ID of the fabric this device belongs to +- `id` (String) The id of the object +- `network_device_id` (String) Network device ID of the fabric device + +### Read-Only + +- `external_vlan_id` (Number) External VLAN number into which the fabric must be extended. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094) +- `interface_name` (String) Interface name of the layer 2 handoff. E.g., GigabitEthernet1/0/4 +- `internal_vlan_id` (Number) VLAN number associated with this fabric. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094) diff --git a/docs/resources/fabric_l2_handoff.md b/docs/resources/fabric_l2_handoff.md new file mode 100644 index 00000000..0ee5f140 --- /dev/null +++ b/docs/resources/fabric_l2_handoff.md @@ -0,0 +1,46 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "catalystcenter_fabric_l2_handoff Resource - terraform-provider-catalystcenter" +subcategory: "SDA" +description: |- + Manages Layer 2 Handoffs in Fabric Devices +--- + +# catalystcenter_fabric_l2_handoff (Resource) + +Manages Layer 2 Handoffs in Fabric Devices + +## Example Usage + +```terraform +resource "catalystcenter_fabric_l2_handoff" "example" { + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = "c4b85bb2-ce3f-4db9-a32b-e439a388ac2f" + interface_name = "GigabitEthernet1/0/4" + internal_vlan_id = 300 + external_vlan_id = 400 +} +``` + + +## Schema + +### Required + +- `external_vlan_id` (Number) External VLAN number into which the fabric must be extended. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094) +- `fabric_id` (String) ID of the fabric this device belongs to +- `interface_name` (String) Interface name of the layer 2 handoff. E.g., GigabitEthernet1/0/4 +- `internal_vlan_id` (Number) VLAN number associated with this fabric. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094) +- `network_device_id` (String) Network device ID of the fabric device + +### Read-Only + +- `id` (String) The id of the object + +## Import + +Import is supported using the following syntax: + +```shell +terraform import catalystcenter_fabric_l2_handoff.example ",," +``` diff --git a/examples/data-sources/catalystcenter_fabric_l2_handoff/data-source.tf b/examples/data-sources/catalystcenter_fabric_l2_handoff/data-source.tf new file mode 100644 index 00000000..f42ba2ed --- /dev/null +++ b/examples/data-sources/catalystcenter_fabric_l2_handoff/data-source.tf @@ -0,0 +1,5 @@ +data "catalystcenter_fabric_l2_handoff" "example" { + id = "76d24097-41c4-4558-a4d0-a8c07ac08470" + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = "c4b85bb2-ce3f-4db9-a32b-e439a388ac2f" +} diff --git a/examples/resources/catalystcenter_fabric_l2_handoff/import.sh b/examples/resources/catalystcenter_fabric_l2_handoff/import.sh new file mode 100644 index 00000000..fbf9f278 --- /dev/null +++ b/examples/resources/catalystcenter_fabric_l2_handoff/import.sh @@ -0,0 +1 @@ +terraform import catalystcenter_fabric_l2_handoff.example ",," diff --git a/examples/resources/catalystcenter_fabric_l2_handoff/resource.tf b/examples/resources/catalystcenter_fabric_l2_handoff/resource.tf new file mode 100644 index 00000000..4d014cdc --- /dev/null +++ b/examples/resources/catalystcenter_fabric_l2_handoff/resource.tf @@ -0,0 +1,7 @@ +resource "catalystcenter_fabric_l2_handoff" "example" { + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = "c4b85bb2-ce3f-4db9-a32b-e439a388ac2f" + interface_name = "GigabitEthernet1/0/4" + internal_vlan_id = 300 + external_vlan_id = 400 +} diff --git a/gen/definitions/fabric_l2_handoff.yaml b/gen/definitions/fabric_l2_handoff.yaml new file mode 100644 index 00000000..5434832d --- /dev/null +++ b/gen/definitions/fabric_l2_handoff.yaml @@ -0,0 +1,67 @@ +--- +name: Fabric L2 Handoff +rest_endpoint: /dna/intent/api/v1/sda/fabricDevices/layer2Handoffs +res_description: Manages Layer 2 Handoffs in Fabric Devices +id_from_query_path: response +id_from_query_path_attribute: id +put_id_include_path: 0.id +get_from_all: true +put_no_id: true +doc_category: SDA +test_tags: [SDA] +attributes: + - model_name: networkDeviceId + query_param: true + requires_replace: true + data_path: '0' + response_data_path: networkDeviceId + mandatory: true + description: Network device ID of the fabric device + type: String + example: 5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1 + - model_name: fabricId + requires_replace: true + query_param: true + data_path: '0' + response_data_path: fabricId + type: String + mandatory: true + description: ID of the fabric this device belongs to + example: c4b85bb2-ce3f-4db9-a32b-e439a388ac2f + test_value: catalystcenter_fabric_site.test.id + - model_name: interfaceName + requires_replace: true + data_path: '0' + response_data_path: interfaceName + type: String + mandatory: true + description: Interface name of the layer 2 handoff. E.g., GigabitEthernet1/0/4 + example: GigabitEthernet1/0/4 + - model_name: internalVlanId + data_path: '0' + requires_replace: true + response_data_path: internalVlanId + type: Int64 + match_id: true + mandatory: true + description: VLAN number associated with this fabric. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094) + example: 300 + - model_name: externalVlanId + data_path: '0' + requires_replace: true + response_data_path: externalVlanId + mandatory: true + type: Int64 + description: External VLAN number into which the fabric must be extended. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094) + example: 400 +test_prerequisites: | + resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" + } + 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] + } diff --git a/gen/templates/resource.go b/gen/templates/resource.go index d2dfcc54..ecbc78a4 100644 --- a/gen/templates/resource.go +++ b/gen/templates/resource.go @@ -459,7 +459,7 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C {{- if and .IdFromQueryPathAttribute .IdFromQueryPath (not .GetExtraQueryParams) (not .GetFromAll) }} 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()) + plan.Id = types.StringValue(res.Get("{{.IdFromQueryPath}}.#({{if $id.ResponseModelName}}{{$id.ResponseModelName}}{{else}}{{$id.ModelName}}{{end}}==\""+ {{if eq $id.Type "Int64"}}strconv.FormatInt(plan.{{toGoName $id.TfName}}.ValueInt64(), 10){{else}}plan.{{toGoName $id.TfName}}.Value{{$id.Type}}(){{end}} +"\").{{if .IdFromQueryPathAttribute}}{{.IdFromQueryPathAttribute}}{{else}}id{{end}}").String()) {{- end}} {{- /* If we have an id attribute we will use that as id */}} {{- else if hasId .Attributes}} diff --git a/internal/provider/data_source_catalystcenter_fabric_l2_handoff.go b/internal/provider/data_source_catalystcenter_fabric_l2_handoff.go new file mode 100644 index 00000000..229f08b0 --- /dev/null +++ b/internal/provider/data_source_catalystcenter_fabric_l2_handoff.go @@ -0,0 +1,128 @@ +// 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 = &FabricL2HandoffDataSource{} + _ datasource.DataSourceWithConfigure = &FabricL2HandoffDataSource{} +) + +func NewFabricL2HandoffDataSource() datasource.DataSource { + return &FabricL2HandoffDataSource{} +} + +type FabricL2HandoffDataSource struct { + client *cc.Client +} + +func (d *FabricL2HandoffDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_fabric_l2_handoff" +} + +func (d *FabricL2HandoffDataSource) 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 Fabric L2 Handoff.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Required: true, + }, + "network_device_id": schema.StringAttribute{ + MarkdownDescription: "Network device ID of the fabric device", + Required: true, + }, + "fabric_id": schema.StringAttribute{ + MarkdownDescription: "ID of the fabric this device belongs to", + Required: true, + }, + "interface_name": schema.StringAttribute{ + MarkdownDescription: "Interface name of the layer 2 handoff. E.g., GigabitEthernet1/0/4", + Computed: true, + }, + "internal_vlan_id": schema.Int64Attribute{ + MarkdownDescription: "VLAN number associated with this fabric. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094)", + Computed: true, + }, + "external_vlan_id": schema.Int64Attribute{ + MarkdownDescription: "External VLAN number into which the fabric must be extended. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094)", + Computed: true, + }, + }, + } +} + +func (d *FabricL2HandoffDataSource) 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 *FabricL2HandoffDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config FabricL2Handoff + + // 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 += "?networkDeviceId=" + url.QueryEscape(config.NetworkDeviceId.ValueString()) + "&fabricId=" + url.QueryEscape(config.FabricId.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 + } + res = res.Get("response.#(id==\"" + config.Id.ValueString() + "\")") + + 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_fabric_l2_handoff_test.go b/internal/provider/data_source_catalystcenter_fabric_l2_handoff_test.go new file mode 100644 index 00000000..76f1486e --- /dev/null +++ b/internal/provider/data_source_catalystcenter_fabric_l2_handoff_test.go @@ -0,0 +1,91 @@ +// 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 testAccDataSource +func TestAccDataSourceCcFabricL2Handoff(t *testing.T) { + if os.Getenv("SDA") == "" { + t.Skip("skipping test, set environment variable SDA") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_l2_handoff.test", "network_device_id", "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_l2_handoff.test", "interface_name", "GigabitEthernet1/0/4")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_l2_handoff.test", "internal_vlan_id", "300")) + checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_fabric_l2_handoff.test", "external_vlan_id", "400")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceCcFabricL2HandoffPrerequisitesConfig + testAccDataSourceCcFabricL2HandoffConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAccDataSource + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites +const testAccDataSourceCcFabricL2HandoffPrerequisitesConfig = ` +resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" +} +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] +} + +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig +func testAccDataSourceCcFabricL2HandoffConfig() string { + config := `resource "catalystcenter_fabric_l2_handoff" "test" {` + "\n" + config += ` network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1"` + "\n" + config += ` fabric_id = catalystcenter_fabric_site.test.id` + "\n" + config += ` interface_name = "GigabitEthernet1/0/4"` + "\n" + config += ` internal_vlan_id = 300` + "\n" + config += ` external_vlan_id = 400` + "\n" + config += `}` + "\n" + + config += ` + data "catalystcenter_fabric_l2_handoff" "test" { + id = catalystcenter_fabric_l2_handoff.test.id + network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1" + fabric_id = catalystcenter_fabric_site.test.id + } + ` + return config +} + +// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/model_catalystcenter_fabric_l2_handoff.go b/internal/provider/model_catalystcenter_fabric_l2_handoff.go new file mode 100644 index 00000000..f58c28a4 --- /dev/null +++ b/internal/provider/model_catalystcenter_fabric_l2_handoff.go @@ -0,0 +1,159 @@ +// 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 FabricL2Handoff struct { + Id types.String `tfsdk:"id"` + NetworkDeviceId types.String `tfsdk:"network_device_id"` + FabricId types.String `tfsdk:"fabric_id"` + InterfaceName types.String `tfsdk:"interface_name"` + InternalVlanId types.Int64 `tfsdk:"internal_vlan_id"` + ExternalVlanId types.Int64 `tfsdk:"external_vlan_id"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath +func (data FabricL2Handoff) getPath() string { + return "/dna/intent/api/v1/sda/fabricDevices/layer2Handoffs" +} + +// 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 FabricL2Handoff) toBody(ctx context.Context, state FabricL2Handoff) string { + body := "" + put := false + if state.Id.ValueString() != "" { + put = true + body, _ = sjson.Set(body, "0.id", state.Id.ValueString()) + } + _ = put + if !data.NetworkDeviceId.IsNull() { + body, _ = sjson.Set(body, "0.networkDeviceId", data.NetworkDeviceId.ValueString()) + } + if !data.FabricId.IsNull() { + body, _ = sjson.Set(body, "0.fabricId", data.FabricId.ValueString()) + } + if !data.InterfaceName.IsNull() { + body, _ = sjson.Set(body, "0.interfaceName", data.InterfaceName.ValueString()) + } + if !data.InternalVlanId.IsNull() { + body, _ = sjson.Set(body, "0.internalVlanId", data.InternalVlanId.ValueInt64()) + } + if !data.ExternalVlanId.IsNull() { + body, _ = sjson.Set(body, "0.externalVlanId", data.ExternalVlanId.ValueInt64()) + } + return body +} + +// End of section. //template:end toBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBody +func (data *FabricL2Handoff) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("networkDeviceId"); value.Exists() { + data.NetworkDeviceId = types.StringValue(value.String()) + } else { + data.NetworkDeviceId = types.StringNull() + } + if value := res.Get("fabricId"); value.Exists() { + data.FabricId = types.StringValue(value.String()) + } else { + data.FabricId = types.StringNull() + } + if value := res.Get("interfaceName"); value.Exists() { + data.InterfaceName = types.StringValue(value.String()) + } else { + data.InterfaceName = types.StringNull() + } + if value := res.Get("internalVlanId"); value.Exists() { + data.InternalVlanId = types.Int64Value(value.Int()) + } else { + data.InternalVlanId = types.Int64Null() + } + if value := res.Get("externalVlanId"); value.Exists() { + data.ExternalVlanId = types.Int64Value(value.Int()) + } else { + data.ExternalVlanId = types.Int64Null() + } +} + +// End of section. //template:end fromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin updateFromBody +func (data *FabricL2Handoff) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("networkDeviceId"); value.Exists() && !data.NetworkDeviceId.IsNull() { + data.NetworkDeviceId = types.StringValue(value.String()) + } else { + data.NetworkDeviceId = types.StringNull() + } + if value := res.Get("fabricId"); value.Exists() && !data.FabricId.IsNull() { + data.FabricId = types.StringValue(value.String()) + } else { + data.FabricId = types.StringNull() + } + if value := res.Get("interfaceName"); value.Exists() && !data.InterfaceName.IsNull() { + data.InterfaceName = types.StringValue(value.String()) + } else { + data.InterfaceName = types.StringNull() + } + if value := res.Get("internalVlanId"); value.Exists() && !data.InternalVlanId.IsNull() { + data.InternalVlanId = types.Int64Value(value.Int()) + } else { + data.InternalVlanId = types.Int64Null() + } + if value := res.Get("externalVlanId"); value.Exists() && !data.ExternalVlanId.IsNull() { + data.ExternalVlanId = types.Int64Value(value.Int()) + } else { + data.ExternalVlanId = types.Int64Null() + } +} + +// End of section. //template:end updateFromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin isNull +func (data *FabricL2Handoff) isNull(ctx context.Context, res gjson.Result) bool { + if !data.InterfaceName.IsNull() { + return false + } + if !data.InternalVlanId.IsNull() { + return false + } + if !data.ExternalVlanId.IsNull() { + return false + } + return true +} + +// End of section. //template:end isNull diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 10c126e2..4f4db7d6 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -256,6 +256,7 @@ func (p *CcProvider) Resources(ctx context.Context) []func() resource.Resource { NewDiscoveryResource, NewFabricAuthenticationProfileResource, NewFabricDeviceResource, + NewFabricL2HandoffResource, NewFabricL3HandoffIPTransitResource, NewFabricProvisionDeviceResource, NewFabricSiteResource, @@ -308,6 +309,7 @@ func (p *CcProvider) DataSources(ctx context.Context) []func() datasource.DataSo NewDiscoveryDataSource, NewFabricAuthenticationProfileDataSource, NewFabricDeviceDataSource, + NewFabricL2HandoffDataSource, NewFabricL3HandoffIPTransitDataSource, NewFabricProvisionDeviceDataSource, NewFabricSiteDataSource, diff --git a/internal/provider/resource_catalystcenter_fabric_l2_handoff.go b/internal/provider/resource_catalystcenter_fabric_l2_handoff.go new file mode 100644 index 00000000..e76770ce --- /dev/null +++ b/internal/provider/resource_catalystcenter_fabric_l2_handoff.go @@ -0,0 +1,277 @@ +// 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" + "strconv" + "strings" + + "github.com/CiscoDevNet/terraform-provider-catalystcenter/internal/provider/helpers" + "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/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/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 = &FabricL2HandoffResource{} +var _ resource.ResourceWithImportState = &FabricL2HandoffResource{} + +func NewFabricL2HandoffResource() resource.Resource { + return &FabricL2HandoffResource{} +} + +type FabricL2HandoffResource struct { + client *cc.Client +} + +func (r *FabricL2HandoffResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_fabric_l2_handoff" +} + +func (r *FabricL2HandoffResource) 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 Layer 2 Handoffs in Fabric Devices").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "network_device_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Network device ID of the fabric device").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "fabric_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("ID of the fabric this device belongs to").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "interface_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Interface name of the layer 2 handoff. E.g., GigabitEthernet1/0/4").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "internal_vlan_id": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("VLAN number associated with this fabric. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094)").String, + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "external_vlan_id": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("External VLAN number into which the fabric must be extended. Allowed VLAN range is 2-4094 except for reserved vlans (1, 1002-1005, 2046, 4094)").String, + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (r *FabricL2HandoffResource) 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 *FabricL2HandoffResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan FabricL2Handoff + + // 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, FabricL2Handoff{}) + + params := "" + res, err := r.client.Post(plan.getPath()+params, body) + 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 += "?networkDeviceId=" + url.QueryEscape(plan.NetworkDeviceId.ValueString()) + "&fabricId=" + url.QueryEscape(plan.FabricId.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.#(internalVlanId==\"" + strconv.FormatInt(plan.InternalVlanId.ValueInt64(), 10) + "\").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 *FabricL2HandoffResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state FabricL2Handoff + + // 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 += "?networkDeviceId=" + url.QueryEscape(state.NetworkDeviceId.ValueString()) + "&fabricId=" + url.QueryEscape(state.FabricId.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 + } + res = res.Get("response.#(id==\"" + state.Id.ValueString() + "\")") + + // 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 *FabricL2HandoffResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state FabricL2Handoff + + // 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) + 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 *FabricL2HandoffResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state FabricL2Handoff + + // 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 *FabricL2HandoffResource) 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("network_device_id"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("fabric_id"), idParts[1])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), idParts[2])...) +} + +// End of section. //template:end import diff --git a/internal/provider/resource_catalystcenter_fabric_l2_handoff_test.go b/internal/provider/resource_catalystcenter_fabric_l2_handoff_test.go new file mode 100644 index 00000000..76065548 --- /dev/null +++ b/internal/provider/resource_catalystcenter_fabric_l2_handoff_test.go @@ -0,0 +1,104 @@ +// 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 TestAccCcFabricL2Handoff(t *testing.T) { + if os.Getenv("SDA") == "" { + t.Skip("skipping test, set environment variable SDA") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_l2_handoff.test", "network_device_id", "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_l2_handoff.test", "interface_name", "GigabitEthernet1/0/4")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_l2_handoff.test", "internal_vlan_id", "300")) + checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_fabric_l2_handoff.test", "external_vlan_id", "400")) + + var steps []resource.TestStep + if os.Getenv("SKIP_MINIMUM_TEST") == "" { + steps = append(steps, resource.TestStep{ + Config: testAccCcFabricL2HandoffPrerequisitesConfig + testAccCcFabricL2HandoffConfig_minimum(), + }) + } + steps = append(steps, resource.TestStep{ + Config: testAccCcFabricL2HandoffPrerequisitesConfig + testAccCcFabricL2HandoffConfig_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 testAccCcFabricL2HandoffPrerequisitesConfig = ` +resource "catalystcenter_area" "test" { + name = "Area1" + parent_name = "Global" +} +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] +} + +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal +func testAccCcFabricL2HandoffConfig_minimum() string { + config := `resource "catalystcenter_fabric_l2_handoff" "test" {` + "\n" + config += ` network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1"` + "\n" + config += ` fabric_id = catalystcenter_fabric_site.test.id` + "\n" + config += ` interface_name = "GigabitEthernet1/0/4"` + "\n" + config += ` internal_vlan_id = 300` + "\n" + config += ` external_vlan_id = 400` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll +func testAccCcFabricL2HandoffConfig_all() string { + config := `resource "catalystcenter_fabric_l2_handoff" "test" {` + "\n" + config += ` network_device_id = "5e6f7b3a-2b0b-4a7d-8b1c-0d4b1cd5e1b1"` + "\n" + config += ` fabric_id = catalystcenter_fabric_site.test.id` + "\n" + config += ` interface_name = "GigabitEthernet1/0/4"` + "\n" + config += ` internal_vlan_id = 300` + "\n" + config += ` external_vlan_id = 400` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll From 7d3ba2fb61dbd6f4e4b07b8838e053b41faf109c Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Wed, 7 Aug 2024 11:54:50 +0200 Subject: [PATCH 29/31] removed test tag from wireless_profile --- gen/definitions/wireless_profile.yaml | 2 +- .../data_source_catalystcenter_wireless_profile_test.go | 5 +---- .../resource_catalystcenter_wireless_profile_test.go | 4 +--- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/gen/definitions/wireless_profile.yaml b/gen/definitions/wireless_profile.yaml index a6431074..d63ea347 100644 --- a/gen/definitions/wireless_profile.yaml +++ b/gen/definitions/wireless_profile.yaml @@ -9,7 +9,6 @@ res_description: 'This resource creates a wireless network profile. To associate a wireless network profile with a site, use the `catalystcenter_associate_site_to_network_profile` resource.' doc_category: Wireless -test_tags: [CC2376] attributes: - model_name: wirelessProfileName type: String @@ -61,4 +60,5 @@ test_prerequisites: | name = "mySSID1" security_level = "wpa3_enterprise" passphrase = "Cisco123" + ghz24_policy = "dot11-g-only" } diff --git a/internal/provider/data_source_catalystcenter_wireless_profile_test.go b/internal/provider/data_source_catalystcenter_wireless_profile_test.go index 13d13997..017ffabb 100644 --- a/internal/provider/data_source_catalystcenter_wireless_profile_test.go +++ b/internal/provider/data_source_catalystcenter_wireless_profile_test.go @@ -19,7 +19,6 @@ 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" @@ -29,9 +28,6 @@ import ( // Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSource func TestAccDataSourceCcWirelessProfile(t *testing.T) { - if os.Getenv("CC2376") == "" { - t.Skip("skipping test, set environment variable CC2376") - } var checks []resource.TestCheckFunc checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_wireless_profile.test", "wireless_profile_name", "Wireless_Profile_1")) checks = append(checks, resource.TestCheckResourceAttr("data.catalystcenter_wireless_profile.test", "ssid_details.0.enable_fabric", "true")) @@ -56,6 +52,7 @@ resource "catalystcenter_wireless_enterprise_ssid" "test" { name = "mySSID1" security_level = "wpa3_enterprise" passphrase = "Cisco123" + ghz24_policy = "dot11-g-only" } ` diff --git a/internal/provider/resource_catalystcenter_wireless_profile_test.go b/internal/provider/resource_catalystcenter_wireless_profile_test.go index 2c578a5a..ce0b4ca7 100644 --- a/internal/provider/resource_catalystcenter_wireless_profile_test.go +++ b/internal/provider/resource_catalystcenter_wireless_profile_test.go @@ -29,9 +29,6 @@ import ( // Section below is generated&owned by "gen/generator.go". //template:begin testAcc func TestAccCcWirelessProfile(t *testing.T) { - if os.Getenv("CC2376") == "" { - t.Skip("skipping test, set environment variable CC2376") - } var checks []resource.TestCheckFunc checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_wireless_profile.test", "wireless_profile_name", "Wireless_Profile_1")) checks = append(checks, resource.TestCheckResourceAttr("catalystcenter_wireless_profile.test", "ssid_details.0.enable_fabric", "true")) @@ -67,6 +64,7 @@ resource "catalystcenter_wireless_enterprise_ssid" "test" { name = "mySSID1" security_level = "wpa3_enterprise" passphrase = "Cisco123" + ghz24_policy = "dot11-g-only" } ` From 4496a674e2dceb19b5d40c3a987364fa0bc061b9 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Wed, 7 Aug 2024 13:14:34 +0200 Subject: [PATCH 30/31] removed ghz24_policy from test prerequisites --- gen/definitions/wireless_profile.yaml | 1 - .../provider/data_source_catalystcenter_wireless_profile_test.go | 1 - .../provider/resource_catalystcenter_wireless_profile_test.go | 1 - 3 files changed, 3 deletions(-) diff --git a/gen/definitions/wireless_profile.yaml b/gen/definitions/wireless_profile.yaml index d63ea347..4e9fde23 100644 --- a/gen/definitions/wireless_profile.yaml +++ b/gen/definitions/wireless_profile.yaml @@ -60,5 +60,4 @@ test_prerequisites: | name = "mySSID1" security_level = "wpa3_enterprise" passphrase = "Cisco123" - ghz24_policy = "dot11-g-only" } diff --git a/internal/provider/data_source_catalystcenter_wireless_profile_test.go b/internal/provider/data_source_catalystcenter_wireless_profile_test.go index 017ffabb..7537e88d 100644 --- a/internal/provider/data_source_catalystcenter_wireless_profile_test.go +++ b/internal/provider/data_source_catalystcenter_wireless_profile_test.go @@ -52,7 +52,6 @@ resource "catalystcenter_wireless_enterprise_ssid" "test" { name = "mySSID1" security_level = "wpa3_enterprise" passphrase = "Cisco123" - ghz24_policy = "dot11-g-only" } ` diff --git a/internal/provider/resource_catalystcenter_wireless_profile_test.go b/internal/provider/resource_catalystcenter_wireless_profile_test.go index ce0b4ca7..c6bbdccd 100644 --- a/internal/provider/resource_catalystcenter_wireless_profile_test.go +++ b/internal/provider/resource_catalystcenter_wireless_profile_test.go @@ -64,7 +64,6 @@ resource "catalystcenter_wireless_enterprise_ssid" "test" { name = "mySSID1" security_level = "wpa3_enterprise" passphrase = "Cisco123" - ghz24_policy = "dot11-g-only" } ` From 78b89fba6021e000f7bc921892440c80c86e4950 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Wed, 7 Aug 2024 13:24:07 +0200 Subject: [PATCH 31/31] 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 f9a3c01a..d727e763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.1.10 (unreleased) +- Add `fabric_l2_handoff` resource and data source - BREAKING CHANGE: Modified `wireless_profile` resource to use `/intent/api/v1/wirelessProfiles` API endpoint, this resource now only works with Catalyst Center version 2.3.7.6+ - Add `wireless_device_provision` resource - Add `fabric_provision_device` resource and data source diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index f399dd51..1e0bc24c 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -9,6 +9,7 @@ description: |- ## 0.1.10 (unreleased) +- Add `fabric_l2_handoff` resource and data source - BREAKING CHANGE: Modified `wireless_profile` resource to use `/intent/api/v1/wirelessProfiles` API endpoint, this resource now only works with Catalyst Center version 2.3.7.6+ - Add `wireless_device_provision` resource - Add `fabric_provision_device` resource and data source diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index f399dd51..1e0bc24c 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -9,6 +9,7 @@ description: |- ## 0.1.10 (unreleased) +- Add `fabric_l2_handoff` resource and data source - BREAKING CHANGE: Modified `wireless_profile` resource to use `/intent/api/v1/wirelessProfiles` API endpoint, this resource now only works with Catalyst Center version 2.3.7.6+ - Add `wireless_device_provision` resource - Add `fabric_provision_device` resource and data source