Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

ENHANCEMENTS:

Fixed import documentation on resources which are supporting import. ([#613](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/613))
- **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))

BUG FIXES:

- Fixed import documentation on resources which are supporting import. ([#613](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/613))
- **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))

# 2.4.0 (2025-11-20)
Expand Down
49 changes: 35 additions & 14 deletions docs/resources/security_certificate.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,45 +117,66 @@ EOT

## Import
This resource supports import, which allows you to import existing security certificate into the state of this resource.
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.

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

### Terraform Import

For example
For example

Import with certificate name; recommended for ONTAP 9.8 or later
```shell
terraform import netapp-ontap_security_certificate.cert_import tfsvm_ca_cert1,tfsvm_ca_cert,root_ca,cluster5
```
Import with SVM name (when multiple SVMs have certificates with same name/common_name):
```shell
terraform import netapp-ontap_security_certificate.cert_svm1 tfsvm_ca_cert1,tfsvm_ca_cert,root_ca,cluster5,carchi-test
```

Import with certificate name; recommended for ONTAP 9.8 or later:
```shell
terraform import netapp-ontap_security_certificate.cert_import tfsvm_ca_cert1,tfsvm_ca_cert,root_ca,cluster5
```

Import with certificate common name & type; applicable for ONTAP 9.6 or 9.7:
```shell
terraform import netapp-ontap_security_certificate.cert_import svm1_cert1,server,cluster5
```

Import with certificate common name & type; applicable for ONTAP 9.6 or 9.7
```shell
terraform import netapp-ontap_security_certificate.cert_import svm1_cert1,server,cluster5
```
!> 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.

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

First create the block

Example with SVM name (for certificates with duplicate names across SVMs):
```terraform
import {
to = netapp-ontap_security_certificate.cert_svm1
id = "tfsvm_ca_cert1,tfsvm_ca_cert,root_ca,cluster5,carchi-test"
}
```

Example with certificate name (standard approach):
```terraform
import {
to = netapp-ontap_security_certificate.cert_import
id = "tfsvm_ca_cert1,tfsvm_ca_cert,root_ca,cluster5"
}
```

Next run, this will auto create the configuration for you
```shell
terraform plan -generate-config-out=generated.tf
```

