Skip to content

Commit 44d82f0

Browse files
committed
Saving changes
1 parent 8579e37 commit 44d82f0

File tree

28 files changed

+239
-151
lines changed

28 files changed

+239
-151
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ Use `sqlcmd` to create SQL Server and Azure SQL Edge instances using a local con
4949
To create a local SQL Server instance with the AdventureWorksLT database restored, query it, and connect to it using Azure Data Studio, run:
5050

5151
```
52-
sqlcmd create mssql --accept-eula --using https://aka.ms/AdventureWorksLT.bak
52+
sqlcmd create mssql --accept-eula --use https://aka.ms/AdventureWorksLT.bak
5353
sqlcmd query "SELECT DB_NAME()"
5454
sqlcmd open ads
5555
```

cmd/modern/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/microsoft/go-sqlcmd/internal/output"
2121
"github.com/microsoft/go-sqlcmd/internal/output/verbosity"
2222
"github.com/microsoft/go-sqlcmd/internal/pal"
23+
"github.com/microsoft/go-sqlcmd/pkg/mssqlcontainer"
2324
"github.com/microsoft/go-sqlcmd/pkg/sqlcmd"
2425
"github.com/spf13/cobra"
2526
"path"
@@ -130,6 +131,10 @@ func initializeCallback() {
130131
HintHandler: displayHints,
131132
LineBreak: sqlcmd.SqlcmdEol,
132133
})
134+
mssqlcontainer.Initialize(mssqlcontainer.InitializeOptions{
135+
ErrorHandler: checkErr,
136+
TraceHandler: outputter.Tracef,
137+
})
133138
config.SetFileName(rootCmd.configFilename)
134139
config.Load()
135140
}

