Skip to content

Commit 69759bd

Browse files
committed
Support X-Trino-Role header
1 parent 57c2f9c commit 69759bd

File tree

8 files changed

+85
-0
lines changed

8 files changed

+85
-0
lines changed

.github/workflows/ci.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ jobs:
8787
--name trino \
8888
--net trino \
8989
--volume "$(pwd)/test-data/trino/test-trino-config.properties:/etc/trino/config.properties" \
90+
--volume "$(pwd)/test-data/trino/catalog/hive.properties:/etc/trino/catalog/hive.properties" \
9091
trinodb/trino:468
9192
9293
echo "Starting Grafana..."
@@ -97,6 +98,20 @@ jobs:
9798
--volume "$(pwd):/var/lib/grafana/plugins/trino" \
9899
--env "GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=trino-datasource" \
99100
grafana/grafana:11.4.0
101+
102+
echo "Waiting for Trino to be ready..."
103+
while true; do
104+
if docker logs trino 2>&1 | grep -q '======== SERVER STARTED ========'; then
105+
echo "Trino is ready!"
106+
break
107+
fi
108+
echo "Waiting for Trino..."
109+
sleep 5
110+
done
111+
112+
echo "Preconfiguring trino..."
113+
docker exec trino trino --user admin --execute "GRANT admin TO USER grafana IN hive;"
114+
echo "Done."
100115
101116
- name: End to end test
102117
run: |

pkg/trino/datasource-context.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
const (
1515
accessTokenKey = "accessToken"
1616
trinoUserHeader = "X-Trino-User"
17+
trinoRoleHeader = "X-Trino-Role"
1718
trinoClientTagsKey = "X-Trino-Client-Tags"
1819
bearerPrefix = "Bearer "
1920
)
@@ -41,6 +42,10 @@ func (ds *SQLDatasourceWithTrinoUserContext) QueryData(ctx context.Context, req
4142
ctx = context.WithValue(ctx, trinoUserHeader, user)
4243
}
4344

45+
if settings.Role != "" {
46+
ctx = context.WithValue(ctx, trinoRoleHeader, settings.Role)
47+
}
48+
4449
if settings.ClientTags != "" {
4550
ctx = context.WithValue(ctx, trinoClientTagsKey, settings.ClientTags)
4651
}

pkg/trino/datasource.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ func (s *TrinoDatasource) SetQueryArgs(ctx context.Context, headers http.Header)
8383

8484
user := ctx.Value(trinoUserHeader)
8585
accessToken := ctx.Value(accessTokenKey)
86+
role := ctx.Value(trinoRoleHeader)
8687
clientTags := ctx.Value(trinoClientTagsKey)
8788

8889
if user != nil {
@@ -93,6 +94,10 @@ func (s *TrinoDatasource) SetQueryArgs(ctx context.Context, headers http.Header)
9394
args = append(args, sql.Named(accessTokenKey, accessToken.(string)))
9495
}
9596

97+
if role != nil {
98+
args = append(args, sql.Named(trinoRoleHeader, role.(string)))
99+
}
100+
96101
if clientTags != nil {
97102
args = append(args, sql.Named(trinoClientTagsKey, clientTags.(string)))
98103
}

pkg/trino/models/settings.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type TrinoDatasourceSettings struct {
2020
ClientId string `json:"clientId"`
2121
ClientSecret string `json:"clientSecret"`
2222
ImpersonationUser string `json:"impersonationUser"`
23+
Role string `json:"role"`
2324
ClientTags string `json:"clientTags"`
2425
}
2526

src/ConfigEditor.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ export class ConfigEditor extends PureComponent<Props, State> {
3434
const onImpersonationUserChange = (event: ChangeEvent<HTMLInputElement>) => {
3535
onOptionsChange({...options, jsonData: {...options.jsonData, impersonationUser: event.target.value}})
3636
};
37+
const onRoleChange = (event: ChangeEvent<HTMLInputElement>) => {
38+
onOptionsChange({...options, jsonData: {...options.jsonData, role: event.target.value}})
39+
};
3740
const onClientTagsChange = (event: ChangeEvent<HTMLInputElement>) => {
3841
onOptionsChange({...options, jsonData: {...options.jsonData, clientTags: event.target.value}})
3942
};
@@ -75,6 +78,19 @@ export class ConfigEditor extends PureComponent<Props, State> {
7578
/>
7679
</InlineField>
7780
</div>
81+
<div className="gf-form-inline">
82+
<InlineField
83+
label="Role"
84+
tooltip="For X-Trino-Role"
85+
labelWidth={26}
86+
>
87+
<Input
88+
value={options.jsonData?.role ?? ''}
89+
onChange={onRoleChange}
90+
width={40}
91+
/>
92+
</InlineField>
93+
</div>
7894
<div className="gf-form-inline">
7995
<InlineField
8096
label="Client Tags"

src/e2e.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ async function setupDataSourceWithClientTags(page: Page, clientTags: string) {
4343
await page.getByTestId('data-testid Data source settings page Save and Test button').click();
4444
}
4545

46+
async function setupDataSourceWithRole(page: Page, role: string) {
47+
await page.getByTestId('data-testid Datasource HTTP settings url').fill('http://trino:8080');
48+
await page.locator('div').filter({hasText: /^Role$/}).locator('input').fill(role);
49+
await page.getByTestId('data-testid Data source settings page Save and Test button').click();
50+
}
51+
4652
async function runQueryAndCheckResults(page: Page) {
4753
await page.getByLabel(EXPORT_DATA).click();
4854
await page.getByTestId('data-testid TimePicker Open Button').click();
@@ -91,3 +97,34 @@ test('test with client tags', async ({ page }) => {
9197
await setupDataSourceWithClientTags(page, 'tag1,tag2,tag3');
9298
await runQueryAndCheckResults(page);
9399
});
100+
101+
test('test with role', async ({ page }) => {
102+
await login(page);
103+
await goToTrinoSettings(page);
104+
await setupDataSourceWithRole(page, 'hive=ROLE{admin}');
105+
await runRoleQuery(page);
106+
await expect(page.getByTestId('data-testid table body')).toContainText(/.*admin.*/);
107+
108+
});
109+
110+
test('test without role', async ({ page }) => {
111+
await login(page);
112+
await goToTrinoSettings(page);
113+
await setupDataSourceWithRole(page, '');
114+
await runRoleQuery(page);
115+
await expect(page.getByText(/Access Denied: Cannot show roles/)).toBeVisible();
116+
});
117+
118+
async function runRoleQuery(page: Page) {
119+
await page.getByLabel(EXPORT_DATA).click();
120+
await page.locator('div').filter({hasText: /^Format asChoose$/}).locator('svg').click();
121+
await page.getByRole('option', {name: 'Table'}).click();
122+
await setQuery(page, 'SHOW ROLES FROM hive')
123+
await page.getByTestId('data-testid Code editor container').click();
124+
await page.getByTestId('data-testid RefreshPicker run button').click();
125+
}
126+
127+
async function setQuery(page: Page, query: string) {
128+
await page.getByTestId('data-testid Code editor container').click({ clickCount: 4 });
129+
await page.keyboard.type(query);
130+
}

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export interface TrinoDataSourceOptions extends DataSourceJsonData {
5656
tokenUrl?: string;
5757
clientId?: string;
5858
impersonationUser?: string;
59+
role?: string;
5960
clientTags?: string;
6061
}
6162
/**
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
connector.name=hive
2+
hive.metastore=file
3+
hive.metastore.catalog.dir=/tmp/metastore
4+
hive.security=sql-standard
5+
fs.hadoop.enabled=true

0 commit comments

Comments
 (0)