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