Skip to content

Commit 9454ca3

Browse files
authored
Merge pull request #616 from NetApp/614-enhancement-support-svm-specific-import-for-netapp-ontap_security_certificate-for-avoiding-duplicate-names
added extra parameter to import to support for avoiding duplicate cer…
2 parents 29bafbd + ee43d73 commit 9454ca3

File tree

4 files changed

+82
-54
lines changed

4 files changed

+82
-54
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
ENHANCEMENTS:
44

5-
Fixed import documentation on resources which are supporting import. ([#613](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/613))
5+
- **netapp-ontap_security_certificate**: added extra parameter to import to support for avoiding duplicate cert names across svms ([#614](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/614))
66

77
BUG FIXES:
88

9+
- Fixed import documentation on resources which are supporting import. ([#613](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/613))
910
- **netapp-ontap_name_services_ldap_resource**: Fixed duplicate Set Element Error with `preferred_ad_servers` ([#615](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/615))
1011

1112
# 2.4.0 (2025-11-20)

docs/resources/security_certificate.md

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -117,45 +117,66 @@ EOT
117117

118118
## Import
119119
This resource supports import, which allows you to import existing security certificate into the state of this resource.
120-
Import require a unique ID composed of the security certificate name, common name, type and connection profile, separated by a comma or security certificate common name, type, and connection profile, separated by a comma.
121120

122-
id = `name`,`common_name`,`type`,`cx_profile_name`
121+
Import supports multiple formats to accommodate different scenarios:
122+
- **5 parts**: `name`,`common_name`,`type`,`cx_profile_name`,`svm_name` - Use when multiple SVMs have certificates with the same name/common_name
123+
- **4 parts**: `name`,`common_name`,`type`,`cx_profile_name` - Recommended for ONTAP 9.8 or later
124+
- **3 parts**: `common_name`,`type`,`cx_profile_name` - Applicable for ONTAP 9.6 or 9.7
123125

124126
### Terraform Import
125127

126-
For example
128+
For example
127129

128-
Import with certificate name; recommended for ONTAP 9.8 or later
129-
```shell
130-
terraform import netapp-ontap_security_certificate.cert_import tfsvm_ca_cert1,tfsvm_ca_cert,root_ca,cluster5
131-
```
130+
Import with SVM name (when multiple SVMs have certificates with same name/common_name):
131+
```shell
132+
terraform import netapp-ontap_security_certificate.cert_svm1 tfsvm_ca_cert1,tfsvm_ca_cert,root_ca,cluster5,carchi-test
133+
```
134+
135+
Import with certificate name; recommended for ONTAP 9.8 or later:
136+
```shell
137+
terraform import netapp-ontap_security_certificate.cert_import tfsvm_ca_cert1,tfsvm_ca_cert,root_ca,cluster5
138+
```
139+
140+
Import with certificate common name & type; applicable for ONTAP 9.6 or 9.7:
141+
```shell
142+
terraform import netapp-ontap_security_certificate.cert_import svm1_cert1,server,cluster5
143+
```
132144

133-
Import with certificate common name & type; applicable for ONTAP 9.6 or 9.7
134-
```shell
135-
terraform import netapp-ontap_security_certificate.cert_import svm1_cert1,server,cluster5
136-
```
145+
!> The terraform import CLI command can only import resources into the state. Importing via the CLI does not generate configuration. If you want to generate the accompanying configuration for imported resources, use the import block instead.
137146

138147
### Terraform Import Block
139148
This requires Terraform 1.5 or higher, and will auto create the configuration for you
140149

141150
First create the block
151+
152+
Example with SVM name (for certificates with duplicate names across SVMs):
153+
```terraform
154+
import {
155+
to = netapp-ontap_security_certificate.cert_svm1
156+
id = "tfsvm_ca_cert1,tfsvm_ca_cert,root_ca,cluster5,carchi-test"
157+
}
158+
```
159+
160+
Example with certificate name (standard approach):
142161
```terraform
143162
import {
144163
to = netapp-ontap_security_certificate.cert_import
145164
id = "tfsvm_ca_cert1,tfsvm_ca_cert,root_ca,cluster5"
146165
}
147166
```
167+
148168
Next run, this will auto create the configuration for you
149169
```shell
150170
terraform plan -generate-config-out=generated.tf
151171
```
172+
152173
This will generate a file called generated.tf, which will contain the configuration for the imported resource
153174
```terraform
154175
# __generated__ by Terraform
155176
# Please review these resources and move them into your main configuration files.
156177
157-
# __generated__ by Terraform from "tfsvm_ca_cert1,tfsvm_ca_cert,root_ca,cluster5"
158-
resource "netapp-ontap_security_certificate" "cert_import" {
178+
# __generated__ by Terraform from "tfsvm_ca_cert1,tfsvm_ca_cert,root_ca,cluster5,carchi-test"
179+
resource "netapp-ontap_security_certificate" "cert_svm1" {
159180
common_name = "tfsvm_ca_cert"
160181
cx_profile_name = "cluster5"
161182
expiry_time = "2025-10-04T01:24:54-04:00"
@@ -165,7 +186,7 @@ resource "netapp-ontap_security_certificate" "cert_import" {
165186
private_key = null # sensitive
166187
public_certificate = "-----BEGIN CERTIFICATE-----\ncertificate\n-----END CERTIFICATE-----\n"
167188
signing_request = null
168-
svm_name = "tfsvm"
189+
svm_name = "carchi-test"
169190
type = "root_ca"
170191
}
171192
```

internal/provider/security/security_certificate_resource.go

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ import (
77

88
"github.com/netapp/terraform-provider-netapp-ontap/internal/provider/connection"
99

10+
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
11+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
1012
"github.com/hashicorp/terraform-plugin-framework/path"
1113
"github.com/hashicorp/terraform-plugin-framework/resource"
1214
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
13-
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
1415
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
16+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
1517
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
1618
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
1719
"github.com/hashicorp/terraform-plugin-framework/types"
18-
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
19-
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
2020
"github.com/hashicorp/terraform-plugin-log/tflog"
2121
"github.com/netapp/terraform-provider-netapp-ontap/internal/interfaces"
2222
"github.com/netapp/terraform-provider-netapp-ontap/internal/utils"
@@ -42,23 +42,23 @@ type SecurityCertificateResource struct {
4242

4343
// SecurityCertificateResourceModel describes the resource data model.
4444
type SecurityCertificateResourceModel struct {
45-
CxProfileName types.String `tfsdk:"cx_profile_name"`
46-
Name types.String `tfsdk:"name"`
47-
CommonName types.String `tfsdk:"common_name"`
48-
Type types.String `tfsdk:"type"`
49-
SVMName types.String `tfsdk:"svm_name"`
50-
Scope types.String `tfsdk:"scope"`
51-
SerialNumber types.String `tfsdk:"serial_number"`
52-
CA types.String `tfsdk:"ca"`
53-
PublicCertificate types.String `tfsdk:"public_certificate"`
54-
SignedCertificate types.String `tfsdk:"signed_certificate"`
55-
PrivateKey types.String `tfsdk:"private_key"`
56-
SigningRequest types.String `tfsdk:"signing_request"`
57-
HashFunction types.String `tfsdk:"hash_function"`
58-
KeySize types.Int64 `tfsdk:"key_size"`
59-
ExpiryTime types.String `tfsdk:"expiry_time"`
60-
ID types.String `tfsdk:"id"`
61-
IntermediateCertificates types.List `tfsdk:"intermediate_certificates"`
45+
CxProfileName types.String `tfsdk:"cx_profile_name"`
46+
Name types.String `tfsdk:"name"`
47+
CommonName types.String `tfsdk:"common_name"`
48+
Type types.String `tfsdk:"type"`
49+
SVMName types.String `tfsdk:"svm_name"`
50+
Scope types.String `tfsdk:"scope"`
51+
SerialNumber types.String `tfsdk:"serial_number"`
52+
CA types.String `tfsdk:"ca"`
53+
PublicCertificate types.String `tfsdk:"public_certificate"`
54+
SignedCertificate types.String `tfsdk:"signed_certificate"`
55+
PrivateKey types.String `tfsdk:"private_key"`
56+
SigningRequest types.String `tfsdk:"signing_request"`
57+
HashFunction types.String `tfsdk:"hash_function"`
58+
KeySize types.Int64 `tfsdk:"key_size"`
59+
ExpiryTime types.String `tfsdk:"expiry_time"`
60+
ID types.String `tfsdk:"id"`
61+
IntermediateCertificates types.List `tfsdk:"intermediate_certificates"`
6262
}
6363

6464
// Metadata returns the resource type name.
@@ -150,7 +150,7 @@ func (r *SecurityCertificateResource) Schema(ctx context.Context, req resource.S
150150
"intermediate_certificates": schema.ListAttribute{
151151
MarkdownDescription: "Chain of intermediate Certificates in PEM format. Only valid in POST when installing a certificate.",
152152
Optional: true,
153-
ElementType: types.StringType,
153+
ElementType: types.StringType,
154154
},
155155
"hash_function": schema.StringAttribute{
156156
MarkdownDescription: "Hashing function.",
@@ -294,7 +294,7 @@ func (r *SecurityCertificateResource) Create(ctx context.Context, req resource.C
294294
if resp.Diagnostics.HasError() {
295295
return
296296
}
297-
297+
298298
errorHandler := utils.NewErrorHandler(ctx, &resp.Diagnostics)
299299
client, err := connection.GetRestClient(errorHandler, r.config, data.CxProfileName)
300300
if err != nil {
@@ -311,7 +311,7 @@ func (r *SecurityCertificateResource) Create(ctx context.Context, req resource.C
311311
// error reporting done inside GetCluster
312312
return
313313
}
314-
314+
315315
name_supported := false
316316
if !data.Name.IsNull() && data.Name.ValueString() != "" {
317317
if cluster.Version.Generation == 9 && cluster.Version.Major < 8 {
@@ -355,7 +355,7 @@ func (r *SecurityCertificateResource) Create(ctx context.Context, req resource.C
355355
if !data.ExpiryTime.IsUnknown() {
356356
body.ExpiryTime = data.ExpiryTime.ValueString()
357357
}
358-
358+
359359
resource, err := interfaces.SignSecurityCertificate(errorHandler, *client, restInfo.UUID, body)
360360
if err != nil {
361361
// error reporting done inside SignSecurityCertificate
@@ -364,13 +364,13 @@ func (r *SecurityCertificateResource) Create(ctx context.Context, req resource.C
364364

365365
// Save public_certificate returned while signing certificate into Terraform state
366366
data.SignedCertificate = types.StringValue(resource.SignedCertificate)
367-
367+
368368
tflog.Trace(ctx, "signed a resource")
369369
} else {
370370
// This else block is for creating or installing security certificate
371371
var body interfaces.SecurityCertificateResourceCreateBodyDataModelONTAP
372372

373-
if !data.Name.IsNull() && data.Name.ValueString() != "" {
373+
if !data.Name.IsNull() && data.Name.ValueString() != "" {
374374
if name_supported {
375375
body.Name = data.Name.ValueString()
376376
}
@@ -502,6 +502,16 @@ func (r *SecurityCertificateResource) ImportState(ctx context.Context, req resou
502502
// Parse the ID
503503
idParts := strings.Split(req.ID, ",")
504504

505+
// import name, common_name, type, cx_profile, and svm_name (for SVM-specific certificates with duplicate names)
506+
if len(idParts) == 5 {
507+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idParts[0])...)
508+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("common_name"), idParts[1])...)
509+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("type"), idParts[2])...)
510+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("cx_profile_name"), idParts[3])...)
511+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("svm_name"), idParts[4])...)
512+
return
513+
}
514+
505515
// import name, common_name, type and cx_profile
506516
if len(idParts) == 4 {
507517
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idParts[0])...)
@@ -521,6 +531,6 @@ func (r *SecurityCertificateResource) ImportState(ctx context.Context, req resou
521531

522532
resp.Diagnostics.AddError(
523533
"Unexpected Import Identifier",
524-
fmt.Sprintf("Expected import identifier with format: name,common_name,type,cx_profile_name or common_name,type,cx_profile_name. Got: %q", req.ID),
534+
fmt.Sprintf("Expected import identifier with format: name,common_name,type,cx_profile_name,svm_name or name,common_name,type,cx_profile_name or common_name,type,cx_profile_name. Got: %q", req.ID),
525535
)
526536
}

internal/provider/security/security_certificate_resource_test.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,14 @@ import (
1111
)
1212

1313
func TestAccSecurityCertificateResource(t *testing.T) {
14-
t.Setenv("TF_ACC_NETAPP_HOST_CIFS", "10.196.21.226")
15-
t.Setenv("TF_ACC_NETAPP_USER", "admin")
16-
t.Setenv("TF_ACC_NETAPP_PASS2", "netapp1!")
1714
resource.Test(t, resource.TestCase{
1815
PreCheck: func() { ntest.TestAccPreCheck(t) },
1916
ProtoV6ProviderFactories: ntest.TestAccProtoV6ProviderFactories,
2017
Steps: []resource.TestStep{
2118
// Create security certificate and read
2219
{
2320
Config: testAccSecurityCertificateResourceCertificateConfig(),
24-
Check: resource.ComposeTestCheckFunc(
21+
Check: resource.ComposeTestCheckFunc(
2522
resource.TestCheckResourceAttr("netapp-ontap_security_certificate.example", "name", "acc_test_ca_cert2"),
2623
),
2724
},
@@ -30,7 +27,7 @@ func TestAccSecurityCertificateResource(t *testing.T) {
3027
ResourceName: "netapp-ontap_security_certificate.example",
3128
ImportState: true,
3229
ImportStateId: fmt.Sprintf("%s,%s,%s,%s", "acc_test_ca_cert2", "acc_test_ca_cert", "server", "cluster1"),
33-
Check: resource.ComposeTestCheckFunc(
30+
Check: resource.ComposeTestCheckFunc(
3431
resource.TestCheckResourceAttr("netapp-ontap_security_certificate.example", "name", "acc_test_ca_cert2"),
3532
),
3633
},
@@ -39,7 +36,7 @@ func TestAccSecurityCertificateResource(t *testing.T) {
3936
// Update security certificate and read
4037
{
4138
Config: testAccSecurityCertificateResourceCertificateConfig(),
42-
Check: resource.ComposeTestCheckFunc(
39+
Check: resource.ComposeTestCheckFunc(
4340
resource.TestCheckResourceAttr("netapp-ontap_security_certificate.example", "name", "acc_test_ca_cert2"),
4441
resource.TestCheckResourceAttr("netapp-ontap_security_certificate.example", "intermediate_certificates.#", "3"),
4542
),
@@ -49,21 +46,20 @@ func TestAccSecurityCertificateResource(t *testing.T) {
4946
ResourceName: "netapp-ontap_security_certificate.example",
5047
ImportState: true,
5148
ImportStateId: fmt.Sprintf("%s,%s,%s,%s", "acc_test_ca_cert2", "acc_test_ca_cert", "server", "cluster1"),
52-
Check: resource.ComposeTestCheckFunc(
49+
Check: resource.ComposeTestCheckFunc(
5350
resource.TestCheckResourceAttr("netapp-ontap_security_certificate.example", "name", "acc_test_ca_cert2"),
5451
),
5552
},
56-
5753
},
5854
})
5955
}
6056

6157
func testAccSecurityCertificateResourceCertificateConfig() string {
62-
host := os.Getenv("TF_ACC_NETAPP_HOST_CIFS")
58+
host := os.Getenv("TF_ACC_NETAPP_HOST")
6359
admin := os.Getenv("TF_ACC_NETAPP_USER")
64-
password := os.Getenv("TF_ACC_NETAPP_PASS2")
60+
password := os.Getenv("TF_ACC_NETAPP_PASS")
6561
if host == "" || admin == "" || password == "" {
66-
fmt.Println("TF_ACC_NETAPP_HOST_CIFS, TF_ACC_NETAPP_USER, and TF_ACC_NETAPP_PASS2 must be set for acceptance tests")
62+
fmt.Println("TF_ACC_NETAPP_HOST, TF_ACC_NETAPP_USER, and TF_ACC_NETAPP_PASS must be set for acceptance tests")
6763
os.Exit(1)
6864
}
6965
return fmt.Sprintf(`
@@ -84,7 +80,7 @@ resource "netapp-ontap_security_certificate" "example" {
8480
name = "acc_test_ca_cert2"
8581
common_name = "acc_test_ca_cert"
8682
type = "server"
87-
svm_name = "acc_test"
83+
svm_name = "tf_acc_svm"
8884
intermediate_certificates = [
8985
"-----BEGIN CERTIFICATE-----\n INTERMEDIATE CERTIFICATE \n-----END CERTIFICATE-----",
9086
"-----BEGIN CERTIFICATE-----\n INTERMEDIATE CERTIFICATE \n-----END CERTIFICATE-----",

0 commit comments

Comments
 (0)