cmd/modern/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type Root struct {
2626
// It also provides usage examples for sqlcmd.
2727
func (c *Root) DefineCommand(...cmdparser.CommandOptions) {
2828
// Example usage steps
29-
steps := []string{"sqlcmd create mssql --accept-eula --using https://aka.ms/AdventureWorksLT.bak"}
29+
steps := []string{"sqlcmd create mssql --accept-eula --use https://aka.ms/AdventureWorksLT.bak"}
3030

3131
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
3232
steps = append(steps, "sqlcmd open ads")

cmd/modern/root/config/connection-strings.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func (c *ConnectionStrings) run() {
6969
if endpoint.AssetDetails != nil && endpoint.AssetDetails.ContainerDetails != nil {
7070
controller := container.NewController()
7171
if controller.ContainerRunning(endpoint.AssetDetails.ContainerDetails.Id) {
72-
s := sql.New(sql.SqlOptions{})
72+
s := sql.NewSql(sql.SqlOptions{})
7373
s.Connect(endpoint, user, sql.ConnectOptions{Interactive: false})
7474
c.database = s.ScalarString("PRINT DB_NAME()")
7575
} else {

cmd/modern/root/install/edge_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func TestInstallEdge(t *testing.T) {
2525
cmdparser.TestCmd[*edge.GetTags]()
2626
cmdparser.TestCmd[*Edge](
2727
fmt.Sprintf(
28-
`--accept-eula --user-database foo --errorlog-wait-line "Hello from Docker!" --registry %v --repo %v`,
28+
`--accept-eula --database foo --errorlog-wait-line "Hello from Docker!" --registry %v --repo %v`,
2929
registry,
3030
repo))
3131

cmd/modern/root/install/mssql-base.go

Lines changed: 61 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -270,31 +270,30 @@ func (c *MssqlBase) AddFlags(
270270
// If the EULA has not been accepted, it prints an error message with suggestions for how to proceed,
271271
// and exits the program.
272272
func (c *MssqlBase) Run() {
273-
output := c.Cmd.Output()
274-
275273
var imageName string
276274

275+
output := c.Cmd.Output()
276+
277277
if !c.acceptEula && viper.GetString("ACCEPT_EULA") == "" {
278278
output.FatalWithHints(
279279
[]string{"Either, add the --accept-eula flag to the command-line",
280-
fmt.Sprintf("Or, set the environment variable i.e. %s SQLCMD_ACCEPT_EULA=YES ", pal.CreateEnvVarKeyword())},
280+
fmt.Sprintf(
281+
"Or, set the environment variable i.e. %s SQLCMD_ACCEPT_EULA=YES ",
282+
pal.CreateEnvVarKeyword())},
281283
"EULA not accepted")
282284
}
283285

284-
imageName = fmt.Sprintf(
285-
"%s/%s:%s",
286-
c.registry,
287-
c.repo,
288-
c.tag)
286+
imageName = fmt.Sprintf("%s/%s:%s", c.registry, c.repo, c.tag)
289287

288+
// If no context name provided, set it to the default (e.g. mssql or edge)
290289
if c.contextName == "" {
291290
c.contextName = c.defaultContextName
292291
}
293292

294293
c.createContainer(imageName, c.contextName)
295294
}
296295

297-
// createContainer installs an image for a SQL Server container. The image
296+
// createContainer creates a SQL Server container for an image. The image
298297
// is specified by imageName, and the container will be given the name contextName.
299298
// If the useCached flag is set, the function will skip downloading the image
300299
// from the internet. The function outputs progress messages to the command-line
@@ -305,25 +304,19 @@ func (c *MssqlBase) createContainer(imageName string, contextName string) {
305304
controller := container.NewController()
306305
saPassword := c.generatePassword()
307306

308-
env := []string{
309-
"ACCEPT_EULA=Y",
310-
fmt.Sprintf("MSSQL_SA_PASSWORD=%s", saPassword),
311-
fmt.Sprintf("MSSQL_COLLATION=%s", c.collation),
312-
}
313-
314307
if c.port == 0 {
315308
c.port = config.FindFreePortForTds()
316309
}
317310

318311
// Do an early exit if url doesn't exist
319312
var useDatabase ingest.Ingest
320313
if c.useDatabaseUrl != "" {
321-
useDatabase = c.verifyUseSourceFileExists(useDatabase, controller, output)
314+
useDatabase = c.verifyUseSourceFileExists(controller, output)
322315
}
323316

324317
if c.defaultDatabase != "" {
325318
if !c.validateDbName(c.defaultDatabase) {
326-
output.Fatalf("--user-database %q contains non-ASCII chars and/or quotes", c.defaultDatabase)
319+
output.Fatalf("--database %q contains non-ASCII chars and/or quotes", c.defaultDatabase)
327320
}
328321
}
329322

@@ -332,38 +325,34 @@ func (c *MssqlBase) createContainer(imageName string, contextName string) {
332325
}
333326

334327
runOptions := container.RunOptions{
335-
Env: env,
336328
Port: c.port,
337329
Name: c.name,
338330
Hostname: c.hostname,
339331
Architecture: c.architecture,
340-
Os: c.os,
341-
Command: []string{},
342-
}
332+
Os: c.os}
333+
334+
runOptions.Env = []string{
335+
"ACCEPT_EULA=Y",
336+
fmt.Sprintf("MSSQL_SA_PASSWORD=%s", saPassword),
337+
fmt.Sprintf("MSSQL_COLLATION=%s", c.collation)}
343338

344339
output.Infof("Starting %v", imageName)
345340
containerId := controller.ContainerRun(imageName, runOptions)
346341
previousContextName := config.CurrentContextName()
347342

348-
userName := pal.UserName()
349-
password := c.generatePassword()
350-
351343
// Save the config now, so user can uninstall/delete, even if mssql in the container
352344
// fails to start
353-
354345
contextOptions := config.ContextOptions{
355346
ImageName: imageName,
356347
PortNumber: c.port,
357348
ContainerId: containerId,
358-
Username: userName,
359-
Password: password,
360-
PasswordEncryption: c.passwordEncryption,
361-
}
362-
349+
Username: pal.UserName(),
350+
Password: c.generatePassword(),
351+
PasswordEncryption: c.passwordEncryption}
363352
config.AddContextWithContainer(contextName, contextOptions)
364353

365354
output.Infof(
366-
"Created context %q in \"%s\", configuring user account...",
355+
"Created context %q in \"%s\", configuring user account",
367356
config.CurrentContextName(),
368357
config.GetConfigFileUsed())
369358

@@ -372,7 +361,7 @@ func (c *MssqlBase) createContainer(imageName string, contextName string) {
372361
output.Infof("Disabled %q account (and rotated %q password). Creating user %q",
373362
"sa",
374363
"sa",
375-
userName)
364+
contextOptions.Username)
376365

377366
endpoint, _ := config.CurrentContext()
378367

@@ -384,7 +373,7 @@ func (c *MssqlBase) createContainer(imageName string, contextName string) {
384373
if c.errorLogEntryToWaitFor == "Hello from Docker!" {
385374
sqlOptions.UnitTesting = true
386375
}
387-
c.sql = sql.New(sqlOptions)
376+
c.sql = sql.NewSql(sqlOptions)
388377

389378
saUser := &sqlconfig.User{
390379
AuthenticationType: "basic",
@@ -394,27 +383,34 @@ func (c *MssqlBase) createContainer(imageName string, contextName string) {
394383
Password: secret.Encode(saPassword, c.passwordEncryption)},
395384
Name: "sa"}
396385

397-
c.sql.Connect(endpoint, saUser, sql.ConnectOptions{Database: "master", Interactive: false})
398-
c.createNonSaUser(userName, password)
386+
// Connect to master database on SQL Server in the container as `sa`
387+
c.sql.Connect(endpoint, saUser, sql.ConnectOptions{Database: "master"})
388+
389+
// Create a new (non-sa) SQL Server user
390+
c.createUser(contextOptions.Username, contextOptions.Password)
399391

400-
// Download and restore DB if asked
392+
// Download and restore/attach etc. DB if asked
401393
if useDatabase != nil {
402-
output.Infof("Copying to container")
394+
if useDatabase.IsRemoteUrl() {
395+
output.Infof("Downloading %q to container", useDatabase.UrlFilename())
396+
} else {
397+
output.Infof("Copying %q to container", useDatabase.UrlFilename())
398+
}
403399
useDatabase.CopyToContainer(containerId)
404400

405401
if useDatabase.IsExtractionNeeded() {
406-
output.Infof("Extracting files from archive")
402+
output.Infof("Extracting files from %q archive", useDatabase.UrlFilename())
407403
useDatabase.Extract()
408404
}
409405

410-
output.Infof("Bringing database online")
411-
useDatabase.BringOnline(c.sql.Query, userName, password)
406+
output.Infof("Bringing database %q online", useDatabase.DatabaseName())
407+
useDatabase.BringOnline(c.sql.Query, contextOptions.Username, contextOptions.Password)
412408
}
413409

414410
if c.openTool == "" {
415411
hints := [][]string{}
416412

417-
// TODO: sqlcmd open ads only support on Windows right now, add Mac support
413+
// TODO: sqlcmd open ads only support on Windows/Mac right now, add Linux support
418414
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
419415
hints = append(hints, []string{"Open in Azure Data Studio", "sqlcmd open ads"})
420416
}
@@ -452,10 +448,10 @@ func (c *MssqlBase) createContainer(imageName string, contextName string) {
452448
user := &sqlconfig.User{
453449
AuthenticationType: "basic",
454450
BasicAuth: &sqlconfig.BasicAuthDetails{
455-
Username: userName,
451+
Username: contextOptions.Username,
456452
PasswordEncryption: c.passwordEncryption,
457-
Password: secret.Encode(password, c.passwordEncryption)},
458-
Name: userName}
453+
Password: secret.Encode(contextOptions.Password, c.passwordEncryption)},
454+
Name: contextOptions.Username}
459455

460456
ads.PersistCredentialForAds(endpoint.EndpointDetails.Address, endpoint, user)
461457

@@ -466,7 +462,7 @@ func (c *MssqlBase) createContainer(imageName string, contextName string) {
466462
c.port))}
467463

468464
args = append(args, fmt.Sprintf("--user=%s",
469-
strings.Replace(userName, `"`, `\"`, -1)))
465+
strings.Replace(contextOptions.Username, `"`, `\"`, -1)))
470466

471467
tool := tools.NewTool("ads")
472468
if !tool.IsInstalled() {
@@ -478,7 +474,10 @@ func (c *MssqlBase) createContainer(imageName string, contextName string) {
478474
}
479475
}
480476

481-
func (c *MssqlBase) verifyUseSourceFileExists(useDatabase ingest.Ingest, controller *container.Controller, output *output.Output) ingest.Ingest {
477+
func (c *MssqlBase) verifyUseSourceFileExists(
478+
controller *container.Controller,
479+
output *output.Output,
480+
) (useDatabase ingest.Ingest) {
482481
useDatabase = ingest.NewIngest(c.useDatabaseUrl, controller, ingest.IngestOptions{
483482
Mechanism: c.useMechanism,
484483
})
@@ -487,44 +486,52 @@ func (c *MssqlBase) verifyUseSourceFileExists(useDatabase ingest.Ingest, control
487486
output.FatalfWithHints(
488487
[]string{
489488
fmt.Sprintf(
490-
"--using must be a path to a file with a %q extension",
489+
"--use must be a path to a file with a %q extension",
491490
ingest.ValidFileExtensions(),
492491
),
493492
},
494-
"%q is not a valid file extension for --using flag", useDatabase.UserProvidedFileExt())
493+
"%q is not a valid file extension for --use flag", useDatabase.UserProvidedFileExt())
495494
}
496495

497496
if useDatabase.IsRemoteUrl() && !useDatabase.IsValidScheme() {
498497
output.FatalfWithHints(
499498
[]string{
500499
fmt.Sprintf(
501-
"--using URL must one of %q",
500+
"--use URL must one of %q",
502501
strings.Join(useDatabase.ValidSchemes(), ", "),
503502
),
504503
},
505-
"%q is not a valid URL for --using flag", c.useDatabaseUrl)
504+
"%q is not a valid URL for --use flag", c.useDatabaseUrl)
506505
}
507506

508507
if !useDatabase.SourceFileExists() {
509508
output.FatalfWithHints(
510509
[]string{fmt.Sprintf("File does not exist at URL %q", c.useDatabaseUrl)},
511510
"Unable to download file")
512511
}
513-
return useDatabase
512+
return
514513
}
515514

