diff --git a/.changelog/804.txt b/.changelog/804.txt
new file mode 100644
index 000000000..856748e75
--- /dev/null
+++ b/.changelog/804.txt
@@ -0,0 +1,12 @@
+```release-note:enhancement
+`resource/pingone_credential_type`: Added support for the `management_mode` and `metadata.fields.required` properties.
+```
+
+```release-note:enhancement
+`data_source/pingone_credential_type`: Added support for the `management_mode` and `metadata.fields.required` properties.
+```
+
+```release-note:note
+`resource/pingone_credential_issuance_rule`: A `credential_issuance_rule` cannot be assigned to a `credential_type` that has a `management_mode` of `MANAGED`.
+```
+
diff --git a/docs/data-sources/credential_type.md b/docs/data-sources/credential_type.md
index 1e30eaef5..e0b56feb9 100644
--- a/docs/data-sources/credential_type.md
+++ b/docs/data-sources/credential_type.md
@@ -34,6 +34,7 @@ data "pingone_credential_type" "example_by_id" {
- `description` (String) A description of the credential type.
- `id` (String) The ID of this resource.
- `issuer_id` (String) Identifier (UUID) of the credential issuer.
+- `management_mode` (String) Specifies the management mode of the credential type.
- `metadata` (Attributes) An object that contains the names, data types, and other metadata related to the credentia (see [below for nested schema](#nestedatt--metadata))
- `revoke_on_delete` (Boolean) Specifies whether a user's issued verifiable credentials are automatically revoked when the credential type is deleted.
- `title` (String) Title of the credential.
@@ -64,6 +65,7 @@ Read-Only:
- `file_support` (String) Specifies how an image is stored in the credential field.
- `id` (String) Identifier of the field object.
- `is_visible` (Boolean) Specifies whether the field should be visible to viewers of the credential.
+- `required` (Boolean) Specifies whether the field is required for the credential.
- `title` (String) Descriptive text when showing the field.
- `type` (String) Type of data in the field.
- `value` (String) The text to appear on the credential for a field.type of Alphanumeric Text.
diff --git a/docs/resources/credential_type.md b/docs/resources/credential_type.md
index 84c1ad5ad..ff9627c14 100644
--- a/docs/resources/credential_type.md
+++ b/docs/resources/credential_type.md
@@ -129,6 +129,7 @@ EOT
- `card_type` (String) A descriptor of the credential type. Can be non-identity types such as proof of employment or proof of insurance.
- `description` (String) A description of the credential type. This value aligns to `${cardSubtitle}` in the `card_design_template`.
+- `management_mode` (String) Specifies the management mode of the credential type. Options are `AUTOMATED`, `MANAGED`. Defaults to `AUTOMATED`.
- `revoke_on_delete` (Boolean) A boolean that specifies whether a user's issued verifiable credentials are automatically revoked when a `credential_type`, `user`, or `environment` is deleted. Defaults to `true`.
### Read-Only
@@ -172,6 +173,7 @@ Optional:
- `attribute` (String) Name of the PingOne Directory attribute. Present if `field.type` is `Directory Attribute`.
- `file_support` (String) Specifies how an image is stored in the credential field. Options are `BASE64_STRING`, `INCLUDE_FILE`, `REFERENCE_FILE`.
- `is_visible` (Boolean) Specifies whether the field should be visible to viewers of the credential.
+- `required` (Boolean) Specifies whether the field is required for the credential.
- `title` (String) Descriptive text when showing the field.
- `value` (String) The text to appear on the credential for a `field.type` of `Alphanumeric Text`.
diff --git a/internal/service/credentials/data_source_credential_issuance_rule.go b/internal/service/credentials/data_source_credential_issuance_rule.go
index 7788cfa27..0394c5be2 100644
--- a/internal/service/credentials/data_source_credential_issuance_rule.go
+++ b/internal/service/credentials/data_source_credential_issuance_rule.go
@@ -297,6 +297,10 @@ func (p *CredentialIssuanceRuleDataSourceModel) toState(apiObject *credentials.C
func toStateAutomationDataSource(automation *credentials.CredentialIssuanceRuleAutomation, ok bool) (types.Object, diag.Diagnostics) {
var diags diag.Diagnostics
+ if !ok || automation == nil {
+ return types.ObjectNull(automationDataSourceServiceTFObjectTypes), diags
+ }
+
automationMap := map[string]attr.Value{
"issue": framework.EnumOkToTF(automation.GetIssueOk()),
"revoke": framework.EnumOkToTF(automation.GetRevokeOk()),
@@ -311,6 +315,10 @@ func toStateAutomationDataSource(automation *credentials.CredentialIssuanceRuleA
func toStateFilterDataSource(filter *credentials.CredentialIssuanceRuleFilter, ok bool) (types.Object, diag.Diagnostics) {
var diags diag.Diagnostics
+ if !ok || filter == nil {
+ return types.ObjectNull(filterDataSourceServiceTFObjectTypes), diags
+ }
+
filterMap := map[string]attr.Value{
"population_ids": framework.StringSetOkToTF(filter.GetPopulationIdsOk()),
"group_ids": framework.StringSetOkToTF(filter.GetGroupIdsOk()),
@@ -325,7 +333,7 @@ func toStateFilterDataSource(filter *credentials.CredentialIssuanceRuleFilter, o
func toStateNotificationDataSource(notification *credentials.CredentialIssuanceRuleNotification, ok bool) (types.Object, diag.Diagnostics) {
var diags diag.Diagnostics
- if notification == nil {
+ if !ok || notification == nil {
return types.ObjectNull(notificationDataSourceServiceTFObjectTypes), diags
}
diff --git a/internal/service/credentials/data_source_credential_type.go b/internal/service/credentials/data_source_credential_type.go
index ea7cc42d8..ce8b399d1 100644
--- a/internal/service/credentials/data_source_credential_type.go
+++ b/internal/service/credentials/data_source_credential_type.go
@@ -23,12 +23,13 @@ type CredentialTypeDataSourceModel struct {
EnvironmentId types.String `tfsdk:"environment_id"`
IssuerId types.String `tfsdk:"issuer_id"`
CredentialTypeId types.String `tfsdk:"credential_type_id"`
- Title types.String `tfsdk:"title"`
- Description types.String `tfsdk:"description"`
CardType types.String `tfsdk:"card_type"`
CardDesignTemplate types.String `tfsdk:"card_design_template"`
+ Description types.String `tfsdk:"description"`
+ ManagementMode types.String `tfsdk:"management_mode"`
Metadata types.Object `tfsdk:"metadata"`
RevokeOnDelete types.Bool `tfsdk:"revoke_on_delete"`
+ Title types.String `tfsdk:"title"`
CreatedAt types.String `tfsdk:"created_at"`
UpdatedAt types.String `tfsdk:"updated_at"`
}
@@ -54,6 +55,7 @@ type FieldsDataSourceModel struct {
IsVisible types.Bool `tfsdk:"is_visible"`
Attribute types.String `tfsdk:"attribute"`
Value types.String `tfsdk:"value"`
+ Required types.Bool `tfsdk:"required"`
}
var (
@@ -78,6 +80,7 @@ var (
"is_visible": types.BoolType,
"attribute": types.StringType,
"value": types.StringType,
+ "required": types.BoolType,
}
)
@@ -138,6 +141,11 @@ func (r *CredentialTypeDataSource) Schema(ctx context.Context, req datasource.Sc
Computed: true,
},
+ "management_mode": schema.StringAttribute{
+ Description: "Specifies the management mode of the credential type.",
+ Computed: true,
+ },
+
"revoke_on_delete": schema.BoolAttribute{
Description: "Specifies whether a user's issued verifiable credentials are automatically revoked when the credential type is deleted.",
Computed: true,
@@ -225,6 +233,10 @@ func (r *CredentialTypeDataSource) Schema(ctx context.Context, req datasource.Sc
Description: "The text to appear on the credential for a field.type of Alphanumeric Text.",
Computed: true,
},
+ "required": schema.BoolAttribute{
+ Description: "Specifies whether the field is required for the credential.",
+ Computed: true,
+ },
},
},
},
@@ -332,6 +344,10 @@ func (p *CredentialTypeDataSourceModel) toState(apiObject *credentials.Credentia
p.CreatedAt = framework.TimeOkToTF(apiObject.GetCreatedAtOk())
p.UpdatedAt = framework.TimeOkToTF(apiObject.GetUpdatedAtOk())
+ if v, ok := apiObject.GetManagementOk(); ok {
+ p.ManagementMode = framework.EnumOkToTF(v.GetModeOk())
+ }
+
revokeOnDelete := types.BoolNull()
if v, ok := apiObject.GetOnDeleteOk(); ok {
revokeOnDelete = framework.BoolOkToTF(v.GetRevokeIssuedCredentialsOk())
@@ -349,6 +365,10 @@ func (p *CredentialTypeDataSourceModel) toState(apiObject *credentials.Credentia
func toStateMetadataDataSource(metadata *credentials.CredentialTypeMetaData, ok bool) (types.Object, diag.Diagnostics) {
var diags diag.Diagnostics
+ if !ok || metadata == nil {
+ return types.ObjectNull(metadataDataSourceServiceTFObjectTypes), diags
+ }
+
// core metadata object
metadataMap := map[string]attr.Value{
"background_image": framework.StringOkToTF(metadata.GetBackgroundImageOk()),
@@ -376,6 +396,10 @@ func toStateMetadataDataSource(metadata *credentials.CredentialTypeMetaData, ok
func toStateFieldsDataSource(innerFields []credentials.CredentialTypeMetaDataFieldsInner, ok bool) (types.List, diag.Diagnostics) {
var diags diag.Diagnostics
+ if !ok || innerFields == nil {
+ return types.ListNull(types.ObjectType{AttrTypes: innerFieldsDataSourceServiceTFObjectTypes}), diags
+ }
+
tfInnerObjType := types.ObjectType{AttrTypes: innerFieldsDataSourceServiceTFObjectTypes}
innerflattenedList := []attr.Value{}
for _, v := range innerFields {
@@ -388,6 +412,7 @@ func toStateFieldsDataSource(innerFields []credentials.CredentialTypeMetaDataFie
"is_visible": framework.BoolOkToTF(v.GetIsVisibleOk()),
"attribute": framework.StringOkToTF(v.GetAttributeOk()),
"value": framework.StringOkToTF(v.GetValueOk()),
+ "required": framework.BoolOkToTF(v.GetRequiredOk()),
}
innerflattenedObj, d := types.ObjectValue(innerFieldsDataSourceServiceTFObjectTypes, fieldsMap)
diags.Append(d...)
diff --git a/internal/service/credentials/data_source_credential_type_test.go b/internal/service/credentials/data_source_credential_type_test.go
index aa5d57a26..0b0468df2 100644
--- a/internal/service/credentials/data_source_credential_type_test.go
+++ b/internal/service/credentials/data_source_credential_type_test.go
@@ -40,6 +40,7 @@ func TestAccCredentialTypeDataSource_ByIDFull(t *testing.T) {
resource.TestCheckResourceAttr(dataSourceFullName, "description", fmt.Sprintf("%s Example Description", name)),
resource.TestCheckResourceAttr(dataSourceFullName, "card_type", name),
resource.TestCheckResourceAttrPair(dataSourceFullName, "card_design_template", resourceFullName, "card_design_template"),
+ resource.TestCheckResourceAttr(dataSourceFullName, "management_mode", "AUTOMATED"),
resource.TestCheckResourceAttrPair(dataSourceFullName, "metadata.%", resourceFullName, "metadata.%"),
resource.TestCheckResourceAttrPair(dataSourceFullName, "metadata.fields.%", resourceFullName, "metadata.fields.%"),
resource.TestCheckResourceAttr(dataSourceFullName, "revoke_on_delete", "false"),
diff --git a/internal/service/credentials/resource_credential_issuance_rule.go b/internal/service/credentials/resource_credential_issuance_rule.go
index c5dce962b..806d41934 100644
--- a/internal/service/credentials/resource_credential_issuance_rule.go
+++ b/internal/service/credentials/resource_credential_issuance_rule.go
@@ -338,7 +338,7 @@ func (r *CredentialIssuanceRuleResource) Create(ctx context.Context, req resourc
}
// Build the model for the API
- CredentialIssuanceRule, d := plan.expand(ctx)
+ CredentialIssuanceRule, d := plan.expand(ctx, r)
resp.Diagnostics.Append(d...)
if resp.Diagnostics.HasError() {
return
@@ -432,7 +432,7 @@ func (r *CredentialIssuanceRuleResource) Update(ctx context.Context, req resourc
}
// Build the model for the API
- CredentialIssuanceRule, d := plan.expand(ctx)
+ CredentialIssuanceRule, d := plan.expand(ctx, r)
resp.Diagnostics.Append(d...)
if resp.Diagnostics.HasError() {
return
@@ -537,8 +537,13 @@ func (r *CredentialIssuanceRuleResource) ImportState(ctx context.Context, req re
}
}
-func (p *CredentialIssuanceRuleResourceModel) expand(ctx context.Context) (*credentials.CredentialIssuanceRule, diag.Diagnostics) {
- var diags diag.Diagnostics
+func (p *CredentialIssuanceRuleResourceModel) expand(ctx context.Context, r *CredentialIssuanceRuleResource) (*credentials.CredentialIssuanceRule, diag.Diagnostics) {
+ // The P1 Credentials service automatically sets the Issuance Rule to disabled in the backend if the Credential Type associated with it has a management.mode of `MANAGED`.
+ // Perform check to prevent an out of plan / drift condition.
+ diags := checkCredentialTypeManagementMode(ctx, r, p.EnvironmentId.ValueString(), p.CredentialTypeId.ValueString())
+ if diags.HasError() {
+ return nil, diags
+ }
// expand automation rules
credentialIssuanceRuleAutomation := credentials.NewCredentialIssuanceRuleAutomationWithDefaults()
@@ -873,3 +878,54 @@ func enumCredentialIssuanceRuleNotificationMethodOkToTF(v []credentials.EnumCred
return types.SetValueMust(types.StringType, list)
}
}
+
+func checkCredentialTypeManagementMode(ctx context.Context, r *CredentialIssuanceRuleResource, environmentId, credentialTypeId string) diag.Diagnostics {
+ var diags diag.Diagnostics
+
+ // Run the API call
+ var respObject *credentials.CredentialType
+ diags.Append(framework.ParseResponse(
+ ctx,
+
+ func() (any, *http.Response, error) {
+ fO, fR, fErr := r.Client.CredentialsAPIClient.CredentialTypesApi.ReadOneCredentialType(ctx, environmentId, credentialTypeId).Execute()
+ return framework.CheckEnvironmentExistsOnPermissionsError(ctx, r.Client.ManagementAPIClient, environmentId, fO, fR, fErr)
+ },
+ "ReadOneCredentialType",
+ framework.DefaultCustomError,
+ sdk.DefaultCreateReadRetryable,
+ &respObject,
+ )...)
+ if diags.HasError() {
+ return diags
+ }
+
+ if respObject == nil {
+ diags.AddError(
+ "Credential Type Id Invalid or Missing",
+ "Creantial Type referenced in `credential_type.id` does not exist",
+ )
+ return diags
+ }
+
+ if v, ok := respObject.GetManagementOk(); ok {
+ managementMode, managementModeOk := v.GetModeOk()
+ if !managementModeOk {
+ diags.AddError(
+ "Credential Type referenced in `credential_type.id` does not have a management mode defined.",
+ fmt.Sprintf("Credential Type Id %s does not contain a `management.mode` value, or the value could not be found. Please report this to the provider maintainers.", credentialTypeId),
+ )
+ return diags
+ }
+
+ if *managementMode == credentials.ENUMCREDENTIALTYPEMANAGEMENTMODE_MANAGED {
+ diags.AddError(
+ fmt.Sprintf("A Credential Issuance Rule cannot be assigned to a Credential Type that has a management mode of %s.", string(credentials.ENUMCREDENTIALTYPEMANAGEMENTMODE_MANAGED)),
+ fmt.Sprintf("The Credential Type Id %s associated with the configured Issuance Rule is set to a `management.mode` of %s. The Issuance Rule must be removed, or the Credential Type updated.", credentialTypeId, string(credentials.ENUMCREDENTIALTYPEMANAGEMENTMODE_MANAGED)),
+ )
+ return diags
+ }
+ }
+
+ return diags
+}
diff --git a/internal/service/credentials/resource_credential_issuance_rule_test.go b/internal/service/credentials/resource_credential_issuance_rule_test.go
index 3cf1fa4af..971ce1130 100644
--- a/internal/service/credentials/resource_credential_issuance_rule_test.go
+++ b/internal/service/credentials/resource_credential_issuance_rule_test.go
@@ -265,6 +265,10 @@ func TestAccCredentialIssuanceRule_InvalidConfigs(t *testing.T) {
ExpectError: regexp.MustCompile("Error: Incorrect attribute value type"),
Destroy: true,
},
+ {
+ Config: testAccCredentialIssuanceRuleConfig_CredentialTypeIsManaged(resourceName, name),
+ ExpectError: regexp.MustCompile("Error: A Credential Issuance Rule cannot be assigned to a Credential Type that has a management mode of MANAGED."),
+ },
},
})
}
@@ -538,10 +542,11 @@ func testAccCredentialIssuanceRuleConfig_Disabled(resourceName, name string) str
%[1]s
resource "pingone_credential_type" "%[2]s" {
- environment_id = data.pingone_environment.general_test.id
- title = "%[3]s"
- description = "%[3]s Example Description"
- card_type = "%[3]s"
+ environment_id = data.pingone_environment.general_test.id
+ title = "%[3]s"
+ description = "%[3]s Example Description"
+ card_type = "%[3]s"
+
card_design_template = ""
metadata = {
@@ -1219,3 +1224,86 @@ resource "pingone_credential_issuance_rule" "%[2]s" {
}
}`, acctest.GenericSandboxEnvironment(), resourceName, name)
}
+
+func testAccCredentialIssuanceRuleConfig_CredentialTypeIsManaged(resourceName, name string) string {
+ return fmt.Sprintf(`
+ %[1]s
+
+resource "pingone_population" "%[2]s" {
+ environment_id = data.pingone_environment.general_test.id
+ name = "%[3]s"
+}
+
+resource "pingone_credential_type" "%[2]s" {
+ environment_id = data.pingone_environment.general_test.id
+ title = "%[3]s"
+ description = "%[3]s Example Description"
+ card_type = "%[3]s"
+ card_design_template = ""
+
+ management_mode = "MANAGED"
+
+ metadata = {
+ name = "%[3]s"
+ description = "%[3]s Example Description"
+ bg_opacity_percent = 100
+ card_color = "#000000"
+ text_color = "#eff0f1"
+
+ fields = [
+ {
+ type = "Alphanumeric Text"
+ title = "Example Field"
+ value = "Demo"
+ is_visible = false
+ },
+ ]
+ }
+}
+
+resource "pingone_application" "%[2]s" {
+ environment_id = data.pingone_environment.general_test.id
+ name = "%[3]s"
+ enabled = true
+
+ oidc_options {
+ type = "NATIVE_APP"
+ grant_types = ["CLIENT_CREDENTIALS"]
+ token_endpoint_authn_method = "CLIENT_SECRET_BASIC"
+
+ mobile_app {
+ bundle_id = "com.pingidentity.ios_%[3]s"
+ package_name = "com.pingidentity.android_%[3]s"
+ passcode_refresh_seconds = 30
+ }
+ }
+}
+
+resource "pingone_digital_wallet_application" "%[2]s" {
+ environment_id = data.pingone_environment.general_test.id
+ application_id = resource.pingone_application.%[2]s.id
+ name = "%[3]s"
+ app_open_url = "https://www.example.com"
+
+ depends_on = [resource.pingone_application.%[2]s]
+}
+
+resource "pingone_credential_issuance_rule" "%[2]s" {
+ environment_id = data.pingone_environment.general_test.id
+ credential_type_id = resource.pingone_credential_type.%[2]s.id
+ digital_wallet_application_id = resource.pingone_digital_wallet_application.%[2]s.id
+ status = "ACTIVE"
+
+ filter = {
+ scim = "address.countryCode eq \"NG\""
+ }
+
+ automation = {
+ issue = "PERIODIC"
+ revoke = "PERIODIC"
+ update = "PERIODIC"
+ }
+
+
+}`, acctest.GenericSandboxEnvironment(), resourceName, name)
+}
diff --git a/internal/service/credentials/resource_credential_type.go b/internal/service/credentials/resource_credential_type.go
index 9538e530a..b5dd17893 100644
--- a/internal/service/credentials/resource_credential_type.go
+++ b/internal/service/credentials/resource_credential_type.go
@@ -41,6 +41,7 @@ type CredentialTypeResourceModel struct {
CardType types.String `tfsdk:"card_type"`
CardDesignTemplate types.String `tfsdk:"card_design_template"`
Description types.String `tfsdk:"description"`
+ ManagementMode types.String `tfsdk:"management_mode"`
Metadata types.Object `tfsdk:"metadata"`
RevokeOnDelete types.Bool `tfsdk:"revoke_on_delete"`
Title types.String `tfsdk:"title"`
@@ -69,6 +70,7 @@ type FieldsModel struct {
IsVisible types.Bool `tfsdk:"is_visible"`
Attribute types.String `tfsdk:"attribute"`
Value types.String `tfsdk:"value"`
+ Required types.Bool `tfsdk:"required"`
}
var (
@@ -93,14 +95,16 @@ var (
"is_visible": types.BoolType,
"attribute": types.StringType,
"value": types.StringType,
+ "required": types.BoolType,
}
)
// Framework interfaces
var (
- _ resource.Resource = &CredentialTypeResource{}
- _ resource.ResourceWithConfigure = &CredentialTypeResource{}
- _ resource.ResourceWithImportState = &CredentialTypeResource{}
+ _ resource.Resource = &CredentialTypeResource{}
+ _ resource.ResourceWithConfigure = &CredentialTypeResource{}
+ _ resource.ResourceWithValidateConfig = &CredentialTypeResource{}
+ _ resource.ResourceWithImportState = &CredentialTypeResource{}
)
// New Object
@@ -134,6 +138,10 @@ func (r *CredentialTypeResource) Schema(ctx context.Context, req resource.Schema
"The identifier (UUID) of the issuer of the credential, which is the `id` of the `credential_issuer_profile` defined in the `environment`.",
)
+ managementModeDescription := framework.SchemaAttributeDescriptionFromMarkdown(
+ "Specifies the management mode of the credential type.",
+ ).AllowedValuesEnum(credentials.AllowedEnumCredentialTypeManagementModeEnumValues).DefaultValue(string(credentials.ENUMCREDENTIALTYPEMANAGEMENTMODE_AUTOMATED))
+
revokeOnDeleteDescription := framework.SchemaAttributeDescriptionFromMarkdown(
"A boolean that specifies whether a user's issued verifiable credentials are automatically revoked when a `credential_type`, `user`, or `environment` is deleted.",
).DefaultValue("true")
@@ -234,6 +242,16 @@ func (r *CredentialTypeResource) Schema(ctx context.Context, req resource.Schema
},
},
+ "management_mode": schema.StringAttribute{
+ Description: managementModeDescription.Description,
+ MarkdownDescription: managementModeDescription.MarkdownDescription,
+ Optional: true,
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(utils.EnumSliceToStringSlice(credentials.AllowedEnumCredentialTypeManagementModeEnumValues)...),
+ },
+ },
+
"revoke_on_delete": schema.BoolAttribute{
Description: revokeOnDeleteDescription.Description,
MarkdownDescription: revokeOnDeleteDescription.MarkdownDescription,
@@ -426,9 +444,13 @@ func (r *CredentialTypeResource) Schema(ctx context.Context, req resource.Schema
Validators: []validator.String{
stringvalidator.LengthAtLeast(attrMinLength),
stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("attribute")),
- customstringvalidator.IsRequiredIfMatchesPathValue(basetypes.NewStringValue(string(credentials.ENUMCREDENTIALTYPEMETADATAFIELDSTYPE_ALPHANUMERIC_TEXT)), path.MatchRelative().AtParent().AtName("type")),
},
},
+ "required": schema.BoolAttribute{
+ Description: "Specifies whether the field is required for the credential.",
+ Optional: true,
+ Computed: true,
+ },
},
},
},
@@ -452,6 +474,43 @@ func (r *CredentialTypeResource) Schema(ctx context.Context, req resource.Schema
}
}
+func (r *CredentialTypeResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
+ var data CredentialTypeResourceModel
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ var metaData MetadataModel
+ resp.Diagnostics.Append(data.Metadata.As(ctx, &metaData, basetypes.ObjectAsOptions{
+ UnhandledNullAsEmpty: false,
+ UnhandledUnknownAsEmpty: false,
+ })...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ var metadataFields []FieldsModel
+ resp.Diagnostics.Append(metaData.Fields.ElementsAs(ctx, &metadataFields, false)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ if data.ManagementMode != types.StringValue(string(credentials.ENUMCREDENTIALTYPEMANAGEMENTMODE_MANAGED)) {
+ for _, v := range metadataFields {
+ if v.Type == types.StringValue(string(credentials.ENUMCREDENTIALTYPEMETADATAFIELDSTYPE_ALPHANUMERIC_TEXT)) && (v.Value.IsNull() || v.Value.IsUnknown()) {
+
+ resp.Diagnostics.AddAttributeError(
+ path.Root("metadata"),
+ "Invalid credential type configuration",
+ fmt.Sprintf("The configuration for `%s` is invalid. The `fields.value` property is required when the `fields.type` property is `%s` and the credential `management_mode` property is undefined or `%s`.", v.Title.ValueString(), v.Type.ValueString(), string(credentials.ENUMCREDENTIALTYPEMANAGEMENTMODE_AUTOMATED)),
+ )
+ }
+ }
+ }
+}
+
func (r *CredentialTypeResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
@@ -726,6 +785,13 @@ func (p *CredentialTypeResourceModel) expand(ctx context.Context) (*credentials.
data.SetCardType(p.CardType.ValueString())
}
+ if !p.ManagementMode.IsNull() && !p.ManagementMode.IsUnknown() {
+ managementModeObject := credentials.NewCredentialTypeManagement()
+ managementModeObject.SetMode(credentials.EnumCredentialTypeManagementMode(p.ManagementMode.ValueString()))
+
+ data.SetManagement(*managementModeObject)
+ }
+
if !p.RevokeOnDelete.IsNull() && !p.RevokeOnDelete.IsUnknown() {
onDeleteObject := credentials.NewCredentialTypeOnDelete()
onDeleteObject.SetRevokeIssuedCredentials(p.RevokeOnDelete.ValueBool())
@@ -812,11 +878,11 @@ func (p *FieldsModel) expandFields() (*credentials.CredentialTypeMetaDataFieldsI
attrId := p.Type.ValueString() + " -> " + p.Title.ValueString() // construct id per P1Creds API recommendations
innerFields.SetId(attrId)
- if attrType == credentials.ENUMCREDENTIALTYPEMETADATAFIELDSTYPE_ALPHANUMERIC_TEXT {
+ if attrType == credentials.ENUMCREDENTIALTYPEMETADATAFIELDSTYPE_ALPHANUMERIC_TEXT && !p.Value.IsNull() && !p.Value.IsUnknown() {
innerFields.SetValue(p.Value.ValueString())
}
- if attrType == credentials.ENUMCREDENTIALTYPEMETADATAFIELDSTYPE_DIRECTORY_ATTRIBUTE {
+ if attrType == credentials.ENUMCREDENTIALTYPEMETADATAFIELDSTYPE_DIRECTORY_ATTRIBUTE && !p.Attribute.IsNull() && !p.Attribute.IsUnknown() {
innerFields.SetAttribute(p.Attribute.ValueString())
if !p.FileSupport.IsNull() && !p.FileSupport.IsUnknown() {
@@ -833,6 +899,10 @@ func (p *FieldsModel) expandFields() (*credentials.CredentialTypeMetaDataFieldsI
innerFields.SetIsVisible(p.IsVisible.ValueBool())
}
+ if !p.Required.IsNull() && !p.Required.IsUnknown() {
+ innerFields.SetRequired(p.Required.ValueBool())
+ }
+
if innerFields == nil {
diags.AddWarning(
"Unexpected Value",
@@ -865,6 +935,10 @@ func (p *CredentialTypeResourceModel) toState(apiObject *credentials.CredentialT
p.CreatedAt = framework.TimeOkToTF(apiObject.GetCreatedAtOk())
p.UpdatedAt = framework.TimeOkToTF(apiObject.GetUpdatedAtOk())
+ if v, ok := apiObject.GetManagementOk(); ok {
+ p.ManagementMode = framework.EnumOkToTF(v.GetModeOk())
+ }
+
revokeOnDelete := types.BoolNull()
if v, ok := apiObject.GetOnDeleteOk(); ok {
revokeOnDelete = framework.BoolOkToTF(v.GetRevokeIssuedCredentialsOk())
@@ -882,6 +956,10 @@ func (p *CredentialTypeResourceModel) toState(apiObject *credentials.CredentialT
func toStateMetadata(metadata *credentials.CredentialTypeMetaData, ok bool) (types.Object, diag.Diagnostics) {
var diags diag.Diagnostics
+ if !ok || metadata == nil {
+ return types.ObjectNull(metadataServiceTFObjectTypes), diags
+ }
+
// core metadata object
metadataMap := map[string]attr.Value{
"background_image": framework.StringOkToTF(metadata.GetBackgroundImageOk()),
@@ -909,6 +987,10 @@ func toStateMetadata(metadata *credentials.CredentialTypeMetaData, ok bool) (typ
func toStateFields(innerFields []credentials.CredentialTypeMetaDataFieldsInner, ok bool) (types.List, diag.Diagnostics) {
var diags diag.Diagnostics
+ if !ok || innerFields == nil {
+ return types.ListNull(types.ObjectType{AttrTypes: innerFieldsServiceTFObjectTypes}), diags
+ }
+
tfInnerObjType := types.ObjectType{AttrTypes: innerFieldsServiceTFObjectTypes}
innerflattenedList := []attr.Value{}
for _, v := range innerFields {
@@ -921,6 +1003,7 @@ func toStateFields(innerFields []credentials.CredentialTypeMetaDataFieldsInner,
"is_visible": framework.BoolOkToTF(v.GetIsVisibleOk()),
"attribute": framework.StringOkToTF(v.GetAttributeOk()),
"value": framework.StringOkToTF(v.GetValueOk()),
+ "required": framework.BoolOkToTF(v.GetRequiredOk()),
}
innerflattenedObj, d := types.ObjectValue(innerFieldsServiceTFObjectTypes, fieldsMap)
diags.Append(d...)
diff --git a/internal/service/credentials/resource_credential_type_test.go b/internal/service/credentials/resource_credential_type_test.go
index 2ed918b3f..6f038c838 100644
--- a/internal/service/credentials/resource_credential_type_test.go
+++ b/internal/service/credentials/resource_credential_type_test.go
@@ -141,6 +141,7 @@ func TestAccCredentialType_Full(t *testing.T) {
resource.TestCheckResourceAttr(resourceFullName, "description", fmt.Sprintf("%s Example Description", name)),
resource.TestCheckResourceAttr(resourceFullName, "card_type", "VerifiedEmployee"),
resource.TestCheckResourceAttr(resourceFullName, "card_design_template", cardDesignTemplate),
+ resource.TestCheckResourceAttr(resourceFullName, "management_mode", "AUTOMATED"),
resource.TestCheckResourceAttr(resourceFullName, "metadata.name", name),
resource.TestCheckResourceAttr(resourceFullName, "metadata.description", fmt.Sprintf("%s Example Description", name)),
resource.TestCheckResourceAttr(resourceFullName, "metadata.version", "5"), // ensures calculated default is 5
@@ -156,6 +157,13 @@ func TestAccCredentialType_Full(t *testing.T) {
resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.3.title", "displayName"),
resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.3.attribute", "name.formatted"),
resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.3.is_visible", "false"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.3.required", "false"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.6.id", "Directory Attribute -> id"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.6.type", "Directory Attribute"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.6.title", "id"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.6.attribute", "id"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.6.is_visible", "false"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.6.required", "true"),
resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.7.file_support", "REFERENCE_FILE"),
resource.TestCheckResourceAttr(resourceFullName, "revoke_on_delete", "true"),
resource.TestMatchResourceAttr(resourceFullName, "created_at", verify.RFC3339Regexp),
@@ -183,6 +191,7 @@ func TestAccCredentialType_Full(t *testing.T) {
resource.TestCheckResourceAttr(resourceFullName, "description", fmt.Sprintf("%s Example Description", updatedName)),
resource.TestCheckResourceAttr(resourceFullName, "card_type", "DemonstrationCard"),
resource.TestCheckResourceAttr(resourceFullName, "card_design_template", updatedCardDesignTemplate),
+ resource.TestCheckResourceAttr(resourceFullName, "management_mode", "AUTOMATED"),
resource.TestCheckResourceAttr(resourceFullName, "metadata.name", updatedName),
resource.TestCheckResourceAttr(resourceFullName, "metadata.version", "5"), // ensures calculated default is 5
resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.#", "1"),
@@ -200,6 +209,44 @@ func TestAccCredentialType_Full(t *testing.T) {
),
}
+ updateManagementModeStep := resource.TestStep{
+ Config: testAccCredentialTypeConfig_ManagedCredential(resourceName, updatedName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestMatchResourceAttr(resourceFullName, "id", verify.P1ResourceIDRegexpFullString),
+ resource.TestMatchResourceAttr(resourceFullName, "environment_id", verify.P1ResourceIDRegexpFullString),
+ resource.TestMatchResourceAttr(resourceFullName, "issuer_id", verify.P1ResourceIDRegexpFullString),
+ resource.TestCheckResourceAttr(resourceFullName, "title", updatedName),
+ resource.TestCheckResourceAttr(resourceFullName, "description", fmt.Sprintf("%s Example Description", updatedName)),
+ resource.TestCheckResourceAttr(resourceFullName, "card_type", "DemonstrationCard"),
+ resource.TestCheckResourceAttr(resourceFullName, "card_design_template", updatedCardDesignTemplate),
+ resource.TestCheckResourceAttr(resourceFullName, "management_mode", "MANAGED"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.name", updatedName),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.version", "5"), // ensures calculated default is 5
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.#", "3"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.0.id", "Issued Timestamp -> timestamp"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.0.type", "Issued Timestamp"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.0.title", "timestamp"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.0.is_visible", "false"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.1.id", "Alphanumeric Text -> selfie"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.1.type", "Alphanumeric Text"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.1.title", "selfie"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.1.is_visible", "false"),
+ resource.TestCheckNoResourceAttr(resourceFullName, "metadata.fields.1.value"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.2.id", "Alphanumeric Text -> other"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.2.type", "Alphanumeric Text"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.2.title", "other"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.2.is_visible", "false"),
+ resource.TestCheckResourceAttr(resourceFullName, "metadata.fields.2.value", "sample"),
+ resource.TestCheckNoResourceAttr(resourceFullName, "metadata.columns"),
+ resource.TestCheckNoResourceAttr(resourceFullName, "metadata.bg_opacity_percent"),
+ resource.TestCheckNoResourceAttr(resourceFullName, "metadata.card_color"),
+ resource.TestCheckNoResourceAttr(resourceFullName, "metadata.text_color"),
+ resource.TestCheckResourceAttr(resourceFullName, "revoke_on_delete", "false"),
+ resource.TestMatchResourceAttr(resourceFullName, "created_at", verify.RFC3339Regexp),
+ resource.TestMatchResourceAttr(resourceFullName, "updated_at", verify.RFC3339Regexp),
+ ),
+ }
+
resource.Test(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheckClient(t)
@@ -223,7 +270,7 @@ func TestAccCredentialType_Full(t *testing.T) {
},
// update
fullStep,
- minimalStep,
+ updateManagementModeStep,
fullStep,
// Test importing the resource
{
@@ -242,6 +289,10 @@ func TestAccCredentialType_Full(t *testing.T) {
ImportStateVerify: true,
},
// clear
+ {
+ Config: testAccCredentialTypeConfig_ManagedCredential(resourceName, updatedName),
+ Destroy: true,
+ },
{
Config: testAccCredentialTypeConfig_Minimal(resourceName, updatedName),
Destroy: true,
@@ -291,7 +342,7 @@ func TestAccCredentialType_MetaData(t *testing.T) {
},
{
Config: testAccCredentialTypeConfig_EmptyFieldsArray(resourceName, name),
- ExpectError: regexp.MustCompile("ttribute metadata.fields list must contain at least 1 elements"),
+ ExpectError: regexp.MustCompile("Attribute metadata.fields list must contain at least 1 elements"),
Destroy: true,
},
{
@@ -299,6 +350,11 @@ func TestAccCredentialType_MetaData(t *testing.T) {
ExpectError: regexp.MustCompile("Error: Invalid Attribute Value Match"),
Destroy: true,
},
+ {
+ Config: testAccCredentialTypeConfig_InvalidManagementModeValueCombination(resourceName, name),
+ ExpectError: regexp.MustCompile("Error: Invalid credential type configuration"),
+ Destroy: true,
+ },
},
})
}
@@ -526,6 +582,7 @@ EOT
title = "id"
attribute = "id"
is_visible = false
+ required = true
},
{
type = "Directory Attribute"
@@ -798,6 +855,41 @@ EOT
}`, acctest.GenericSandboxEnvironment(), resourceName, name)
}
+func testAccCredentialTypeConfig_InvalidManagementModeValueCombination(resourceName, name string) string {
+ return fmt.Sprintf(`
+ %[1]s
+
+resource "pingone_credential_type" "%[3]s" {
+ environment_id = data.pingone_environment.general_test.id
+ title = "%[3]s"
+ description = "%[3]s Example Description"
+ card_type = "DemonstrationCard"
+ revoke_on_delete = true
+
+ card_design_template = <<-EOT
+
+EOT
+
+ metadata = {
+ name = "%[3]s"
+
+ fields = [
+ {
+ type = "Alphanumeric Text"
+ title = "selfie"
+ is_visible = false
+ }
+ ]
+ }
+}`, acctest.GenericSandboxEnvironment(), resourceName, name)
+}
+
func testAccCredentialTypeConfig_CardDesignTemplate_NoSVG(resourceName, name string) string {
return fmt.Sprintf(`
%[1]s
@@ -1032,3 +1124,50 @@ EOT
}
}`, acctest.GenericSandboxEnvironment(), resourceName, name)
}
+
+func testAccCredentialTypeConfig_ManagedCredential(resourceName, name string) string {
+ return fmt.Sprintf(`
+ %[1]s
+
+resource "pingone_credential_type" "%[2]s" {
+ environment_id = data.pingone_environment.general_test.id
+ title = "%[3]s"
+ description = "%[3]s Example Description"
+ card_type = "DemonstrationCard"
+ management_mode = "MANAGED"
+ revoke_on_delete = false
+
+ card_design_template = <<-EOT
+
+EOT
+
+ metadata = {
+ name = "%[3]s"
+
+ fields = [
+ {
+ type = "Issued Timestamp"
+ title = "timestamp"
+ is_visible = false
+ },
+ {
+ type = "Alphanumeric Text"
+ title = "selfie"
+ is_visible = false
+ },
+ {
+ type = "Alphanumeric Text"
+ title = "other"
+ is_visible = false
+ value = "sample"
+ }
+ ]
+ }
+}`, acctest.GenericSandboxEnvironment(), resourceName, name)
+}