diff --git a/okta/app_filter.go b/okta/app_filter.go index 8f6ab8b6a..ce78d0d4e 100644 --- a/okta/app_filter.go +++ b/okta/app_filter.go @@ -67,3 +67,17 @@ func getAppFilters(d *schema.ResourceData) (*appFilters, error) { } return filters, nil } + +func getAppsFilters(d *schema.ResourceData) (*appFilters, error) { + label := d.Get("label").(string) + labelPrefix := d.Get("label_prefix").(string) + + filters := &appFilters{ + Label: label, + LabelPrefix: labelPrefix, + } + if d.Get("active_only").(bool) { + filters.Status = fmt.Sprintf(`status eq "%s"`, statusActive) + } + return filters, nil +} diff --git a/okta/data_source_okta_apps.go b/okta/data_source_okta_apps.go new file mode 100644 index 000000000..2b497aa09 --- /dev/null +++ b/okta/data_source_okta_apps.go @@ -0,0 +1,104 @@ +package okta + +import ( + "context" + "encoding/json" + "fmt" + "hash/crc32" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceApps() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceAppsRead, + Schema: map[string]*schema.Schema{ + "active_only": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Search only active applications.", + }, + "label": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"label_prefix"}, + Description: "Searches for applications whose label or name property matches this value exactly", + }, + "label_prefix": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"label"}, + Description: "Searches for applications whose label or name property begins with this value", + }, + "apps": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "label": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "links": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceAppsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + filters, err := getAppsFilters(d) + if err != nil { + return diag.Errorf("invalid apps filters: %v", err) + } + + appsResponse, err := listApps(ctx, getOktaClientFromMetadata(m), filters, 200) + if err != nil { + return diag.Errorf("failed to list apps: %v", err) + } + d.SetId(fmt.Sprintf("%d", crc32.ChecksumIEEE([]byte(filters.String())))) + + if len(appsResponse) > 0 { + appsArr := []map[string]interface{}{} + for _, app := range appsResponse { + // Okta API for list apps uses a starts with query on label and name. + // This can yield unexpected results if an exact match is desired. + // If requested, drop apps that don't match the exact value. + if filters.Label != "" && app.Label != filters.Label && app.Name != filters.Label { + continue + } + links, _ := json.Marshal(app.Links) + app := map[string]interface{}{ + "id": app.Id, + "name": app.Name, + "label": app.Label, + "status": app.Status, + "links": string(links), + } + appsArr = append(appsArr, app) + } + _ = d.Set("apps", appsArr) + } else { + _ = d.Set("apps", make([]map[string]interface{}, 0)) + } + + return nil +} diff --git a/okta/data_source_okta_apps_test.go b/okta/data_source_okta_apps_test.go new file mode 100644 index 000000000..3199727ad --- /dev/null +++ b/okta/data_source_okta_apps_test.go @@ -0,0 +1,88 @@ +package okta + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceOktaApps_read(t *testing.T) { + mgr := newFixtureManager("datasources", apps, t.Name()) + appsCreate := appsResources + appsRead := fmt.Sprintf("%s%s", appsResources, appsDataSources) + + oktaResourceTest(t, resource.TestCase{ + PreCheck: testAccPreCheck(t), + ErrorCheck: testAccErrorChecks(t), + ProviderFactories: testAccProvidersFactories, + Steps: []resource.TestStep{ + { + Config: mgr.ConfigReplace(appsCreate), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("okta_app_oauth.test1", "id"), + resource.TestCheckResourceAttrSet("okta_app_oauth.test2", "id"), + resource.TestCheckResourceAttrSet("okta_app_oauth.test3", "id"), + ), + }, + { + Config: mgr.ConfigReplace(appsRead), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.okta_apps.test_by_exact_match", "apps.#", "1"), + resource.TestCheckResourceAttrSet("data.okta_apps.test_by_exact_match", "apps.#.id"), + resource.TestCheckResourceAttr("data.okta_apps.test_by_exact_match", "apps.#.label", fmt.Sprintf("testApp_%s_one", buildResourceName(mgr.Seed))), + resource.TestCheckResourceAttr("data.okta_apps.test_by_exact_match", "apps.#.status", statusActive), + + resource.TestCheckResourceAttr("data.okta_apps.test_by_prefix", "apps.#", "2"), + + resource.TestCheckResourceAttr("data.okta_apps.test_by_no_match", "apps.#", "0"), + ), + }, + }, + }) +} + +const appsResources = ` +resource "okta_app_oauth" "test1" { + label = "testApp_testAcc_replace_with_uuid_one" + type = "web" + grant_types = ["implicit", "authorization_code"] + redirect_uris = ["http://a.com/"] + response_types = ["code", "token", "id_token"] + issuer_mode = "ORG_URL" + consent_method = "TRUSTED" +} +resource "okta_app_oauth" "test2" { + label = "testApp_testAcc_replace_with_uuid_two" + type = "web" + grant_types = ["implicit", "authorization_code"] + redirect_uris = ["http://b.com/"] + response_types = ["code", "token", "id_token"] + issuer_mode = "ORG_URL" + consent_method = "TRUSTED" +} +resource "okta_app_oauth" "test3" { + label = "testAppInvalid_testAcc_replace_with_uuid" + type = "web" + grant_types = ["implicit", "authorization_code"] + redirect_uris = ["http://c.com/"] + response_types = ["code", "token", "id_token"] + issuer_mode = "ORG_URL" + consent_method = "TRUSTED" +} +` + +const appsDataSources = ` + +data "okta_apps" "test_by_exact_match" { + label = "testApp_testAcc_replace_with_uuid_one" +} + +data "okta_apps" "test_by_prefix" { + label_prefix = "testApp_testAcc_replace_with_uuid_" +} + +data "okta_apps" "test_by_no_match" { + label = "invalidApp_replace_with_uuid" +} +` diff --git a/okta/provider.go b/okta/provider.go index a014d1907..128b68a4d 100644 --- a/okta/provider.go +++ b/okta/provider.go @@ -21,6 +21,7 @@ const ( adminRoleCustomAssignments = "okta_admin_role_custom_assignments" adminRoleTargets = "okta_admin_role_targets" app = "okta_app" + apps = "okta_apps" appAutoLogin = "okta_app_auto_login" appBasicAuth = "okta_app_basic_auth" appBookmark = "okta_app_bookmark" @@ -331,6 +332,7 @@ func Provider() *schema.Provider { }, DataSourcesMap: map[string]*schema.Resource{ app: dataSourceApp(), + apps: dataSourceApps(), appGroupAssignments: dataSourceAppGroupAssignments(), appMetadataSaml: dataSourceAppMetadataSaml(), appOAuth: dataSourceAppOauth(), diff --git a/website/docs/d/app.html.markdown b/website/docs/d/app.html.markdown new file mode 100644 index 000000000..f4b398c8d --- /dev/null +++ b/website/docs/d/app.html.markdown @@ -0,0 +1,46 @@ +--- +layout: 'okta' +page_title: 'Okta: okta_app' +sidebar_current: 'docs-okta-datasource-app' +description: |- + Get an application of any kind from Okta. +--- + +# okta_app + +Use this data source to retrieve an application from Okta. + +## Example Usage + +```hcl +data "okta_app" "example" { + label = "Example App" +} +``` + +## Arguments Reference + +- `label` - (Optional) The label or name of the app to retrieve, conflicts with `label_prefix` and `id`. Label uses + the `?q=