diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab44ba8..6feac89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,8 @@ jobs: docker run --rm --detach \ --name trino \ --net trino \ - --volume "$(pwd)/test-data/test-trino-config.properties:/etc/trino/config.properties" \ + --volume "$(pwd)/test-data/trino/test-trino-config.properties:/etc/trino/config.properties" \ + --volume "$(pwd)/test-data/trino/catalog/hive.properties:/etc/trino/catalog/hive.properties" \ trinodb/trino:468 echo "Starting Grafana..." @@ -97,6 +98,20 @@ jobs: --volume "$(pwd):/var/lib/grafana/plugins/trino" \ --env "GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=trino-datasource" \ grafana/grafana:11.4.0 + + echo "Waiting for Trino to be ready..." + while true; do + if docker logs trino 2>&1 | grep -q '======== SERVER STARTED ========'; then + echo "Trino is ready!" + break + fi + echo "Waiting for Trino..." + sleep 5 + done + + echo "Preconfiguring trino..." + docker exec trino trino --user admin --execute "GRANT admin TO USER grafana IN hive;" + echo "Done." - name: End to end test run: | diff --git a/pkg/trino/datasource-context.go b/pkg/trino/datasource-context.go index 72c723d..029c7d5 100644 --- a/pkg/trino/datasource-context.go +++ b/pkg/trino/datasource-context.go @@ -14,6 +14,7 @@ import ( const ( accessTokenKey = "accessToken" trinoUserHeader = "X-Trino-User" + trinoRoleHeader = "X-Trino-Role" trinoClientTagsKey = "X-Trino-Client-Tags" bearerPrefix = "Bearer " ) @@ -41,6 +42,10 @@ func (ds *SQLDatasourceWithTrinoUserContext) QueryData(ctx context.Context, req ctx = context.WithValue(ctx, trinoUserHeader, user) } + if settings.Role != "" { + ctx = context.WithValue(ctx, trinoRoleHeader, settings.Role) + } + if settings.ClientTags != "" { ctx = context.WithValue(ctx, trinoClientTagsKey, settings.ClientTags) } diff --git a/pkg/trino/datasource.go b/pkg/trino/datasource.go index 9c8d8f0..92336d1 100644 --- a/pkg/trino/datasource.go +++ b/pkg/trino/datasource.go @@ -83,6 +83,7 @@ func (s *TrinoDatasource) SetQueryArgs(ctx context.Context, headers http.Header) user := ctx.Value(trinoUserHeader) accessToken := ctx.Value(accessTokenKey) + role := ctx.Value(trinoRoleHeader) clientTags := ctx.Value(trinoClientTagsKey) if user != nil { @@ -93,6 +94,10 @@ func (s *TrinoDatasource) SetQueryArgs(ctx context.Context, headers http.Header) args = append(args, sql.Named(accessTokenKey, accessToken.(string))) } + if role != nil { + args = append(args, sql.Named(trinoRoleHeader, role.(string))) + } + if clientTags != nil { args = append(args, sql.Named(trinoClientTagsKey, clientTags.(string))) } diff --git a/pkg/trino/models/settings.go b/pkg/trino/models/settings.go index 2867ab4..aff27d1 100644 --- a/pkg/trino/models/settings.go +++ b/pkg/trino/models/settings.go @@ -20,6 +20,7 @@ type TrinoDatasourceSettings struct { ClientId string `json:"clientId"` ClientSecret string `json:"clientSecret"` ImpersonationUser string `json:"impersonationUser"` + Role string `json:"role"` ClientTags string `json:"clientTags"` } diff --git a/src/ConfigEditor.tsx b/src/ConfigEditor.tsx index 4e63670..412dad0 100644 --- a/src/ConfigEditor.tsx +++ b/src/ConfigEditor.tsx @@ -34,6 +34,9 @@ export class ConfigEditor extends PureComponent { const onImpersonationUserChange = (event: ChangeEvent) => { onOptionsChange({...options, jsonData: {...options.jsonData, impersonationUser: event.target.value}}) }; + const onRoleChange = (event: ChangeEvent) => { + onOptionsChange({...options, jsonData: {...options.jsonData, role: event.target.value}}) + }; const onClientTagsChange = (event: ChangeEvent) => { onOptionsChange({...options, jsonData: {...options.jsonData, clientTags: event.target.value}}) }; @@ -75,6 +78,19 @@ export class ConfigEditor extends PureComponent { /> +
+ + + +
{ await setupDataSourceWithClientTags(page, 'tag1,tag2,tag3'); await runQueryAndCheckResults(page); }); + +test('test with role', async ({ page }) => { + await login(page); + await goToTrinoSettings(page); + await setupDataSourceWithRole(page, 'hive=ROLE{admin}'); + await runRoleQuery(page); + await expect(page.getByTestId('data-testid table body')).toContainText(/.*admin.*/); + +}); + +test('test without role', async ({ page }) => { + await login(page); + await goToTrinoSettings(page); + await setupDataSourceWithRole(page, ''); + await runRoleQuery(page); + await expect(page.getByText(/Access Denied: Cannot show roles/)).toBeVisible(); +}); + +async function runRoleQuery(page: Page) { + await page.getByLabel(EXPORT_DATA).click(); + await page.locator('div').filter({hasText: /^Format asChoose$/}).locator('svg').click(); + await page.getByRole('option', {name: 'Table'}).click(); + await setQuery(page, 'SHOW ROLES FROM hive') + await page.getByTestId('data-testid Code editor container').click(); + await page.getByTestId('data-testid RefreshPicker run button').click(); +} + +async function setQuery(page: Page, query: string) { + await page.getByTestId('data-testid Code editor container').click({ clickCount: 4 }); + await page.keyboard.type(query); +} diff --git a/src/types.ts b/src/types.ts index 528a5da..6ab52c9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -56,6 +56,7 @@ export interface TrinoDataSourceOptions extends DataSourceJsonData { tokenUrl?: string; clientId?: string; impersonationUser?: string; + role?: string; clientTags?: string; } /** diff --git a/test-data/trino/catalog/hive.properties b/test-data/trino/catalog/hive.properties new file mode 100644 index 0000000..19b6f14 --- /dev/null +++ b/test-data/trino/catalog/hive.properties @@ -0,0 +1,5 @@ +connector.name=hive +hive.metastore=file +hive.metastore.catalog.dir=/tmp/metastore +hive.security=sql-standard +fs.hadoop.enabled=true diff --git a/test-data/test-trino-config.properties b/test-data/trino/test-trino-config.properties similarity index 100% rename from test-data/test-trino-config.properties rename to test-data/trino/test-trino-config.properties