From 195c2968916123f6adc0a500e747de2c5c313728 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Sun, 24 Nov 2024 18:00:49 -0500 Subject: [PATCH] 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')