├── .nvmrc
├── package.nls.json
├── .gitattributes
├── .github
├── CODEOWNERS
└── workflows
│ ├── locker.yml
│ ├── bump-version-pr.yaml
│ ├── info-needed-closer.yml
│ ├── main.yml
│ └── feature-request.yml
├── .eslintignore
├── .eslintrc.js
├── resources
├── SignIn.gif
├── storageTree.png
├── readme
│ ├── deploy.png
│ ├── activityLog.png
│ ├── createAdvanced.png
│ ├── createResource.png
│ └── createStorageAccount.png
├── storage-deploy.png
├── storageAccount.png
├── SelectSubscriptions.gif
├── dark
│ ├── BrandAzureStaticWebsites.svg
│ ├── AzureQueue.svg
│ ├── AzureBlobContainer.svg
│ ├── AzureFileShare.svg
│ ├── AzureTable.svg
│ └── AzureStorageAccount.svg
└── light
│ ├── BrandAzureStaticWebsites.svg
│ ├── AzureQueue.svg
│ ├── AzureBlobContainer.svg
│ ├── AzureFileShare.svg
│ ├── AzureTable.svg
│ └── AzureStorageAccount.svg
├── .vscode
├── extensions.json
├── tasks.json
└── settings.json
├── test
├── test.code-workspace
├── .eslintrc.js
├── nightly
│ ├── testFolder
│ │ └── html-hello-world
│ │ │ └── index.html
│ └── global.resource.test.ts
├── assertThrowsAsync.ts
├── global.test.ts
├── runTest.ts
└── index.ts
├── .azure-pipelines
├── compliance
│ ├── CredScanSuppressions.json
│ ├── tsaoptions.json
│ ├── PoliCheckExclusions.xml
│ └── compliance.yml
├── common
│ ├── lint.yml
│ ├── build.yml
│ ├── sbom.yml
│ └── test.yml
├── release.yml
├── SignExtension.signproj
├── linux
│ └── xvfb.init
├── main.yml
└── 1esmain.yml
├── SUPPORT.md
├── src
├── getApiExport.ts
├── utils
│ ├── localize.ts
│ ├── delay.ts
│ ├── stringUtils.ts
│ ├── azureUtils.ts
│ ├── openUrl.ts
│ ├── workspaceUtils.ts
│ ├── getPropertyFromConnectionString.ts
│ ├── copyAndShowToast.ts
│ ├── getCoreNodeModule.ts
│ ├── settingsUtils.ts
│ ├── fs.ts
│ ├── launcher.ts
│ ├── nonNull.ts
│ ├── errorUtils.ts
│ ├── storageWrappers.ts
│ ├── askOpenInStorageExplorer.ts
│ ├── treeUtils.ts
│ ├── azuriteUtils.ts
│ ├── azureClients.ts
│ ├── activityUtils.ts
│ ├── progress.ts
│ ├── checkCanOverwrite.ts
│ ├── blobPathUtils.ts
│ ├── validateNames.ts
│ └── quickPickUtils.ts
├── tree
│ ├── ICopyUrl.ts
│ ├── IStorageTreeItem.ts
│ ├── fileShare
│ │ ├── createFileShare
│ │ │ ├── IFileShareWizardContext.ts
│ │ │ ├── StorageQuotaPromptStep.ts
│ │ │ └── FileShareNameStep.ts
│ │ └── FileTreeItem.ts
│ ├── ITransferSrcOrDstTreeItem.ts
│ ├── createWizard
│ │ ├── IStaticWebsiteConfigWizardContext.ts
│ │ ├── StaticWebsiteIndexDocumentStep.ts
│ │ ├── StaticWebsiteErrorDocument404Step.ts
│ │ ├── StorageAccountTreeItemCreateStep.ts
│ │ ├── StaticWebsiteEnableStep.ts
│ │ ├── storageAccountNameStep.ts
│ │ └── StaticWebsiteConfigureStep.ts
│ ├── refreshTreeItem.ts
│ ├── SubscriptionTreeItem.ts
│ ├── IStorageRoot.ts
│ ├── table
│ │ └── TableTreeItem.ts
│ ├── blob
│ │ └── createBlobContainer
│ │ │ └── BlobContainerNameStep.ts
│ └── queue
│ │ └── QueueTreeItem.ts
├── storageExplorerLauncher
│ ├── ResourceType.ts
│ ├── IStorageExplorerLauncher.ts
│ └── storageExplorerLauncher.ts
├── vscode-azurestorage.api.d.ts
├── commands
│ ├── transfers
│ │ └── azCopy
│ │ │ ├── IAzCopyResolution.ts
│ │ │ ├── credentialStore.ts
│ │ │ └── azCopyLocations.ts
│ ├── downloadFiles
│ │ ├── ISasDownloadContext.ts
│ │ ├── IDownloadWizardContext.ts
│ │ ├── DestinationPromptStep.ts
│ │ ├── DownloadFilesStep.ts
│ │ └── SasUrlPromptStep.ts
│ ├── uploadFiles
│ │ ├── IExistingFileContext.ts
│ │ ├── IUploadFilesWizardContext.ts
│ │ ├── GetFileDestinationDirectoryStep.ts
│ │ └── uploadFiles.ts
│ ├── deleteBlob
│ │ ├── IDeleteBlobWizardContext.ts
│ │ └── DeleteBlobStep.ts
│ ├── fileShare
│ │ ├── fileActionHandlers.ts
│ │ ├── fileShareGroupActionHandlers.ts
│ │ ├── directoryActionHandlers.ts
│ │ └── fileShareActionHandlers.ts
│ ├── deleteBlobDirectory
│ │ ├── IDeleteBlobDirectoryWizardContext.ts
│ │ └── DeleteBlobDirectoryStep.ts
│ ├── deleteStorageAccount
│ │ ├── DeleteStorageAccountWizardContext.ts
│ │ └── DeleteStorageAccountStep.ts
│ ├── openInFileExplorer
│ │ ├── IOpenInFileExplorerWizardContext.ts
│ │ ├── OpenBehaviorStep.ts
│ │ └── OpenTreeItemStep.ts
│ ├── table
│ │ ├── tableGroupActionHandlers.ts
│ │ └── tableActionHandlers.ts
│ ├── generateSasUrl.ts
│ ├── api
│ │ └── revealTreeItem.ts
│ ├── uploadFolder
│ │ ├── IUploadFolderWizardContext.ts
│ │ ├── GetFolderDestinationDirectoryStep.ts
│ │ ├── uploadFolder.ts
│ │ └── UploadFolderStep.ts
│ ├── detachStorageAccount.ts
│ ├── queue
│ │ ├── queueGroupActionHandlers.ts
│ │ └── queueActionHandlers.ts
│ ├── blob
│ │ └── blobContainerGroupActionHandlers.ts
│ ├── commonTreeCommands.ts
│ ├── startEmulator.ts
│ ├── deleteFilesAndDirectories.ts
│ ├── downloadFile.ts
│ └── StartingResourcesLogStep.ts
├── vscode.proposed.authLearnMore.d.ts
├── polyfill.worker.ts
├── AttachedAccountRoot.ts
├── StorageWorkspaceProvider.ts
├── extensionVariables.ts
├── constants.ts
└── TransferProgress.ts
├── CODE_OF_CONDUCT.md
├── .vscodeignore
├── tsconfig.json
├── main.js
├── LICENSE.md
├── extension.bundle.ts
└── SECURITY.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | 20.18.1
2 |
--------------------------------------------------------------------------------
/package.nls.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | NOTICE.html linguist-vendored=true
2 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @Microsoft/vscodeazuretoolsdev
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | gulpfile.ts
2 | .eslintrc.js
3 | webpack.config.js
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": "@microsoft/eslint-config-azuretools"
3 | };
4 |
--------------------------------------------------------------------------------
/resources/SignIn.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/vscode-azurestorage/HEAD/resources/SignIn.gif
--------------------------------------------------------------------------------
/resources/storageTree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/vscode-azurestorage/HEAD/resources/storageTree.png
--------------------------------------------------------------------------------
/resources/readme/deploy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/vscode-azurestorage/HEAD/resources/readme/deploy.png
--------------------------------------------------------------------------------
/resources/storage-deploy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/vscode-azurestorage/HEAD/resources/storage-deploy.png
--------------------------------------------------------------------------------
/resources/storageAccount.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/vscode-azurestorage/HEAD/resources/storageAccount.png
--------------------------------------------------------------------------------
/resources/SelectSubscriptions.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/vscode-azurestorage/HEAD/resources/SelectSubscriptions.gif
--------------------------------------------------------------------------------
/resources/readme/activityLog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/vscode-azurestorage/HEAD/resources/readme/activityLog.png
--------------------------------------------------------------------------------
/resources/readme/createAdvanced.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/vscode-azurestorage/HEAD/resources/readme/createAdvanced.png
--------------------------------------------------------------------------------
/resources/readme/createResource.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/vscode-azurestorage/HEAD/resources/readme/createResource.png
--------------------------------------------------------------------------------
/resources/readme/createStorageAccount.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/vscode-azurestorage/HEAD/resources/readme/createStorageAccount.png
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "ms-azuretools.vscode-azureresourcegroups"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/test/test.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": "./nightly/testFolder/html-hello-world"
5 | }
6 | ],
7 | "settings": {}
8 | }
9 |
--------------------------------------------------------------------------------
/test/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": [
3 | "@microsoft/eslint-config-azuretools",
4 | "@microsoft/eslint-config-azuretools/test"
5 | ]
6 | };
7 |
--------------------------------------------------------------------------------
/test/nightly/testFolder/html-hello-world/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Hello World!
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.azure-pipelines/compliance/CredScanSuppressions.json:
--------------------------------------------------------------------------------
1 | {
2 | "tool": "Credential Scanner",
3 | "suppressions": [
4 | {
5 | "folder": "node_modules\\",
6 | "_justification": "No need to scan external node modules."
7 | }
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.azure-pipelines/common/lint.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | - task: Npm@1
3 | displayName: 'Lint'
4 | inputs:
5 | command: custom
6 | customCommand: run lint
7 |
8 | - task: ComponentGovernanceComponentDetection@0
9 | displayName: 'Component Detection'
10 | condition: ne(variables['System.PullRequest.IsFork'], 'True')
11 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | # Support
2 |
3 | ## How to file issues and get help
4 |
5 | This project uses GitHub Issues to track bugs and feature requests. Please see https://aka.ms/azCodeIssueReporting for our issue reporting guidelines.
6 |
7 | ## Microsoft Support Policy
8 |
9 | Support for this project is limited to the resources listed above.
10 |
--------------------------------------------------------------------------------
/.azure-pipelines/common/build.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | - task: NodeTool@0
3 | displayName: 'Use Node'
4 | inputs:
5 | versionSource: fromFile
6 | versionFilePath: .nvmrc
7 |
8 | - task: Npm@1
9 | displayName: 'npm ci'
10 | inputs:
11 | command: ci
12 |
13 | - task: Npm@1
14 | displayName: 'Build'
15 | inputs:
16 | command: custom
17 | customCommand: run build
18 |
--------------------------------------------------------------------------------
/src/getApiExport.ts:
--------------------------------------------------------------------------------
1 | import { Extension, extensions } from "vscode";
2 |
3 | export async function getApiExport(extensionId: string): Promise {
4 | const extension: Extension | undefined = extensions.getExtension(extensionId);
5 | if (extension) {
6 | if (!extension.isActive) {
7 | await extension.activate();
8 | }
9 |
10 | return extension.exports;
11 | }
12 |
13 | return undefined;
14 | }
15 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/src/utils/localize.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import * as nls from 'vscode-nls';
7 |
8 | export const localize: nls.LocalizeFunc = nls.loadMessageBundle();
9 |
--------------------------------------------------------------------------------
/resources/dark/BrandAzureStaticWebsites.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/tree/ICopyUrl.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | 'use strict';
7 |
8 | export interface ICopyUrl {
9 | getUrl?(): string;
10 | copyUrl(): void;
11 | }
12 |
--------------------------------------------------------------------------------
/resources/light/BrandAzureStaticWebsites.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/storageExplorerLauncher/ResourceType.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | export type ResourceType = 'Azure.BlobContainer' | 'Azure.FileShare' | 'Azure.Queue' | 'Azure.Table';
7 |
--------------------------------------------------------------------------------
/src/tree/IStorageTreeItem.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IStorageRoot } from "./IStorageRoot";
7 |
8 | export interface IStorageTreeItem {
9 | root: IStorageRoot;
10 | }
11 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | # These files are excluded during vsce package
2 | .vscode/**
3 | .vscode-test/**
4 | out
5 | src/**
6 | .gitignore
7 | tsconfig.json
8 | vsc-extension-quickstart.md
9 | .github/**
10 | test-results.xml
11 | .azure-pipelines/**
12 | *.cmd
13 | **/*.map
14 | out/test/**
15 | README.dev.md
16 | test/**
17 | tsd.json
18 | typings/**
19 | build
20 | out/**
21 | tslint.json
22 | node_modules/**
23 | **/*.map
24 | dist/test/**
25 | gulp*
26 | webpack.config*
27 | stats.json
28 | *.vsix
29 | *.zip
30 | testWorkspace/**
31 |
--------------------------------------------------------------------------------
/src/utils/delay.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | export async function delay(ms: number): Promise {
7 | await new Promise((resolve: () => void): NodeJS.Timer => setTimeout(resolve, ms));
8 | }
9 |
--------------------------------------------------------------------------------
/src/vscode-azurestorage.api.d.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | export interface AzureStorageExtensionApi {
7 | apiVersion: string;
8 |
9 | revealTreeItem(resourceId: string): Promise;
10 | }
11 |
--------------------------------------------------------------------------------
/src/commands/transfers/azCopy/IAzCopyResolution.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IParsedError } from "@microsoft/vscode-azext-utils";
7 |
8 | export interface IAzCopyResolution {
9 | errors: IParsedError[];
10 | }
11 |
--------------------------------------------------------------------------------
/src/commands/downloadFiles/ISasDownloadContext.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext } from "@microsoft/vscode-azext-utils";
7 |
8 | export interface ISasDownloadContext extends IActionContext {
9 | sasUrl?: string;
10 | }
11 |
--------------------------------------------------------------------------------
/.azure-pipelines/compliance/tsaoptions.json:
--------------------------------------------------------------------------------
1 | {
2 | "tsaVersion": "TsaV2",
3 | "codeBase": "NewOrUpdate",
4 | "codeBaseName": "vscode-azurestorage",
5 | "tsaStamp": "DevDiv",
6 | "notificationAliases": [
7 | "AzCode@microsoft.com"
8 | ],
9 | "codebaseAdmins": [
10 | "REDMOND\\jinglou",
11 | "REDMOND\\AzCode"
12 | ],
13 | "instanceUrl": "https://devdiv.visualstudio.com",
14 | "projectName": "DevDiv",
15 | "areaPath": "DevDiv\\VS Azure Tools\\AzCode Extensions",
16 | "iterationPath": "DevDiv",
17 | "allTools": true
18 | }
19 |
--------------------------------------------------------------------------------
/.azure-pipelines/common/sbom.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0
3 | displayName: "SBoM Generation Task"
4 | inputs:
5 | BuildDropPath: "$(Build.ArtifactStagingDirectory)"
6 | condition: ne(variables['System.PullRequest.IsFork'], 'True')
7 |
8 | - task: PublishBuildArtifacts@1
9 | displayName: "Publish artifacts: sbom"
10 | inputs:
11 | PathtoPublish: "$(Build.ArtifactStagingDirectory)/_manifest"
12 | ArtifactName: _manifest
13 | condition: ne(variables['System.PullRequest.IsFork'], 'True')
14 |
--------------------------------------------------------------------------------
/src/commands/uploadFiles/IExistingFileContext.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext } from "@microsoft/vscode-azext-utils";
7 |
8 | export interface IExistingFileContext extends IActionContext {
9 | localFilePath: string;
10 | remoteFilePath: string;
11 | }
12 |
--------------------------------------------------------------------------------
/src/tree/fileShare/createFileShare/IFileShareWizardContext.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext } from "@microsoft/vscode-azext-utils";
7 |
8 | export interface IFileShareWizardContext extends IActionContext {
9 | name?: string;
10 | quota?: number;
11 | }
12 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "npm",
6 | "script": "compile",
7 | "group": {
8 | "kind": "build",
9 | "isDefault": true
10 | },
11 | "isBackground": true,
12 | "presentation": {
13 | "reveal": "never"
14 | },
15 | "problemMatcher": "$tsc-watch"
16 | },
17 | {
18 | "type": "npm",
19 | "script": "lint",
20 | "problemMatcher": "$eslint-stylish"
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/src/utils/stringUtils.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import * as crypto from "crypto";
7 |
8 | export function getRandomHexString(length: number = 10): string {
9 | const buffer: Buffer = crypto.randomBytes(Math.ceil(length / 2));
10 | return buffer.toString('hex').slice(0, length);
11 | }
12 |
--------------------------------------------------------------------------------
/src/tree/ITransferSrcOrDstTreeItem.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IStorageTreeItem } from "./IStorageTreeItem";
7 |
8 | export interface ITransferSrcOrDstTreeItem extends IStorageTreeItem {
9 | remoteFilePath: string;
10 | resourceUri: string;
11 | transferSasToken: string;
12 | }
13 |
--------------------------------------------------------------------------------
/src/utils/azureUtils.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | export function getAppResourceIdFromId(id: string): string | undefined {
7 | const matches: RegExpMatchArray | null = id.match(/\/subscriptions\/.*\/resourceGroups\/.*\/providers\/Microsoft.Storage\/storageAccounts\/[^\/]*/);
8 |
9 | return matches?.[0];
10 | }
11 |
--------------------------------------------------------------------------------
/src/storageExplorerLauncher/IStorageExplorerLauncher.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { ResourceType } from "./ResourceType";
7 |
8 | export interface IStorageExplorerLauncher {
9 | openResource(accountId: string, subscriptionid: string, resourceType?: ResourceType, resourceName?: string): Promise;
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/openUrl.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import * as open from 'open';
7 |
8 | export async function openUrl(url: string): Promise {
9 | // Using this functionality is blocked by https://github.com/Microsoft/vscode/issues/85930
10 | // await vscode.env.openExternal(vscode.Uri.parse(url));
11 |
12 | await open(url);
13 | }
14 |
--------------------------------------------------------------------------------
/resources/dark/AzureQueue.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/light/AzureQueue.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/tree/createWizard/IStaticWebsiteConfigWizardContext.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IStorageAccountWizardContext } from "@microsoft/vscode-azext-azureutils";
7 |
8 | export interface IStaticWebsiteConfigWizardContext extends IStorageAccountWizardContext {
9 | enableStaticWebsite: boolean;
10 | indexDocument: string;
11 | errorDocument404Path: string;
12 | }
13 |
--------------------------------------------------------------------------------
/src/commands/deleteBlob/IDeleteBlobWizardContext.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { ExecuteActivityContext, IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { BlobTreeItem } from "../../tree/blob/BlobTreeItem";
8 |
9 | export interface IDeleteBlobWizardContext extends IActionContext, ExecuteActivityContext {
10 | blobName?: string;
11 | blob?: BlobTreeItem;
12 | }
13 |
--------------------------------------------------------------------------------
/src/utils/workspaceUtils.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import * as vscode from 'vscode';
7 |
8 | export function getSingleRootWorkspace(): vscode.WorkspaceFolder | undefined {
9 | // if this is a multi-root workspace, return undefined
10 | return vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length === 1 ? vscode.workspace.workspaceFolders[0] : undefined;
11 | }
12 |
--------------------------------------------------------------------------------
/src/commands/fileShare/fileActionHandlers.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { registerCommandWithTreeNodeUnwrapping } from '@microsoft/vscode-azext-utils';
7 | import { deleteFilesAndDirectories } from '../deleteFilesAndDirectories';
8 |
9 | export function registerFileActionHandlers(): void {
10 | registerCommandWithTreeNodeUnwrapping("azureStorage.deleteFile", deleteFilesAndDirectories);
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/getPropertyFromConnectionString.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | export function getPropertyFromConnectionString(connectionString: string, property: string): string | undefined {
7 | const regexp: RegExp = new RegExp(`(?:^|;)\\s*${property}=([^;]+)(?:;|$)`, 'i');
8 | const match: RegExpMatchArray | undefined = connectionString.match(regexp) || undefined;
9 | return match && match[1];
10 | }
11 |
--------------------------------------------------------------------------------
/.azure-pipelines/compliance/PoliCheckExclusions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | NODE_MODULES
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/commands/deleteBlobDirectory/IDeleteBlobDirectoryWizardContext.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { ExecuteActivityContext, IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { BlobDirectoryTreeItem } from "../../tree/blob/BlobDirectoryTreeItem";
8 |
9 | export interface IDeleteBlobDirectoryWizardContext extends IActionContext, ExecuteActivityContext {
10 | dirName?: string;
11 | blobDirectory?: BlobDirectoryTreeItem;
12 | }
13 |
--------------------------------------------------------------------------------
/src/utils/copyAndShowToast.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { env } from "vscode";
7 | import { ext } from "../extensionVariables";
8 | import { localize } from "./localize";
9 |
10 | export async function copyAndShowToast(text: string, name?: string): Promise {
11 | await env.clipboard.writeText(text);
12 | ext.outputChannel.show();
13 | ext.outputChannel.appendLog(localize('copiedClipboard', '{1} copied to clipboard: {0}', text, name));
14 | }
15 |
--------------------------------------------------------------------------------
/src/commands/deleteStorageAccount/DeleteStorageAccountWizardContext.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { ExecuteActivityContext, IActionContext, ISubscriptionContext } from "@microsoft/vscode-azext-utils";
7 | import { StorageAccountWrapper } from "../../utils/storageWrappers";
8 |
9 | export interface DeleteStorageAccountWizardContext extends IActionContext, ExecuteActivityContext {
10 | resourceGroupName?: string;
11 | storageAccount?: StorageAccountWrapper;
12 | subscription: ISubscriptionContext;
13 | }
14 |
--------------------------------------------------------------------------------
/src/vscode.proposed.authLearnMore.d.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | declare module 'vscode' {
7 |
8 | // https://github.com/microsoft/vscode/issues/206587
9 |
10 | export interface AuthenticationGetSessionPresentationOptions {
11 | /**
12 | * An optional Uri to open in the browser to learn more about this authentication request.
13 | */
14 | learnMore?: Uri;
15 | }
16 | }
17 |
18 | // this proposed api is only included because vscode.proposed.authenticationChallenges.d.ts depends on it
19 |
--------------------------------------------------------------------------------
/resources/dark/AzureBlobContainer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/light/AzureBlobContainer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/dark/AzureFileShare.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/light/AzureFileShare.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es2018",
5 | "outDir": "out",
6 | "lib": [
7 | "es2021",
8 | "dom",
9 | "es6",
10 | "esnext.asynciterable"
11 | ],
12 | "sourceMap": true,
13 | "rootDir": ".",
14 | "noUnusedLocals": true,
15 | "noImplicitThis": true,
16 | "noImplicitReturns": true,
17 | "strictNullChecks": true,
18 | "noUnusedParameters": true,
19 | "baseUrl": "./",
20 | "paths": {
21 | "*": [
22 | "node_modules/@types/*",
23 | "*"
24 | ]
25 | },
26 | "skipLibCheck": false
27 | },
28 | "exclude": [
29 | "node_modules",
30 | ".vscode-test",
31 | "gulpfile.ts"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | "source.fixAll.eslint": "explicit",
4 | "source.organizeImports": "explicit"
5 | },
6 | "editor.detectIndentation": false,
7 | "editor.formatOnSave": true,
8 | "editor.insertSpaces": true,
9 | "editor.tabSize": 4,
10 | "files.insertFinalNewline": true,
11 | "files.trimTrailingWhitespace": true,
12 | "search.exclude": {
13 | "out": true,
14 | "**/node_modules": true,
15 | ".vscode-test": true
16 | },
17 | "typescript.preferences.importModuleSpecifier": "relative",
18 | "typescript.tsdk": "node_modules/typescript/lib",
19 | "cSpell.words": [
20 | "azureextensionui",
21 | "azurestorage",
22 | "azuretools",
23 | "mailto",
24 | "norequired",
25 | "opencode",
26 | "repo"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/test/assertThrowsAsync.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See LICENSE.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import * as assert from 'assert';
7 |
8 | /**
9 | * Same as assert.throws except for async functions
10 | */
11 | export async function assertThrowsAsync(block: () => Promise, error: RegExp | Function, message?: string): Promise {
12 | let blockSync = (): void => { /* ignore */ };
13 | try {
14 | await block();
15 | } catch (e) {
16 | blockSync = (): void => { throw e; };
17 | } finally {
18 | assert.throws(blockSync, error, message);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.github/workflows/locker.yml:
--------------------------------------------------------------------------------
1 | name: Locker
2 | on:
3 | schedule:
4 | - cron: 0 5 * * * # 10:00pm PT
5 | workflow_dispatch:
6 |
7 | jobs:
8 | main:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout Actions
12 | uses: actions/checkout@v2
13 | with:
14 | repository: "microsoft/vscode-github-triage-actions"
15 | path: ./actions
16 | ref: stable
17 | - name: Install Actions
18 | run: npm install --production --prefix ./actions
19 | - name: Run Locker
20 | uses: ./actions/locker
21 | with:
22 | app_id: ${{ secrets.AZURETOOLS_VSCODE_BOT_APP_ID }}
23 | app_installation_id: ${{ secrets.AZURETOOLS_VSCODE_BOT_APP_INSTALLATION_ID }}
24 | app_private_key: ${{ secrets.AZURETOOLS_VSCODE_BOT_APP_PRIVATE_KEY }}
25 | daysSinceClose: 45
26 | daysSinceUpdate: 7
27 |
--------------------------------------------------------------------------------
/.azure-pipelines/common/test.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | - script: |
3 | sudo cp .azure-pipelines/linux/xvfb.init /etc/init.d/xvfb
4 | sudo chmod +x /etc/init.d/xvfb
5 | sudo update-rc.d xvfb defaults
6 | sudo service xvfb start
7 | displayName: 'Start X Virtual Frame Buffer'
8 | condition: eq(variables['Agent.OS'], 'Linux')
9 |
10 | - task: Npm@1
11 | displayName: 'Test'
12 | inputs:
13 | command: custom
14 | customCommand: test
15 | env:
16 | SERVICE_PRINCIPAL_CLIENT_ID: $(SERVICE_PRINCIPAL_CLIENT_ID)
17 | SERVICE_PRINCIPAL_SECRET: $(SERVICE_PRINCIPAL_SECRET)
18 | SERVICE_PRINCIPAL_DOMAIN: $(SERVICE_PRINCIPAL_DOMAIN)
19 | DISPLAY: :10 # Only necessary for linux tests
20 |
21 | - task: PublishTestResults@2
22 | displayName: 'Publish Test Results'
23 | inputs:
24 | testResultsFiles: '*-results.xml'
25 | testRunTitle: '$(Agent.OS)'
26 | condition: succeededOrFailed()
27 |
--------------------------------------------------------------------------------
/src/polyfill.worker.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { JSDOM } from "jsdom";
7 |
8 | const { window } = new JSDOM();
9 |
10 | export function polyfill(): void {
11 | if (!globalThis.document) {
12 | globalThis.document = window.document;
13 | }
14 |
15 | if (!globalThis.DOMParser) {
16 | globalThis.DOMParser = window.DOMParser;
17 | }
18 |
19 | if (!globalThis.XMLSerializer) {
20 | globalThis.XMLSerializer = window.XMLSerializer;
21 | }
22 |
23 | if (!globalThis.Node) {
24 | globalThis.Node = window.Node;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/commands/downloadFiles/IDownloadWizardContext.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { ExecuteActivityContext, IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { ITransferSrcOrDstTreeItem } from "../../tree/ITransferSrcOrDstTreeItem";
8 | import { DownloadItem } from "../transfers/transfers";
9 |
10 | export interface IDownloadWizardContext extends IActionContext, ExecuteActivityContext {
11 | destinationFolder?: string;
12 | sasUrl?: string;
13 | treeItems?: ITransferSrcOrDstTreeItem[];
14 |
15 | allFileDownloads?: DownloadItem[];
16 | allFolderDownloads?: DownloadItem[];
17 | }
18 |
--------------------------------------------------------------------------------
/src/commands/openInFileExplorer/IOpenInFileExplorerWizardContext.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { BlobContainerTreeItem } from '../../tree/blob/BlobContainerTreeItem';
8 | import { FileShareTreeItem } from '../../tree/fileShare/FileShareTreeItem';
9 |
10 | export interface IOpenInFileExplorerWizardContext extends IActionContext {
11 | treeItem: BlobContainerTreeItem | FileShareTreeItem;
12 | openBehavior?: OpenBehavior;
13 | }
14 |
15 | export type OpenBehavior = 'AddToWorkspace' | 'OpenInNewWindow' | 'OpenInCurrentWindow' | 'AlreadyOpen' | 'DontOpen';
16 |
--------------------------------------------------------------------------------
/src/tree/refreshTreeItem.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { AttachedAccountRoot } from "../AttachedAccountRoot";
8 | import { ext } from "../extensionVariables";
9 | import { IStorageTreeItem } from "./IStorageTreeItem";
10 |
11 | export async function refreshTreeItem(actionContext: IActionContext, node: (AzExtTreeItem & IStorageTreeItem) | undefined): Promise {
12 | node && node.root instanceof AttachedAccountRoot ?
13 | await ext.rgApi.workspaceResourceTree.refresh(actionContext, node) :
14 | await ext.rgApi.appResourceTree.refresh(actionContext, node);
15 | }
16 |
--------------------------------------------------------------------------------
/src/commands/table/tableGroupActionHandlers.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext, registerCommandWithTreeNodeUnwrapping } from '@microsoft/vscode-azext-utils';
7 | import { TableGroupTreeItem } from '../../tree/table/TableGroupTreeItem';
8 | import { createChildNode } from '../commonTreeCommands';
9 |
10 | export function registerTableGroupActionHandlers(): void {
11 | registerCommandWithTreeNodeUnwrapping("azureStorage.createTable", createTable);
12 | }
13 |
14 | export async function createTable(context: IActionContext, treeItem?: TableGroupTreeItem): Promise {
15 | await createChildNode(context, TableGroupTreeItem.contextValue, treeItem);
16 | }
17 |
--------------------------------------------------------------------------------
/src/storageExplorerLauncher/storageExplorerLauncher.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import * as os from "os";
7 | import { IStorageExplorerLauncher } from "./IStorageExplorerLauncher";
8 | import { MacOSStorageExplorerLauncher } from "./macOSStorageExplorerLauncher";
9 | import { WindowsStorageExplorerLauncher } from "./windowsStorageExplorerLauncher";
10 |
11 | let storageExplorerLauncher: IStorageExplorerLauncher;
12 |
13 | if (os.platform() === "win32") {
14 | storageExplorerLauncher = new WindowsStorageExplorerLauncher();
15 | } else {
16 | // assume Mac Os for now.
17 | storageExplorerLauncher = new MacOSStorageExplorerLauncher();
18 | }
19 |
20 | export { storageExplorerLauncher };
21 |
--------------------------------------------------------------------------------
/.github/workflows/bump-version-pr.yaml:
--------------------------------------------------------------------------------
1 | name: Bump version after release
2 |
3 | # Run when release is published, or manually triggered
4 | on:
5 | release:
6 | types: [published]
7 | workflow_dispatch:
8 |
9 | jobs:
10 | bump:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 |
15 | - name: Bump version
16 | id: bump
17 | uses: alexweininger/bump-prerelease-version@v0.1.1
18 |
19 | - name: Create pull request
20 | uses: peter-evans/create-pull-request@v4
21 | with:
22 | title: ${{ env.MESSAGE }}
23 | body: Automatically created by ${{ env.RUN_LINK }}
24 | commit-message: ${{ env.MESSAGE }}
25 | branch: bot/bump-${{ steps.bump.outputs.new-version }}
26 | base: main
27 | author: GitHub
28 | env:
29 | RUN_LINK: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
30 | MESSAGE: Bump version after release
31 |
--------------------------------------------------------------------------------
/src/commands/fileShare/fileShareGroupActionHandlers.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext, registerCommandWithTreeNodeUnwrapping } from '@microsoft/vscode-azext-utils';
7 | import { FileShareGroupTreeItem } from '../../tree/fileShare/FileShareGroupTreeItem';
8 | import { createChildNode } from '../commonTreeCommands';
9 |
10 | export function registerFileShareGroupActionHandlers(): void {
11 | registerCommandWithTreeNodeUnwrapping("azureStorage.createFileShare", createFileShare);
12 | }
13 |
14 | export async function createFileShare(context: IActionContext, treeItem?: FileShareGroupTreeItem): Promise {
15 | await createChildNode(context, FileShareGroupTreeItem.contextValue, treeItem);
16 | }
17 |
--------------------------------------------------------------------------------
/src/commands/generateSasUrl.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext } from '@microsoft/vscode-azext-utils';
7 | import { posix } from 'path';
8 | import { ITransferSrcOrDstTreeItem } from '../tree/ITransferSrcOrDstTreeItem';
9 | import { copyAndShowToast } from '../utils/copyAndShowToast';
10 |
11 | export async function generateSasUrl(_context: IActionContext, treeItem: ITransferSrcOrDstTreeItem): Promise {
12 | const resourceUri = treeItem.resourceUri;
13 | const sasToken = treeItem.transferSasToken;
14 | const sasUrl: string = `${resourceUri}${posix.sep}${treeItem.remoteFilePath}?${sasToken}`;
15 | await copyAndShowToast(sasUrl, 'SAS Token and URL');
16 | return sasUrl;
17 | }
18 |
--------------------------------------------------------------------------------
/src/commands/api/revealTreeItem.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzExtTreeItem, callWithTelemetryAndErrorHandling, IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { ext } from "../../extensionVariables";
8 |
9 | export async function revealTreeItem(resourceId: string): Promise {
10 | return await callWithTelemetryAndErrorHandling('api.revealTreeItem', async (context: IActionContext) => {
11 | const node: AzExtTreeItem | undefined = await ext.rgApi.appResourceTree.findTreeItem(resourceId, { ...context, loadAll: true });
12 | if (node) {
13 | await ext.rgApi.appResourceTreeView.reveal(node, { select: true, focus: true, expand: true });
14 | }
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/src/commands/uploadFiles/IUploadFilesWizardContext.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { ExecuteActivityContext, IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { Uri } from "vscode";
8 | import { BlobContainerTreeItem } from "../../tree/blob/BlobContainerTreeItem";
9 | import { FileShareTreeItem } from "../../tree/fileShare/FileShareTreeItem";
10 | import { IAzCopyResolution } from "../transfers/azCopy/IAzCopyResolution";
11 |
12 | export interface IUploadFilesWizardContext extends IActionContext, ExecuteActivityContext {
13 | calledFromUploadToAzureStorage: boolean;
14 | destinationDirectory?: string;
15 | resolution?: IAzCopyResolution;
16 | treeItem?: BlobContainerTreeItem | FileShareTreeItem;
17 | uris?: Uri[];
18 | }
19 |
--------------------------------------------------------------------------------
/src/commands/uploadFolder/IUploadFolderWizardContext.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { ExecuteActivityContext, IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { Uri } from "vscode";
8 | import { BlobContainerTreeItem } from "../../tree/blob/BlobContainerTreeItem";
9 | import { FileShareTreeItem } from "../../tree/fileShare/FileShareTreeItem";
10 | import { IAzCopyResolution } from "../transfers/azCopy/IAzCopyResolution";
11 |
12 | export interface IUploadFolderWizardContext extends IActionContext, ExecuteActivityContext {
13 | calledFromUploadToAzureStorage: boolean;
14 | destinationDirectory?: string;
15 | resolution?: IAzCopyResolution;
16 | treeItem?: BlobContainerTreeItem | FileShareTreeItem;
17 | uri?: Uri;
18 | }
19 |
--------------------------------------------------------------------------------
/src/utils/getCoreNodeModule.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See LICENSE.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import * as vscode from 'vscode';
7 |
8 | /**
9 | * Returns a node module installed with VSCode, or undefined if it fails.
10 | */
11 | export function getCoreNodeModule(moduleName: string): T | undefined {
12 | try {
13 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return
14 | return require(`${vscode.env.appRoot}/node_modules.asar/${moduleName}`);
15 | } catch (err) {
16 | // ignore
17 | }
18 |
19 | try {
20 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return
21 | return require(`${vscode.env.appRoot}/node_modules/${moduleName}`);
22 | } catch (err) {
23 | // ignore
24 | }
25 | return undefined;
26 | }
27 |
--------------------------------------------------------------------------------
/resources/dark/AzureTable.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/light/AzureTable.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/commands/detachStorageAccount.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { ext } from "../extensionVariables";
8 | import { AttachedStorageAccountTreeItem } from "../tree/AttachedStorageAccountTreeItem";
9 |
10 | export async function detachStorageAccount(actionContext: IActionContext, treeItem?: AttachedStorageAccountTreeItem): Promise {
11 | if (!treeItem) {
12 | treeItem = await ext.rgApi.workspaceResourceTree.showTreeItemPicker(AttachedStorageAccountTreeItem.baseContextValue, actionContext);
13 | }
14 |
15 | await ext.attachedStorageAccountsTreeItem.detach(treeItem);
16 | await ext.rgApi.workspaceResourceTree.refresh(actionContext, ext.attachedStorageAccountsTreeItem);
17 | }
18 |
--------------------------------------------------------------------------------
/test/nightly/global.resource.test.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See LICENSE.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { StorageManagementClient } from '@azure/arm-storage';
7 | import { longRunningTestsEnabled } from '../global.test';
8 |
9 | export let webSiteClient: StorageManagementClient;
10 | export const resourceGroupsToDelete: string[] = [];
11 |
12 | suiteSetup(async function (this: Mocha.Context): Promise {
13 | this.skip();
14 | if (longRunningTestsEnabled) {
15 | this.timeout(2 * 60 * 1000);
16 | }
17 | });
18 |
19 | suiteTeardown(async function (this: Mocha.Context): Promise {
20 | if (longRunningTestsEnabled) {
21 | this.timeout(10 * 60 * 1000);
22 | // await Promise.all(resourceGroupsToDelete.map(async resource => {
23 | // await beginDeleteResourceGroup(resource);
24 | // }));
25 | }
26 | });
27 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See LICENSE.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | "use strict";
7 |
8 | // This is the extension entrypoint module, which imports extension.bundle.js, the actual extension code.
9 | //
10 | // This is in a separate file so we can properly measure extension.bundle.js load time.
11 |
12 | let perfStats = {
13 | loadStartTime: Date.now(),
14 | loadEndTime: undefined
15 | };
16 |
17 | Object.defineProperty(exports, "__esModule", { value: true });
18 |
19 | const extension = require('./out/src/extension');
20 |
21 | async function activate(ctx) {
22 | return await extension.activate(ctx, perfStats, true);
23 | }
24 |
25 | async function deactivate(ctx) {
26 | return await extension.deactivate(ctx, perfStats);
27 | }
28 |
29 | // Export as entrypoints for vscode
30 | exports.activate = activate;
31 | exports.deactivate = deactivate;
32 |
33 | perfStats.loadEndTime = Date.now();
34 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/src/commands/queue/queueGroupActionHandlers.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext, registerCommandWithTreeNodeUnwrapping } from '@microsoft/vscode-azext-utils';
7 | import { QueueGroupTreeItem } from '../../tree/queue/QueueGroupTreeItem';
8 | import { isAzuriteInstalled, warnAzuriteNotInstalled } from '../../utils/azuriteUtils';
9 | import { createChildNode } from '../commonTreeCommands';
10 |
11 | export function registerQueueGroupActionHandlers(): void {
12 | registerCommandWithTreeNodeUnwrapping("azureStorage.createQueue", createQueue);
13 | }
14 |
15 | export async function createQueue(context: IActionContext, treeItem?: QueueGroupTreeItem): Promise {
16 | if (treeItem?.root.isEmulated && !(await isAzuriteInstalled())) {
17 | warnAzuriteNotInstalled(context);
18 | }
19 | await createChildNode(context, QueueGroupTreeItem.contextValue, treeItem);
20 | }
21 |
--------------------------------------------------------------------------------
/.azure-pipelines/release.yml:
--------------------------------------------------------------------------------
1 | trigger: none # Only run this pipeline when manually triggered
2 |
3 | parameters:
4 | - name: publishVersion
5 | displayName: Version to publish
6 | type: string
7 | - name: dryRun
8 | displayName: Dry run
9 | type: boolean
10 | default: false
11 |
12 | resources:
13 | pipelines:
14 | - pipeline: build # identifier to use in pipeline resource variables
15 | source: \Azure Tools\VSCode\Extensions\vscode-azurestorage # name of the pipeline that produces the artifacts
16 | repositories:
17 | - repository: azExtTemplates
18 | type: github
19 | name: microsoft/vscode-azuretools
20 | ref: main
21 | endpoint: GitHub-AzureTools # The service connection to use when accessing this repository
22 |
23 | variables:
24 | # Required by MicroBuild template
25 | - name: TeamName
26 | value: "Azure Tools for VS Code"
27 |
28 | # Use those templates
29 | extends:
30 | template: azure-pipelines/release-extension.yml@azExtTemplates
31 | parameters:
32 | pipelineID: $(resources.pipeline.build.pipelineID)
33 | runID: $(resources.pipeline.build.runID)
34 | publishVersion: ${{ parameters.publishVersion }}
35 | dryRun: ${{ parameters.dryRun }}
36 | environmentName: AzCodeDeploy
37 |
--------------------------------------------------------------------------------
/src/utils/settingsUtils.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { Uri, workspace, WorkspaceConfiguration } from "vscode";
7 | import { ext } from "../extensionVariables";
8 |
9 | /**
10 | * Uses ext.prefix unless otherwise specified
11 | */
12 | export async function updateWorkspaceSetting(section: string, value: T, fsPath: string, prefix: string = ext.prefix): Promise {
13 | const projectConfiguration: WorkspaceConfiguration = workspace.getConfiguration(prefix, Uri.file(fsPath));
14 | await projectConfiguration.update(section, value);
15 | }
16 |
17 | /**
18 | * Uses ext.prefix unless otherwise specified
19 | */
20 | export function getWorkspaceSetting(key: string, fsPath?: string, prefix: string = ext.prefix): T | undefined {
21 | const projectConfiguration: WorkspaceConfiguration = workspace.getConfiguration(prefix, fsPath ? Uri.file(fsPath) : undefined);
22 | return projectConfiguration.get(key);
23 | }
24 |
--------------------------------------------------------------------------------
/resources/dark/AzureStorageAccount.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/resources/light/AzureStorageAccount.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/src/commands/blob/blobContainerGroupActionHandlers.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext, registerCommandWithTreeNodeUnwrapping } from '@microsoft/vscode-azext-utils';
7 | import { BlobContainerGroupTreeItem } from '../../tree/blob/BlobContainerGroupTreeItem';
8 | import { isAzuriteInstalled, warnAzuriteNotInstalled } from '../../utils/azuriteUtils';
9 | import { createChildNode } from '../commonTreeCommands';
10 |
11 | export function registerBlobContainerGroupActionHandlers(): void {
12 | registerCommandWithTreeNodeUnwrapping("azureStorage.createBlobContainer", createBlobContainer);
13 | }
14 |
15 | export async function createBlobContainer(context: IActionContext, treeItem?: BlobContainerGroupTreeItem): Promise {
16 | if (treeItem?.root.isEmulated && !(await isAzuriteInstalled())) {
17 | warnAzuriteNotInstalled(context);
18 | }
19 | await createChildNode(context, BlobContainerGroupTreeItem.contextValue, treeItem);
20 | }
21 |
--------------------------------------------------------------------------------
/src/commands/fileShare/directoryActionHandlers.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzExtParentTreeItem, IActionContext, registerCommandWithTreeNodeUnwrapping } from '@microsoft/vscode-azext-utils';
7 | import { DirectoryTreeItem } from '../../tree/fileShare/DirectoryTreeItem';
8 | import { IFileShareCreateChildContext } from '../../tree/fileShare/FileShareTreeItem';
9 | import { deleteFilesAndDirectories } from '../deleteFilesAndDirectories';
10 |
11 | export function registerDirectoryActionHandlers(): void {
12 | registerCommandWithTreeNodeUnwrapping("azureStorage.deleteDirectory", deleteFilesAndDirectories);
13 | registerCommandWithTreeNodeUnwrapping("azureStorage.createSubdirectory", async (context: IActionContext, treeItem: AzExtParentTreeItem) => await treeItem.createChild({ ...context, childType: DirectoryTreeItem.contextValue }));
14 |
15 | // Note: azureStorage.createTextFile is registered in fileShareActionHandlers
16 | }
17 |
--------------------------------------------------------------------------------
/src/utils/fs.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import * as path from 'path';
7 | import * as vscode from 'vscode';
8 | import { Uri, workspace } from 'vscode';
9 |
10 | export function isPathEqual(fsPath1: string, fsPath2: string): boolean {
11 | const relativePath: string = path.relative(fsPath1, fsPath2);
12 | return relativePath === '';
13 | }
14 |
15 | export function isSubpath(expectedParent: string, expectedChild: string): boolean {
16 | const relativePath: string = path.relative(expectedParent, expectedChild);
17 | return relativePath !== '' && !relativePath.startsWith('..') && relativePath !== expectedChild;
18 | }
19 |
20 | export async function isEmptyDirectory(pathOrUri: Uri | string): Promise {
21 | if (typeof pathOrUri === 'string') {
22 | pathOrUri = vscode.Uri.file(pathOrUri)
23 | }
24 |
25 | const files = await workspace.fs.readDirectory(pathOrUri);
26 | if (files.length === 0) {
27 | return true;
28 | }
29 | return false;
30 | }
31 |
--------------------------------------------------------------------------------
/src/commands/downloadFiles/DestinationPromptStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardPromptStep, IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { configurationSettingsKeys } from "../../constants";
8 | import { localize } from "../../utils/localize";
9 | import { showWorkspaceFoldersQuickPick } from "../../utils/quickPickUtils";
10 | import { IDownloadWizardContext } from "./IDownloadWizardContext";
11 |
12 | export class DestinationPromptStep extends AzureWizardPromptStep {
13 | public async prompt(context: IDownloadWizardContext): Promise {
14 | const placeHolderString: string = localize('selectFolderForDownload', 'Select destination folder for download');
15 | context.destinationFolder = await showWorkspaceFoldersQuickPick(placeHolderString, context, configurationSettingsKeys.deployPath);
16 | }
17 |
18 | public shouldPrompt(context: IDownloadWizardContext): boolean {
19 | return !context.destinationFolder;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/utils/launcher.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | **/
5 |
6 | import { spawn } from "child_process";
7 |
8 | // eslint-disable-next-line @typescript-eslint/no-extraneous-class
9 | export class Launcher {
10 | public static async launch(command: string, ...args: string[]): Promise {
11 | await new Promise((resolve, _reject) => {
12 | const spawnEnv = <{ [key: string]: string }>JSON.parse(JSON.stringify(process.env));
13 | // remove those env vars
14 | delete spawnEnv.ATOM_SHELL_INTERNAL_RUN_AS_NODE;
15 | delete spawnEnv.ELECTRON_RUN_AS_NODE;
16 |
17 | const childProcess = spawn(
18 | command,
19 | args,
20 | {
21 | env: spawnEnv
22 | }
23 | );
24 |
25 | childProcess.stdout.on("data", (chunk) => {
26 | resolve();
27 | console.log(`child process message: ${chunk}`);
28 | });
29 |
30 | childProcess.stderr.on("data", (chunk) => {
31 | console.log(`child process message: ${chunk}`);
32 | });
33 | });
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/utils/nonNull.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | /**
7 | * Retrieves a property by name from an object and checks that it's not null and not undefined. It is strongly typed
8 | * for the property and will give a compile error if the given name is not a property of the source.
9 | */
10 | export function nonNullProp(source: TSource, name: TKey): NonNullable {
11 | const value: NonNullable = >source[name];
12 | return nonNullValue(value, name);
13 | }
14 |
15 | /**
16 | * Validates that a given value is not null and not undefined.
17 | */
18 | export function nonNullValue(value: T | undefined | null, propertyNameOrMessage?: string): T {
19 | if (value === null || value === undefined) {
20 | throw new Error(
21 | 'Internal error: Expected value to be neither null nor undefined'
22 | + (propertyNameOrMessage ? `: ${propertyNameOrMessage}` : ''));
23 | }
24 |
25 | return value;
26 | }
27 |
--------------------------------------------------------------------------------
/.azure-pipelines/SignExtension.signproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 |
6 |
7 |
9 |
10 |
11 | VSCodePublisher
12 |
13 |
14 |
15 |
16 |
17 | runtime; build; native; contentfiles; analyzers; buildtransitive
18 | all
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.github/workflows/info-needed-closer.yml:
--------------------------------------------------------------------------------
1 | name: Info Needed Closer
2 | on:
3 | schedule:
4 | - cron: 30 5 * * * # 10:30pm PT
5 | workflow_dispatch:
6 |
7 | jobs:
8 | main:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout Actions
12 | uses: actions/checkout@v2
13 | with:
14 | repository: "microsoft/vscode-github-triage-actions"
15 | path: ./actions
16 | ref: stable
17 | - name: Install Actions
18 | run: npm install --production --prefix ./actions
19 | - name: Run Info Needed Closer
20 | uses: ./actions/needs-more-info-closer
21 | with:
22 | app_id: ${{ secrets.AZURETOOLS_VSCODE_BOT_APP_ID }}
23 | app_installation_id: ${{ secrets.AZURETOOLS_VSCODE_BOT_APP_INSTALLATION_ID }}
24 | app_private_key: ${{ secrets.AZURETOOLS_VSCODE_BOT_APP_PRIVATE_KEY }}
25 | label: info-needed
26 | closeDays: 14
27 | closeComment: "This issue has been closed automatically because it needs more information and has not had recent activity. See also our [issue reporting](https://aka.ms/azcodeissuereporting) guidelines.\n\nHappy Coding!"
28 | pingDays: 80
29 | pingComment: "Hey @${assignee}, this issue might need further attention.\n\n@${author}, you can help us out by closing this issue if the problem no longer exists, or adding more information."
30 |
--------------------------------------------------------------------------------
/src/utils/errorUtils.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IParsedError, TelemetryProperties, UserCancelledError } from '@microsoft/vscode-azext-utils';
7 | import * as vscode from 'vscode';
8 | import { ext } from '../extensionVariables';
9 | import { localize } from './localize';
10 |
11 | export const multipleAzCopyErrorsMessage: string = localize('multipleAzCopyErrors', 'Multiple AzCopy errors occurred while uploading. Check the [output window](command:{0}) for more details.', `${ext.prefix}.showOutputChannel`);
12 |
13 | export function throwIfCanceled(cancellationToken: vscode.CancellationToken | undefined, properties: TelemetryProperties | undefined, cancelStep: string): void {
14 | if (cancellationToken && cancellationToken.isCancellationRequested) {
15 | if (properties && cancelStep) {
16 | properties.cancelStep = cancelStep;
17 | }
18 | throw new UserCancelledError();
19 | }
20 | }
21 |
22 | export function isAzCopyError(parsedError: IParsedError): boolean {
23 | return /azcopy/i.test(parsedError.message);
24 | }
25 |
--------------------------------------------------------------------------------
/src/commands/downloadFiles/DownloadFilesStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardExecuteStep } from "@microsoft/vscode-azext-utils";
7 | import { CancellationToken } from "vscode";
8 | import { NotificationProgress } from "../../constants";
9 | import { downloadFoldersAndFiles } from "../transfers/transfers";
10 | import { IDownloadWizardContext } from "./IDownloadWizardContext";
11 |
12 | export class DownloadFilesStep extends AzureWizardExecuteStep {
13 | public priority: number = 300;
14 |
15 | public constructor(private readonly cancellationToken?: CancellationToken) {
16 | super();
17 | }
18 |
19 | public async execute(context: IDownloadWizardContext, notificationProgress: NotificationProgress): Promise {
20 | await downloadFoldersAndFiles(context, context.allFolderDownloads ?? [], context.allFileDownloads ?? [], notificationProgress, this.cancellationToken);
21 | }
22 |
23 | public shouldExecute(wizardContext: IDownloadWizardContext): boolean {
24 | return !!wizardContext.destinationFolder;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/global.test.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See LICENSE.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { TestUserInput } from '@microsoft/vscode-azext-dev';
7 | import * as vscode from 'vscode';
8 | import { registerOnActionStartHandler } from "../extension.bundle";
9 |
10 | const longRunningLocalTestsEnabled: boolean = !/^(false|0)?$/i.test(process.env.AzCode_EnableLongRunningTestsLocal || '');
11 | const longRunningRemoteTestsEnabled: boolean = !/^(false|0)?$/i.test(process.env.AzCode_UseAzureFederatedCredentials || '');
12 |
13 | export const longRunningTestsEnabled: boolean = longRunningLocalTestsEnabled || longRunningRemoteTestsEnabled;
14 |
15 | // Runs before all tests
16 | suiteSetup(async function (this: Mocha.Context): Promise {
17 | this.timeout(120 * 1000);
18 | await vscode.commands.executeCommand('azureStorage.refresh'); // activate the extension before tests begin
19 |
20 | registerOnActionStartHandler(context => {
21 | // Use `TestUserInput` by default so we get an error if an unexpected call to `context.ui` occurs, rather than timing out
22 | context.ui = new TestUserInput(vscode);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/.azure-pipelines/linux/xvfb.init:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # COPIED FROM https://github.com/microsoft/vscode/blob/01e9903967417ba243cec705445eef9ecbfebfea/build/azure-pipelines/linux/xvfb.init
4 | #
5 | #
6 | # /etc/rc.d/init.d/xvfbd
7 | #
8 | # chkconfig: 345 95 28
9 | # description: Starts/Stops X Virtual Framebuffer server
10 | # processname: Xvfb
11 | #
12 | ### BEGIN INIT INFO
13 | # Provides: xvfb
14 | # Required-Start: $remote_fs $syslog
15 | # Required-Stop: $remote_fs $syslog
16 | # Default-Start: 2 3 4 5
17 | # Default-Stop: 0 1 6
18 | # Short-Description: Start xvfb at boot time
19 | # Description: Enable xvfb provided by daemon.
20 | ### END INIT INFO
21 |
22 | [ "${NETWORKING}" = "no" ] && exit 0
23 |
24 | PROG="/usr/bin/Xvfb"
25 | PROG_OPTIONS=":10 -ac -screen 0 1024x768x24"
26 | PROG_OUTPUT="/tmp/Xvfb.out"
27 |
28 | case "$1" in
29 | start)
30 | echo "Starting : X Virtual Frame Buffer "
31 | $PROG $PROG_OPTIONS>>$PROG_OUTPUT 2>&1 &
32 | disown -ar
33 | ;;
34 | stop)
35 | echo "Shutting down : X Virtual Frame Buffer"
36 | killproc $PROG
37 | RETVAL=$?
38 | [ $RETVAL -eq 0 ] && /bin/rm -f /var/lock/subsys/Xvfb
39 | /var/run/Xvfb.pid
40 | echo
41 | ;;
42 | restart|reload)
43 | $0 stop
44 | $0 start
45 | RETVAL=$?
46 | ;;
47 | status)
48 | status Xvfb
49 | RETVAL=$?
50 | ;;
51 | *)
52 | echo $"Usage: $0 (start|stop|restart|reload|status)"
53 | exit 1
54 | esac
55 |
56 | exit $RETVAL
57 |
--------------------------------------------------------------------------------
/src/tree/SubscriptionTreeItem.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { SubscriptionTreeItemBase } from '@microsoft/vscode-azext-azureutils';
7 | import { AzExtTreeItem, IActionContext } from '@microsoft/vscode-azext-utils';
8 | import { AttachedStorageAccountTreeItem } from './AttachedStorageAccountTreeItem';
9 |
10 | export class SubscriptionTreeItem extends SubscriptionTreeItemBase {
11 | public childTypeLabel: string = "Storage Account";
12 | public supportsAdvancedCreation: boolean = true;
13 |
14 | private _nextLink: string | undefined;
15 |
16 | async loadMoreChildrenImpl(clearCache: boolean, _context: IActionContext): Promise {
17 | if (clearCache) {
18 | this._nextLink = undefined;
19 | }
20 | return [];
21 | }
22 |
23 | public hasMoreChildrenImpl(): boolean {
24 | return !!this._nextLink;
25 | }
26 |
27 | public isAncestorOfImpl(contextValue: string): boolean {
28 | return contextValue !== AttachedStorageAccountTreeItem.baseContextValue && contextValue !== AttachedStorageAccountTreeItem.emulatedContextValue;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/commands/commonTreeCommands.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzExtParentTreeItem, AzExtTreeItem, IActionContext } from '@microsoft/vscode-azext-utils';
7 | import { storageFilter } from '../constants';
8 | import { ext } from '../extensionVariables';
9 |
10 | export async function deleteNode(context: IActionContext, expectedContextValue?: string | RegExp, node?: AzExtTreeItem): Promise {
11 | if (!node) {
12 | node = await ext.rgApi.pickAppResource({ ...context, suppressCreatePick: true }, {
13 | filter: storageFilter,
14 | expectedChildContextValue: expectedContextValue
15 | });
16 | }
17 |
18 | await node.deleteTreeItem(context);
19 | }
20 |
21 | export async function createChildNode(context: IActionContext, expectedContextValue: string, node?: AzExtParentTreeItem): Promise {
22 | if (!node) {
23 | node = await ext.rgApi.pickAppResource(context, {
24 | filter: storageFilter,
25 | expectedChildContextValue: expectedContextValue
26 | });
27 | }
28 |
29 | await node.createChild(context);
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils/storageWrappers.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { Endpoints, StorageAccount, StorageAccountKey } from '@azure/arm-storage';
7 | import { nonNullProp } from '@microsoft/vscode-azext-utils';
8 |
9 | /**
10 | * Wrappers around StorageAccountWrapper that guarantees certain properties are not undefined/null
11 | */
12 | export class StorageAccountWrapper {
13 | constructor(private readonly _account: StorageAccount) { }
14 | readonly id: string = nonNullProp(this._account, 'id');
15 | readonly name: string = nonNullProp(this._account, 'name');
16 | readonly type: string = nonNullProp(this._account, 'type');
17 | readonly primaryEndpoints: Endpoints = nonNullProp(this._account, 'primaryEndpoints');
18 | readonly location: string = nonNullProp(this._account, 'location');
19 | }
20 |
21 | /**
22 | * Wrappers around StorageAccountKeyWrapper that guarantees certain properties are not undefined/null
23 | */
24 | export class StorageAccountKeyWrapper {
25 | constructor(private readonly _key: StorageAccountKey) { }
26 | readonly value = nonNullProp(this._key, 'value');
27 | readonly keyName = nonNullProp(this._key, 'keyName');
28 | }
29 |
--------------------------------------------------------------------------------
/.azure-pipelines/main.yml:
--------------------------------------------------------------------------------
1 | variables:
2 | WEB_BUILDS_STG_ACCT: vscodedotdev
3 | WEB_BUILDS_CONTAINER: web-builds
4 | ${{ if eq(variables['Build.Reason'], 'Schedule') }}:
5 | ENABLE_LONG_RUNNING_TESTS: true
6 | ENABLE_COMPLIANCE: true
7 |
8 | jobs:
9 | - job: Windows
10 | pool:
11 | vmImage: windows-latest
12 | steps:
13 | - template: common/build.yml
14 | - template: common/package.yml
15 | - template: common/lint.yml
16 | - template: compliance/compliance.yml # Only works on Windows
17 | - template: common/test.yml
18 |
19 | - job: Linux
20 | pool:
21 | vmImage: ubuntu-latest
22 | steps:
23 | - template: common/build.yml
24 | - template: common/package.yml
25 | - template: common/sbom.yml # only generate on linux
26 | - template: common/lint.yml
27 | - template: common/test.yml
28 | variables:
29 | Codeql.Enabled: $[in(variables['Build.Reason'], 'Schedule')] # Enable CodeQL only on scheduled builds because it is slow
30 |
31 | - job: macOS
32 | pool:
33 | vmImage: macOS-latest
34 | steps:
35 | - template: common/build.yml
36 | - template: common/package.yml
37 | - template: common/lint.yml
38 | - template: common/test.yml
39 |
40 | trigger:
41 | branches:
42 | include:
43 | - '*'
44 |
45 | pr:
46 | branches:
47 | include:
48 | - '*'
49 |
50 | schedules:
51 | - cron: "30 8 * * *"
52 | displayName: Nightly at 1:30 PT
53 | always: true # Run even when there are no code changes
54 | branches:
55 | include:
56 | - main
57 |
--------------------------------------------------------------------------------
/src/utils/askOpenInStorageExplorer.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext, UserCancelledError } from "@microsoft/vscode-azext-utils";
7 | import { platform } from "os";
8 | import { window } from "vscode";
9 | import { ResourceType } from "../storageExplorerLauncher/ResourceType";
10 | import { storageExplorerLauncher } from "../storageExplorerLauncher/storageExplorerLauncher";
11 | import { localize } from "./localize";
12 |
13 | export function askOpenInStorageExplorer(context: IActionContext, errorMessage: string, resourceId: string, subscriptionId: string, resourceType: ResourceType, resourceName: string): void {
14 | const message: string = localize("openInSE", "Open resource in Storage Explorer");
15 | const items: string[] = platform() === 'linux' ? [] : [message];
16 | void window.showErrorMessage(errorMessage, ...items).then(async result => {
17 | if (result === message) {
18 | context.telemetry.properties.openInStorageExplorer = 'true';
19 | await storageExplorerLauncher.openResource(resourceId, subscriptionId, resourceType, resourceName);
20 | }
21 | });
22 |
23 | // Either way, throw canceled error
24 | throw new UserCancelledError(message);
25 | }
26 |
--------------------------------------------------------------------------------
/src/commands/queue/queueActionHandlers.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext, registerCommandWithTreeNodeUnwrapping } from '@microsoft/vscode-azext-utils';
7 | import { storageExplorerLauncher } from '../../storageExplorerLauncher/storageExplorerLauncher';
8 | import { QueueTreeItem } from '../../tree/queue/QueueTreeItem';
9 | import { deleteNode } from '../commonTreeCommands';
10 |
11 | export function registerQueueActionHandlers(): void {
12 | registerCommandWithTreeNodeUnwrapping("azureStorage.openQueue", openQueueInStorageExplorer);
13 | registerCommandWithTreeNodeUnwrapping("azureStorage.deleteQueue", deleteQueue);
14 | }
15 |
16 | async function openQueueInStorageExplorer(_context: IActionContext, treeItem: QueueTreeItem): Promise {
17 | const accountId = treeItem.root.storageAccountId;
18 | const resourceType = "Azure.Queue";
19 | const resourceName = treeItem.queue.name;
20 |
21 | await storageExplorerLauncher.openResource(accountId, treeItem.subscription.subscriptionId, resourceType, resourceName);
22 | }
23 |
24 | export async function deleteQueue(context: IActionContext, treeItem?: QueueTreeItem): Promise {
25 | await deleteNode(context, QueueTreeItem.contextValue, treeItem);
26 | }
27 |
--------------------------------------------------------------------------------
/src/commands/table/tableActionHandlers.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext, registerCommandWithTreeNodeUnwrapping } from '@microsoft/vscode-azext-utils';
7 | import { storageExplorerLauncher } from '../../storageExplorerLauncher/storageExplorerLauncher';
8 | import { TableTreeItem } from '../../tree/table/TableTreeItem';
9 | import { deleteNode } from '../commonTreeCommands';
10 |
11 | export function registerTableActionHandlers(): void {
12 | registerCommandWithTreeNodeUnwrapping("azureStorage.openTable", openTableInStorageExplorer);
13 | registerCommandWithTreeNodeUnwrapping("azureStorage.deleteTable", deleteTable);
14 | }
15 |
16 | async function openTableInStorageExplorer(_context: IActionContext, treeItem: TableTreeItem): Promise {
17 | const accountId = treeItem.root.storageAccountId;
18 | const resourceType = "Azure.Table";
19 | const resourceName = treeItem.tableName;
20 |
21 | await storageExplorerLauncher.openResource(accountId, treeItem.subscription.subscriptionId, resourceType, resourceName);
22 | }
23 |
24 | export async function deleteTable(context: IActionContext, treeItem?: TableTreeItem): Promise {
25 | await deleteNode(context, TableTreeItem.contextValue, treeItem);
26 | }
27 |
--------------------------------------------------------------------------------
/src/utils/treeUtils.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzExtTreeItem, NoResourceFoundError } from "@microsoft/vscode-azext-utils";
7 |
8 | export namespace treeUtils {
9 | export function findNearestParent(node: AzExtTreeItem, parentContextValues: string | RegExp | (string | RegExp)[]): T {
10 | parentContextValues = Array.isArray(parentContextValues) ? parentContextValues : [parentContextValues];
11 | if (!parentContextValues.length) throw new NoResourceFoundError();
12 |
13 | let currentNode: AzExtTreeItem = node;
14 | let foundParent: boolean = false;
15 | while (currentNode.parent) {
16 | for (const contextValue of parentContextValues) {
17 | const parentRegex: RegExp = contextValue instanceof RegExp ? contextValue : new RegExp(contextValue);
18 | if (parentRegex.test(currentNode.contextValue)) {
19 | foundParent = true;
20 | break;
21 | }
22 | }
23 | if (foundParent) break;
24 | currentNode = currentNode.parent;
25 | }
26 | if (!foundParent) throw new NoResourceFoundError();
27 | return currentNode as T;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/AttachedAccountRoot.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { ISubscriptionContext } from "@microsoft/vscode-azext-utils";
7 | import { localize } from "./utils/localize";
8 |
9 | export class AttachedAccountRoot implements ISubscriptionContext {
10 | private _error: Error = new Error(localize('cannotRetrieveAzureSubscriptionInfoForAttachedAccount', 'Cannot retrieve Azure subscription information for an attached account.'));
11 |
12 | public get credentials(): never {
13 | throw this._error;
14 | }
15 |
16 | public get createCredentialsForScopes(): never {
17 | throw this._error;
18 | }
19 |
20 | public get subscriptionDisplayName(): never {
21 | throw this._error;
22 | }
23 |
24 | public get subscriptionId(): never {
25 | throw this._error;
26 | }
27 |
28 | public get subscriptionPath(): never {
29 | throw this._error;
30 | }
31 |
32 | public get tenantId(): never {
33 | throw this._error;
34 | }
35 |
36 | public get userId(): never {
37 | throw this._error;
38 | }
39 |
40 | public get environment(): never {
41 | throw this._error;
42 | }
43 |
44 | public get isCustomCloud(): never {
45 | throw this._error;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/StorageWorkspaceProvider.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzExtParentTreeItem, AzExtTreeItem, callWithTelemetryAndErrorHandling, IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { WorkspaceResourceProvider } from "@microsoft/vscode-azext-utils/hostapi";
8 | import { Disposable } from "vscode";
9 | import { ext } from "./extensionVariables";
10 | import { AttachedStorageAccountsTreeItem } from "./tree/AttachedStorageAccountsTreeItem";
11 |
12 |
13 | export class StorageWorkspaceProvider implements WorkspaceResourceProvider {
14 |
15 | public disposables: Disposable[] = [];
16 |
17 | constructor(parent: AzExtParentTreeItem) {
18 | ext.attachedStorageAccountsTreeItem = new AttachedStorageAccountsTreeItem(parent);
19 | }
20 |
21 | public async provideResources(): Promise {
22 |
23 | return await callWithTelemetryAndErrorHandling('StorageWorkspaceProvider.provideResources', async (_context: IActionContext) => {
24 | return [ext.attachedStorageAccountsTreeItem];
25 | });
26 | }
27 | private _projectDisposables: Disposable[] = [];
28 |
29 | public dispose(): void {
30 | Disposable.from(...this._projectDisposables).dispose();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/.azure-pipelines/1esmain.yml:
--------------------------------------------------------------------------------
1 | # Trigger the build whenever `main` or `rel/*` is updated
2 | trigger:
3 | - main
4 | - rel/*
5 |
6 | # Disable PR trigger
7 | pr: none
8 |
9 | # Scheduled nightly build of `main`
10 | schedules:
11 | - cron: "0 0 * * *"
12 | displayName: Nightly scheduled build
13 | always: false # Don't rebuild if there haven't been changes
14 | branches:
15 | include:
16 | - main
17 |
18 | # `resources` specifies the location of templates to pick up, use it to get AzExt templates
19 | resources:
20 | repositories:
21 | - repository: azExtTemplates
22 | type: github
23 | name: microsoft/vscode-azuretools
24 | ref: main
25 | endpoint: GitHub-AzureTools # The service connection to use when accessing this repository
26 |
27 | parameters:
28 | - name: enableLongRunningTests
29 | displayName: Enable Long Running Tests
30 | type: boolean
31 | default: true
32 |
33 | variables:
34 | # Required by MicroBuild template
35 | - name: TeamName
36 | value: "Azure Tools for VS Code"
37 |
38 | # Use those templates
39 | extends:
40 | template: azure-pipelines/1esmain.yml@azExtTemplates
41 | parameters:
42 | useAzureFederatedCredentials: ${{ parameters.enableLongRunningTests }}
43 | additionalSetupSteps:
44 | - script: npm install --force @azure-tools/azcopy-darwin @azure-tools/azcopy-linux @azure-tools/azcopy-win32 @azure-tools/azcopy-win64
45 | displayName: Install AzCopy
46 | name: Install_azcopy
47 | - script: npm run setazcopypermissions
48 | displayName: Set AzCopy Permissions
49 | name: Set_Azcopy_Permissions
50 |
--------------------------------------------------------------------------------
/src/commands/transfers/azCopy/credentialStore.ts:
--------------------------------------------------------------------------------
1 | /*!---------------------------------------------------------
2 | * Copyright (C) Microsoft Corporation. All rights reserved.
3 | *----------------------------------------------------------*/
4 |
5 | import { ICredentialStore } from "@azure-tools/azcopy-node";
6 | import { ext } from "../../../extensionVariables";
7 |
8 | export class CredentialStore implements ICredentialStore {
9 | private credentialMap: Map = new Map();
10 | public async setEntry(service: string, account: string, value: string): Promise {
11 | try {
12 | this.credentialMap.set(`${service}-${account}`, value);
13 | } catch (error) {
14 | ext.outputChannel.appendLog(`Unable to set password. service:${service} account:${account}`, error);
15 | }
16 | }
17 |
18 | public async getEntry(service: string, account: string): Promise {
19 | try {
20 | return this.credentialMap.get(`${service}-${account}`) ?? null;
21 | } catch (error) {
22 | ext.outputChannel.appendLog(`Unable to get password. service:${service} account:${account}`, error);
23 | return null;
24 | }
25 | }
26 |
27 | public async deleteEntry(service: string, account: string): Promise {
28 | try {
29 | return this.credentialMap.delete(`${service}-${account}`);
30 | } catch (error) {
31 | ext.outputChannel.appendLog(`Unable to delete password. service:${service} account:${account}`, error);
32 | return false;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/utils/azuriteUtils.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { extensions } from "vscode";
8 | import { azuriteExtensionId } from "../constants";
9 | import { cpUtils } from "./cpUtils";
10 | import { localize } from "./localize";
11 |
12 | export async function isAzuriteInstalled(): Promise {
13 | return isAzuriteExtensionInstalled() || await isAzuriteCliInstalled();
14 | }
15 |
16 | export function isAzuriteExtensionInstalled(): boolean {
17 | return !!extensions.getExtension(azuriteExtensionId);
18 | }
19 |
20 | export async function isAzuriteCliInstalled(): Promise {
21 | try {
22 | await cpUtils.executeCommand(undefined, undefined, 'azurite -v');
23 | return true;
24 | } catch {
25 | return false;
26 | }
27 | }
28 |
29 | export function warnAzuriteNotInstalled(context: IActionContext): void {
30 | void context.ui.showWarningMessage(localize('mustInstallAzurite', 'You must install the [Azurite extension](command:azureStorage.showAzuriteExtension) to perform this operation.'));
31 | context.telemetry.properties.cancelStep = 'installAzuriteExtension';
32 | context.errorHandling.suppressDisplay = true;
33 | throw new Error(`"${azuriteExtensionId}" extension is not installed.`);
34 | }
35 |
--------------------------------------------------------------------------------
/src/tree/createWizard/StaticWebsiteIndexDocumentStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils";
7 | import { localize } from "../../utils/localize";
8 | import { DocumentType, validateDocumentPath } from "../../utils/validateNames";
9 | import { IStaticWebsiteConfigWizardContext } from "./IStaticWebsiteConfigWizardContext";
10 |
11 | export class StaticWebsiteIndexDocumentStep extends AzureWizardPromptStep {
12 | static readonly defaultIndexDocument: string = 'index.html';
13 | private oldIndexDocument: string | undefined;
14 |
15 | public constructor(oldIndexDocument?: string) {
16 | super();
17 | this.oldIndexDocument = oldIndexDocument;
18 | }
19 |
20 | public async prompt(context: IStaticWebsiteConfigWizardContext): Promise {
21 | context.indexDocument = await context.ui.showInputBox({
22 | prompt: localize('enterTheIndexDocumentName', 'Enter the index document name'),
23 | value: this.oldIndexDocument || StaticWebsiteIndexDocumentStep.defaultIndexDocument,
24 | validateInput: (value) => { return validateDocumentPath(value, DocumentType.index); }
25 | });
26 | }
27 |
28 | public shouldPrompt(): boolean {
29 | return true;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.azure-pipelines/compliance/compliance.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | - task: securedevelopmentteam.vss-secure-development-tools.build-task-policheck.PoliCheck@1
3 | displayName: 'Run PoliCheck'
4 | inputs:
5 | targetType: F # search files and folders
6 | optionsUEPATH: '$(Build.SourcesDirectory)/.azure-pipelines/compliance/PoliCheckExclusions.xml'
7 | continueOnError: true
8 | condition: eq(variables['ENABLE_COMPLIANCE'], 'true')
9 |
10 | - task: securedevelopmentteam.vss-secure-development-tools.build-task-credscan.CredScan@2
11 | displayName: 'Run CredScan'
12 | inputs:
13 | toolMajorVersion: V2
14 | suppressionsFile: '$(Build.SourcesDirectory)/.azure-pipelines/compliance/CredScanSuppressions.json'
15 | debugMode: true # Needed to suppress folders
16 | folderSuppression: true
17 | continueOnError: true
18 | condition: eq(variables['ENABLE_COMPLIANCE'], 'true')
19 |
20 | - task: securedevelopmentteam.vss-secure-development-tools.build-task-antimalware.AntiMalware@3
21 | displayName: 'AntiMalware Scanner'
22 | inputs:
23 | FileDirPath: '$(Build.SourcesDirectory)'
24 | EnableServices: true
25 | continueOnError: true
26 | condition: eq(variables['ENABLE_COMPLIANCE'], 'true')
27 |
28 | - task: securedevelopmentteam.vss-secure-development-tools.build-task-publishsecurityanalysislogs.PublishSecurityAnalysisLogs@2
29 | displayName: 'Publish Security Analysis Logs'
30 | condition: eq(variables['ENABLE_COMPLIANCE'], 'true')
31 |
32 | - task: securedevelopmentteam.vss-secure-development-tools.build-task-postanalysis.PostAnalysis@1
33 | displayName: 'Post Analysis'
34 | inputs:
35 | AllTools: true
36 | condition: eq(variables['ENABLE_COMPLIANCE'], 'true')
37 |
--------------------------------------------------------------------------------
/src/utils/azureClients.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { type ResourceGraphClient } from '@azure/arm-resourcegraph';
7 | import { StorageManagementClient } from '@azure/arm-storage';
8 | import { AzExtClientContext, createAzureClient, createAzureSubscriptionClient, parseClientContext } from '@microsoft/vscode-azext-azureutils';
9 |
10 | // Lazy-load @azure packages to improve startup performance.
11 | // NOTE: The client is the only import that matters, the rest of the types disappear when compiled to JavaScript
12 |
13 | export async function createStorageClient(context: AzExtClientContext): Promise {
14 | if (parseClientContext(context).isCustomCloud) {
15 | // set API version for Azure Stack
16 | process.env.AZCOPY_DEFAULT_SERVICE_API_VERSION = "2019-02-02";
17 | return createAzureClient(context, (await import('@azure/arm-storage-profile-2020-09-01-hybrid')).StorageManagementClient);
18 | } else {
19 | return createAzureClient(context, (await import('@azure/arm-storage')).StorageManagementClient);
20 | }
21 | }
22 |
23 | export async function createResourceGraphClient(context: AzExtClientContext): Promise {
24 | return createAzureSubscriptionClient(context, (await import('@azure/arm-resourcegraph')).ResourceGraphClient);
25 | }
26 |
--------------------------------------------------------------------------------
/src/commands/openInFileExplorer/OpenBehaviorStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardPromptStep, IAzureQuickPickItem } from '@microsoft/vscode-azext-utils';
7 | import { localize } from '../../utils/localize';
8 | import { IOpenInFileExplorerWizardContext, OpenBehavior } from './IOpenInFileExplorerWizardContext';
9 |
10 | export class OpenBehaviorStep extends AzureWizardPromptStep {
11 | public async prompt(context: IOpenInFileExplorerWizardContext): Promise {
12 | const picks: IAzureQuickPickItem[] = [
13 | { label: localize('OpenInCurrentWindow', 'Open in current window'), data: 'OpenInCurrentWindow' },
14 | { label: localize('OpenInNewWindow', 'Open in new window'), data: 'OpenInNewWindow' },
15 | { label: localize('AddToWorkspace', 'Add to workspace'), data: 'AddToWorkspace' }
16 | ];
17 |
18 | const placeHolder: string = localize('selectOpenBehavior', 'Select how you would like to open this resource');
19 | context.openBehavior = (await context.ui.showQuickPick(picks, { placeHolder })).data;
20 | }
21 |
22 | public shouldPrompt(context: IOpenInFileExplorerWizardContext): boolean {
23 | return !context.openBehavior && context.openBehavior !== 'AlreadyOpen' && context.openBehavior !== 'DontOpen';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/tree/IStorageRoot.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import type { TableServiceClient } from "@azure/data-tables";
7 | import type { AccountSASSignatureValues as AccountSASSignatureValuesBlob, BlobServiceClient } from "@azure/storage-blob";
8 | import type { AccountSASSignatureValues as AccountSASSignatureValuesFileShare, ShareServiceClient } from "@azure/storage-file-share";
9 | import type { QueueServiceClient } from "@azure/storage-queue";
10 |
11 | import { type Endpoints, type StorageManagementClient } from "@azure/arm-storage";
12 | import { type IActionContext } from "@microsoft/vscode-azext-utils";
13 |
14 | export interface IStorageRoot {
15 | storageAccountName: string;
16 | storageAccountId: string;
17 | tenantId: string;
18 | isEmulated: boolean;
19 | allowSharedKeyAccess: boolean;
20 | primaryEndpoints?: Endpoints;
21 | getAccessToken(): Promise;
22 | generateSasToken(accountSASSignatureValues: AccountSASSignatureValuesBlob | AccountSASSignatureValuesFileShare): string;
23 | getStorageManagementClient(context: IActionContext): Promise;
24 | createBlobServiceClient(): Promise;
25 | createShareServiceClient(): Promise;
26 | createQueueServiceClient(): Promise;
27 | createTableServiceClient(): Promise;
28 | }
29 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Node PR Lint, Build and Test
2 |
3 | on:
4 | # Trigger when manually run
5 | workflow_dispatch:
6 |
7 | # Trigger on pushes to `main` or `rel/*`
8 | push:
9 | branches:
10 | - main
11 | - rel/*
12 |
13 | # Trigger on pull requests to `main` or `rel/*`
14 | pull_request:
15 | branches:
16 | - main
17 | - rel/*
18 |
19 | # not using shared template (https://github.com/microsoft/vscode-azuretools/tree/main/.github/workflows) so that we can install azcopy
20 | jobs:
21 | Build:
22 | runs-on: ubuntu-latest
23 |
24 | defaults:
25 | run:
26 | working-directory: "."
27 |
28 | steps:
29 | # Setup
30 | - uses: actions/checkout@v3
31 | - name: Using Node.js
32 | uses: actions/setup-node@v3
33 | with:
34 | node-version-file: .nvmrc
35 | - run: npm ci --no-optional
36 |
37 | # Install AzCopy
38 | - run: npm install --force @azure-tools/azcopy-darwin @azure-tools/azcopy-linux @azure-tools/azcopy-win32 @azure-tools/azcopy-win64
39 | - run: npm run setazcopypermissions
40 |
41 | # Set AzCopy permissions
42 | # comment out in main because I think it is effecting other branches
43 | # - run: npm run postinstall
44 |
45 | # Lint
46 | - run: npm run lint
47 |
48 | # Build
49 | - run: npm run build
50 |
51 | # Package
52 | - run: npm run package
53 | - name: Upload Artifacts
54 | uses: actions/upload-artifact@v4
55 | with:
56 | name: Artifacts
57 | path: |
58 | **/*.vsix
59 | **/*.tgz
60 | !**/node_modules
61 |
62 | # Test
63 | - run: xvfb-run -a npm test
64 |
--------------------------------------------------------------------------------
/src/tree/createWizard/StaticWebsiteErrorDocument404Step.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils";
7 | import { localize } from "../../utils/localize";
8 | import { DocumentType, validateDocumentPath } from "../../utils/validateNames";
9 | import { IStaticWebsiteConfigWizardContext } from "./IStaticWebsiteConfigWizardContext";
10 |
11 | export class StaticWebsiteErrorDocument404Step extends AzureWizardPromptStep {
12 | static readonly defaultErrorDocument404Path: string = 'index.html';
13 | private oldErrorDocument404Path: string | undefined;
14 |
15 | public constructor(oldErrorDocument404Path?: string) {
16 | super();
17 | this.oldErrorDocument404Path = oldErrorDocument404Path;
18 | }
19 |
20 | public async prompt(context: IStaticWebsiteConfigWizardContext): Promise {
21 | context.errorDocument404Path = await context.ui.showInputBox({
22 | prompt: localize('enterThe404ErrorDocumentPath', 'Enter the 404 error document path'),
23 | value: this.oldErrorDocument404Path || StaticWebsiteErrorDocument404Step.defaultErrorDocument404Path,
24 | validateInput: (value) => { return validateDocumentPath(value, DocumentType.error); }
25 | });
26 | }
27 |
28 | public shouldPrompt(): boolean {
29 | return true;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/extensionVariables.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IAzExtOutputChannel } from "@microsoft/vscode-azext-utils";
7 | import { AzureHostExtensionApi } from "@microsoft/vscode-azext-utils/hostapi";
8 | import { ExtensionContext, UIKind, Uri, env } from "vscode";
9 | import { AzureStorageFS } from "./AzureStorageFS";
10 | import { AttachedStorageAccountsTreeItem } from "./tree/AttachedStorageAccountsTreeItem";
11 |
12 | /**
13 | * Namespace for common variables used throughout the extension. They must be initialized in the activate() method of extension.ts
14 | */
15 | export namespace ext {
16 | export let context: ExtensionContext;
17 | export let outputChannel: IAzExtOutputChannel;
18 | export let ignoreBundle: boolean | undefined;
19 |
20 | export let attachedStorageAccountsTreeItem: AttachedStorageAccountsTreeItem;
21 | export let azureStorageFS: AzureStorageFS;
22 | export let azureStorageWorkspaceFS: AzureStorageFS;
23 | export const azCopyExePath: string = 'azcopy';
24 | export const prefix: string = 'azureStorage';
25 |
26 | export let rgApi: AzureHostExtensionApi;
27 | export let lastUriUpload: Uri | undefined;
28 |
29 | // When debugging thru VS Code as a web environment, the UIKind is Desktop. However, if you sideload it into the browser, you must
30 | // change this to UIKind.Web and then webpack it again
31 | export const isWeb: boolean = env.uiKind === UIKind.Web;
32 | }
33 |
--------------------------------------------------------------------------------
/src/tree/fileShare/createFileShare/StorageQuotaPromptStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils";
7 | import { localize } from "../../../utils/localize";
8 | import { IFileShareWizardContext } from "./IFileShareWizardContext";
9 |
10 | const minQuotaGB = 1;
11 | const maxQuotaGB = 5120;
12 |
13 | export class StorageQuotaPromptStep extends AzureWizardPromptStep {
14 | public async prompt(context: IFileShareWizardContext): Promise {
15 | context.quota = Number(await context.ui.showInputBox({
16 | prompt: localize('quotaPrompt', 'Specify quota (in GB, between {0} and {1}), to limit total storage size', minQuotaGB, maxQuotaGB),
17 | value: maxQuotaGB.toString(),
18 | validateInput: this.validateQuota
19 | }));
20 | }
21 |
22 | public shouldPrompt(context: IFileShareWizardContext): boolean {
23 | return !context.quota;
24 | }
25 |
26 | private validateQuota(input: string): string | undefined {
27 | const value = Number(input);
28 | if (isNaN(value)) {
29 | return localize('quotaNum', 'Value must be a number');
30 | } else if (value < minQuotaGB || value > maxQuotaGB) {
31 | return localize('quotaBetween', 'Value must be between {0} and {1}', minQuotaGB, maxQuotaGB);
32 | }
33 |
34 | return undefined;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test/runTest.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See LICENSE.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath, runTests } from '@vscode/test-electron';
7 | import * as cp from 'child_process';
8 | import * as path from 'path';
9 |
10 | async function main(): Promise {
11 | try {
12 | const vscodeExecutablePath = await downloadAndUnzipVSCode('stable');
13 | const [cli, ...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);
14 |
15 | cp.spawnSync(
16 | cli,
17 | [
18 | ...args,
19 | '--install-extension', 'ms-azuretools.vscode-azureresourcegroups',
20 | ],
21 | {
22 | encoding: 'utf-8',
23 | stdio: 'inherit'
24 | });
25 |
26 | const repoRoot: string = path.resolve(__dirname, '..', '..');
27 | await runTests({
28 | vscodeExecutablePath,
29 | extensionDevelopmentPath: repoRoot,
30 | launchArgs: [
31 | path.resolve(repoRoot, 'test', 'test.code-workspace'),
32 | '--disable-workspace-trust'
33 | ],
34 | extensionTestsPath: path.resolve(repoRoot, 'dist', 'test', 'index'),
35 | extensionTestsEnv: {
36 | DEBUGTELEMETRY: 'v',
37 | MOCHA_timeout: '20000'
38 | }
39 | });
40 | } catch (err) {
41 | console.error('Failed to run tests');
42 | process.exit(1);
43 | }
44 | }
45 |
46 | void main();
47 |
--------------------------------------------------------------------------------
/src/utils/activityUtils.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { ActivityChildItemBase, ActivityChildType, ExecuteActivityContext } from "@microsoft/vscode-azext-utils";
7 | import { ext } from "../extensionVariables";
8 | import { getWorkspaceSetting } from "./settingsUtils";
9 |
10 | export async function createActivityContext(options?: { withChildren?: boolean }): Promise {
11 | return {
12 | registerActivity: async (activity) => ext.rgApi.registerActivity(activity),
13 | suppressNotification: await getWorkspaceSetting('suppressActivityNotifications', undefined, 'azureResourceGroups'),
14 | activityChildren: options?.withChildren ? [] : undefined,
15 | };
16 | }
17 |
18 | /**
19 | * Adds a new activity child after the last info child in the `activityChildren` array.
20 | * If no info child already exists, the new child is prepended to the front of the array.
21 | * (This utility function is useful for keeping the info children grouped at the front of the list)
22 | */
23 | export type ActivityInfoChild = ActivityChildItemBase & { activityType: ActivityChildType.Info };
24 | export function prependOrInsertAfterLastInfoChild(context: Partial, infoChild: ActivityInfoChild): void {
25 | if (!context.activityChildren) {
26 | return;
27 | }
28 |
29 | const idx: number = context.activityChildren
30 | .map(child => child.activityType)
31 | .lastIndexOf(ActivityChildType.Info);
32 |
33 | if (idx === -1) {
34 | context.activityChildren.unshift(infoChild);
35 | } else {
36 | context.activityChildren.splice(idx + 1, 0, infoChild);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/commands/deleteBlob/DeleteBlobStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import type { BlobClient } from "@azure/storage-blob";
7 |
8 | import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils";
9 | import { Progress, window } from "vscode";
10 | import { ext } from "../../extensionVariables";
11 | import { createBlobClient } from "../../utils/blobUtils";
12 | import { localize } from "../../utils/localize";
13 | import { IDeleteBlobWizardContext } from "./IDeleteBlobWizardContext";
14 |
15 | export class DeleteBlobStep extends AzureWizardExecuteStep {
16 | public priority: number = 100;
17 |
18 | public async execute(wizardContext: IDeleteBlobWizardContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined; }>): Promise {
19 | const blobName = nonNullProp(wizardContext, 'blobName');
20 | const blob = nonNullProp(wizardContext, 'blob');
21 | const deletingBlob: string = localize('deletingBlob', 'Deleting blob "{0}"...', blobName);
22 | ext.outputChannel.appendLog(deletingBlob);
23 | progress.report({ message: deletingBlob });
24 | const blobClient: BlobClient = await createBlobClient(blob.root, blob.container.name, blob.blobPath);
25 | await blobClient.delete();
26 | const deleteSuccessful: string = localize('successfullyDeletedBlob', 'Successfully deleted blob "{0}".', blobName);
27 | ext.outputChannel.appendLog(deleteSuccessful);
28 | if (!wizardContext.suppressNotification) {
29 | void window.showInformationMessage(deleteSuccessful);
30 | }
31 | }
32 |
33 | public shouldExecute(): boolean {
34 | return true;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { Progress } from 'vscode';
7 | import { ext } from './extensionVariables';
8 |
9 | export const staticWebsiteContainerName = '$web';
10 |
11 | export const maxPageSize = 50;
12 |
13 | export enum configurationSettingsKeys {
14 | deployPath = 'deployPath',
15 | preDeployTask = 'preDeployTask',
16 | deleteBeforeDeploy = 'deleteBeforeDeploy'
17 | }
18 |
19 | export const extensionPrefix: string = 'azureStorage';
20 |
21 | export const azuriteExtensionId: string = 'Azurite.azurite';
22 | export const emulatorTimeoutMS: number = 3 * 1000;
23 | export const emulatorAccountName: string = 'devstoreaccount1';
24 | export const emulatorConnectionString: string = 'UseDevelopmentStorage=true;';
25 | export const emulatorKey: string = 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==';
26 | export const azuriteKey: string = 'azurite'
27 | export const defaultEmulatorHost: string = '127.0.0.1';
28 |
29 | export const maxRemoteFileEditSizeMB: number = 50;
30 | export const maxRemoteFileEditSizeBytes: number = maxRemoteFileEditSizeMB * 1024 * 1024;
31 |
32 | export const storageExplorerDownloadUrl: string = 'https://go.microsoft.com/fwlink/?LinkId=723579';
33 |
34 | export function getResourcesPath(): string {
35 | return ext.context.asAbsolutePath('resources');
36 | }
37 |
38 | export type NotificationProgress = Progress<{
39 | message?: string | undefined;
40 | increment?: number | undefined;
41 | }>;
42 |
43 | export const storageProvider: string = 'Microsoft.Storage';
44 |
45 | export const storageFilter = {
46 | type: 'Microsoft.Storage/storageAccounts',
47 | }
48 |
49 | export const threeDaysInMS: number = 1000 * 60 * 60 * 24 * 3;
50 |
--------------------------------------------------------------------------------
/src/commands/startEmulator.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IActionContext } from '@microsoft/vscode-azext-utils';
7 | import * as vscode from 'vscode';
8 | import { emulatorTimeoutMS } from '../constants';
9 | import { ext } from "../extensionVariables";
10 | import { isAzuriteCliInstalled, isAzuriteExtensionInstalled, warnAzuriteNotInstalled } from '../utils/azuriteUtils';
11 | import { cpUtils } from '../utils/cpUtils';
12 |
13 | export async function startEmulator(context: IActionContext, emulatorType: EmulatorType): Promise {
14 | if (isAzuriteExtensionInstalled()) {
15 | // Use the Azurite extension
16 | await vscode.commands.executeCommand(`azurite.start_${emulatorType}`);
17 | await ext.rgApi.workspaceResourceTree.refresh(context, ext.attachedStorageAccountsTreeItem);
18 | } else if (await isAzuriteCliInstalled()) {
19 | // Use the Azurite CLI
20 |
21 | // This task will remain active as long as the user keeps the emulator running. Only show an error if it happens in the first three seconds
22 | const emulatorTask: Promise = cpUtils.executeCommand(ext.outputChannel, undefined, `azurite-${emulatorType}`);
23 | ext.outputChannel.show();
24 | await new Promise((resolve: (value: unknown) => void, reject: (error: unknown) => void) => {
25 | emulatorTask.catch(reject);
26 | setTimeout(resolve, emulatorTimeoutMS);
27 | });
28 |
29 | await ext.rgApi.workspaceResourceTree.refresh(context, ext.attachedStorageAccountsTreeItem);
30 | } else {
31 | warnAzuriteNotInstalled(context);
32 | }
33 | }
34 |
35 | export enum EmulatorType {
36 | blob = 'blob',
37 | queue = 'queue',
38 | table = 'table'
39 | }
40 |
--------------------------------------------------------------------------------
/src/tree/createWizard/StorageAccountTreeItemCreateStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IStorageAccountWizardContext } from "@microsoft/vscode-azext-azureutils";
7 | import { AzureWizardExecuteStep, ExecuteActivityContext, ISubscriptionContext } from "@microsoft/vscode-azext-utils";
8 | import { AppResource } from "@microsoft/vscode-azext-utils/hostapi";
9 | import { nonNullProp } from '../../utils/nonNull';
10 | import { StorageAccountTreeItem } from "../StorageAccountTreeItem";
11 |
12 | export interface IStorageAccountTreeItemCreateContext extends IStorageAccountWizardContext, ExecuteActivityContext {
13 | accountTreeItem: StorageAccountTreeItem;
14 | }
15 |
16 | export class StorageAccountTreeItemCreateStep extends AzureWizardExecuteStep {
17 | public priority: number = 170;
18 | public subscription: ISubscriptionContext;
19 |
20 | public constructor(subscription: ISubscriptionContext) {
21 | super();
22 | this.subscription = subscription;
23 | }
24 |
25 | public async execute(wizardContext: IStorageAccountTreeItemCreateContext): Promise {
26 | wizardContext.accountTreeItem = new StorageAccountTreeItem(this.subscription, nonNullProp(wizardContext, 'storageAccount'));
27 | await wizardContext.accountTreeItem.initStorageAccount(wizardContext);
28 |
29 | const appResource: AppResource = {
30 | id: wizardContext.accountTreeItem.storageAccount.id,
31 | name: wizardContext.accountTreeItem.storageAccount.name,
32 | type: wizardContext.accountTreeItem.storageAccount.type,
33 | };
34 | wizardContext.activityResult = appResource;
35 | }
36 |
37 | public shouldExecute(): boolean {
38 | return true;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/commands/uploadFolder/GetFolderDestinationDirectoryStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardPromptStep } from '@microsoft/vscode-azext-utils';
7 | import * as vscode from "vscode";
8 | import { storageFilter } from '../../constants';
9 | import { ext } from '../../extensionVariables';
10 | import { BlobContainerTreeItem } from '../../tree/blob/BlobContainerTreeItem';
11 | import { FileShareTreeItem } from '../../tree/fileShare/FileShareTreeItem';
12 | import { getDestinationDirectory, upload } from '../../utils/uploadUtils';
13 | import { IUploadFolderWizardContext } from './IUploadFolderWizardContext';
14 |
15 | export class GetFolderDestinationDirectoryStep extends AzureWizardPromptStep {
16 | public async prompt(context: IUploadFolderWizardContext): Promise {
17 | if (context.uri === undefined) {
18 | context.uri = (await context.ui.showOpenDialog({
19 | canSelectFiles: false,
20 | canSelectFolders: true,
21 | canSelectMany: false,
22 | defaultUri: vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0 ? vscode.workspace.workspaceFolders[0].uri : undefined,
23 | openLabel: upload
24 | }))[0];
25 | }
26 | context.treeItem = context.treeItem || await ext.rgApi.pickAppResource(context, {
27 | filter: storageFilter,
28 | expectedChildContextValue: [BlobContainerTreeItem.contextValue, FileShareTreeItem.contextValue]
29 | });
30 | context.destinationDirectory = await getDestinationDirectory(context, context.destinationDirectory);
31 | }
32 |
33 | public shouldPrompt(): boolean {
34 | return true;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/commands/uploadFiles/GetFileDestinationDirectoryStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardPromptStep } from '@microsoft/vscode-azext-utils';
7 | import { storageFilter } from '../../constants';
8 | import { ext } from '../../extensionVariables';
9 | import { BlobContainerTreeItem } from '../../tree/blob/BlobContainerTreeItem';
10 | import { FileShareTreeItem } from '../../tree/fileShare/FileShareTreeItem';
11 | import { getDestinationDirectory, upload } from '../../utils/uploadUtils';
12 | import { IUploadFilesWizardContext } from './IUploadFilesWizardContext';
13 |
14 | export class GetFileDestinationDirectoryStep extends AzureWizardPromptStep {
15 | public async prompt(context: IUploadFilesWizardContext): Promise {
16 | context.destinationDirectory = await getDestinationDirectory(context, context.destinationDirectory);
17 | if (context.uris === undefined) {
18 | context.uris = await context.ui.showOpenDialog(
19 | {
20 | canSelectFiles: true,
21 | canSelectFolders: false,
22 | canSelectMany: true,
23 | defaultUri: ext.lastUriUpload,
24 | filters: {
25 | "All files": ['*']
26 | },
27 | openLabel: upload
28 | }
29 | );
30 | }
31 | context.treeItem = context.treeItem || await ext.rgApi.pickAppResource(context, {
32 | filter: storageFilter,
33 | expectedChildContextValue: [BlobContainerTreeItem.contextValue, FileShareTreeItem.contextValue]
34 | });
35 | }
36 |
37 | public shouldPrompt(): boolean {
38 | return true;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/tree/table/TableTreeItem.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzExtTreeItem, DialogResponses, IActionContext, UserCancelledError } from '@microsoft/vscode-azext-utils';
7 | import * as path from 'path';
8 | import { getResourcesPath } from '../../constants';
9 | import { IStorageRoot } from '../IStorageRoot';
10 | import { IStorageTreeItem } from '../IStorageTreeItem';
11 | import { TableGroupTreeItem } from './TableGroupTreeItem';
12 |
13 | export class TableTreeItem extends AzExtTreeItem implements IStorageTreeItem {
14 | public parent: TableGroupTreeItem;
15 | constructor(
16 | parent: TableGroupTreeItem,
17 | public readonly tableName: string) {
18 | super(parent);
19 | this.iconPath = {
20 | light: path.join(getResourcesPath(), 'light', 'AzureTable.svg'),
21 | dark: path.join(getResourcesPath(), 'dark', 'AzureTable.svg')
22 | };
23 | }
24 |
25 | public get root(): IStorageRoot {
26 | return this.parent.root;
27 | }
28 |
29 | public label: string = this.tableName;
30 | public static contextValue: string = 'azureTable';
31 | public contextValue: string = TableTreeItem.contextValue;
32 |
33 | public async deleteTreeItemImpl(context: IActionContext): Promise {
34 | const message: string = `Are you sure you want to delete table '${this.label}' and all its contents?`;
35 | const result = await context.ui.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
36 | if (result === DialogResponses.deleteResponse) {
37 | const tableServiceClient = await this.root.createTableServiceClient();
38 | await tableServiceClient.deleteTable(this.tableName);
39 | } else {
40 | throw new UserCancelledError();
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/tree/blob/createBlobContainer/BlobContainerNameStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardPromptStep, IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { IBlobContainerCreateChildContext } from "../../../utils/blobUtils";
8 | import { localize } from "../../../utils/localize";
9 |
10 | export class BlobContainerNameStep extends AzureWizardPromptStep {
11 | public async prompt(context: IActionContext & { name?: string }): Promise {
12 | context.name = await context.ui.showInputBox({
13 | placeHolder: localize('enterBlobContainerName', 'Enter a name for the new blob container'),
14 | validateInput: this.validateBlobContainerName
15 | });
16 | }
17 |
18 | public shouldPrompt(context: IBlobContainerCreateChildContext): boolean {
19 | return !context.childName;
20 | }
21 |
22 | private validateBlobContainerName(name: string): string | undefined | null {
23 | const validLength = { min: 3, max: 63 };
24 |
25 | if (!name) {
26 | return "Container name cannot be empty";
27 | } else if (/\s/.test(name)) {
28 | return "Container name cannot contain spaces";
29 | } else if (name.length < validLength.min || name.length > validLength.max) {
30 | return `Container name must contain between ${validLength.min} and ${validLength.max} characters`;
31 | } else if (!/^[a-z0-9-]+$/.test(name)) {
32 | return 'Container name can only contain lowercase letters, numbers and hyphens';
33 | } else if (/--/.test(name)) {
34 | return 'Container name cannot contain two hyphens in a row';
35 | } else if (/(^-)|(-$)/.test(name)) {
36 | return 'Container name cannot begin or end with a hyphen';
37 | }
38 |
39 | return undefined;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/tree/createWizard/StaticWebsiteEnableStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardPromptStep, DialogResponses, IWizardOptions } from "@microsoft/vscode-azext-utils";
7 | import { QuickPickItem } from 'vscode';
8 | import { localize } from "../../utils/localize";
9 | import { IStaticWebsiteConfigWizardContext } from "./IStaticWebsiteConfigWizardContext";
10 | import { StaticWebsiteErrorDocument404Step } from "./StaticWebsiteErrorDocument404Step";
11 | import { StaticWebsiteIndexDocumentStep } from "./StaticWebsiteIndexDocumentStep";
12 |
13 | export class StaticWebsiteEnableStep extends AzureWizardPromptStep {
14 | public async prompt(context: IStaticWebsiteConfigWizardContext): Promise {
15 | if (!context.isCustomCloud) {
16 | const placeHolder: string = localize('wouldYouLikeToEnableStaticWebsiteHosting', 'Would you like to enable static website hosting?');
17 | const yes: QuickPickItem = { label: DialogResponses.yes.title };
18 | const no: QuickPickItem = { label: DialogResponses.no.title };
19 |
20 | context.enableStaticWebsite = await context.ui.showQuickPick([yes, no], { placeHolder }) === yes;
21 | } else {
22 | context.enableStaticWebsite = false;
23 | }
24 | }
25 |
26 | // eslint-disable-next-line @typescript-eslint/require-await
27 | public async getSubWizard(wizardContext: IStaticWebsiteConfigWizardContext): Promise | undefined> {
28 | if (wizardContext.enableStaticWebsite) {
29 | return {
30 | promptSteps: [new StaticWebsiteIndexDocumentStep(), new StaticWebsiteErrorDocument404Step()]
31 | };
32 | }
33 |
34 | return undefined;
35 | }
36 |
37 | public shouldPrompt(): boolean {
38 | return true;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/tree/queue/QueueTreeItem.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import type { QueueItem } from '@azure/storage-queue';
7 |
8 | import { AzExtTreeItem, DialogResponses, IActionContext, UserCancelledError } from '@microsoft/vscode-azext-utils';
9 | import * as path from 'path';
10 | import { getResourcesPath } from "../../constants";
11 | import { IStorageRoot } from "../IStorageRoot";
12 | import { IStorageTreeItem } from '../IStorageTreeItem';
13 | import { QueueGroupTreeItem } from "./QueueGroupTreeItem";
14 |
15 | export class QueueTreeItem extends AzExtTreeItem implements IStorageTreeItem {
16 | public parent: QueueGroupTreeItem;
17 | constructor(
18 | parent: QueueGroupTreeItem,
19 | public readonly queue: QueueItem) {
20 | super(parent);
21 | this.iconPath = {
22 | light: path.join(getResourcesPath(), 'light', 'AzureQueue.svg'),
23 | dark: path.join(getResourcesPath(), 'dark', 'AzureQueue.svg')
24 | };
25 | }
26 |
27 | public get root(): IStorageRoot {
28 | return this.parent.root;
29 | }
30 |
31 | public label: string = this.queue.name;
32 | public static contextValue: string = 'azureQueue';
33 | public contextValue: string = QueueTreeItem.contextValue;
34 |
35 | public async deleteTreeItemImpl(context: IActionContext): Promise {
36 | const message: string = `Are you sure you want to delete queue '${this.label}' and all its contents?`;
37 | const result = await context.ui.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
38 | if (result === DialogResponses.deleteResponse) {
39 | const queueServiceClient = await this.root.createQueueServiceClient();
40 | await queueServiceClient.deleteQueue(this.queue.name);
41 | } else {
42 | throw new UserCancelledError();
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/commands/deleteFilesAndDirectories.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { ext } from "../extensionVariables";
8 | import { BlobContainerGroupTreeItem } from "../tree/blob/BlobContainerGroupTreeItem";
9 | import { FileShareGroupTreeItem } from "../tree/fileShare/FileShareGroupTreeItem";
10 | import { isTreeItemDirectory } from "../utils/directoryUtils";
11 | import { isSubpath } from "../utils/fs";
12 | import { treeUtils } from "../utils/treeUtils";
13 |
14 | export async function deleteFilesAndDirectories(context: IActionContext, treeItem: AzExtTreeItem, selection: AzExtTreeItem[] = []): Promise {
15 | const parentContainer = treeUtils.findNearestParent(treeItem, [
16 | BlobContainerGroupTreeItem.contextValue,
17 | FileShareGroupTreeItem.contextValue
18 | ]);
19 |
20 | // Covers both single selection and an edge case where the node you delete from isn't part of the selection
21 | if (!selection.some(s => s === treeItem)) {
22 | await ext.rgApi.appResourceTreeView.reveal(treeItem);
23 | await treeItem.deleteTreeItem(context);
24 | await parentContainer.refresh(context);
25 | return;
26 | }
27 |
28 | const dirPaths: string[] = [];
29 | let shouldSkip;
30 | for (const node of selection) {
31 | shouldSkip = false;
32 |
33 | // Check to see if it's a resource we've already deleted
34 | for (const dirPath of dirPaths) {
35 | if (isSubpath(dirPath, node.fullId)) {
36 | shouldSkip = true;
37 | break;
38 | }
39 | }
40 | if (shouldSkip) continue;
41 | if (isTreeItemDirectory(node)) {
42 | dirPaths.push(node.fullId);
43 | }
44 | await node.deleteTreeItem(context);
45 | }
46 | await parentContainer.refresh(context);
47 | }
48 |
--------------------------------------------------------------------------------
/src/tree/fileShare/createFileShare/FileShareNameStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils";
7 | import { localize } from "../../../utils/localize";
8 | import { IFileShareWizardContext } from "./IFileShareWizardContext";
9 |
10 | export class FileShareNameStep extends AzureWizardPromptStep {
11 | public async prompt(context: IFileShareWizardContext): Promise {
12 | context.name = await context.ui.showInputBox({
13 | placeHolder: localize('enterFileShareName', 'Enter a name for the new file share'),
14 | validateInput: this.validateFileShareName
15 | });
16 | }
17 |
18 | public shouldPrompt(context: IFileShareWizardContext): boolean {
19 | return !context.name;
20 | }
21 |
22 | private validateFileShareName(name: string): string | undefined | null {
23 | const validLength = { min: 3, max: 63 };
24 |
25 | if (!name) {
26 | return localize('shareNameEmpty', "Share name cannot be empty");
27 | } else if (/\s/.test(name)) {
28 | return localize('shareNameSpaces', "Share name cannot contain spaces");
29 | } else if (name.length < validLength.min || name.length > validLength.max) {
30 | return localize('shareNameBetween', 'Share name must contain between {0} and {1} characters', validLength.min, validLength.max);
31 | } else if (!/^[a-z0-9-]+$/.test(name)) {
32 | return localize('shareNameInvalidChar', 'Share name can only contain lowercase letters, numbers and hyphens');
33 | } else if (/--/.test(name)) {
34 | return localize('shareNameDoubleHyphen', 'Share name cannot contain two hyphens in a row');
35 | } else if (/(^-)|(-$)/.test(name)) {
36 | return localize('shareNameHyphen', 'Share name cannot begin or end with a hyphen');
37 | }
38 |
39 | return undefined;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/test/index.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See LICENSE.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import * as glob from 'glob';
7 | import * as Mocha from 'mocha';
8 | import * as path from 'path';
9 |
10 | export async function run(): Promise {
11 | const options: Mocha.MochaOptions = {
12 | ui: 'tdd',
13 | color: true,
14 | reporter: 'mocha-multi-reporters',
15 | reporterOptions: {
16 | reporterEnabled: 'spec, mocha-junit-reporter',
17 | mochaJunitReporterReporterOptions: {
18 | mochaFile: path.resolve(__dirname, '..', '..', 'test-results.xml')
19 | }
20 | }
21 | };
22 |
23 | addEnvVarsToMochaOptions(options);
24 | console.log(`Mocha options: ${JSON.stringify(options, undefined, 2)}`);
25 |
26 | const mocha = new Mocha(options);
27 |
28 | const files: string[] = await new Promise((resolve, reject) => {
29 | glob('**/**.test.js', { cwd: __dirname }, (err, result) => {
30 | err ? reject(err) : resolve(result);
31 | });
32 | });
33 |
34 | files.forEach(f => mocha.addFile(path.resolve(__dirname, f)));
35 |
36 | const failures = await new Promise(resolve => mocha.run(resolve));
37 | if (failures > 0) {
38 | throw new Error(`${failures} tests failed.`);
39 | }
40 | }
41 |
42 | function addEnvVarsToMochaOptions(options: Mocha.MochaOptions): void {
43 | for (const envVar of Object.keys(process.env)) {
44 | const match: RegExpMatchArray | null = envVar.match(/^mocha_(.+)/i);
45 | if (match) {
46 | const [, option] = match;
47 | let value: string | number = process.env[envVar] || '';
48 | if (typeof value === 'string' && !isNaN(parseInt(value))) {
49 | value = parseInt(value);
50 | }
51 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
52 | (options)[option] = value;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/commands/deleteStorageAccount/DeleteStorageAccountStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils";
7 | import { Progress, window } from "vscode";
8 | import { ext } from "../../extensionVariables";
9 | import { StorageAccountTreeItem } from "../../tree/StorageAccountTreeItem";
10 | import { createStorageClient } from "../../utils/azureClients";
11 | import { localize } from "../../utils/localize";
12 | import { DeleteStorageAccountWizardContext } from "./DeleteStorageAccountWizardContext";
13 |
14 | export class DeleteStorageAccountStep extends AzureWizardExecuteStep {
15 | public priority: number = 100;
16 |
17 | public async execute(wizardContext: DeleteStorageAccountWizardContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined; }>): Promise {
18 |
19 | const storageAccount = nonNullProp(wizardContext, 'storageAccount');
20 |
21 | const deletingStorageAccount: string = localize('deletingStorageAccount', 'Deleting storage account "{0}"...', storageAccount.name);
22 | const storageManagementClient = await createStorageClient([wizardContext, wizardContext.subscription]);
23 | const parsedId = StorageAccountTreeItem.parseAzureResourceId(storageAccount.id);
24 | const resourceGroupName = parsedId.resourceGroups;
25 |
26 | ext.outputChannel.appendLog(deletingStorageAccount);
27 | progress.report({ message: deletingStorageAccount });
28 | await storageManagementClient.storageAccounts.delete(resourceGroupName, storageAccount.name);
29 |
30 | const deleteSuccessful: string = localize('successfullyDeletedStorageAccount', 'Successfully deleted storage account "{0}".', storageAccount.name);
31 | ext.outputChannel.appendLog(deleteSuccessful);
32 | void window.showInformationMessage(deleteSuccessful);
33 | }
34 |
35 | public shouldExecute(): boolean {
36 | return true;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/utils/progress.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { Progress, ProgressLocation, ProgressOptions, window } from "vscode";
7 | import { ext } from "../extensionVariables";
8 |
9 | type StatusBarProgress = Progress<{ message: string }>;
10 |
11 | /**
12 | * Shows progress in both the output window and the status bar
13 | */
14 | export async function awaitWithProgress(title: string, promise: Promise, getProgress: () => string): Promise {
15 | const uiIntervalMs = 1 * 500;
16 | const uiUpdatesPerChannelUpdate = 5000 / uiIntervalMs;
17 | let nextChannelUpdate = uiUpdatesPerChannelUpdate;
18 |
19 | let thisProgress: StatusBarProgress;
20 |
21 | void window.withProgress(
22 | {
23 | location: ProgressLocation.Window,
24 | title: title
25 | },
26 | async (progress: StatusBarProgress): Promise => {
27 | thisProgress = progress;
28 | return promise;
29 | });
30 |
31 | pollDuringPromise(uiIntervalMs, promise, () => {
32 | const msg = getProgress();
33 |
34 | nextChannelUpdate -= 1;
35 | if (nextChannelUpdate <= 0) {
36 | nextChannelUpdate = uiUpdatesPerChannelUpdate;
37 | ext.outputChannel.appendLog(`${title}: ${msg}`);
38 | }
39 |
40 | thisProgress.report({ message: msg });
41 | });
42 |
43 | return await promise;
44 | }
45 |
46 | /**
47 | * Runs the given poll function repeatedly until the given promise is resolved or rejected
48 | */
49 | function pollDuringPromise(intervalMs: number, promise: Promise, poll: () => void): void {
50 | let inProgress = true;
51 | promise.then(
52 | () => {
53 | inProgress = false;
54 | },
55 | () => {
56 | inProgress = false;
57 | }
58 | );
59 |
60 | const pollFunction = () => {
61 | if (inProgress) {
62 | poll();
63 | setTimeout(pollFunction, intervalMs);
64 | }
65 | };
66 |
67 | pollFunction();
68 | }
69 |
--------------------------------------------------------------------------------
/src/commands/downloadFiles/SasUrlPromptStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardPromptStep, IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { env } from "vscode";
8 | import { localize } from "../../utils/localize";
9 | import { ISasDownloadContext } from "./ISasDownloadContext";
10 |
11 | export class SasUrlPromptStep extends AzureWizardPromptStep {
12 | public async prompt(context: ISasDownloadContext): Promise {
13 | let value: string | undefined = await env.clipboard.readText();
14 | if (await this.validateInput(context, value)) {
15 | // if there is a string value here, then the clipboard does not contain a valid SAS url
16 | value = undefined;
17 | }
18 |
19 | context.sasUrl = await context.ui.showInputBox({
20 | prompt: localize('enterSasUrl', 'Enter a Azure Storage SAS URL'),
21 | validateInput: async (s): Promise => await this.validateInput(context, s),
22 | value
23 | });
24 | }
25 |
26 | public shouldPrompt(): boolean {
27 | return true;
28 | }
29 |
30 | public async validateInput(_wizardContext: ISasDownloadContext, value: string | undefined): Promise {
31 | if (!value) {
32 | return localize('emptySasUrl', 'A SAS token and URL cannot be empty.')
33 | }
34 |
35 | try {
36 | const url = new URL(value);
37 | // not a comprehensive list of required params
38 | if (
39 | !url.searchParams.get('sp') ||
40 | !url.searchParams.get('se') ||
41 | !url.searchParams.get('sig')
42 | ) {
43 | return localize('enterValidToken', 'The SAS token is missing a parameter. Enter a valid SAS token.',)
44 | }
45 | } catch (err) {
46 | return localize('enterValidSasUrl', 'The URL "{0}" is not valid. Enter a valid URL', value);
47 | }
48 |
49 | return undefined;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/TransferProgress.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { NotificationProgress } from './constants';
7 | import { ext } from './extensionVariables';
8 |
9 | export class TransferProgress {
10 | private message: string = '';
11 | private percentage: number = 0;
12 | private lastPercentage: number = 0;
13 | private lastUpdated: number = Date.now();
14 |
15 | constructor(
16 | private readonly units: 'bytes' | 'files' | 'blobs',
17 | private readonly messagePrefix?: string,
18 | private readonly updateTimerMs: number = 200
19 | ) { }
20 |
21 | public reportToNotification(finishedWork: number, totalWork: number, notificationProgress: NotificationProgress): void {
22 | // This function may be called very frequently. Calls made to notificationProgress.report too rapidly result in incremental
23 | // progress not displaying in the notification window. So debounce calls to notificationProgress.report
24 | if (this.lastUpdated + this.updateTimerMs < Date.now()) {
25 | this.preReport(finishedWork, totalWork);
26 | if (this.percentage !== this.lastPercentage) {
27 | notificationProgress.report({ message: this.message, increment: this.percentage - this.lastPercentage });
28 | }
29 | this.postReport();
30 | }
31 | }
32 |
33 | public reportToOutputWindow(finishedWork: number, totalWork: number): void {
34 | this.preReport(finishedWork, totalWork);
35 | if (this.percentage !== this.lastPercentage) {
36 | ext.outputChannel.appendLog(this.message);
37 | }
38 | this.postReport();
39 | }
40 |
41 | private preReport(finishedWork: number, totalWork: number): void {
42 | this.percentage = Math.trunc((finishedWork / totalWork) * 100);
43 | const prefix: string = this.messagePrefix ? `${this.messagePrefix}: ` : '';
44 | this.message = `${prefix}${finishedWork}/${totalWork} ${this.units} (${this.percentage}%)`;
45 | }
46 |
47 | private postReport(): void {
48 | this.lastPercentage = this.percentage;
49 | this.lastUpdated = Date.now();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/commands/downloadFile.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizard, AzureWizardPromptStep, IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { ITransferSrcOrDstTreeItem } from "../tree/ITransferSrcOrDstTreeItem";
8 | import { createActivityContext } from "../utils/activityUtils";
9 | import { localize } from "../utils/localize";
10 | import { DestinationPromptStep } from "./downloadFiles/DestinationPromptStep";
11 | import { DownloadFilesStep } from "./downloadFiles/DownloadFilesStep";
12 | import { GetAzCopyDownloadsStep } from "./downloadFiles/GetAzCopyDownloadsStep";
13 | import { IDownloadWizardContext } from "./downloadFiles/IDownloadWizardContext";
14 | import { SasUrlPromptStep } from "./downloadFiles/SasUrlPromptStep";
15 |
16 | export interface IAzCopyDownload {
17 | remoteFileName: string;
18 | remoteFilePath: string;
19 | localFilePath: string;
20 | isDirectory: boolean;
21 | resourceUri: string;
22 | sasToken: string;
23 | }
24 |
25 | export async function download(context: IActionContext, treeItems?: ITransferSrcOrDstTreeItem[]): Promise {
26 | const wizardContext: IDownloadWizardContext = { ...context, ...(await createActivityContext()) };
27 | const promptSteps: AzureWizardPromptStep[] = [new DestinationPromptStep()];
28 | if (!treeItems) {
29 | promptSteps.push(new SasUrlPromptStep());
30 | } else {
31 | wizardContext.treeItems = treeItems;
32 | }
33 |
34 | const wizard: AzureWizard = new AzureWizard(wizardContext, {
35 | promptSteps,
36 | executeSteps: [new GetAzCopyDownloadsStep(), new DownloadFilesStep()],
37 | title: localize('download', 'Download Files')
38 | });
39 |
40 | await wizard.prompt();
41 | await wizard.execute();
42 | }
43 |
44 | export async function downloadSasUrl(context: IActionContext): Promise {
45 | return await download(context);
46 | }
47 |
48 | export async function downloadTreeItems(context: IActionContext, treeItem: ITransferSrcOrDstTreeItem, treeItems?: ITransferSrcOrDstTreeItem[]): Promise {
49 | treeItems ??= [treeItem];
50 | await download(context, treeItems);
51 | }
52 |
--------------------------------------------------------------------------------
/src/commands/uploadFolder/uploadFolder.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzExtTreeItem, AzureWizard, IActionContext, IWizardOptions, nonNullProp } from '@microsoft/vscode-azext-utils';
7 | import { basename } from "path";
8 | import * as vscode from 'vscode';
9 | import { BlobContainerTreeItem } from '../../tree/blob/BlobContainerTreeItem';
10 | import { FileShareTreeItem } from '../../tree/fileShare/FileShareTreeItem';
11 | import { createActivityContext } from '../../utils/activityUtils';
12 | import { localize } from '../../utils/localize';
13 | import { IAzCopyResolution } from '../transfers/azCopy/IAzCopyResolution';
14 | import { GetFolderDestinationDirectoryStep } from './GetFolderDestinationDirectoryStep';
15 | import { IUploadFolderWizardContext } from './IUploadFolderWizardContext';
16 | import { UploadFolderStep } from './UploadFolderStep';
17 |
18 | export async function uploadFolder(
19 | context: IActionContext,
20 | treeItem?: BlobContainerTreeItem | FileShareTreeItem,
21 | _selectedNodes?: AzExtTreeItem[],
22 | uri?: vscode.Uri,
23 | cancellationToken?: vscode.CancellationToken,
24 | destinationDirectory?: string
25 | ): Promise {
26 | const wizardContext: IUploadFolderWizardContext = {
27 | ...context, ...await createActivityContext(), destinationDirectory, treeItem, uri,
28 | calledFromUploadToAzureStorage: uri !== undefined
29 | };
30 | const wizardOptions: IWizardOptions = {
31 | promptSteps: [new GetFolderDestinationDirectoryStep()],
32 | executeSteps: [new UploadFolderStep(cancellationToken)],
33 | };
34 | const wizard: AzureWizard = new AzureWizard(wizardContext, wizardOptions);
35 | await wizard.prompt();
36 |
37 | const nTreeItem: BlobContainerTreeItem | FileShareTreeItem = nonNullProp(wizardContext, "treeItem");
38 | const nUri: vscode.Uri = nonNullProp(wizardContext, "uri");
39 | wizardContext.activityTitle = localize('activityLogUploadFolder', 'Upload "{0}" folder to "{1}"', basename(nUri.path), nTreeItem.label);
40 |
41 | await wizard.execute();
42 | return wizardContext.resolution as IAzCopyResolution;
43 | }
44 |
--------------------------------------------------------------------------------
/extension.bundle.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See LICENSE.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | /**
7 | * This is the external face of extension.bundle.js, the main webpack bundle for the extension.
8 | * Anything needing to be exposed outside of the extension sources must be exported from here, because
9 | * everything else will be in private modules in extension.bundle.js.
10 | */
11 |
12 | // Exports for tests
13 | // The tests are not packaged with the webpack bundle and therefore only have access to code exported from this file.
14 | //
15 | // The tests should import '../extension.bundle.ts'. At design-time they live in tests/ and so will pick up this file (extension.bundle.ts).
16 | // At runtime the tests live in dist/tests and will therefore pick up the main webpack bundle at dist/extension.bundle.js.
17 | export * from '@microsoft/vscode-azext-azureutils';
18 | export * from '@microsoft/vscode-azext-utils';
19 | export { ResolvedAppResourceTreeItem } from '@microsoft/vscode-azext-utils/hostapi';
20 | export * from './src/commands/blob/blobContainerActionHandlers';
21 | export * from './src/commands/blob/blobContainerGroupActionHandlers';
22 | export * from './src/commands/createStorageAccount';
23 | export * from './src/commands/fileShare/fileShareActionHandlers';
24 | export * from './src/commands/fileShare/fileShareGroupActionHandlers';
25 | export * from './src/commands/queue/queueActionHandlers';
26 | export * from './src/commands/queue/queueGroupActionHandlers';
27 | export * from './src/commands/storageAccountActionHandlers';
28 | export * from './src/commands/table/tableActionHandlers';
29 | export * from './src/commands/table/tableGroupActionHandlers';
30 | // Export activate/deactivate for main.js
31 | export { activate, deactivate } from './src/extension';
32 | export { ext } from './src/extensionVariables';
33 | export { ResolvedStorageAccount } from './src/StorageAccountResolver';
34 | export { StorageAccountTreeItem } from './src/tree/StorageAccountTreeItem';
35 | export { delay } from './src/utils/delay';
36 | export { getRandomHexString } from './src/utils/stringUtils';
37 |
38 | // NOTE: The auto-fix action "source.organizeImports" does weird things with this file, but there doesn't seem to be a way to disable it on a per-file basis so we'll just let it happen
39 |
--------------------------------------------------------------------------------
/src/utils/checkCanOverwrite.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { DialogResponses, IActionContext } from "@microsoft/vscode-azext-utils";
7 | import { localize } from "./localize";
8 | import { OverwriteChoice } from "./uploadUtils";
9 |
10 | // Pass `overwriteChoice` as an object to make use of pass by reference.
11 | export async function checkCanOverwrite(
12 | context: IActionContext,
13 | destPath: string,
14 | overwriteChoice: { choice: OverwriteChoice | undefined },
15 | destPathExists: () => Promise
16 | ): Promise {
17 | if (overwriteChoice.choice === OverwriteChoice.yesToAll) {
18 | // Always overwrite
19 | return true;
20 | }
21 |
22 | if (await destPathExists()) {
23 | if (overwriteChoice.choice === OverwriteChoice.noToAll) {
24 | // Resources that already exist shouldn't be overwritten
25 | return false;
26 | } else {
27 | overwriteChoice.choice = await showDuplicateResourceWarning(context, destPath);
28 | switch (overwriteChoice.choice) {
29 | case OverwriteChoice.no:
30 | case OverwriteChoice.noToAll:
31 | return false;
32 |
33 | case OverwriteChoice.yes:
34 | case OverwriteChoice.yesToAll:
35 | default:
36 | return true;
37 | }
38 | }
39 | } else {
40 | // This resource doesn't exist yet, so overwriting is OK
41 | return true;
42 | }
43 | }
44 |
45 | async function showDuplicateResourceWarning(context: IActionContext, resourceName: string): Promise {
46 | const message: string = localize('resourceExists', 'A resource named "{0}" already exists. Do you want to overwrite it?', resourceName);
47 | const items = [
48 | { title: localize('yesToAll', 'Yes to all'), data: OverwriteChoice.yesToAll },
49 | { title: DialogResponses.yes.title, data: OverwriteChoice.yes },
50 | { title: localize('noToAll', 'No to all'), data: OverwriteChoice.noToAll },
51 | { title: DialogResponses.no.title, data: OverwriteChoice.no }
52 | ];
53 | return (await context.ui.showWarningMessage(message, { modal: true }, ...items)).data;
54 | }
55 |
--------------------------------------------------------------------------------
/src/commands/transfers/azCopy/azCopyLocations.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { ILocalLocation, IRemoteAuthLocation, IRemoteSasLocation } from "@azure-tools/azcopy-node";
7 | import { posix, sep } from "path";
8 | import { DownloadItem, UploadItem } from "../transfers";
9 |
10 | export function createAzCopyLocalLocation(path: string, isFolder?: boolean): ILocalLocation {
11 | if (isFolder && !path.endsWith(sep)) {
12 | path += sep;
13 | }
14 | return { type: 'Local', path, useWildCard: !!isFolder };
15 | }
16 |
17 | export async function createAzCopyRemoteLocation(item: DownloadItem | UploadItem): Promise {
18 | let path = item.remoteFilePath;
19 | if (item.isDirectory && !path.endsWith(posix.sep)) {
20 | path += posix.sep;
21 | }
22 |
23 | // Ensure path begins with '/' to transfer properly
24 | path = path[0] === posix.sep ? path : `${posix.sep}${path}`;
25 | let remoteLocation: IRemoteSasLocation | IRemoteAuthLocation;
26 | const { sasToken, resourceUri, treeItem } = item;
27 |
28 | if (treeItem && !treeItem.root.allowSharedKeyAccess) {
29 | const accessToken = await treeItem.root.getAccessToken();
30 | const refreshToken = treeItem.root.getAccessToken;
31 | remoteLocation = createRemoteAuthLocation(item, path, resourceUri, accessToken, refreshToken);
32 | } else if (sasToken) {
33 | remoteLocation = {
34 | type: 'RemoteSas',
35 | sasToken,
36 | resourceUri,
37 | path,
38 | useWildCard: !!item.isDirectory
39 | };
40 | } else {
41 | throw new Error(`No sasToken or accessToken found for resourceUri "${resourceUri}".`);
42 | }
43 |
44 | return remoteLocation;
45 | }
46 |
47 | function createRemoteAuthLocation(item: DownloadItem | UploadItem, path: string, resourceUri: string, accessToken: string, refreshToken: () => Promise): IRemoteAuthLocation {
48 | const tenantId = item.treeItem?.root.tenantId ?? '';
49 | return {
50 | type: 'RemoteAuth',
51 | authToken: accessToken,
52 | refreshToken,
53 | tenantId,
54 | resourceUri,
55 | path,
56 | aadEndpoint: 'https://storage.azure.com',
57 | useWildCard: !!item.isDirectory
58 | };
59 | }
60 |
--------------------------------------------------------------------------------
/src/commands/uploadFiles/uploadFiles.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzExtTreeItem, AzureWizard, IActionContext, IWizardOptions, nonNullProp } from "@microsoft/vscode-azext-utils";
7 | import { basename } from "path";
8 | import { CancellationToken, Uri } from "vscode";
9 | import { BlobContainerTreeItem } from "../../tree/blob/BlobContainerTreeItem";
10 | import { FileShareTreeItem } from "../../tree/fileShare/FileShareTreeItem";
11 | import { createActivityContext } from "../../utils/activityUtils";
12 | import { localize } from "../../utils/localize";
13 | import { IAzCopyResolution } from "../transfers/azCopy/IAzCopyResolution";
14 | import { GetFileDestinationDirectoryStep } from './GetFileDestinationDirectoryStep';
15 | import { IUploadFilesWizardContext } from "./IUploadFilesWizardContext";
16 | import { UploadFilesStep } from "./UploadFilesStep";
17 |
18 | export async function uploadFiles(
19 | context: IActionContext,
20 | treeItem?: BlobContainerTreeItem | FileShareTreeItem,
21 | _selectedNodes?: AzExtTreeItem[],
22 | uris?: Uri[],
23 | cancellationToken?: CancellationToken,
24 | destinationDirectory?: string
25 | ): Promise {
26 | const wizardContext: IUploadFilesWizardContext = {
27 | ...context, ...await createActivityContext(), destinationDirectory, treeItem, uris,
28 | calledFromUploadToAzureStorage: !!uris?.length
29 | };
30 | const wizardOptions: IWizardOptions = {
31 | promptSteps: [new GetFileDestinationDirectoryStep()],
32 | executeSteps: [new UploadFilesStep(cancellationToken)],
33 | };
34 | const wizard: AzureWizard = new AzureWizard(wizardContext, wizardOptions);
35 | await wizard.prompt();
36 |
37 | const nUris: Uri[] = nonNullProp(wizardContext, "uris");
38 | const nTreeItem: BlobContainerTreeItem | FileShareTreeItem = nonNullProp(wizardContext, "treeItem");
39 | if (nUris.length === 1) {
40 | wizardContext.activityTitle = localize('activityLogUploadFiles', 'Upload "{0}" to "{1}"', basename(nUris[0].path), nTreeItem.label);
41 | } else {
42 | wizardContext.activityTitle = localize('activityLogUploadFiles', 'Upload {0} files to "{1}"', nUris.length, nTreeItem.label);
43 | }
44 |
45 | await wizard.execute();
46 | return wizardContext.resolution as IAzCopyResolution;
47 | }
48 |
--------------------------------------------------------------------------------
/src/utils/blobPathUtils.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | function endsWithSlash(path: string) {
7 | return path.endsWith("/");
8 | }
9 |
10 | export class BlobPathUtils {
11 | /**
12 | * Gets the parent directory of the given `path`.
13 | * This function is unsafe to use on a path to an empty name blob.
14 | *
15 | * @example dirname("abc/efg/") -> "abc/"
16 | */
17 | public static dirname(path: string): string {
18 | if (endsWithSlash(path)) {
19 | const secondLastIndex = path.slice(0, path.length - 1).lastIndexOf("/");
20 | return secondLastIndex < 0 ? "" : `${path.slice(0, secondLastIndex)}/`;
21 | } else {
22 | const lastIndex = path.lastIndexOf("/");
23 | return lastIndex < 0 ? "" : `${path.slice(0, lastIndex)}/`;
24 | }
25 | }
26 |
27 | /**
28 | * Gets the last segment of the given `path`.
29 | * This function is unsafe to use on a path to an empty name blob.
30 | *
31 | * @example basename("abc/efg/") -> "efg/"
32 | */
33 | public static basename(path: string): string {
34 | const lastIndex = path.lastIndexOf("/");
35 | if (lastIndex === path.length - 1) {
36 | // path is a directory path
37 | const secondLastIndex = path.slice(0, path.length - 1).lastIndexOf("/");
38 | return path.slice(secondLastIndex + 1);
39 | } else {
40 | // path is a blob path
41 | return path.slice(lastIndex + 1);
42 | }
43 | }
44 |
45 | /**
46 | * Joins an array of path segments to a base path to form a new path.
47 | */
48 | public static join(basePath: string, ...segments: [...string[], string]): string {
49 | return basePath + segments.join("");
50 | }
51 |
52 | /**
53 | * Removes the trailing slash if the `path` ends with a slash.
54 | */
55 | public static trimSlash(path: string): string {
56 | if (endsWithSlash(path)) {
57 | return path.slice(0, path.length - 1);
58 | }
59 | return path;
60 | }
61 |
62 | /**
63 | * Removes the trailing slash if the `path` ends with a slash.
64 | */
65 | public static appendSlash(path: string): string {
66 | if (!endsWithSlash(path)) {
67 | return path + "/";
68 | }
69 | return path;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/commands/fileShare/fileShareActionHandlers.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 | import { IActionContext, registerCommandWithTreeNodeUnwrapping } from '@microsoft/vscode-azext-utils';
6 | import * as vscode from 'vscode';
7 | import { AzureStorageFS } from '../../AzureStorageFS';
8 | import { storageExplorerLauncher } from '../../storageExplorerLauncher/storageExplorerLauncher';
9 | import { DirectoryTreeItem } from '../../tree/fileShare/DirectoryTreeItem';
10 | import { FileShareTreeItem, IFileShareCreateChildContext } from '../../tree/fileShare/FileShareTreeItem';
11 | import { FileTreeItem } from '../../tree/fileShare/FileTreeItem';
12 | import { deleteNode } from '../commonTreeCommands';
13 |
14 | export function registerFileShareActionHandlers(): void {
15 | registerCommandWithTreeNodeUnwrapping("azureStorage.openFileShare", openFileShareInStorageExplorer);
16 | registerCommandWithTreeNodeUnwrapping("azureStorage.editFile", async (context: IActionContext, treeItem: FileTreeItem) => AzureStorageFS.showEditor(context, treeItem), 250);
17 | registerCommandWithTreeNodeUnwrapping("azureStorage.deleteFileShare", deleteFileShare);
18 | registerCommandWithTreeNodeUnwrapping("azureStorage.createDirectory", async (context: IActionContext, treeItem: FileShareTreeItem) => await treeItem.createChild({ ...context, childType: DirectoryTreeItem.contextValue }));
19 | registerCommandWithTreeNodeUnwrapping("azureStorage.createFile", async (context: IActionContext, treeItem: FileShareTreeItem) => {
20 | const childTreeItem = await treeItem.createChild({ ...context, childType: FileTreeItem.contextValue });
21 | await vscode.commands.executeCommand("azureStorage.editFile", childTreeItem);
22 | });
23 | }
24 |
25 | async function openFileShareInStorageExplorer(_context: IActionContext, treeItem: FileShareTreeItem): Promise {
26 | const accountId = treeItem.root.storageAccountId;
27 | const subscriptionid = treeItem.subscription.subscriptionId;
28 | const resourceType = 'Azure.FileShare';
29 | const resourceName = treeItem.shareName;
30 |
31 | await storageExplorerLauncher.openResource(accountId, subscriptionid, resourceType, resourceName);
32 | }
33 |
34 | export async function deleteFileShare(context: IActionContext, treeItem?: FileShareTreeItem): Promise {
35 | await deleteNode(context, FileShareTreeItem.contextValue, treeItem);
36 | }
37 |
--------------------------------------------------------------------------------
/src/utils/validateNames.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { localize } from "./localize";
7 |
8 | const invalidBlobDirectoryChar = '\\'; // Keeps behavior consistent on Mac & Windows when creating blob directories via the file explorer
9 | const invalidFileAndDirectoryChars = ['"', '/', '\\', ':', '|', '<', '>', '?', '*'];
10 | const invalidFileAndDirectoryCharsString = invalidFileAndDirectoryChars.join(', ');
11 |
12 | export enum DocumentType {
13 | index = 'index',
14 | error = 'error'
15 | }
16 |
17 | export function validateBlobDirectoryName(name: string): string | undefined {
18 | if (!name || name.includes(invalidBlobDirectoryChar)) {
19 | return `Directory name cannot be empty or contain '${invalidBlobDirectoryChar}'`;
20 | }
21 |
22 | return undefined;
23 | }
24 |
25 | export function validateFileOrDirectoryName(name: string): string | undefined {
26 | const validLength = { min: 1, max: 255 };
27 |
28 | if (!name) {
29 | return localize('nameCantBeEmpty', 'Name cannot be empty');
30 | }
31 |
32 | if (name.length < validLength.min || name.length > validLength.max) {
33 | return localize('nameMustContain', 'Name must contain between {0} and {1} characters', validLength.min, validLength.max);
34 | }
35 |
36 | if (invalidFileAndDirectoryChars.some(ch => name.indexOf(ch) >= 0)) {
37 | return localize('nameCantContain', 'Name cannot contain the following characters: {0}', invalidFileAndDirectoryCharsString);
38 | }
39 |
40 | return undefined;
41 | }
42 |
43 | export function validateDocumentPath(documentPath: string, documentType: DocumentType): undefined | string {
44 | const minLengthDocumentPath = 3;
45 | const maxLengthDocumentPath = 255;
46 |
47 | if (documentType === DocumentType.index && documentPath.includes('/')) {
48 | return localize('indexDocumentPathCannotContainForwardSlash', 'The index document path cannot contain a "/" character.');
49 | } else if (documentType === DocumentType.error && (documentPath.startsWith('/') || documentPath.endsWith('/'))) {
50 | return localize('errorDocumentCannotStartOrEndWithForwardSlash', 'The error document path start or end with a "/" character.');
51 | } else if (documentPath.length < minLengthDocumentPath || documentPath.length > maxLengthDocumentPath) {
52 | return localize('documentPathLengthIsInvalid', 'The {0} document path must be between {1} and {2} characters in length.', documentType, minLengthDocumentPath, maxLengthDocumentPath);
53 | }
54 |
55 | return undefined;
56 | }
57 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
40 |
41 |
--------------------------------------------------------------------------------
/.github/workflows/feature-request.yml:
--------------------------------------------------------------------------------
1 | name: Feature Request Manager
2 | on:
3 | issues:
4 | types: [milestoned]
5 | schedule:
6 | - cron: 15 5 * * * # 10:15pm PT
7 | workflow_dispatch:
8 |
9 | jobs:
10 | main:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout Actions
14 | if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request')
15 | uses: actions/checkout@v3
16 | with:
17 | repository: "microsoft/vscode-github-triage-actions"
18 | path: ./actions
19 | ref: stable
20 | - name: Install Actions
21 | if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request')
22 | run: npm install --production --prefix ./actions
23 | - name: Run Feature Request Manager
24 | if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request')
25 | uses: ./actions/feature-request
26 | with:
27 | app_id: ${{ secrets.AZURETOOLS_VSCODE_BOT_APP_ID }}
28 | app_installation_id: ${{ secrets.AZURETOOLS_VSCODE_BOT_APP_INSTALLATION_ID }}
29 | app_private_key: ${{ secrets.AZURETOOLS_VSCODE_BOT_APP_PRIVATE_KEY }}
30 | owner: "microsoft"
31 | repo: "vscode-azurestorage"
32 | candidateMilestoneID: 4
33 | candidateMilestoneName: "Backlog Candidates"
34 | backlogMilestoneID: 24
35 | featureRequestLabel: "feature"
36 | upvotesRequired: 5
37 | numCommentsOverride: 10
38 | initComment: "This feature request is now a candidate for our backlog. The community has 240 days to upvote the issue. If it receives 5 upvotes we will move it to our backlog. If not, we will close it. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/azcodeissuetriaging).\n\nHappy Coding!"
39 | rejectComment: ":slightly_frowning_face: In the last 60 days, this issue has received less than 5 community upvotes and we closed it. Still a big Thank You to you for taking the time to create it! To learn more about how we handle issues, please see our [documentation](https://aka.ms/azcodeissuetriaging).\n\nHappy Coding!"
40 | rejectLabel: "out of scope"
41 | warnComment: "This issue has become stale and is at risk of being closed. The community has 60 days to upvote the issue. If it receives 5 upvotes we will keep it open and take another look. If not, we will close it. To learn more about how we handle issues, please see our [documentation](https://aka.ms/azcodeissuetriaging).\n\nHappy Coding!"
42 | labelsToExclude: "P0,P1"
43 | acceptComment: ":slightly_smiling_face: This feature request received a sufficient number of community upvotes and we moved it to our backlog. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/azcodeissuetriaging).\n\nHappy Coding!"
44 | warnDays: 60
45 | closeDays: 240
46 | milestoneDelaySeconds: 60
47 |
--------------------------------------------------------------------------------
/src/tree/createWizard/storageAccountNameStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { CheckNameAvailabilityResult, StorageManagementClient } from '@azure/arm-storage';
7 | import { IStorageAccountWizardContext, ResourceGroupListStep, resourceGroupNamingRules, storageAccountNamingRules } from '@microsoft/vscode-azext-azureutils';
8 | import { AzureNameStep } from '@microsoft/vscode-azext-utils';
9 | import { createStorageClient } from '../../utils/azureClients';
10 |
11 | export class StorageAccountNameStep extends AzureNameStep {
12 | public async prompt(context: T): Promise {
13 | const client: StorageManagementClient = await createStorageClient(context);
14 |
15 | const suggestedName: string | undefined = context.relatedNameTask ? await context.relatedNameTask : undefined;
16 | context.newStorageAccountName = (await context.ui.showInputBox({
17 | value: suggestedName,
18 | prompt: 'Enter a globally unique name for the new Storage Account',
19 | validateInput: async (value: string): Promise => await this.validateStorageAccountName(client, value)
20 | })).trim();
21 | context.valuesToMask.push(context.newStorageAccountName);
22 | if (!context.relatedNameTask) {
23 | context.relatedNameTask = this.generateRelatedName(context, context.newStorageAccountName, resourceGroupNamingRules);
24 | }
25 | }
26 |
27 | public shouldPrompt(wizardContext: T): boolean {
28 | return !wizardContext.newStorageAccountName;
29 | }
30 |
31 | protected async isRelatedNameAvailable(wizardContext: T, name: string): Promise {
32 | return await ResourceGroupListStep.isNameAvailable(wizardContext, name);
33 | }
34 |
35 | private async validateStorageAccountName(client: StorageManagementClient, name: string): Promise {
36 | name = name ? name.trim() : '';
37 |
38 | if (!name || name.length < storageAccountNamingRules.minLength || name.length > storageAccountNamingRules.maxLength) {
39 | return `The name must be between ${storageAccountNamingRules.minLength} and ${storageAccountNamingRules.maxLength} characters.`;
40 | } else if (name.match(storageAccountNamingRules.invalidCharsRegExp) !== null) {
41 | return "The name can only contain lowercase letters and numbers.";
42 | } else {
43 | const nameAvailabilityResult: CheckNameAvailabilityResult = await client.storageAccounts.checkNameAvailability({ name, type: 'Microsoft.Storage/storageAccounts' });
44 | if (!nameAvailabilityResult.nameAvailable) {
45 | return nameAvailabilityResult.message;
46 | } else {
47 | return undefined;
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/commands/StartingResourcesLogStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { type ResourceGroup } from "@azure/arm-resources";
7 | import { LocationListStep, type ILocationWizardContext } from "@microsoft/vscode-azext-azureutils";
8 | import { ActivityChildItem, ActivityChildType, activityInfoContext, activityInfoIcon, AzureWizardPromptStep, createContextValue, type ExecuteActivityContext, type IActionContext } from "@microsoft/vscode-azext-utils";
9 | import { ext } from "../extensionVariables";
10 | import { ActivityInfoChild, prependOrInsertAfterLastInfoChild } from "../utils/activityUtils";
11 | import { localize } from "../utils/localize";
12 |
13 | type StartingResourcesLogContext = IActionContext & Partial & ILocationWizardContext & {
14 | resourceGroup?: ResourceGroup,
15 | };
16 |
17 | const startingResourcesContext: string = 'startingResourcesLogStepItem';
18 |
19 | /**
20 | * Use to display primary Azure resource data to the output and activity log
21 | * i.e. resource group, location, etc.
22 | */
23 | export class StartingResourcesLogStep extends AzureWizardPromptStep {
24 | public hideStepCount: boolean = true;
25 | protected hasLogged: boolean = false;
26 |
27 | /**
28 | * Implement if you require additional context loading before resource logging
29 | */
30 | protected configureStartingResources?(context: T): void | Promise;
31 |
32 | public async configureBeforePrompt(context: T): Promise {
33 | if (this.hasLogged) {
34 | return;
35 | }
36 | await this.configureStartingResources?.(context);
37 | await this.logStartingResources(context);
38 | }
39 |
40 | public async prompt(): Promise {
41 | // Don't prompt, just use to log starting resources
42 | }
43 |
44 | public shouldPrompt(): boolean {
45 | return false;
46 | }
47 |
48 | protected async logStartingResources(context: T): Promise {
49 | if (context.resourceGroup) {
50 | prependOrInsertAfterLastInfoChild(context,
51 | new ActivityChildItem({
52 | contextValue: createContextValue([startingResourcesContext, activityInfoContext]),
53 | label: localize('useResourceGroup', 'Use resource group "{0}"', context.resourceGroup.name),
54 | activityType: ActivityChildType.Info,
55 | iconPath: activityInfoIcon
56 | }) as ActivityInfoChild
57 | );
58 | ext.outputChannel.appendLog(localize('usingResourceGroup', 'Using resource group "{0}".', context.resourceGroup.name));
59 | }
60 |
61 | if (LocationListStep.hasLocation(context)) {
62 | const location: string = (await LocationListStep.getLocation(context)).name;
63 | ext.outputChannel.appendLog(localize('usingLocation', 'Using location: "{0}".', location));
64 | }
65 |
66 | this.hasLogged = true;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/utils/quickPickUtils.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzExtFsExtra, IActionContext, IAzureQuickPickItem } from '@microsoft/vscode-azext-utils';
7 | import * as path from 'path';
8 | import * as vscode from 'vscode';
9 | import { extensionPrefix } from '../constants';
10 |
11 | export async function showWorkspaceFoldersQuickPick(placeHolderString: string, context: IActionContext, subPathSetting: string | undefined): Promise {
12 | const folderQuickPickItems: IAzureQuickPickItem[] = [];
13 | if (vscode.workspace.workspaceFolders) {
14 | for (const workspaceFolder of vscode.workspace.workspaceFolders) {
15 | {
16 | let fsPath: string = workspaceFolder.uri.fsPath;
17 | if (subPathSetting) {
18 | const subpath: string | undefined = vscode.workspace.getConfiguration(extensionPrefix, workspaceFolder.uri).get(subPathSetting);
19 | if (subpath) {
20 | fsPath = path.join(fsPath, subpath);
21 | }
22 | }
23 |
24 | folderQuickPickItems.push({
25 | label: path.basename(fsPath),
26 | description: fsPath,
27 | data: fsPath
28 | });
29 |
30 | // If the workspace has any of build, dist, or out, show those as well
31 | const buildDefaultPaths = ["build", "dist", "out"];
32 | for (const defaultPath of buildDefaultPaths) {
33 | const buildPath: string = path.join(fsPath, defaultPath);
34 | if (await AzExtFsExtra.pathExists(buildPath)) {
35 | folderQuickPickItems.push({
36 | label: path.basename(buildPath),
37 | description: buildPath,
38 | data: buildPath
39 | });
40 | }
41 | }
42 |
43 | }
44 | }
45 | }
46 |
47 | folderQuickPickItems.unshift({ label: '$(file-directory) Browse...', description: '', data: undefined });
48 |
49 | const folderQuickPickOption = { placeHolder: placeHolderString };
50 | context.telemetry.properties.cancelStep = 'showWorkspaceFolders';
51 | const pickedItem = await context.ui.showQuickPick(folderQuickPickItems, folderQuickPickOption);
52 |
53 | if (!pickedItem.data) {
54 | context.telemetry.properties.cancelStep = 'showWorkspaceFoldersBrowse';
55 | const browseResult = await context.ui.showOpenDialog({
56 | canSelectFiles: false,
57 | canSelectFolders: true,
58 | canSelectMany: false,
59 | defaultUri: vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri : undefined
60 | });
61 |
62 | context.telemetry.properties.cancelStep = undefined;
63 | return browseResult[0].fsPath;
64 | } else {
65 | context.telemetry.properties.cancelStep = undefined;
66 | return pickedItem.data;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/commands/uploadFolder/UploadFolderStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardExecuteStep, IParsedError, nonNullValue, parseError } from "@microsoft/vscode-azext-utils";
7 | import * as vscode from "vscode";
8 | import { NotificationProgress } from '../../constants';
9 | import { refreshTreeItem } from "../../tree/refreshTreeItem";
10 | import { isAzCopyError } from "../../utils/errorUtils";
11 | import { checkCanUpload, convertLocalPathToRemotePath, getUploadingMessageWithSource, showUploadSuccessMessage, uploadLocalFolder } from "../../utils/uploadUtils";
12 | import { IAzCopyResolution } from "../transfers/azCopy/IAzCopyResolution";
13 | import { IUploadFolderWizardContext } from "./IUploadFolderWizardContext";
14 |
15 | export class UploadFolderStep extends AzureWizardExecuteStep {
16 | public priority: number = 100;
17 |
18 | public constructor(private readonly cancellationToken?: vscode.CancellationToken) {
19 | super();
20 | }
21 |
22 | public async execute(context: Required, notificationProgress: NotificationProgress): Promise {
23 | const sourcePath: string = context.uri.fsPath;
24 | const destPath: string = convertLocalPathToRemotePath(sourcePath, context.destinationDirectory);
25 | const resolution: IAzCopyResolution = { errors: [] };
26 | if (!context.calledFromUploadToAzureStorage && !(await checkCanUpload(context, destPath, { choice: undefined }, context.treeItem))) {
27 | // Don't upload this folder
28 | context.resolution = resolution;
29 | return;
30 | }
31 |
32 | try {
33 | if (notificationProgress && this.cancellationToken) {
34 | await uploadLocalFolder(context, context.treeItem, sourcePath, destPath, notificationProgress, this.cancellationToken, destPath);
35 | } else {
36 | const title: string = getUploadingMessageWithSource(sourcePath, context.treeItem.label);
37 | await vscode.window.withProgress({ cancellable: true, location: vscode.ProgressLocation.Notification, title }, async (newNotificationProgress, newCancellationToken) => {
38 | await uploadLocalFolder(context, nonNullValue(context.treeItem), sourcePath, nonNullValue(destPath), newNotificationProgress, newCancellationToken, destPath);
39 | });
40 | }
41 | } catch (error) {
42 | const parsedError: IParsedError = parseError(error);
43 | if (context.calledFromUploadToAzureStorage && isAzCopyError(parsedError)) {
44 | // `uploadToAzureStorage` will deal with this error
45 | resolution.errors.push(parsedError);
46 | } else {
47 | throw error;
48 | }
49 | }
50 |
51 | if (!context.calledFromUploadToAzureStorage) {
52 | showUploadSuccessMessage(context.treeItem.label);
53 | }
54 |
55 | await refreshTreeItem(context, context.treeItem);
56 | context.resolution = resolution;
57 | }
58 |
59 | public shouldExecute(): boolean {
60 | return true;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/commands/openInFileExplorer/OpenTreeItemStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzureWizardExecuteStep } from '@microsoft/vscode-azext-utils';
7 | import { commands, Uri, workspace, WorkspaceFolder } from 'vscode';
8 | import { AzureStorageFS } from '../../AzureStorageFS';
9 | import { BlobContainerFS } from '../../BlobContainerFS';
10 | import { nonNullProp } from "../../utils/nonNull";
11 | import { IOpenInFileExplorerWizardContext } from './IOpenInFileExplorerWizardContext';
12 |
13 | export class OpenTreeItemStep extends AzureWizardExecuteStep {
14 | public priority: number = 250;
15 | public hideStepCount: boolean = true;
16 |
17 |
18 | public async execute(context: IOpenInFileExplorerWizardContext): Promise {
19 | const openFolders: readonly WorkspaceFolder[] = workspace.workspaceFolders || [];
20 | if (context.openBehavior === 'AddToWorkspace' && openFolders.length === 0) {
21 | // no point in adding to an empty workspace
22 | context.openBehavior = 'OpenInCurrentWindow';
23 | }
24 |
25 | const treeItem = nonNullProp(context, 'treeItem');
26 |
27 | if (!AzureStorageFS.isAttachedAccount(treeItem)) {
28 | const storageAccountId = treeItem.root.storageAccountId;
29 | const serviceType = 'container' in treeItem ? "blob" : "fileShare";
30 | const containerName = 'container' in treeItem ? treeItem.container.name : treeItem.shareName;
31 | let uriByService: Uri;
32 | if (serviceType === "blob") {
33 | uriByService = BlobContainerFS.constructUri(containerName, storageAccountId);
34 | } else {
35 | // @todo: Use static methods from FileShareFS
36 | uriByService = Uri.parse(`azurestorage:///${containerName}?resourceId=${storageAccountId}&name=${containerName}`);
37 | }
38 |
39 | if (context.openBehavior === 'AddToWorkspace') {
40 | workspace.updateWorkspaceFolders(openFolders.length, 0, { uri: uriByService });
41 | await commands.executeCommand('workbench.view.explorer');
42 | } else {
43 | await commands.executeCommand('vscode.openFolder', uriByService, context.openBehavior === 'OpenInNewWindow' /* forceNewWindow */);
44 | }
45 | } else {
46 | // @todo: Support attached accounts in BlobContainerFS
47 | const uri = AzureStorageFS.idToUri(nonNullProp(context, 'treeItem').fullId);
48 | if (context.openBehavior === 'AddToWorkspace') {
49 | // @todo: Test if this should the BlobContainerFS uri or the original uri
50 | workspace.updateWorkspaceFolders(openFolders.length, 0, { uri: uri });
51 | await commands.executeCommand('workbench.view.explorer');
52 | } else {
53 | await commands.executeCommand('vscode.openFolder', uri, context.openBehavior === 'OpenInNewWindow' /* forceNewWindow */);
54 | }
55 | }
56 | }
57 |
58 | public shouldExecute(context: IOpenInFileExplorerWizardContext): boolean {
59 | return !!context.openBehavior && context.openBehavior !== 'AlreadyOpen' && context.openBehavior !== 'DontOpen';
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/tree/createWizard/StaticWebsiteConfigureStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import type { BlobServiceProperties } from "@azure/storage-blob";
7 |
8 | import { AzExtTreeItem, AzureWizardExecuteStepWithActivityOutput } from "@microsoft/vscode-azext-utils";
9 | import { NotificationProgress } from '../../constants';
10 | import { ext } from '../../extensionVariables';
11 | import { localize } from '../../utils/localize';
12 | import { StorageAccountTreeItem } from "../StorageAccountTreeItem";
13 | import { IStaticWebsiteConfigWizardContext } from "./IStaticWebsiteConfigWizardContext";
14 | import { IStorageAccountTreeItemCreateContext } from "./StorageAccountTreeItemCreateStep";
15 |
16 | export class StaticWebsiteConfigureStep extends AzureWizardExecuteStepWithActivityOutput {
17 | public priority: number = 200;
18 | public stepName: string = 'staticWebsiteConfigureStep';
19 | public accountTreeItem: StorageAccountTreeItem | undefined;
20 |
21 | private outputLogMessage: string;
22 | private previouslyEnabled: boolean | undefined;
23 |
24 | protected getOutputLogSuccess = (): string => this.outputLogMessage;
25 | protected getOutputLogFail = (): string => this.outputLogMessage;
26 | protected getTreeItemLabel(context: T): string {
27 | this.accountTreeItem ??= context.accountTreeItem;
28 |
29 | return this.previouslyEnabled ?
30 | localize('updateStaticHostingConfig', 'Update static website hosting config for storage account "{0}"', this.accountTreeItem.label) :
31 | localize('enableStaticHostingConfig', 'Enable static website hosting for storage account "{0}"', this.accountTreeItem.label);
32 | }
33 |
34 | public constructor(accountTreeItem?: StorageAccountTreeItem, previouslyEnabled?: boolean) {
35 | super();
36 | this.accountTreeItem = accountTreeItem;
37 | this.previouslyEnabled = previouslyEnabled;
38 | }
39 |
40 | public async execute(wizardContext: T, progress: NotificationProgress): Promise {
41 | this.accountTreeItem = this.accountTreeItem || wizardContext.accountTreeItem;
42 |
43 | progress.report({ message: localize('configuringStaticWebsiteHosting', `Configuring static website hosing for storage account "${this.accountTreeItem.label}"...`) });
44 |
45 | const newStatus: BlobServiceProperties = {
46 | staticWebsite: {
47 | enabled: true,
48 | indexDocument: wizardContext.indexDocument,
49 | errorDocument404Path: wizardContext.errorDocument404Path
50 | }
51 | };
52 |
53 | await this.accountTreeItem.setWebsiteHostingProperties(newStatus);
54 |
55 | this.outputLogMessage = this.previouslyEnabled ?
56 | localize('staticWebsiteHostingConfigurationUpdated', 'Static website hosting configuration updated for storage account "{0}".', this.accountTreeItem.label) :
57 | localize('storageAccountHasBeenEnabledForStaticWebsiteHosting', 'The storage account "{0}" has been enabled for static website hosting.', this.accountTreeItem.label);
58 | this.outputLogMessage += localize('indexDocumentAndErrorDocument', ' Index document: "{0}", 404 error document: "{1}"', wizardContext.indexDocument, wizardContext.errorDocument404Path);
59 |
60 | if (newStatus.staticWebsite && this.previouslyEnabled !== newStatus.staticWebsite.enabled) {
61 | await ext.rgApi.appResourceTree.refresh(wizardContext, this.accountTreeItem as unknown as AzExtTreeItem);
62 | }
63 | }
64 |
65 | public shouldExecute(wizardContext: T): boolean {
66 | return wizardContext.enableStaticWebsite;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/commands/deleteBlobDirectory/DeleteBlobDirectoryStep.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { AzExtTreeItem, AzureWizardExecuteStep, nonNullProp, parseError } from "@microsoft/vscode-azext-utils";
7 | import { MessageItem, Progress, window } from "vscode";
8 | import { ext } from "../../extensionVariables";
9 | import { BlobDirectoryTreeItem } from "../../tree/blob/BlobDirectoryTreeItem";
10 | import { BlobTreeItem, ISuppressMessageContext } from "../../tree/blob/BlobTreeItem";
11 | import { localize } from "../../utils/localize";
12 | import { IDeleteBlobDirectoryWizardContext } from "./IDeleteBlobDirectoryWizardContext";
13 |
14 | export class DeleteBlobDirectoryStep extends AzureWizardExecuteStep {
15 | public priority: number = 100;
16 |
17 | public async execute(wizardContext: IDeleteBlobDirectoryWizardContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined; }>): Promise {
18 | const directoryName = nonNullProp(wizardContext, 'dirName');
19 | const deletingBlobDirectory: string = localize('deletingDirectory', 'Deleting directory "{0}"...', directoryName);
20 | ext.outputChannel.appendLog(deletingBlobDirectory);
21 | progress.report({ message: deletingBlobDirectory });
22 | const errors: boolean = await this.deleteFolder(wizardContext);
23 | if (errors) {
24 | ext.outputChannel.appendLog('Please refresh the viewlet to see the changes made.');
25 |
26 | const viewOutput: MessageItem = { title: 'View Errors' };
27 | const errorMessage: string = `Errors occurred when deleting "${directoryName}".`;
28 | void window.showWarningMessage(errorMessage, viewOutput).then((result: MessageItem | undefined) => {
29 | if (result === viewOutput) {
30 | ext.outputChannel.show();
31 | }
32 | });
33 |
34 | throw new Error(`Errors occurred when deleting "${directoryName}".`);
35 | }
36 | const deleteSuccessful: string = localize('successfullyDeletedBlobDirectory', 'Successfully deleted directory "{0}".', directoryName);
37 | ext.outputChannel.appendLog(deleteSuccessful);
38 |
39 | if (!wizardContext.suppressNotification) {
40 | void window.showInformationMessage(deleteSuccessful);
41 | }
42 | }
43 |
44 | public shouldExecute(): boolean {
45 | return true;
46 | }
47 |
48 | private async deleteFolder(context: IDeleteBlobDirectoryWizardContext): Promise {
49 | const blobDirectory = nonNullProp(context, 'blobDirectory');
50 |
51 | const dirPaths: BlobDirectoryTreeItem[] = [];
52 | // eslint-disable-next-line @typescript-eslint/no-this-alias
53 | let dirPath: BlobDirectoryTreeItem | undefined = blobDirectory;
54 | let errors: boolean = false;
55 |
56 | while (dirPath) {
57 | const children: AzExtTreeItem[] = await dirPath.loadAllChildren(context);
58 | for (const child of children) {
59 | if (child instanceof BlobTreeItem) {
60 | try {
61 | await child.deleteTreeItemImpl({ ...context, suppressMessage: true });
62 | } catch (error) {
63 | ext.outputChannel.appendLog(`Cannot delete ${child.blobPath}. ${parseError(error).message}`);
64 | errors = true;
65 | }
66 | } else if (child instanceof BlobDirectoryTreeItem) {
67 | dirPaths.push(child);
68 | }
69 | }
70 |
71 | dirPath = dirPaths.pop();
72 | }
73 | return errors;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/tree/fileShare/FileTreeItem.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import type { AccountSASSignatureValues, ShareFileClient } from '@azure/storage-file-share';
7 |
8 | import { polyfill } from '../../polyfill.worker';
9 | polyfill();
10 |
11 | import { AccountSASPermissions } from '@azure/storage-file-share';
12 |
13 | import { AzExtTreeItem, DialogResponses, IActionContext, TreeItemIconPath, UserCancelledError } from '@microsoft/vscode-azext-utils';
14 | import { posix } from 'path';
15 | import * as vscode from 'vscode';
16 | import { MessageItem, window } from 'vscode';
17 | import { AzureStorageFS } from "../../AzureStorageFS";
18 | import { threeDaysInMS } from '../../constants';
19 | import { copyAndShowToast } from '../../utils/copyAndShowToast';
20 | import { createFileClient, deleteFile } from '../../utils/fileUtils';
21 | import { ICopyUrl } from '../ICopyUrl';
22 | import { IStorageRoot } from '../IStorageRoot';
23 | import { ITransferSrcOrDstTreeItem } from '../ITransferSrcOrDstTreeItem';
24 | import { DirectoryTreeItem, IDirectoryDeleteContext } from "./DirectoryTreeItem";
25 | import { FileShareTreeItem } from './FileShareTreeItem';
26 |
27 | export class FileTreeItem extends AzExtTreeItem implements ICopyUrl, ITransferSrcOrDstTreeItem {
28 | public parent: FileShareTreeItem | DirectoryTreeItem;
29 | constructor(
30 | parent: FileShareTreeItem | DirectoryTreeItem,
31 | public readonly fileName: string,
32 | public readonly directoryPath: string,
33 | public readonly shareName: string,
34 | public readonly resourceUri: string) {
35 | super(parent);
36 | this.commandId = 'azureStorage.editFile';
37 | }
38 |
39 | public label: string = this.fileName;
40 | public static contextValue: string = 'azureFile';
41 | public contextValue: string = FileTreeItem.contextValue;
42 |
43 | public get root(): IStorageRoot {
44 | return this.parent.root;
45 | }
46 |
47 | public get remoteFilePath(): string {
48 | return posix.join(this.directoryPath, this.fileName);
49 | }
50 |
51 | public get iconPath(): TreeItemIconPath {
52 | return new vscode.ThemeIcon('file');
53 | }
54 |
55 | public get transferSasToken(): string {
56 | const accountSASSignatureValues: AccountSASSignatureValues = {
57 | expiresOn: new Date(Date.now() + threeDaysInMS),
58 | permissions: AccountSASPermissions.parse("rwl"), // read, write, list
59 | services: 'f', // file
60 | resourceTypes: 'co' // container, object
61 | };
62 | return this.root.generateSasToken(accountSASSignatureValues);
63 | }
64 |
65 | public async copyUrl(): Promise {
66 | const fileClient: ShareFileClient = await createFileClient(this.root, this.shareName, this.directoryPath, this.fileName);
67 | const url = fileClient.url;
68 | await copyAndShowToast(url, 'File URL');
69 | }
70 |
71 | public async deleteTreeItemImpl(context: IActionContext & IDirectoryDeleteContext): Promise {
72 | let result: MessageItem | undefined;
73 | if (!context.suppressMessage) {
74 | const message: string = `Are you sure you want to delete the file '${this.label}'?`;
75 | result = await window.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
76 | } else {
77 | result = DialogResponses.deleteResponse;
78 | }
79 |
80 | if (result === DialogResponses.deleteResponse) {
81 | await deleteFile(this.directoryPath, this.fileName, this.shareName, this.root);
82 | } else {
83 | throw new UserCancelledError();
84 | }
85 |
86 | AzureStorageFS.fireDeleteEvent(context, this);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------