Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
94 changes: 94 additions & 0 deletions .github/workflows/integration-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: integration-test

# baton-retool can't be exercised with a simple mock: sync + group/page provisioning
# need Retool's internal Postgres, and account provisioning needs the Retool REST API.
# This job stands up a throwaway self-hosted Retool (postgres + jobs-runner + api) via
# Docker, seeds an admin/org, mints a REST token, then runs sync + grant/revoke +
# account provisioning against it. It's heavy (image pull + DB migrations), so it runs
# on a schedule and on demand rather than on every PR.
on:
schedule:
- cron: '0 7 * * 1' # Mondays 07:00 UTC
workflow_dispatch:

jobs:
integration-test:
runs-on: ubuntu-latest
env:
RETOOL_VERSION: 3.334.17-stable
ADMIN_EMAIL: admin@example.com
ADMIN_PASSWORD: BatonCITest123!
BATON_LOG_LEVEL: info
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Build baton-retool
run: go build -o baton-retool ./cmd/baton-retool

- name: Generate Retool env (throwaway secrets for the ephemeral DB)
working-directory: test/integration
run: |
cat > docker.env <<EOF
DEPLOYMENT_TEMPLATE_TYPE=docker-compose
POSTGRES_HOST=postgres
POSTGRES_DB=hammerhead_production
POSTGRES_PORT=5432
POSTGRES_USER=retool_internal_user
POSTGRES_PASSWORD=$(openssl rand -hex 24)
JWT_SECRET=$(openssl rand -hex 64)
ENCRYPTION_KEY=$(openssl rand -hex 24)
LICENSE_KEY=EXPIRED-LICENSE-KEY-TRIAL
COOKIE_INSECURE=true
IGNORE_CODE_EXECUTOR_STARTUP_CHECK=true
DOMAINS=localhost -> http://api:3000
BASE_DOMAIN=http://localhost:3000
WORKFLOW_TEMPORAL_CLUSTER_FRONTEND_HOST=temporal
WORKFLOW_TEMPORAL_CLUSTER_FRONTEND_PORT=7233
EOF

- name: Start Retool stack
working-directory: test/integration
run: docker compose up -d

- name: Wait for Retool, seed admin, mint API token
run: |
TOKEN=$(bash test/integration/setup.sh)
PGPW=$(grep '^POSTGRES_PASSWORD=' test/integration/docker.env | cut -d= -f2-)
{
echo "BATON_RETOOL_API_TOKEN=$TOKEN"
echo "BATON_RETOOL_API_BASE_URL=http://localhost:3000"
echo "BATON_CONNECTION_STRING=user=retool_internal_user password=$PGPW host=localhost port=5432 dbname=hammerhead_production sslmode=disable"
} >> "$GITHUB_ENV"

- name: Download Baton
uses: ConductorOne/github-workflows/actions/get-baton@v2

- name: Run sync
run: ./baton-retool

- name: Grant/Revoke group membership
uses: ConductorOne/github-workflows/actions/sync-test@v4
with:
connector: ./baton-retool
baton-entitlement: 'group:g2:member'
baton-principal: 'u1'
baton-principal-type: user

- name: Account provisioning (create -> delete -> dup-delete)
run: bash test/integration/provisioning-test.sh

- name: Dump Retool logs on failure
if: failure()
working-directory: test/integration
run: docker compose logs --no-color --tail 200

