зеркало из https://github.com/microsoft/go-sqlcmd.git
Add azidentity-based AAD auth to sqlcmd (#15)
* basic azure auth support * add tests for AAD auth * pipeline fixes * pipeline fix * fix variable cyclic reference * fix file name in pipeline * merge coverage data * fix missing quote * remove unneeded scope * fix test for azure auth
This commit is contained in:
Родитель
44e5b52904
Коммит
63b8f6c791
|
@ -1,80 +1,51 @@
|
|||
variables:
|
||||
# AZURE_CLIENT_SECRET and SQLPASSWORD must be defined as secret variables in the pipeline.
|
||||
# AZURE_TENANT_ID and AZURE_CLIENT_ID are not expected to be secret variables, just regular variables
|
||||
AZURECLIENTSECRET: $(AZURE_CLIENT_SECRET)
|
||||
PASSWORD: $(SQLPASSWORD)
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- task: GoTool@0
|
||||
inputs:
|
||||
version: '1.16.5'
|
||||
- task: Go@0
|
||||
displayName: 'Go: get dependencies'
|
||||
inputs:
|
||||
command: 'get'
|
||||
arguments: '-d'
|
||||
workingDirectory: '$(Build.SourcesDirectory)/cmd/sqlcmd'
|
||||
steps:
|
||||
- template: include-install-go-tools.yml
|
||||
|
||||
- task: Docker@2
|
||||
displayName: 'Run SQL 2017 docker image'
|
||||
inputs:
|
||||
command: run
|
||||
arguments: '-m 2GB -e ACCEPT_EULA=1 -d --name sql2017 -p:1433:1433 -e SA_PASSWORD=$(PASSWORD) mcr.microsoft.com/mssql/server:2017-latest'
|
||||
|
||||
- task: Go@0
|
||||
displayName: 'Go: install gotest.tools/gotestsum'
|
||||
inputs:
|
||||
command: 'custom'
|
||||
customCommand: 'install'
|
||||
arguments: 'gotest.tools/gotestsum@latest'
|
||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||
|
||||
- task: Go@0
|
||||
displayName: 'Go: install github.com/axw/gocov/gocov'
|
||||
inputs:
|
||||
command: 'custom'
|
||||
customCommand: 'install'
|
||||
arguments: 'github.com/axw/gocov/gocov@latest'
|
||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||
|
||||
- task: Go@0
|
||||
displayName: 'Go: install github.com/axw/gocov/gocov'
|
||||
inputs:
|
||||
command: 'custom'
|
||||
customCommand: 'install'
|
||||
arguments: 'github.com/AlekSi/gocov-xml@latest'
|
||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||
|
||||
#Your build pipeline references an undefined variables named SQLPASSWORD.
|
||||
#Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972
|
||||
|
||||
- task: Docker@2
|
||||
displayName: 'Run SQL 2017 docker image'
|
||||
inputs:
|
||||
command: run
|
||||
arguments: '-m 2GB -e ACCEPT_EULA=1 -d --name sql2017 -p:1433:1433 -e SA_PASSWORD=$(SQLPASSWORD) mcr.microsoft.com/mssql/server:2017-latest'
|
||||
|
||||
- script: |
|
||||
~/go/bin/gotestsum --junitfile testresults.xml -- ./... -coverprofile=coverage.txt -covermode count
|
||||
~/go/bin/gocov convert coverage.txt > coverage.json
|
||||
~/go/bin/gocov-xml < coverage.json > coverage.xml
|
||||
mkdir coverage
|
||||
workingDirectory: '$(Build.SourcesDirectory)'
|
||||
displayName: 'run tests'
|
||||
env:
|
||||
SQLPASSWORD: $(SQLPASSWORD)
|
||||
SQLCMDUSER: sa
|
||||
SQLCMDPASSWORD: $(SQLPASSWORD)
|
||||
continueOnError: true
|
||||
- task: PublishTestResults@2
|
||||
displayName: "Publish junit-style results"
|
||||
inputs:
|
||||
testResultsFiles: 'testresults.xml'
|
||||
testResultsFormat: JUnit
|
||||
searchFolder: '$(Build.SourcesDirectory)'
|
||||
testRunTitle: 'SQL 2017 - $(Build.SourceBranchName)'
|
||||
condition: always()
|
||||
continueOnError: true
|
||||
|
||||
- task: PublishCodeCoverageResults@1
|
||||
inputs:
|
||||
codeCoverageTool: Cobertura
|
||||
pathToSources: '$(Build.SourcesDirectory)'
|
||||
summaryFileLocation: $(Build.SourcesDirectory)/**/coverage.xml
|
||||
reportDirectory: $(Build.SourcesDirectory)/**/coverage
|
||||
failIfCoverageEmpty: true
|
||||
condition: always()
|
||||
continueOnError: true
|
||||
- template: include-runtests-linux.yml
|
||||
parameters:
|
||||
RunName: 'SQL 2017'
|
||||
SQLCMDUSER: sa
|
||||
SQLPASSWORD: $(PASSWORD)
|
||||
|
||||
- template: include-runtests-linux.yml
|
||||
parameters:
|
||||
RunName: 'SQL DB'
|
||||
# AZURESERVER must be defined as a variable in the pipeline
|
||||
SQLCMDSERVER: $(AZURESERVER)
|
||||
AZURECLIENTSECRET: $(AZURECLIENTSECRET)
|
||||
|
||||
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
|
||||
displayName: Merge coverage data
|
||||
inputs:
|
||||
reports: '"SQL 2017.coverage.xml";"SQL DB.coverage.xml"' # REQUIRED # The coverage reports that should be parsed (separated by semicolon). Globbing is supported.
|
||||
targetdir: 'coverage' # REQUIRED # The directory where the generated report should be saved.
|
||||
reporttypes: 'HtmlInline_AzurePipelines;Cobertura' # The output formats and scope (separated by semicolon) Values: Badges, Clover, Cobertura, CsvSummary, Html, HtmlChart, HtmlInline, HtmlInline_AzurePipelines, HtmlInline_AzurePipelines_Dark, HtmlSummary, JsonSummary, Latex, LatexSummary, lcov, MarkdownSummary, MHtml, PngChart, SonarQube, TeamCitySummary, TextSummary, Xml, XmlSummary
|
||||
sourcedirs: '$(Build.SourcesDirectory)' # Optional directories which contain the corresponding source code (separated by semicolon). The source directories are used if coverage report contains classes without path information.
|
||||
verbosity: 'Info' # The verbosity level of the log messages. Values: Verbose, Info, Warning, Error, Off
|
||||
tag: '$(build.buildnumber)_#$(build.buildid)_$(Build.SourceBranchName)' # Optional tag or build version.
|
||||
- task: PublishCodeCoverageResults@1
|
||||
inputs:
|
||||
codeCoverageTool: Cobertura
|
||||
pathToSources: '$(Build.SourcesDirectory)'
|
||||
summaryFileLocation: $(Build.SourcesDirectory)/coverage/*.xml
|
||||
reportDirectory: $(Build.SourcesDirectory)/coverage
|
||||
failIfCoverageEmpty: true
|
||||
condition: always()
|
||||
continueOnError: true
|
||||
env:
|
||||
disable.coverage.autogenerate: 'true'
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
steps:
|
||||
- task: GoTool@0
|
||||
inputs:
|
||||
version: '1.16.5'
|
||||
- task: Go@0
|
||||
displayName: 'Go: get dependencies'
|
||||
inputs:
|
||||
command: 'get'
|
||||
arguments: '-d'
|
||||
workingDirectory: '$(Build.SourcesDirectory)/cmd/sqlcmd'
|
||||
|
||||
|
||||
- task: Go@0
|
||||
displayName: 'Go: install gotest.tools/gotestsum'
|
||||
inputs:
|
||||
command: 'custom'
|
||||
customCommand: 'install'
|
||||
arguments: 'gotest.tools/gotestsum@latest'
|
||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||
|
||||
- task: Go@0
|
||||
displayName: 'Go: install github.com/axw/gocov/gocov'
|
||||
inputs:
|
||||
command: 'custom'
|
||||
customCommand: 'install'
|
||||
arguments: 'github.com/axw/gocov/gocov@latest'
|
||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||
|
||||
- task: Go@0
|
||||
displayName: 'Go: install github.com/axw/gocov/gocov'
|
||||
inputs:
|
||||
command: 'custom'
|
||||
customCommand: 'install'
|
||||
arguments: 'github.com/AlekSi/gocov-xml@latest'
|
||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
parameters:
|
||||
- name: RunName
|
||||
type: string
|
||||
- name: SQLCMDUSER
|
||||
type: string
|
||||
default: ''
|
||||
- name: SQLPASSWORD
|
||||
type: string
|
||||
default: ''
|
||||
- name: AZURECLIENTSECRET
|
||||
type: string
|
||||
default: ''
|
||||
- name: SQLCMDSERVER
|
||||
type: string
|
||||
default: .
|
||||
- name: SQLCMDDBNAME
|
||||
type: string
|
||||
default: ''
|
||||
steps:
|
||||
- script: |
|
||||
~/go/bin/gotestsum --junitfile "${{ parameters.RunName }}.testresults.xml" -- ./... -coverprofile="${{ parameters.RunName }}.coverage.txt" -covermode count
|
||||
~/go/bin/gocov convert "${{ parameters.RunName }}.coverage.txt" > "${{ parameters.RunName }}.coverage.json"
|
||||
~/go/bin/gocov-xml < "${{ parameters.RunName }}.coverage.json" > "${{ parameters.RunName }}.coverage.xml"
|
||||
mkdir -p coverage
|
||||
workingDirectory: '$(Build.SourcesDirectory)'
|
||||
displayName: 'run tests'
|
||||
env:
|
||||
SQLPASSWORD: ${{ parameters.SQLPASSWORD }}
|
||||
SQLCMDUSER: ${{ parameters.SQLCMDUSER }}
|
||||
SQLCMDPASSWORD: ${{ parameters.SQLPASSWORD }}
|
||||
AZURE_TENANT_ID: $(AZURE_TENANT_ID)
|
||||
AZURE_CLIENT_ID: $(AZURE_CLIENT_ID)
|
||||
AZURE_CLIENT_SECRET: ${{ parameters.AZURECLIENTSECRET }}
|
||||
SQLCMDSERVER: ${{ parameters.SQLCMDSERVER }}
|
||||
SQLCMDDBNAME: ${{ parameters.SQLCMDDBNAME }}
|
||||
continueOnError: true
|
||||
|
||||
- task: PublishTestResults@2
|
||||
displayName: "Publish junit-style results"
|
||||
inputs:
|
||||
testResultsFiles: '"${{ parameters.RunName }}.coverage.xml"'
|
||||
testResultsFormat: JUnit
|
||||
searchFolder: '$(Build.SourcesDirectory)'
|
||||
testRunTitle: '${{ parameters.RunName }} - $(Build.SourceBranchName)'
|
||||
condition: always()
|
||||
continueOnError: true
|
54
README.md
54
README.md
|
@ -20,6 +20,60 @@ We will be implementing as many command line switches and behaviors as possible
|
|||
- Some behaviors that were kept to maintain compatibility with `OSQL` may be changed, such as alignment of column headers for some data types.
|
||||
- All commands must fit on one line, even `EXIT`. Interactive mode will not check for open parentheses or quotes for commands and prompt for successive lines. The native sqlcmd allows the query run by `EXIT(query)` to span multiple lines.
|
||||
|
||||
### Azure Active Directory Authentication
|
||||
|
||||
This version of sqlcmd supports a broader range of AAD authentication models, based on the [azidentity package](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity).
|
||||
|
||||
#### Command line
|
||||
|
||||
To use AAD auth, you can use one of two command line switches
|
||||
|
||||
`-G` is (mostly) compatible with its usage in the prior version of sqlcmd. If a user name and password are provided, it will authenticate using AAD Password authentication. If a user name is provided it will use AAD Interactive authentication which may display a web browser. If no user name or password is provided, it will use a DefaultAzureCredential which attempts to authenticate through a variety of mechanisms.
|
||||
|
||||
`--authentication-method=` can be used to specify one of the following authentication types.
|
||||
|
||||
`ActiveDirectoryDefault`
|
||||
|
||||
- For an overview of the types of authentication this mode will use, see (<https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/azidentity#defaultazurecredential>).
|
||||
- Choose this method if your database automation scripts are intended to run in both local development environments and in a production deployment in Azure. You'll be able to use a client secret or an Azure CLI login on your development environment and a managed identity or client secret on your production deployment without changing the script.
|
||||
- Setting environment variables AZURE_TENANT_ID, and AZURE_CLIENT_ID are necessary for DefaultAzureCredential to begin checking the environment configuration and look for one of the following additional environment variables in order to authenticate:
|
||||
|
||||
- Setting environment variable AZURE_CLIENT_SECRET configures the DefaultAzureCredential to choose ClientSecretCredential.
|
||||
- Setting environment variable AZURE_CLIENT_CERTIFICATE_PATH configures the DefaultAzureCredential to choose ClientCertificateCredential if AZURE_CLIENT_SECRET is not set.
|
||||
- Setting environment variable AZURE_USERNAME configures the DefaultAzureCredential to choose UsernamePasswordCredential if AZURE_CLIENT_SECRET and AZURE_CLIENT_CERTIFICATE_PATH are not set.
|
||||
|
||||
`ActiveDirectoryIntegrated`
|
||||
|
||||
This method is currently not implemented and will fall back to `ActiveDirectoryDefault`
|
||||
|
||||
`ActiveDirectoryPassword`
|
||||
|
||||
This method will authenticate using a user name and password. It will not work if MFA is required.
|
||||
You provide the user name and password using the usual command line switches or SQLCMD environment variables.
|
||||
Set `AZURE_TENANT_ID` environment variable to the tenant id of the server if not using the default tenant of the user.
|
||||
|
||||
`ActiveDirectoryInteractive`
|
||||
|
||||
This method will launch a web browser to authenticate the user.
|
||||
Set `AZURE_TENANT_ID` environment variable to the tenant id of the server if not using the default.
|
||||
|
||||
`ActiveDirectoryManagedIdentity`
|
||||
|
||||
Use this method when running sqlcmd on an Azure VM that has either a system-assigned or user-assigned managed identity. If using a user-assigned managed identity, set the user name to the ID of the managed identity. If using a system-assigned identity, leave user name empty.
|
||||
|
||||
`ActiveDirectoryServicePrincipal`
|
||||
|
||||
This method authenticates the provided user name as a service principal id and the password as the client secret for the service principal. Set `AZURE_TENANT_ID` environment variable to the tenant id of the service principal.
|
||||
|
||||
### Environment variables for AAD auth
|
||||
|
||||
Some settings for AAD auth do not have command line inputs, and some environment variables are consumed directly by the `azidentity` package used by `sqlcmd`.
|
||||
These environment variables can be set to configure some aspects of AAD auth and to bypass default behaviors. In addition to the variables listed above, the following are sqlcmd-specific and apply to multiple methods.
|
||||
|
||||
`SQLCMDAZURERESOURCE` - defines the URL of the Azure SQL database resource in the Azure cloud where the database resides. By default, `sqlcmd` attempts to match the DNS suffix of the server name with one of the well known Azure cloud DNS suffixes. If no match is found it uses `https://database.windows.net`.
|
||||
|
||||
`SQLCMDCLIENTID` - set this to the identifier of an application registered in your AAD which is authorized to authenticate to Azure SQL Database. Applies to `ActiveDirectoryInteractive` and `ActiveDirectoryPassword` methods.
|
||||
|
||||
### Packages
|
||||
|
||||
#### sqlcmd executable
|
||||
|
|
|
@ -20,7 +20,7 @@ type SQLCmdArguments struct {
|
|||
// Whether to trust the server certificate on an encrypted connection
|
||||
TrustServerCertificate bool `short:"C" help:"Implicitly trust the server certificate without validation."`
|
||||
DatabaseName string `short:"d" help:"This option sets the sqlcmd scripting variable SQLCMDDBNAME. This parameter specifies the initial database. The default is your login's default-database property. If the database does not exist, an error message is generated and sqlcmd exits."`
|
||||
UseTrustedConnection bool `short:"E" xor:"uid" help:"Uses a trusted connection instead of using a user name and password to sign in to SQL Server, ignoring any any environment variables that define user name and password."`
|
||||
UseTrustedConnection bool `short:"E" xor:"uid, auth" help:"Uses a trusted connection instead of using a user name and password to sign in to SQL Server, ignoring any any environment variables that define user name and password."`
|
||||
UserName string `short:"U" xor:"uid" help:"The login name or contained database user name. For contained database users, you must provide the database name option"`
|
||||
// Files from which to read query text
|
||||
InputFile []string `short:"i" xor:"input1, input2" type:"existingFile" help:"Identifies one or more files that contain batches of SQL statements. If one or more files do not exist, sqlcmd will exit. Mutually exclusive with -Q/-q."`
|
||||
|
@ -31,7 +31,10 @@ type SQLCmdArguments struct {
|
|||
Query string `short:"Q" xor:"input2" help:"Executes a query when sqlcmd starts and then immediately exits sqlcmd. Multiple-semicolon-delimited queries can be executed."`
|
||||
Server string `short:"S" help:"[tcp:]server[\\instance_name][,port]Specifies the instance of SQL Server to which to connect. It sets the sqlcmd scripting variable SQLCMDSERVER."`
|
||||
// Disable syscommands with a warning
|
||||
DisableCmdAndWarn bool `short:"X" xor:"syscmd" help:"Disables commands that might compromise system security. Sqlcmd issues a warning and continues."`
|
||||
DisableCmdAndWarn bool `short:"X" xor:"syscmd" help:"Disables commands that might compromise system security. Sqlcmd issues a warning and continues."`
|
||||
// AuthenticationMethod is new for go-sqlcmd
|
||||
AuthenticationMethod string `xor:"auth" help:"Specifies the SQL authentication method to use to connect to Azure SQL Database. One of:ActiveDirectoryDefault,ActiveDirectoryIntegrated,ActiveDirectoryPassword,ActiveDirectoryInteractive,ActiveDirectoryManagedIdentity,ActiveDirectoryServicePrincipal,SqlPassword"`
|
||||
UseAad bool `short:"G" xor:"auth" help:"Tells sqlcmd to use Active Directory authentication. If no user name is provided, authentication method ActiveDirectoryDefault is used. If a password is provided, ActiveDirectoryPassword is used. Otherwise ActiveDirectoryInteractive is used."`
|
||||
DisableVariableSubstitution bool `short:"x" help:"Causes sqlcmd to ignore scripting variables. This parameter is useful when a script contains many INSERT statements that may contain strings that have the same format as regular variables, such as $(variable_name)."`
|
||||
Variables map[string]string `short:"v" help:"Creates a sqlcmd scripting variable that can be used in a sqlcmd script. Enclose the value in quotation marks if the value contains spaces. You can specify multiple var=values values. If there are errors in any of the values specified, sqlcmd generates an error message and then exits"`
|
||||
}
|
||||
|
@ -51,6 +54,26 @@ func newArguments() SQLCmdArguments {
|
|||
|
||||
var args SQLCmdArguments
|
||||
|
||||
func (a SQLCmdArguments) authenticationMethod(hasPassword bool) string {
|
||||
if a.UseTrustedConnection {
|
||||
return sqlcmd.NotSpecified
|
||||
}
|
||||
if a.UseAad {
|
||||
switch {
|
||||
case a.UserName == "":
|
||||
return sqlcmd.ActiveDirectoryIntegrated
|
||||
case hasPassword:
|
||||
return sqlcmd.ActiveDirectoryPassword
|
||||
default:
|
||||
return sqlcmd.ActiveDirectoryInteractive
|
||||
}
|
||||
}
|
||||
if a.AuthenticationMethod == "" {
|
||||
return sqlcmd.NotSpecified
|
||||
}
|
||||
return a.AuthenticationMethod
|
||||
}
|
||||
|
||||
func main() {
|
||||
kong.Parse(&args)
|
||||
vars := sqlcmd.InitializeVariables(!args.DisableCmdAndWarn)
|
||||
|
@ -64,9 +87,20 @@ func main() {
|
|||
// setVars initializes scripting variables from command line arguments
|
||||
func setVars(vars *sqlcmd.Variables, args *SQLCmdArguments) {
|
||||
varmap := map[string]func(*SQLCmdArguments) string{
|
||||
sqlcmd.SQLCMDDBNAME: func(a *SQLCmdArguments) string { return a.DatabaseName },
|
||||
sqlcmd.SQLCMDLOGINTIMEOUT: func(a *SQLCmdArguments) string { return "" },
|
||||
sqlcmd.SQLCMDUSEAAD: func(a *SQLCmdArguments) string { return "" },
|
||||
sqlcmd.SQLCMDDBNAME: func(a *SQLCmdArguments) string { return a.DatabaseName },
|
||||
sqlcmd.SQLCMDLOGINTIMEOUT: func(a *SQLCmdArguments) string { return "" },
|
||||
sqlcmd.SQLCMDUSEAAD: func(a *SQLCmdArguments) string {
|
||||
if a.UseAad {
|
||||
return "true"
|
||||
}
|
||||
switch a.AuthenticationMethod {
|
||||
case sqlcmd.ActiveDirectoryIntegrated:
|
||||
case sqlcmd.ActiveDirectoryInteractive:
|
||||
case sqlcmd.ActiveDirectoryPassword:
|
||||
return "true"
|
||||
}
|
||||
return ""
|
||||
},
|
||||
sqlcmd.SQLCMDWORKSTATION: func(a *SQLCmdArguments) string { return "" },
|
||||
sqlcmd.SQLCMDSERVER: func(a *SQLCmdArguments) string { return a.Server },
|
||||
sqlcmd.SQLCMDERRORLEVEL: func(a *SQLCmdArguments) string { return "" },
|
||||
|
@ -124,6 +158,7 @@ func run(vars *sqlcmd.Variables) (int, error) {
|
|||
}
|
||||
s.Connect.UseTrustedConnection = args.UseTrustedConnection
|
||||
s.Connect.TrustServerCertificate = args.TrustServerCertificate
|
||||
s.Connect.AuthenticationMethod = args.authenticationMethod(s.Connect.Password != "")
|
||||
s.Connect.DisableEnvironmentVariables = args.DisableCmdAndWarn
|
||||
s.Connect.DisableVariableSubstitution = args.DisableVariableSubstitution
|
||||
s.Format = sqlcmd.NewSQLCmdDefaultFormatter(false)
|
||||
|
|
|
@ -105,6 +105,9 @@ func TestRunInputFiles(t *testing.T) {
|
|||
args = newArguments()
|
||||
args.InputFile = []string{"testdata/select100.sql", "testdata/select100.sql"}
|
||||
args.OutputFile = o.Name()
|
||||
if canTestAzureAuth() {
|
||||
args.UseAad = true
|
||||
}
|
||||
vars := sqlcmd.InitializeVariables(!args.DisableCmdAndWarn)
|
||||
vars.Set(sqlcmd.SQLCMDMAXVARTYPEWIDTH, "0")
|
||||
setVars(vars, &args)
|
||||
|
@ -127,6 +130,9 @@ func TestQueryAndExit(t *testing.T) {
|
|||
args.Query = "SELECT '$(VAR1) $(VAR2)'"
|
||||
args.OutputFile = o.Name()
|
||||
args.Variables = map[string]string{"var2": "val2"}
|
||||
if canTestAzureAuth() {
|
||||
args.UseAad = true
|
||||
}
|
||||
vars := sqlcmd.InitializeVariables(!args.DisableCmdAndWarn)
|
||||
vars.Set(sqlcmd.SQLCMDMAXVARTYPEWIDTH, "0")
|
||||
vars.Set("VAR1", "100")
|
||||
|
@ -140,3 +146,37 @@ func TestQueryAndExit(t *testing.T) {
|
|||
assert.Equal(t, "100 val2"+sqlcmd.SqlcmdEol+sqlcmd.SqlcmdEol, string(bytes), "Incorrect output from run")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAzureAuth(t *testing.T) {
|
||||
|
||||
if !canTestAzureAuth() {
|
||||
t.Skip("AZURE auth environment variables are not set or server name is not an Azure DB name")
|
||||
}
|
||||
o, err := os.CreateTemp("", "sqlcmdmain")
|
||||
assert.NoError(t, err, "os.CreateTemp")
|
||||
defer os.Remove(o.Name())
|
||||
defer o.Close()
|
||||
args = newArguments()
|
||||
args.Query = "SELECT 'AZURE'"
|
||||
args.OutputFile = o.Name()
|
||||
args.UseAad = true
|
||||
vars := sqlcmd.InitializeVariables(!args.DisableCmdAndWarn)
|
||||
vars.Set(sqlcmd.SQLCMDMAXVARTYPEWIDTH, "0")
|
||||
setVars(vars, &args)
|
||||
|
||||
exitCode, err := run(vars)
|
||||
assert.NoError(t, err, "run")
|
||||
assert.Equal(t, 0, exitCode, "exitCode")
|
||||
bytes, err := os.ReadFile(o.Name())
|
||||
if assert.NoError(t, err, "os.ReadFile") {
|
||||
assert.Equal(t, "AZURE"+sqlcmd.SqlcmdEol+sqlcmd.SqlcmdEol, string(bytes), "Incorrect output from run")
|
||||
}
|
||||
}
|
||||
|
||||
func canTestAzureAuth() bool {
|
||||
tenant := os.Getenv("AZURE_TENANT_ID")
|
||||
clientId := os.Getenv("AZURE_CLIENT_ID")
|
||||
clientSecret := os.Getenv("AZURE_CLIENT_SECRET")
|
||||
server := os.Getenv("SQLCMDSERVER")
|
||||
return tenant != "" && clientId != "" && clientSecret != "" && strings.Contains(server, ".database.")
|
||||
}
|
||||
|
|
4
go.mod
4
go.mod
|
@ -3,7 +3,9 @@ module github.com/microsoft/go-sqlcmd
|
|||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/alecthomas/kong v0.2.17
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0
|
||||
github.com/alecthomas/kong v0.2.18-0.20210621093454-54558f65e86f
|
||||
github.com/chzyer/logex v1.1.10 // indirect
|
||||
github.com/chzyer/test v0.0.0-20210722231415-061457976a23 // indirect
|
||||
github.com/denisenkom/go-mssqldb v0.10.0
|
||||
|
|
33
go.sum
33
go.sum
|
@ -1,5 +1,13 @@
|
|||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0 h1:lhSJz9RMbJcTgxifR1hUNJnn6CNYtbgEDtQV22/9RBA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0 h1:OYa9vmRX2XC5GXRAzeggG12sF/z5D9Ahtdm9EJ00WN4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0 h1:v9p9TfTbf7AwNb5NYQt7hI41IfPoLFiFkLtb+bmGjT0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
|
||||
github.com/alecthomas/kong v0.2.17 h1:URDISCI96MIgcIlQyoCAlhOmrSw6pZScBNkctg8r0W0=
|
||||
github.com/alecthomas/kong v0.2.17/go.mod h1:ka3VZ8GZNPXv9Ov+j4YNLkI8mTuhXyr/0ktSlqIydQQ=
|
||||
github.com/alecthomas/kong v0.2.18-0.20210621093454-54558f65e86f h1:VgRM6/wqZIB1D9W3XMllm/wplTmPgI5yvCHUXEsmKps=
|
||||
github.com/alecthomas/kong v0.2.18-0.20210621093454-54558f65e86f/go.mod h1:ka3VZ8GZNPXv9Ov+j4YNLkI8mTuhXyr/0ktSlqIydQQ=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ=
|
||||
|
@ -9,12 +17,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8=
|
||||
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/gohxs/readline v0.0.0-20171011095936-a780388e6e7c h1:yE35fKFwcelIte3q5q1/cPiY7pI7vvf5/j/0ddxNCKs=
|
||||
github.com/gohxs/readline v0.0.0-20171011095936-a780388e6e7c/go.mod h1:9S/fKAutQ6wVHqm1jnp9D9sc5hu689s9AaTWFS92LaU=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
|
||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
@ -22,12 +34,29 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package sqlcmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
mssql "github.com/denisenkom/go-mssqldb"
|
||||
)
|
||||
|
||||
const (
|
||||
ActiveDirectoryDefault = "ActiveDirectoryDefault"
|
||||
ActiveDirectoryIntegrated = "ActiveDirectoryIntegrated"
|
||||
ActiveDirectoryPassword = "ActiveDirectoryPassword"
|
||||
ActiveDirectoryInteractive = "ActiveDirectoryInteractive"
|
||||
ActiveDirectoryManagedIdentity = "ActiveDirectoryManagedIdentity"
|
||||
ActiveDirectoryServicePrincipal = "ActiveDirectoryServicePrincipal"
|
||||
SqlPassword = "SqlPassword"
|
||||
NotSpecified = "NotSpecified"
|
||||
sqlClientId = "a94f9c62-97fe-4d19-b06d-472bed8d2bcf"
|
||||
)
|
||||
|
||||
func azureTenantId() string {
|
||||
t := os.Getenv("AZURE_TENANT_ID")
|
||||
if t == "" {
|
||||
t = "common"
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
var resourceMap = map[string]string{
|
||||
".database.chinacloudapi.cn": "https://database.chinacloudapi.cn/",
|
||||
".database.cloudapi.de": "https://database.cloudapi.de/",
|
||||
".database.usgovcloudapi.net": "https://database.usgovcloudapi.net/",
|
||||
".database.windows.net": "https://database.windows.net/",
|
||||
}
|
||||
|
||||
func (s *Sqlcmd) getResourceUrl() string {
|
||||
resource := os.Getenv("SQLCMDAZURERESOURCE")
|
||||
if resource == "" {
|
||||
server, _, _, _ := s.vars.SQLCmdServer()
|
||||
for k := range resourceMap {
|
||||
if strings.HasSuffix(strings.ToLower(server), k) {
|
||||
return resourceMap[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
return "https://database.windows.net"
|
||||
}
|
||||
|
||||
func getSqlClientId() string {
|
||||
if clientId := os.Getenv("SQLCMDCLIENTID"); clientId != "" {
|
||||
return clientId
|
||||
}
|
||||
return sqlClientId
|
||||
}
|
||||
|
||||
func (s *Sqlcmd) GetTokenBasedConnection(connstr string, user string, password string) (driver.Connector, error) {
|
||||
var cred azcore.TokenCredential
|
||||
var err error
|
||||
scope := ".default"
|
||||
t := azureTenantId()
|
||||
switch s.Connect.AuthenticationMethod {
|
||||
case ActiveDirectoryDefault:
|
||||
cred, err = azidentity.NewDefaultAzureCredential(nil)
|
||||
case ActiveDirectoryInteractive:
|
||||
cred, err = azidentity.NewInteractiveBrowserCredential(&azidentity.InteractiveBrowserCredentialOptions{TenantID: t, ClientID: getSqlClientId()})
|
||||
case ActiveDirectoryPassword:
|
||||
cred, err = azidentity.NewUsernamePasswordCredential(t, getSqlClientId(), user, password, nil)
|
||||
case ActiveDirectoryManagedIdentity:
|
||||
cred, err = azidentity.NewManagedIdentityCredential(user, nil)
|
||||
case ActiveDirectoryServicePrincipal:
|
||||
cred, err = azidentity.NewClientSecretCredential(t, user, password, nil)
|
||||
default:
|
||||
// no implementation of AAD Integrated yet
|
||||
cred, err = azidentity.NewDefaultAzureCredential(nil)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resourceUrl := s.getResourceUrl()
|
||||
conn, err := mssql.NewAccessTokenConnector(connstr, func() (string, error) {
|
||||
opts := policy.TokenRequestOptions{Scopes: []string{resourceUrl + scope}}
|
||||
tk, err := cred.GetToken(context.Background(), opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tk.Token, err
|
||||
})
|
||||
|
||||
return conn, err
|
||||
}
|
|
@ -6,6 +6,7 @@ package sqlcmd
|
|||
import (
|
||||
"bufio"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -37,6 +38,7 @@ type ConnectSettings struct {
|
|||
UseTrustedConnection bool
|
||||
// TrustServerCertificate sets the TrustServerCertificate setting on the connection string
|
||||
TrustServerCertificate bool
|
||||
AuthenticationMethod string
|
||||
// DisableEnvironmentVariables determines if sqlcmd resolves scripting variables from the process environment
|
||||
DisableEnvironmentVariables bool
|
||||
// DisableVariableSubstitution determines if scripting variables should be evaluated
|
||||
|
@ -45,6 +47,13 @@ type ConnectSettings struct {
|
|||
Password string
|
||||
}
|
||||
|
||||
func (c ConnectSettings) authenticationMethod() string {
|
||||
if c.AuthenticationMethod == "" {
|
||||
return NotSpecified
|
||||
}
|
||||
return c.AuthenticationMethod
|
||||
}
|
||||
|
||||
// Sqlcmd is the core processor for text lines.
|
||||
//
|
||||
// It accumulates non-command lines in a buffer and and sends command lines to the appropriate command runner.
|
||||
|
@ -208,8 +217,8 @@ func (s *Sqlcmd) ConnectionString() (connectionString string, err error) {
|
|||
Scheme: "sqlserver",
|
||||
Path: instance,
|
||||
}
|
||||
useTrustedConnection := s.Connect.UseTrustedConnection || (s.vars.SQLCmdUser() == "" && !s.vars.UseAad())
|
||||
if !useTrustedConnection {
|
||||
|
||||
if s.sqlAuthentication() {
|
||||
connectionURL.User = url.UserPassword(s.vars.SQLCmdUser(), s.Connect.Password)
|
||||
}
|
||||
if port > 0 {
|
||||
|
@ -257,21 +266,33 @@ func (s *Sqlcmd) ConnectDb(server string, user string, password string, nopw boo
|
|||
}
|
||||
}
|
||||
|
||||
var connector driver.Connector
|
||||
// To determine whether to use Sql auth/windows auth/aad auth, compare the current ConnectSettings with the new parameters
|
||||
// If sqlcmd was started with sql auth or windows auth, :connect will not switch to AAD
|
||||
// if sqlcmd was started with AAD auth, it will remain in some variant of AAD auth depending on the user/password combination
|
||||
useAad := !s.sqlAuthentication() && !s.integratedAuthentication()
|
||||
if password == "" {
|
||||
password = s.Connect.Password
|
||||
}
|
||||
if !useAad {
|
||||
if user != "" {
|
||||
connectionURL.User = url.UserPassword(user, password)
|
||||
}
|
||||
|
||||
if user != "" {
|
||||
connectionURL.User = url.UserPassword(user, password)
|
||||
connector, err = mssql.NewConnector(connectionURL.String())
|
||||
} else {
|
||||
if user == "" {
|
||||
user = s.vars.SQLCmdUser()
|
||||
}
|
||||
connector, err = s.GetTokenBasedConnection(connectionURL.String(), user, password)
|
||||
}
|
||||
|
||||
connector, err := mssql.NewConnector(connectionURL.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db := sql.OpenDB(connector)
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
fmt.Fprintln(s.GetOutput(), err)
|
||||
return err
|
||||
}
|
||||
// we got a good connection so we can update the Sqlcmd
|
||||
|
@ -291,7 +312,9 @@ func (s *Sqlcmd) ConnectDb(server string, user string, password string, nopw boo
|
|||
if e != nil {
|
||||
panic("Unable to get user name")
|
||||
}
|
||||
s.Connect.UseTrustedConnection = true
|
||||
if !useAad {
|
||||
s.Connect.UseTrustedConnection = true
|
||||
}
|
||||
s.vars.Set(SQLCMDUSER, u.Username)
|
||||
}
|
||||
|
||||
|
@ -384,6 +407,15 @@ func setupCloseHandler(s *Sqlcmd) {
|
|||
}()
|
||||
}
|
||||
|
||||
func (s *Sqlcmd) integratedAuthentication() bool {
|
||||
return s.Connect.UseTrustedConnection || (s.vars.SQLCmdUser() == "" && s.Connect.authenticationMethod() == NotSpecified)
|
||||
}
|
||||
|
||||
func (s *Sqlcmd) sqlAuthentication() bool {
|
||||
return s.Connect.authenticationMethod() == SqlPassword ||
|
||||
(!s.Connect.UseTrustedConnection && s.Connect.authenticationMethod() == NotSpecified && s.vars.SQLCmdUser() != "")
|
||||
}
|
||||
|
||||
// runQuery runs the query and prints the results
|
||||
// The return value is based on the first cell of the last column of the last result set.
|
||||
// If it's numeric, it will be converted to int
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
@ -84,7 +85,12 @@ set will be to localhost using Windows auth.
|
|||
func TestSqlCmdConnectDb(t *testing.T) {
|
||||
v := InitializeVariables(true)
|
||||
s := &Sqlcmd{vars: v}
|
||||
s.Connect.Password = os.Getenv(SQLCMDPASSWORD)
|
||||
if canTestAzureAuth() {
|
||||
s.Connect.AuthenticationMethod = ActiveDirectoryDefault
|
||||
} else {
|
||||
s.Connect.Password = os.Getenv(SQLCMDPASSWORD)
|
||||
}
|
||||
|
||||
err := s.ConnectDb("", "", "", false)
|
||||
if assert.NoError(t, err, "ConnectDb should succeed") {
|
||||
sqlcmduser := os.Getenv(SQLCMDUSER)
|
||||
|
@ -99,7 +105,11 @@ func TestSqlCmdConnectDb(t *testing.T) {
|
|||
func ConnectDb() (*sql.DB, error) {
|
||||
v := InitializeVariables(true)
|
||||
s := &Sqlcmd{vars: v}
|
||||
s.Connect.Password = os.Getenv(SQLCMDPASSWORD)
|
||||
if canTestAzureAuth() {
|
||||
s.Connect.AuthenticationMethod = ActiveDirectoryDefault
|
||||
} else {
|
||||
s.Connect.Password = os.Getenv(SQLCMDPASSWORD)
|
||||
}
|
||||
err := s.ConnectDb("", "", "", false)
|
||||
return s.db, err
|
||||
}
|
||||
|
@ -225,7 +235,11 @@ func setupSqlCmdWithMemoryOutput(t testing.TB) (*Sqlcmd, *memoryBuffer) {
|
|||
v := InitializeVariables(true)
|
||||
v.Set(SQLCMDMAXVARTYPEWIDTH, "0")
|
||||
s := New(nil, "", v)
|
||||
s.Connect.Password = os.Getenv(SQLCMDPASSWORD)
|
||||
if canTestAzureAuth() {
|
||||
s.Connect.AuthenticationMethod = ActiveDirectoryDefault
|
||||
} else {
|
||||
s.Connect.Password = os.Getenv(SQLCMDPASSWORD)
|
||||
}
|
||||
s.Format = NewSQLCmdDefaultFormatter(true)
|
||||
buf := &memoryBuffer{buf: new(bytes.Buffer)}
|
||||
s.SetOutput(buf)
|
||||
|
@ -238,7 +252,11 @@ func setupSqlcmdWithFileOutput(t testing.TB) (*Sqlcmd, *os.File) {
|
|||
v := InitializeVariables(true)
|
||||
v.Set(SQLCMDMAXVARTYPEWIDTH, "0")
|
||||
s := New(nil, "", v)
|
||||
s.Connect.Password = os.Getenv(SQLCMDPASSWORD)
|
||||
if canTestAzureAuth() {
|
||||
s.Connect.AuthenticationMethod = ActiveDirectoryDefault
|
||||
} else {
|
||||
s.Connect.Password = os.Getenv(SQLCMDPASSWORD)
|
||||
}
|
||||
s.Format = NewSQLCmdDefaultFormatter(true)
|
||||
file, err := os.CreateTemp("", "sqlcmdout")
|
||||
assert.NoError(t, err, "os.CreateTemp")
|
||||
|
@ -247,3 +265,11 @@ func setupSqlcmdWithFileOutput(t testing.TB) (*Sqlcmd, *os.File) {
|
|||
assert.NoError(t, err, "s.ConnectDB")
|
||||
return s, file
|
||||
}
|
||||
|
||||
func canTestAzureAuth() bool {
|
||||
tenant := os.Getenv("AZURE_TENANT_ID")
|
||||
clientId := os.Getenv("AZURE_CLIENT_ID")
|
||||
clientSecret := os.Getenv("AZURE_CLIENT_SECRET")
|
||||
server := os.Getenv("SQLCMDSERVER")
|
||||
return tenant != "" && clientId != "" && clientSecret != "" && strings.Contains(server, ".database.")
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче