diff --git a/package.json b/package.json index 71ef677dd..5a3bf542d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "activationEvents": [ "onLanguage:yaml", + "onLanguage:yml", "workspaceContains:tox-ansible.ini", "onWebviewPanel:ansible-home" ], @@ -444,6 +445,10 @@ { "command": "ansible.open-language-server-logs", "title": "Ansible: Open Language Server Logs" + }, + { + "command": "extension.buildExecutionEnvironment", + "title": "Build Ansible execution environment" } ], "configuration": [ @@ -795,6 +800,11 @@ "group": "2_main@1", "command": "ansible.lightspeed.playbookExplanation", "when": "redhat.ansible.lightspeedSuggestionsEnabled && editorLangId == ansible" + }, + { + "when": "resourceFilename == 'execution-environment.yml' || resourceFilename == 'execution-environment.yaml'", + "command": "extension.buildExecutionEnvironment", + "group": "navigation" } ], "explorer/context": [ @@ -802,6 +812,11 @@ "group": "2_main@1", "submenu": "ansible.playbook.run", "when": "isFileSystemResource && resourceLangId == ansible" + }, + { + "when": "resourceFilename == 'execution-environment.yml' || resourceFilename == 'execution-environment.yaml'", + "command": "extension.buildExecutionEnvironment", + "group": "navigation" } ], "view/title": [ diff --git a/src/extension.ts b/src/extension.ts index 2210b7ebe..b204a27e4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -75,6 +75,7 @@ import { PlaybookFeedbackEvent } from "./interfaces/lightspeed"; import { CreateDevfile } from "./features/contentCreator/createDevfilePage"; import { CreateSampleExecutionEnv } from "./features/contentCreator/createSampleExecutionEnvPage"; import { CreateDevcontainer } from "./features/contentCreator/createDevcontainerPage"; +import { rightClickEEBuildCommand } from "./features/utils/buildExecutionEnvironment"; export let client: LanguageClient; export let lightSpeedManager: LightSpeedManager; @@ -146,7 +147,6 @@ export async function activate(context: ExtensionContext): Promise { // handle python status bar const pythonInterpreterManager = new PythonInterpreterManager( context, - client, telemetry, extSettings, ); @@ -159,15 +159,14 @@ export async function activate(context: ExtensionContext): Promise { /** * Handle "Ansible Lightspeed" in the extension */ - lightSpeedManager = new LightSpeedManager( - context, - client, - extSettings, - telemetry, - ); + lightSpeedManager = new LightSpeedManager(context, extSettings, telemetry); vscode.commands.executeCommand("setContext", "lightspeedConnectReady", true); + const eeBuilderCommand = rightClickEEBuildCommand( + "extension.buildExecutionEnvironment", + ); + context.subscriptions.push( vscode.commands.registerCommand( LightSpeedCommands.LIGHTSPEED_STATUS_BAR_CLICK, @@ -827,6 +826,7 @@ export async function activate(context: ExtensionContext): Promise { lsOutputChannel.show(); }), ); + context.subscriptions.push(eeBuilderCommand); } const startClient = async ( diff --git a/src/features/contentCreator/addPluginPage.ts b/src/features/contentCreator/addPluginPage.ts index d8972002e..307ddbf5e 100644 --- a/src/features/contentCreator/addPluginPage.ts +++ b/src/features/contentCreator/addPluginPage.ts @@ -139,6 +139,7 @@ export class AddPlugin { filter lookup + action diff --git a/src/features/contentCreator/createDevcontainerPage.ts b/src/features/contentCreator/createDevcontainerPage.ts index 52d1ac1a6..c42362dd3 100644 --- a/src/features/contentCreator/createDevcontainerPage.ts +++ b/src/features/contentCreator/createDevcontainerPage.ts @@ -177,7 +177,7 @@ export class CreateDevcontainer { -   Explore Devcontainer +   Open Devcontainer diff --git a/src/features/contentCreator/createSampleExecutionEnvPage.ts b/src/features/contentCreator/createSampleExecutionEnvPage.ts index 718652f3b..16bd3876b 100644 --- a/src/features/contentCreator/createSampleExecutionEnvPage.ts +++ b/src/features/contentCreator/createSampleExecutionEnvPage.ts @@ -115,7 +115,7 @@ export class CreateSampleExecutionEnv {
-

Create a sample Ansible Execution Environment file

+

Create a sample Ansible execution environment file

Streamlining automation

@@ -175,7 +175,7 @@ export class CreateSampleExecutionEnv { -   Open Execution Environment file +   Open Execution environment file diff --git a/src/features/lightspeed/base.ts b/src/features/lightspeed/base.ts index 6c5435ec0..63838a38b 100644 --- a/src/features/lightspeed/base.ts +++ b/src/features/lightspeed/base.ts @@ -1,5 +1,4 @@ import * as vscode from "vscode"; -import { LanguageClient } from "vscode-languageclient/node"; import { LightSpeedAPI } from "./api"; import { TelemetryManager } from "../../utils/telemetryUtils"; import { SettingsManager } from "../../settings"; @@ -25,7 +24,6 @@ import { LightspeedExplorerWebviewViewProvider } from "./explorerWebviewViewProv export class LightSpeedManager { private context; - public client; public settingsManager: SettingsManager; public telemetry: TelemetryManager; public apiInstance: LightSpeedAPI; @@ -43,12 +41,10 @@ export class LightSpeedManager { constructor( context: vscode.ExtensionContext, - client: LanguageClient, settingsManager: SettingsManager, telemetry: TelemetryManager, ) { this.context = context; - this.client = client; this.settingsManager = settingsManager; this.telemetry = telemetry; this.lightSpeedActivityTracker = {}; @@ -78,7 +74,6 @@ export class LightSpeedManager { ); this.contentMatchesProvider = new ContentMatchesWebview( this.context, - this.client, this.settingsManager, this.apiInstance, this.lightspeedAuthenticatedUser, @@ -89,7 +84,6 @@ export class LightSpeedManager { this.apiInstance, this.lightspeedAuthenticatedUser, context, - client, settingsManager, ); diff --git a/src/features/lightspeed/contentMatchesWebview.ts b/src/features/lightspeed/contentMatchesWebview.ts index fc9e86060..1bd78f9b1 100644 --- a/src/features/lightspeed/contentMatchesWebview.ts +++ b/src/features/lightspeed/contentMatchesWebview.ts @@ -1,5 +1,4 @@ import * as vscode from "vscode"; -import { LanguageClient } from "vscode-languageclient/node"; import { LightSpeedAPI } from "./api"; import { SettingsManager } from "../../settings"; import { @@ -10,7 +9,6 @@ import { IContentMatchParams, ISuggestionDetails, } from "../../interfaces/lightspeed"; -import { getCurrentUTCDateTime } from "../utils/dateTime"; import * as yaml from "yaml"; import { LightspeedUser } from "./lightspeedUser"; import { parsePlays } from "./utils/parsePlays"; @@ -22,20 +20,17 @@ export class ContentMatchesWebview implements vscode.WebviewViewProvider { private _extensionUri: vscode.Uri; private context; private lightspeedAuthenticatedUser: LightspeedUser; - public client; public settingsManager: SettingsManager; public apiInstance: LightSpeedAPI; public suggestionDetails: ISuggestionDetails[] = []; constructor( context: vscode.ExtensionContext, - client: LanguageClient, settingsManager: SettingsManager, apiInstance: LightSpeedAPI, lightspeedAuthenticatedUser: LightspeedUser, ) { this.context = context; - this.client = client; this.settingsManager = settingsManager; this.apiInstance = apiInstance; this._extensionUri = context.extensionUri; @@ -83,19 +78,9 @@ export class ContentMatchesWebview implements vscode.WebviewViewProvider { contentMatchesRequestData.model = model; } - this.client.outputChannel?.appendLine( - `${getCurrentUTCDateTime().toISOString()}: request content matches from Ansible Lightspeed:\n${JSON.stringify( - contentMatchesRequestData, - )}`, - ); - const outputData: ContentMatchesResponseParams | IError = await this.apiInstance.contentMatchesRequest(contentMatchesRequestData); - this.client.outputChannel?.appendLine( - `${getCurrentUTCDateTime().toISOString()}: response data from Ansible lightspeed:\n${JSON.stringify( - outputData, - )}`, - ); + return outputData; } diff --git a/src/features/lightspeed/statusBar.ts b/src/features/lightspeed/statusBar.ts index 6abe829ff..9914fd86e 100644 --- a/src/features/lightspeed/statusBar.ts +++ b/src/features/lightspeed/statusBar.ts @@ -1,5 +1,4 @@ import * as vscode from "vscode"; -import { LanguageClient } from "vscode-languageclient/node"; import { LightSpeedAPI } from "./api"; import { SettingsManager } from "../../settings"; import { @@ -15,7 +14,6 @@ export class LightspeedStatusBar { private apiInstance: LightSpeedAPI; private lightspeedAuthenticatedUser: LightspeedUser; private context; - public client; public settingsManager: SettingsManager; public statusBar: vscode.StatusBarItem; @@ -23,13 +21,11 @@ export class LightspeedStatusBar { apiInstance: LightSpeedAPI, lightspeedAuthenticatedUser: LightspeedUser, context: vscode.ExtensionContext, - client: LanguageClient, settingsManager: SettingsManager, ) { this.apiInstance = apiInstance; this.lightspeedAuthenticatedUser = lightspeedAuthenticatedUser; this.context = context; - this.client = client; this.settingsManager = settingsManager; // create a new project lightspeed status bar item that we can manage this.statusBar = this.initialiseStatusBar(); @@ -73,9 +69,6 @@ export class LightspeedStatusBar { } private handleStatusBar() { - if (!this.client.isRunning()) { - return; - } try { this.getLightSpeedStatusBarText().then((text) => { this.statusBar.text = text; diff --git a/src/features/pythonMetadata.ts b/src/features/pythonMetadata.ts index 6e9466697..6599c6b8e 100644 --- a/src/features/pythonMetadata.ts +++ b/src/features/pythonMetadata.ts @@ -7,7 +7,6 @@ import { ThemeColor, workspace, } from "vscode"; -import { LanguageClient } from "vscode-languageclient/node"; import { TelemetryManager } from "../utils/telemetryUtils"; import { SettingsManager } from "../settings"; import { AnsibleCommands } from "../definitions/constants"; @@ -15,19 +14,16 @@ import { execSync } from "child_process"; export class PythonInterpreterManager { private context; - private client; private pythonInterpreterStatusBarItem: StatusBarItem; private telemetry: TelemetryManager; private extensionSettings: SettingsManager; constructor( context: ExtensionContext, - client: LanguageClient, telemetry: TelemetryManager, extensionSettings: SettingsManager, ) { this.context = context; - this.client = client; this.telemetry = telemetry; this.extensionSettings = extensionSettings; @@ -62,9 +58,6 @@ export class PythonInterpreterManager { * and receives notification from the server with ansible meta data associated with the opened file as param */ public async updatePythonInfo(): Promise { - if (!this.client.isRunning()) { - return; - } this.pythonInterpreterStatusBarItem.tooltip = new MarkdownString( ` Change environment `, true, diff --git a/src/features/quickLinks/quickLinksView.ts b/src/features/quickLinks/quickLinksView.ts index cbe9022c6..72c3e1b0a 100644 --- a/src/features/quickLinks/quickLinksView.ts +++ b/src/features/quickLinks/quickLinksView.ts @@ -125,7 +125,7 @@ export function getWebviewQuickLinks(webview: Webview, extensionUri: Uri) {

- Execution environment template + Execution environment NEW

diff --git a/src/features/utils/buildExecutionEnvironment.ts b/src/features/utils/buildExecutionEnvironment.ts new file mode 100644 index 000000000..04c6faa11 --- /dev/null +++ b/src/features/utils/buildExecutionEnvironment.ts @@ -0,0 +1,70 @@ +import * as vscode from "vscode"; +import * as path from "path"; +import { withInterpreter } from "../utils/commandRunner"; +import { SettingsManager } from "../../settings"; +import { runCommand } from "../contentCreator/utils"; + +export function rightClickEEBuildCommand(commandId: string): vscode.Disposable { + return vscode.commands.registerCommand(commandId, async (uri: vscode.Uri) => { + if (!uri?.fsPath) { + const getFileFromEditor = vscode.window.activeTextEditor; + if (!getFileFromEditor) { + vscode.window.showErrorMessage( + "No file selected and no active file found!", + ); + return; + } + const filePath = getFileFromEditor.document.uri.fsPath; + if ( + !filePath.endsWith("execution-environment.yml") && + !filePath.endsWith("execution-environment.yaml") + ) { + vscode.window.showErrorMessage( + "Active file is not an execution environment file!", + ); + return; + } + uri = getFileFromEditor.document.uri; + } + + const filePath = uri.fsPath; + const dirPath = path.dirname(filePath); + + const builderCommand = `ansible-builder build -f ${filePath} -c ${dirPath}/context`; + + vscode.window.showInformationMessage(`Running: ${builderCommand}`); + + if (!dirPath) { + vscode.window.showErrorMessage("Could not determine workspace folder."); + return; + } + + try { + const extSettings = new SettingsManager(); + await extSettings.initialize(); + + const { command, env } = withInterpreter( + extSettings.settings, + builderCommand, + "", + ); + + const result = await runCommand(command, env); + + if (result.status === "failed") { + vscode.window.showErrorMessage( + `Build failed with status ${result.status}: \n${result.output.trim()}`, + ); + return; + } + + vscode.window.showInformationMessage( + `Build successful:\n${result.output.trim()}`, + ); + } catch (error) { + vscode.window.showErrorMessage( + `Unexpected error: ${(error as Error).message}`, + ); + } + }); +} diff --git a/test/ui-test/contentCreatorUiTest.ts b/test/ui-test/contentCreatorUiTest.ts index 023c29443..94b18483e 100644 --- a/test/ui-test/contentCreatorUiTest.ts +++ b/test/ui-test/contentCreatorUiTest.ts @@ -1,4 +1,10 @@ -import { By, EditorView, WebElement } from "vscode-extension-tester"; +import { + By, + EditorView, + WebElement, + Workbench, + InputBox, +} from "vscode-extension-tester"; import { getWebviewByLocator, sleep, @@ -176,10 +182,55 @@ describe("Test Ansible sample execution environment file scaffolding", () => { it("Check create-sample-execution-env-file webview elements", async () => { await testWebViewElements( - "Ansible: Create a sample Ansible Execution Environment file", + "Ansible: Create a sample Ansible execution environment file", "Create Sample Ansible Execution Environment", ); }); + + it("Executes the build command from the right-click menu", async function () { + const workbench = new Workbench(); + + await workbenchExecuteCommand("Build Ansible execution environment"); + let notifications = await workbench.getNotifications(); + const errorNotification = notifications.find(async (notification) => { + return (await notification.getMessage()).includes( + "No file selected and no active file found!", + ); + }); + if (!errorNotification) throw new Error("Notification not found"); + + await workbenchExecuteCommand("File: New Untitled Text file"); + await workbenchExecuteCommand("Build Ansible execution environment"); + notifications = await workbench.getNotifications(); + const fileTypeError = notifications.find(async (notification) => { + return (await notification.getMessage()).includes( + "Active file is not an execution environment file!", + ); + }); + if (!fileTypeError) throw new Error("Notification not found"); + + await workbenchExecuteCommand("Go to File..."); + const inputBox = await InputBox.create(); + await inputBox.setText( + path.join(os.homedir(), "execution-environment.yml"), + ); + await inputBox.confirm(); + await workbenchExecuteCommand("Build Ansible execution environment"); + + await new Promise((resolve) => setTimeout(resolve, 3000)); + notifications = await workbench.getNotifications(); + const buildResultNotification = notifications.find(async (notification) => { + const message = await notification.getMessage(); + return ( + message.includes("Build successful") || message.includes("Build failed") + ); + }); + if (!buildResultNotification) throw new Error("Notification not found"); + + expect(await buildResultNotification.getMessage()).to.match( + /^Build (successful|failed)/, + ); + }); }); describe("Test collection plugins scaffolding", () => { @@ -249,7 +300,7 @@ describe("Test collection plugins scaffolding", () => { } } - it("Check add-plugin webview elements", async () => { + it("Check add-plugin webview elements for lookup plugin", async () => { await testWebViewElements( "Ansible: Add a Plugin", "Add Plugin", @@ -257,4 +308,12 @@ describe("Test collection plugins scaffolding", () => { "lookup", ); }); + it("Check add-plugin webview elements for action plugin", async () => { + await testWebViewElements( + "Ansible: Add a Plugin", + "Add Plugin", + "test_plugin_name", + "action", + ); + }); }); diff --git a/test/units/lightspeed/contentmatches.test.ts b/test/units/lightspeed/contentmatches.test.ts index 937be73ed..c92d3509c 100644 --- a/test/units/lightspeed/contentmatches.test.ts +++ b/test/units/lightspeed/contentmatches.test.ts @@ -2,7 +2,6 @@ require("assert"); import { ContentMatchesWebview } from "../../../src/features/lightspeed/contentMatchesWebview"; import { SettingsManager } from "../../../src/settings"; -import { LanguageClient } from "vscode-languageclient/node"; import sinon from "sinon"; import assert from "assert"; @@ -49,7 +48,6 @@ function createMatchErrorResponse(detail: unknown): IError { function createContentMatchesWebview(): ContentMatchesWebview { const m_context: Partial = {}; - const m_client: Partial = {}; const m_settings: Partial = {}; m_settings.settings = {} as ExtensionSettings; m_settings.settings.lightSpeedService = {} as LightSpeedServiceSettings; @@ -57,7 +55,6 @@ function createContentMatchesWebview(): ContentMatchesWebview { const m_l_user: Partial = {}; const cmw = new ContentMatchesWebview( m_context as ExtensionContext, - m_client as LanguageClient, m_settings as SettingsManager, m_api_instance as LightSpeedAPI, m_l_user as LightspeedUser,