This will generate a file called generated.tf, which will contain the configuration for the imported resource
```terraform
# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.

# __generated__ by Terraform from "tfsvm_ca_cert1,tfsvm_ca_cert,root_ca,cluster5"
resource "netapp-ontap_security_certificate" "cert_import" {
# __generated__ by Terraform from "tfsvm_ca_cert1,tfsvm_ca_cert,root_ca,cluster5,carchi-test"
resource "netapp-ontap_security_certificate" "cert_svm1" {
common_name = "tfsvm_ca_cert"
cx_profile_name = "cluster5"
expiry_time = "2025-10-04T01:24:54-04:00"
Expand All @@ -165,7 +186,7 @@ resource "netapp-ontap_security_certificate" "cert_import" {
private_key = null # sensitive
public_certificate = "-----BEGIN CERTIFICATE-----\ncertificate\n-----END CERTIFICATE-----\n"
signing_request = null
svm_name = "tfsvm"
svm_name = "carchi-test"
type = "root_ca"
}
```
64 changes: 37 additions & 27 deletions internal/provider/security/security_certificate_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ import (

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

"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/planmodifier"
"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-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/netapp/terraform-provider-netapp-ontap/internal/interfaces"
"github.com/netapp/terraform-provider-netapp-ontap/internal/utils"
Expand All @@ -42,23 +42,23 @@ type SecurityCertificateResource struct {

// SecurityCertificateResourceModel describes the resource data model.
type SecurityCertificateResourceModel struct {
CxProfileName types.String `tfsdk:"cx_profile_name"`
Name types.String `tfsdk:"name"`
CommonName types.String `tfsdk:"common_name"`
Type types.String `tfsdk:"type"`
SVMName types.String `tfsdk:"svm_name"`
Scope types.String `tfsdk:"scope"`
SerialNumber types.String `tfsdk:"serial_number"`
CA types.String `tfsdk:"ca"`
PublicCertificate types.String `tfsdk:"public_certificate"`
SignedCertificate types.String `tfsdk:"signed_certificate"`
PrivateKey types.String `tfsdk:"private_key"`
SigningRequest types.String `tfsdk:"signing_request"`
HashFunction types.String `tfsdk:"hash_function"`
KeySize types.Int64 `tfsdk:"key_size"`
ExpiryTime types.String `tfsdk:"expiry_time"`
ID types.String `tfsdk:"id"`
IntermediateCertificates types.List `tfsdk:"intermediate_certificates"`
CxProfileName types.String `tfsdk:"cx_profile_name"`
Name types.String `tfsdk:"name"`
CommonName types.String `tfsdk:"common_name"`
Type types.String `tfsdk:"type"`
SVMName types.String `tfsdk:"svm_name"`
Scope types.String `tfsdk:"scope"`
SerialNumber types.String `tfsdk:"serial_number"`
CA types.String `tfsdk:"ca"`
PublicCertificate types.String `tfsdk:"public_certificate"`
SignedCertificate types.String `tfsdk:"signed_certificate"`
PrivateKey types.String `tfsdk:"private_key"`
SigningRequest types.String `tfsdk:"signing_request"`
HashFunction types.String `tfsdk:"hash_function"`
KeySize types.Int64 `tfsdk:"key_size"`
ExpiryTime types.String `tfsdk:"expiry_time"`
ID types.String `tfsdk:"id"`
IntermediateCertificates types.List `tfsdk:"intermediate_certificates"`
}

// Metadata returns the resource type name.
Expand Down Expand Up @@ -150,7 +150,7 @@ func (r *SecurityCertificateResource) Schema(ctx context.Context, req resource.S
"intermediate_certificates": schema.ListAttribute{
MarkdownDescription: "Chain of intermediate Certificates in PEM format. Only valid in POST when installing a certificate.",
Optional: true,
ElementType: types.StringType,
ElementType: types.StringType,
},
"hash_function": schema.StringAttribute{
MarkdownDescription: "Hashing function.",
Expand Down Expand Up @@ -294,7 +294,7 @@ func (r *SecurityCertificateResource) Create(ctx context.Context, req resource.C
if resp.Diagnostics.HasError() {
return
}

errorHandler := utils.NewErrorHandler(ctx, &resp.Diagnostics)
client, err := connection.GetRestClient(errorHandler, r.config, data.CxProfileName)
if err != nil {
Expand All @@ -311,7 +311,7 @@ func (r *SecurityCertificateResource) Create(ctx context.Context, req resource.C
// error reporting done inside GetCluster
return
}

name_supported := false
if !data.Name.IsNull() && data.Name.ValueString() != "" {
if cluster.Version.Generation == 9 && cluster.Version.Major < 8 {
Expand Down Expand Up @@ -355,7 +355,7 @@ func (r *SecurityCertificateResource) Create(ctx context.Context, req resource.C
if !data.ExpiryTime.IsUnknown() {
body.ExpiryTime = data.ExpiryTime.ValueString()
}

resource, err := interfaces.SignSecurityCertificate(errorHandler, *client, restInfo.UUID, body)
if err != nil {
// error reporting done inside SignSecurityCertificate
Expand All @@ -364,13 +364,13 @@ func (r *SecurityCertificateResource) Create(ctx context.Context, req resource.C

// Save public_certificate returned while signing certificate into Terraform state
data.SignedCertificate = types.StringValue(resource.SignedCertificate)

tflog.Trace(ctx, "signed a resource")
} else {
// This else block is for creating or installing security certificate
var body interfaces.SecurityCertificateResourceCreateBodyDataModelONTAP

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

// import name, common_name, type, cx_profile, and svm_name (for SVM-specific certificates with duplicate names)
if len(idParts) == 5 {
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("common_name"), idParts[1])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("type"), idParts[2])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("cx_profile_name"), idParts[3])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("svm_name"), idParts[4])...)
return
}

// import name, common_name, type and cx_profile
if len(idParts) == 4 {
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idParts[0])...)
Expand All @@ -521,6 +531,6 @@ func (r *SecurityCertificateResource) ImportState(ctx context.Context, req resou

resp.Diagnostics.AddError(
"Unexpected Import Identifier",
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),
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),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,14 @@ import (
)

func TestAccSecurityCertificateResource(t *testing.T) {
t.Setenv("TF_ACC_NETAPP_HOST_CIFS", "10.196.21.226")
t.Setenv("TF_ACC_NETAPP_USER", "admin")
t.Setenv("TF_ACC_NETAPP_PASS2", "netapp1!")
resource.Test(t, resource.TestCase{
PreCheck: func() { ntest.TestAccPreCheck(t) },
ProtoV6ProviderFactories: ntest.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create security certificate and read
{
Config: testAccSecurityCertificateResourceCertificateConfig(),
Check: resource.ComposeTestCheckFunc(
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("netapp-ontap_security_certificate.example", "name", "acc_test_ca_cert2"),
),
},
Expand All @@ -30,7 +27,7 @@ func TestAccSecurityCertificateResource(t *testing.T) {
ResourceName: "netapp-ontap_security_certificate.example",
ImportState: true,
ImportStateId: fmt.Sprintf("%s,%s,%s,%s", "acc_test_ca_cert2", "acc_test_ca_cert", "server", "cluster1"),
Check: resource.ComposeTestCheckFunc(
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("netapp-ontap_security_certificate.example", "name", "acc_test_ca_cert2"),
),
},
Expand All @@ -39,7 +36,7 @@ func TestAccSecurityCertificateResource(t *testing.T) {
// Update security certificate and read
{
Config: testAccSecurityCertificateResourceCertificateConfig(),
Check: resource.ComposeTestCheckFunc(
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("netapp-ontap_security_certificate.example", "name", "acc_test_ca_cert2"),
resource.TestCheckResourceAttr("netapp-ontap_security_certificate.example", "intermediate_certificates.#", "3"),
),
Expand All @@ -49,21 +46,20 @@ func TestAccSecurityCertificateResource(t *testing.T) {
ResourceName: "netapp-ontap_security_certificate.example",
ImportState: true,
ImportStateId: fmt.Sprintf("%s,%s,%s,%s", "acc_test_ca_cert2", "acc_test_ca_cert", "server", "cluster1"),
Check: resource.ComposeTestCheckFunc(
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("netapp-ontap_security_certificate.example", "name", "acc_test_ca_cert2"),
),
},

},
})
}

func testAccSecurityCertificateResourceCertificateConfig() string {
host := os.Getenv("TF_ACC_NETAPP_HOST_CIFS")
host := os.Getenv("TF_ACC_NETAPP_HOST")
admin := os.Getenv("TF_ACC_NETAPP_USER")
password := os.Getenv("TF_ACC_NETAPP_PASS2")
password := os.Getenv("TF_ACC_NETAPP_PASS")
if host == "" || admin == "" || password == "" {
fmt.Println("TF_ACC_NETAPP_HOST_CIFS, TF_ACC_NETAPP_USER, and TF_ACC_NETAPP_PASS2 must be set for acceptance tests")
fmt.Println("TF_ACC_NETAPP_HOST, TF_ACC_NETAPP_USER, and TF_ACC_NETAPP_PASS must be set for acceptance tests")
os.Exit(1)
}
return fmt.Sprintf(`
Expand All @@ -84,7 +80,7 @@ resource "netapp-ontap_security_certificate" "example" {
name = "acc_test_ca_cert2"
common_name = "acc_test_ca_cert"
type = "server"
svm_name = "acc_test"
svm_name = "tf_acc_svm"
intermediate_certificates = [
"-----BEGIN CERTIFICATE-----\n INTERMEDIATE CERTIFICATE \n-----END CERTIFICATE-----",
"-----BEGIN CERTIFICATE-----\n INTERMEDIATE CERTIFICATE \n-----END CERTIFICATE-----",
Expand Down
Loading