diff --git a/CHANGELOG.md b/CHANGELOG.md index f205104..7f67829 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to the terraform-toolbox extension will be documented in this file. +## [0.3.1] + +- (refac): The extension now checks your internet connection before running commands that require an internet connection. Status bar items that require an internet connection will be hidden if no internet connection is available. +- (feat): Added a new setting to control if a login notification should be shown on startup, if the current spacectl token is not valid. + ## [0.3.0] - (feat): New Spacelift spacectl authentication handling. Since Spacelift has changed its token validity (only 1 token per user is now allowed to be active), the extension now uses the spacectl with web browser login to authenticate the user. If the current token provided by spacectl has expried or is revoked, a status item will be shown. Clicking on the status item will prompt you to authenticate spacectl with your browser. diff --git a/package.json b/package.json index 4eafc2e..e5da9b1 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/Noahnc/vscode-terraform-toolbox" }, "description": "VSCode extension adding a bunch of featurees regarding Terraform and Spacelift.", - "version": "0.3.0", + "version": "0.3.1", "icon": "Images/terraform_toolbox_icon.png", "engines": { "vscode": "^1.83.0" @@ -81,6 +81,11 @@ "default": null, "description": "Spacectl profile name to select when running this extension. The currently active Profile will be used when this is not set." }, + "tftoolbox.spacelift.showLoginNotificationOnStartup": { + "type": "boolean", + "default": false, + "description": "If enabled, a notification will be shown regarding spacectl login if spacectl is not authenticated." + }, "tftoolbox.terraform.autoSelectVersion": { "type": "boolean", "default": false, diff --git a/src/commands/base_command.ts b/src/commands/base_command.ts index 6fb6b94..5176d2a 100644 --- a/src/commands/base_command.ts +++ b/src/commands/base_command.ts @@ -1,11 +1,13 @@ import * as vscode from "vscode"; import { UserShownError } from "../custom_errors"; import { getLogger } from "../utils/logger"; +import * as helper from "../utils/helper_functions"; export interface IvscodeCommandSettings { command: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any successCallback?: (...arg: any) => Promise; + checkInternetConnection?: boolean; } export abstract class BaseCommand { @@ -25,6 +27,10 @@ export abstract class BaseCommand { // eslint-disable-next-line @typescript-eslint/no-explicit-any async run(...args: any[]): Promise { getLogger().debug("Running command " + this._settings.command); + if (this._settings.checkInternetConnection && (await helper.checkInternetConnection()) === false) { + helper.showWarning("No internet connection, command " + this._settings.command + " will not be executed"); + return; + } await this.init(...args) .then(() => { getLogger().debug("Successfully ran command " + this._settings.command); diff --git a/src/extension.ts b/src/extension.ts index 13dc9ec..ce2f0b5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -64,71 +64,84 @@ export async function activate(context: vscode.ExtensionContext) { ); // Init spacelift commands if spacelift is configured - spacectlInit(settings) - .then(([spaceliftClient, spacectlInstance, tenantID, authenticationHandler]) => { - new RunSpacectlLocalPreviewCurrentStackCommand(context, { command: cst.COMMAND_LOCAL_PREVIEW_CURRENT_STACK }, spaceliftClient, spacectlInstance); - new RunSpacectlLocalPreviewCommand(context, { command: cst.COMMAND_LOCAL_PREVIEW }, spaceliftClient, spacectlInstance); - const openSpaceliftWebPortalCommand = "openSpaceliftWebPortal"; - context.subscriptions.push( - vscode.commands.registerCommand(openSpaceliftWebPortalCommand, () => { - vscode.env.openExternal(vscode.Uri.parse("https://" + tenantID + cst.SPACELIFT_BASE_DOMAIN)); - }) - ); - new SpaceliftPenStackConfCount( - context, - { - alignment: vscode.StatusBarAlignment.Left, - priority: 99, - refreshIntervalSeconds: settings.spaceliftStatusBarItemRefreshIntervalSeconds, - tooltip: "Count of Spacelift Stacks pending confirmation", - onClickCommand: openSpaceliftWebPortalCommand, - }, - spaceliftClient - ).refresh(); - const spaceliftAuthStatusItem = new SpaceliftApiAuthenticationStatus( - context, - { - alignment: vscode.StatusBarAlignment.Left, - priority: 100, - refreshIntervalSeconds: settings.spaceliftStatusBarItemRefreshIntervalSeconds, - tooltip: "Log-in to Spacelift with spacectl and your Web browser", - onClickCommand: cst.COMMAND_SPACELIFT_LOGIN, - }, - authenticationHandler - ); - spaceliftAuthStatusItem.refresh(); - context.subscriptions.push( - vscode.commands.registerCommand(cst.COMMAND_SPACELIFT_LOGIN, async () => { - if (await authenticationHandler.login_interactive()) { - await spaceliftAuthStatusItem.refresh(); - } - }) - ); - }) - .catch((error) => { - getLogger().error("Failed to initialize spacectl: " + error); - helpers - .showNotificationWithDecisions( - "Failed to initialize spacectl. Some features will be disabled until spacectl is configured.", - "tftoolbox.spacelift.showSpaceliftInitErrorOnStart", - "Open spacectl documentation", - "warning" - ) - .then((result) => { - if (result) { - vscode.env.openExternal(vscode.Uri.parse("https://github.com/spacelift-io/spacectl")); - } - }); - }); + spacectlInit(settings).then(([spaceliftClient, spacectlInstance, tenantID, authenticationHandler]) => { + new RunSpacectlLocalPreviewCurrentStackCommand(context, { command: cst.COMMAND_LOCAL_PREVIEW_CURRENT_STACK, checkInternetConnection: true }, spaceliftClient, spacectlInstance); + new RunSpacectlLocalPreviewCommand(context, { command: cst.COMMAND_LOCAL_PREVIEW, checkInternetConnection: true }, spaceliftClient, spacectlInstance); + const openSpaceliftWebPortalCommand = "openSpaceliftWebPortal"; + context.subscriptions.push( + vscode.commands.registerCommand(openSpaceliftWebPortalCommand, () => { + vscode.env.openExternal(vscode.Uri.parse("https://" + tenantID + cst.SPACELIFT_BASE_DOMAIN)); + }) + ); + new SpaceliftPenStackConfCount( + context, + { + alignment: vscode.StatusBarAlignment.Left, + priority: 99, + refreshIntervalSeconds: settings.spaceliftStatusBarItemRefreshIntervalSeconds, + tooltip: "Count of Spacelift Stacks pending confirmation", + onClickCommand: openSpaceliftWebPortalCommand, + checkInternetConnection: true, + }, + spaceliftClient + ).refresh(); + const spaceliftAuthStatusItem = new SpaceliftApiAuthenticationStatus( + context, + { + alignment: vscode.StatusBarAlignment.Left, + priority: 100, + refreshIntervalSeconds: settings.spaceliftStatusBarItemRefreshIntervalSeconds, + tooltip: "Log-in to Spacelift with spacectl and your Web browser", + onClickCommand: cst.COMMAND_SPACELIFT_LOGIN, + checkInternetConnection: true, + }, + authenticationHandler + ); + spaceliftAuthStatusItem.refresh(); + context.subscriptions.push( + vscode.commands.registerCommand(cst.COMMAND_SPACELIFT_LOGIN, async () => { + if (await authenticationHandler.login_interactive()) { + await spaceliftAuthStatusItem.refresh(); + } + }) + ); + authenticationHandler + .check_token_valid() + .then((valid: boolean) => { + if (!valid && settings.showSpacectlNotAuthenticatedWarningOnStartup) { + getLogger().info("Spacectl token is not valid, showing notification to log-in to Spacelift"); + vscode.commands.executeCommand(cst.COMMAND_SPACELIFT_LOGIN); + } + }) + .catch((error) => { + getLogger().error("Failed to initialize spacectl: " + error); + helpers + .showNotificationWithDecisions( + "Failed to initialize spacectl. Some features will be disabled until spacectl is configured.", + "tftoolbox.spacelift.showSpaceliftInitErrorOnStart", + "Open spacectl documentation", + "warning" + ) + .then((result) => { + if (result) { + vscode.env.openExternal(vscode.Uri.parse("https://github.com/spacelift-io/spacectl")); + } + }); + }); + }); // Terraform version management commands const setTFVersionBasedOnProjectCommand = new SetTerraformVersionBasedOnProjectRequirementsCommand( context, - { command: cst.COMMAND_AUTO_SET_TERRAFORM_VERSION, successCallback: tfVersionItem.refresh.bind(tfVersionItem) }, + { command: cst.COMMAND_AUTO_SET_TERRAFORM_VERSION, successCallback: tfVersionItem.refresh.bind(tfVersionItem), checkInternetConnection: true }, tfVersionManager, tfProjectHelper ); - new ChoseAndSetTerraformVersionCommand(context, { command: cst.COMMAND_SET_TERRAFORM_VERSION, successCallback: tfVersionItem.refresh.bind(tfVersionItem) }, tfVersionManager); - new ChoseAndDeleteTerraformVersionsCommand(context, { command: cst.COMMAND_DELETE_TERRAFORM_VERSIONS }, tfVersionManager); + new ChoseAndSetTerraformVersionCommand( + context, + { command: cst.COMMAND_SET_TERRAFORM_VERSION, successCallback: tfVersionItem.refresh.bind(tfVersionItem), checkInternetConnection: true }, + tfVersionManager + ); + new ChoseAndDeleteTerraformVersionsCommand(context, { command: cst.COMMAND_DELETE_TERRAFORM_VERSIONS, checkInternetConnection: true }, tfVersionManager); // Terraform init commands const tfInitAllProjectsCommand = new TerraformInitAllProjectsCommand(context, { command: cst.COMMAND_INIT_ALL_PROJECTS }, tfProjectHelper); diff --git a/src/models/settings.ts b/src/models/settings.ts index 3f9b511..5c6d5ab 100644 --- a/src/models/settings.ts +++ b/src/models/settings.ts @@ -39,4 +39,7 @@ export class Settings { get excludedGlobPatterns(): string[] { return this.getVSCodeSettingKey("tftoolbox.excludeGlobPatterns"); } + get showSpacectlNotAuthenticatedWarningOnStartup(): boolean { + return this.getVSCodeSettingKey("tftoolbox.spacelift.showLoginNotificationOnStartup"); + } } diff --git a/src/utils/helper_functions.ts b/src/utils/helper_functions.ts index 34fddf1..3c69457 100644 --- a/src/utils/helper_functions.ts +++ b/src/utils/helper_functions.ts @@ -2,6 +2,7 @@ import * as path from "path"; import * as vscode from "vscode"; import { getLogger } from "./logger"; +import * as dns from "dns"; export function checkIfOpenTextEditorIsTerraform(): boolean { const activeDocument = vscode.window.activeTextEditor?.document; @@ -103,3 +104,15 @@ export function showError(message: string, silent = false) { export function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } + +export function checkInternetConnection(): Promise { + return new Promise((resolve) => { + dns.lookup("google.com", function (err: any) { + if (err && err.code == "ENOTFOUND") { + resolve(false); + } else { + resolve(true); + } + }); + }); +} diff --git a/src/view/statusbar/base_statusbar_item.ts b/src/view/statusbar/base_statusbar_item.ts index b205c51..fa3f454 100644 --- a/src/view/statusbar/base_statusbar_item.ts +++ b/src/view/statusbar/base_statusbar_item.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode"; import { UserShownError } from "../../custom_errors"; import { getLogger } from "../../utils/logger"; +import * as helper from "../../utils/helper_functions"; export interface IvscodeStatusBarItemSettings { alignment: vscode.StatusBarAlignment; @@ -9,6 +10,7 @@ export interface IvscodeStatusBarItemSettings { updateOnDidChangeTextEditorSelection?: boolean; refreshIntervalSeconds?: number; tooltip: string; + checkInternetConnection?: boolean; } export abstract class BaseStatusBarItem { @@ -40,6 +42,13 @@ export abstract class BaseStatusBarItem { } // eslint-disable-next-line @typescript-eslint/no-explicit-any async refresh(...args: any[]): Promise { + if (this._settings.checkInternetConnection) { + if (await helper.checkInternetConnection() === false) { + getLogger().debug("No internet connection, hiding status bar item") + this._statusBarItem.hide(); + return; + } + } await this.run(...args).catch((error) => { handleError(error); });