From a61ceb88576922a034d2248db98e672e6c4f63af Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Sun, 24 Nov 2024 15:55:38 -0500 Subject: [PATCH 1/7] Add Arch support for linux installation Signed-off-by: Sanjula Ganepola --- src/act.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/act.ts b/src/act.ts index dc84b68..9e04d37 100644 --- a/src/act.ts +++ b/src/act.ts @@ -119,6 +119,7 @@ export class Act { this.installationCommands = { 'Homebrew': 'brew install act', 'Nix': 'nix run nixpkgs#act', + 'Arch': 'pacman -Syu act', 'AUR': 'yay -Syu act', 'COPR': 'dnf copr enable goncalossilva/act && dnf install act-cli', 'GitHub CLI': 'gh extension install https://github.com/nektos/gh-act' From 195c2968916123f6adc0a500e747de2c5c313728 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Sun, 24 Nov 2024 18:00:49 -0500 Subject: [PATCH 2/7] Add check for permission denied in Linux and add action to fix permissions Signed-off-by: Sanjula Ganepola --- package.json | 15 +++++ src/act.ts | 4 +- src/componentsManager.ts | 63 +++++++++++++++++-- .../components/componentsTreeDataProvider.ts | 10 ++- src/views/decorationProvider.ts | 4 +- 5 files changed, 86 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 3c863b5..15e0ac7 100644 --- a/package.json +++ b/package.json @@ -156,6 +156,12 @@ "title": "Start", "icon": "$(debug-start)" }, + { + "category": "GitHub Local Actions", + "command": "githubLocalActions.fixPermissions", + "title": "Fix Permissions", + "icon": "$(unlock)" + }, { "category": "GitHub Local Actions", "command": "githubLocalActions.runAllWorkflows", @@ -366,6 +372,10 @@ "command": "githubLocalActions.startComponent", "when": "never" }, + { + "command": "githubLocalActions.fixPermissions", + "when": "never" + }, { "command": "githubLocalActions.runAllWorkflows", "when": "never" @@ -555,6 +565,11 @@ "when": "view == components && viewItem =~ /^githubLocalActions.component_Not Running.*/", "group": "inline@1" }, + { + "command": "githubLocalActions.fixPermissions", + "when": "view == components && viewItem =~ /^githubLocalActions.component_Invalid Permissions.*/", + "group": "inline@1" + }, { "command": "githubLocalActions.runAllWorkflows", "when": "view == workflows && viewItem =~ /^githubLocalActions.workspaceFolderWorkflows.*/ && workspaceFolderCount > 1", diff --git a/src/act.ts b/src/act.ts index 9e04d37..b482b8d 100644 --- a/src/act.ts +++ b/src/act.ts @@ -369,7 +369,9 @@ export class Act { await tasks.executeTask({ name: 'nektos/act', detail: 'Install nektos/act', - definition: { type: 'nektos/act installation' }, + definition: { + type: 'nektos/act installation' + }, source: 'GitHub Local Actions', scope: TaskScope.Workspace, isBackground: true, diff --git a/src/componentsManager.ts b/src/componentsManager.ts index d3400e2..2b0d21f 100644 --- a/src/componentsManager.ts +++ b/src/componentsManager.ts @@ -14,6 +14,7 @@ export interface Component { information: string, installation: () => Promise, start?: () => Promise, + fixPermissions?: () => Promise, message?: string } @@ -21,7 +22,8 @@ export enum CliStatus { Installed = 'Installed', NotInstalled = 'Not Installed', Running = 'Running', - NotRunning = 'Not Running' + NotRunning = 'Not Running', + InvalidPermissions = 'Invalid Permissions' } export enum ExtensionStatus { @@ -145,7 +147,9 @@ export class ComponentsManager { await tasks.executeTask({ name: 'Docker Engine', detail: 'Start Docker Engine', - definition: { type: 'GitHub Local Actions' }, + definition: { + type: 'Start Docker Engine' + }, source: 'GitHub Local Actions', scope: TaskScope.Workspace, isBackground: true, @@ -161,7 +165,7 @@ export class ComponentsManager { problemMatchers: [], runOptions: {}, group: TaskGroup.Build, - execution: new ShellExecution('sudo dockerd') + execution: new ShellExecution('systemctl start docker') }); } else { window.showErrorMessage(`Invalid environment: ${process.platform}`, 'Report an Issue').then(async value => { @@ -198,6 +202,53 @@ export class ComponentsManager { }); } }); + }, + fixPermissions: async () => { + if (process.platform === Platform.linux) { + window.showInformationMessage('By default, the Docker daemon binds to a Unix socket owned by the root user. To manage Docker as a non-root user, a Unix group called "docker" should be created with your user added to it.', 'Proceed', 'Learn More').then(async value => { + if (value === 'Proceed') { + await tasks.executeTask({ + name: 'Docker Engine', + detail: 'Fix Docker Engine Permissions', + definition: { + type: 'Fix Docker Engine Permissions' + }, + source: 'GitHub Local Actions', + scope: TaskScope.Workspace, + isBackground: true, + presentationOptions: { + reveal: TaskRevealKind.Always, + focus: false, + clear: true, + close: false, + echo: true, + panel: TaskPanelKind.Shared, + showReuseMessage: false + }, + problemMatchers: [], + runOptions: {}, + group: TaskGroup.Build, + execution: new ShellExecution('sudo groupadd docker; sudo usermod -aG docker $USER; newgrp docker') + }); + + window.withProgress({ location: { viewId: ComponentsTreeDataProvider.VIEW_ID } }, async () => { + // Delay 4 seconds for Docker to be started + const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + await delay(4000); + + // Check again for docker status + const newDockerCliInfo = await this.getCliInfo('docker version', /Client:\n.+\n\sVersion:\s+(.+)/, true, true); + if (dockerCliInfo.status !== newDockerCliInfo.status) { + componentsTreeDataProvider.refresh(); + } + }); + } else if (value === 'Learn More') { + await env.openExternal(Uri.parse('https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user')); + } + }); + } else { + window.showErrorMessage(`Permissions cannot be automatically fixed for ${process.platform} environment.`); + } } }); @@ -234,7 +285,7 @@ export class ComponentsManager { async getUnreadyComponents(): Promise[]> { const components = await this.getComponents(); - return components.filter(component => component.required && [CliStatus.NotInstalled, CliStatus.NotRunning, ExtensionStatus.NotActivated].includes(component.status)); + return components.filter(component => component.required && [CliStatus.NotInstalled, CliStatus.NotRunning, CliStatus.InvalidPermissions, ExtensionStatus.NotActivated].includes(component.status)); } async getCliInfo(command: string, versionRegex: RegExp, ignoreError: boolean, checksIfRunning: boolean): Promise<{ version?: string, status: CliStatus }> { @@ -246,7 +297,9 @@ export class ComponentsManager { if (ignoreError && version) { resolve({ version: version[1], - status: CliStatus.NotRunning + status: (process.platform === Platform.linux && error.message.toLowerCase().includes('permission denied')) ? + CliStatus.InvalidPermissions : + CliStatus.NotRunning }); } else { resolve({ diff --git a/src/views/components/componentsTreeDataProvider.ts b/src/views/components/componentsTreeDataProvider.ts index 725f931..99f5413 100644 --- a/src/views/components/componentsTreeDataProvider.ts +++ b/src/views/components/componentsTreeDataProvider.ts @@ -27,9 +27,15 @@ export default class ComponentsTreeDataProvider implements TreeDataProvider { + const fixPermissions = componentTreeItem.component.fixPermissions; + if (fixPermissions) { + await fixPermissions(); + this.refresh(); } - - this.refresh(); }) ); } diff --git a/src/views/decorationProvider.ts b/src/views/decorationProvider.ts index a34b56e..c3a8416 100644 --- a/src/views/decorationProvider.ts +++ b/src/views/decorationProvider.ts @@ -17,12 +17,12 @@ export class DecorationProvider implements FileDecorationProvider { badge: '✅', color: new ThemeColor('GitHubLocalActions.green') }; - } else if (!required && (status === CliStatus.NotInstalled || status === CliStatus.NotRunning || status === ExtensionStatus.NotActivated)) { + } else if (!required && (status === CliStatus.NotInstalled || status === CliStatus.NotRunning || status === CliStatus.InvalidPermissions || status === ExtensionStatus.NotActivated)) { return { badge: '⚠️', color: new ThemeColor('GitHubLocalActions.yellow') }; - } else if (required && (status === CliStatus.NotInstalled || status === CliStatus.NotRunning || status === ExtensionStatus.NotActivated)) { + } else if (required && (status === CliStatus.NotInstalled || status === CliStatus.NotRunning || status === CliStatus.InvalidPermissions || status === ExtensionStatus.NotActivated)) { return { badge: '❌', color: new ThemeColor('GitHubLocalActions.red') From eca9244ec9f0c30d81bb978d50d7e118da140e4b Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Sun, 24 Nov 2024 19:32:40 -0500 Subject: [PATCH 3/7] Fix regex for linux Signed-off-by: Sanjula Ganepola --- src/componentsManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/componentsManager.ts b/src/componentsManager.ts index 2b0d21f..00c667d 100644 --- a/src/componentsManager.ts +++ b/src/componentsManager.ts @@ -183,7 +183,7 @@ export class ComponentsManager { await delay(4000); // Check again for docker status - const newDockerCliInfo = await this.getCliInfo('docker version', /Client:\n.+\n\sVersion:\s+(.+)/, true, true); + const newDockerCliInfo = await this.getCliInfo('docker version', /Client:\n(.+\n)?\sVersion:\s+(.+)/, true, true); if (dockerCliInfo.status !== newDockerCliInfo.status) { componentsTreeDataProvider.refresh(); } else { From 32d99f81792845ee8b921fd8a1f480c79585facf Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Sun, 24 Nov 2024 23:04:46 -0500 Subject: [PATCH 4/7] Move regex to static variable Signed-off-by: Sanjula Ganepola --- src/componentsManager.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/componentsManager.ts b/src/componentsManager.ts index 00c667d..8c427eb 100644 --- a/src/componentsManager.ts +++ b/src/componentsManager.ts @@ -32,10 +32,13 @@ export enum ExtensionStatus { } export class ComponentsManager { + static actVersionRegExp: RegExp = /act version (.+)/; + static dockerVersionRegExp: RegExp = /Client:\n(.+\n)?\sVersion:\s+(.+)/; + async getComponents(): Promise[]> { const components: Component[] = []; - const actCliInfo = await this.getCliInfo('act --version', /act version (.+)/, false, false); + const actCliInfo = await this.getCliInfo('act --version', ComponentsManager.actVersionRegExp, false, false); components.push({ name: 'nektos/act', icon: 'terminal', @@ -125,7 +128,7 @@ export class ComponentsManager { } }); - const dockerCliInfo = await this.getCliInfo('docker version', /Client:\n.+\n\sVersion:\s+(.+)/, true, true); + const dockerCliInfo = await this.getCliInfo('docker version', ComponentsManager.dockerVersionRegExp, true, true); const dockerDesktopPath = ConfigurationManager.get(Section.dockerDesktopPath); components.push({ name: 'Docker Engine', @@ -183,7 +186,7 @@ export class ComponentsManager { await delay(4000); // Check again for docker status - const newDockerCliInfo = await this.getCliInfo('docker version', /Client:\n(.+\n)?\sVersion:\s+(.+)/, true, true); + const newDockerCliInfo = await this.getCliInfo('docker version', ComponentsManager.dockerVersionRegExp, true, true); if (dockerCliInfo.status !== newDockerCliInfo.status) { componentsTreeDataProvider.refresh(); } else { @@ -237,7 +240,7 @@ export class ComponentsManager { await delay(4000); // Check again for docker status - const newDockerCliInfo = await this.getCliInfo('docker version', /Client:\n.+\n\sVersion:\s+(.+)/, true, true); + const newDockerCliInfo = await this.getCliInfo('docker version', ComponentsManager.dockerVersionRegExp, true, true); if (dockerCliInfo.status !== newDockerCliInfo.status) { componentsTreeDataProvider.refresh(); } From e15b7bb71f0545960bca493eb89c113720d19fac Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Tue, 26 Nov 2024 00:38:43 -0500 Subject: [PATCH 5/7] Fix regex Signed-off-by: Sanjula Ganepola --- src/componentsManager.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/componentsManager.ts b/src/componentsManager.ts index 7d99d15..53da835 100644 --- a/src/componentsManager.ts +++ b/src/componentsManager.ts @@ -34,7 +34,7 @@ export enum ExtensionStatus { export class ComponentsManager { static actVersionRegExp: RegExp = /act version (.+)/; - static dockerVersionRegExp: RegExp = /Client:\n(.+\n)?\sVersion:\s+(.+)/; + static dockerVersionRegExp: RegExp = /Docker Engine Version:\s(.+)/; async getComponents(): Promise[]> { const components: Component[] = []; @@ -129,7 +129,7 @@ export class ComponentsManager { } }); - const dockerCliInfo = await this.getCliInfo('docker version', ComponentsManager.dockerVersionRegExp, true, true); + const dockerCliInfo = await this.getCliInfo(`docker version --format "Docker Engine Version: {{.Client.Version}}"`, ComponentsManager.dockerVersionRegExp, true, true); const dockerDesktopPath = ConfigurationManager.get(Section.dockerDesktopPath); components.push({ name: 'Docker Engine', @@ -187,7 +187,7 @@ export class ComponentsManager { await delay(4000); // Check again for docker status - const newDockerCliInfo = await this.getCliInfo('docker version', ComponentsManager.dockerVersionRegExp, true, true); + const newDockerCliInfo = await this.getCliInfo(`docker version --format "Docker Engine Version: {{.Client.Version}}"`, ComponentsManager.dockerVersionRegExp, true, true); if (dockerCliInfo.status !== newDockerCliInfo.status) { componentsTreeDataProvider.refresh(); } else { @@ -241,7 +241,7 @@ export class ComponentsManager { await delay(4000); // Check again for docker status - const newDockerCliInfo = await this.getCliInfo('docker version', ComponentsManager.dockerVersionRegExp, true, true); + const newDockerCliInfo = await this.getCliInfo(`docker version --format "Docker Engine Version: {{.Client.Version}}"`, ComponentsManager.dockerVersionRegExp, true, true); if (dockerCliInfo.status !== newDockerCliInfo.status) { componentsTreeDataProvider.refresh(); } From b3a83301ac7ac18adc720cb27fa844ea6a76e511 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Tue, 26 Nov 2024 22:29:14 -0500 Subject: [PATCH 6/7] Add message for user to restart PC Signed-off-by: Sanjula Ganepola --- src/componentsManager.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/componentsManager.ts b/src/componentsManager.ts index a87a09a..bbdaaf9 100644 --- a/src/componentsManager.ts +++ b/src/componentsManager.ts @@ -244,6 +244,8 @@ export class ComponentsManager { const newDockerCliInfo = await this.getCliInfo(`docker version --format "Docker Engine Version: {{.Client.Version}}"`, ComponentsManager.dockerVersionRegExp, true, true); if (dockerCliInfo.status !== newDockerCliInfo.status) { componentsTreeDataProvider.refresh(); + } else { + window.showInformationMessage('You may need to restart your PC for these changes to take affect.'); } }); } else if (value === 'Learn More') { From e1435f2b0c9cb309361623f1dc45c54cbd58e2f8 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Wed, 27 Nov 2024 00:17:30 -0500 Subject: [PATCH 7/7] Add message to update act command Signed-off-by: Sanjula Ganepola --- src/act.ts | 31 ++++++++++++++++++++----------- src/componentsManager.ts | 6 +++++- src/configurationManager.ts | 2 +- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/act.ts b/src/act.ts index ef9445a..c4b9139 100644 --- a/src/act.ts +++ b/src/act.ts @@ -2,7 +2,7 @@ import * as childProcess from "child_process"; import * as fs from "fs/promises"; import * as path from "path"; import sanitize from "sanitize-filename"; -import { CustomExecution, env, EventEmitter, ExtensionContext, Pseudoterminal, ShellExecution, TaskDefinition, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, TerminalDimensions, Uri, window, workspace, WorkspaceFolder } from "vscode"; +import { commands, CustomExecution, env, EventEmitter, ExtensionContext, Pseudoterminal, ShellExecution, TaskDefinition, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, TerminalDimensions, Uri, window, workspace, WorkspaceFolder } from "vscode"; import { ComponentsManager } from "./componentsManager"; import { ConfigurationManager, Platform, Section } from "./configurationManager"; import { componentsTreeDataProvider, historyTreeDataProvider } from './extension'; @@ -70,8 +70,8 @@ export interface CommandArgs { } export class Act { - static command: string = 'act'; - static githubCliCommand: string = 'gh act'; + static defaultActCommand: string = 'act'; + static githubCliActCommand: string = 'gh act'; context: ExtensionContext; storageManager: StorageManager; secretManager: SecretManager; @@ -175,20 +175,29 @@ export class Act { tasks.onDidEndTaskProcess(async e => { const taskDefinition = e.execution.task.definition; if (taskDefinition.type === 'nektos/act installation' && e.exitCode === 0) { - // Update base act command based on installation method - if (taskDefinition.ghCliInstall) { - await ConfigurationManager.set(Section.actCommand, Act.githubCliCommand); - } else { - await ConfigurationManager.set(Section.actCommand, Act.command); - } - + this.updateActCommand(taskDefinition.ghCliInstall ? Act.githubCliActCommand : Act.defaultActCommand); componentsTreeDataProvider.refresh(); } }); } static getActCommand() { - return ConfigurationManager.get(Section.actCommand) || Act.command; + return ConfigurationManager.get(Section.actCommand) || Act.defaultActCommand; + } + + updateActCommand(newActCommand: string) { + const actCommand = ConfigurationManager.get(Section.actCommand); + + if (newActCommand !== actCommand) { + window.showInformationMessage(`The act command is currently set to "${actCommand}". Once the installation is complete, it is recommended to update this to "${newActCommand}" for this selected installation method.`, 'Proceed', 'Manually Edit').then(async value => { + if (value === 'Proceed') { + await ConfigurationManager.set(Section.actCommand, newActCommand); + componentsTreeDataProvider.refresh(); + } else if (value === 'Manually Edit') { + await commands.executeCommand('workbench.action.openSettings', ConfigurationManager.getSearchTerm(Section.actCommand)); + } + }); + } } async runAllWorkflows(workspaceFolder: WorkspaceFolder) { diff --git a/src/componentsManager.ts b/src/componentsManager.ts index bbdaaf9..1ace6dd 100644 --- a/src/componentsManager.ts +++ b/src/componentsManager.ts @@ -115,6 +115,8 @@ export class ComponentsManager { } }); } + + act.updateActCommand(Act.defaultActCommand); } else if (selectedInstallationMethod.link) { await env.openExternal(Uri.parse(selectedInstallationMethod.link)); window.showInformationMessage('Once nektos/act is successfully installed, add it to your shell\'s PATH and then refresh the components view.', 'Refresh').then(async value => { @@ -122,6 +124,8 @@ export class ComponentsManager { componentsTreeDataProvider.refresh(); } }); + + act.updateActCommand(Act.defaultActCommand); } else { await act.install(selectedInstallationMethod.label); } @@ -232,7 +236,7 @@ export class ComponentsManager { problemMatchers: [], runOptions: {}, group: TaskGroup.Build, - execution: new ShellExecution('sudo groupadd docker; sudo usermod -aG docker $USER; newgrp docker') + execution: new ShellExecution('sudo groupadd docker; sudo usermod -aG docker $USER') }); window.withProgress({ location: { viewId: ComponentsTreeDataProvider.VIEW_ID } }, async () => { diff --git a/src/configurationManager.ts b/src/configurationManager.ts index e6c8c84..4d38acd 100644 --- a/src/configurationManager.ts +++ b/src/configurationManager.ts @@ -35,7 +35,7 @@ export namespace ConfigurationManager { let actCommand = ConfigurationManager.get(Section.actCommand); if (!actCommand) { - await ConfigurationManager.set(Section.actCommand, Act.command); + await ConfigurationManager.set(Section.actCommand, Act.defaultActCommand); } }