From e4fa9461854bc8cafac3e54008b4bb3da6341c2b Mon Sep 17 00:00:00 2001 From: Jules Casteran Date: Mon, 7 Jul 2025 14:51:01 +0200 Subject: [PATCH] feat(keycloak_saml_client_installation_provider): support zip files Signed-off-by: Jules Casteran --- .../saml_client_installation_provider.md | 1 + ...cloak_saml_client_installation_provider.go | 50 +++++++++++++++++++ ..._saml_client_installation_provider_test.go | 48 ++++++++++++++---- 3 files changed, 90 insertions(+), 9 deletions(-) diff --git a/docs/data-sources/saml_client_installation_provider.md b/docs/data-sources/saml_client_installation_provider.md index 45dcdfdfd..509431dd6 100644 --- a/docs/data-sources/saml_client_installation_provider.md +++ b/docs/data-sources/saml_client_installation_provider.md @@ -52,3 +52,4 @@ resource "aws_iam_saml_provider" "default" { - `id` - (Computed) The hash of the value. - `value` - (Computed) The returned document needed for SAML installation. +- `zip_files` - (Computed) A map of files if the returned document is a zip file. (ex: provider `mod-auth-mellon`) diff --git a/provider/data_source_keycloak_saml_client_installation_provider.go b/provider/data_source_keycloak_saml_client_installation_provider.go index 1e15bf14a..4f1ceb68d 100644 --- a/provider/data_source_keycloak_saml_client_installation_provider.go +++ b/provider/data_source_keycloak_saml_client_installation_provider.go @@ -1,9 +1,15 @@ package provider import ( + "archive/zip" + "bytes" "context" "crypto/sha1" "encoding/base64" + "errors" + "fmt" + "io" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/keycloak/terraform-provider-keycloak/keycloak" @@ -29,6 +35,13 @@ func dataSourceKeycloakSamlClientInstallationProvider() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "zip_files": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, }, } } @@ -55,5 +68,42 @@ func dataSourceKeycloakSamlClientInstallationProviderRead(ctx context.Context, d data.Set("provider_id", providerId) data.Set("value", string(value)) + zipFiles, err := readZipFiles(value) + if err != nil { + return diag.FromErr(err) + } + data.Set("zip_files", zipFiles) + return nil } + +func readZipFiles(content []byte) (map[string]string, error) { + zipReader, err := zip.NewReader(bytes.NewReader(content), int64(len(content))) + if err != nil { + if errors.Is(err, zip.ErrFormat) { + return nil, nil + } + + return nil, fmt.Errorf("error reading zip files: %w", err) + } + + files := make(map[string]string, len(zipReader.File)) + for _, file := range zipReader.File { + fileReader, err := file.Open() + if err != nil { + return nil, fmt.Errorf("error opening zip file for reading: %w", err) + } + fileContent, err := io.ReadAll(fileReader) + if err != nil { + return nil, fmt.Errorf("error reading zip file content: %w", err) + } + files[file.FileInfo().Name()] = string(fileContent) + + err = fileReader.Close() + if err != nil { + return nil, fmt.Errorf("error closing zip file content: %w", err) + } + } + + return files, nil +} diff --git a/provider/data_source_keycloak_saml_client_installation_provider_test.go b/provider/data_source_keycloak_saml_client_installation_provider_test.go index 6c0b42913..321a86017 100644 --- a/provider/data_source_keycloak_saml_client_installation_provider_test.go +++ b/provider/data_source_keycloak_saml_client_installation_provider_test.go @@ -15,7 +15,7 @@ func TestAccKeycloakDataSourceSamlClientInstallationProvider_basic(t *testing.T) clientId := acctest.RandomWithPrefix("tf-acc") resourceName := "keycloak_saml_client.saml_client" - dataSourceName := "data.keycloak_saml_client_installation_provider.saml_sp_descriptor" + dataSourceName := "data.keycloak_saml_client_installation_provider.descriptor" resource.Test(t, resource.TestCase{ ProviderFactories: testAccProviderFactories, @@ -23,26 +23,56 @@ func TestAccKeycloakDataSourceSamlClientInstallationProvider_basic(t *testing.T) CheckDestroy: testAccCheckKeycloakSamlClientDestroy(), Steps: []resource.TestStep{ { - Config: testDataSourceKeycloakSamlClientInstallationProvider_basic(clientId), + Config: testDataSourceKeycloakSamlClientInstallationProvider(clientId, "saml-sp-descriptor"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(dataSourceName, "realm_id", resourceName, "realm_id"), resource.TestCheckResourceAttrPair(dataSourceName, "client_id", resourceName, "id"), resource.TestCheckResourceAttr(dataSourceName, "provider_id", "saml-sp-descriptor"), - testAccCheckDataKeycloakSamlClientInstallationProvider(dataSourceName), + resource.TestCheckResourceAttr(dataSourceName, "zip_files.%", "0"), + testAccCheckDataKeycloakSamlClientInstallationProvider_isXML(dataSourceName, "value"), ), }, }, }) } -func testAccCheckDataKeycloakSamlClientInstallationProvider(resourceName string) resource.TestCheckFunc { +func TestAccKeycloakDataSourceSamlClientInstallationProvider_zip(t *testing.T) { + t.Parallel() + clientId := acctest.RandomWithPrefix("tf-acc") + + resourceName := "keycloak_saml_client.saml_client" + dataSourceName := "data.keycloak_saml_client_installation_provider.descriptor" + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakSamlClientDestroy(), + Steps: []resource.TestStep{ + { + Config: testDataSourceKeycloakSamlClientInstallationProvider(clientId, "mod-auth-mellon"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "realm_id", resourceName, "realm_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "client_id", resourceName, "id"), + resource.TestCheckResourceAttr(dataSourceName, "provider_id", "mod-auth-mellon"), + resource.TestCheckResourceAttr(dataSourceName, "zip_files.%", "4"), + testAccCheckDataKeycloakSamlClientInstallationProvider_isXML(dataSourceName, "zip_files.idp-metadata.xml"), + testAccCheckDataKeycloakSamlClientInstallationProvider_isXML(dataSourceName, "zip_files.sp-metadata.xml"), + resource.TestCheckResourceAttrSet(dataSourceName, "zip_files.client-cert.pem"), + resource.TestCheckResourceAttrSet(dataSourceName, "zip_files.client-private-key.pem"), + ), + }, + }, + }) +} + +func testAccCheckDataKeycloakSamlClientInstallationProvider_isXML(resourceName string, attributeName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] if !ok { return fmt.Errorf("resource not found: %s", resourceName) } - value := rs.Primary.Attributes["value"] + value := rs.Primary.Attributes[attributeName] err := xml.Unmarshal([]byte(value), new(interface{})) if err != nil { @@ -53,7 +83,7 @@ func testAccCheckDataKeycloakSamlClientInstallationProvider(resourceName string) } } -func testDataSourceKeycloakSamlClientInstallationProvider_basic(clientId string) string { +func testDataSourceKeycloakSamlClientInstallationProvider(clientId string, providerId string) string { return fmt.Sprintf(` data "keycloak_realm" "realm" { realm = "%s" @@ -64,10 +94,10 @@ resource "keycloak_saml_client" "saml_client" { realm_id = data.keycloak_realm.realm.id } -data "keycloak_saml_client_installation_provider" "saml_sp_descriptor" { +data "keycloak_saml_client_installation_provider" "descriptor" { realm_id = data.keycloak_realm.realm.id client_id = keycloak_saml_client.saml_client.id - provider_id = "saml-sp-descriptor" + provider_id = "%s" } - `, testAccRealm.Realm, clientId) + `, testAccRealm.Realm, clientId, providerId) }