Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added command to export an environment as an HCL file #47 #57

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
],
"dependencies": {
"clipboardy": "^4.0.0",
"commander": "^13.0.0",
"delay": "^6.0.0",
"fuse.js": "^7.0.0",
"ink": "^5.1.0",
Expand All @@ -36,8 +37,8 @@
"open": "^10.1.0",
"pastel": "^3.0.0",
"permitio": "^2.7.2",
"react": "^18.2.0",
"zod": "^3.21.3"
"react": "^18.3.1",
"zod": "^3.24.1"
},
"devDependencies": {
"@eslint/js": "^9.14.0",
Expand Down
8 changes: 8 additions & 0 deletions source/commands/commands.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export { default as apiKeyCommand } from './apiKey.tsx';
export { default as loginCommand } from './login.tsx';
export { default as logoutCommand } from './logout.tsx';
export { default as githubCommand } from './gitops/create/github.tsx';
export { default as policyCommand } from './opa/policy.tsx';
export { default as checkCommand } from './pdp/check.tsx';
export { default as runCommand } from './pdp/run.tsx';
export { terraformExportCommand } from './env/export/terraform.tsx';
132 changes: 132 additions & 0 deletions source/commands/env/export/terraform.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { Command } from 'commander';
// @ts-ignore
// import { AuthProvider } from '../components/AuthProvider'; // Removed
// @ts-ignore
// import { EnvironmentSelection } from '../components/EnvironmentSelection'; // Removed
import { writeFileSync } from 'fs';
// @ts-ignore
// import { useEnvironmentApi } from '../hooks/useEnvironmentApi'; // Removed
// @ts-ignore
// import { useResourceApi } from '../hooks/useResourceApi'; // Removed
// @ts-ignore
// import { useRoleApi } from '../hooks/useRoleApi'; // Removed
// @ts-ignore
// import { useUserSetApi } from '../hooks/useUserSetApi'; // Removed
// @ts-ignore
// import { useResourceSetApi } from '../hooks/useResourceSetApi'; // Removed
// @ts-ignore
// import { useConditionSetApi } from '../hooks/useConditionSetApi'; // Removed

interface TerraformExportOptions {
key?: string;
file?: string;
}

async function fetchEnvironmentContent(apiKey: string, environmentId: string) {
// Removed the useEnvironmentApi logic
// Removed the useResourceApi logic
// Removed the useRoleApi logic
// Removed the useUserSetApi logic
// Removed the useResourceSetApi logic
// Removed the useConditionSetApi logic

const [resources, roles, userSets, resourceSets, conditionSets] = await Promise.all([
// Placeholder for resource fetching logic
]);

return {
resources,
roles,
userSets,
resourceSets,
conditionSets
};
}

function generateHCL(content: any): string {
let hcl = `# Terraform export for environment\n\n`;

// Generate resources
hcl += 'resource "permit_resource" "resources" {\n';
content.resources.forEach((resource: any) => {
hcl += ` resource "${resource.key}" {\n`;
hcl += ` name = "${resource.name}"\n`;
hcl += ` description = "${resource.description}"\n`;
hcl += ` actions = ${JSON.stringify(resource.actions)}\n`;
hcl += ` attributes = ${JSON.stringify(resource.attributes)}\n`;
hcl += ' }\n';
});
hcl += '}\n\n';

// Generate roles
hcl += 'resource "permit_role" "roles" {\n';
content.roles.forEach((role: any) => {
hcl += ` role "${role.key}" {\n`;
hcl += ` name = "${role.name}"\n`;
hcl += ` description = "${role.description}"\n`;
hcl += ` permissions = ${JSON.stringify(role.permissions)}\n`;
hcl += ' }\n';
});
hcl += '}\n\n';

// Generate user sets
hcl += 'resource "permit_user_set" "user_sets" {\n';
content.userSets.forEach((userSet: any) => {
hcl += ` user_set "${userSet.key}" {\n`;
hcl += ` name = "${userSet.name}"\n`;
hcl += ` description = "${userSet.description}"\n`;
hcl += ' }\n';
});
hcl += '}\n\n';

// Generate resource sets
hcl += 'resource "permit_resource_set" "resource_sets" {\n';
content.resourceSets.forEach((resourceSet: any) => {
hcl += ` resource_set "${resourceSet.key}" {\n`;
hcl += ` name = "${resourceSet.name}"\n`;
hcl += ` description = "${resourceSet.description}"\n`;
hcl += ' }\n';
});
hcl += '}\n\n';

// Generate condition sets
hcl += 'resource "permit_condition_set" "condition_sets" {\n';
content.conditionSets.forEach((conditionSet: any) => {
hcl += ` condition_set "${conditionSet.key}" {\n`;
hcl += ` name = "${conditionSet.name}"\n`;
hcl += ` description = "${conditionSet.description}"\n`;
hcl += ` conditions = ${JSON.stringify(conditionSet.conditions)}\n`;
hcl += ' }\n';
});
hcl += '}\n';

return hcl;
}

export const terraformExportCommand = new Command('terraform')
.description('Export environment content in Terraform provider format')
.option('--key <key>', 'API key to use for authentication')
.option('--file <file>', 'File path to save the exported HCL')
.action(async (options: TerraformExportOptions) => {
try {
const apiKey = options.key; // Removed AuthProvider logic
if (!apiKey) {
console.error('API key is required.');
process.exit(1);
}
// Removed EnvironmentSelection logic

const content = await fetchEnvironmentContent(apiKey, 'default-environment-id'); // Placeholder for environment ID
const hclContent = generateHCL(content);

if (options.file) {
writeFileSync(options.file, hclContent);
console.log(`Exported HCL to ${options.file}`);
} else {
console.log(hclContent);
}
} catch (error) {
console.error('Error exporting environment:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
43 changes: 43 additions & 0 deletions source/commands/envExportTerraform.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import { Text } from 'ink';
import zod from 'zod';
import { useEnvironmentApi } from '../hooks/useEnvironmentApi.js';

// Define command arguments schema
export const args = zod.tuple([]);

// Define command options schema
export const options = zod.object({
key: zod.string().optional().describe('Permit API key'),
file: zod.string().optional().describe('Output file path for HCL'),
projectId: zod.string().describe('Project ID'),
environmentId: zod.string().describe('Environment ID'),
});

type Props = {
args: zod.infer<typeof args>;
options: zod.infer<typeof options>;
};

export default function EnvExportTerraform({ options }: Props) {
const { getEnvironment } = useEnvironmentApi();

const fetchEnvironment = async () => {
const { projectId, environmentId, key } = options;
if (!key) {
console.error('API key is required.');
return;
}
const environment = await getEnvironment(projectId, environmentId, key);
console.log(environment); // Placeholder for actual logic
};

// Call fetchEnvironment to utilize the function
fetchEnvironment();

return (
<Text>
<Text color="green">Terraform export command</Text>
</Text>
);
}
33 changes: 33 additions & 0 deletions source/commands/graph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Command } from 'commander';
import { fetchResources, fetchRelationships, fetchRoleAssignments } from '../lib/api.ts';
import { createGraph } from '../utils/graphUtils.ts';

const program = new Command();

program
.command('fga graph')
.description('Show the graph of the Permit permissions in ReBAC')
.action(async () => {
const getToken = async () => {
return process.env['PERMIT_API_TOKEN'] || 'your_default_token'; // Use bracket notation to access the environment variable
};
const token = await getToken();
try {
const resourcesResponse = await fetchResources(token);
const relationshipsResponse = await fetchRelationships(token);
const roleAssignmentsResponse = await fetchRoleAssignments(token);

const graphData = createGraph(
resourcesResponse.response,
relationshipsResponse.response,
roleAssignmentsResponse.response
);

// Output the graph data in a user-friendly format
console.log('Graph Data:', JSON.stringify(graphData, null, 2)); // Pretty print the graph data
} catch (error) {
console.error('Error fetching data:', error);
}
});

export default program;
2 changes: 2 additions & 0 deletions source/components/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { AuthProvider } from './AuthProvider.js';
export { default as EnvironmentSelection } from './EnvironmentSelection.js';
16 changes: 16 additions & 0 deletions source/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,19 @@ export const apiCall = async <T = any>(
}
return defaultResponse;
};

// New functions to fetch resources, relationships, and role assignments
export const fetchResources = async (token: string) => {
const response = await apiCall('resources', token);
return response;
};

export const fetchRelationships = async (token: string) => {
const response = await apiCall('relationships', token);
return response;
};

export const fetchRoleAssignments = async (token: string) => {
const response = await apiCall('role-assignments', token);
return response;
};
30 changes: 30 additions & 0 deletions source/utils/graphUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
interface Resource {
id: string;
name: string;
}

interface Relationship {
sourceId: string;
targetId: string;
type: string;
}

interface RoleAssignment {
// Define the structure based on actual role assignment data
userId: string;
roleId: string;
}

export const createGraph = (resources: Resource[], relationships: Relationship[], roleAssignments: RoleAssignment[]) => {
const graph = {
nodes: resources.map(resource => ({ id: resource.id, label: resource.name })),
edges: relationships.map(rel => ({
from: rel.sourceId,
to: rel.targetId,
label: rel.type,
})),
roleAssignments: roleAssignments,
};

return graph;
};
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"jsx": "react",
"moduleResolution": "node16",
"module": "Node16",
"outDir": "dist"
"outDir": "dist",
"allowImportingTsExtensions": true,
"emitDeclarationOnly": true
},
"include": ["source"],
"exclude": ["node_modules"]
Expand Down