From e1792ceb111d8aa356211c94d781ba646282808a Mon Sep 17 00:00:00 2001 From: Dmitri Bourlatchkov Date: Thu, 31 Jul 2025 23:47:55 -0400 Subject: [PATCH 1/5] feat(docs): Add Getting Stated guide for MinIO A simple page of step-by-step instructions for setting up a local environment with Polaris, MinIO and Spark. Closes #1530 --- .../unreleased/getting-started/minio.md | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 site/content/in-dev/unreleased/getting-started/minio.md diff --git a/site/content/in-dev/unreleased/getting-started/minio.md b/site/content/in-dev/unreleased/getting-started/minio.md new file mode 100644 index 0000000000..ce98b24798 --- /dev/null +++ b/site/content/in-dev/unreleased/getting-started/minio.md @@ -0,0 +1,114 @@ +--- +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +Title: Using MinIO +type: docs +weight: 500 +--- + +In this guide we walk through setting up a simple Polaris Server with local [MinIO](https://www.min.io/) storage. + +Similar configurations are expected to work with other S3-compatible systems that also have the +[STS](https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html) API. + +# Setup + +Start MinIO. Here's a `docker` example. + +```shell +docker run -p 9100:9000 -p 9101:9001 --name minio \ + -e "MINIO_ROOT_USER=miniouser" -e "MINIO_ROOT_PASSWORD=miniopwd" \ + quay.io/minio/minio:latest server /data --console-address ":9001" +``` + +Create a bucket called "test" in MinIO UI or CLI. + +Edit `~/.aws/credentials` and create a `minio` profile. + +``` +[minio] +aws_access_key_id = miniouser +aws_secret_access_key = miniopwd +region=us-west-2 +``` + +In the shell where the Polaris Server will be started `export AWS_PROFILE=minio`. + +Build and run Polaris Server. + +```shell +./gradlew assemble + +env POLARIS_BOOTSTRAP_CREDENTIALS=POLARIS,root,s3cr3t \ + java -jar runtime/server/build/quarkus-app/quarkus-run.jar +``` + +Note: Apache Polaris 1.0.0-incubating does not yet have all the code required for proper operation with MinIO. +The next release is expected to provide full support for S3-compatible storage with STS. + +Create a catalog named "polaris". + +```shell +./polaris --client-id root --client-secret s3cr3t \ + catalogs create polaris --storage-type S3 \ + --default-base-location 's3://test' \ + --role-arn arn:aws:iam::123456789012:role/dummy \ + --region us-west-2 \ + --endpoint http://127.0.0.1:9100 \ + --path-style-access +``` + +Note: the role and region parameters need to be set to avoid runtime errors in Polaris, +but they will be ignored by MinIO. + +For the sake of simplicity, grant `CATALOG_MANAGE_CONTENT` directly to the `catalog_admin` role +using `polaris` CLI. + +```shell +./polaris --client-id root --client-secret s3cr3t \ + privileges catalog grant --catalog polaris \ + --catalog-role catalog_admin CATALOG_MANAGE_CONTENT +``` + +# Connecting from Spark + +Start Spark. + +```shell +bin/spark-sql \ + --packages org.apache.iceberg:iceberg-spark-runtime-3.5_2.12:1.9.0,org.apache.iceberg:iceberg-aws-bundle:1.9.0 \ + --conf spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions \ + --conf spark.sql.catalog.polaris=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.polaris.type=rest \ + --conf spark.sql.catalog.polaris.uri=http://localhost:8181/api/catalog \ + --conf spark.sql.catalog.polaris.token-refresh-enabled=false \ + --conf spark.sql.catalog.polaris.warehouse=polaris \ + --conf spark.sql.catalog.polaris.scope=PRINCIPAL_ROLE:ALL \ + --conf spark.sql.catalog.polaris.header.X-Iceberg-Access-Delegation=vended-credentials \ + --conf spark.sql.catalog.polaris.credential=root:s3cr3t +``` + +Create a table in Spark. + +```sql +use polaris; +create namespace ns; +create table ns.t1 as select 'abc'; +select * from ns.t1; +``` From 5e04c48ac7b545ab41c8e24570ecf436b5aa8646 Mon Sep 17 00:00:00 2001 From: Dmitri Bourlatchkov Date: Tue, 12 Aug 2025 20:10:53 -0400 Subject: [PATCH 2/5] review: weight 350, update title --- site/content/in-dev/unreleased/getting-started/minio.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/content/in-dev/unreleased/getting-started/minio.md b/site/content/in-dev/unreleased/getting-started/minio.md index ce98b24798..216982587b 100644 --- a/site/content/in-dev/unreleased/getting-started/minio.md +++ b/site/content/in-dev/unreleased/getting-started/minio.md @@ -17,9 +17,9 @@ # specific language governing permissions and limitations # under the License. # -Title: Using MinIO +Title: Deploying on MinIO type: docs -weight: 500 +weight: 350 --- In this guide we walk through setting up a simple Polaris Server with local [MinIO](https://www.min.io/) storage. From f7abd57d21eaa56f6537ec4a096dbffb732d29d0 Mon Sep 17 00:00:00 2001 From: Dmitri Bourlatchkov Date: Wed, 13 Aug 2025 19:15:45 -0400 Subject: [PATCH 3/5] Rework with docker compose --- .../assets/polaris/create-catalog.sh | 12 +- .../assets/polaris/obtain-token.sh | 37 ++++++ getting-started/minio/README.md | 90 ++++++++++++++ getting-started/minio/docker-compose.yml | 112 ++++++++++++++++++ .../unreleased/getting-started/minio.md | 99 ++++++++-------- 5 files changed, 296 insertions(+), 54 deletions(-) create mode 100755 getting-started/assets/polaris/obtain-token.sh create mode 100644 getting-started/minio/README.md create mode 100644 getting-started/minio/docker-compose.yml diff --git a/getting-started/assets/polaris/create-catalog.sh b/getting-started/assets/polaris/create-catalog.sh index 7d67c169c3..919e96bd57 100755 --- a/getting-started/assets/polaris/create-catalog.sh +++ b/getting-started/assets/polaris/create-catalog.sh @@ -57,12 +57,14 @@ else echo "Using StorageType: $STORAGE_TYPE" fi -STORAGE_CONFIG_INFO="{\"storageType\": \"$STORAGE_TYPE\", \"allowedLocations\": [\"$STORAGE_LOCATION\"]}" +if [ -z "${STORAGE_CONFIG_INFO}" ]; then + STORAGE_CONFIG_INFO="{\"storageType\": \"$STORAGE_TYPE\", \"allowedLocations\": [\"$STORAGE_LOCATION\"]}" -if [[ "$STORAGE_TYPE" == "S3" ]]; then - STORAGE_CONFIG_INFO=$(echo "$STORAGE_CONFIG_INFO" | jq --arg roleArn "$AWS_ROLE_ARN" '. + {roleArn: $roleArn}') -elif [[ "$STORAGE_TYPE" == "AZURE" ]]; then - STORAGE_CONFIG_INFO=$(echo "$STORAGE_CONFIG_INFO" | jq --arg tenantId "$AZURE_TENANT_ID" '. + {tenantId: $tenantId}') + if [[ "$STORAGE_TYPE" == "S3" ]]; then + STORAGE_CONFIG_INFO=$(echo "$STORAGE_CONFIG_INFO" | jq --arg roleArn "$AWS_ROLE_ARN" '. + {roleArn: $roleArn}') + elif [[ "$STORAGE_TYPE" == "AZURE" ]]; then + STORAGE_CONFIG_INFO=$(echo "$STORAGE_CONFIG_INFO" | jq --arg tenantId "$AZURE_TENANT_ID" '. + {tenantId: $tenantId}') + fi fi echo diff --git a/getting-started/assets/polaris/obtain-token.sh b/getting-started/assets/polaris/obtain-token.sh new file mode 100755 index 0000000000..a0d51776b8 --- /dev/null +++ b/getting-started/assets/polaris/obtain-token.sh @@ -0,0 +1,37 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +set -e + +apk add --no-cache jq + +realm=${1:-"POLARIS"} + +TOKEN=$(curl -s http://polaris:8181/api/catalog/v1/oauth/tokens \ + --user ${CLIENT_ID}:${CLIENT_SECRET} \ + -H "Polaris-Realm: $realm" \ + -d grant_type=client_credentials \ + -d scope=PRINCIPAL_ROLE:ALL | jq -r .access_token) + +if [ -z "${TOKEN}" ]; then + echo "Failed to obtain access token." + exit 1 +fi + +export TOKEN diff --git a/getting-started/minio/README.md b/getting-started/minio/README.md new file mode 100644 index 0000000000..5b42714588 --- /dev/null +++ b/getting-started/minio/README.md @@ -0,0 +1,90 @@ + + +# Getting Started with Apache Polaris and MinIO + +## Overview + +This example uses MinIO as a storage provider with Polaris. + +Spark is used as a query engine. This example assumes a local Spark installation. +See the [Spark Notebooks Example](../spark/README.md) for a more advanced Spark setup. + +## Starting the Example + +1. Build the Polaris server image if it's not already present locally: + + ```shell + ./gradlew \ + :polaris-server:assemble \ + :polaris-server:quarkusAppPartsBuild --rerun \ + -Dquarkus.container-image.build=true + ``` + +2. Start the docker compose group by running the following command from the root of the repository: + + ```shell + docker compose -f getting-started/minio/docker-compose.yml up + ``` + +## Connecting From Spark + +```shell +bin/spark-sql \ + --packages org.apache.iceberg:iceberg-spark-runtime-3.5_2.12:1.9.0,org.apache.iceberg:iceberg-aws-bundle:1.9.0 \ + --conf spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions \ + --conf spark.sql.catalog.polaris=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.polaris.type=rest \ + --conf spark.sql.catalog.polaris.uri=http://localhost:8181/api/catalog \ + --conf spark.sql.catalog.polaris.token-refresh-enabled=false \ + --conf spark.sql.catalog.polaris.warehouse=quickstart_catalog \ + --conf spark.sql.catalog.polaris.scope=PRINCIPAL_ROLE:ALL \ + --conf spark.sql.catalog.polaris.header.X-Iceberg-Access-Delegation=vended-credentials \ + --conf spark.sql.catalog.polaris.credential=root:s3cr3t +``` + +Note: `s3cr3t` is defined as the password for the `root` users in the `docker-compose.yml` file. + +## Running Queries + +Run inside the Spark SQL shell: + +``` +spark-sql (default)> use polaris; +Time taken: 0.837 seconds + +spark-sql ()> create namespace ns; +Time taken: 0.374 seconds + +spark-sql ()> create table ns.t1 as select 'abc'; +Time taken: 2.192 seconds + +spark-sql ()> select * from ns.t1; +abc +Time taken: 0.579 seconds, Fetched 1 row(s) +``` + +## MinIO Endpoints + +Note that the catalog configuration defined in the `docker-compose.yml` contains +different endpoints for the Polaris Server and the client (Spark). Specifically, +the client endpoint is `http://localhost:9000`, but `endpointInternal` is `http://minio:9000`. + +This is necessary because clients running on `localhost` do not normally see service +names (such as `minio`) that are internal to the docker compose environment. diff --git a/getting-started/minio/docker-compose.yml b/getting-started/minio/docker-compose.yml new file mode 100644 index 0000000000..6731476ed5 --- /dev/null +++ b/getting-started/minio/docker-compose.yml @@ -0,0 +1,112 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +services: + + minio: + image: quay.io/minio/minio:latest + ports: + # API port + - "9000:9000" + # UI port + - "9001:9001" + environment: + MINIO_ROOT_USER: minio_root + MINIO_ROOT_PASSWORD: m1n1opwd + command: + - "server" + - "/data" + - "--console-address" + - ":9001" + healthcheck: + test: ["CMD", "curl", "http://127.0.0.1:9000/minio/health/live"] + interval: 1s + timeout: 10s + + polaris: + image: apache/polaris:latest + ports: + # API port + - "8181:8181" + # Optional, allows attaching a debugger to the Polaris JVM + - "5005:5005" + depends_on: + minio: + condition: service_healthy + environment: + JAVA_DEBUG: true + JAVA_DEBUG_PORT: "*:5005" + AWS_REGION: us-west-2 + AWS_ACCESS_KEY_ID: minio_root + AWS_SECRET_ACCESS_KEY: m1n1opwd + POLARIS_BOOTSTRAP_CREDENTIALS: POLARIS,root,s3cr3t + polaris.realm-context.realms: POLARIS + quarkus.otel.sdk.disabled: "true" + healthcheck: + test: ["CMD", "curl", "http://localhost:8182/q/health"] + interval: 2s + timeout: 10s + retries: 10 + start_period: 10s + + setup_bucket: + image: quay.io/minio/mc:latest + depends_on: + minio: + condition: service_healthy + entrypoint: "/bin/sh" + command: + - "-c" + - >- + echo Creating MinIO bucket...; + mc alias set pol http://minio:9000 minio_root m1n1opwd; + mc mb pol/bucket123; + mc ls pol; + echo Bucket setup complete.; + + polaris-setup: + image: alpine/curl + depends_on: + polaris: + condition: service_healthy + environment: + - CLIENT_ID=root + - CLIENT_SECRET=s3cr3t + volumes: + - ../assets/polaris/:/polaris + entrypoint: "/bin/sh" + command: + - "-c" + - >- + chmod +x /polaris/create-catalog.sh; + chmod +x /polaris/obtain-token.sh; + source /polaris/obtain-token.sh; + echo Creating catalog...; + export STORAGE_CONFIG_INFO='{"storageType":"S3", + "endpoint":"http://localhost:9000", + "endpointInternal":"http://minio:9000", + "pathStyleAccess":true}'; + export STORAGE_LOCATION='s3://bucket123'; + /polaris/create-catalog.sh POLARIS $$TOKEN; + echo Extra grants...; + curl -H "Authorization: Bearer $$TOKEN" -H 'Content-Type: application/json' \ + -X PUT \ + http://polaris:8181/api/management/v1/catalogs/quickstart_catalog/catalog-roles/catalog_admin/grants \ + -d '{"type":"catalog", "privilege":"CATALOG_MANAGE_CONTENT"}'; + echo Done.; diff --git a/site/content/in-dev/unreleased/getting-started/minio.md b/site/content/in-dev/unreleased/getting-started/minio.md index 216982587b..a28ffd7b03 100644 --- a/site/content/in-dev/unreleased/getting-started/minio.md +++ b/site/content/in-dev/unreleased/getting-started/minio.md @@ -17,7 +17,7 @@ # specific language governing permissions and limitations # under the License. # -Title: Deploying on MinIO +Title: Deploying Polaris on MinIO type: docs weight: 350 --- @@ -29,68 +29,30 @@ Similar configurations are expected to work with other S3-compatible systems tha # Setup -Start MinIO. Here's a `docker` example. +Clone the Polaris source repository, then build a docker image for Polaris. ```shell -docker run -p 9100:9000 -p 9101:9001 --name minio \ - -e "MINIO_ROOT_USER=miniouser" -e "MINIO_ROOT_PASSWORD=miniopwd" \ - quay.io/minio/minio:latest server /data --console-address ":9001" +./gradlew :polaris-server:assemble -Dquarkus.container-image.build=true ``` -Create a bucket called "test" in MinIO UI or CLI. - -Edit `~/.aws/credentials` and create a `minio` profile. - -``` -[minio] -aws_access_key_id = miniouser -aws_secret_access_key = miniopwd -region=us-west-2 -``` - -In the shell where the Polaris Server will be started `export AWS_PROFILE=minio`. - -Build and run Polaris Server. +Start MinIO with Polaris using the `docker compose` example. ```shell -./gradlew assemble - -env POLARIS_BOOTSTRAP_CREDENTIALS=POLARIS,root,s3cr3t \ - java -jar runtime/server/build/quarkus-app/quarkus-run.jar +docker compose -f getting-started/minio/docker-compose.yml up ``` -Note: Apache Polaris 1.0.0-incubating does not yet have all the code required for proper operation with MinIO. -The next release is expected to provide full support for S3-compatible storage with STS. +The compose script will start MinIO on default ports (API on 9000, UI on 9001) +plus a Polaris Server pre-configured to that MinIO instance. -Create a catalog named "polaris". - -```shell -./polaris --client-id root --client-secret s3cr3t \ - catalogs create polaris --storage-type S3 \ - --default-base-location 's3://test' \ - --role-arn arn:aws:iam::123456789012:role/dummy \ - --region us-west-2 \ - --endpoint http://127.0.0.1:9100 \ - --path-style-access -``` - -Note: the role and region parameters need to be set to avoid runtime errors in Polaris, -but they will be ignored by MinIO. - -For the sake of simplicity, grant `CATALOG_MANAGE_CONTENT` directly to the `catalog_admin` role -using `polaris` CLI. - -```shell -./polaris --client-id root --client-secret s3cr3t \ - privileges catalog grant --catalog polaris \ - --catalog-role catalog_admin CATALOG_MANAGE_CONTENT -``` +In this example the `root` Polaris uses the password set to `s3cr3t`. # Connecting from Spark Start Spark. ```shell +export AWS_REGION=us-west-2 + bin/spark-sql \ --packages org.apache.iceberg:iceberg-spark-runtime-3.5_2.12:1.9.0,org.apache.iceberg:iceberg-aws-bundle:1.9.0 \ --conf spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions \ @@ -98,12 +60,14 @@ bin/spark-sql \ --conf spark.sql.catalog.polaris.type=rest \ --conf spark.sql.catalog.polaris.uri=http://localhost:8181/api/catalog \ --conf spark.sql.catalog.polaris.token-refresh-enabled=false \ - --conf spark.sql.catalog.polaris.warehouse=polaris \ + --conf spark.sql.catalog.polaris.warehouse=quickstart_catalog \ --conf spark.sql.catalog.polaris.scope=PRINCIPAL_ROLE:ALL \ --conf spark.sql.catalog.polaris.header.X-Iceberg-Access-Delegation=vended-credentials \ --conf spark.sql.catalog.polaris.credential=root:s3cr3t ``` +Note: `AWS_REGION` is required by the AWS SDK used by Spark, but the value is irrelevant in this case. + Create a table in Spark. ```sql @@ -112,3 +76,40 @@ create namespace ns; create table ns.t1 as select 'abc'; select * from ns.t1; ``` + +# Connecting from MinIO client + +```shell +mc alias set pol http://localhost:9000 minio_root m1n1opwd +mc ls pol/bucket123/ns/t1 +[2025-08-13 18:52:38 EDT] 0B data/ +[2025-08-13 18:52:38 EDT] 0B metadata/ +``` + +Note: the values of `minio_root`, `m1n1opwd` and `bucket123` are defined in the docker compose file. + +# Notes on Storage Configuation + +In this example the Polaris Catalog is defined as (excluding uninteresting properties): + +```json + { + "name": "quickstart_catalog", + "storageConfigInfo": { + "endpoint": "http://localhost:9000", + "endpointInternal": "http://minio:9000", + "pathStyleAccess": true, + "storageType": "S3", + "allowedLocations": [ + "s3://bucket123" + ] + } + } +``` + +Note that the `roleArn` parameter, which is required for AWS storage, does not need to be set for MinIO. + +Note the two endpoint values. `endpointInternal` is used by the Polaris Server, while `endpoint` is communicated +to clients (such as Spark) in Iceberg REST API responses. This distinction allows the system to work smoothly +when the clients and the server have different views of the network (in this example the host name `minio` is +resolvable only inside the docker compose environment). \ No newline at end of file From ebdf5289a4e48138c5d3d98340b30635a23b15cd Mon Sep 17 00:00:00 2001 From: Dmitri Bourlatchkov Date: Thu, 14 Aug 2025 17:34:41 -0400 Subject: [PATCH 4/5] review: typo --- site/content/in-dev/unreleased/getting-started/minio.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/in-dev/unreleased/getting-started/minio.md b/site/content/in-dev/unreleased/getting-started/minio.md index a28ffd7b03..3eda7db622 100644 --- a/site/content/in-dev/unreleased/getting-started/minio.md +++ b/site/content/in-dev/unreleased/getting-started/minio.md @@ -44,7 +44,7 @@ docker compose -f getting-started/minio/docker-compose.yml up The compose script will start MinIO on default ports (API on 9000, UI on 9001) plus a Polaris Server pre-configured to that MinIO instance. -In this example the `root` Polaris uses the password set to `s3cr3t`. +In this example the `root` principal has its password set to `s3cr3t`. # Connecting from Spark From d806bfd86cf5289fdd1ea758c0b8f40b58702958 Mon Sep 17 00:00:00 2001 From: Dmitri Bourlatchkov Date: Thu, 14 Aug 2025 17:46:26 -0400 Subject: [PATCH 5/5] review: share obtain-token.sh --- .../assets/polaris/create-catalog.sh | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/getting-started/assets/polaris/create-catalog.sh b/getting-started/assets/polaris/create-catalog.sh index 919e96bd57..77cee447a8 100755 --- a/getting-started/assets/polaris/create-catalog.sh +++ b/getting-started/assets/polaris/create-catalog.sh @@ -23,23 +23,16 @@ apk add --no-cache jq realm=${1:-"POLARIS"} -token=${2:-""} +TOKEN=${2:-""} -if [ -z "$token" ]; then - token=$(curl -s http://polaris:8181/api/catalog/v1/oauth/tokens \ - --user ${CLIENT_ID}:${CLIENT_SECRET} \ - -H "Polaris-Realm: $realm" \ - -d grant_type=client_credentials \ - -d scope=PRINCIPAL_ROLE:ALL | jq -r .access_token) +BASEDIR=$(dirname $0) - if [ -z "${token}" ]; then - echo "Failed to obtain access token." - exit 1 - fi +if [ -z "$TOKEN" ]; then + source $BASEDIR/obtain-token.sh fi echo -echo "Obtained access token: ${token}" +echo "Obtained access token: ${TOKEN}" STORAGE_TYPE="FILE" if [ -z "${STORAGE_LOCATION}" ]; then @@ -84,7 +77,7 @@ PAYLOAD='{ echo $PAYLOAD -curl -s -H "Authorization: Bearer ${token}" \ +curl -s -H "Authorization: Bearer ${TOKEN}" \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -H "Polaris-Realm: $realm" \