diff --git a/docs/data-sources/application_apis.md b/docs/data-sources/application_apis.md index 0d5c3c69..4a1bbfba 100644 --- a/docs/data-sources/application_apis.md +++ b/docs/data-sources/application_apis.md @@ -2,12 +2,12 @@ page_title: "zitadel_application_apis Data Source - terraform-provider-zitadel" subcategory: "" description: |- - Datasource representing an API application belonging to a project, with all configuration possibilities. + Datasource representing multiple API applications belonging to a project. --- # zitadel_application_apis (Data Source) -Datasource representing an API application belonging to a project, with all configuration possibilities. +Datasource representing multiple API applications belonging to a project. ## Example Usage diff --git a/docs/data-sources/application_oidcs.md b/docs/data-sources/application_oidcs.md index 3501ac9e..52c0aff3 100644 --- a/docs/data-sources/application_oidcs.md +++ b/docs/data-sources/application_oidcs.md @@ -2,12 +2,12 @@ page_title: "zitadel_application_oidcs Data Source - terraform-provider-zitadel" subcategory: "" description: |- - Datasource representing an OIDC application belonging to a project, with all configuration possibilities. + Datasource representing multiple OIDC applications belonging to a project. --- # zitadel_application_oidcs (Data Source) -Datasource representing an OIDC application belonging to a project, with all configuration possibilities. +Datasource representing multiple OIDC applications belonging to a project. ## Example Usage diff --git a/docs/data-sources/application_saml.md b/docs/data-sources/application_saml.md new file mode 100644 index 00000000..c9385133 --- /dev/null +++ b/docs/data-sources/application_saml.md @@ -0,0 +1,38 @@ +--- +page_title: "zitadel_application_saml Data Source - terraform-provider-zitadel" +subcategory: "" +description: |- + Datasource representing a SAML application belonging to a project, with all configuration possibilities. +--- + +# zitadel_application_saml (Data Source) + +Datasource representing a SAML application belonging to a project, with all configuration possibilities. + +## Example Usage + +```terraform +data "zitadel_application_saml" "default" { + org_id = data.zitadel_org.default.id + project_id = data.zitadel_project.default.id + app_id = "123456789012345678" +} +``` + + +## Schema + +### Required + +- `app_id` (String) The ID of this resource. +- `project_id` (String) ID of the project + +### Optional + +- `org_id` (String) ID of the organization + +### Read-Only + +- `id` (String) The ID of this resource. +- `metadata_xml` (String) Metadata as XML file +- `name` (String) Name of the application \ No newline at end of file diff --git a/docs/data-sources/application_samls.md b/docs/data-sources/application_samls.md new file mode 100644 index 00000000..6ff8735e --- /dev/null +++ b/docs/data-sources/application_samls.md @@ -0,0 +1,50 @@ +--- +page_title: "zitadel_application_samls Data Source - terraform-provider-zitadel" +subcategory: "" +description: |- + Datasource representing multiple SAML applications belonging to a project. +--- + +# zitadel_application_samls (Data Source) + +Datasource representing multiple SAML applications belonging to a project. + +## Example Usage + +```terraform +data "zitadel_application_samls" "default" { + org_id = data.zitadel_org.default.id + project_id = data.zitadel_project.default.id + name = "example-name" + name_method = "TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE" +} + +data "zitadel_application_saml" "default" { + for_each = toset(data.zitadel_application_samls.default.app_ids) + id = each.value +} + +output "app_saml_names" { + value = toset([ + for app in data.zitadel_application_saml.default : app.name + ]) +} +``` + + +## Schema + +### Required + +- `name` (String) Name of the application +- `project_id` (String) ID of the project + +### Optional + +- `name_method` (String) Method for querying applications by name, supported values: TEXT_QUERY_METHOD_EQUALS, TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE, TEXT_QUERY_METHOD_STARTS_WITH, TEXT_QUERY_METHOD_STARTS_WITH_IGNORE_CASE, TEXT_QUERY_METHOD_CONTAINS, TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE, TEXT_QUERY_METHOD_ENDS_WITH, TEXT_QUERY_METHOD_ENDS_WITH_IGNORE_CASE +- `org_id` (String) ID of the organization + +### Read-Only + +- `app_ids` (List of String) A set of all IDs. +- `id` (String) The ID of this resource. \ No newline at end of file diff --git a/docs/resources/application_saml.md b/docs/resources/application_saml.md new file mode 100644 index 00000000..7688a54f --- /dev/null +++ b/docs/resources/application_saml.md @@ -0,0 +1,45 @@ +--- +page_title: "zitadel_application_saml Resource - terraform-provider-zitadel" +subcategory: "" +description: |- + Resource representing a SAML application belonging to a project, with all configuration possibilities. +--- + +# zitadel_application_saml (Resource) + +Resource representing a SAML application belonging to a project, with all configuration possibilities. + +## Example Usage + +```terraform +resource "zitadel_application_saml" "default" { + org_id = data.zitadel_org.default.id + project_id = data.zitadel_project.default.id + name = "applicationapi" + metadata_xml = "\n\n \n urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\n \n \n \n" +} +``` + + +## Schema + +### Required + +- `metadata_xml` (String, Sensitive) Metadata as XML file +- `name` (String) Name of the application +- `project_id` (String) ID of the project + +### Optional + +- `org_id` (String) ID of the organization + +### Read-Only + +- `id` (String) The ID of this resource. + +## Import + +```terraform +# The resource can be imported using the ID format ``, e.g. +terraform import application_saml.imported '123456789012345678:123456789012345678:123456789012345678' +``` diff --git a/examples/provider/data-sources/application_saml.tf b/examples/provider/data-sources/application_saml.tf new file mode 100644 index 00000000..2dd638ab --- /dev/null +++ b/examples/provider/data-sources/application_saml.tf @@ -0,0 +1,5 @@ +data "zitadel_application_saml" "default" { + org_id = data.zitadel_org.default.id + project_id = data.zitadel_project.default.id + app_id = "123456789012345678" +} diff --git a/examples/provider/data-sources/application_samls.tf b/examples/provider/data-sources/application_samls.tf new file mode 100644 index 00000000..a9fb6d65 --- /dev/null +++ b/examples/provider/data-sources/application_samls.tf @@ -0,0 +1,17 @@ +data "zitadel_application_samls" "default" { + org_id = data.zitadel_org.default.id + project_id = data.zitadel_project.default.id + name = "example-name" + name_method = "TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE" +} + +data "zitadel_application_saml" "default" { + for_each = toset(data.zitadel_application_samls.default.app_ids) + id = each.value +} + +output "app_saml_names" { + value = toset([ + for app in data.zitadel_application_saml.default : app.name + ]) +} diff --git a/examples/provider/resources/application_saml-import.sh b/examples/provider/resources/application_saml-import.sh new file mode 100644 index 00000000..6a418a4d --- /dev/null +++ b/examples/provider/resources/application_saml-import.sh @@ -0,0 +1,2 @@ +# The resource can be imported using the ID format ``, e.g. +terraform import application_saml.imported '123456789012345678:123456789012345678:123456789012345678' diff --git a/examples/provider/resources/application_saml.tf b/examples/provider/resources/application_saml.tf new file mode 100644 index 00000000..b4da07db --- /dev/null +++ b/examples/provider/resources/application_saml.tf @@ -0,0 +1,6 @@ +resource "zitadel_application_saml" "default" { + org_id = data.zitadel_org.default.id + project_id = data.zitadel_project.default.id + name = "applicationapi" + metadata_xml = "\n\n \n urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\n \n \n \n" +} diff --git a/templates/data-sources/application_saml.md.tmpl b/templates/data-sources/application_saml.md.tmpl new file mode 100644 index 00000000..dc9679ac --- /dev/null +++ b/templates/data-sources/application_saml.md.tmpl @@ -0,0 +1,16 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/provider/data-sources/application_saml.tf" }} + +{{ .SchemaMarkdown | trimspace }} \ No newline at end of file diff --git a/templates/data-sources/application_samls.md.tmpl b/templates/data-sources/application_samls.md.tmpl new file mode 100644 index 00000000..d98fe1d3 --- /dev/null +++ b/templates/data-sources/application_samls.md.tmpl @@ -0,0 +1,16 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/provider/data-sources/application_samls.tf" }} + +{{ .SchemaMarkdown | trimspace }} \ No newline at end of file diff --git a/templates/resources/application_saml.md.tmpl b/templates/resources/application_saml.md.tmpl new file mode 100644 index 00000000..af4521b3 --- /dev/null +++ b/templates/resources/application_saml.md.tmpl @@ -0,0 +1,20 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/provider/resources/application_saml.tf" }} + +{{ .SchemaMarkdown | trimspace }} + +## Import + +{{ tffile "examples/provider/resources/application_saml-import.sh" }} diff --git a/zitadel/application_api/datasource.go b/zitadel/application_api/datasource.go index 39b26a4b..c6207f0a 100644 --- a/zitadel/application_api/datasource.go +++ b/zitadel/application_api/datasource.go @@ -41,7 +41,7 @@ func GetDatasource() *schema.Resource { func ListDatasources() *schema.Resource { return &schema.Resource{ - Description: "Datasource representing an API application belonging to a project, with all configuration possibilities.", + Description: "Datasource representing multiple API applications belonging to a project.", Schema: map[string]*schema.Schema{ helper.OrgIDVar: helper.OrgIDDatasourceField, appIDsVar: { diff --git a/zitadel/application_api/funcs.go b/zitadel/application_api/funcs.go index 78cfbffd..4ac4e63c 100644 --- a/zitadel/application_api/funcs.go +++ b/zitadel/application_api/funcs.go @@ -179,6 +179,9 @@ func list(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagn } ids := make([]string, len(resp.Result)) for i, res := range resp.Result { + if res.GetApiConfig() == nil { + continue + } ids[i] = res.Id } // If the ID is blank, the datasource is deleted and not usable. diff --git a/zitadel/application_oidc/datasource.go b/zitadel/application_oidc/datasource.go index c1aea588..3d715d34 100644 --- a/zitadel/application_oidc/datasource.go +++ b/zitadel/application_oidc/datasource.go @@ -121,7 +121,7 @@ func GetDatasource() *schema.Resource { func ListDatasources() *schema.Resource { return &schema.Resource{ - Description: "Datasource representing an OIDC application belonging to a project, with all configuration possibilities.", + Description: "Datasource representing multiple OIDC applications belonging to a project.", Schema: map[string]*schema.Schema{ helper.OrgIDVar: helper.OrgIDDatasourceField, appIDsVar: { diff --git a/zitadel/application_oidc/funcs.go b/zitadel/application_oidc/funcs.go index 691069aa..d961eb5a 100644 --- a/zitadel/application_oidc/funcs.go +++ b/zitadel/application_oidc/funcs.go @@ -284,6 +284,9 @@ func list(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagn } ids := make([]string, len(resp.Result)) for i, res := range resp.Result { + if res.GetOidcConfig() == nil { + continue + } ids[i] = res.Id } // If the ID is blank, the datasource is deleted and not usable. diff --git a/zitadel/application_saml/application_saml_test_dep/dependency.go b/zitadel/application_saml/application_saml_test_dep/dependency.go new file mode 100644 index 00000000..46a60dc3 --- /dev/null +++ b/zitadel/application_saml/application_saml_test_dep/dependency.go @@ -0,0 +1,25 @@ +package application_saml_test_dep + +import ( + "testing" + + "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/management" + + "github.com/zitadel/terraform-provider-zitadel/zitadel/application_saml" + "github.com/zitadel/terraform-provider-zitadel/zitadel/helper/test_utils" +) + +func Create(t *testing.T, frame *test_utils.OrgTestFrame, projectID, name string) (string, string) { + return test_utils.CreateDefaultDependency(t, "zitadel_application_saml", application_saml.AppIDVar, func() (string, error) { + app, err := frame.AddSAMLApp(frame, &management.AddSAMLAppRequest{ + ProjectId: projectID, + Name: name, + Metadata: &management.AddSAMLAppRequest_MetadataXml{MetadataXml: metadata(name)}, + }) + return app.GetAppId(), err + }) +} + +func metadata(name string) []byte { + return []byte("\n\n \n urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\n \n \n \n") +} diff --git a/zitadel/application_saml/const.go b/zitadel/application_saml/const.go new file mode 100644 index 00000000..a305df7a --- /dev/null +++ b/zitadel/application_saml/const.go @@ -0,0 +1,10 @@ +package application_saml + +const ( + AppIDVar = "app_id" + appIDsVar = "app_ids" + ProjectIDVar = "project_id" + NameVar = "name" + nameMethodVar = "name_method" + MetadataXMLVar = "metadata_xml" +) diff --git a/zitadel/application_saml/datasource.go b/zitadel/application_saml/datasource.go new file mode 100644 index 00000000..6d28a94c --- /dev/null +++ b/zitadel/application_saml/datasource.go @@ -0,0 +1,75 @@ +package application_saml + +import ( + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/object" + + "github.com/zitadel/terraform-provider-zitadel/zitadel/helper" +) + +func GetDatasource() *schema.Resource { + return &schema.Resource{ + Description: "Datasource representing a SAML application belonging to a project, with all configuration possibilities.", + Schema: map[string]*schema.Schema{ + AppIDVar: { + Type: schema.TypeString, + Required: true, + Description: "The ID of this resource.", + }, + helper.OrgIDVar: helper.OrgIDDatasourceField, + ProjectIDVar: { + Type: schema.TypeString, + Required: true, + Description: "ID of the project", + }, + NameVar: { + Type: schema.TypeString, + Computed: true, + Description: "Name of the application", + }, + MetadataXMLVar: { + Type: schema.TypeString, + Computed: true, + Description: "Metadata as XML file", + }, + }, + ReadContext: read, + } +} + +func ListDatasources() *schema.Resource { + return &schema.Resource{ + Description: "Datasource representing multiple SAML applications belonging to a project.", + Schema: map[string]*schema.Schema{ + helper.OrgIDVar: helper.OrgIDDatasourceField, + appIDsVar: { + Type: schema.TypeList, + Computed: true, + Description: "A set of all IDs.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + ProjectIDVar: { + Type: schema.TypeString, + Required: true, + Description: "ID of the project", + }, + NameVar: { + Type: schema.TypeString, + Required: true, + Description: "Name of the application", + }, + nameMethodVar: { + Type: schema.TypeString, + Optional: true, + Description: "Method for querying applications by name" + helper.DescriptionEnumValuesList(object.TextQueryMethod_name), + ValidateDiagFunc: func(value interface{}, path cty.Path) diag.Diagnostics { + return helper.EnumValueValidation(nameMethodVar, value, object.TextQueryMethod_value) + }, + Default: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE.String(), + }, + }, + ReadContext: list, + } +} diff --git a/zitadel/application_saml/datasource_test.go b/zitadel/application_saml/datasource_test.go new file mode 100644 index 00000000..c2eede61 --- /dev/null +++ b/zitadel/application_saml/datasource_test.go @@ -0,0 +1,105 @@ +package application_saml_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/management" + + "github.com/zitadel/terraform-provider-zitadel/zitadel/application_saml" + "github.com/zitadel/terraform-provider-zitadel/zitadel/application_saml/application_saml_test_dep" + "github.com/zitadel/terraform-provider-zitadel/zitadel/helper/test_utils" + "github.com/zitadel/terraform-provider-zitadel/zitadel/project/project_test_dep" +) + +func TestAccApplicationSAMLDatasource_ID(t *testing.T) { + datasourceName := "zitadel_application_saml" + frame := test_utils.NewOrgTestFrame(t, datasourceName) + config, attributes := test_utils.ReadExample(t, test_utils.Datasources, datasourceName) + exampleID := test_utils.AttributeValue(t, application_saml.AppIDVar, attributes).AsString() + projectDep, projectID := project_test_dep.Create(t, frame, frame.UniqueResourcesID) + appName := "application_saml_datasource_" + frame.UniqueResourcesID + _, appID := application_saml_test_dep.Create(t, frame, projectID, appName) + config = strings.Replace(config, exampleID, appID, 1) + test_utils.RunDatasourceTest( + t, + frame.BaseTestFrame, + config, + []string{frame.AsOrgDefaultDependency, projectDep}, + nil, + map[string]string{ + "org_id": frame.OrgID, + "project_id": projectID, + "app_id": appID, + "name": appName, + }, + ) +} + +func TestAccApplicationSAMLsDatasources_ID_Name_Match(t *testing.T) { + datasourceName := "zitadel_application_samls" + frame := test_utils.NewOrgTestFrame(t, datasourceName) + config, attributes := test_utils.ReadExample(t, test_utils.Datasources, datasourceName) + exampleName := test_utils.AttributeValue(t, application_saml.NameVar, attributes).AsString() + appName := fmt.Sprintf("%s-%s", exampleName, frame.UniqueResourcesID) + // for-each is not supported in acceptance tests, so we cut the example down to the first block + // https://github.com/hashicorp/terraform-plugin-sdk/issues/536 + config = strings.Join(strings.Split(config, "\n")[0:6], "\n") + config = strings.Replace(config, exampleName, appName, 1) + projectDep, projectID := project_test_dep.Create(t, frame, frame.UniqueResourcesID) + _, appID := application_saml_test_dep.Create(t, frame, projectID, appName) + test_utils.RunDatasourceTest( + t, + frame.BaseTestFrame, + config, + []string{frame.AsOrgDefaultDependency, projectDep}, + checkRemoteDatasourceProperty(frame, projectID, appID)(appName), + map[string]string{ + "app_ids.0": appID, + "app_ids.#": "1", + }, + ) +} + +func TestAccApplicationSAMLsDatasources_ID_Name_Mismatch(t *testing.T) { + datasourceName := "zitadel_application_samls" + frame := test_utils.NewOrgTestFrame(t, datasourceName) + config, attributes := test_utils.ReadExample(t, test_utils.Datasources, datasourceName) + exampleName := test_utils.AttributeValue(t, application_saml.NameVar, attributes).AsString() + appName := fmt.Sprintf("%s-%s", exampleName, frame.UniqueResourcesID) + // for-each is not supported in acceptance tests, so we cut the example down to the first block + // https://github.com/hashicorp/terraform-plugin-sdk/issues/536 + config = strings.Join(strings.Split(config, "\n")[0:6], "\n") + config = strings.Replace(config, exampleName, "mismatch", 1) + projectDep, projectID := project_test_dep.Create(t, frame, frame.UniqueResourcesID) + _, appID := application_saml_test_dep.Create(t, frame, projectID, appName) + test_utils.RunDatasourceTest( + t, + frame.BaseTestFrame, + config, + []string{frame.AsOrgDefaultDependency, projectDep}, + checkRemoteDatasourceProperty(frame, projectID, appID)(appName), + map[string]string{ + "app_ids.#": "0", + }, + ) +} + +func checkRemoteDatasourceProperty(frame *test_utils.OrgTestFrame, projectId, id string) func(string) resource.TestCheckFunc { + return func(expect string) resource.TestCheckFunc { + return func(state *terraform.State) error { + remoteResource, err := frame.GetAppByID(frame, &management.GetAppByIDRequest{AppId: id, ProjectId: projectId}) + if err != nil { + return err + } + actual := remoteResource.GetApp().GetName() + if actual != expect { + return fmt.Errorf("expected %s, but got %s", expect, actual) + } + return nil + } + } +} diff --git a/zitadel/application_saml/funcs.go b/zitadel/application_saml/funcs.go new file mode 100644 index 00000000..493c524a --- /dev/null +++ b/zitadel/application_saml/funcs.go @@ -0,0 +1,181 @@ +package application_saml + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/app" + "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/management" + "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/object" + + "github.com/zitadel/terraform-provider-zitadel/zitadel/helper" +) + +func delete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + tflog.Info(ctx, "started delete") + + clientinfo, ok := m.(*helper.ClientInfo) + if !ok { + return diag.Errorf("failed to get client") + } + + client, err := helper.GetManagementClient(clientinfo) + if err != nil { + return diag.FromErr(err) + } + + _, err = client.RemoveApp(helper.CtxWithOrgID(ctx, d), &management.RemoveAppRequest{ + ProjectId: d.Get(ProjectIDVar).(string), + AppId: d.Id(), + }) + if err != nil { + return diag.Errorf("failed to delete applicationSAML: %v", err) + } + return nil +} + +func update(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + tflog.Info(ctx, "started update") + + clientinfo, ok := m.(*helper.ClientInfo) + if !ok { + return diag.Errorf("failed to get client") + } + + client, err := helper.GetManagementClient(clientinfo) + if err != nil { + return diag.FromErr(err) + } + + projectID := d.Get(ProjectIDVar).(string) + if d.HasChange(NameVar) { + _, err = client.UpdateApp(helper.CtxWithOrgID(ctx, d), &management.UpdateAppRequest{ + ProjectId: projectID, + AppId: d.Id(), + Name: d.Get(NameVar).(string), + }) + if err != nil { + return diag.Errorf("failed to update application: %v", err) + } + } + + if d.HasChanges(MetadataXMLVar) { + _, err = client.UpdateSAMLAppConfig(helper.CtxWithOrgID(ctx, d), &management.UpdateSAMLAppConfigRequest{ + ProjectId: projectID, + AppId: d.Id(), + Metadata: &management.UpdateSAMLAppConfigRequest_MetadataXml{ + MetadataXml: []byte(d.Get(MetadataXMLVar).(string)), + }, + }) + if err != nil { + return diag.Errorf("failed to update applicationSAML: %v", err) + } + } + return nil +} + +func create(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + tflog.Info(ctx, "started create") + + clientinfo, ok := m.(*helper.ClientInfo) + if !ok { + return diag.Errorf("failed to get client") + } + + client, err := helper.GetManagementClient(clientinfo) + if err != nil { + return diag.FromErr(err) + } + + resp, err := client.AddSAMLApp(helper.CtxWithOrgID(ctx, d), &management.AddSAMLAppRequest{ + ProjectId: d.Get(ProjectIDVar).(string), + Name: d.Get(NameVar).(string), + Metadata: &management.AddSAMLAppRequest_MetadataXml{MetadataXml: []byte(d.Get(MetadataXMLVar).(string))}, + }) + if err != nil { + return diag.Errorf("failed to create applicationSAML: %v", err) + } + d.SetId(resp.GetAppId()) + return nil +} + +func read(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + tflog.Info(ctx, "started read") + + clientinfo, ok := m.(*helper.ClientInfo) + if !ok { + return diag.Errorf("failed to get client") + } + + client, err := helper.GetManagementClient(clientinfo) + if err != nil { + return diag.FromErr(err) + } + + resp, err := client.GetAppByID(helper.CtxWithOrgID(ctx, d), &management.GetAppByIDRequest{ProjectId: d.Get(ProjectIDVar).(string), AppId: helper.GetID(d, AppIDVar)}) + if err != nil && helper.IgnoreIfNotFoundError(err) == nil { + d.SetId("") + return nil + } + if err != nil { + return diag.Errorf("failed to get application saml") + } + + app := resp.GetApp() + set := map[string]interface{}{ + helper.OrgIDVar: app.GetDetails().GetResourceOwner(), + NameVar: app.GetName(), + MetadataXMLVar: string(app.GetSamlConfig().GetMetadataXml()), + } + for k, v := range set { + if err := d.Set(k, v); err != nil { + return diag.Errorf("failed to set %s of applicationSAML: %v", k, err) + } + } + d.SetId(app.GetId()) + return nil +} + +func list(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + tflog.Info(ctx, "started list") + name := d.Get(NameVar).(string) + nameMethod := d.Get(nameMethodVar).(string) + clientinfo, ok := m.(*helper.ClientInfo) + if !ok { + return diag.Errorf("failed to get client") + } + client, err := helper.GetManagementClient(clientinfo) + if err != nil { + return diag.FromErr(err) + } + req := &management.ListAppsRequest{ + ProjectId: d.Get(ProjectIDVar).(string), + } + if name != "" { + req.Queries = append(req.Queries, + &app.AppQuery{ + Query: &app.AppQuery_NameQuery{ + NameQuery: &app.AppNameQuery{ + Name: name, + Method: object.TextQueryMethod(object.TextQueryMethod_value[nameMethod]), + }, + }, + }) + } + resp, err := client.ListApps(helper.CtxWithOrgID(ctx, d), req) + if err != nil { + return diag.Errorf("error while getting app by name %s: %v", name, err) + } + ids := make([]string, len(resp.Result)) + for i, res := range resp.Result { + if res.GetSamlConfig() == nil { + continue + } + ids[i] = res.Id + } + // If the ID is blank, the datasource is deleted and not usable. + d.SetId("-") + return diag.FromErr(d.Set(appIDsVar, ids)) +} diff --git a/zitadel/application_saml/resource.go b/zitadel/application_saml/resource.go new file mode 100644 index 00000000..2bf425b7 --- /dev/null +++ b/zitadel/application_saml/resource.go @@ -0,0 +1,41 @@ +package application_saml + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/zitadel/terraform-provider-zitadel/zitadel/helper" +) + +func GetResource() *schema.Resource { + return &schema.Resource{ + Description: "Resource representing a SAML application belonging to a project, with all configuration possibilities.", + Schema: map[string]*schema.Schema{ + helper.OrgIDVar: helper.OrgIDResourceField, + ProjectIDVar: { + Type: schema.TypeString, + Required: true, + Description: "ID of the project", + ForceNew: true, + }, + NameVar: { + Type: schema.TypeString, + Required: true, + Description: "Name of the application", + }, + MetadataXMLVar: { + Type: schema.TypeString, + Required: true, + Description: "Metadata as XML file", + Sensitive: true, + }, + }, + DeleteContext: delete, + CreateContext: create, + UpdateContext: update, + ReadContext: read, + Importer: helper.ImportWithIDAndOptionalOrg( + AppIDVar, + helper.NewImportAttribute(ProjectIDVar, helper.ConvertID, false), + ), + } +} diff --git a/zitadel/application_saml/resource_test.go b/zitadel/application_saml/resource_test.go new file mode 100644 index 00000000..a751194f --- /dev/null +++ b/zitadel/application_saml/resource_test.go @@ -0,0 +1,55 @@ +package application_saml_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/management" + + "github.com/zitadel/terraform-provider-zitadel/zitadel/application_saml" + "github.com/zitadel/terraform-provider-zitadel/zitadel/helper" + "github.com/zitadel/terraform-provider-zitadel/zitadel/helper/test_utils" + "github.com/zitadel/terraform-provider-zitadel/zitadel/project/project_test_dep" +) + +func TestAccAppSAML(t *testing.T) { + frame := test_utils.NewOrgTestFrame(t, "zitadel_application_saml") + resourceExample, exampleAttributes := test_utils.ReadExample(t, test_utils.Resources, frame.ResourceType) + exampleProperty := test_utils.AttributeValue(t, application_saml.NameVar, exampleAttributes).AsString() + projectDep, projectID := project_test_dep.Create(t, frame, frame.UniqueResourcesID) + test_utils.RunLifecyleTest( + t, + frame.BaseTestFrame, + []string{frame.AsOrgDefaultDependency, projectDep}, + test_utils.ReplaceAll(resourceExample, exampleProperty, ""), + exampleProperty, "updatedproperty", + "", "", "", + false, + checkRemoteProperty(frame, projectID), + helper.ZitadelGeneratedIdOnlyRegex, + test_utils.CheckIsNotFoundFromPropertyCheck(checkRemoteProperty(frame, projectID), ""), + test_utils.ChainImportStateIdFuncs( + test_utils.ImportResourceId(frame.BaseTestFrame), + test_utils.ImportStateAttribute(frame.BaseTestFrame, application_saml.ProjectIDVar), + test_utils.ImportOrgId(frame), + ), + ) +} + +func checkRemoteProperty(frame *test_utils.OrgTestFrame, projectId string) func(string) resource.TestCheckFunc { + return func(expect string) resource.TestCheckFunc { + return func(state *terraform.State) error { + remoteResource, err := frame.GetAppByID(frame, &management.GetAppByIDRequest{AppId: frame.State(state).ID, ProjectId: projectId}) + if err != nil { + return err + } + actual := remoteResource.GetApp().GetName() + if actual != expect { + return fmt.Errorf("expected %s, but got %s", expect, actual) + } + return nil + } + } +} diff --git a/zitadel/provider.go b/zitadel/provider.go index 899e901e..86bb46d7 100644 --- a/zitadel/provider.go +++ b/zitadel/provider.go @@ -17,6 +17,7 @@ import ( "github.com/zitadel/terraform-provider-zitadel/zitadel/application_api" "github.com/zitadel/terraform-provider-zitadel/zitadel/application_key" "github.com/zitadel/terraform-provider-zitadel/zitadel/application_oidc" + "github.com/zitadel/terraform-provider-zitadel/zitadel/application_saml" "github.com/zitadel/terraform-provider-zitadel/zitadel/default_domain_claimed_message_text" "github.com/zitadel/terraform-provider-zitadel/zitadel/default_domain_policy" "github.com/zitadel/terraform-provider-zitadel/zitadel/default_init_message_text" @@ -217,6 +218,8 @@ func Provider() *schema.Provider { "zitadel_application_oidcs": application_oidc.ListDatasources(), "zitadel_application_api": application_api.GetDatasource(), "zitadel_application_apis": application_api.ListDatasources(), + "zitadel_application_saml": application_saml.GetDatasource(), + "zitadel_application_samls": application_saml.ListDatasources(), "zitadel_trigger_actions": trigger_actions.GetDatasource(), "zitadel_idp_github": idp_github.GetDatasource(), "zitadel_idp_github_es": idp_github_es.GetDatasource(), @@ -278,6 +281,7 @@ func Provider() *schema.Provider { "zitadel_action": action.GetResource(), "zitadel_application_oidc": application_oidc.GetResource(), "zitadel_application_api": application_api.GetResource(), + "zitadel_application_saml": application_saml.GetResource(), "zitadel_application_key": application_key.GetResource(), "zitadel_project_grant": project_grant.GetResource(), "zitadel_user_grant": user_grant.GetResource(),