Prefetch: Typeahead Fields

Prefetch Reducers are used to dynamically populate a Slack prompt from an external data source.

Overview

Prefetch Reducers are useful for scenarios when you might not know every option for a given Prompt Fields ahead of time.

For example:

  • Growing datasets that live outside of Terraform, like lists of customer-specific AWS instances
  • Sensitive situations where you might want to check information about a requestor before rendering
  • Datasets that you do not control directly, as in the example below

Implementation

Prefetch Reducers are two steps to their implementation:

  1. Marking a Prompt Field with prefetch = true indicates that it will be filled via Prefetch.
  2. If using Prefetch for an Access Target, you must name your Prompt Field to match the settings attribute expected by that Target, and bind the corresponding Prompt Field to your Strategy via field_bindings (see example below)
  3. Each Prompt Field marked with prefetch = true must have a corresponding Prefetch Reducer, and there may be exactly one Prefetch Reducer for a given Prompt Field.

A Prefetch Reducer must return a list of FieldOptions, where a FieldOption defines a given item to be displayed in Slack.

The list of FieldOption returned by your reducer will dictate the allowed values a requester may select from for that Prompt Field. When filling out the request form, the requester's input will be used to search for a FieldOption based on its label.

🚧

Prefetch Reducer Limits

  1. Prefetch Reducers may return up to a maximum of 1000 items. If your reducer returns more than 1000 items, your Flow will terminate with an error.

  2. Prefetch Reducers are run before displaying the request form to users. If your reducer is slow, it may impact the performance of your requests!

  3. Prefetch reducers will be validated at runtime, and not during terraform apply. If there is a missing or duplicate prefetch reducer, an error will be raised as the user makes the request in Slack.

541

A demo of a Prompt Field that returns Pokemon names as FieldOptions

Prefetch for non-Target fields

Given the following Prompt Field definition:

prompt_field {
  name     = "pokemon"
  label    = "What Pokemon do you want?"
  type     = "string"
  prefetch = true
  required = true
}

You could define the following Prefetch Reducer:

import requests
from sym.sdk.annotations import prefetch
from sym.sdk.field_option import FieldOption


@prefetch(field_name="pokemon")
def get_pokemon(evt):
  # Make an API Call or even invoke an AWS Lambda
  response = requests.get(url="https://pokeapi.co/api/v2/pokemon?limit=100")
  all_pokemon = response.json()["results"]

  # Return a list of FieldOption
  return [
    FieldOption(value=pokemon["name"], label=pokemon["name"].upper())
    for pokemon 
    in all_pokemon
  ]

Prefetch for Access Targets

Prefetching values for an Access Target will vary depending on integration. In the below, we will show how to create a dynamic, typeahead field that uses Prefetch for a list of Okta Groups.

Static Okta Strategy

The sym_target associated with Sym's Okta Strategy expects a group_id in the settings field.

resource "sym_target" "okta" {
  type = "okta_group"

  name  = "main-okta-access"

  settings = {
    # `type=okta_group` sym_targets have a required setting `group_id`,
    # which must be ID of the Okta Group the requester will be escalated to when this target is selected
    group_id = "00g12345"
  }
}

Dynamic Okta Strategy with Prefetch

To make the above example dynamic, we will need to:

  1. Create a Prompt Field named group_id and mark it as prefetch = true
  2. Bind the field to our sym_target
  3. Provide a Prefetch Reducer to populate the field
# 1. Create a Prompt Field named `group_id` and mark it as `prefetch = true`
resource "sym_flow" "this" {
  name  = "okta"
  label = "Okta Group Access"

  implementation = file("${path.module}/impl.py")
  environment_id = sym_environment.this.id

  params {
    strategy_id = sym_strategy.okta.id

    prompt_field {
      # This prompt_field will be used to populate the `group_id` setting of the Okta Access Target.
      # The name must match the setting name and type.
      name     = "group_id"
      label    = "Okta Group"
      type     = "string"
      required = true

      # Setting prefetch = true means that this field's allowed values will be populated by
      # a prefetch reducer defined in the impl.py
      prefetch = true
    }

    prompt_field {
      name     = "reason"
      type     = "string"
      required = true
    }
  }
}
# 2. Bind the `group_id` field to our `sym_target` 
resource "sym_target" "okta" {
  type = "okta_group"

  name  = "main-okta-access"

  # A special attribute indicating which settings will be dynamically populated by prompt fields.
  # In this case, the setting is the required `group_id` setting. The value will be populated by the
  # `group_id` field in the `sym_flow.params.prompt_fields` attribute.
  field_bindings = ["group_id"]
}
from sym.sdk.annotations import prefetch
from sym.sdk.integrations import okta
from sym.sdk.forms import FieldOption


# 3. Provide a Prefetch Reducer to populate the field
@prefetch(field_name="group_id")
def get_okta_groups(event):
    all_groups = okta.list_groups()
    return [
        FieldOption(value=group["id"], label=group["profile"]["name"])
        for group in all_groups
    ]