diff --git a/.changelog/575.txt b/.changelog/575.txt new file mode 100644 index 000000000..dc306ee4b --- /dev/null +++ b/.changelog/575.txt @@ -0,0 +1,3 @@ +```release-note:improvement +Add `vault_plugin` resource as optional subresource for `hcp_vault_cluster` +``` diff --git a/docs/data-sources/vault_cluster.md b/docs/data-sources/vault_cluster.md index 03e16b1cf..478458924 100644 --- a/docs/data-sources/vault_cluster.md +++ b/docs/data-sources/vault_cluster.md @@ -31,6 +31,7 @@ data "hcp_vault_cluster" "example" { If not specified, the project specified in the HCP Provider config block will be used, if configured. If a project is not configured in the HCP Provider config block, the oldest project in the organization will be used. - `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) +- `vault_plugin` (Block List) The external plugins to install on the vault cluster (see [below for nested schema](#nestedblock--vault_plugin)) ### Read-Only @@ -63,6 +64,15 @@ Optional: - `default` (String) + +### Nested Schema for `vault_plugin` + +Required: + +- `plugin_name` (String) The name of the plugin +- `plugin_type` (String) The type of the plugin + + ### Nested Schema for `audit_log_config` diff --git a/docs/resources/vault_cluster.md b/docs/resources/vault_cluster.md index 18ca644ee..b3f0d8cd4 100644 --- a/docs/resources/vault_cluster.md +++ b/docs/resources/vault_cluster.md @@ -61,6 +61,7 @@ If a project is not configured in the HCP Provider config block, the oldest proj - `public_endpoint` (Boolean) Denotes that the cluster has a public endpoint. Defaults to false. - `tier` (String) Tier of the HCP Vault cluster. Valid options for tiers - `dev`, `starter_small`, `standard_small`, `standard_medium`, `standard_large`, `plus_small`, `plus_medium`, `plus_large`. See [pricing information](https://www.hashicorp.com/products/vault/pricing). Changing a cluster's size or tier is only available to admins. See [Scale a cluster](https://registry.terraform.io/providers/hashicorp/hcp/latest/docs/guides/vault-scaling). - `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) +- `vault_plugin` (Block List) The external plugins to install on the vault cluster (see [below for nested schema](#nestedblock--vault_plugin)) ### Read-Only @@ -127,6 +128,15 @@ Optional: - `delete` (String) - `update` (String) + + +### Nested Schema for `vault_plugin` + +Required: + +- `plugin_name` (String) The name of the plugin - Valid options for plugin name - 'venafi-pki-backend' +- `plugin_type` (String) The type of the plugin - Valid options for plugin type - 'SECRET', 'AUTH', 'DATABASE' + -> **Note:** When establishing performance replication links between clusters in different HVNs, an HVN peering connection is required. This can be defined explicitly using an [`hcp_hvn_peering_connection`](hvn_peering_connection.md), or HCP will create the connection automatically (peering connections can be imported after creation using [terraform import](https://www.terraform.io/cli/import)). Note HVN peering [CIDR block requirements](https://cloud.hashicorp.com/docs/hcp/network/routes#cidr-block-requirements). ## Import diff --git a/internal/clients/vault_cluster.go b/internal/clients/vault_cluster.go index ea8c5444d..f8654d272 100644 --- a/internal/clients/vault_cluster.go +++ b/internal/clients/vault_cluster.go @@ -273,3 +273,92 @@ func DeleteVaultPathsFilter(ctx context.Context, client *Client, loc *sharedmode return deleteResp.Payload, nil } + +// AddPlugin will make a call to the Vault service to add a plugin to a Vault cluster +func AddPlugin(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, clusterID string, + request *vaultmodels.HashicorpCloudVault20201125AddPluginRequest) (vaultmodels.HashicorpCloudVault20201125AddPluginResponse, error) { + + region := &sharedmodels.HashicorpCloudLocationRegion{} + if loc.Region != nil { + region = loc.Region + } + locInternal := &vaultmodels.HashicorpCloudInternalLocationLocation{ + OrganizationID: loc.OrganizationID, + ProjectID: loc.ProjectID, + Region: &vaultmodels.HashicorpCloudInternalLocationRegion{ + Provider: region.Provider, + Region: region.Region, + }, + } + request.Location = locInternal + request.ClusterID = clusterID + addPluginParams := vault_service.NewAddPluginParams() + addPluginParams.Context = ctx + addPluginParams.ClusterID = clusterID + addPluginParams.LocationProjectID = loc.ProjectID + addPluginParams.LocationOrganizationID = loc.OrganizationID + addPluginParams.Body = request + + addPluginResp, err := client.Vault.AddPlugin(addPluginParams, nil) + if err != nil { + return nil, err + } + + return addPluginResp.Payload, nil +} + +// DeletePlugin will make a call to the Vault service to remove a plugin to a Vault cluster +func DeletePlugin(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, clusterID string, + request *vaultmodels.HashicorpCloudVault20201125DeletePluginRequest) (vaultmodels.HashicorpCloudVault20201125DeletePluginResponse, error) { + + region := &sharedmodels.HashicorpCloudLocationRegion{} + if loc.Region != nil { + region = loc.Region + } + locInternal := &vaultmodels.HashicorpCloudInternalLocationLocation{ + OrganizationID: loc.OrganizationID, + ProjectID: loc.ProjectID, + Region: &vaultmodels.HashicorpCloudInternalLocationRegion{ + Provider: region.Provider, + Region: region.Region, + }, + } + request.Location = locInternal + request.ClusterID = clusterID + delPluginParams := vault_service.NewDeletePluginParams() + delPluginParams.Context = ctx + delPluginParams.ClusterID = clusterID + delPluginParams.LocationProjectID = loc.ProjectID + delPluginParams.LocationOrganizationID = loc.OrganizationID + delPluginParams.Body = request + + delPluginResp, err := client.Vault.DeletePlugin(delPluginParams, nil) + if err != nil { + return nil, err + } + + return delPluginResp.Payload, nil +} + +// ListPlugins will make a call to the Vault service plugin status api to get all available plugins for the cluster. +func ListPlugins(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, clusterID string) (*vaultmodels.HashicorpCloudVault20201125PluginRegistrationStatusResponse, error) { + region := &sharedmodels.HashicorpCloudLocationRegion{} + if loc.Region != nil { + region = loc.Region + } + + listPluginsParams := vault_service.NewPluginRegistrationStatusParams() + listPluginsParams.Context = ctx + listPluginsParams.ClusterID = clusterID + listPluginsParams.LocationProjectID = loc.ProjectID + listPluginsParams.LocationOrganizationID = loc.OrganizationID + listPluginsParams.LocationRegionProvider = ®ion.Provider + listPluginsParams.LocationRegionRegion = ®ion.Region + + listPluginsResp, err := client.Vault.PluginRegistrationStatus(listPluginsParams, nil) + if err != nil { + return nil, err + } + + return listPluginsResp.Payload, nil +} diff --git a/internal/provider/data_source_vault_cluster.go b/internal/provider/data_source_vault_cluster.go index 768a22b46..ae911fbb9 100644 --- a/internal/provider/data_source_vault_cluster.go +++ b/internal/provider/data_source_vault_cluster.go @@ -208,6 +208,26 @@ If a project is not configured in the HCP Provider config block, the oldest proj }, }, }, + "vault_plugin": { + Description: "The external plugins to install on the vault cluster", + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "plugin_name": { + Description: "The name of the plugin", + Type: schema.TypeString, + Required: true, + }, + "plugin_type": { + Description: "The type of the plugin", + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, }, } } @@ -242,8 +262,14 @@ func dataSourceVaultClusterRead(ctx context.Context, d *schema.ResourceData, met d.SetId(url) + plugins, err := clients.ListPlugins(ctx, client, loc, clusterID) + if err != nil { + log.Printf("[ERROR] Vault cluster (%s) failed to list plugins", clusterID) + return diag.FromErr(err) + } + // Cluster found, update resource data. - if err := setVaultClusterResourceData(d, cluster); err != nil { + if err := setVaultClusterResourceData(d, cluster, plugins.Plugins); err != nil { return diag.FromErr(err) } diff --git a/internal/provider/resource_vault_cluster.go b/internal/provider/resource_vault_cluster.go index 610aeb9fe..3ef09a69c 100644 --- a/internal/provider/resource_vault_cluster.go +++ b/internal/provider/resource_vault_cluster.go @@ -276,6 +276,37 @@ If a project is not configured in the HCP Provider config block, the oldest proj }, }, }, + // vault_plugin is a terraform resource used to specify a plugin for installation. + // + // plugin_name is only validated on updates because the PluginStatus API to list the valid available plugin names requires a created cluster. + // plugin_type is validated on create & update because there is a static list for valid plugint types. + "vault_plugin": { + Description: "The external plugins to install on the vault cluster", + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "plugin_name": { + Description: "The name of the plugin - Valid options for plugin name - 'venafi-pki-backend'", + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: func(_, old, new string, _ *schema.ResourceData) bool { + return strings.EqualFold(old, new) + }, + }, + "plugin_type": { + Description: "The type of the plugin - Valid options for plugin type - 'SECRET', 'AUTH', 'DATABASE'", + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: validateVaultPluginType, + DiffSuppressFunc: func(_, old, new string, _ *schema.ResourceData) bool { + return strings.EqualFold(old, new) + }, + }, + }, + }, + }, "vault_public_endpoint_url": { Description: "The public URL for the Vault cluster. This will be empty if `public_endpoint` is `false`.", Type: schema.TypeString, @@ -306,7 +337,6 @@ If a project is not configured in the HCP Provider config block, the oldest proj } func resourceVaultClusterCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.Client) clusterID := d.Get("cluster_id").(string) @@ -335,6 +365,11 @@ func resourceVaultClusterCreate(ctx context.Context, d *schema.ResourceData, met return diagErr } + pluginConfig, diagErr := getPluginConfig(d, nil) + if diagErr != nil { + return diagErr + } + // Use the hvn to get provider and region. hvn, err := clients.GetHvnByID(ctx, client, loc, hvnID) if err != nil { @@ -506,7 +541,6 @@ func resourceVaultClusterCreate(ctx context.Context, d *schema.ResourceData, met // If we pass the major version upgrade configuration we need to update it after the creation of the cluster, // since the cluster is created by default to automatic upgrade if mvuConfig != nil { - _, err := clients.UpdateVaultMajorVersionUpgradeConfig(ctx, client, clusterLocationShared, payload.ClusterID, mvuConfig) if err != nil { return diag.Errorf("error updating Vault cluster major version upgrade config (%s): %v", payload.ClusterID, err) @@ -519,7 +553,21 @@ func resourceVaultClusterCreate(ctx context.Context, d *schema.ResourceData, met } } - if err := setVaultClusterResourceData(d, cluster); err != nil { + // add plugins to cluster after cluster creation + for _, plugin := range pluginConfig { + _, err := clients.AddPlugin(ctx, client, clusterLocationShared, payload.ClusterID, plugin) + if err != nil { + return diag.Errorf("error adding plugin (%s) to Vault cluster (%s): %v", plugin.PluginName, payload.ClusterID, err) + } + } + + plugins, err := clients.ListPlugins(ctx, client, loc, clusterID) + if err != nil { + log.Printf("[ERROR] Vault cluster (%s) failed to list plugins", clusterID) + return diag.FromErr(err) + } + + if err := setVaultClusterResourceData(d, cluster, plugins.Plugins); err != nil { return diag.FromErr(err) } @@ -557,8 +605,14 @@ func resourceVaultClusterRead(ctx context.Context, d *schema.ResourceData, meta return nil } + plugins, err := clients.ListPlugins(ctx, client, loc, clusterID) + if err != nil { + log.Printf("[ERROR] Vault cluster (%s) failed to list plugins", clusterID) + return diag.FromErr(err) + } + // Cluster found, update resource data. - if err := setVaultClusterResourceData(d, cluster); err != nil { + if err := setVaultClusterResourceData(d, cluster, plugins.Plugins); err != nil { return diag.FromErr(err) } @@ -610,6 +664,18 @@ func resourceVaultClusterUpdate(ctx context.Context, d *schema.ResourceData, met return diagErr } + // get list of plugins for plugin-name validation in getPluginConfig + plugins, err := clients.ListPlugins(ctx, client, loc, clusterID) + if err != nil { + log.Printf("[ERROR] Vault cluster (%s) failed to list plugins", clusterID) + return diag.FromErr(err) + } + + newPluginConfig, diagErr := getPluginConfig(d, plugins.Plugins) + if diagErr != nil { + return diagErr + } + if d.HasChange("tier") || d.HasChange("metrics_config") || d.HasChange("audit_log_config") { diagErr := updateVaultClusterConfig(ctx, client, d, cluster, clusterID) if diagErr != nil { @@ -680,7 +746,55 @@ func resourceVaultClusterUpdate(ctx context.Context, d *schema.ResourceData, met return diag.Errorf("unable to retrieve Vault cluster (%s): %v", clusterID, err) } - if err := setVaultClusterResourceData(d, cluster); err != nil { + // on update, delete plugins that were removed from plugin config. Add all plugins in new config + if d.HasChange("vault_plugin") { + old, _ := d.GetChange("vault_plugin") + + oldPlugins := old.([]interface{}) + + for _, oldP := range oldPlugins { + config, ok := oldP.(map[string]interface{}) + if !ok { + return diag.Errorf("could not parse old plugin config: %v", err) + } + + pluginName := config["plugin_name"].(string) + pluginType := config["plugin_type"].(string) + + // if plugin in old config is not found in the new config, delete the plugin + found := false + for _, plugin := range newPluginConfig { + if strings.EqualFold(pluginName, plugin.PluginName) && strings.EqualFold(pluginType, plugin.PluginType) { + found = true + } + } + + if !found { + req := &vaultmodels.HashicorpCloudVault20201125DeletePluginRequest{PluginName: pluginName, PluginType: pluginType} + _, err := clients.DeletePlugin(ctx, client, clusterLocationShared, clusterID, req) + if err != nil { + return diag.Errorf("error deleting plugin (%s) on Vault cluster (%s): %v", pluginName, clusterID, err) + } + } + + } + + // add all plugins in new plugin config + for _, plugin := range newPluginConfig { + _, err := clients.AddPlugin(ctx, client, clusterLocationShared, clusterID, plugin) + if err != nil { + return diag.Errorf("error adding plugin (%s) to Vault cluster (%s): %v", plugin.PluginName, clusterID, err) + } + } + } + + plugins, err = clients.ListPlugins(ctx, client, loc, clusterID) + if err != nil { + log.Printf("[ERROR] Vault cluster (%s) failed to list plugins", clusterID) + return diag.FromErr(err) + } + + if err := setVaultClusterResourceData(d, cluster, plugins.Plugins); err != nil { return diag.FromErr(err) } @@ -807,8 +921,7 @@ func getClusterTier(d *schema.ResourceData) *string { } // setVaultClusterResourceData sets the KV pairs of the Vault cluster resource schema. -func setVaultClusterResourceData(d *schema.ResourceData, cluster *vaultmodels.HashicorpCloudVault20201125Cluster) error { - +func setVaultClusterResourceData(d *schema.ResourceData, cluster *vaultmodels.HashicorpCloudVault20201125Cluster, plugins []*vaultmodels.HashicorpCloudVault20201125PluginRegistrationStatus) error { if err := d.Set("cluster_id", cluster.ID); err != nil { return err } @@ -937,6 +1050,20 @@ func setVaultClusterResourceData(d *schema.ResourceData, cluster *vaultmodels.Ha } } + var pluginConfig []map[string]any + for _, plugin := range plugins { + if plugin.IsRegistered { + pluginMap := map[string]any{} + pluginMap["plugin_name"] = plugin.PluginName + pluginMap["plugin_type"] = plugin.PluginType + pluginConfig = append(pluginConfig, pluginMap) + } + if err = d.Set("vault_plugin", pluginConfig); err != nil { + return err + } + + } + return nil } @@ -1150,6 +1277,50 @@ func flattenMajorVersionUpgradeConfig(config *vaultmodels.HashicorpCloudVault202 return []interface{}{configMap} } +func getPluginConfig(d *schema.ResourceData, plugins []*vaultmodels.HashicorpCloudVault20201125PluginRegistrationStatus) ([]*vaultmodels.HashicorpCloudVault20201125AddPluginRequest, diag.Diagnostics) { + if !d.HasChange("vault_plugin") { + return nil, nil + } + configParam, ok := d.GetOk("vault_plugin") + if !ok { + return nil, nil + } + + configIfaceArr, ok := configParam.([]interface{}) + if !ok || len(configIfaceArr) == 0 { + return nil, nil + } + + if !ok || len(configIfaceArr) == 0 { + return nil, nil + } + + var pluginConfigs []*vaultmodels.HashicorpCloudVault20201125AddPluginRequest + + for _, plugin := range configIfaceArr { + config, ok := plugin.(map[string]interface{}) + if !ok { + return nil, nil + } + pluginName := config["plugin_name"].(string) + pluginType := config["plugin_type"].(string) + + if plugins != nil { + err := validateVaultPluginName(pluginName, pluginType, plugins) + if err != nil { + return nil, err + } + } + + pluginConfigs = append(pluginConfigs, &vaultmodels.HashicorpCloudVault20201125AddPluginRequest{ + PluginName: pluginName, + PluginType: pluginType, + }) + } + + return pluginConfigs, nil +} + func resourceVaultClusterImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { // with multi-projects, import arguments must become dynamic: // use explicit project ID with terraform import: diff --git a/internal/provider/resource_vault_cluster_const_test.go b/internal/provider/resource_vault_cluster_const_test.go index b6dae1070..dcc4d9136 100644 --- a/internal/provider/resource_vault_cluster_const_test.go +++ b/internal/provider/resource_vault_cluster_const_test.go @@ -48,6 +48,10 @@ resource "hcp_vault_cluster" "test" { major_version_upgrade_config { upgrade_type = "MANUAL" } + vault_plugin { + plugin_type = "SECRET" + plugin_name = "venafi-pki-backend" + } } ` diff --git a/internal/provider/resource_vault_cluster_test.go b/internal/provider/resource_vault_cluster_test.go index d80ffd8b8..4e0826f57 100644 --- a/internal/provider/resource_vault_cluster_test.go +++ b/internal/provider/resource_vault_cluster_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-hcp/internal/clients" ) @@ -156,7 +157,7 @@ func testAccCheckVaultClusterDestroy(s *terraform.State) error { func awsTestSteps(t *testing.T, inp inputT) []resource.TestStep { in := &inp return []resource.TestStep{ - createClusteAndTestAdminTokenGeneration(t, in), + createClusterAndTestAdminTokenGeneration(t, in), importResourcesInTFState(t, in), tfApply(t, in), testTFDataSources(t, in), @@ -169,7 +170,7 @@ func awsTestSteps(t *testing.T, inp inputT) []resource.TestStep { func azureTestSteps(t *testing.T, inp inputT) []resource.TestStep { in := &inp return []resource.TestStep{ - createClusteAndTestAdminTokenGeneration(t, in), + createClusterAndTestAdminTokenGeneration(t, in), importResourcesInTFState(t, in), tfApply(t, in), testTFDataSources(t, in), @@ -178,7 +179,7 @@ func azureTestSteps(t *testing.T, inp inputT) []resource.TestStep { } // This step tests Vault cluster and admin token resource creation. -func createClusteAndTestAdminTokenGeneration(t *testing.T, in *inputT) resource.TestStep { +func createClusterAndTestAdminTokenGeneration(t *testing.T, in *inputT) resource.TestStep { return resource.TestStep{ Config: testConfig(in.tf), Check: resource.ComposeTestCheckFunc( @@ -285,11 +286,13 @@ func updateClusterTier(t *testing.T, in *inputT) resource.TestStep { resource.TestCheckResourceAttr(vaultClusterResourceName, "major_version_upgrade_config.0.upgrade_type", "SCHEDULED"), resource.TestCheckResourceAttr(vaultClusterResourceName, "major_version_upgrade_config.0.maintenance_window_day", "WEDNESDAY"), resource.TestCheckResourceAttr(vaultClusterResourceName, "major_version_upgrade_config.0.maintenance_window_time", "WINDOW_12AM_4AM"), + resource.TestCheckNoResourceAttr(in.VaultClusterResourceName, "vault_plugin.0"), + resource.TestCheckNoResourceAttr(in.VaultClusterResourceName, "vault_plugin.0"), ), } } -// This step verifies the successful update of "public_endpoint", "audit_log", "metrics" and MVU config +// This step verifies the successful update of "public_endpoint", "audit_log", "metrics", MVU config, and plugin config func updateVaultPublicEndpointObservabilityDataAndMVU(t *testing.T, in *inputT) resource.TestStep { newIn := *in newIn.PublicEndpoint = "true" @@ -307,6 +310,8 @@ func updateVaultPublicEndpointObservabilityDataAndMVU(t *testing.T, in *inputT) resource.TestCheckResourceAttrSet(in.VaultClusterResourceName, "audit_log_config.0.datadog_api_key"), resource.TestCheckResourceAttr(in.VaultClusterResourceName, "audit_log_config.0.datadog_region", "us1"), resource.TestCheckResourceAttr(in.VaultClusterResourceName, "major_version_upgrade_config.0.upgrade_type", "MANUAL"), + resource.TestCheckResourceAttr(in.VaultClusterResourceName, "vault_plugin.0.plugin_name", "venafi-pki-backend"), + resource.TestCheckResourceAttr(in.VaultClusterResourceName, "vault_plugin.0.plugin_type", "SECRET"), ), } } @@ -330,6 +335,8 @@ func updateTierPublicEndpointAndRemoveObservabilityData(t *testing.T, in *inputT resource.TestCheckResourceAttr(in.VaultClusterResourceName, "major_version_upgrade_config.0.upgrade_type", "SCHEDULED"), resource.TestCheckResourceAttr(in.VaultClusterResourceName, "major_version_upgrade_config.0.maintenance_window_day", "WEDNESDAY"), resource.TestCheckResourceAttr(in.VaultClusterResourceName, "major_version_upgrade_config.0.maintenance_window_time", "WINDOW_12AM_4AM"), + resource.TestCheckNoResourceAttr(in.VaultClusterResourceName, "vault_plugin.0"), + resource.TestCheckNoResourceAttr(in.VaultClusterResourceName, "vault_plugin.0"), ), } } diff --git a/internal/provider/validators.go b/internal/provider/validators.go index cf05e8041..bc60c4b70 100644 --- a/internal/provider/validators.go +++ b/internal/provider/validators.go @@ -441,3 +441,44 @@ func validateBoundaryPassword(v interface{}, path cty.Path) diag.Diagnostics { return diagnostics } + +func validateVaultPluginType(v interface{}, path cty.Path) diag.Diagnostics { + var diagnostics diag.Diagnostics + + err := vaultmodels.HashicorpCloudVault20201125PluginType(strings.ToUpper(v.(string))).Validate(strfmt.Default) + if err != nil { + enumList := regexp.MustCompile(`\[.*\]`).FindString(err.Error()) + expectedEnumList := strings.ToLower(enumList) + // Remove invalid option from allowed list in error message + expectedEnumList = strings.ReplaceAll( + expectedEnumList, + strings.ToLower(string(vaultmodels.HashicorpCloudVault20201125PluginTypePLUGINTYPEINVALID)), + "", + ) + msg := fmt.Sprintf("expected '%v' to be one of: %v", v, expectedEnumList) + diagnostics = append(diagnostics, diag.Diagnostic{ + Severity: diag.Error, + Summary: msg, + Detail: msg + " (value is case-insensitive).", + AttributePath: path, + }) + } + + return diagnostics +} + +func validateVaultPluginName(pluginName string, pluginType string, plugins []*vaultmodels.HashicorpCloudVault20201125PluginRegistrationStatus) diag.Diagnostics { + var found bool + for _, plugin := range plugins { + if strings.EqualFold(pluginName, plugin.PluginName) && strings.EqualFold(pluginType, string(*plugin.PluginType)) { + found = true + break + } + } + + if !found { + return diag.Errorf(fmt.Sprintf("plugin of plugin name: %s and plugin type: %s is not supported for installation by HCP Vault", pluginName, pluginType)) + } + + return nil +}