Support advanced options in command line arguments (#23104) (#23124)

This commit is contained in:
Cheena Malhotra
2023-05-12 13:51:15 -07:00
committed by GitHub
parent 0fe638974d
commit b6bd726066
4 changed files with 144 additions and 20 deletions

View File

@@ -97,10 +97,24 @@ export class TestCapabilitiesService implements ICapabilitiesService {
valueType: ServiceOptionType.string
}
];
let mssqlAdvancedOptions: azdata.ConnectionOption[] = [
{
name: 'trustServerCertificate',
displayName: undefined!,
description: undefined!,
groupName: undefined!,
categoryValues: undefined!,
defaultValue: 'false',
isIdentity: false,
isRequired: false,
specialValueType: undefined!,
valueType: ServiceOptionType.boolean
}
];
let msSQLCapabilities = {
providerId: mssqlProviderName,
displayName: 'MSSQL',
connectionOptions: connectionProvider,
connectionOptions: connectionProvider.concat(mssqlAdvancedOptions),
};
let pgSQLCapabilities = {
providerId: this.pgsqlProviderName,

View File

@@ -202,6 +202,27 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
* Example: "providerName:MSSQL|authenticationType:|databaseName:database|serverName:server3|userName:user|group:testid"
*/
public getOptionsKey(): string {
let idNames = this.getOptionKeyIdNames();
idNames = idNames.filter(x => x !== undefined);
//Sort to make sure using names in the same order every time otherwise the ids would be different
idNames.sort();
let idValues: string[] = [];
for (let index = 0; index < idNames.length; index++) {
let value = this.options[idNames[index]!];
value = value ? value : '';
idValues.push(`${idNames[index]}${ProviderConnectionInfo.nameValueSeparator}${value}`);
}
return ProviderConnectionInfo.ProviderPropertyName + ProviderConnectionInfo.nameValueSeparator +
this.providerName + ProviderConnectionInfo.idSeparator + idValues.join(ProviderConnectionInfo.idSeparator);
}
/**
* @returns Array of option key names
*/
public getOptionKeyIdNames(): string[] {
let idNames = [];
if (this.serverCapabilities) {
idNames = this.serverCapabilities.connectionOptions.map(o => {
@@ -217,21 +238,7 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
// This should never happen but just incase the serverCapabilities was not ready at this time
idNames = ['authenticationType', 'database', 'server', 'user'];
}
idNames = idNames.filter(x => x !== undefined);
//Sort to make sure using names in the same order every time otherwise the ids would be different
idNames.sort();
let idValues: string[] = [];
for (let index = 0; index < idNames.length; index++) {
let value = this.options[idNames[index]!];
value = value ? value : '';
idValues.push(`${idNames[index]}${ProviderConnectionInfo.nameValueSeparator}${value}`);
}
return ProviderConnectionInfo.ProviderPropertyName + ProviderConnectionInfo.nameValueSeparator +
this.providerName + ProviderConnectionInfo.idSeparator + idValues.join(ProviderConnectionInfo.idSeparator);
return idNames;
}
public static getProviderFromOptionsKey(optionsKey: string) {

View File

@@ -31,17 +31,61 @@ import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/envi
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
export interface SqlArgs {
/**
* Used to determine file paths to be opened with SQL Editor.
* If provided, we connect the given profile to to it.
* More than one files can be passed to connect to provided profile.
*/
_?: string[];
/**
* Provide authenticationType to be used.
* accepted values: AzureMFA, SqlLogin, Integrated, etc.
*/
authenticationType?: string
/**
* Name of database
*/
database?: string;
/**
* Name of server
*/
server?: string;
/**
* User name/email address
*/
user?: string;
/**
* Operation to perform:
* accepted values: connect, openConnectionDialog
*/
command?: string;
/**
* Name of connection provider,
* accepted values: mssql (by default), pgsql, etc.
*/
provider?: string;
aad?: boolean; // deprecated - used by SSMS - authenticationType should be used instead
integrated?: boolean; // deprecated - used by SSMS - authenticationType should be used instead.
/**
* Deprecated - used by SSMS - authenticationType should be used instead
*/
aad?: boolean;
/**
* Deprecated - used by SSMS - authenticationType should be used instead.
*/
integrated?: boolean;
/**
* Whether or not to show dashboard
* accepted values: true, false (by default).
*/
showDashboard?: boolean;
/**
* Supports providing applicationName that will be used for connection profile app name.
*/
applicationName?: string;
/**
* Supports providing advanced connection properties that providers support.
* Value must be a json object containing key-value pairs in format: '{"key1":"value1","key2":"value2",...}'
*/
connectionProperties?: string;
}
//#region decorators
@@ -324,9 +368,33 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution,
profile.setOptionValue('applicationName', applicationName);
profile.setOptionValue('databaseDisplayName', profile.databaseName);
profile.setOptionValue('groupId', profile.groupId);
// Set all advanced options
let advancedOptions = this.getAdvancedOptions(args.connectionProperties, profile.getOptionKeyIdNames());
advancedOptions.forEach((v, k) => {
profile.setOptionValue(k, v);
});
return this._connectionManagementService ? this.tryMatchSavedProfile(profile) : profile;
}
private getAdvancedOptions(options: string, idNames: string[]): Map<string, string> {
const ignoredProperties = idNames.concat(['password', 'azureAccountToken']);
let advancedOptionsMap = new Map<string, string>();
if (options) {
try {
// Decode options if they contain any encoded URL characters
options = decodeURI(options);
JSON.parse(options, (k, v) => {
if (!(k in ignoredProperties)) {
advancedOptionsMap.set(k, v);
}
});
} catch (e) {
throw new Error(localize('commandline.propertiesFormatError', 'Advanced connection properties could not be parsed as JSON, error occurred: {0} Received properties value: {1}', e, options));
}
}
return advancedOptionsMap;
}
private tryMatchSavedProfile(profile: ConnectionProfile) {
let match: ConnectionProfile = undefined;
// If we can find a saved mssql provider connection that matches the args, use it

View File

@@ -96,6 +96,7 @@ class TestParsedArgs implements NativeParsedArgs, SqlArgs {
waitMarkerFilePath?: string;
authenticationType?: string;
applicationName?: string;
connectionProperties?: string;
}
suite('commandLineService tests', () => {
@@ -219,6 +220,42 @@ suite('commandLineService tests', () => {
connectionManagementService.verifyAll();
});
test('processCommandLine loads advanced options in args', async () => {
const connectionManagementService: TypeMoq.Mock<IConnectionManagementService>
= TypeMoq.Mock.ofType<IConnectionManagementService>(TestConnectionManagementService, TypeMoq.MockBehavior.Strict);
const args: TestParsedArgs = new TestParsedArgs();
args.server = 'myserver';
args.database = 'mydatabase';
args.user = 'myuser';
args.authenticationType = Constants.AuthenticationType.SqlLogin;
args.applicationName = 'myapplication';
// Pass advanced connection properties
args.connectionProperties = `{"trustServerCertificate":"true"}`;
connectionManagementService.setup((c) => c.showConnectionDialog()).verifiable(TypeMoq.Times.never());
connectionManagementService.setup(c => c.hasRegisteredServers()).returns(() => true).verifiable(TypeMoq.Times.atMostOnce());
connectionManagementService.setup(c => c.getConnectionGroups(TypeMoq.It.isAny())).returns(() => []);
let originalProfile: IConnectionProfile = undefined;
connectionManagementService.setup(c => c.connectIfNotConnected(TypeMoq.It.is<ConnectionProfile>(
p => p.serverName === 'myserver'
&& p.authenticationType === Constants.AuthenticationType.SqlLogin
&& p.options['applicationName'] === 'myapplication-azdata'), 'connection', true))
.returns((conn) => {
originalProfile = conn;
return Promise.resolve('unused');
})
.verifiable(TypeMoq.Times.once());
connectionManagementService.setup(c => c.getConnectionProfileById(TypeMoq.It.isAnyString())).returns(() => originalProfile);
const configurationService = getConfigurationServiceMock(true);
const logService = new NullLogService();
let contribution = getCommandLineContribution(connectionManagementService.object, configurationService.object, capabilitiesService, undefined, undefined, logService);
await contribution.processCommandLine(args);
assert.equal(originalProfile.options['applicationName'], 'myapplication-azdata', 'Application Name not received as expected.');
assert.equal(originalProfile.options['trustServerCertificate'], 'true', 'Advanced option not received as expected.');
connectionManagementService.verifyAll();
});
test('processCommandLine invokes a command without a profile parameter when no server is passed', async () => {
const connectionManagementService: TypeMoq.Mock<IConnectionManagementService>
= TypeMoq.Mock.ofType<IConnectionManagementService>(TestConnectionManagementService, TypeMoq.MockBehavior.Loose);
@@ -568,7 +605,5 @@ suite('commandLineService tests', () => {
notificationService.verifyAll();
connectionManagementService.verifyAll();
});
});
});