From e0a5cbf95c685adf25827aa8a080757b558bc2eb Mon Sep 17 00:00:00 2001 From: Dan Yishai Date: Mon, 3 Nov 2025 16:00:39 +0200 Subject: [PATCH] Add Dockerfile for multi-stage build, update package version to 0.2.7, and enhance Trino and PDP components with new options for column resource creation and image tagging. Adjust healthcheck intervals in Docker Compose and add access control configuration for Trino. --- Dockerfile | 49 ++++++++ package-lock.json | 4 +- source/commands/env/apply/trino.tsx | 10 ++ source/commands/pdp/run.tsx | 12 +- .../components/env/trino/TrinoComponent.tsx | 4 +- source/components/env/trino/types.ts | 1 + source/components/pdp/PDPRunComponent.tsx | 4 +- source/utils/trinoUtils.ts | 107 ++++++++++-------- tests/trino/docker-compose.yml | 4 +- .../trino_config/access-control.properties | 5 + 10 files changed, 142 insertions(+), 58 deletions(-) create mode 100644 Dockerfile create mode 100644 tests/trino/trino_config/access-control.properties diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..8c8b846d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,49 @@ +# Stage 1: Build Stage +FROM node:lts-slim AS build + +# Install libsecret for keytar +RUN apt-get update && apt-get install -y libsecret-1-0 && rm -rf /var/lib/apt/lists/* + +# Set the working directory +WORKDIR /app + +# Copy package.json and package-lock.json for caching npm install +COPY package.json package-lock.json* ./ + +# Install dependencies +RUN npm install + +# Copy the rest of the source code +COPY . . + +# Build the TypeScript code +RUN npm run build + +# Stage 2: Runtime Stage +FROM node:lts-slim + +# Install only necessary libraries for runtime +RUN apt-get update && apt-get install -y libsecret-1-0 && rm -rf /var/lib/apt/lists/* + +# Create non-root user +RUN groupadd -r cli-group && useradd -r -g cli-group cli-user + +# Set the working directory +WORKDIR /app + +# Copy only the built files and necessary runtime files from the build stage +COPY --from=build /app/dist /app/dist +COPY --from=build /app/package.json /app/package.json +COPY --from=build /app/node_modules /app/node_modules + +# Rebuild native dependencies for the current environment +RUN npm rebuild + +# Switch to non-root user +USER cli-user + +# Set the ENTRYPOINT to your CLI tool +ENTRYPOINT ["node", "/app/dist/cli.js"] + +# Provide a default argument (like --help) which users can override +CMD ["--help"] diff --git a/package-lock.json b/package-lock.json index 1b4aad58..b3dbd74c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@permitio/cli", - "version": "0.2.6", + "version": "0.2.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@permitio/cli", - "version": "0.2.6", + "version": "0.2.7", "license": "MIT", "dependencies": { "@ai-sdk/openai": "^1.3.16", diff --git a/source/commands/env/apply/trino.tsx b/source/commands/env/apply/trino.tsx index ea1d9db6..9194a728 100644 --- a/source/commands/env/apply/trino.tsx +++ b/source/commands/env/apply/trino.tsx @@ -56,6 +56,16 @@ export const options = zod.object({ alias: 's', }), ), + createColumnResources: zod + .boolean() + .optional() + .default(false) + .describe( + option({ + description: 'Create individual column resources (default: false)', + alias: 'cols', + }), + ), }); export default function Trino({ options }: { options: TrinoOptions }) { diff --git a/source/commands/pdp/run.tsx b/source/commands/pdp/run.tsx index edb8c1b5..6e01e5d6 100644 --- a/source/commands/pdp/run.tsx +++ b/source/commands/pdp/run.tsx @@ -28,16 +28,24 @@ export const options = object({ alias: 'k', }), ), + tag: string() + .default('latest') + .describe( + option({ + description: 'The tag of the PDP image to use', + alias: 't', + }), + ), }); type Props = { options: zInfer; }; -export default function Run({ options: { opa, dryRun, apiKey } }: Props) { +export default function Run({ options: { opa, dryRun, apiKey, tag } }: Props) { return ( - + ); } diff --git a/source/components/env/trino/TrinoComponent.tsx b/source/components/env/trino/TrinoComponent.tsx index 7b7b2e41..a74a5b49 100644 --- a/source/components/env/trino/TrinoComponent.tsx +++ b/source/components/env/trino/TrinoComponent.tsx @@ -21,7 +21,9 @@ export default function TrinoComponent( catalog: props.catalog, schema: props.schema, }); - const permitResources = mapTrinoSchemaToPermitResources(trinoSchema); + const permitResources = mapTrinoSchemaToPermitResources(trinoSchema, { + createColumnResources: props.createColumnResources, + }); setCreatedResources(permitResources); await processTrinoSchema(props); })(); diff --git a/source/components/env/trino/types.ts b/source/components/env/trino/types.ts index 413099dc..8c59d8dd 100644 --- a/source/components/env/trino/types.ts +++ b/source/components/env/trino/types.ts @@ -5,6 +5,7 @@ export type TrinoOptions = { password?: string; catalog?: string; schema?: string; + createColumnResources?: boolean; }; export interface PermitResource { diff --git a/source/components/pdp/PDPRunComponent.tsx b/source/components/pdp/PDPRunComponent.tsx index c9100d11..a968e315 100644 --- a/source/components/pdp/PDPRunComponent.tsx +++ b/source/components/pdp/PDPRunComponent.tsx @@ -16,6 +16,7 @@ type Props = { onComplete?: () => void; onError?: (error: string) => void; skipWaitScreen?: boolean; // New prop to control wait behavior + tag?: string; }; export default function PDPRunComponent({ @@ -24,6 +25,7 @@ export default function PDPRunComponent({ onComplete, onError, skipWaitScreen = true, // Default to showing wait screen + tag = 'latest', }: Props) { const { authToken } = useAuth(); const [loading, setLoading] = useState(true); @@ -82,7 +84,7 @@ export default function PDPRunComponent({ // Generate the Docker command const cmd = `docker run -d -p 7766:7000 ${ opa ? `-p ${opa}:8181` : '' - } -e PDP_API_KEY=${token} -e PDP_CONTROL_PLANE=${config.controlPlane || 'https://api.permit.io'} permitio/pdp-v2:latest`; + } -e PDP_API_KEY=${token} -e PDP_CONTROL_PLANE=${config.controlPlane || 'https://api.permit.io'} permitio/pdp-v2:${tag}`; setDockerCommand(cmd); diff --git a/source/utils/trinoUtils.ts b/source/utils/trinoUtils.ts index 6a951e4c..855c696e 100644 --- a/source/utils/trinoUtils.ts +++ b/source/utils/trinoUtils.ts @@ -105,20 +105,24 @@ export function trinoTypeToPermitType( /** * Map Trino schema data to Permit resources. - * - Each catalog, schema, table, and column is a resource. + * - Each catalog, schema, table, and optionally column is a resource. * - Each table resource includes columns as attributes (with type/description). + * @param trino - The Trino schema data to map + * @param options - Configuration options + * @param options.createColumnResources - Whether to create individual column resources (default: false) */ export function mapTrinoSchemaToPermitResources( trino: TrinoSchemaData, + options: { createColumnResources?: boolean } = {}, ): PermitResource[] { const resources: PermitResource[] = []; - const SEP = '-'; + const SEP = '_'; // Catalogs for (const catalog of trino.catalogs) { resources.push({ key: `trino${SEP}catalog${SEP}${catalog.name}`, - name: catalog.name, + name: `Catalog: ${catalog.name}`, description: `Trino resource type: catalog. Trino catalog: ${catalog.name}`, actions: [ 'AccessCatalog', @@ -133,7 +137,7 @@ export function mapTrinoSchemaToPermitResources( for (const schema of trino.schemas) { resources.push({ key: `trino${SEP}schema${SEP}${schema.catalog}${SEP}${schema.name}`, - name: `${schema.catalog}.${schema.name}`, + name: `Schema: ${schema.catalog}.${schema.name}`, description: `Trino resource type: schema. Schema ${schema.name} in catalog ${schema.catalog}`, actions: [ 'CreateSchema', @@ -175,7 +179,7 @@ export function mapTrinoSchemaToPermitResources( const tableKey = `trino${SEP}table${SEP}${table.catalog}${SEP}${table.schema}${SEP}${table.name}`; resources.push({ key: tableKey, - name: `${table.catalog}.${table.schema}.${table.name}`, + name: `Table: ${table.catalog}.${table.schema}.${table.name}`, description: `Trino resource type: ${table.type.toLowerCase()}. ${table.type} ${table.name} in ${table.catalog}.${table.schema}`, actions: TABLE_AND_COLUMN_ACTIONS, attributes: table.columns.reduce( @@ -202,29 +206,32 @@ export function mapTrinoSchemaToPermitResources( }, ), }); - // Columns as resources - for (const column of table.columns) { - resources.push({ - key: `trino${SEP}column${SEP}${table.catalog}${SEP}${table.schema}${SEP}${table.name}${SEP}${column.name}`, - name: `${table.catalog}.${table.schema}.${table.name}.${column.name}`, - description: `Trino resource type: column. Column ${column.name} in ${table.catalog}.${table.schema}.${table.name}`, - actions: TABLE_AND_COLUMN_ACTIONS, - attributes: { - parent_table: { - type: 'string', - description: `${table.catalog}.${table.schema}.${table.name}`, - }, - table_type: { type: 'string', description: table.type.toLowerCase() }, - type: { - type: trinoTypeToPermitType(column.type), - description: column.type, - }, - nullable: { - type: 'bool', - description: column.nullable ? 'nullable' : undefined, + + // Create column resources if requested + if (options.createColumnResources) { + for (const column of table.columns) { + resources.push({ + key: `trino${SEP}column${SEP}${table.catalog}${SEP}${table.schema}${SEP}${table.name}${SEP}${column.name}`, + name: `Column: ${table.catalog}.${table.schema}.${table.name}.${column.name}`, + description: `Trino resource type: column. Column ${column.name} in ${table.catalog}.${table.schema}.${table.name}`, + actions: TABLE_AND_COLUMN_ACTIONS, + attributes: { + parent_table: { + type: 'string', + description: `${table.catalog}.${table.schema}.${table.name}`, + }, + table_type: { type: 'string', description: table.type.toLowerCase() }, + type: { + type: trinoTypeToPermitType(column.type), + description: column.type, + }, + nullable: { + type: 'bool', + description: column.nullable ? 'nullable' : undefined, + }, }, - }, - }); + }); + } } } @@ -232,7 +239,7 @@ export function mapTrinoSchemaToPermitResources( for (const view of trino.views) { resources.push({ key: `trino${SEP}view${SEP}${view.catalog}${SEP}${view.schema}${SEP}${view.name}`, - name: `${view.catalog}.${view.schema}.${view.name}`, + name: `View: ${view.catalog}.${view.schema}.${view.name}`, description: `Trino resource type: view. View ${view.name} in ${view.catalog}.${view.schema}`, actions: [ 'CreateView', @@ -272,7 +279,7 @@ export function mapTrinoSchemaToPermitResources( for (const mview of trino.materializedViews) { resources.push({ key: `trino${SEP}materialized_view${SEP}${mview.catalog}${SEP}${mview.schema}${SEP}${mview.name}`, - name: `${mview.catalog}.${mview.schema}.${mview.name}`, + name: `Materialized View: ${mview.catalog}.${mview.schema}.${mview.name}`, description: `Trino resource type: materialized view. Materialized view ${mview.name} in ${mview.catalog}.${mview.schema}`, actions: [ 'CreateMaterializedView', @@ -311,7 +318,7 @@ export function mapTrinoSchemaToPermitResources( for (const fn of trino.functions) { resources.push({ key: `trino${SEP}function${SEP}${fn.catalog}${SEP}${fn.schema}${SEP}${fn.name}`, - name: `${fn.catalog}.${fn.schema}.${fn.name}`, + name: `Function: ${fn.catalog}.${fn.schema}.${fn.name}`, description: `Trino resource type: function. Function ${fn.name} in ${fn.catalog}.${fn.schema}`, actions: [ 'ShowFunctions', @@ -333,7 +340,7 @@ export function mapTrinoSchemaToPermitResources( for (const proc of trino.procedures) { resources.push({ key: `trino${SEP}procedure${SEP}${proc.catalog}${SEP}${proc.schema}${SEP}${proc.name}`, - name: `${proc.catalog}.${proc.schema}.${proc.name}`, + name: `Procedure: ${proc.catalog}.${proc.schema}.${proc.name}`, description: `Trino resource type: procedure. Procedure ${proc.name} in ${proc.catalog}.${proc.schema}`, actions: ['ExecuteProcedure', 'ExecuteTableProcedure'], attributes: { @@ -344,7 +351,7 @@ export function mapTrinoSchemaToPermitResources( // Add Trino System resource resources.push({ - key: 'trino_sys', + key: 'trino.sys', name: 'Trino System', description: 'Trino system-level resource for system-wide actions.', actions: [ @@ -426,20 +433,20 @@ export async function fetchTrinoFunctionsAndProceduresPassthrough( if (catalog.toLowerCase() === 'postgresql') { const passthrough = `SELECT * FROM TABLE(postgresql.system.query(query => ' - SELECT p.proname as function_name, n.nspname as schema_name, - pg_catalog.pg_get_function_result(p.oid) as return_type, - pg_catalog.pg_get_function_arguments(p.oid) as arguments, - CASE p.prokind - WHEN ''f'' THEN ''FUNCTION'' - WHEN ''p'' THEN ''PROCEDURE'' - WHEN ''a'' THEN ''AGGREGATE'' - WHEN ''w'' THEN ''WINDOW'' - ELSE p.prokind::text - END as kind - FROM pg_catalog.pg_proc p - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace - WHERE n.nspname = ''${schema}'' - AND p.proname NOT LIKE ''pg_%'' + SELECT p.proname as function_name, n.nspname as schema_name, + pg_catalog.pg_get_function_result(p.oid) as return_type, + pg_catalog.pg_get_function_arguments(p.oid) as arguments, + CASE p.prokind + WHEN ''f'' THEN ''FUNCTION'' + WHEN ''p'' THEN ''PROCEDURE'' + WHEN ''a'' THEN ''AGGREGATE'' + WHEN ''w'' THEN ''WINDOW'' + ELSE p.prokind::text + END as kind + FROM pg_catalog.pg_proc p + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace + WHERE n.nspname = ''${schema}'' + AND p.proname NOT LIKE ''pg_%'' ORDER BY p.proname '))`; try { @@ -472,8 +479,8 @@ export async function fetchTrinoFunctionsAndProceduresPassthrough( } } else if (catalog.toLowerCase() === 'mysql') { const passthrough = `SELECT * FROM TABLE(mysql.system.query(query => ' - SELECT routine_name, routine_type, data_type, routine_definition - FROM information_schema.routines + SELECT routine_name, routine_type, data_type, routine_definition + FROM information_schema.routines WHERE routine_schema = ''${schema}'' '))`; try { @@ -556,8 +563,8 @@ export async function fetchTrinoSchema( const tableComments = new Map(); try { const commentsQuery = ` - SELECT catalog_name, schema_name, table_name, comment - FROM system.metadata.table_comments + SELECT catalog_name, schema_name, table_name, comment + FROM system.metadata.table_comments WHERE comment IS NOT NULL `; const commentRows = await executeTrinoQuery(client, commentsQuery); diff --git a/tests/trino/docker-compose.yml b/tests/trino/docker-compose.yml index e7815389..7e670b7f 100644 --- a/tests/trino/docker-compose.yml +++ b/tests/trino/docker-compose.yml @@ -14,7 +14,7 @@ services: - ./sample_data/postgres_init.sql:/docker-entrypoint-initdb.d/init.sql healthcheck: test: ['CMD-SHELL', 'pg_isready -U testuser -d testdb'] - interval: 10s + interval: 5s timeout: 5s retries: 5 @@ -42,7 +42,7 @@ services: 'testuser', '-ptestpass', ] - interval: 10s + interval: 5s timeout: 5s retries: 5 diff --git a/tests/trino/trino_config/access-control.properties b/tests/trino/trino_config/access-control.properties new file mode 100644 index 00000000..96e1dac8 --- /dev/null +++ b/tests/trino/trino_config/access-control.properties @@ -0,0 +1,5 @@ +# access-control.name=allow-all +access-control.name=opa +opa.policy.uri=http://host.docker.internal:7766/trino/allowed +opa.log-requests=true +opa.log-responses=true