- name: Tear down
if: always()
working-directory: test/integration
run: docker compose down -v
65 changes: 50 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
# baton-retool
`baton-retool` is a connector for [Retool](https://retool.com/) built using the [Baton SDK](https://github.com/conductorone/baton-sdk). It connects directly to your primary Retool Postgres database and syncs data about users, groups, organizations, pages, and resources.
`baton-retool` is a connector for [Retool](https://retool.com/) built using the [Baton SDK](https://github.com/conductorone/baton-sdk). It connects directly to your primary Retool Postgres database and syncs data about users, groups, organizations, pages, and resources. It can also provision and deprovision Retool user accounts via the Retool REST API.

Check out [Baton](https://github.com/conductorone/baton) to learn more the project in general.

## Capabilities

| Capability | Status |
|------------|--------|
| Sync (users, groups, organizations, pages, resources) | Yes |
| Provisioning (Grant/Revoke) | Yes — group membership and page access |
| Account Creation | Yes — via the Retool REST API |
| Account Deletion | Yes — via the Retool REST API (deactivates the account; see note below) |
| Event Feeds | No |

> **Note on account deletion:** Retool's REST API has no hard delete. Deprovisioning **deactivates** the user (blocks sign-in) and keeps their group memberships; the account can be reactivated in Retool. Account provisioning requires the `retool-api-base-url` and `retool-api-token` settings; sync and group/page provisioning need only the `connection-string`.

# Getting Started

## Setup
Expand Down Expand Up @@ -32,6 +44,27 @@ GRANT DELETE ON user_groups TO baton;
BATON_CONNECTION_STRING="user=baton password=baton host=localhost port=5432 dbname=hammerhead_production" baton-retool
```

4. (Optional) To enable account provisioning/deprovisioning, create a Retool API token (**Settings → API**) with the `users:read` and `users:write` scopes, then also set the REST configuration. Both fields are required together; omit them for sync-only deployments.
```bash
BATON_CONNECTION_STRING="user=baton password=baton host=localhost port=5432 dbname=hammerhead_production" \
BATON_RETOOL_API_BASE_URL="https://your-org.retool.com" \
BATON_RETOOL_API_TOKEN="retool_xxxxxxxx" \
baton-retool --provisioning
```

## Configuration

| Flag | Env Var | Required | Description |
|------|---------|----------|-------------|
| `--connection-string` | `BATON_CONNECTION_STRING` | Yes | Postgres DSN for the Retool database |
| `--retool-api-base-url` | `BATON_RETOOL_API_BASE_URL` | No* | Retool REST base URL (e.g. `https://your-org.retool.com`) — required for account provisioning |
| `--retool-api-token` | `BATON_RETOOL_API_TOKEN` | No* | Retool API token (`users:read` + `users:write`) — required for account provisioning |
| `--skip-pages` | `BATON_SKIP_PAGES` | No | Skip syncing pages |
| `--skip-resources` | `BATON_SKIP_RESOURCES` | No | Skip syncing resources |
| `--skip-disabled-users` | `BATON_SKIP_DISABLED_USERS` | No | Skip syncing disabled users |

\* `retool-api-base-url` and `retool-api-token` are required together (both or neither).

## brew

```
Expand Down Expand Up @@ -87,20 +120,22 @@ Available Commands:
help Help about any command

Flags:
--client-id string The client ID used to authenticate with ConductorOne ($BATON_CLIENT_ID)
--client-secret string The client secret used to authenticate with ConductorOne ($BATON_CLIENT_SECRET)
--connection-string string required: The connection string for connecting to retool database ($BATON_CONNECTION_STRING)
-f, --file string The path to the c1z file to sync with ($BATON_FILE) (default "sync.c1z")
-h, --help help for baton-retool
--log-format string The output format for logs: json, console ($BATON_LOG_FORMAT) (default "json")
--log-level string The log level: debug, info, warn, error ($BATON_LOG_LEVEL) (default "info")
-p, --provisioning This must be set in order for provisioning actions to be enabled ($BATON_PROVISIONING)
--skip-disabled-users Skip syncing disabled users ($BATON_SKIP_DISABLED_USERS)
--skip-full-sync This must be set to skip a full sync ($BATON_SKIP_FULL_SYNC)
--skip-pages Skip syncing pages ($BATON_SKIP_PAGES)
--skip-resources Skip syncing resources ($BATON_SKIP_RESOURCES)
--ticketing This must be set to enable ticketing support ($BATON_TICKETING)
-v, --version version for baton-retool
--client-id string The client ID used to authenticate with ConductorOne ($BATON_CLIENT_ID)
--client-secret string The client secret used to authenticate with ConductorOne ($BATON_CLIENT_SECRET)
--connection-string string required: The connection string for connecting to retool database ($BATON_CONNECTION_STRING)
-f, --file string The path to the c1z file to sync with ($BATON_FILE) (default "sync.c1z")
-h, --help help for baton-retool
--log-format string The output format for logs: json, console ($BATON_LOG_FORMAT) (default "json")
--log-level string The log level: debug, info, warn, error ($BATON_LOG_LEVEL) (default "info")
-p, --provisioning This must be set in order for provisioning actions to be enabled ($BATON_PROVISIONING)
--retool-api-base-url string Base URL of the Retool REST API, e.g. https://<org>.retool.com. Required only for account provisioning/deprovisioning. ($BATON_RETOOL_API_BASE_URL)
--retool-api-token string Retool API token with users:read + users:write. Required only for account provisioning/deprovisioning. ($BATON_RETOOL_API_TOKEN)
--skip-disabled-users Skip syncing disabled users ($BATON_SKIP_DISABLED_USERS)
--skip-full-sync This must be set to skip a full sync ($BATON_SKIP_FULL_SYNC)
--skip-pages Skip syncing pages ($BATON_SKIP_PAGES)
--skip-resources Skip syncing resources ($BATON_SKIP_RESOURCES)
--ticketing This must be set to enable ticketing support ($BATON_TICKETING)
-v, --version version for baton-retool

Use "baton-retool [command] --help" for more information about a command.
```
21 changes: 20 additions & 1 deletion cmd/baton-retool/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,34 @@ var (
"skip-disabled-users",
field.WithDescription("Skip syncing disabled users"),
)
// REST surface for account provisioning/deprovisioning (CXH-1585). Optional:
// sync-only deployments keep working without these; the lifecycle handlers fail
// fast with a clear error when they are absent.
RetoolAPIBaseURL = field.StringField(
Comment thread
sergiocorral-conductorone marked this conversation as resolved.
"retool-api-base-url",
field.WithDescription("Base URL of the Retool REST API, e.g. https://<org>.retool.com. Required only for account provisioning/deprovisioning."),
)
RetoolAPIToken = field.StringField(
Comment thread
sergiocorral-conductorone marked this conversation as resolved.
"retool-api-token",
field.WithDescription("Retool API token with users:read + users:write. Required only for account provisioning/deprovisioning."),
// This SDK version predates field.WithIsSecret; WithHidden keeps the bearer
// token out of --help output and config dumps.
field.WithHidden(true),
)
Comment thread
sergiocorral-conductorone marked this conversation as resolved.
)

var configurationFields = []field.SchemaField{
ConnectionString,
SkipPages,
SkipResources,
SkipDisabledUsers,
RetoolAPIBaseURL,
RetoolAPIToken,
}

var configRelations = []field.SchemaFieldRelationship{}
// retool-api-base-url and retool-api-token are both-or-neither.
var configRelations = []field.SchemaFieldRelationship{
field.FieldsRequiredTogether(RetoolAPIBaseURL, RetoolAPIToken),
}

var configuration = field.NewConfiguration(configurationFields, configRelations...)
4 changes: 3 additions & 1 deletion cmd/baton-retool/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ func getConnector(ctx context.Context, v *viper.Viper) (types.ConnectorServer, e
skipPages := v.GetBool(SkipPages.FieldName)
skipResources := v.GetBool(SkipResources.FieldName)
skipDisabledUsers := v.GetBool(SkipDisabledUsers.FieldName)
apiBaseURL := v.GetString(RetoolAPIBaseURL.FieldName)
apiToken := v.GetString(RetoolAPIToken.FieldName)

cb, err := connector.New(ctx, connString, skipPages, skipResources, skipDisabledUsers)
cb, err := connector.New(ctx, connString, skipPages, skipResources, skipDisabledUsers, apiBaseURL, apiToken)
if err != nil {
l.Error("error creating connector builder", zap.Error(err))
return nil, err
Expand Down
54 changes: 47 additions & 7 deletions docs/connector.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,26 @@ description: C1 provides identity governance and just-in-time provisioning for R
sidebarTitle: Retool
---

{/* AUTO-GENERATED:START - capabilities
Generated from baton_capabilities.json. Do not edit manually. */}

## Capabilities

| Resource | Sync | Provision |
The Retool connector syncs the following resources:

| Resource | Sync | Provision |
| :--- | :--- | :--- |
| Accounts | <Icon icon="square-check" iconType="solid" color="#c937ae"/> | |
| Groups | <Icon icon="square-check" iconType="solid" color="#c937ae"/> | <Icon icon="square-check" iconType="solid" color="#c937ae"/> |
| Organizations | <Icon icon="square-check" iconType="solid" color="#c937ae"/> | |
| Pages (Apps) | <Icon icon="square-check" iconType="solid" color="#c937ae"/> | <Icon icon="square-check" iconType="solid" color="#c937ae"/> |
| Resources | <Icon icon="square-check" iconType="solid" color="#c937ae"/> | |
| Accounts | <Icon icon="square-check" iconType="solid" color="#c937ae"/> | <Icon icon="square-check" iconType="solid" color="#c937ae"/> Create, Delete |
| Groups | <Icon icon="square-check" iconType="solid" color="#c937ae"/> | <Icon icon="square-check" iconType="solid" color="#c937ae"/> Grant, Revoke |
| Organizations | <Icon icon="square-check" iconType="solid" color="#c937ae"/> | |
| Pages (Apps) | <Icon icon="square-check" iconType="solid" color="#c937ae"/> | <Icon icon="square-check" iconType="solid" color="#c937ae"/> Grant, Revoke |
| Resources | <Icon icon="square-check" iconType="solid" color="#c937ae"/> | |

{/* AUTO-GENERATED:END - capabilities */}

**Notes:**
- Sync and group/page provisioning use a direct connection to the Retool Postgres database (the `connection-string` setting). Account provisioning (Create / Delete) additionally uses the Retool REST API and requires the `retool-api-base-url` and `retool-api-token` settings — sync-only deployments work without them.
- Deprovisioning (Delete) **deactivates** the account (blocks sign-in) rather than permanently deleting it. The user is retained with its group memberships and can be reactivated in Retool; Retool's API does not expose a hard delete.

## Gather Retool credentials

Expand Down Expand Up @@ -56,6 +67,27 @@ Compose and save the Retool connection string you'll use when setting up the con
`"user=baton password=secure-password host=localhost port=5432 dbname=hammerhead_production"`
</Step>
</Steps>

### (Optional) Create a Retool API token for account provisioning

Account creation and deprovisioning use the Retool REST API. Skip this section if you only need sync and group/page provisioning.

<Warning>
A Retool admin must perform this task, and your Retool plan must include REST API access.
</Warning>

<Steps>
<Step>
In Retool, go to **Settings** > **API** and create a new API token.
</Step>
<Step>
Grant the token the **`users:read`** and **`users:write`** scopes (both are required to create, look up, and deprovision users).
</Step>
<Step>
Copy and save the token, and note your Retool base URL (for example `https://<your-org>.retool.com`). You'll provide both when configuring the connector.
</Step>
</Steps>

**That's it!** Next, move on to the connector configuration instructions.

## Configure the Retool connector
Expand Down Expand Up @@ -139,6 +171,10 @@ stringData:
# Retool credentials
BATON_CONNECTION_STRING: <The Retool connection string, in format "user=baton password=secure-password host=localhost port=5432 dbname=hammerhead_production">

# Optional: required only for account provisioning/deprovisioning (REST API)
BATON_RETOOL_API_BASE_URL: <Your Retool base URL, e.g. https://your-org.retool.com>
BATON_RETOOL_API_TOKEN: <Retool API token with users:read + users:write scopes>

# Optional: include if you want C1 to provision access using this connector
BATON_PROVISIONING: true
```
Expand Down Expand Up @@ -190,4 +226,8 @@ Check that the connector data uploaded correctly. In C1, click **Apps**. On the
</Steps>
**That's it!** Your Retool connector is now pulling access data into C1.
</Tab>
</Tabs>
</Tabs>

<Tip>
You can download the latest `baton-retool` release binaries from the [ConductorOne download center](https://dist.conductorone.com/ConductorOne/baton-retool).
</Tip>
Loading
Loading