Skip to content
Merged
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
40 changes: 40 additions & 0 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
# yamllint disable rule:truthy rule:line-length
name: Claude Code

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]

jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 1

- name: Run Claude Code
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
additional_permissions: |
actions: read
14 changes: 14 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@
]
},
"default": []
},
"infrahub-vscode.showInfrahubctlWarnings": {
"type": "boolean",
"default": true,
"description": "Show warnings when infrahubctl is not available in the Python environment."
},
"infrahub-vscode.infrahubctlPath": {
"type": "string",
"description": "Custom path to infrahubctl executable. If not specified, the extension will look for infrahubctl in the active Python environment."
}
}
},
Expand Down Expand Up @@ -158,6 +167,11 @@
"title": "Visualize Schema",
"icon": "$(graph)",
"category": "Infrahub"
},
{
"command": "infrahub.showInfrahubctlGuidance",
"title": "Show infrahubctl Installation Guidance",
"category": "Infrahub"
}
],
"viewsContainers": {
Expand Down
37 changes: 36 additions & 1 deletion src/common/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as path from 'path';
import { InfrahubYamlTreeItem } from '../treeview/infrahubYamlTreeViewProvider';
import { promptForVariables, searchForConfigSchemaFiles } from '../common/infrahub';
import { BranchCreateInput } from 'infrahub-sdk/dist/graphql/branch';
import { showError, showInfo, escapeHtml, showConfirm, promptBranchAndRunInfrahubctl, getBranchPrompt, getServerPrompt, getGraphQLResultHtml, runInfrahubctlInTerminal } from '../common/utilities';
import { showError, showInfo, escapeHtml, showConfirm, promptBranchAndRunInfrahubctl, getBranchPrompt, getServerPrompt, getGraphQLResultHtml, runInfrahubctlInTerminal, checkInfrahubctlBeforeCommand } from '../common/utilities';
import { SchemaVisualizerPanel } from '../webview/SchemaVisualizerPanel';


Expand Down Expand Up @@ -231,6 +231,13 @@ export async function newBranchCommand(serverItem: any, provider: { refresh?: ()
* Finds all config schema files in the workspace and runs infrahubctl schema check on the first base path.
*/
export async function checkAllSchemaFiles() {
// Check infrahubctl availability first
const canProceed = await checkInfrahubctlBeforeCommand();
if (!canProceed) {
showInfo('Schema check cancelled.');
return;
}

const foundFiles = searchForConfigSchemaFiles();
const workspaceFolder = vscode.workspace.workspaceFolders?.[0].uri.fsPath;
if (!workspaceFolder) {
Expand All @@ -251,6 +258,13 @@ export async function checkAllSchemaFiles() {
* Finds all config schema files in the workspace and runs infrahubctl schema load on the first base path.
*/
export async function loadAllSchemaFiles() {
// Check infrahubctl availability first
const canProceed = await checkInfrahubctlBeforeCommand();
if (!canProceed) {
showInfo('Schema load cancelled.');
return;
}

const foundFiles = searchForConfigSchemaFiles();
const workspaceFolder = vscode.workspace.workspaceFolders?.[0].uri.fsPath;
if (!workspaceFolder) {
Expand All @@ -271,13 +285,27 @@ export async function loadAllSchemaFiles() {
* Runs infrahubctl schema check on a specific file.
*/
export async function checkSchemaFile(filePath: string) {
// Check infrahubctl availability first
const canProceed = await checkInfrahubctlBeforeCommand();
if (!canProceed) {
showInfo('Schema check cancelled.');
return;
}

await promptBranchAndRunInfrahubctl('check', filePath);
}

/**
* Runs infrahubctl schema load on a specific file.
*/
export async function loadSchemaFile(filePath: string) {
// Check infrahubctl availability first
const canProceed = await checkInfrahubctlBeforeCommand();
if (!canProceed) {
showInfo('Schema load cancelled.');
return;
}

await promptBranchAndRunInfrahubctl('load', filePath);
}

Expand All @@ -291,6 +319,13 @@ export async function runTransformCommand(item: InfrahubYamlTreeItem): Promise<v
return;
}

// Check infrahubctl availability first
const canProceed = await checkInfrahubctlBeforeCommand();
if (!canProceed) {
showInfo('Transform cancelled.');
return;
}

const transformationName = item.transformation.name;
const transformType = item.transform_type;

Expand Down
152 changes: 152 additions & 0 deletions src/common/infrahubctlChecker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import * as vscode from 'vscode';
import { PythonExtension } from '@vscode/python-extension';
import * as path from 'path';
import * as fs from 'fs';

export interface InfrahubctlCheckResult {
isAvailable: boolean;
path?: string;
errorMessage?: string;
pythonEnvironment?: string;
}

/**
* Utility class for checking infrahubctl availability in the current Python environment.
*/
export class InfrahubctlChecker {
private cachedResult: InfrahubctlCheckResult | null = null;
private lastCheckTime: number = 0;
private readonly CACHE_DURATION = 30000; // 30 seconds cache

/**
* Checks if infrahubctl is available in the current Python environment.
* Results are cached for 30 seconds to avoid excessive checks.
*/
public async checkInfrahubctlAvailability(): Promise<InfrahubctlCheckResult> {
const now = Date.now();
if (this.cachedResult && (now - this.lastCheckTime) < this.CACHE_DURATION) {
return this.cachedResult;
}

try {
const infrahubctlPath = await this.getInfrahubctlPath();
if (infrahubctlPath && await this.fileExists(infrahubctlPath)) {
this.cachedResult = {
isAvailable: true,
path: infrahubctlPath,
pythonEnvironment: await this.getPythonEnvironmentInfo()
};
} else {
this.cachedResult = {
isAvailable: false,
errorMessage: 'infrahubctl not found in Python environment',
pythonEnvironment: await this.getPythonEnvironmentInfo()
};
}
} catch (error) {
this.cachedResult = {
isAvailable: false,
errorMessage: `Failed to check infrahubctl: ${error instanceof Error ? error.message : 'Unknown error'}`,
pythonEnvironment: await this.getPythonEnvironmentInfo()
};
}

this.lastCheckTime = now;
return this.cachedResult;
}

/**
* Gets the resolved path to infrahubctl in the current Python environment.
* Returns null if not found or if Python extension is not available.
*/
public async getInfrahubctlPath(): Promise<string | null> {
try {
// Check if custom path is configured
const config = vscode.workspace.getConfiguration('infrahub-vscode');
const customPath = config.get<string>('infrahubctlPath');
if (customPath) {
return customPath;
}

// Get active Python environment and resolve infrahubctl path (same logic as utilities.ts)
const pythonApi: PythonExtension = await PythonExtension.api();
const environmentPathObj = pythonApi.environments.getActiveEnvironmentPath();
const pythonPath = environmentPathObj?.path || environmentPathObj?.id || '';

if (!pythonPath) {
return 'infrahubctl'; // Fallback to system PATH
}

const infrahubctlPath = path.join(path.dirname(pythonPath), 'infrahubctl');
return infrahubctlPath;
} catch (error) {
console.warn('Failed to resolve infrahubctl path:', error);
return 'infrahubctl'; // Fallback to system PATH
}
}

/**
* Gets installation guidance for infrahubctl with environment context.
*/
public async getInstallationGuidance(): Promise<string> {
const pythonEnv = await this.getPythonEnvironmentInfo();
const envInfo = pythonEnv ? `Current Python environment: ${pythonEnv}` : 'Python environment: Not detected';

return `infrahubctl is required for schema validation and transform operations.

${envInfo}

Installation options:
1. Install via uv: uv add "infrahub-sdk[all]"
2. Visit: https://docs.infrahub.app/python-sdk/guides/installation for detailed instructions

After installation, restart VS Code to refresh the extension.`;
}

/**
* Invalidates the cached result to force a fresh check.
*/
public invalidateCache(): void {
this.cachedResult = null;
this.lastCheckTime = 0;
}

/**
* Gets information about the current Python environment.
*/
private async getPythonEnvironmentInfo(): Promise<string | undefined> {
try {
const pythonApi: PythonExtension = await PythonExtension.api();
const environmentPathObj = pythonApi.environments.getActiveEnvironmentPath();
const pythonPath = environmentPathObj?.path || environmentPathObj?.id;

if (pythonPath) {
return path.dirname(pythonPath);
}
} catch (error) {
console.warn('Failed to get Python environment info:', error);
}
return undefined;
}

/**
* Checks if a file exists (cross-platform).
*/
private async fileExists(filePath: string): Promise<boolean> {
try {
await fs.promises.access(filePath, fs.constants.F_OK);
return true;
} catch {
// Try with .exe extension on Windows
if (process.platform === 'win32' && !filePath.endsWith('.exe')) {
try {
await fs.promises.access(`${filePath}.exe`, fs.constants.F_OK);
return true;
} catch {
return false;
}
}
return false;
}
}
}
Loading