Skip to content
Open
Show file tree
Hide file tree
Changes from 99 commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
3eb1b14
An initial draft of davinci codegen
henryrecker-pingidentity May 16, 2025
aeaa9ad
Updated fully-generated draft
henryrecker-pingidentity Jun 6, 2025
6d75ebe
Initial working resource, moving legacy sdk funcs to separate child p…
henryrecker-pingidentity Jun 9, 2025
99b8198
Fixes based on initial testing
henryrecker-pingidentity Jun 9, 2025
e662e89
Basic acceptance test
henryrecker-pingidentity Jun 9, 2025
2891e68
Move retry logic for new sdk into framework package
henryrecker-pingidentity Jun 10, 2025
0720ec4
Add tests for all variable types
henryrecker-pingidentity Jun 11, 2025
e895032
Handle secrets with separate sensitive attribute
henryrecker-pingidentity Jun 11, 2025
f157a31
Simplify error handling in new SDK
henryrecker-pingidentity Jun 11, 2025
e3089a6
Add resource-level validation
henryrecker-pingidentity Jun 11, 2025
005626b
Check for id presence in error handling
henryrecker-pingidentity Jun 11, 2025
79dc835
Use mux for a new provider with the new client
henryrecker-pingidentity Jun 12, 2025
65aa4db
Merge branch 'main' into DavinciVarInitialGenDraft
henryrecker-pingidentity Jun 12, 2025
3addb81
Move custom ValidateConfig to separate file
henryrecker-pingidentity Jun 17, 2025
e85bfcc
Reorder alphabetically
henryrecker-pingidentity Jun 17, 2025
bfc7636
Select region for acctest client
henryrecker-pingidentity Jun 17, 2025
a146439
Set user agent correctly
henryrecker-pingidentity Jun 17, 2025
451c613
WIP generated dv connector instance
henryrecker-pingidentity Jun 30, 2025
7009c41
Fix for unneeded error and diagnostics
henryrecker-pingidentity Jul 7, 2025
6c65324
Regenerated resource and tests
henryrecker-pingidentity Jul 8, 2025
ebbb1bd
WIP
henryrecker-pingidentity Jul 14, 2025
7ea3317
Handle non-UUID field
henryrecker-pingidentity Jul 23, 2025
2ef8fd8
Handle non-uuid id and get basic tests working
henryrecker-pingidentity Jul 23, 2025
6787d94
Add connector.id RequiresReplace, fix import regex, improve tests
henryrecker-pingidentity Jul 23, 2025
53b7e70
Custom error for DV-ignored properties
henryrecker-pingidentity Jul 23, 2025
63bb75e
Mark connector.id as required
henryrecker-pingidentity Jul 24, 2025
f33ecfc
Initial generated application resource with a successful create
henryrecker-pingidentity Jul 24, 2025
af5d041
Remove unneeded api_key_enabled attr
henryrecker-pingidentity Jul 25, 2025
28c640a
Generate correct computed+optional+required
henryrecker-pingidentity Jul 25, 2025
d6d9edd
Generate with enum and validator fixes
henryrecker-pingidentity Jul 28, 2025
7013c3f
Manual schema fixes
henryrecker-pingidentity Jul 28, 2025
e054ceb
Flesh out test HCL
henryrecker-pingidentity Jul 28, 2025
2a39c61
Build update request after create
henryrecker-pingidentity Jul 28, 2025
fe2ef4b
Fix wrong ID for post-create update
henryrecker-pingidentity Jul 28, 2025
530fab5
Davinci import regex
henryrecker-pingidentity Jul 28, 2025
f6aeadb
Schema defaults and set apiKeyEnabled in update
henryrecker-pingidentity Jul 28, 2025
a4edbe1
Test workarounds
henryrecker-pingidentity Jul 28, 2025
8118149
Fix properties read on imports
henryrecker-pingidentity Jul 28, 2025
cfb0b29
Fix attr name
henryrecker-pingidentity Jul 29, 2025
dbaa08d
Move handwritten code to separate file
henryrecker-pingidentity Jul 29, 2025
6670cfd
Add examples and generate docs
henryrecker-pingidentity Aug 7, 2025
3802255
Add more extensive tests
henryrecker-pingidentity Aug 7, 2025
38871e0
Regenerate doc
henryrecker-pingidentity Aug 7, 2025
59dddf6
Remove superfluous notes
henryrecker-pingidentity Aug 7, 2025
48f140c
Handle error in test region suffix build
henryrecker-pingidentity Aug 7, 2025
236f685
Add SG domain and fix user agent append
henryrecker-pingidentity Aug 7, 2025
a4c6782
Improve error output when no error ID present
henryrecker-pingidentity Aug 7, 2025
b037a9b
Add examples and docs and lint
henryrecker-pingidentity Aug 7, 2025
5a2d065
Initial complex properties test
henryrecker-pingidentity Aug 8, 2025
1a6c370
Passing complex tests, with secrets commented out
henryrecker-pingidentity Aug 8, 2025
ff796cf
Implement jsontypes.NormalizedObfuscatable
henryrecker-pingidentity Aug 8, 2025
8c20ed8
Use new json type in connector instance tests
henryrecker-pingidentity Aug 8, 2025
5d7410b
Mark properties as sensitive and format tests
henryrecker-pingidentity Aug 8, 2025
7409c77
Simplify test
henryrecker-pingidentity Aug 8, 2025
52a7f9c
Add test to cover optional properties and drift detection
henryrecker-pingidentity Aug 8, 2025
82a01c9
Cover modifying a sensitive attribute
henryrecker-pingidentity Aug 8, 2025
a3f4a25
Create davinci bootstrapped test env
henryrecker-pingidentity Aug 11, 2025
b2ef63e
Test bootstrapped and non bootstrapped DV env
henryrecker-pingidentity Aug 11, 2025
b4d09f7
Create davinci bootstrapped test env
henryrecker-pingidentity Aug 11, 2025
c45ef8e
Add tests covering bootstrapped DV env
henryrecker-pingidentity Aug 11, 2025
acd7171
Update to released client
henryrecker-pingidentity Aug 11, 2025
3bda7d1
Extend application tests, default all oauth sets to empty
henryrecker-pingidentity Aug 11, 2025
216179b
Create davinci bootstrapped test env
henryrecker-pingidentity Aug 11, 2025
c4f9092
Fill out tests for bootstrap and jwks
henryrecker-pingidentity Aug 11, 2025
a040cd6
Add examples and doc
henryrecker-pingidentity Aug 11, 2025
9c1d3f8
Add placeholder expand value to env read calls
henryrecker-pingidentity Aug 12, 2025
71da389
Changelog
henryrecker-pingidentity Aug 12, 2025
cccd787
Merge branch 'main' into DavinciVarInitialGenDraft
henryrecker-pingidentity Aug 12, 2025
01d2be7
Merge branch 'main' into DavinciVarInitialGenDraft
henryrecker-pingidentity Aug 12, 2025
23366ee
Fix mismatched provider schemas
henryrecker-pingidentity Aug 12, 2025
218a4a3
Add missing test util method for new SDK
henryrecker-pingidentity Aug 12, 2025
e0685c8
Avoid printing empty errors
henryrecker-pingidentity Aug 13, 2025
0e660f1
Update client version and remove placeholder expand values in tests
henryrecker-pingidentity Aug 13, 2025
ec21f58
Changelog update
henryrecker-pingidentity Aug 13, 2025
8f86338
Cleanup
henryrecker-pingidentity Aug 15, 2025
127fcbd
Schema cleanup
henryrecker-pingidentity Aug 15, 2025
107c9d3
Cleanup todos
henryrecker-pingidentity Aug 15, 2025
da32ce1
Cleanup schema
henryrecker-pingidentity Aug 15, 2025
93012ed
Lower-case region code when getting TLD
henryrecker-pingidentity Aug 20, 2025
54c9173
Move davinci env creation sweep to separate file
henryrecker-pingidentity Aug 20, 2025
8ff187a
Update client version
henryrecker-pingidentity Aug 20, 2025
198e3d8
Add retry wrapper for new sdk
henryrecker-pingidentity Aug 20, 2025
5e3ac55
Merge branch 'DavinciVarInitialGenDraft' into DavinciConnectorInstanc…
henryrecker-pingidentity Sep 4, 2025
15413c9
Merge branch 'DavinciConnectorInstanceResource' into DavinciApplicati…
henryrecker-pingidentity Sep 4, 2025
183b445
Fix forgotten conflict
henryrecker-pingidentity Sep 4, 2025
bc209df
Merge branch 'DavinciConnectorInstanceResource' into DavinciApplicati…
henryrecker-pingidentity Sep 4, 2025
2cef201
Merge branch 'main' into DavinciConnectorInstanceResource
henryrecker-pingidentity Sep 23, 2025
38da6d5
Marking connector instance as beta
henryrecker-pingidentity Sep 23, 2025
f9f5b07
Regenerate docs, removing beta resources
henryrecker-pingidentity Sep 23, 2025
b163e5f
Add note in beta.md with example test command
henryrecker-pingidentity Sep 23, 2025
a6ad025
Make command easier to copy
henryrecker-pingidentity Sep 23, 2025
74a4d60
Remove duplicate sweep env creation
henryrecker-pingidentity Sep 23, 2025
3149c94
Merge branch 'DavinciConnectorInstanceResource' into DavinciApplicati…
henryrecker-pingidentity Sep 23, 2025
c957fce
Mark davinci_application as beta
henryrecker-pingidentity Sep 23, 2025
3f2456a
Fix incorrect test precheck
henryrecker-pingidentity Sep 23, 2025
6dd1f76
Handle false value for enforce_signed_request_openid
Oct 3, 2025
c01a946
Merge branch 'main' into DavinciConnectorInstanceResource
henryrecker-pingidentity Dec 1, 2025
304a686
Merge branch 'DavinciConnectorInstanceResource' into DavinciApplicati…
henryrecker-pingidentity Dec 1, 2025
d9ae6ba
Merge branch 'main' into DavinciApplicationResource
henryrecker-pingidentity Dec 9, 2025
ae83d73
Add null and unknown check
henryrecker-pingidentity Dec 16, 2025
f3949d0
Fix duplicate resource declaration
henryrecker-pingidentity Dec 16, 2025
112cded
Replace example.com with pingidentity.com in tests
henryrecker-pingidentity Dec 16, 2025
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
26 changes: 26 additions & 0 deletions betatemplates/resources/davinci_application.md.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}"
subcategory: "DaVinci"
description: |-
{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
---

# {{.Name}} ({{.Type}})

{{ .Description | trimspace }}

{{ if .HasExample -}}
## Example Usage

{{ tffile (printf "%s%s%s" "examples/resources/" .Name "/resource.tf") }}
{{- end }}

{{ .SchemaMarkdown | trimspace }}

{{ if .HasImport -}}
## Import

Import is supported using the following syntax, where attributes in `<>` brackets are replaced with the relevant ID. For example, `<environment_id>` should be replaced with the ID of the environment to import from.

{{ codefile "shell" (printf "%s%s%s" "examples/resources/" .Name "/import.sh") }}
{{- end }}
1 change: 1 addition & 0 deletions examples/resources/pingone_davinci_application/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import pingone_davinci_application.example <environment_id>/<application_id>
44 changes: 44 additions & 0 deletions examples/resources/pingone_davinci_application/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
resource "pingone_davinci_application" "my_awesome_application" {
environment_id = var.pingone_environment_id

name = "My Awesome Application"

oauth = {
grant_types = ["authorizationCode"]
scopes = ["openid", "profile"]
enforce_signed_request_openid = false
redirect_uris = ["https://auth.pingone.com/0000-0000-000/rp/callback/openid_connect"]
}
}

resource "pingone_davinci_application_flow_policy" "authentication_flow_policy" {
environment_id = var.pingone_environment_id
application_id = pingone_davinci_application.my_awesome_application.id

name = "PingOne - Authentication"
status = "enabled"

flow_distributions = [
{
flow_id = pingone_davinci_flow.authentication.id
version = -1
weight = 100
}
]
}

resource "pingone_davinci_application_flow_policy" "registration_flow_policy" {
environment_id = var.pingone_environment_id
application_id = pingone_davinci_application.my_awesome_application.id

name = "PingOne - Registration"
status = "enabled"

flow_distributions = [
{
flow_id = pingone_davinci_flow.registration.id
version = -1
weight = 100
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,4 @@ resource "pingone_davinci_connector_instance" "pingfederate_connector_example" {
}
}
})
}
}
3 changes: 3 additions & 0 deletions internal/framework/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,9 @@ func ParseImportID(id string, components ...ImportComponent) (map[string]string,

i := 0
for _, v := range components {
if v.Regexp == nil {
return nil, fmt.Errorf("cannot parse import ID as component %d has no Regexp", i)
}
keys[i] = v.Label
regexpList[i] = v.Regexp.String()
i++
Expand Down
201 changes: 201 additions & 0 deletions internal/service/davinci/resource_davinci_application.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// Copyright © 2025 Ping Identity Corporation

//go:build beta

package davinci

import (
"context"
"fmt"
"net/http"

"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/pingidentity/pingone-go-client/pingone"
"github.com/pingidentity/terraform-provider-pingone/internal/framework"
)

// Build the PUT client struct to be used after initial creation
func (model *davinciApplicationResourceModel) buildClientStructPutAfterCreate(createResponse *pingone.DaVinciApplicationResponse) (*pingone.DaVinciApplicationReplaceRequest, diag.Diagnostics) {
result := &pingone.DaVinciApplicationReplaceRequest{}
var respDiags diag.Diagnostics
// First copy over values from the create response
result.ApiKeyEnabled = &createResponse.ApiKey.Enabled
var grantTypes []pingone.DaVinciApplicationReplaceRequestOAuthGrantType
for _, grantType := range createResponse.Oauth.GrantTypes {
grantTypes = append(grantTypes, pingone.DaVinciApplicationReplaceRequestOAuthGrantType(grantType))
}
var scopes []pingone.DaVinciApplicationReplaceRequestOAuthScope
for _, scope := range createResponse.Oauth.Scopes {
scopes = append(scopes, pingone.DaVinciApplicationReplaceRequestOAuthScope(scope))
}
result.Oauth = &pingone.DaVinciApplicationReplaceRequestOAuth{
EnforceSignedRequestOpenid: createResponse.Oauth.EnforceSignedRequestOpenid,
GrantTypes: grantTypes,
LogoutUris: createResponse.Oauth.LogoutUris,
RedirectUris: createResponse.Oauth.RedirectUris,
Scopes: scopes,
SpJwksOpenid: createResponse.Oauth.SpJwksOpenid,
SpjwksUrl: createResponse.Oauth.SpjwksUrl,
}

// Then overwrite with anything specified in the plan model
result.Name = model.Name.ValueString()
if !model.ApiKey.IsNull() && !model.ApiKey.IsUnknown() {
if !model.ApiKey.Attributes()["enabled"].IsNull() && !model.ApiKey.Attributes()["enabled"].IsUnknown() {
result.ApiKeyEnabled = model.ApiKey.Attributes()["enabled"].(types.Bool).ValueBoolPointer()
}
}
if !model.Oauth.IsNull() && !model.Oauth.IsUnknown() {
oauthAttrs := model.Oauth.Attributes()
if !oauthAttrs["enforce_signed_request_openid"].IsNull() && !oauthAttrs["enforce_signed_request_openid"].IsUnknown() {
result.Oauth.EnforceSignedRequestOpenid = oauthAttrs["enforce_signed_request_openid"].(types.Bool).ValueBoolPointer()
}
if !oauthAttrs["grant_types"].IsNull() && !oauthAttrs["grant_types"].IsUnknown() {
result.Oauth.GrantTypes = []pingone.DaVinciApplicationReplaceRequestOAuthGrantType{}
for _, grantTypesElement := range oauthAttrs["grant_types"].(types.Set).Elements() {
var grantTypesValue pingone.DaVinciApplicationReplaceRequestOAuthGrantType
grantTypesEnumValue, err := pingone.NewDaVinciApplicationReplaceRequestOAuthGrantTypeFromValue(grantTypesElement.(types.String).ValueString())
if err != nil {
respDiags.AddAttributeError(
path.Root("grant_types"),
"Provided value is not valid",
fmt.Sprintf("The value provided for grant_types is not valid: %s", err.Error()),
)
} else {
grantTypesValue = *grantTypesEnumValue
}
result.Oauth.GrantTypes = append(result.Oauth.GrantTypes, grantTypesValue)
}
}
if !oauthAttrs["logout_uris"].IsNull() && !oauthAttrs["logout_uris"].IsUnknown() {
result.Oauth.LogoutUris = []string{}
for _, logoutUrisElement := range oauthAttrs["logout_uris"].(types.Set).Elements() {
result.Oauth.LogoutUris = append(result.Oauth.LogoutUris, logoutUrisElement.(types.String).ValueString())
}
}
if !oauthAttrs["redirect_uris"].IsNull() && !oauthAttrs["redirect_uris"].IsUnknown() {
result.Oauth.RedirectUris = []string{}
for _, redirectUrisElement := range oauthAttrs["redirect_uris"].(types.Set).Elements() {
result.Oauth.RedirectUris = append(result.Oauth.RedirectUris, redirectUrisElement.(types.String).ValueString())
}
}
if !oauthAttrs["scopes"].IsNull() && !oauthAttrs["scopes"].IsUnknown() {
result.Oauth.Scopes = []pingone.DaVinciApplicationReplaceRequestOAuthScope{}
for _, scopesElement := range oauthAttrs["scopes"].(types.Set).Elements() {
var scopesValue pingone.DaVinciApplicationReplaceRequestOAuthScope
scopesEnumValue, err := pingone.NewDaVinciApplicationReplaceRequestOAuthScopeFromValue(scopesElement.(types.String).ValueString())
if err != nil {
respDiags.AddAttributeError(
path.Root("scopes"),
"Provided value is not valid",
fmt.Sprintf("The value provided for scopes is not valid: %s", err.Error()),
)
} else {
scopesValue = *scopesEnumValue
}
result.Oauth.Scopes = append(result.Oauth.Scopes, scopesValue)
}
}
if !oauthAttrs["sp_jwks_openid"].IsNull() && !oauthAttrs["sp_jwks_openid"].IsUnknown() {
result.Oauth.SpJwksOpenid = oauthAttrs["sp_jwks_openid"].(types.String).ValueStringPointer()
}
if !oauthAttrs["sp_jwks_url"].IsNull() && !oauthAttrs["sp_jwks_url"].IsUnknown() {
result.Oauth.SpjwksUrl = oauthAttrs["sp_jwks_url"].(types.String).ValueStringPointer()
}
}

return result, respDiags
}

// Application Creates have to run a POST followed by a PUT to set fields other than name.
func (r *davinciApplicationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data davinciApplicationResourceModel

if r.Client == nil {
resp.Diagnostics.AddError(
"Client not initialized",
"Expected the PingOne client, got nil. Please report this issue to the provider maintainers.")
return
}

// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

// Create API call logic
clientData, diags := data.buildClientStructPost()
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

environmentIdUuid, err := uuid.Parse(data.EnvironmentId.ValueString())
if err != nil {
resp.Diagnostics.AddAttributeError(
path.Root("environment_id"),
"Attribute Validation Error",
fmt.Sprintf("The value '%s' for attribute '%s' is not a valid UUID: %s", data.EnvironmentId.ValueString(), "EnvironmentId", err.Error()),
)
return
}
var createResponseData *pingone.DaVinciApplicationResponse
resp.Diagnostics.Append(framework.ParseResponse(
ctx,

func() (any, *http.Response, error) {
fO, fR, fErr := r.Client.DaVinciApplicationsApi.CreateDavinciApplication(ctx, environmentIdUuid).DaVinciApplicationCreateRequest(*clientData).Execute()
return framework.CheckEnvironmentExistsOnPermissionsError(ctx, r.Client, data.EnvironmentId.ValueString(), fO, fR, fErr)
},
"CreateDavinciApplication",
framework.DefaultCustomError,
framework.DefaultRetryable,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be DefaultCreateReadRetryable (at least until we make a provider-wide effort to remove it)

&createResponseData,
)...)

if resp.Diagnostics.HasError() {
return
}

// In order to update fields, a second call to the PUT endpoint has to be made, because only name can be set on creation.
// Update API call logic
updateClientData, diags := data.buildClientStructPutAfterCreate(createResponseData)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

var responseData *pingone.DaVinciApplicationResponse
resp.Diagnostics.Append(framework.ParseResponse(
ctx,

func() (any, *http.Response, error) {
fO, fR, fErr := r.Client.DaVinciApplicationsApi.ReplaceDavinciApplicationById(ctx, environmentIdUuid, createResponseData.Id).DaVinciApplicationReplaceRequest(*updateClientData).Execute()
return framework.CheckEnvironmentExistsOnPermissionsError(ctx, r.Client, data.EnvironmentId.ValueString(), fO, fR, fErr)
},
"ReplaceDavinciApplicationById-Create",
framework.DefaultCustomError,
framework.DefaultRetryable,
&responseData,
)...)

if resp.Diagnostics.HasError() {
return
}

// Read update response into the model
resp.Diagnostics.Append(data.readClientResponse(responseData)...)

if resp.Diagnostics.HasError() {
return
}

// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
Loading