516-
// createNonSaUser creates a user (non-sa) and assigns the sysadmin role
515+
// createUser creates a user (non-sa) and assigns the sysadmin role
517516
// to the user. It also creates a default database with the provided name
518517
// and assigns the default database to the user. Finally, it disables
519518
// the sa account and rotates the sa password for security reasons.
520-
func (c *MssqlBase) createNonSaUser(
519+
func (c *MssqlBase) createUser(
521520
userName string,
522521
password string,
523522
) {
523+
const createLogin = `CREATE LOGIN [%s]
524+
WITH PASSWORD=N'%s',
525+
DEFAULT_DATABASE=[%s],
526+
CHECK_EXPIRATION=OFF,
527+
CHECK_POLICY=OFF`
528+
const addSrvRoleMember = `EXEC master..sp_addsrvrolemember
529+
@loginame = N'%s',
530+
@rolename = N'sysadmin'`
531+
524532
output := c.Cmd.Output()
525533

526534
defaultDatabase := "master"
527-
528535
if c.defaultDatabase != "" {
529536
defaultDatabase = c.defaultDatabase
530537

@@ -533,15 +540,6 @@ func (c *MssqlBase) createNonSaUser(
533540
c.sql.Query(fmt.Sprintf("CREATE DATABASE [%s]", defaultDatabase))
534541
}
535542

536-
const createLogin = `CREATE LOGIN [%s]
537-
WITH PASSWORD=N'%s',
538-
DEFAULT_DATABASE=[%s],
539-
CHECK_EXPIRATION=OFF,
540-
CHECK_POLICY=OFF`
541-
const addSrvRoleMember = `EXEC master..sp_addsrvrolemember
542-
@loginame = N'%s',
543-
@rolename = N'sysadmin'`
544-
545543
c.sql.Query(fmt.Sprintf(createLogin, userName, password, defaultDatabase))
546544
c.sql.Query(fmt.Sprintf(addSrvRoleMember, userName))
547545

cmd/modern/root/install/mssql.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ func (c *Mssql) DefineCommand(...cmdparser.CommandOptions) {
3535
},
3636
{
3737
Description: "Create SQL Server, download and attach AdventureWorks sample database",
38-
Steps: []string{"sqlcmd create mssql --using https://aka.ms/AdventureWorksLT.bak"}},
38+
Steps: []string{"sqlcmd create mssql --use https://aka.ms/AdventureWorksLT.bak"}},
3939
{
4040
Description: "Create SQL Server, download and attach AdventureWorks sample database with different database name",
41-
Steps: []string{"sqlcmd create mssql --using https://aka.ms/AdventureWorksLT.bak,adventureworks"}},
41+
Steps: []string{"sqlcmd create mssql --use https://aka.ms/AdventureWorksLT.bak,adventureworks"}},
4242
{
4343
Description: "Create SQL Server with an empty user database",
44-
Steps: []string{"sqlcmd create mssql --user-database db1"}},
44+
Steps: []string{"sqlcmd create mssql --database db1"}},
4545
{
4646
Description: "Install/Create SQL Server with full logging",
4747
Steps: []string{"sqlcmd create mssql --verbosity 4"}},

0 commit comments

Comments
 (0)