From c95fc8e922ad75f15f9d58ebcb7c1e37b2600a3f Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Fri, 19 Jul 2024 14:43:56 +0200 Subject: [PATCH 01/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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 df90953d583717848d87d9636e07a3619c0ec989 Mon Sep 17 00:00:00 2001 From: kuba-mazurkiewicz Date: Mon, 5 Aug 2024 12:14:42 +0200 Subject: [PATCH 18/19] 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 19/19] 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{