mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-05-16 13:25:06 +01:00
wip for private endpoint support
This commit is contained in:
parent
c62e89228e
commit
80edb66fbf
@ -5,12 +5,13 @@ import "xterm/css/xterm.css";
|
|||||||
import { TerminalKind } from "../../../Contracts/ViewModels";
|
import { TerminalKind } from "../../../Contracts/ViewModels";
|
||||||
import { startCloudShellTerminal } from "./UseTerminal";
|
import { startCloudShellTerminal } from "./UseTerminal";
|
||||||
|
|
||||||
|
|
||||||
export interface CloudShellTerminalProps {
|
export interface CloudShellTerminalProps {
|
||||||
shellType: TerminalKind;
|
shellType: TerminalKind;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CloudShellTerminalComponent: React.FC<CloudShellTerminalProps> = ({
|
export const CloudShellTerminalComponent: React.FC<CloudShellTerminalProps> = ({
|
||||||
shellType
|
shellType
|
||||||
}: CloudShellTerminalProps) => {
|
}: CloudShellTerminalProps) => {
|
||||||
const terminalRef = useRef(null); // Reference for terminal container
|
const terminalRef = useRef(null); // Reference for terminal container
|
||||||
const xtermRef = useRef(null); // Reference for XTerm instance
|
const xtermRef = useRef(null); // Reference for XTerm instance
|
||||||
|
@ -2,12 +2,45 @@
|
|||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ApiVersionsConfig, ResourceType } from "Explorer/Tabs/CloudShellTab/DataModels";
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { configContext } from "../../../ConfigContext";
|
import { configContext } from "../../../ConfigContext";
|
||||||
|
import { TerminalKind } from "../../../Contracts/ViewModels";
|
||||||
import { userContext } from '../../../UserContext';
|
import { userContext } from '../../../UserContext';
|
||||||
import { armRequest } from "../../../Utils/arm/request";
|
import { armRequest } from "../../../Utils/arm/request";
|
||||||
import { Authorization, ConnectTerminalResponse, NetworkType, OsType, ProvisionConsoleResponse, SessionType, Settings, ShellType } from "./DataModels";
|
import { Authorization, ConnectTerminalResponse, NetworkType, OsType, ProvisionConsoleResponse, SessionType, Settings, ShellType } from "./DataModels";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API version configuration by terminal type and resource type
|
||||||
|
*/
|
||||||
|
const API_VERSIONS : ApiVersionsConfig = {
|
||||||
|
// Default version for fallback
|
||||||
|
DEFAULT: "2024-07-01",
|
||||||
|
|
||||||
|
// Resource type specific defaults
|
||||||
|
RESOURCE_DEFAULTS: {
|
||||||
|
[ResourceType.NETWORK]: "2023-05-01",
|
||||||
|
[ResourceType.DATABASE]: "2024-07-01",
|
||||||
|
[ResourceType.VNET]: "2023-05-01",
|
||||||
|
[ResourceType.SUBNET]: "2023-05-01",
|
||||||
|
[ResourceType.RELAY]: "2024-01-01",
|
||||||
|
[ResourceType.ROLE]: "2022-04-01"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Shell-type specific versions with resource overrides
|
||||||
|
SHELL_TYPES: {
|
||||||
|
[TerminalKind.Mongo]: {
|
||||||
|
[ResourceType.DATABASE]: "2024-11-15"
|
||||||
|
},
|
||||||
|
[TerminalKind.VCoreMongo]: {
|
||||||
|
[ResourceType.DATABASE]: "2024-07-01"
|
||||||
|
},
|
||||||
|
[TerminalKind.Cassandra]: {
|
||||||
|
[ResourceType.DATABASE]: "2024-11-15"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const validateUserSettings = (userSettings: Settings) => {
|
export const validateUserSettings = (userSettings: Settings) => {
|
||||||
if (userSettings.sessionType !== SessionType.Ephemeral && userSettings.osType !== OsType.Linux) {
|
if (userSettings.sessionType !== SessionType.Ephemeral && userSettings.osType !== OsType.Linux) {
|
||||||
return false;
|
return false;
|
||||||
@ -16,6 +49,47 @@ export const validateUserSettings = (userSettings: Settings) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Current shell type context
|
||||||
|
let currentShellType: TerminalKind | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the active shell type to determine API version
|
||||||
|
*/
|
||||||
|
export const setShellType = (shellType: TerminalKind): void => {
|
||||||
|
currentShellType = shellType;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the appropriate API version based on shell type and resource type
|
||||||
|
* Uses a cascading fallback approach for maximum flexibility
|
||||||
|
*/
|
||||||
|
export const getApiVersion = (resourceType?: ResourceType): string => {
|
||||||
|
// If no shell type is set, fallback to resource default or global default
|
||||||
|
if (!currentShellType) {
|
||||||
|
return resourceType ?
|
||||||
|
(API_VERSIONS.RESOURCE_DEFAULTS[resourceType] || API_VERSIONS.DEFAULT) :
|
||||||
|
API_VERSIONS.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shell type is set, try to get specific version in this priority:
|
||||||
|
// 1. Shell-specific + resource-specific
|
||||||
|
if (resourceType &&
|
||||||
|
API_VERSIONS.SHELL_TYPES[currentShellType]) {
|
||||||
|
const shellTypeConfig = API_VERSIONS.SHELL_TYPES[currentShellType];
|
||||||
|
if (resourceType in shellTypeConfig) {
|
||||||
|
return shellTypeConfig[resourceType] as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Resource-specific default
|
||||||
|
if (resourceType && resourceType in API_VERSIONS.RESOURCE_DEFAULTS) {
|
||||||
|
return API_VERSIONS.RESOURCE_DEFAULTS[resourceType];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Global default
|
||||||
|
return API_VERSIONS.DEFAULT;
|
||||||
|
};
|
||||||
|
|
||||||
export const getUserRegion = async (subscriptionId: string, resourceGroup: string, accountName: string) => {
|
export const getUserRegion = async (subscriptionId: string, resourceGroup: string, accountName: string) => {
|
||||||
return await armRequest({
|
return await armRequest({
|
||||||
host: configContext.ARM_ENDPOINT,
|
host: configContext.ARM_ENDPOINT,
|
||||||
@ -26,25 +100,6 @@ export const getUserRegion = async (subscriptionId: string, resourceGroup: strin
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function GetARMCall<T>(path: string, apiVersion: string = "2024-07-01"): Promise<T> {
|
|
||||||
return await armRequest<T>({
|
|
||||||
host: configContext.ARM_ENDPOINT,
|
|
||||||
path: path,
|
|
||||||
method: "GET",
|
|
||||||
apiVersion: apiVersion
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function PutARMCall<T>(path: string, request: object, apiVersion: string = "2024-07-01"): Promise<T> {
|
|
||||||
return await armRequest<T>({
|
|
||||||
host: configContext.ARM_ENDPOINT,
|
|
||||||
path: path,
|
|
||||||
method: "PUT",
|
|
||||||
apiVersion: apiVersion,
|
|
||||||
body: request
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteUserSettings = async (): Promise<void> => {
|
export const deleteUserSettings = async (): Promise<void> => {
|
||||||
await armRequest<void>({
|
await armRequest<void>({
|
||||||
host: configContext.ARM_ENDPOINT,
|
host: configContext.ARM_ENDPOINT,
|
||||||
@ -173,5 +228,93 @@ export const getNormalizedRegion = (region: string, defaultCloudshellRegion: str
|
|||||||
|
|
||||||
const normalizedRegion = regionMap[region.toLowerCase()] || region;
|
const normalizedRegion = regionMap[region.toLowerCase()] || region;
|
||||||
return validCloudShellRegions.has(normalizedRegion.toLowerCase()) ? normalizedRegion : defaultCloudshellRegion;
|
return validCloudShellRegions.has(normalizedRegion.toLowerCase()) ? normalizedRegion : defaultCloudshellRegion;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export async function getNetworkProfileInfo<T>(networkProfileResourceId: string, apiVersionOverride?: string): Promise<T> {
|
||||||
|
const apiVersion = apiVersionOverride || getApiVersion(ResourceType.NETWORK);
|
||||||
|
return await GetARMCall<T>(networkProfileResourceId, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAccountDetails<T>(databaseAccountId: string, apiVersionOverride?: string): Promise<T> {
|
||||||
|
const apiVersion = apiVersionOverride || getApiVersion(ResourceType.DATABASE);
|
||||||
|
return await GetARMCall<T>(databaseAccountId, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getVnetInformation<T>(vnetId: string, apiVersionOverride?: string): Promise<T> {
|
||||||
|
const apiVersion = apiVersionOverride || getApiVersion(ResourceType.VNET);
|
||||||
|
return await GetARMCall<T>(vnetId, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSubnetInformation<T>(subnetId: string, apiVersionOverride?: string): Promise<T> {
|
||||||
|
const apiVersion = apiVersionOverride || getApiVersion(ResourceType.SUBNET);
|
||||||
|
return await GetARMCall<T>(subnetId, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateSubnetInformation<T>(subnetId: string, request: object, apiVersionOverride?: string): Promise<T> {
|
||||||
|
const apiVersion = apiVersionOverride || getApiVersion(ResourceType.SUBNET);
|
||||||
|
return await PutARMCall(subnetId, request, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateDatabaseAccount<T>(accountId: string, request: object, apiVersionOverride?: string): Promise<T> {
|
||||||
|
const apiVersion = apiVersionOverride || getApiVersion(ResourceType.DATABASE);
|
||||||
|
return await PutARMCall(accountId, request, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDatabaseOperations<T>(accountId: string, apiVersionOverride?: string): Promise<T> {
|
||||||
|
const apiVersion = apiVersionOverride || getApiVersion(ResourceType.DATABASE);
|
||||||
|
return await GetARMCall<T>(`${accountId}/operations`, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateVnet<T>(vnetId: string, request: object, apiVersionOverride?: string) {
|
||||||
|
const apiVersion = apiVersionOverride || getApiVersion(ResourceType.VNET);
|
||||||
|
return await PutARMCall<T>(vnetId, request, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getVnet<T>(vnetId: string, apiVersionOverride?: string): Promise<T> {
|
||||||
|
const apiVersion = apiVersionOverride || getApiVersion(ResourceType.VNET);
|
||||||
|
return await GetARMCall<T>(vnetId, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createNetworkProfile<T>(networkProfileId: string, request: object, apiVersionOverride?: string): Promise<T> {
|
||||||
|
const apiVersion = apiVersionOverride || getApiVersion(ResourceType.NETWORK);
|
||||||
|
return await PutARMCall<T>(networkProfileId, request, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createRelay<T>(relayId: string, request: object, apiVersionOverride?: string): Promise<T> {
|
||||||
|
const apiVersion = apiVersionOverride || getApiVersion(ResourceType.RELAY);
|
||||||
|
return await PutARMCall<T>(relayId, request, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getRelay<T>(relayId: string, apiVersionOverride?: string): Promise<T> {
|
||||||
|
const apiVersion = apiVersionOverride || getApiVersion(ResourceType.RELAY);
|
||||||
|
return await GetARMCall<T>(relayId, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createRoleOnNetworkProfile<T>(roleid: string, request: object, apiVersionOverride?: string): Promise<T> {
|
||||||
|
const apiVersion = apiVersionOverride || getApiVersion(ResourceType.ROLE);
|
||||||
|
return await PutARMCall<T>(roleid, request, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createRoleOnRelay<T>(roleid: string, request: object, apiVersionOverride?: string): Promise<T> {
|
||||||
|
const apiVersion = apiVersionOverride || getApiVersion(ResourceType.ROLE);
|
||||||
|
return await PutARMCall<T>(roleid, request, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GetARMCall<T>(path: string, apiVersion: string = API_VERSIONS.DEFAULT): Promise<T> {
|
||||||
|
return await armRequest<T>({
|
||||||
|
host: configContext.ARM_ENDPOINT,
|
||||||
|
path: path,
|
||||||
|
method: "GET",
|
||||||
|
apiVersion: apiVersion
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function PutARMCall<T>(path: string, request: object, apiVersion: string = API_VERSIONS.DEFAULT): Promise<T> {
|
||||||
|
return await armRequest<T>({
|
||||||
|
host: configContext.ARM_ENDPOINT,
|
||||||
|
path: path,
|
||||||
|
method: "PUT",
|
||||||
|
apiVersion: apiVersion,
|
||||||
|
body: request
|
||||||
|
});
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
import { TerminalKind } from "../../../Contracts/ViewModels";
|
||||||
|
|
||||||
export const enum OsType {
|
export const enum OsType {
|
||||||
Linux = "linux",
|
Linux = "linux",
|
||||||
@ -150,5 +150,36 @@ export type RelayNamespaceResponse = {
|
|||||||
value: RelayNamespace[];
|
value: RelayNamespace[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resource types for API versioning
|
||||||
|
*/
|
||||||
|
export enum ResourceType {
|
||||||
|
NETWORK = "NETWORK",
|
||||||
|
DATABASE = "DATABASE",
|
||||||
|
VNET = "VNET",
|
||||||
|
SUBNET = "SUBNET",
|
||||||
|
RELAY = "RELAY",
|
||||||
|
ROLE = "ROLE"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type definition for API_VERSIONS configuration
|
||||||
|
export type ApiVersionsConfig = {
|
||||||
|
// Global default API version
|
||||||
|
DEFAULT: string;
|
||||||
|
|
||||||
|
// Resource-specific default API versions
|
||||||
|
RESOURCE_DEFAULTS: {
|
||||||
|
[key in ResourceType]: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Shell-type specific configurations
|
||||||
|
SHELL_TYPES: {
|
||||||
|
[key in TerminalKind]?: {
|
||||||
|
// Resource-specific overrides for this shell type
|
||||||
|
[key in ResourceType]?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
29
src/Explorer/Tabs/CloudShellTab/LogFormatter.tsx
Normal file
29
src/Explorer/Tabs/CloudShellTab/LogFormatter.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Standardized terminal logging functions for consistent formatting
|
||||||
|
*/
|
||||||
|
export const terminalLog = {
|
||||||
|
// Section headers
|
||||||
|
header: (message: string) => `\n\x1B[1;34m┌─ ${message} ${"─".repeat(Math.max(45 - message.length, 0))}\x1B[0m`,
|
||||||
|
subheader: (message: string) => `\x1B[1;36m├ ${message}\x1B[0m`,
|
||||||
|
sectionEnd: () => `\x1B[1;34m└${"─".repeat(50)}\x1B[0m\n`,
|
||||||
|
|
||||||
|
// Status messages
|
||||||
|
success: (message: string) => `\x1B[32m✓ ${message}\x1B[0m`,
|
||||||
|
warning: (message: string) => `\x1B[33m⚠ ${message}\x1B[0m`,
|
||||||
|
error: (message: string) => `\x1B[31m✗ ${message}\x1B[0m`,
|
||||||
|
info: (message: string) => `\x1B[34m${message}\x1B[0m`,
|
||||||
|
|
||||||
|
// Resource information
|
||||||
|
database: (message: string) => `\x1B[35m🔶 Database: ${message}\x1B[0m`,
|
||||||
|
vnet: (message: string) => `\x1B[36m🔷 Network: ${message}\x1B[0m`,
|
||||||
|
cloudshell: (message: string) => `\x1B[32m🔷 CloudShell: ${message}\x1B[0m`,
|
||||||
|
|
||||||
|
// Data formatting
|
||||||
|
item: (label: string, value: string) => ` • ${label}: \x1B[32m${value}\x1B[0m`,
|
||||||
|
progress: (operation: string, status: string) => `\x1B[34m${operation}: \x1B[36m${status}\x1B[0m`,
|
||||||
|
|
||||||
|
// User interaction
|
||||||
|
prompt: (message: string) => `\x1B[1;37m${message}\x1B[0m`,
|
||||||
|
separator: () => `\x1B[30;1m${"─".repeat(50)}\x1B[0m`
|
||||||
|
};
|
@ -7,21 +7,40 @@ import { listKeys } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { Terminal } from "xterm";
|
import { Terminal } from "xterm";
|
||||||
import { TerminalKind } from "../../../Contracts/ViewModels";
|
import { TerminalKind } from "../../../Contracts/ViewModels";
|
||||||
|
import {
|
||||||
|
IsPublicAccessAvailable,
|
||||||
|
hasPrivateEndpointsRestrictions,
|
||||||
|
hasVNetRestrictions
|
||||||
|
} from "Explorer/Tabs/Shared/CheckFirewallRules";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { AttachAddon } from "./AttachAddOn";
|
import { AttachAddon } from "./AttachAddOn";
|
||||||
import { getCommands } from "./Commands";
|
import { getCommands } from "./Commands";
|
||||||
import {
|
import {
|
||||||
authorizeSession,
|
authorizeSession,
|
||||||
connectTerminal,
|
connectTerminal,
|
||||||
GetARMCall,
|
createNetworkProfile,
|
||||||
|
createRelay,
|
||||||
|
createRoleOnNetworkProfile,
|
||||||
|
createRoleOnRelay,
|
||||||
|
getAccountDetails,
|
||||||
|
getDatabaseOperations,
|
||||||
|
getNetworkProfileInfo,
|
||||||
getNormalizedRegion,
|
getNormalizedRegion,
|
||||||
|
getRelay,
|
||||||
|
getSubnetInformation,
|
||||||
getUserSettings,
|
getUserSettings,
|
||||||
|
getVnet,
|
||||||
|
getVnetInformation,
|
||||||
provisionConsole,
|
provisionConsole,
|
||||||
PutARMCall,
|
|
||||||
putEphemeralUserSettings,
|
putEphemeralUserSettings,
|
||||||
registerCloudShellProvider,
|
registerCloudShellProvider,
|
||||||
|
setShellType,
|
||||||
|
updateDatabaseAccount,
|
||||||
|
updateSubnetInformation,
|
||||||
|
updateVnet,
|
||||||
verifyCloudShellProviderRegistration
|
verifyCloudShellProviderRegistration
|
||||||
} from "./Data";
|
} from "./Data";
|
||||||
|
import { terminalLog } from "./LogFormatter";
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const DEFAULT_CLOUDSHELL_REGION = "westus";
|
const DEFAULT_CLOUDSHELL_REGION = "westus";
|
||||||
@ -33,39 +52,13 @@ const DEFAULT_VNET_ADDRESS_PREFIX = "10.0.0.0/16";
|
|||||||
const DEFAULT_SUBNET_ADDRESS_PREFIX = "10.0.1.0/24";
|
const DEFAULT_SUBNET_ADDRESS_PREFIX = "10.0.1.0/24";
|
||||||
const DEFAULT_CONTAINER_INSTANCE_OID = "88536fb9-d60a-4aee-8195-041425d6e927";
|
const DEFAULT_CONTAINER_INSTANCE_OID = "88536fb9-d60a-4aee-8195-041425d6e927";
|
||||||
|
|
||||||
/**
|
|
||||||
* Standardized terminal logging functions for consistent formatting
|
|
||||||
*/
|
|
||||||
const terminalLog = {
|
|
||||||
// Section headers
|
|
||||||
header: (message: string) => `\n\x1B[1;34m┌─ ${message} ${"─".repeat(Math.max(45 - message.length, 0))}\x1B[0m`,
|
|
||||||
subheader: (message: string) => `\x1B[1;36m├ ${message}\x1B[0m`,
|
|
||||||
sectionEnd: () => `\x1B[1;34m└${"─".repeat(50)}\x1B[0m\n`,
|
|
||||||
|
|
||||||
// Status messages
|
|
||||||
success: (message: string) => `\x1B[32m✓ ${message}\x1B[0m`,
|
|
||||||
warning: (message: string) => `\x1B[33m⚠ ${message}\x1B[0m`,
|
|
||||||
error: (message: string) => `\x1B[31m✗ ${message}\x1B[0m`,
|
|
||||||
info: (message: string) => `\x1B[34m${message}\x1B[0m`,
|
|
||||||
|
|
||||||
// Resource information
|
|
||||||
database: (message: string) => `\x1B[35m🔶 Database: ${message}\x1B[0m`,
|
|
||||||
vnet: (message: string) => `\x1B[36m🔷 Network: ${message}\x1B[0m`,
|
|
||||||
cloudshell: (message: string) => `\x1B[32m🔷 CloudShell: ${message}\x1B[0m`,
|
|
||||||
|
|
||||||
// Data formatting
|
|
||||||
item: (label: string, value: string) => ` • ${label}: \x1B[32m${value}\x1B[0m`,
|
|
||||||
progress: (operation: string, status: string) => `\x1B[34m${operation}: \x1B[36m${status}\x1B[0m`,
|
|
||||||
|
|
||||||
// User interaction
|
|
||||||
prompt: (message: string) => `\x1B[1;37m${message}\x1B[0m`,
|
|
||||||
separator: () => `\x1B[30;1m${"─".repeat(50)}\x1B[0m`
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main function to start a CloudShell terminal
|
* Main function to start a CloudShell terminal
|
||||||
*/
|
*/
|
||||||
export const startCloudShellTerminal = async (terminal: Terminal, shellType: TerminalKind) => {
|
export const startCloudShellTerminal = async (terminal: Terminal, shellType: TerminalKind) => {
|
||||||
|
// Set the shell type to use the appropriate API version for all calls
|
||||||
|
setShellType(shellType);
|
||||||
|
|
||||||
terminal.writeln(terminalLog.header("Initializing Azure CloudShell"));
|
terminal.writeln(terminalLog.header("Initializing Azure CloudShell"));
|
||||||
await ensureCloudShellProviderRegistered(terminal);
|
await ensureCloudShellProviderRegistered(terminal);
|
||||||
|
|
||||||
@ -77,57 +70,72 @@ export const startCloudShellTerminal = async (terminal: Terminal, shellType: Ter
|
|||||||
return {}; // Exit if user declined
|
return {}; // Exit if user declined
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check database network restrictions
|
const isAllPublicAccessEnabled = await IsPublicAccessAvailable(shellType);
|
||||||
const hasNetworkRestrictions = hasDatabaseNetworkRestrictions();
|
|
||||||
|
|
||||||
let settings: Settings | undefined;
|
let settings: Settings | undefined;
|
||||||
let vNetSettings: VnetSettings | undefined;
|
let cloudShellVnetSettings: VnetSettings | undefined;
|
||||||
let finalVNetSettings: VnetSettings | {};
|
let finalVNetSettings: VnetSettings | {};
|
||||||
|
|
||||||
if (hasNetworkRestrictions) {
|
if (!isAllPublicAccessEnabled) {
|
||||||
// Fetch and process user settings for restricted networks
|
// Fetch and process user settings for restricted networks
|
||||||
terminal.writeln(terminalLog.database("Network restrictions detected"));
|
terminal.writeln(terminalLog.database("Network restrictions detected"));
|
||||||
terminal.writeln(terminalLog.info("Loading CloudShell configuration..."));
|
terminal.writeln(terminalLog.info("Loading CloudShell configuration..."));
|
||||||
settings = await fetchUserSettings(terminal);
|
settings = await getUserSettings();
|
||||||
vNetSettings = await retrieveCloudShellVnetSettings(settings, terminal);
|
if (!settings) {
|
||||||
|
terminal.writeln(terminalLog.warning("No existing user settings found."));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cloudShellVnetSettings = await retrieveCloudShellVnetSettings(settings, terminal);
|
||||||
|
}
|
||||||
|
|
||||||
// If CloudShell has VNet settings, check with database config
|
// If CloudShell has VNet settings, check with database config
|
||||||
if (vNetSettings && vNetSettings.networkProfileResourceId) {
|
if (cloudShellVnetSettings && cloudShellVnetSettings.networkProfileResourceId) {
|
||||||
|
|
||||||
|
//TODO: askForVNetConfigConsent consent should change to support private end point
|
||||||
|
// if shell type is vcore or for any other shell as private endpoint restrictions
|
||||||
const isContinueWithSameVnet = await askForVNetConfigConsent(terminal);
|
const isContinueWithSameVnet = await askForVNetConfigConsent(terminal);
|
||||||
|
|
||||||
if(isContinueWithSameVnet) {
|
if(isContinueWithSameVnet) {
|
||||||
const isVNetInDatabaseConfig = await isCloudShellVNetInDatabaseConfig(vNetSettings, terminal);
|
// TODO: isCloudShellVNetInDatabaseConfig call should handle private endpoint check also.
|
||||||
|
// a private endpoint is associated with the vnet and subnet.
|
||||||
|
// vcore supports only private endpoint, it doesnot support vnet, dbAccount?.properties?.privateEndpointConnections doesn't have any info. ARM call might needs to make to get private endpoint info
|
||||||
|
const isVNetInDatabaseConfig = await isCloudShellVNetInDatabaseConfig(cloudShellVnetSettings, terminal);
|
||||||
|
|
||||||
if (!isVNetInDatabaseConfig) {
|
if (!isVNetInDatabaseConfig) {
|
||||||
terminal.writeln(terminalLog.warning("CloudShell VNet is not configured in database access list"));
|
terminal.writeln(terminalLog.warning("CloudShell VNet is not configured in database access list"));
|
||||||
const addToDatabase = await askToAddVNetToDatabase(terminal, vNetSettings);
|
// TODO: Add logic in askToAddVNetToDatabase to ask accordingly if user has private endpoint or vnet settings
|
||||||
|
const addToDatabase = await askToAddVNetToDatabase(terminal, cloudShellVnetSettings);
|
||||||
|
|
||||||
if (addToDatabase) {
|
if (addToDatabase) {
|
||||||
await addCloudShellVNetToDatabase(vNetSettings, terminal);
|
// TODO: Add a logic to add private endpoint to database if user has private endpoint settings in the database
|
||||||
|
await addCloudShellVNetToDatabase(cloudShellVnetSettings, terminal);
|
||||||
} else {
|
} else {
|
||||||
// User declined to add VNet to database, need to recreate
|
// User declined to add VNet to database, need to recreate
|
||||||
terminal.writeln(terminalLog.warning("Will configure new VNet..."));
|
terminal.writeln(terminalLog.warning("Will configure new VNet..."));
|
||||||
vNetSettings = undefined;
|
cloudShellVnetSettings = undefined;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
terminal.writeln(terminalLog.success("CloudShell VNet is already in database configuration"));
|
terminal.writeln(terminalLog.success("CloudShell VNet is already in database configuration"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
vNetSettings = undefined; // User declined to use existing VNet settings
|
cloudShellVnetSettings = undefined; // User declined to use existing VNet settings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure VNet if needed
|
if (!cloudShellVnetSettings || !cloudShellVnetSettings.networkProfileResourceId) {
|
||||||
if (!vNetSettings || !vNetSettings.networkProfileResourceId) {
|
|
||||||
terminal.writeln(terminalLog.subheader("Configuring network infrastructure"));
|
|
||||||
finalVNetSettings = await configureCloudShellVNet(terminal, resolvedRegion, vNetSettings);
|
|
||||||
|
|
||||||
// Add the new VNet to database configuration
|
//TODO: Add logic to check if the user has existing VNet or private endpoint (which would be associated with the Vnet) settings in the database with cloudshell as CloudShellDelegation,
|
||||||
|
// if yes, use that vnet to get network profile and get the existing relay and
|
||||||
|
// use that for cloudshell, else create only those resources which are required and ternminal write the existing relay and vnet settings.which it is going to use.
|
||||||
|
terminal.writeln(terminalLog.subheader("Configuring network infrastructure"));
|
||||||
|
finalVNetSettings = await configureCloudShellVNet(terminal, resolvedRegion);
|
||||||
|
|
||||||
|
// TODO: Add a logic to add private endpoint to database if user has private endpoint settings in the database
|
||||||
await addCloudShellVNetToDatabase(finalVNetSettings as VnetSettings, terminal);
|
await addCloudShellVNetToDatabase(finalVNetSettings as VnetSettings, terminal);
|
||||||
} else {
|
} else {
|
||||||
terminal.writeln(terminalLog.success("Using existing network configuration"));
|
terminal.writeln(terminalLog.success("Using existing network configuration"));
|
||||||
finalVNetSettings = vNetSettings;
|
finalVNetSettings = cloudShellVnetSettings;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
terminal.writeln(terminalLog.database("Public access enabled. Skipping VNet configuration."));
|
terminal.writeln(terminalLog.database("Public access enabled. Skipping VNet configuration."));
|
||||||
@ -144,13 +152,13 @@ export const startCloudShellTerminal = async (terminal: Terminal, shellType: Ter
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sessionDetails = await provisionCloudShellSession(resolvedRegion, terminal, finalVNetSettings);
|
sessionDetails = await provisionCloudShellSession(resolvedRegion, terminal, finalVNetSettings, isAllPublicAccessEnabled);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
terminal.writeln(terminalLog.error(err));
|
terminal.writeln(terminalLog.error(err));
|
||||||
terminal.writeln(terminalLog.error("Failed to provision in primary region"));
|
terminal.writeln(terminalLog.error("Failed to provision in primary region"));
|
||||||
terminal.writeln(terminalLog.warning(`Attempting with fallback region: ${defaultCloudShellRegion}`));
|
terminal.writeln(terminalLog.warning(`Attempting with fallback region: ${defaultCloudShellRegion}`));
|
||||||
|
|
||||||
sessionDetails = await provisionCloudShellSession(defaultCloudShellRegion, terminal, finalVNetSettings);
|
sessionDetails = await provisionCloudShellSession(defaultCloudShellRegion, terminal, finalVNetSettings, isAllPublicAccessEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sessionDetails.socketUri) {
|
if (!sessionDetails.socketUri) {
|
||||||
@ -171,13 +179,21 @@ export const startCloudShellTerminal = async (terminal: Terminal, shellType: Ter
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asks the user if they want to use existing VNet settings or create new ones
|
* Asks the user if they want to use existing network configuration (VNet or private endpoint)
|
||||||
*/
|
*/
|
||||||
const askForVNetConfigConsent = async (terminal: Terminal): Promise<boolean> => {
|
const askForVNetConfigConsent = async (terminal: Terminal, shellType: TerminalKind = null): Promise<boolean> => {
|
||||||
|
// Check if this shell type supports only private endpoints
|
||||||
|
const isPrivateEndpointOnlyShell = shellType === TerminalKind.VCoreMongo;
|
||||||
|
// Check if the database has private endpoints configured
|
||||||
|
const hasPrivateEndpoints = hasPrivateEndpointsRestrictions();
|
||||||
|
|
||||||
|
// Determine which network type to mention based on shell type and database configuration
|
||||||
|
const networkType = isPrivateEndpointOnlyShell || hasPrivateEndpoints ? "private endpoint" : "network";
|
||||||
|
|
||||||
// Ask for consent
|
// Ask for consent
|
||||||
terminal.writeln("");
|
terminal.writeln("");
|
||||||
terminal.writeln(terminalLog.prompt("Use this existing network configuration? (Y/N)"));
|
terminal.writeln(terminalLog.prompt(`Use this existing ${networkType} configuration? (Y/N)`));
|
||||||
terminal.writeln(terminalLog.info("Answering 'N' will configure a new network for CloudShell"));
|
terminal.writeln(terminalLog.info(`Answering 'N' will configure a new ${networkType} for CloudShell`));
|
||||||
|
|
||||||
return new Promise<boolean>((resolve) => {
|
return new Promise<boolean>((resolve) => {
|
||||||
const keyListener = terminal.onKey(({ key }: { key: string }) => {
|
const keyListener = terminal.onKey(({ key }: { key: string }) => {
|
||||||
@ -185,10 +201,10 @@ const askForVNetConfigConsent = async (terminal: Terminal): Promise<boolean> =>
|
|||||||
terminal.writeln("");
|
terminal.writeln("");
|
||||||
|
|
||||||
if (key.toLowerCase() === 'y') {
|
if (key.toLowerCase() === 'y') {
|
||||||
terminal.writeln(terminalLog.success("Proceeding with existing network configuration"));
|
terminal.writeln(terminalLog.success(`Proceeding with existing ${networkType} configuration`));
|
||||||
resolve(true);
|
resolve(true);
|
||||||
} else {
|
} else {
|
||||||
terminal.writeln(terminalLog.info("Will configure new network settings"));
|
terminal.writeln(terminalLog.info(`Will configure new ${networkType} settings`));
|
||||||
resolve(false);
|
resolve(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -203,7 +219,7 @@ const isCloudShellVNetInDatabaseConfig = async (vNetSettings: VnetSettings, term
|
|||||||
terminal.writeln(terminalLog.subheader("Verifying if CloudShell VNet is configured in database"));
|
terminal.writeln(terminalLog.subheader("Verifying if CloudShell VNet is configured in database"));
|
||||||
|
|
||||||
// Get the subnet ID from the CloudShell Network Profile
|
// Get the subnet ID from the CloudShell Network Profile
|
||||||
const netProfileInfo = await GetARMCall<any>(vNetSettings.networkProfileResourceId);
|
const netProfileInfo = await getNetworkProfileInfo<any>(vNetSettings.networkProfileResourceId);
|
||||||
|
|
||||||
if (!netProfileInfo?.properties?.containerNetworkInterfaceConfigurations?.[0]
|
if (!netProfileInfo?.properties?.containerNetworkInterfaceConfigurations?.[0]
|
||||||
?.properties?.ipConfigurations?.[0]?.properties?.subnet?.id) {
|
?.properties?.ipConfigurations?.[0]?.properties?.subnet?.id) {
|
||||||
@ -225,9 +241,8 @@ const isCloudShellVNetInDatabaseConfig = async (vNetSettings: VnetSettings, term
|
|||||||
const vnetRules = dbAccount.properties.virtualNetworkRules;
|
const vnetRules = dbAccount.properties.virtualNetworkRules;
|
||||||
|
|
||||||
// Check if the CloudShell subnet is already in the rules
|
// Check if the CloudShell subnet is already in the rules
|
||||||
const isAlreadyConfigured = vnetRules.some(rule => rule.id === cloudShellSubnetId);
|
return vnetRules.some(rule => rule.id === cloudShellSubnetId);
|
||||||
|
|
||||||
return isAlreadyConfigured;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
terminal.writeln(terminalLog.error("Error checking database VNet configuration"));
|
terminal.writeln(terminalLog.error("Error checking database VNet configuration"));
|
||||||
return false;
|
return false;
|
||||||
@ -271,7 +286,7 @@ const addCloudShellVNetToDatabase = async (vNetSettings: VnetSettings, terminal:
|
|||||||
const { cloudShellSubnetId, cloudShellVnetId } = await getCloudShellNetworkIds(vNetSettings, terminal);
|
const { cloudShellSubnetId, cloudShellVnetId } = await getCloudShellNetworkIds(vNetSettings, terminal);
|
||||||
|
|
||||||
// Step 2: Get current database account details
|
// Step 2: Get current database account details
|
||||||
const { dbAccountId, currentDbAccount } = await getDatabaseAccountDetails(terminal);
|
const { currentDbAccount } = await getDatabaseAccountDetails(terminal);
|
||||||
|
|
||||||
// Step 3: Check if VNet is already configured in database
|
// Step 3: Check if VNet is already configured in database
|
||||||
if (await isVNetAlreadyConfigured(cloudShellSubnetId, currentDbAccount, terminal)) {
|
if (await isVNetAlreadyConfigured(cloudShellSubnetId, currentDbAccount, terminal)) {
|
||||||
@ -280,7 +295,7 @@ const addCloudShellVNetToDatabase = async (vNetSettings: VnetSettings, terminal:
|
|||||||
|
|
||||||
// Step 4: Check network resource statuses
|
// Step 4: Check network resource statuses
|
||||||
const { vnetInfo, subnetInfo, operationInProgress } =
|
const { vnetInfo, subnetInfo, operationInProgress } =
|
||||||
await checkNetworkResourceStatuses(cloudShellSubnetId, cloudShellVnetId, dbAccountId, terminal);
|
await checkNetworkResourceStatuses(cloudShellSubnetId, cloudShellVnetId, currentDbAccount.id, terminal);
|
||||||
|
|
||||||
// Step 5: If no operation in progress, update the subnet and database
|
// Step 5: If no operation in progress, update the subnet and database
|
||||||
if (!operationInProgress) {
|
if (!operationInProgress) {
|
||||||
@ -288,11 +303,11 @@ const addCloudShellVNetToDatabase = async (vNetSettings: VnetSettings, terminal:
|
|||||||
await enableCosmosDBServiceEndpoint(cloudShellSubnetId, subnetInfo, terminal);
|
await enableCosmosDBServiceEndpoint(cloudShellSubnetId, subnetInfo, terminal);
|
||||||
|
|
||||||
// Step 5b: Update database account with VNet rule
|
// Step 5b: Update database account with VNet rule
|
||||||
await updateDatabaseWithVNetRule(currentDbAccount, cloudShellSubnetId, dbAccountId, terminal);
|
await updateDatabaseWithVNetRule(currentDbAccount, cloudShellSubnetId, currentDbAccount.id, terminal);
|
||||||
} else {
|
} else {
|
||||||
terminal.writeln(terminalLog.info("Monitoring existing VNet operation..."));
|
terminal.writeln(terminalLog.info("Monitoring existing VNet operation..."));
|
||||||
// Step 6: Monitor the update progress
|
// Step 6: Monitor the update progress
|
||||||
await monitorVNetAdditionProgress(cloudShellSubnetId, dbAccountId, terminal);
|
await monitorVNetAdditionProgress(cloudShellSubnetId, currentDbAccount.id, terminal);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -305,7 +320,7 @@ const addCloudShellVNetToDatabase = async (vNetSettings: VnetSettings, terminal:
|
|||||||
* Gets the subnet and VNet IDs from CloudShell Network Profile
|
* Gets the subnet and VNet IDs from CloudShell Network Profile
|
||||||
*/
|
*/
|
||||||
const getCloudShellNetworkIds = async (vNetSettings: VnetSettings, terminal: Terminal): Promise<{ cloudShellSubnetId: string; cloudShellVnetId: string }> => {
|
const getCloudShellNetworkIds = async (vNetSettings: VnetSettings, terminal: Terminal): Promise<{ cloudShellSubnetId: string; cloudShellVnetId: string }> => {
|
||||||
const netProfileInfo = await GetARMCall<any>(vNetSettings.networkProfileResourceId, "2023-05-01");
|
const netProfileInfo = await getNetworkProfileInfo<any>(vNetSettings.networkProfileResourceId);
|
||||||
|
|
||||||
if (!netProfileInfo?.properties?.containerNetworkInterfaceConfigurations?.[0]
|
if (!netProfileInfo?.properties?.containerNetworkInterfaceConfigurations?.[0]
|
||||||
?.properties?.ipConfigurations?.[0]?.properties?.subnet?.id) {
|
?.properties?.ipConfigurations?.[0]?.properties?.subnet?.id) {
|
||||||
@ -328,14 +343,12 @@ const getCloudShellNetworkIds = async (vNetSettings: VnetSettings, terminal: Ter
|
|||||||
/**
|
/**
|
||||||
* Gets the database account details
|
* Gets the database account details
|
||||||
*/
|
*/
|
||||||
const getDatabaseAccountDetails = async (terminal: Terminal): Promise<{ dbAccountId: string; currentDbAccount: any }> => {
|
const getDatabaseAccountDetails = async (terminal: Terminal): Promise<{ currentDbAccount: any }> => {
|
||||||
const dbAccount = userContext.databaseAccount;
|
const dbAccount = userContext.databaseAccount;
|
||||||
const dbAccountId = `/subscriptions/${userContext.subscriptionId}/resourceGroups/${userContext.resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${dbAccount.name}`;
|
|
||||||
|
|
||||||
terminal.writeln(terminalLog.database("Verifying current configuration"));
|
terminal.writeln(terminalLog.database("Verifying current configuration"));
|
||||||
const currentDbAccount = await GetARMCall<any>(dbAccountId, "2023-04-15");
|
const currentDbAccount = await getAccountDetails(dbAccount.id);
|
||||||
|
|
||||||
return { dbAccountId, currentDbAccount };
|
return { currentDbAccount };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -372,8 +385,8 @@ const checkNetworkResourceStatuses = async (
|
|||||||
|
|
||||||
if (cloudShellVnetId && cloudShellSubnetId) {
|
if (cloudShellVnetId && cloudShellSubnetId) {
|
||||||
// Get VNet and subnet resource status
|
// Get VNet and subnet resource status
|
||||||
vnetInfo = await GetARMCall<any>(cloudShellVnetId, "2023-05-01");
|
vnetInfo = await getVnetInformation<any>(cloudShellVnetId);
|
||||||
subnetInfo = await GetARMCall<any>(cloudShellSubnetId, "2023-05-01");
|
subnetInfo = await getSubnetInformation<any>(cloudShellSubnetId);
|
||||||
|
|
||||||
// Check if there's an ongoing operation on the VNet or subnet
|
// Check if there's an ongoing operation on the VNet or subnet
|
||||||
const vnetProvisioningState = vnetInfo?.properties?.provisioningState;
|
const vnetProvisioningState = vnetInfo?.properties?.provisioningState;
|
||||||
@ -390,7 +403,7 @@ const checkNetworkResourceStatuses = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Also check database operations
|
// Also check database operations
|
||||||
const latestDbAccount = await GetARMCall<any>(dbAccountId, "2023-04-15");
|
const latestDbAccount = await getAccountDetails<any>(dbAccountId);
|
||||||
|
|
||||||
if (latestDbAccount.properties.virtualNetworkRules) {
|
if (latestDbAccount.properties.virtualNetworkRules) {
|
||||||
const isPendingAdd = latestDbAccount.properties.virtualNetworkRules.some(
|
const isPendingAdd = latestDbAccount.properties.virtualNetworkRules.some(
|
||||||
@ -455,14 +468,14 @@ const enableCosmosDBServiceEndpoint = async (cloudShellSubnetId: string, subnetI
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Apply the subnet update
|
// Apply the subnet update
|
||||||
await PutARMCall(subnetUrl, subnetUpdatePayload, "2023-05-01");
|
await updateSubnetInformation(subnetUrl, subnetUpdatePayload);
|
||||||
|
|
||||||
// Wait for the subnet update to complete
|
// Wait for the subnet update to complete
|
||||||
let subnetUpdateComplete = false;
|
let subnetUpdateComplete = false;
|
||||||
let subnetRetryCount = 0;
|
let subnetRetryCount = 0;
|
||||||
|
|
||||||
while (!subnetUpdateComplete && subnetRetryCount < MAX_RETRY_COUNT) {
|
while (!subnetUpdateComplete && subnetRetryCount < MAX_RETRY_COUNT) {
|
||||||
const updatedSubnet = await GetARMCall<any>(subnetUrl, "2023-05-01");
|
const updatedSubnet = await getSubnetInformation<any>(subnetUrl);
|
||||||
|
|
||||||
const endpointEnabled = updatedSubnet.properties.serviceEndpoints &&
|
const endpointEnabled = updatedSubnet.properties.serviceEndpoints &&
|
||||||
updatedSubnet.properties.serviceEndpoints.some(
|
updatedSubnet.properties.serviceEndpoints.some(
|
||||||
@ -504,7 +517,7 @@ const updateDatabaseWithVNetRule = async (currentDbAccount: any, cloudShellSubne
|
|||||||
|
|
||||||
// Update the database account
|
// Update the database account
|
||||||
terminal.writeln(terminalLog.subheader("Submitting VNet update request to database"));
|
terminal.writeln(terminalLog.subheader("Submitting VNet update request to database"));
|
||||||
await PutARMCall(dbAccountId, updatePayload, "2023-04-15");
|
await updateDatabaseAccount(dbAccountId, updatePayload);
|
||||||
terminal.writeln(terminalLog.success("Updated Database account with Cloud Shell Vnet"));
|
terminal.writeln(terminalLog.success("Updated Database account with Cloud Shell Vnet"));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -522,7 +535,7 @@ const monitorVNetAdditionProgress = async (cloudShellSubnetId: string, dbAccount
|
|||||||
|
|
||||||
while (!updateComplete && retryCount < MAX_RETRY_COUNT) {
|
while (!updateComplete && retryCount < MAX_RETRY_COUNT) {
|
||||||
// Check if the VNet is now in the database account
|
// Check if the VNet is now in the database account
|
||||||
const updatedDbAccount = await GetARMCall<any>(dbAccountId, "2023-04-15");
|
const updatedDbAccount = await getAccountDetails<any>(dbAccountId);
|
||||||
|
|
||||||
const isVNetAdded = updatedDbAccount.properties.virtualNetworkRules?.some(
|
const isVNetAdded = updatedDbAccount.properties.virtualNetworkRules?.some(
|
||||||
(rule: any) => rule.id === cloudShellSubnetId && (!rule.status || rule.status === 'Succeeded')
|
(rule: any) => rule.id === cloudShellSubnetId && (!rule.status || rule.status === 'Succeeded')
|
||||||
@ -535,7 +548,7 @@ const monitorVNetAdditionProgress = async (cloudShellSubnetId: string, dbAccount
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If not yet added, check for operation progress
|
// If not yet added, check for operation progress
|
||||||
const operations = await GetARMCall<any>(`${dbAccountId}/operations`, "2023-04-15");
|
const operations = await getDatabaseOperations<any>(dbAccountId);
|
||||||
|
|
||||||
// Find network-related operations
|
// Find network-related operations
|
||||||
const networkOps = operations.value?.filter(
|
const networkOps = operations.value?.filter(
|
||||||
@ -584,55 +597,6 @@ const monitorVNetAdditionProgress = async (cloudShellSubnetId: string, dbAccount
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the database account has network restrictions
|
|
||||||
*/
|
|
||||||
const hasDatabaseNetworkRestrictions = (): boolean => {
|
|
||||||
const dbAccount = userContext.databaseAccount;
|
|
||||||
|
|
||||||
if (!dbAccount) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for virtual network filters
|
|
||||||
const hasVNetFilters = dbAccount.properties.virtualNetworkRules && dbAccount.properties.virtualNetworkRules.length > 0;
|
|
||||||
|
|
||||||
// Check for IP-based firewall
|
|
||||||
const hasIpRules = dbAccount.properties.isVirtualNetworkFilterEnabled;
|
|
||||||
|
|
||||||
// Check for private endpoints
|
|
||||||
const hasPrivateEndpoints = dbAccount.properties.privateEndpointConnections &&
|
|
||||||
dbAccount.properties.privateEndpointConnections.length > 0;
|
|
||||||
|
|
||||||
return hasVNetFilters || hasIpRules || hasPrivateEndpoints;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if there's an ongoing VNet operation for the database account
|
|
||||||
*/
|
|
||||||
const isVNetOperationInProgress = async (dbAccountId: string): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
// Get the ongoing operations for the database account
|
|
||||||
const operationsUrl = `${dbAccountId}/operations`;
|
|
||||||
const operations = await GetARMCall<any>(operationsUrl);
|
|
||||||
|
|
||||||
if (!operations || !operations.value || !operations.value.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there's any network-related operation in progress
|
|
||||||
return operations.value.some(
|
|
||||||
(op: any) =>
|
|
||||||
op.properties.status === 'InProgress' &&
|
|
||||||
(op.properties.description?.toLowerCase().includes('network') ||
|
|
||||||
op.properties.description?.toLowerCase().includes('vnet'))
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
// If we can't check operations, assume no operations in progress
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures that the CloudShell provider is registered for the current subscription
|
* Ensures that the CloudShell provider is registered for the current subscription
|
||||||
*/
|
*/
|
||||||
@ -652,32 +616,22 @@ const ensureCloudShellProviderRegistered = async (terminal: Terminal): Promise<v
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches user settings safely, handling errors
|
|
||||||
*/
|
|
||||||
const fetchUserSettings = async (terminal: Terminal): Promise<Settings | undefined> => {
|
|
||||||
try {
|
|
||||||
return await getUserSettings();
|
|
||||||
} catch (err) {
|
|
||||||
terminal.writeln(terminalLog.warning("No user settings found. Using defaults."));
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves existing VNet settings from user settings if available
|
* Retrieves existing VNet settings from user settings if available
|
||||||
*/
|
*/
|
||||||
const retrieveCloudShellVnetSettings = async (settings: Settings, terminal: Terminal): Promise<VnetSettings> => {
|
const retrieveCloudShellVnetSettings = async (settings: Settings, terminal: Terminal): Promise<VnetSettings> => {
|
||||||
if (settings?.properties?.vnetSettings && Object.keys(settings.properties.vnetSettings).length > 0) {
|
if (settings?.properties?.vnetSettings && Object.keys(settings.properties.vnetSettings).length > 0) {
|
||||||
try {
|
try {
|
||||||
const netProfileInfo = await GetARMCall<any>(settings.properties.vnetSettings.networkProfileResourceId);
|
const netProfileInfo = await getNetworkProfileInfo<any>(settings.properties.vnetSettings.networkProfileResourceId);
|
||||||
|
|
||||||
terminal.writeln(terminalLog.header("Existing Network Configuration"));
|
terminal.writeln(terminalLog.header("Existing Network Configuration"));
|
||||||
|
|
||||||
const vnetResourceId = netProfileInfo.properties.containerNetworkInterfaceConfigurations[0]
|
const subnetId = netProfileInfo.properties.containerNetworkInterfaceConfigurations[0]
|
||||||
.properties.ipConfigurations[0].properties.subnet.id.replace(/\/subnets\/[^/]+$/, '');
|
.properties.ipConfigurations[0].properties.subnet.id;
|
||||||
|
const vnetResourceId = subnetId.replace(/\/subnets\/[^/]+$/, '');
|
||||||
|
|
||||||
terminal.writeln(terminalLog.item("VNet", vnetResourceId));
|
terminal.writeln(terminalLog.item("VNet", vnetResourceId));
|
||||||
|
terminal.writeln(terminalLog.item("Subnet", subnetId));
|
||||||
terminal.writeln(terminalLog.item("Location", settings.properties.vnetSettings.location));
|
terminal.writeln(terminalLog.item("Location", settings.properties.vnetSettings.location));
|
||||||
terminal.writeln(terminalLog.item("Network Profile", settings.properties.vnetSettings.networkProfileResourceId));
|
terminal.writeln(terminalLog.item("Network Profile", settings.properties.vnetSettings.networkProfileResourceId));
|
||||||
terminal.writeln(terminalLog.item("Relay Namespace", settings.properties.vnetSettings.relayNamespaceResourceId));
|
terminal.writeln(terminalLog.item("Relay Namespace", settings.properties.vnetSettings.relayNamespaceResourceId));
|
||||||
@ -709,31 +663,33 @@ const determineCloudShellRegion = (terminal: Terminal): { resolvedRegion: string
|
|||||||
/**
|
/**
|
||||||
* Configures a new VNet for CloudShell
|
* Configures a new VNet for CloudShell
|
||||||
*/
|
*/
|
||||||
const configureCloudShellVNet = async (terminal: Terminal, resolvedRegion: string, vNetSettings: VnetSettings): Promise<VnetSettings> => {
|
const configureCloudShellVNet = async (terminal: Terminal, resolvedRegion: string): Promise<VnetSettings> => {
|
||||||
|
|
||||||
|
// TODO: Use professional and shorter names for resources
|
||||||
const randomSuffix = Math.floor(10000 + Math.random() * 90000);
|
const randomSuffix = Math.floor(10000 + Math.random() * 90000);
|
||||||
|
|
||||||
const subnetName = `cloudshell-subnet-${randomSuffix}`;
|
const subnetName = `cloudshell-subnet-${randomSuffix}`;
|
||||||
const vnetName = `cloudshell-vnet-${randomSuffix}`;
|
const vnetName = `cloudshell-vnet-${randomSuffix}`;
|
||||||
const networkProfileName = `cloudshell-netprofile-${randomSuffix}`;
|
const networkProfileName = `cloudshell-network-profile-${randomSuffix}`;
|
||||||
const relayName = `cloudshell-relay-${randomSuffix}`;
|
const relayName = `cloudshell-relay-${randomSuffix}`;
|
||||||
|
|
||||||
terminal.writeln(terminalLog.header("Network Resource Configuration"));
|
terminal.writeln(terminalLog.header("Network Resource Configuration"));
|
||||||
|
|
||||||
const azureContainerInstanceOID = await askQuestion(
|
const azureContainerInstanceOID = await askQuestion(
|
||||||
terminal,
|
terminal,
|
||||||
"Azure Container Instance OID",
|
"Enter Azure Container Instance OID (Refer. https://learn.microsoft.com/en-us/azure/cloud-shell/vnet/deployment#get-the-azure-container-instance-id)",
|
||||||
DEFAULT_CONTAINER_INSTANCE_OID
|
DEFAULT_CONTAINER_INSTANCE_OID
|
||||||
);
|
);
|
||||||
|
|
||||||
const vNetSubscriptionId = await askQuestion(
|
const vNetSubscriptionId = await askQuestion(
|
||||||
terminal,
|
terminal,
|
||||||
"VNet subscription ID",
|
"Enter Virtual Network Subscription ID",
|
||||||
userContext.subscriptionId
|
userContext.subscriptionId
|
||||||
);
|
);
|
||||||
|
|
||||||
const vNetResourceGroup = await askQuestion(
|
const vNetResourceGroup = await askQuestion(
|
||||||
terminal,
|
terminal,
|
||||||
"VNet resource group",
|
"Enter Virtual Network Resource Group",
|
||||||
userContext.resourceGroup
|
userContext.resourceGroup
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -749,7 +705,7 @@ const configureCloudShellVNet = async (terminal: Terminal, resolvedRegion: strin
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Step 2: Create Network Profile
|
// Step 2: Create Network Profile
|
||||||
await createNetworkProfile(
|
await createNetworkProfileWithVnet(
|
||||||
vNetSubscriptionId,
|
vNetSubscriptionId,
|
||||||
vNetResourceGroup,
|
vNetResourceGroup,
|
||||||
vnetName,
|
vnetName,
|
||||||
@ -836,13 +792,13 @@ const createCloudShellVnet = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
terminal.writeln(terminalLog.vnet(`Creating VNet: ${vnetName}`));
|
terminal.writeln(terminalLog.vnet(`Creating VNet: ${vnetName}`));
|
||||||
let vNetResponse = await PutARMCall<any>(
|
let vNetResponse = await updateVnet<any>(
|
||||||
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Network/virtualNetworks/${vnetName}`,
|
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Network/virtualNetworks/${vnetName}`,
|
||||||
vNetConfigPayload
|
vNetConfigPayload
|
||||||
);
|
);
|
||||||
|
|
||||||
while (vNetResponse?.properties?.provisioningState !== "Succeeded") {
|
while (vNetResponse?.properties?.provisioningState !== "Succeeded") {
|
||||||
vNetResponse = await GetARMCall<any>(
|
vNetResponse = await getVnet<any>(
|
||||||
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Network/virtualNetworks/${vnetName}`
|
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Network/virtualNetworks/${vnetName}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -862,7 +818,7 @@ const createCloudShellVnet = async (
|
|||||||
/**
|
/**
|
||||||
* Creates a Network Profile for CloudShell
|
* Creates a Network Profile for CloudShell
|
||||||
*/
|
*/
|
||||||
const createNetworkProfile = async (
|
const createNetworkProfileWithVnet = async (
|
||||||
vNetSubscriptionId: string,
|
vNetSubscriptionId: string,
|
||||||
vNetResourceGroup: string,
|
vNetResourceGroup: string,
|
||||||
vnetName: string,
|
vnetName: string,
|
||||||
@ -897,14 +853,13 @@ const createNetworkProfile = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
terminal.writeln(terminalLog.vnet("Creating Network Profile"));
|
terminal.writeln(terminalLog.vnet("Creating Network Profile"));
|
||||||
let networkProfileResponse = await PutARMCall<any>(
|
let networkProfileResponse = await createNetworkProfile<any>(
|
||||||
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Network/networkProfiles/${networkProfileName}`,
|
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Network/networkProfiles/${networkProfileName}`,
|
||||||
createNetworkProfilePayload,
|
createNetworkProfilePayload
|
||||||
"2024-01-01"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
while (networkProfileResponse?.properties?.provisioningState !== "Succeeded") {
|
while (networkProfileResponse?.properties?.provisioningState !== "Succeeded") {
|
||||||
networkProfileResponse = await GetARMCall<any>(
|
networkProfileResponse = await getNetworkProfileInfo<any>(
|
||||||
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Network/networkProfiles/${networkProfileName}`
|
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Network/networkProfiles/${networkProfileName}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -939,14 +894,13 @@ const createNetworkRelay = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
terminal.writeln(terminalLog.vnet("Creating Relay Namespace"));
|
terminal.writeln(terminalLog.vnet("Creating Relay Namespace"));
|
||||||
let relayResponse = await PutARMCall<any>(
|
let relayResponse = await createRelay<any>(
|
||||||
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Relay/namespaces/${relayName}`,
|
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Relay/namespaces/${relayName}`,
|
||||||
relayPayload,
|
relayPayload
|
||||||
"2024-01-01"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
while (relayResponse?.properties?.provisioningState !== "Succeeded") {
|
while (relayResponse?.properties?.provisioningState !== "Succeeded") {
|
||||||
relayResponse = await GetARMCall<any>(
|
relayResponse = await getRelay<any>(
|
||||||
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Relay/namespaces/${relayName}`
|
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Relay/namespaces/${relayName}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -981,10 +935,9 @@ const assignRoleToNetworkProfile = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
terminal.writeln(terminalLog.info("Assigning permissions to Network Profile"));
|
terminal.writeln(terminalLog.info("Assigning permissions to Network Profile"));
|
||||||
await PutARMCall<any>(
|
await createRoleOnNetworkProfile<any>(
|
||||||
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Network/networkProfiles/${networkProfileName}/providers/Microsoft.Authorization/roleAssignments/${nfRoleName}`,
|
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Network/networkProfiles/${networkProfileName}/providers/Microsoft.Authorization/roleAssignments/${nfRoleName}`,
|
||||||
networkProfileRoleAssignmentPayload,
|
networkProfileRoleAssignmentPayload
|
||||||
"2022-04-01"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
terminal.writeln(terminalLog.success("Network Profile permissions assigned"));
|
terminal.writeln(terminalLog.success("Network Profile permissions assigned"));
|
||||||
@ -1009,10 +962,9 @@ const assignRoleToRelay = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
terminal.writeln(terminalLog.info("Assigning permissions to Relay Namespace"));
|
terminal.writeln(terminalLog.info("Assigning permissions to Relay Namespace"));
|
||||||
await PutARMCall<any>(
|
await createRoleOnRelay<any>(
|
||||||
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Relay/namespaces/${relayName}/providers/Microsoft.Authorization/roleAssignments/${relayRoleName}`,
|
`/subscriptions/${vNetSubscriptionId}/resourceGroups/${vNetResourceGroup}/providers/Microsoft.Relay/namespaces/${relayName}/providers/Microsoft.Authorization/roleAssignments/${relayRoleName}`,
|
||||||
relayRoleAssignmentPayload,
|
relayRoleAssignmentPayload
|
||||||
"2022-04-01"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
terminal.writeln(terminalLog.success("Relay Namespace permissions assigned"));
|
terminal.writeln(terminalLog.success("Relay Namespace permissions assigned"));
|
||||||
@ -1024,7 +976,8 @@ const assignRoleToRelay = async (
|
|||||||
const provisionCloudShellSession = async (
|
const provisionCloudShellSession = async (
|
||||||
resolvedRegion: string,
|
resolvedRegion: string,
|
||||||
terminal: Terminal,
|
terminal: Terminal,
|
||||||
vNetSettings: object
|
vNetSettings: object,
|
||||||
|
isAllPublicAccessEnabled: boolean
|
||||||
): Promise<{ socketUri?: string; provisionConsoleResponse?: any; targetUri?: string }> => {
|
): Promise<{ socketUri?: string; provisionConsoleResponse?: any; targetUri?: string }> => {
|
||||||
return new Promise( async (resolve, reject) => {
|
return new Promise( async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
@ -1052,7 +1005,7 @@ const provisionCloudShellSession = async (
|
|||||||
terminal.writeln(terminalLog.warning("No VNet configuration provided"));
|
terminal.writeln(terminalLog.warning("No VNet configuration provided"));
|
||||||
terminal.writeln(terminalLog.warning("CloudShell will be provisioned with public network access"));
|
terminal.writeln(terminalLog.warning("CloudShell will be provisioned with public network access"));
|
||||||
|
|
||||||
if (hasDatabaseNetworkRestrictions()) {
|
if (!isAllPublicAccessEnabled) {
|
||||||
terminal.writeln(terminalLog.error("Warning: Your database has network restrictions"));
|
terminal.writeln(terminalLog.error("Warning: Your database has network restrictions"));
|
||||||
terminal.writeln(terminalLog.error("CloudShell may not be able to connect without proper VNet configuration"));
|
terminal.writeln(terminalLog.error("CloudShell may not be able to connect without proper VNet configuration"));
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { configContext } from "ConfigContext";
|
import { configContext } from "ConfigContext";
|
||||||
import * as DataModels from "Contracts/DataModels";
|
import * as DataModels from "Contracts/DataModels";
|
||||||
|
import * as ViewModels from "Contracts/ViewModels";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { armRequest } from "Utils/arm/request";
|
import { armRequest } from "Utils/arm/request";
|
||||||
|
|
||||||
@ -10,16 +11,8 @@ export async function checkFirewallRules(
|
|||||||
setMessageFunc?: (message: string) => void,
|
setMessageFunc?: (message: string) => void,
|
||||||
message?: string,
|
message?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const firewallRulesUri = `${userContext.databaseAccount.id}/firewallRules`;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const isEnabled = await callFirewallAPis(apiVersion, firewallRulesPredicate);
|
||||||
const response: any = await armRequest({
|
|
||||||
host: configContext.ARM_ENDPOINT,
|
|
||||||
path: firewallRulesUri,
|
|
||||||
method: "GET",
|
|
||||||
apiVersion: apiVersion,
|
|
||||||
});
|
|
||||||
const firewallRules: DataModels.FirewallRule[] = response?.data?.value || response?.value || [];
|
|
||||||
const isEnabled = firewallRules.some(firewallRulesPredicate);
|
|
||||||
|
|
||||||
if (isAllPublicIPAddressesEnabled) {
|
if (isAllPublicIPAddressesEnabled) {
|
||||||
isAllPublicIPAddressesEnabled(isEnabled);
|
isAllPublicIPAddressesEnabled(isEnabled);
|
||||||
@ -42,3 +35,89 @@ export async function checkFirewallRules(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function callFirewallAPis(
|
||||||
|
apiVersion: string,
|
||||||
|
firewallRulesPredicate: (rule: DataModels.FirewallRule) => unknown):
|
||||||
|
Promise<boolean> {
|
||||||
|
const firewallRulesUri = `${userContext.databaseAccount.id}/firewallRules`;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const response: any = await armRequest({
|
||||||
|
host: configContext.ARM_ENDPOINT,
|
||||||
|
path: firewallRulesUri,
|
||||||
|
method: "GET",
|
||||||
|
apiVersion: apiVersion,
|
||||||
|
});
|
||||||
|
const firewallRules: DataModels.FirewallRule[] = response?.data?.value || response?.value || [];
|
||||||
|
const isEnabled = firewallRules.some(firewallRulesPredicate);
|
||||||
|
|
||||||
|
return isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkNetworkRules(kind: ViewModels.TerminalKind, isPublicAccessEnabledFlag: ko.Observable<boolean> | React.Dispatch<React.SetStateAction<boolean>>): Promise<void> {
|
||||||
|
if (kind === ViewModels.TerminalKind.Postgres) {
|
||||||
|
await checkFirewallRules(
|
||||||
|
"2022-11-08",
|
||||||
|
(rule) => rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255",
|
||||||
|
isPublicAccessEnabledFlag,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind === ViewModels.TerminalKind.VCoreMongo) {
|
||||||
|
await checkFirewallRules(
|
||||||
|
"2023-03-01-preview",
|
||||||
|
(rule) =>
|
||||||
|
rule.name.startsWith("AllowAllAzureServicesAndResourcesWithinAzureIps") ||
|
||||||
|
(rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"),
|
||||||
|
isPublicAccessEnabledFlag,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function IsPublicAccessAvailable(kind: ViewModels.TerminalKind): Promise<boolean> {
|
||||||
|
if (kind === ViewModels.TerminalKind.Postgres) {
|
||||||
|
return await callFirewallAPis(
|
||||||
|
"2022-11-08",
|
||||||
|
(rule) => rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind === ViewModels.TerminalKind.VCoreMongo) {
|
||||||
|
return await callFirewallAPis(
|
||||||
|
"2023-03-01-preview",
|
||||||
|
(rule) =>
|
||||||
|
rule.name.startsWith("AllowAllAzureServicesAndResourcesWithinAzureIps") ||
|
||||||
|
(rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasDatabaseNetworkRestrictions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the database account has network restrictions
|
||||||
|
*/
|
||||||
|
const hasDatabaseNetworkRestrictions = (): boolean => {
|
||||||
|
return hasVNetRestrictions() || hasFirewallRestrictions() || hasPrivateEndpointsRestrictions();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the database account has Private Endpoint restrictions
|
||||||
|
*/
|
||||||
|
export const hasPrivateEndpointsRestrictions = (): boolean => {
|
||||||
|
return userContext.databaseAccount.properties.privateEndpointConnections && userContext.databaseAccount.properties.privateEndpointConnections.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the database account has Firewall restrictions
|
||||||
|
*/
|
||||||
|
export const hasFirewallRestrictions = (): boolean => {
|
||||||
|
return userContext.databaseAccount.properties.isVirtualNetworkFilterEnabled;;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the database account has VNet restrictions
|
||||||
|
*/
|
||||||
|
export const hasVNetRestrictions = (): boolean => {
|
||||||
|
return userContext.databaseAccount.properties.virtualNetworkRules && userContext.databaseAccount.properties.virtualNetworkRules.length > 0
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
import { Spinner, SpinnerSize } from "@fluentui/react";
|
import { Spinner, SpinnerSize } from "@fluentui/react";
|
||||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||||
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
|
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
|
||||||
import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
|
import { checkNetworkRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import FirewallRuleScreenshot from "../../../images/firewallRule.png";
|
import FirewallRuleScreenshot from "../../../images/firewallRule.png";
|
||||||
@ -65,23 +65,16 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
|
|||||||
* CloudShell terminal tab
|
* CloudShell terminal tab
|
||||||
*/
|
*/
|
||||||
class CloudShellTerminalComponentAdapter implements ReactAdapter {
|
class CloudShellTerminalComponentAdapter implements ReactAdapter {
|
||||||
|
|
||||||
// parameters: true: show, false: hide
|
// parameters: true: show, false: hide
|
||||||
public parameters: ko.Computed<boolean>;
|
public parameters: ko.Computed<boolean>;
|
||||||
constructor(
|
constructor(
|
||||||
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>,
|
|
||||||
private kind: ViewModels.TerminalKind,
|
private kind: ViewModels.TerminalKind,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
public renderComponent(): JSX.Element {
|
||||||
if (!this.isAllPublicIPAddressesEnabled()) {
|
|
||||||
return (
|
console.log("this.parameters() " + this.parameters() );
|
||||||
<QuickstartFirewallNotification
|
|
||||||
messageType={MessageTypes.OpenPostgresNetworkingBlade}
|
|
||||||
screenshot={FirewallRuleScreenshot}
|
|
||||||
shellName={getShellNameForDisplay(this.kind)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return this.parameters() ? (
|
return this.parameters() ? (
|
||||||
<CloudShellTerminalComponent
|
<CloudShellTerminalComponent
|
||||||
shellType={this.kind}/>
|
shellType={this.kind}/>
|
||||||
@ -109,39 +102,25 @@ export default class TerminalTab extends TabsBase {
|
|||||||
private terminalComponentAdapter: any;
|
private terminalComponentAdapter: any;
|
||||||
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>;
|
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>;
|
||||||
|
|
||||||
constructor(options: TerminalTabOptions) {
|
constructor (options: TerminalTabOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
this.isAllPublicIPAddressesEnabled = ko.observable(true);
|
this.isAllPublicIPAddressesEnabled = ko.observable(true);
|
||||||
|
|
||||||
if (options.kind === ViewModels.TerminalKind.Postgres) {
|
checkNetworkRules(options.kind, this.isAllPublicIPAddressesEnabled);
|
||||||
checkFirewallRules(
|
|
||||||
"2022-11-08",
|
|
||||||
(rule) => rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255",
|
|
||||||
this.isAllPublicIPAddressesEnabled,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.kind === ViewModels.TerminalKind.VCoreMongo) {
|
|
||||||
checkFirewallRules(
|
|
||||||
"2023-03-01-preview",
|
|
||||||
(rule) =>
|
|
||||||
rule.name.startsWith("AllowAllAzureServicesAndResourcesWithinAzureIps") ||
|
|
||||||
(rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"),
|
|
||||||
this.isAllPublicIPAddressesEnabled,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.initializeNotebookTerminalAdapter(options);
|
this.initializeNotebookTerminalAdapter(options);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeNotebookTerminalAdapter(options: TerminalTabOptions): void {
|
private async initializeNotebookTerminalAdapter(options: TerminalTabOptions): Promise<void> {
|
||||||
if (userContext.features.enableCloudShell) {
|
if (userContext.features.enableCloudShell) {
|
||||||
this.terminalComponentAdapter = new CloudShellTerminalComponentAdapter(
|
this.terminalComponentAdapter = new CloudShellTerminalComponentAdapter(
|
||||||
this.isAllPublicIPAddressesEnabled,
|
|
||||||
options.kind
|
options.kind
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.terminalComponentAdapter.parameters = ko.computed<boolean>(() =>
|
||||||
|
this.isTemplateReady()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.terminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
this.terminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
||||||
@ -152,15 +131,14 @@ export default class TerminalTab extends TabsBase {
|
|||||||
this.isAllPublicIPAddressesEnabled,
|
this.isAllPublicIPAddressesEnabled,
|
||||||
options.kind
|
options.kind
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
this.terminalComponentAdapter.parameters = ko.computed<boolean>(() =>
|
this.terminalComponentAdapter.parameters = ko.computed<boolean>(() =>
|
||||||
this.isTemplateReady() &&
|
this.isTemplateReady() &&
|
||||||
(userContext.features.enableCloudShell ||
|
useNotebook.getState().isNotebookEnabled &&
|
||||||
(useNotebook.getState().isNotebookEnabled &&
|
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint &&
|
||||||
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint)) &&
|
this.isAllPublicIPAddressesEnabled()
|
||||||
this.isAllPublicIPAddressesEnabled()
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getContainer(): Explorer {
|
public getContainer(): Explorer {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user