New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a command line interface for connecting to a SQL Server #3047
Changes from all commits
27f49d6
3ab91a3
d12ac5f
964b276
d19a9c8
7116adb
f4b0ec8
ee1df12
b68bf53
b0a60f4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the Source EULA. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
'use strict'; | ||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; | ||
export interface ICommandLineProcessing { | ||
_serviceBrand: any; | ||
/** | ||
* Interprets the various Azure Data Studio-specific command line switches and | ||
* performs the requisite tasks such as connecting to a server | ||
*/ | ||
processCommandLine() : void; | ||
} | ||
|
||
export const ICommandLineProcessing = createDecorator<ICommandLineProcessing>('commandLineService'); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the Source EULA. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
'use strict'; | ||
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile'; | ||
import { ICommandLineProcessing } from 'sql/parts/commandLine/common/commandLine'; | ||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement'; | ||
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService'; | ||
import { IEnvironmentService } from 'vs/platform/environment/common/environment'; | ||
import * as Constants from 'sql/parts/connection/common/constants'; | ||
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; | ||
import * as platform from 'vs/platform/registry/common/platform'; | ||
import { ConnectionProviderProperties, IConnectionProviderRegistry, Extensions as ConnectionProviderExtensions } from 'sql/workbench/parts/connection/common/connectionProviderExtension'; | ||
import * as TaskUtilities from 'sql/workbench/common/taskUtilities'; | ||
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService'; | ||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; | ||
|
||
export class CommandLineService implements ICommandLineProcessing { | ||
private _connectionProfile : ConnectionProfile; | ||
private _showConnectionDialog: boolean; | ||
|
||
constructor( | ||
@IConnectionManagementService private _connectionManagementService : IConnectionManagementService, | ||
@ICapabilitiesService private _capabilitiesService : ICapabilitiesService, | ||
@IEnvironmentService private _environmentService : IEnvironmentService, | ||
@IQueryEditorService private _queryEditorService : IQueryEditorService, | ||
@IObjectExplorerService private _objectExplorerService : IObjectExplorerService, | ||
@IEditorService private _editorService : IEditorService, | ||
) | ||
{ | ||
let profile = null; | ||
if (this._environmentService && this._environmentService.args.server) { | ||
profile = new ConnectionProfile(_capabilitiesService, null); | ||
// We want connection store to use any matching password it finds | ||
profile.savePassword = true; | ||
profile.providerName = Constants.mssqlProviderName; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this mechanism will only work for MSSQL connections? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, currently. I assume we'd need to add more switches to support more server types in the future. |
||
profile.serverName = _environmentService.args.server; | ||
profile.databaseName = _environmentService.args.database ? _environmentService.args.database : ''; | ||
profile.userName = _environmentService.args.user ? _environmentService.args.user : ''; | ||
profile.authenticationType = _environmentService.args.integrated ? 'Integrated' : 'SqlLogin'; | ||
profile.connectionName = ''; | ||
profile.setOptionValue('applicationName', Constants.applicationName); | ||
profile.setOptionValue('databaseDisplayName', profile.databaseName); | ||
profile.setOptionValue('groupId', profile.groupId); | ||
} | ||
this._connectionProfile = profile; | ||
const registry = platform.Registry.as<IConnectionProviderRegistry>(ConnectionProviderExtensions.ConnectionProviderContributions); | ||
let sqlProvider = registry.getProperties( Constants.mssqlProviderName); | ||
// We can't connect to object explorer until the MSSQL connection provider is registered | ||
if (sqlProvider) { | ||
this.processCommandLine(); | ||
} else { | ||
registry.onNewProvider(e => { | ||
if (e.id === Constants.mssqlProviderName) | ||
{ | ||
this.processCommandLine(); | ||
} | ||
}); | ||
} | ||
} | ||
public _serviceBrand: any; | ||
public processCommandLine(): void { | ||
if (!this._connectionProfile && !this._connectionManagementService.hasRegisteredServers()) { | ||
// prompt the user for a new connection on startup if no profiles are registered | ||
this._connectionManagementService.showConnectionDialog(); | ||
} else if (this._connectionProfile) { | ||
this._connectionManagementService.connectIfNotConnected(this._connectionProfile, 'connection') | ||
.then(result => TaskUtilities.newQuery(this._connectionProfile, | ||
this._connectionManagementService, | ||
this._queryEditorService, | ||
this._objectExplorerService, | ||
this._editorService)) | ||
.catch(() => {}); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the Source EULA. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
'use strict'; | ||
import * as Constants from 'sql/parts/connection/common/constants'; | ||
import * as Utils from 'sql/parts/connection/common/utils'; | ||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; | ||
import * as sqlops from 'sqlops'; | ||
|
||
import { TPromise } from 'vs/base/common/winjs.base'; | ||
import * as assert from 'assert'; | ||
import * as TypeMoq from 'typemoq'; | ||
|
||
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile'; | ||
import { CommandLineService } from 'sql/parts/commandLine/common/commandLineService'; | ||
import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; | ||
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; | ||
import { CapabilitiesService, ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService'; | ||
import { CapabilitiesTestService } from 'sqltest/stubs/capabilitiesTestService'; | ||
import { QueryEditorService } from 'sql/parts/query/services/queryEditorService'; | ||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; | ||
import { ObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService'; | ||
import { | ||
IConnectionManagementService, IConnectionDialogService, INewConnectionParams, | ||
ConnectionType, IConnectableInput, IConnectionCompletionOptions, IConnectionCallbacks, | ||
IConnectionParams, IConnectionResult, IServerGroupController, IServerGroupDialogCallbacks, | ||
RunQueryOnConnectionMode | ||
} from 'sql/parts/connection/common/connectionManagement'; | ||
import { ConnectionStore } from 'sql/parts/connection/common/connectionStore'; | ||
import { TestConnectionManagementService } from 'sqltest/stubs/connectionManagementService.test'; | ||
|
||
|
||
class TestParsedArgs implements ParsedArgs{ | ||
[arg: string]: any; | ||
_: string[]; | ||
aad?: boolean; | ||
add?: boolean; | ||
database?:string; | ||
debugBrkPluginHost?: string; | ||
debugBrkSearch?: string; | ||
debugId?: string; | ||
debugPluginHost?: string; | ||
debugSearch?: string; | ||
diff?: boolean; | ||
'disable-crash-reporter'?: string; | ||
'disable-extension'?: string | string[]; | ||
'disable-extensions'?: boolean; | ||
'disable-restore-windows'?: boolean; | ||
'disable-telemetry'?: boolean; | ||
'disable-updates'?: string; | ||
'driver'?: string; | ||
'enable-proposed-api'?: string | string[]; | ||
'export-default-configuration'?: string; | ||
'extensions-dir'?: string; | ||
extensionDevelopmentPath?: string; | ||
extensionTestsPath?: string; | ||
'file-chmod'?: boolean; | ||
'file-write'?: boolean; | ||
'folder-uri'?: string | string[]; | ||
goto?: boolean; | ||
help?: boolean; | ||
'install-extension'?: string | string[]; | ||
'install-source'?: string; | ||
integrated?: boolean; | ||
'list-extensions'?: boolean; | ||
locale?: string; | ||
log?: string; | ||
logExtensionHostCommunication?: boolean; | ||
'max-memory'?: number; | ||
'new-window'?: boolean; | ||
'open-url'?: boolean; | ||
performance?: boolean; | ||
'prof-append-timers'?: string; | ||
'prof-startup'?: string; | ||
'prof-startup-prefix'?: string; | ||
'reuse-window'?: boolean; | ||
server?: string; | ||
'show-versions'?: boolean; | ||
'skip-add-to-recently-opened'?: boolean; | ||
'skip-getting-started'?: boolean; | ||
'skip-release-notes'?: boolean; | ||
status?: boolean; | ||
'sticky-quickopen'?: boolean; | ||
'uninstall-extension'?: string | string[]; | ||
'unity-launch'?: boolean; // Always open a new window, except if opening the first window or opening a file or folder as part of the launch. | ||
'upload-logs'?: string; | ||
user?: string; | ||
'user-data-dir'?: string; | ||
_urls?: string[]; | ||
verbose?: boolean; | ||
version?: boolean; | ||
wait?: boolean; | ||
waitMarkerFilePath?: string; | ||
} | ||
suite('commandLineService tests', () => { | ||
|
||
let capabilitiesService: CapabilitiesTestService; | ||
let commandLineService : CommandLineService; | ||
let environmentService : TypeMoq.Mock<EnvironmentService>; | ||
let queryEditorService : TypeMoq.Mock<QueryEditorService>; | ||
let editorService:TypeMoq.Mock<IEditorService>; | ||
let objectExplorerService : TypeMoq.Mock<ObjectExplorerService>; | ||
let connectionStore: TypeMoq.Mock<ConnectionStore>; | ||
|
||
setup(() => { | ||
capabilitiesService = new CapabilitiesTestService(); | ||
connectionStore = TypeMoq.Mock.ofType(ConnectionStore); | ||
}); | ||
|
||
function getCommandLineService(connectionManagementService : IConnectionManagementService, | ||
environmentService? : IEnvironmentService, | ||
capabilitiesService? : ICapabilitiesService | ||
) : CommandLineService | ||
{ | ||
let service= new CommandLineService( | ||
connectionManagementService, | ||
capabilitiesService, | ||
environmentService, | ||
undefined, | ||
undefined, | ||
undefined | ||
); | ||
return service; | ||
} | ||
|
||
test('processCommandLine shows connection dialog by default', done => { | ||
const connectionManagementService : TypeMoq.Mock<IConnectionManagementService> | ||
= TypeMoq.Mock.ofType<IConnectionManagementService>(TestConnectionManagementService, TypeMoq.MockBehavior.Strict); | ||
|
||
connectionManagementService.setup((c) => c.showConnectionDialog()).verifiable(); | ||
connectionManagementService.setup(c => c.hasRegisteredServers()).returns(() => false); | ||
connectionManagementService.setup(c => c.connectIfNotConnected(TypeMoq.It.isAny(), TypeMoq.It.isAny())) | ||
.verifiable(TypeMoq.Times.never()); | ||
let service = getCommandLineService(connectionManagementService.object); | ||
service.processCommandLine(); | ||
connectionManagementService.verifyAll(); | ||
done(); | ||
}); | ||
|
||
test('processCommandLine does nothing if registered servers exist and no server name is provided', done => { | ||
const connectionManagementService : TypeMoq.Mock<IConnectionManagementService> | ||
= TypeMoq.Mock.ofType<IConnectionManagementService>(TestConnectionManagementService, TypeMoq.MockBehavior.Strict); | ||
|
||
connectionManagementService.setup((c) => c.showConnectionDialog()).verifiable(TypeMoq.Times.never()); | ||
connectionManagementService.setup(c => c.hasRegisteredServers()).returns(() => true); | ||
connectionManagementService.setup(c => c.connectIfNotConnected(TypeMoq.It.isAny(), TypeMoq.It.isAny())) | ||
.verifiable(TypeMoq.Times.never()); | ||
let service = getCommandLineService(connectionManagementService.object); | ||
service.processCommandLine(); | ||
connectionManagementService.verifyAll(); | ||
done(); | ||
}); | ||
|
||
test('processCommandLine opens a new connection if a server name is passed', done => { | ||
const connectionManagementService : TypeMoq.Mock<IConnectionManagementService> | ||
= TypeMoq.Mock.ofType<IConnectionManagementService>(TestConnectionManagementService, TypeMoq.MockBehavior.Strict); | ||
|
||
const environmentService : TypeMoq.Mock<IEnvironmentService> = TypeMoq.Mock.ofType<IEnvironmentService>(EnvironmentService); | ||
const args : TestParsedArgs = new TestParsedArgs(); | ||
args.server = 'myserver'; | ||
args.database = 'mydatabase'; | ||
environmentService.setup(e => e.args).returns(() => args).verifiable(TypeMoq.Times.atLeastOnce()); | ||
connectionManagementService.setup((c) => c.showConnectionDialog()).verifiable(TypeMoq.Times.never()); | ||
connectionManagementService.setup(c => c.hasRegisteredServers()).returns(() => true).verifiable(TypeMoq.Times.atMostOnce()); | ||
connectionManagementService.setup(c => c.connectIfNotConnected(TypeMoq.It.isAny(), 'connection')) | ||
.returns(() => new Promise<string>((resolve, reject) => { reject('unused');})) | ||
.verifiable(TypeMoq.Times.once()); | ||
let service = getCommandLineService(connectionManagementService.object, environmentService.object, capabilitiesService); | ||
service.processCommandLine(); | ||
environmentService.verifyAll(); | ||
connectionManagementService.verifyAll(); | ||
done(); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it the case we always want to save the password? there are some security concerns here (particularly on Linux where we don't have an encryption library and depend only on filesystem ACLs)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ConnectionStore only looks for saved credentials that match the profile if savePassword is true. Also, we never pass any password on the command line. So if there's no saved credential that matches the server name/user name/database name combination, this connection attempt will fail, and the user will be shown the connection dialog. At that point they have the option to check/uncheck the Remember password checkbox.