diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index f719b3fe..c8e46e66 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -45,7 +45,7 @@ jobs:
     - name: Setup go
       uses: actions/setup-go@v2
       with:
-        go-version: '1.18'
+        go-version: '1.20'
     
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index a28ef0ed..c8cdbdee 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -11,7 +11,7 @@ jobs:
     steps:
       - uses: actions/setup-go@v3
         with:
-          go-version: 1.18
+          go-version: 1.20
       - uses: actions/checkout@v3
       - name: golangci-lint
         uses: golangci/golangci-lint-action@v3
diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml
index d6812940..2245d7fe 100644
--- a/.github/workflows/pr-validation.yml
+++ b/.github/workflows/pr-validation.yml
@@ -13,7 +13,7 @@ jobs:
     - name: Setup go
       uses: actions/setup-go@v2
       with:
-        go-version: '1.18'
+        go-version: '1.20'
     - name: Run tests against Linux SQL
       run: |
         go version
diff --git a/.pipelines/include-install-go-tools.yml b/.pipelines/include-install-go-tools.yml
index 23d7190d..7ce69446 100644
--- a/.pipelines/include-install-go-tools.yml
+++ b/.pipelines/include-install-go-tools.yml
@@ -1,7 +1,7 @@
 steps: 
   - task: GoTool@0
     inputs:
-      version: '1.18'
+      version: '1.20'
   - task: Go@0
     displayName: 'Go: get dependencies'
     inputs:
diff --git a/README.md b/README.md
index 6332b6df..d25484b3 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ Use `sqlcmd` to create SQL Server and Azure SQL Edge instances using a local con
 To create a local SQL Server instance with the AdventureWorksLT database restored, query it, and connect to it using Azure Data Studio, run:
 
 ```
-sqlcmd create mssql --accept-eula --using https://aka.ms/AdventureWorksLT.bak
+sqlcmd create mssql --accept-eula --use https://aka.ms/AdventureWorksLT.bak
 sqlcmd query "SELECT DB_NAME()"
 sqlcmd open ads
 ```
diff --git a/build/azure-pipelines/build-common.yml b/build/azure-pipelines/build-common.yml
index dcb9a0d1..02cd747c 100644
--- a/build/azure-pipelines/build-common.yml
+++ b/build/azure-pipelines/build-common.yml
@@ -17,7 +17,7 @@ parameters:
 steps:
 - task: GoTool@0
   inputs:
-    version: '1.18'
+    version: '1.20'
     goBin: $(Build.SourcesDirectory)
 
 - task: Go@0
diff --git a/cmd/modern/main.go b/cmd/modern/main.go
index 4b358b48..e5393e89 100644
--- a/cmd/modern/main.go
+++ b/cmd/modern/main.go
@@ -20,6 +20,7 @@ import (
 	"github.com/microsoft/go-sqlcmd/internal/output"
 	"github.com/microsoft/go-sqlcmd/internal/output/verbosity"
 	"github.com/microsoft/go-sqlcmd/internal/pal"
+	"github.com/microsoft/go-sqlcmd/pkg/mssqlcontainer"
 	"github.com/microsoft/go-sqlcmd/pkg/sqlcmd"
 	"github.com/spf13/cobra"
 	"path"
@@ -95,9 +96,7 @@ func initializeEnvVars() {
 				os.Setenv("SQLCMDPASSWORD", password)
 			}
 		}
-
 	}
-
 }
 
 // isFirstArgModernCliSubCommand is TEMPORARY code, to be removed when
@@ -131,7 +130,12 @@ func initializeCallback() {
 			TraceHandler: outputter.Tracef,
 			HintHandler:  displayHints,
 			LineBreak:    sqlcmd.SqlcmdEol,
+			LoggingLevel: verbosity.Level(rootCmd.loggingLevel),
 		})
+	mssqlcontainer.Initialize(mssqlcontainer.InitializeOptions{
+		ErrorHandler: checkErr,
+		TraceHandler: outputter.Tracef,
+	})
 	config.SetFileName(rootCmd.configFilename)
 	config.Load()
 }
diff --git a/cmd/modern/root.go b/cmd/modern/root.go
index 8a83f02b..8724cb5e 100644
--- a/cmd/modern/root.go
+++ b/cmd/modern/root.go
@@ -27,7 +27,7 @@ type Root struct {
 // It also provides usage examples for sqlcmd.
 func (c *Root) DefineCommand(...cmdparser.CommandOptions) {
 	// Example usage steps
-	steps := []string{"sqlcmd create mssql --accept-eula --using https://aka.ms/AdventureWorksLT.bak"}
+	steps := []string{"sqlcmd create mssql --accept-eula --use https://aka.ms/AdventureWorksLT.bak"}
 
 	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
 		steps = append(steps, "sqlcmd open ads")
@@ -66,10 +66,12 @@ func (c *Root) SubCommands() []cmdparser.Command {
 
 	subCommands := []cmdparser.Command{
 		cmdparser.New[*root.Config](dependencies),
+		cmdparser.New[*root.Deploy](dependencies),
 		cmdparser.New[*root.Install](dependencies),
 		cmdparser.New[*root.Query](dependencies),
 		cmdparser.New[*root.Start](dependencies),
 		cmdparser.New[*root.Stop](dependencies),
+		cmdparser.New[*root.Use](dependencies),
 		cmdparser.New[*root.Uninstall](dependencies),
 	}
 
diff --git a/cmd/modern/root/config/connection-strings.go b/cmd/modern/root/config/connection-strings.go
index a011aadc..4a6740c0 100644
--- a/cmd/modern/root/config/connection-strings.go
+++ b/cmd/modern/root/config/connection-strings.go
@@ -71,7 +71,7 @@ func (c *ConnectionStrings) run() {
 		if endpoint.AssetDetails != nil && endpoint.AssetDetails.ContainerDetails != nil {
 			controller := container.NewController()
 			if controller.ContainerRunning(endpoint.AssetDetails.ContainerDetails.Id) {
-				s := sql.New(sql.SqlOptions{})
+				s := sql.NewSql(sql.SqlOptions{})
 				s.Connect(endpoint, user, sql.ConnectOptions{Interactive: false})
 				c.database = s.ScalarString("PRINT DB_NAME()")
 			} else {
diff --git a/cmd/modern/root/config/current-context.go b/cmd/modern/root/config/current-context.go
index fa1d9872..506a1a26 100644
--- a/cmd/modern/root/config/current-context.go
+++ b/cmd/modern/root/config/current-context.go
@@ -17,10 +17,10 @@ type CurrentContext struct {
 func (c *CurrentContext) DefineCommand(...cmdparser.CommandOptions) {
 	options := cmdparser.CommandOptions{
 		Use:   "current-context",
-		Short: localizer.Sprintf("Display the current-context"),
+		Short: localizer.Sprintf("Display the name of the current-context"),
 		Examples: []cmdparser.ExampleOptions{
 			{
-				Description: localizer.Sprintf("Display the current-context"),
+				Description: localizer.Sprintf("Display the current-context name"),
 				Steps: []string{
 					"sqlcmd config current-context"},
 			},
diff --git a/cmd/modern/root/config/delete-context.go b/cmd/modern/root/config/delete-context.go
index 75f3df5c..898df0fb 100644
--- a/cmd/modern/root/config/delete-context.go
+++ b/cmd/modern/root/config/delete-context.go
@@ -74,6 +74,10 @@ func (c *DeleteContext) run() {
 			if config.UserExists(context) {
 				config.DeleteUser(*context.ContextDetails.User)
 			}
+
+			for _, c := range context.AddOns {
+				config.DeleteEndpoint(c.Endpoint)
+			}
 		}
 
 		config.DeleteContext(c.name)
diff --git a/cmd/modern/root/config/get-endpoints.go b/cmd/modern/root/config/get-endpoints.go
index a19b28b3..bddb6549 100644
--- a/cmd/modern/root/config/get-endpoints.go
+++ b/cmd/modern/root/config/get-endpoints.go
@@ -15,6 +15,7 @@ type GetEndpoints struct {
 
 	name     string
 	detailed bool
+	value    string
 }
 
 func (c *GetEndpoints) DefineCommand(...cmdparser.CommandOptions) {
@@ -47,6 +48,11 @@ func (c *GetEndpoints) DefineCommand(...cmdparser.CommandOptions) {
 		Bool:  &c.detailed,
 		Name:  "detailed",
 		Usage: localizer.Sprintf("Include endpoint details")})
+
+	c.AddFlag(cmdparser.FlagOptions{
+		String: &c.value,
+		Name:   "value",
+		Usage:  localizer.Sprintf("Value to get, (endpoint, port")})
 }
 
 func (c *GetEndpoints) run() {
@@ -55,7 +61,19 @@ func (c *GetEndpoints) run() {
 	if c.name != "" {
 		if config.EndpointExists(c.name) {
 			context := config.GetEndpoint(c.name)
-			output.Struct(context)
+
+			if c.value == "" {
+				output.Struct(context)
+			} else {
+				if c.value == "address" {
+					output.Struct(context.Address)
+				} else if c.value == "port" {
+					output.Struct(context.Port)
+				} else {
+					panic("Invalid value")
+				}
+			}
+
 		} else {
 			output.FatalWithHints(
 				[]string{localizer.Sprintf("To view available endpoints run `%s`", localizer.GetEndpointsCommand)},
diff --git a/cmd/modern/root/deploy.go b/cmd/modern/root/deploy.go
new file mode 100644
index 00000000..80a7cb3c
--- /dev/null
+++ b/cmd/modern/root/deploy.go
@@ -0,0 +1,777 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package root
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"path/filepath"
+	"runtime"
+
+	"github.com/joho/godotenv"
+	"github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig"
+	"github.com/microsoft/go-sqlcmd/internal/cmdparser"
+	"github.com/microsoft/go-sqlcmd/internal/config"
+	"github.com/microsoft/go-sqlcmd/internal/dotsqlcmdconfig"
+	"github.com/microsoft/go-sqlcmd/internal/io/file"
+	"github.com/microsoft/go-sqlcmd/internal/io/folder"
+	"github.com/microsoft/go-sqlcmd/internal/localizer"
+	"github.com/microsoft/go-sqlcmd/internal/sql"
+	"github.com/microsoft/go-sqlcmd/internal/tools"
+	"github.com/microsoft/go-sqlcmd/internal/tools/tool"
+
+	"github.com/rdegges/go-ipify"
+
+	"os"
+	"os/exec"
+	"strings"
+)
+
+// Open defines the `sqlcmd open` sub-commands
+type Deploy struct {
+	cmdparser.Cmd
+
+	target      string
+	environment string
+	notFree     bool
+	force       bool
+}
+
+func (c *Deploy) DefineCommand(...cmdparser.CommandOptions) {
+	options := cmdparser.CommandOptions{
+		Use:   "deploy",
+		Short: localizer.Sprintf("Deploy current current context to a target environment"),
+		Run:   c.run,
+	}
+
+	c.Cmd.DefineCommand(options)
+
+	c.AddFlag(cmdparser.FlagOptions{
+		String:        &c.target,
+		DefaultString: "azure",
+		Name:          "target",
+		Shorthand:     "t",
+		Usage:         localizer.Sprintf("Target cloud platform (azure, fabric)")})
+
+	c.AddFlag(cmdparser.FlagOptions{
+		String:        &c.environment,
+		DefaultString: "",
+		Name:          "environment",
+		Shorthand:     "e",
+		Usage:         localizer.Sprintf("Target environment name (default {username}-{sqlcmd-context-name})")})
+
+	c.AddFlag(cmdparser.FlagOptions{
+		Bool:  &c.notFree,
+		Name:  "not-free",
+		Usage: localizer.Sprintf("Use not free SKUs")})
+
+	c.AddFlag(cmdparser.FlagOptions{
+		Bool:  &c.force,
+		Name:  "force",
+		Usage: localizer.Sprintf("Remove existing azure.yaml, and .azure and infra folders")})
+}
+
+func (c *Deploy) run() {
+	output := c.Output()
+
+	current_contextName := config.CurrentContextName()
+	if current_contextName == "" {
+		output.FatalWithHintExamples([][]string{
+			{localizer.Sprintf("To view available contexts"), "sqlcmd config get-contexts"},
+		}, localizer.Sprintf("No current context"))
+	}
+
+	if c.force {
+		if file.Exists("azure.yaml") {
+			file.Remove("azure.yaml")
+		}
+		folder.RemoveAll("infra")
+		folder.RemoveAll(".azure")
+		folder.RemoveAll(filepath.Join(".sqlcmd", "DataApiBuilder"))
+	}
+
+	// BUBUG: For some reason azd provision needs dotnet cli installed, so do an early check
+	// See the comment at the point we call "azd provision" for more details
+	// TEMP: Check dotnet is installed
+	{
+		dotnetCliName := "dotnet"
+		if runtime.GOOS == "windows" {
+			dotnetCliName = "dotnet.exe"
+		}
+
+		path, err := exec.LookPath(dotnetCliName)
+		c.CheckErr(err)
+
+		if path == "" {
+			output.FatalWithHints(
+				[]string{"Install the dotnet CLI"},
+				fmt.Sprintf("%q CLI does not exist in the PATH directories", dotnetCliName))
+		}
+	}
+
+	// azd provision requires docker.  podman isn't enough
+	{
+		dockerCliName := "docker"
+		if runtime.GOOS == "windows" {
+			dockerCliName = "docker.exe"
+		}
+
+		path, err := exec.LookPath(dockerCliName)
+		c.CheckErr(err)
+
+		if path == "" {
+			output.FatalWithHints(
+				[]string{"Install Docker Desktop"},
+				fmt.Sprintf("%q does not exist in the PATH directories.  `azd provision` requires docker.", dockerCliName))
+		}
+	}
+
+	azd := tools.NewTool("azd")
+	if !azd.IsInstalled() {
+		output.Fatalf(azd.HowToInstall())
+	}
+
+	var stdout bytes.Buffer
+
+	cmd := exec.Command("azd", "auth", "token", "--output", "json")
+	cmd.Stdout = &stdout
+	cmd.Start()
+	cmd.Wait()
+
+	// If we're not logged in, log in
+	if cmd.ProcessState.ExitCode() == 1 {
+		output.Info("Not logged using `azd`.  Running `azd auth login`.  Please complete login in the browser.")
+		exitCode, _ := azd.Run([]string{"auth", "login"}, tool.RunOptions{})
+		if exitCode != 0 {
+			output.Fatal(localizer.Sprintf("Error logging in to azd"))
+		}
+	}
+
+	stdout.Truncate(0)
+
+	cmd = exec.Command("azd", "auth", "token", "--output", "json")
+	cmd.Stdout = &stdout
+	cmd.Start()
+	cmd.Wait()
+	if cmd.ProcessState.ExitCode() != 0 {
+		output.Fatal(localizer.Sprintf("Error getting token from azd auth token"))
+	}
+
+	tokenBlob := stdout.String()
+
+	var token string
+	{
+		var payloadJson map[string]interface{}
+		json.Unmarshal([]byte(tokenBlob), &payloadJson)
+		token = payloadJson["token"].(string)
+	}
+
+	split := strings.Split(token, ".")
+
+	// base64 decode the payload
+	payload, _ := base64.StdEncoding.DecodeString(split[1])
+	payloadstring := string(payload)
+	if payloadstring[len(payloadstring)-1:] != "}" {
+		payloadstring += "}" // BUGBUG: Why do I need to do this!
+	}
+
+	// Get the email of the person logged into azd
+	var email string
+	{
+		var payloadJson map[string]interface{}
+		json.Unmarshal([]byte(payloadstring), &payloadJson)
+
+		// test is the key in the json
+		if payloadJson["email"] != nil {
+			email = payloadJson["email"].(string)
+		} else if payloadJson["upn"] != nil {
+			email = payloadJson["upn"].(string)
+		} else {
+			panic("Unable to get principal name from token")
+		}
+	}
+
+	output.Info("Using principal name: " + email)
+
+	email = strings.ToLower(email)
+	// get the string up to the @ from email
+	parts := strings.Split(email, "@")
+	username := parts[0]
+
+	// BUGBUG: Temporary code because go-mssqldb cannot log into Azure SQL
+	// if the azd auth login is not the same as the user logged into shell, e.g.
+	// if I log in to shell as alias@mycompany.com, and then log in to azd auth login
+	// as alias@hotmail.com.  go-mssqldb cannot login as alias@hotmail.com.  But
+	// SSMS can.  So there is an issuse with go-mssqldb AAD auth here.
+
+	// if on windows, verify the logged in upn is the same as the azd auth login
+	if runtime.GOOS == "windows" {
+		out, err := exec.Command("cmd", "/C", "whoami /upn").Output()
+		if err != nil {
+			whoami := strings.ToLower(string(out))
+			whoami = strings.TrimRight(whoami, "\r\n")
+			if whoami != "" {
+				if email != whoami {
+					output.FatalWithHints(
+						[]string{
+							localizer.Sprintf("Log in to shell as %q", whoami),
+							localizer.Sprintf("Log in to `azd auth login` as %q", email),
+						},
+						localizer.Sprintf(
+							"TEMP: Due to an issue with go-mssqldb, the shell login %q must be the same as the `azd auth login` %q",
+							whoami, email))
+				}
+			}
+		}
+	}
+
+	if _, err := os.Stat("azure.yaml"); os.IsNotExist(err) {
+		output.Info("")
+		output.Info("TEMP: `azd init` will run, and ask 2 questions, accept both defaults ('Use Code in Current Directory' and 'Confirm and continue initializing my app').")
+		output.Info("TEMP: https://github.com/Azure/azure-dev/issues/3339")
+		output.Info("TEMP: https://github.com/Azure/azure-dev/issues/3340")
+		output.Info("")
+	}
+
+	dotsqlcmdconfig.SetFileName(dotsqlcmdconfig.DefaultFileName())
+	dotsqlcmdconfig.Load()
+
+	databases := dotsqlcmdconfig.DatabaseNames()
+	if len(databases) == 0 {
+		panic("POC Limitation: At least one database has to be found in .sqlcmd file")
+	}
+	databaseName := databases[0]
+
+	// If the file azure.yaml does not exist in current directory
+	if _, err := os.Stat("azure.yaml"); os.IsNotExist(err) {
+		addons := dotsqlcmdconfig.AddonTypes()
+
+		for i, addon := range addons {
+			if addon == "dab" {
+
+				output.Info("TEMP: `azd init` will ask 'What port does 'DataApiBuilder' listen on?', 5000 is the common standard port")
+				output.Info("TEMP: https://github.com/Azure/azure-dev/issues/3341")
+				output.Info("")
+
+				path := filepath.Join(".sqlcmd", "DataApiBuilder")
+
+				folder.MkdirAll(path)
+
+				f := file.OpenFile(filepath.Join(path, "DataApiBuilder.csproj"))
+				f.WriteString(dataApiBuilderCsProj)
+				f.Close()
+
+				f = file.OpenFile(filepath.Join(path, "Dockerfile"))
+				f.WriteString(dockerfile)
+				f.Close()
+
+				f = file.OpenFile(filepath.Join(path, "Program.cs"))
+				f.Close()
+
+				var dabConfigJson map[string]interface{}
+
+				files := dotsqlcmdconfig.AddonFiles(i)
+
+				// There should be one --use-file (which points to the dab-config.json file)
+				if len(files) == 1 {
+
+					// Edit that dab-config.json file and force the data-source to read from the CONN_STRING variable
+					dabConfigString := file.GetContents(files[0])
+					json.Unmarshal([]byte(dabConfigString), &dabConfigJson)
+
+					dataSource := dabConfigJson["data-source"]
+					dataSource.(map[string]interface{})["connection-string"] = "@env('CONN_STRING')"
+
+					newData, err := json.Marshal(dabConfigJson)
+					if err != nil {
+						panic(err)
+					}
+
+					var prettyJSON bytes.Buffer
+					json.Indent(&prettyJSON, newData, "", "    ")
+
+					f = file.OpenFile(filepath.Join(path, "dab-config.json"))
+					f.WriteString(prettyJSON.String())
+					f.Close()
+				} else {
+					panic("There should be exactly one dab-config.json file specified as a 'use' in the .sqlcmd file")
+				}
+			}
+		}
+
+		if c.environment == "" {
+			c.environment = username + "-" + current_contextName
+		}
+
+		exitCode, _ := azd.Run([]string{"init", "--environment", c.environment}, tool.RunOptions{Interactive: true})
+		if exitCode != 0 {
+			output.Fatal(localizer.Sprintf("Error initializing application"))
+		}
+
+		// Update the git ignore file so all the files azd init generated don't get checked
+		// in, unless the user intentionally wants them to
+		{
+			gitignore := ""
+			if file.Exists(".gitignore") {
+				gitignore = file.GetContents(".gitignore")
+			}
+
+			f := file.OpenFile(".gitignore")
+			defer f.Close()
+			if !strings.Contains(gitignore, ".sqlcmd/DataApiBuilder") {
+				f.WriteString(".sqlcmd/DataApiBuilder\n")
+			}
+
+			if file.Exists("next-steps.md") {
+				file.Remove("next-steps.md")
+			}
+
+			// Add bicep for the Azure SQL Server
+			{
+				f := file.OpenFile(filepath.Join("infra", "app", "db.bicep"))
+				f.WriteString(dbBicep)
+				f.Close()
+
+				folder.MkdirAll(filepath.Join("infra", "core", "database", "sqlserver"))
+				f = file.OpenFile(filepath.Join("infra", "core", "database", "sqlserver", "sqlserver.bicep"))
+				f.WriteString(sqlserverBicep)
+				f.Close()
+			}
+
+			// Alter azd init generated bicep to do the right things
+			{
+				// Alter bicep to create an Azure SQL database
+				mainBicep := file.GetContents(filepath.Join("infra", "main.bicep"))
+				mainBicep = strings.Replace(mainBicep, "module monitoring",
+					mainBicepDbCall+"\n\nmodule monitoring", 1)
+
+				// If windows then replace \r\n with \n
+				if runtime.GOOS == "windows" {
+					keyvaultBicep = strings.Replace(keyvaultBicep, "\n", "\r\n", -1)
+				}
+				mainBicep = strings.Replace(mainBicep, keyvaultBicep, "/*\n"+keyvaultBicep+"*/\n", 1)
+
+				// Alter bicep to remove Key Vault (we do everything with managed identities and entra, so no secrets to store
+				mainBicep = strings.Replace(mainBicep, "output AZURE_KEY_VAULT_NAME",
+					"// output AZURE_KEY_VAULT_NAME", 1)
+
+				mainBicep = strings.Replace(mainBicep, "output AZURE_KEY_VAULT_ENDPOINT",
+					"// output AZURE_KEY_VAULT_ENDPOINT", 1)
+
+				// Output the DAB uri, so we can pass it in to the front end
+				mainBicep += "\noutput DATA_API_BUILDER_ENDPOINT string = dataApiBuilder.outputs.uri\noutput AZURE_CLIENT_ID string = dataApiBuilder.outputs.managedUserIdentity\n"
+
+				f := file.OpenFile(filepath.Join("infra", "main.bicep"))
+				f.WriteString(string(mainBicep))
+				f.Close()
+			}
+
+			{
+				// Shrink the container size to minimum, to keep costs down
+				dabBicep := file.GetContents(filepath.Join("infra", "app", "DataApiBuilder.bicep"))
+				dabBicep += "\noutput managedUserIdentity string = identity.properties.clientId\n"
+
+				f := file.OpenFile(filepath.Join("infra", "app", "DataApiBuilder.bicep"))
+				f.WriteString(dabBicep)
+				f.Close()
+
+				// go through all the Bicep files and reduce the container size to keep costs down
+				files, err := ioutil.ReadDir(filepath.Join("infra", "app"))
+				if err != nil {
+					output.FatalErr(err)
+				}
+
+				for _, f := range files {
+					if !f.IsDir() {
+						if strings.HasSuffix(f.Name(), ".bicep") {
+							bicep := file.GetContents(filepath.Join("infra", "app", f.Name()))
+							bicep = strings.Replace(bicep, "cpu: json('1.0')", "cpu: json('0.25')", -1)
+							bicep = strings.Replace(bicep, "memory: '2.0Gi'", "memory: '0.5Gi'", -1)
+							f := file.OpenFile(filepath.Join("infra", "app", f.Name()))
+							f.WriteString(bicep)
+							f.Close()
+						}
+					}
+				}
+			}
+		}
+
+		{
+			var mainParamsJson map[string]interface{}
+			mainParamsString := file.GetContents(filepath.Join("infra", "main.parameters.json"))
+			json.Unmarshal([]byte(mainParamsString), &mainParamsJson)
+
+			// Append a parameter sqlAdminLoginName to the parameters object
+			mainParamsJson["parameters"].(map[string]interface{})["sqlAdminLoginName"] = map[string]interface{}{
+				"value": "${AZURE_PRINCIPAL_NAME}",
+			}
+
+			mainParamsJson["parameters"].(map[string]interface{})["sqlDatabaseName"] = map[string]interface{}{
+				"value": databaseName,
+			}
+
+			mainParamsJson["parameters"].(map[string]interface{})["sqlClientIpAddress"] = map[string]interface{}{
+				"value": "${MY_IP}",
+			}
+
+			mainParamsJson["parameters"].(map[string]interface{})["useFreeLimit"] = map[string]interface{}{
+				"value": "${USE_FREE_LIMIT}",
+			}
+
+			var arr []interface{}
+			arr = append(arr, map[string]interface {
+			}{
+				"name":  "ASPNETCORE_ENVIRONMENT",
+				"value": "Development",
+			})
+
+			mainParamsJson["parameters"].(map[string]interface {
+			})["dataApiBuilderDefinition"].(map[string]interface {
+			})["value"].(map[string]interface {
+			})["settings"] = arr
+
+			newData, err := json.Marshal(mainParamsJson)
+			if err != nil {
+				panic(err)
+			}
+
+			var prettyJSON bytes.Buffer
+			json.Indent(&prettyJSON, newData, "", "  ")
+
+			f := file.OpenFile(filepath.Join("infra", "main.parameters.json"))
+			f.WriteString(prettyJSON.String())
+			f.Close()
+		}
+	}
+
+	// BUGBUG: Do this using a microsoft blessed method (SSMS/ADS must do this in the connection dialogs)
+	output.Infof("\nDiscovering IP address for this client, to allow firewall access to the Azure SQL server")
+
+	ip, err := ipify.GetIp()
+	output.FatalErr(err)
+
+	output.Infof("Setting local Address to %q to have access to the Azure SQL server", ip)
+
+	exitCode, _ := azd.Run([]string{"env", "set", "MY_IP", ip}, tool.RunOptions{})
+	if exitCode != 0 {
+		output.Fatal(localizer.Sprintf("Error setting environment variable MY_IP"))
+	}
+
+	exitCode, _ = azd.Run([]string{"env", "set", "AZURE_PRINCIPAL_NAME", email}, tool.RunOptions{})
+	if exitCode != 0 {
+		output.Fatal(localizer.Sprintf("Error setting environment variable AZURE_PRINCIPAL_NAME"))
+	}
+
+	exitCode, _ = azd.Run([]string{"env", "set", "USE_FREE_LIMIT", fmt.Sprintf("%t", !c.notFree)}, tool.RunOptions{})
+	if exitCode != 0 {
+		output.Fatal(localizer.Sprintf("Error setting environment variable USE_FREE_LIMIT"))
+	}
+
+	// BUGBUG: There seems to be a dependency on dotnet being on the machine!
+
+	/* Provisioning Azure resources (azd provision)
+
+		Provisioning Azure resources can take some time.
+
+	ERROR: initializing service 'DataApiBuilder', failed to initialize secrets at project '/Users/stuartpa/demo/.sqlcmd/DataApiBuilder/DataApiBuilder.csproj': exec: "dotnet": executable file not found in $PATH
+
+	Although interesting, is the secrets even needed for what we are doing!
+	*/
+	exitCode, _ = azd.Run([]string{"provision"}, tool.RunOptions{Interactive: true})
+	if exitCode != 0 {
+		output.FatalWithHintExamples([][]string{
+			{localizer.Sprintf("To clean up any resources created"), "azd down --force"},
+			{localizer.Sprintf("To not create an Azure SQL 'Spinnaker' run again with --not-free"), "sqlcmd deploy --not-free"},
+			{localizer.Sprintf("If failed with 'Invalid value given for parameter ExternalAdministratorLoginName'"), "azd auth login. Use a corp account (e.g. not hotmail.com etc.)"},
+			{localizer.Sprintf("If, 'The client ... does not have permission to perform action 'Microsoft.Authorization/roleAssignments/write' at scope ... Microsoft.ContainerRegistry'"), "See: https://github.com/Azure-Samples/azure-search-openai-demo#azure-account-requirements"},
+		}, localizer.Sprintf("Error provisioning infrastructure"))
+	}
+
+	var defaultEnvironment string
+	{
+		var payloadJson map[string]interface{}
+		configJson := file.GetContents(filepath.Join(".azure", "config.json"))
+		json.Unmarshal([]byte(configJson), &payloadJson)
+		if payloadJson["defaultEnvironment"] != nil {
+			defaultEnvironment = payloadJson["defaultEnvironment"].(string)
+		} else {
+			panic("Unable to get defaultEnvironment from " + filepath.Join(".azure", "config.json"))
+		}
+	}
+
+	// Run the SQL scripts
+	filename := filepath.Join(".azure", defaultEnvironment, ".env")
+
+	envFile, err := godotenv.Read(filename)
+	if err != nil {
+		panic("Unable to read .env file: " + filename)
+	}
+
+	random, ok := envFile["AZURE_CONTAINER_REGISTRY_ENDPOINT"]
+	if !ok {
+		panic("AZURE_CONTAINER_REGISTRY_ENDPOINT is not set in .env")
+	}
+	if random == "" {
+		panic("AZURE_CONTAINER_REGISTRY_ENDPOINT is not set in .env")
+	}
+
+	// Get the word up to the first .
+	random = strings.Split(random, ".")[0]
+
+	if len(random) < 2 {
+		panic("Random is too short, the value is '" + random + "'")
+	}
+
+	// Remove the first two characters from random (the 'cr' which stands for Container Registry)
+	random = random[2:]
+
+	endpoint := sqlconfig.Endpoint{
+		EndpointDetails: sqlconfig.EndpointDetails{
+			Address: "sql-" + random + ".database.windows.net",
+			Port:    1433,
+		},
+	}
+
+	authType := "ActiveDirectoryDefault"
+
+	// if on mac use Interactive, because Default doesn't work
+	if runtime.GOOS == "darwin" {
+		authType = "ActiveDirectoryInteractive"
+	}
+
+	user := sqlconfig.User{
+		Name:               email,
+		AuthenticationType: authType,
+	}
+
+	// options := sql.ConnectOptions{Database: databaseName, Interactive: false}
+	// options.LogLevel = 255
+
+	// Enable the Managed Identity for the DataApiBuilder service to have permissions
+	// to the Azure SQL Database
+	s := sql.NewSql(sql.SqlOptions{})
+	s.Connect(endpoint, &user, sql.ConnectOptions{Database: databaseName, Interactive: false})
+
+	addons := dotsqlcmdconfig.AddonTypes()
+	for _, addon := range addons {
+		if addon == "dab" {
+
+			azureClientId, ok := envFile["AZURE_CLIENT_ID"]
+			if !ok {
+				panic("AZURE_CLIENT_ID is not set in .env")
+			}
+			if azureClientId == "" {
+				panic("AZURE_CLIENT_ID is not set in .env")
+			}
+
+			f := file.OpenFile(filepath.Join(".sqlcmd", "DataApiBuilder", "Dockerfile"))
+			f.WriteString(
+				fmt.Sprintf(dockerfile,
+					fmt.Sprintf("Server=sql-%s.database.windows.net; Database=%s; Authentication=Active Directory Default; Encrypt=True",
+						random,
+						databaseName),
+					azureClientId))
+			f.Close()
+
+			s.Query("DROP USER IF EXISTS [id-dataapibuild-" + random + "]")
+			s.Query("CREATE USER [id-dataapibuild-" + random + "] FROM EXTERNAL PROVIDER")
+			s.Query("ALTER ROLE db_datareader ADD MEMBER [id-dataapibuild-" + random + "]")
+			s.Query("ALTER ROLE db_datawriter ADD MEMBER [id-dataapibuild-" + random + "]")
+		}
+	}
+
+	// If folder .sqlcmd exists
+	if file.Exists(".sqlcmd") {
+		dotsqlcmdconfig.SetFileName(dotsqlcmdconfig.DefaultFileName())
+		dotsqlcmdconfig.Load()
+		files := dotsqlcmdconfig.DatabaseFiles(0)
+
+		//for each file in folder .sqlcmd
+		for _, fi := range files {
+			//if file is .sql
+			if strings.HasSuffix(fi, ".sql") {
+
+				// if on Windows, replace / with \\
+				if runtime.GOOS == "windows" {
+					fi = strings.Replace(fi, "/", "\\", -1)
+				} else {
+					fi = strings.Replace(fi, "\\", "/", -1)
+				}
+
+				//run sql file
+				output.Infof("Running %q", fi)
+
+				s.ExecuteSqlFile(fi)
+			} else {
+				panic(fmt.Sprintf("File %q is not supported", fi))
+			}
+		}
+
+		exitCode, _ = azd.Run([]string{"package"}, tool.RunOptions{Interactive: true})
+		if exitCode != 0 {
+			output.Fatal(localizer.Sprintf("Error packaging application"))
+		}
+
+		exitCode, _ = azd.Run([]string{"deploy"}, tool.RunOptions{Interactive: true})
+		if exitCode != 0 {
+			output.Fatal(localizer.Sprintf("Error deploying application"))
+		}
+
+		output.InfofWithHintExamples([][]string{
+			{localizer.Sprintf("To view the deployed resources"), "azd show"},
+			{localizer.Sprintf("To setup a deployment pipeline"), "azd pipeline config --help"},
+			{localizer.Sprintf("To delete all resource in Azure"), "azd down --force"},
+		}, localizer.Sprintf("Successfully deployed application to %q", c.target))
+
+	}
+}
+
+var dataApiBuilderCsProj = `<Project Sdk="Microsoft.NET.Sdk.Web">
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+  </PropertyGroup>
+</Project>
+`
+
+var dockerfile = `FROM mcr.microsoft.com/azure-databases/data-api-builder:latest
+
+COPY dab-config.json /App
+WORKDIR /App
+ENV CONN_STRING='%s'
+ENV AZURE_CLIENT_ID=%s
+ENV ASPNETCORE_URLS=http://+:5000
+EXPOSE 5000
+ENTRYPOINT ["dotnet", "Azure.DataApiBuilder.Service.dll"]`
+
+var mainBicepDbCall = `param sqlDatabaseName string = ''
+param sqlServerName string = ''
+
+param sqlAdminLoginName string
+param sqlClientIpAddress string
+param useFreeLimit bool
+
+// The application database
+module sqlServer './app/db.bicep' = {
+  name: 'sql'
+  scope: rg
+  params: {
+    name: !empty(sqlServerName) ? sqlServerName : '${abbrs.sqlServers}${resourceToken}'
+    databaseName: sqlDatabaseName
+    location: location
+    tags: tags
+    sqlAdminLoginName: sqlAdminLoginName
+    sqlAdminLoginObjectId: principalId
+    sqlClientIpAddress: sqlClientIpAddress
+    useFreeLimit: useFreeLimit
+  }
+}
+`
+
+var dbBicep = `param name string
+param location string = resourceGroup().location
+param tags object = {}
+
+param databaseName string = ''
+param sqlAdminLoginName string
+param sqlAdminLoginObjectId string
+param sqlClientIpAddress string
+param useFreeLimit bool
+
+module sqlServer '../core/database/sqlserver/sqlserver.bicep' = {
+  name: 'sqlserver'
+  params: {
+    name: name
+    location: location
+    tags: tags
+    databaseName: databaseName
+    sqlAdminLoginName: sqlAdminLoginName
+    sqlAdminLoginObjectId: sqlAdminLoginObjectId
+    sqlClientIpAddress: sqlClientIpAddress
+    useFreeLimit: useFreeLimit
+  }
+}
+
+output connectionString string = sqlServer.outputs.connectionString
+`
+
+var sqlserverBicep = `metadata description = 'Creates an Azure SQL Server instance.'
+param name string
+param location string = resourceGroup().location
+param tags object = {}
+
+param sqlAdminLoginName string
+param sqlAdminLoginObjectId string
+param sqlClientIpAddress string
+param databaseName string
+param useFreeLimit bool
+
+resource sqlServer 'Microsoft.Sql/servers@2023-05-01-preview' = {
+  name: name
+  location: location
+  tags: tags
+  properties: {
+    administrators: {
+      azureADOnlyAuthentication: true
+      administratorType: 'ActiveDirectory'
+      tenantId: subscription().tenantId
+      principalType: 'User'
+      login: sqlAdminLoginName
+      sid: sqlAdminLoginObjectId
+    }
+    version: '12.0'
+    minimalTlsVersion: '1.2'
+    publicNetworkAccess: 'Enabled'
+  }
+  identity: {
+    type: 'SystemAssigned'
+  }
+
+  resource database 'databases' = {
+    name: databaseName
+    location: location
+    sku: {
+      name: 'GP_S_Gen5_2'
+    }
+    properties: {
+      useFreeLimit: useFreeLimit
+    }
+  }
+
+  resource firewall 'firewallRules' = {
+    name: 'Azure Services'
+    properties: {
+      // Note: range [0.0.0.0-0.0.0.0] means "allow all Azure-hosted clients only".
+      startIpAddress: '0.0.0.0'
+      endIpAddress: '0.0.0.0'
+    }
+  }
+}
+
+resource clientFirewallRules 'Microsoft.Sql/servers/firewallRules@2023-05-01-preview' = {
+  name: 'AllowClientIp'
+  parent: sqlServer
+  properties: {
+    startIpAddress: sqlClientIpAddress
+    endIpAddress: sqlClientIpAddress
+  }
+}
+
+var connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; Authentication=Active Directory Default; Encrypt=True'
+output connectionString string = connectionString
+output databaseName string = sqlServer::database.name
+`
+
+var keyvaultBicep = `module keyVault './shared/keyvault.bicep' = {
+  name: 'keyvault'
+  params: {
+    location: location
+    tags: tags
+    name: '${abbrs.keyVaultVaults}${resourceToken}'
+    principalId: principalId
+  }
+  scope: rg
+}`
diff --git a/cmd/modern/root/install/dot-sqlcmd-config.go b/cmd/modern/root/install/dot-sqlcmd-config.go
new file mode 100644
index 00000000..c0ed29c1
--- /dev/null
+++ b/cmd/modern/root/install/dot-sqlcmd-config.go
@@ -0,0 +1 @@
+package install
diff --git a/cmd/modern/root/install/edge_test.go b/cmd/modern/root/install/edge_test.go
index c01d7dc0..18c04f2f 100644
--- a/cmd/modern/root/install/edge_test.go
+++ b/cmd/modern/root/install/edge_test.go
@@ -25,7 +25,7 @@ func TestInstallEdge(t *testing.T) {
 	cmdparser.TestCmd[*edge.GetTags]()
 	cmdparser.TestCmd[*Edge](
 		fmt.Sprintf(
-			`--accept-eula --user-database foo --errorlog-wait-line "Hello from Docker!" --registry %v --repo %v`,
+			`--accept-eula --database foo --errorlog-wait-line "Hello from Docker!" --registry %v --repo %v`,
 			registry,
 			repo))
 
diff --git a/cmd/modern/root/install/mssql-base.go b/cmd/modern/root/install/mssql-base.go
index 924123ae..b22d0d59 100644
--- a/cmd/modern/root/install/mssql-base.go
+++ b/cmd/modern/root/install/mssql-base.go
@@ -4,23 +4,33 @@
 package install
 
 import (
+	"bytes"
+	"encoding/json"
 	"fmt"
-	"net/url"
-	"path"
 	"path/filepath"
 	"runtime"
+	"strconv"
 	"strings"
 
+	"github.com/microsoft/go-sqlcmd/cmd/modern/root/open"
+	"github.com/microsoft/go-sqlcmd/internal/cmdparser/dependency"
+	"github.com/microsoft/go-sqlcmd/internal/dotsqlcmdconfig"
+	"github.com/microsoft/go-sqlcmd/internal/io/file"
+	"github.com/microsoft/go-sqlcmd/internal/io/folder"
+	"github.com/microsoft/go-sqlcmd/internal/tools"
+	"github.com/microsoft/go-sqlcmd/internal/tools/tool"
+
 	"github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig"
 	"github.com/microsoft/go-sqlcmd/internal/cmdparser"
 	"github.com/microsoft/go-sqlcmd/internal/config"
 	"github.com/microsoft/go-sqlcmd/internal/container"
-	"github.com/microsoft/go-sqlcmd/internal/http"
 	"github.com/microsoft/go-sqlcmd/internal/localizer"
 	"github.com/microsoft/go-sqlcmd/internal/output"
 	"github.com/microsoft/go-sqlcmd/internal/pal"
 	"github.com/microsoft/go-sqlcmd/internal/secret"
 	"github.com/microsoft/go-sqlcmd/internal/sql"
+	"github.com/microsoft/go-sqlcmd/pkg/mssqlcontainer/ingest"
+	"github.com/microsoft/go-sqlcmd/pkg/mssqlcontainer/ingest/mechanism"
 	"github.com/spf13/viper"
 )
 
@@ -55,7 +65,15 @@ type MssqlBase struct {
 
 	port int
 
-	usingDatabaseUrl string
+	useUrl       []string
+	useMechanism []string
+
+	openTool string
+	openFile string
+
+	network  string
+	addOn    []string
+	addOnUse []string
 
 	unitTesting bool
 
@@ -101,6 +119,14 @@ func (c *MssqlBase) AddFlags(
 		String:    &c.defaultDatabase,
 		Name:      "user-database",
 		Shorthand: "u",
+		Hidden:    true,
+		Usage:     localizer.Sprintf("[DEPRECATED use --database] Create a user database and set it as the default for login"),
+	})
+
+	addFlag(cmdparser.FlagOptions{
+		String:    &c.defaultDatabase,
+		Name:      "database",
+		Shorthand: "d",
 		Usage:     localizer.Sprintf("Create a user database and set it as the default for login"),
 	})
 
@@ -213,10 +239,57 @@ func (c *MssqlBase) AddFlags(
 	})
 
 	addFlag(cmdparser.FlagOptions{
-		String:        &c.usingDatabaseUrl,
+		StringArray: &c.useUrl,
+		Name:        "using",
+		Hidden:      true,
+		Usage:       localizer.Sprintf("[DEPRECATED use --use] Download %q and use database", ingest.ValidFileExtensions()),
+	})
+
+	addFlag(cmdparser.FlagOptions{
+		StringArray: &c.useUrl,
+		Name:        "use",
+		Usage:       localizer.Sprintf("Download %q and use database", ingest.ValidFileExtensions()),
+	})
+
+	addFlag(cmdparser.FlagOptions{
+		StringArray: &c.useMechanism,
+		Name:        "use-mechanism",
+		Usage:       localizer.Sprintf("Mechanism to use to bring database online (%s)", strings.Join(mechanism.Mechanisms(), ",")),
+	})
+
+	addFlag(cmdparser.FlagOptions{
+		String:        &c.openTool,
+		DefaultString: "",
+		Name:          "open",
+		Usage:         localizer.Sprintf("Open tool e.g. ads"),
+	})
+
+	addFlag(cmdparser.FlagOptions{
+		String:        &c.openFile,
+		DefaultString: "",
+		Name:          "open-file",
+		Usage:         localizer.Sprintf("Open file in tool e.g. https://aks.ms/adventureworks-demo.sql"),
+	})
+
+	addFlag(cmdparser.FlagOptions{
+		String:        &c.network,
+		DefaultString: "",
+		Name:          "network",
+		Usage:         localizer.Sprintf("Container network name (defaults to 'container-network' if --add-on specified)"),
+	})
+
+	addFlag(cmdparser.FlagOptions{
+		StringArray:   &c.addOn,
+		DefaultString: "",
+		Name:          "add-on",
+		Usage:         localizer.Sprintf("Create add-on container (i.e. dab, fleet-manager)"),
+	})
+
+	addFlag(cmdparser.FlagOptions{
+		StringArray:   &c.addOnUse,
 		DefaultString: "",
-		Name:          "using",
-		Usage:         localizer.Sprintf("Download (into container) and attach database (.bak) from URL"),
+		Name:          "add-on-use",
+		Usage:         localizer.Sprintf("File to use for add-on container"),
 	})
 }
 
@@ -229,10 +302,10 @@ func (c *MssqlBase) AddFlags(
 // If the EULA has not been accepted, it prints an error message with suggestions for how to proceed,
 // and exits the program.
 func (c *MssqlBase) Run() {
-	output := c.Cmd.Output()
-
 	var imageName string
 
+	output := c.Cmd.Output()
+
 	if !c.acceptEula && viper.GetString("ACCEPT_EULA") == "" {
 		output.FatalWithHints(
 			[]string{localizer.Sprintf("Either, add the %s flag to the command-line", localizer.AcceptEulaFlag),
@@ -240,12 +313,9 @@ func (c *MssqlBase) Run() {
 			localizer.Sprintf("EULA not accepted"))
 	}
 
-	imageName = fmt.Sprintf(
-		"%s/%s:%s",
-		c.registry,
-		c.repo,
-		c.tag)
+	imageName = fmt.Sprintf("%s/%s:%s", c.registry, c.repo, c.tag)
 
+	// If no context name provided, set it to the default (e.g. mssql or edge)
 	if c.contextName == "" {
 		c.contextName = c.defaultContextName
 	}
@@ -253,7 +323,43 @@ func (c *MssqlBase) Run() {
 	c.createContainer(imageName, c.contextName)
 }
 
-// createContainer installs an image for a SQL Server container. The image
+func (c *MssqlBase) GetValuesFromDotSqlcmd() {
+	if file.Exists(filepath.Join(".sqlcmd", "sqlcmd.yaml")) {
+		dotsqlcmdconfig.SetFileName(dotsqlcmdconfig.DefaultFileName())
+
+		// If there is a .sqlcmd/sqlcmd.yaml file, then load that up and use it for any values not provided
+		dotsqlcmdconfig.Load()
+		dbs := dotsqlcmdconfig.DatabaseNames()
+
+		if len(dbs) > 1 {
+			panic("Only a single database is supported at this time")
+		}
+
+		if len(dbs) > 0 {
+			if c.defaultDatabase == "" {
+				c.defaultDatabase = dbs[0]
+			}
+
+			if len(c.useUrl) == 0 {
+				c.useUrl = append(c.useUrl, dotsqlcmdconfig.DatabaseFiles(0)...)
+			}
+		}
+
+		addons := dotsqlcmdconfig.AddonTypes()
+
+		if len(addons) > 0 {
+			c.addOn = append(c.addOn, addons...)
+		}
+
+		for i, _ := range c.addOn {
+			if len(c.addOnUse) < i+1 {
+				c.addOnUse = append(c.addOnUse, dotsqlcmdconfig.AddonFiles(i)...)
+			}
+		}
+	}
+}
+
+// createContainer creates a SQL Server container for an image. The image
 // is specified by imageName, and the container will be given the name contextName.
 // If the useCached flag is set, the function will skip downloading the image
 // from the internet. The function outputs progress messages to the command-line
@@ -261,77 +367,118 @@ func (c *MssqlBase) Run() {
 // command-line and the program will exit.
 func (c *MssqlBase) createContainer(imageName string, contextName string) {
 	output := c.Cmd.Output()
+	controller := container.NewController()
 	saPassword := c.generatePassword()
+	userName := pal.UserName()
+	password := c.generatePassword()
 
-	env := []string{
-		"ACCEPT_EULA=Y",
-		fmt.Sprintf("MSSQL_SA_PASSWORD=%s", saPassword),
-		fmt.Sprintf("MSSQL_COLLATION=%s", c.collation),
-	}
+	contextName = config.FindUniqueContextName(contextName, userName)
 
 	if c.port == 0 {
-		c.port = config.FindFreePortForTds()
+		c.port = config.FindFreePort(1433)
 	}
 
 	// Do an early exit if url doesn't exist
-	if c.usingDatabaseUrl != "" {
-		c.validateUsingUrlExists()
+	var useUrls []ingest.Ingest
+	if len(c.useUrl) > 0 {
+		useUrls = c.verifyUseSourceFileExists(controller, output)
+	}
+
+	if len(useUrls) == 1 {
+		if useUrls[0].UserProvidedFileExt() == "git" {
+			useUrls[0].BringOnline(nil, "", "")
+			useUrls = nil
+			c.useUrl = nil // Blank this out, because we now will get more useUrls from the .sqlcmd/sqlcmd.yaml file
+			c.useMechanism = nil
+		}
+	}
+
+	// Now that we have any remote repo cloned local, now is the time to
+	// go look for the .sqlcmd/sqlcmd.yaml settings
+	c.GetValuesFromDotSqlcmd()
+
+	if len(c.useUrl) > 0 {
+		useUrls = c.verifyUseSourceFileExists(controller, output)
 	}
 
 	if c.defaultDatabase != "" {
 		if !c.validateDbName(c.defaultDatabase) {
-			output.Fatalf(localizer.Sprintf("--user-database %q contains non-ASCII chars and/or quotes", c.defaultDatabase))
+			output.Fatalf(localizer.Sprintf("--database %q contains non-ASCII chars and/or quotes", c.defaultDatabase))
 		}
 	}
 
-	controller := container.NewController()
+	// If an add-on is specified, and no network name, then set a default network name
+	if len(c.addOn) > 0 && c.network == "" {
+		c.network = "sqlcmd-" + contextName + "-network"
+	}
+
+	if c.network != "" {
+		// Create a docker network
+		if !controller.NetworkExists(c.network) {
+			output.Info(localizer.Sprintf("Creating %q, for add-on cross container communication", c.network))
+			controller.NetworkCreate(c.network)
+		}
+	}
+
+	// Very strange issue that we need to work here.  If we are using add-on containers
+	// we have to specify the name of the mssql container!
+	// Details in this bug/DCR here:
+	//		https://github.com/moby/moby/issues/45183
+	if c.name == "" {
+		c.name = contextName + "-container"
+	}
 
 	if !c.useCached {
 		c.downloadImage(imageName, output, controller)
 	}
 
-	output.Info(localizer.Sprintf("Starting %v", imageName))
+	runOptions := container.RunOptions{
+		PortInternal: 1433,
+		Port:         c.port,
+		Name:         c.name,
+		Hostname:     c.hostname,
+		Architecture: c.architecture,
+		Os:           c.os,
+		Network:      c.network,
+	}
+
+	runOptions.Env = []string{
+		"ACCEPT_EULA=Y",
+		fmt.Sprintf("MSSQL_SA_PASSWORD=%s", saPassword),
+		fmt.Sprintf("MSSQL_COLLATION=%s", c.collation)}
+
+	output.Info(localizer.Sprintf("Starting %q", imageName))
+
 	containerId := controller.ContainerRun(
 		imageName,
-		env,
-		c.port,
-		c.name,
-		c.hostname,
-		c.architecture,
-		c.os,
-		[]string{},
-		false,
+		runOptions,
 	)
 	previousContextName := config.CurrentContextName()
 
-	userName := pal.UserName()
-	password := c.generatePassword()
-
 	// Save the config now, so user can uninstall/delete, even if mssql in the container
 	// fails to start
-	config.AddContextWithContainer(
-		contextName,
-		imageName,
-		c.port,
-		containerId,
-		userName,
-		password,
-		c.passwordEncryption,
-	)
-
-	output.Info(
-		localizer.Sprintf("Created context %q in \"%s\", configuring user account...",
+	contextOptions := config.ContextOptions{
+		ImageName:          imageName,
+		PortNumber:         c.port,
+		ContainerId:        containerId,
+		Username:           pal.UserName(),
+		Password:           password,
+		PasswordEncryption: c.passwordEncryption,
+		Network:            c.network}
+	config.AddContextWithContainer(contextName, contextOptions)
+
+	output.Infof(
+		localizer.Sprintf("Created context %q in \"%s\", configuring user account",
 			config.CurrentContextName(),
 			config.GetConfigFileUsed()))
 
-	controller.ContainerWaitForLogEntry(
-		containerId, c.errorLogEntryToWaitFor)
+	controller.ContainerWaitForLogEntry(containerId, c.errorLogEntryToWaitFor)
 
 	output.Info(
 		localizer.Sprintf("Disabled %q account (and rotated %q password). Creating user %q",
 			"sa",
 			"sa",
-			userName))
+			contextOptions.Username))
 
 	endpoint, _ := config.CurrentContext()
 
@@ -339,11 +486,11 @@ func (c *MssqlBase) createContainer(imageName string, contextName string) {
 	//
 	// For Unit Testing we use the Docker Hello World container, which
 	// starts much faster than the SQL Server container!
+	sqlOptions := sql.SqlOptions{}
 	if c.errorLogEntryToWaitFor == "Hello from Docker!" {
-		c.sql = sql.New(sql.SqlOptions{UnitTesting: true})
-	} else {
-		c.sql = sql.New(sql.SqlOptions{UnitTesting: false})
+		sqlOptions.UnitTesting = true
 	}
+	c.sql = sql.NewSql(sqlOptions)
 
 	saUser := &sqlconfig.User{
 		AuthenticationType: "basic",
@@ -353,22 +500,209 @@ func (c *MssqlBase) createContainer(imageName string, contextName string) {
 			Password:           secret.Encode(saPassword, c.passwordEncryption)},
 		Name: "sa"}
 
-	c.sql.Connect(endpoint, saUser, sql.ConnectOptions{Database: "master", Interactive: false})
+	// Connect to master database on SQL Server in the container as `sa`
+	c.sql.Connect(
+		endpoint,
+		saUser,
+		sql.ConnectOptions{Database: "master", Interactive: false},
+	)
 
-	c.createNonSaUser(userName, password)
+	// Create a new (non-sa) SQL Server user
+	c.createUser(contextOptions.Username, contextOptions.Password)
+
+	// Download and restore/attach etc. DB if asked
+	if len(useUrls) > 0 {
+		for i, useUrl := range useUrls {
+			if useUrl.IsRemoteUrl() {
+				if useUrl.UserProvidedFileExt() != "git" {
+					output.Infof("Downloading %q to container", useUrl.UrlFilename())
+				}
+			} else {
+				if useUrl.OnlineMethod() != "script" {
+					output.Infof("Copying %q to container", useUrl.UrlFilename())
+				}
+			}
+			useUrl.CopyToContainer(containerId)
 
-	// Download and restore DB if asked
-	if c.usingDatabaseUrl != "" {
-		c.downloadAndRestoreDb(
-			controller,
-			containerId,
-			userName,
-		)
+			if useUrl.IsExtractionNeeded() {
+				output.Infof("Extracting files from %q archive", useUrl.UrlFilename())
+				useUrl.Extract()
+			}
+
+			if useUrl.OnlineMethod() != "script" {
+				output.Infof("Bringing database %q online (%s)", useUrl.DatabaseName(), useUrl.OnlineMethod())
+			}
+
+			// Connect to master, unless a default database was specified (at this point the default database
+			// has not been set yet, so we need to specify it in the -d statement
+			databaseToConnectTo := "master"
+			if c.defaultDatabase != "" {
+				databaseToConnectTo = c.defaultDatabase
+			}
+			if useUrl.OnlineMethod() == "script" {
+				runSqlcmdInContainer := func(query string) {
+					cmd := []string{
+						"/opt/mssql-tools/bin/sqlcmd",
+						"-S",
+						"localhost",
+						"-U",
+						contextOptions.Username,
+						"-P",
+						contextOptions.Password,
+						"-d",
+						databaseToConnectTo,
+						"-i",
+						"/var/opt/mssql/backup/" + useUrl.UrlFilename(),
+					}
+
+					controller.RunCmdInContainer(containerId, cmd, container.ExecOptions{})
+				}
+				useUrl.BringOnline(runSqlcmdInContainer, contextOptions.Username, contextOptions.Password)
+			} else {
+				useUrl.BringOnline(c.sql.Query, contextOptions.Username, contextOptions.Password)
+			}
+
+			for _, f := range dotsqlcmdconfig.DatabaseFiles(i) {
+				//if file is .sql
+				if strings.HasSuffix(f, ".sql") {
+					// If not on Windows, Replace \ with / in the file path
+					if runtime.GOOS != "windows" {
+						f = strings.Replace(f, "\\", "/", -1)
+					} else {
+						// If on Windows, replace / with \ in the file path
+						f = strings.Replace(f, "/", "\\", -1)
+					}
+
+					//run sql file
+					output.Infof("Running %q", f)
+
+					c.sql.ExecuteSqlFile(f)
+				}
+			}
+		}
+	}
+
+	dabPort := 0
+	fleetManagerPort := 0
+	for i, addOn := range c.addOn {
+
+		if addOn == "fleet-manager" {
+			dabImageName := "fleet-manager"
+
+			if !c.useCached {
+				c.downloadImage(dabImageName, output, controller)
+			}
+
+			fleetManagerPort = config.FindFreePort(8080)
+
+			fleetManagerRunOptions := container.RunOptions{
+				PortInternal: 80,
+				Port:         fleetManagerPort,
+				Architecture: c.architecture,
+				Os:           c.os,
+				Network:      c.network,
+			}
+
+			addOnContainerId := controller.ContainerRun(
+				dabImageName,
+				fleetManagerRunOptions,
+			)
+
+			contextName := config.CurrentContextName()
+
+			// Save add-on details to config file now, so it can be deleted even
+			// if something below fails
+			config.AddAddOn(
+				contextName,
+				"fleet-manager",
+				addOnContainerId,
+				dabImageName,
+				"127.0.0.1",
+				fleetManagerPort)
+		} else if addOn == "dab" {
+			dabImageName := "mcr.microsoft.com/azure-databases/data-api-builder"
+
+			if !c.useCached {
+				c.downloadImage(dabImageName, output, controller)
+			}
+
+			dabPort = config.FindFreePort(5000)
+
+			dabRunOptions := container.RunOptions{
+				PortInternal: 5000,
+				Port:         dabPort,
+				Architecture: c.architecture,
+				Os:           c.os,
+				Network:      c.network,
+			}
+
+			addOnContainerId := controller.ContainerRun(
+				dabImageName,
+				dabRunOptions,
+			)
+
+			contextName := config.CurrentContextName()
+
+			// Save add-on details to config file now, so it can be deleted even
+			// if something below fails
+			config.AddAddOn(
+				contextName,
+				"dab",
+				addOnContainerId,
+				dabImageName,
+				"127.0.0.1",
+				dabPort)
+
+			if len(c.addOnUse) >= i+1 {
+
+				var dabConfigJson map[string]interface{}
+				dabConfigString := file.GetContents(c.addOnUse[i])
+				json.Unmarshal([]byte(dabConfigString), &dabConfigJson)
+
+				dataSource := dabConfigJson["data-source"]
+				dataSource.(map[string]interface{})["connection-string"] =
+					fmt.Sprintf("Server=%s;Database=%s;User ID=%s;Password=%s;TrustServerCertificate=true",
+						c.name,
+						c.defaultDatabase,
+						userName,
+						password)
+
+				newData, err := json.Marshal(dabConfigJson)
+				if err != nil {
+					panic(err)
+				}
+
+				var prettyJSON bytes.Buffer
+				json.Indent(&prettyJSON, newData, "", "    ")
+
+				folder.RemoveAll("tmp-dab-config.json")
+				folder.MkdirAll("tmp-dab-config.json")
+
+				f := file.OpenFile(filepath.Join("tmp-dab-config.json", "dab-config.json"))
+				f.WriteString(prettyJSON.String())
+				f.Close()
+
+				// Download the dab-config file to the container
+				controller.CopyFile(
+					addOnContainerId,
+					filepath.Join("tmp-dab-config.json", "dab-config.json"),
+					"/App",
+				)
+
+				folder.RemoveAll("tmp-dab-config.json")
+			}
+
+			// Restart the container, now that the dab-config file is there
+			controller.ContainerStop(addOnContainerId)
+			controller.ContainerStart(addOnContainerId)
+		} else {
+			output.Fatal("%q is an invalid add-on type", addOn)
+		}
 	}
 
 	hints := [][]string{}
 
-	// TODO: sqlcmd open ads only support on Windows right now, add Mac support
+	// TODO: sqlcmd open ads only supported on Windows and Mac right now, add Linux support
 	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
 		hints = append(hints, []string{localizer.Sprintf("Open in Azure Data Studio"), "sqlcmd open ads"})
 	}
@@ -390,72 +724,147 @@ func (c *MssqlBase) createContainer(imageName string, contextName string) {
 	hints = append(hints, []string{localizer.Sprintf("See connection strings"), "sqlcmd config connection-strings"})
 	hints = append(hints, []string{localizer.Sprintf("Remove"), "sqlcmd delete"})
 
-	output.InfoWithHintExamples(hints,
-		localizer.Sprintf("Now ready for client connections on port %d",
-			c.port),
-	)
-}
+	for _, addOn := range c.addOn {
+		if addOn == "fleet-manager" {
+			hints = append(hints, []string{
+				localizer.Sprintf("Fleet Manager (Renzo) API UI"),
+				fmt.Sprintf("http://localhost:%d/swagger/index.html", fleetManagerPort),
+			})
+		}
 
-func (c *MssqlBase) validateUsingUrlExists() {
-	output := c.Cmd.Output()
-	databaseUrl := extractUrl(c.usingDatabaseUrl)
-	u, err := url.Parse(databaseUrl)
-	c.CheckErr(err)
+		if addOn == "dab" {
+			hints = append(hints, []string{
+				localizer.Sprintf("Data API Builder (DAB) Health Status"),
+				fmt.Sprintf("curl -s http://localhost:%d", dabPort),
+			})
+		}
 
-	if u.Scheme != "http" && u.Scheme != "https" {
-		output.FatalWithHints(
-			[]string{
-				localizer.Sprintf("--using URL must be http or https"),
-			},
-			localizer.Sprintf("%q is not a valid URL for --using flag", c.usingDatabaseUrl))
-	}
+		if addOn == "fleet-manager" {
+			output.Info(localizer.Sprintf("Now ready for Fleet Manager connections on port %v",
+				strconv.Itoa(fleetManagerPort)),
+			)
+		}
 
-	if u.Path == "" {
-		output.FatalWithHints(
-			[]string{
-				localizer.Sprintf("--using URL must have a path to .bak file"),
-			},
-			localizer.Sprintf("%q is not a valid URL for --using flag", c.usingDatabaseUrl))
+		if addOn == "dab" {
+			output.Info(localizer.Sprintf("Now ready for DAB connections on port %v",
+				strconv.Itoa(dabPort)),
+			)
+		}
 	}
 
-	// At the moment we only support attaching .bak files, but we should
-	// support .bacpacs and .mdfs in the future
-	if _, file := filepath.Split(u.Path); filepath.Ext(file) != ".bak" {
-		output.FatalWithHints(
-			[]string{
-				localizer.Sprintf("--using file URL must be a .bak file"),
-			},
-			localizer.Sprintf("Invalid --using file type"))
+	output.InfofWithHintExamples(hints,
+		localizer.Sprintf("Now ready for SQL connections on port %v",
+			strconv.Itoa(c.port)),
+	)
+
+	if c.openTool == "ads" {
+		ads := open.Ads{}
+		ads.SetCrossCuttingConcerns(dependency.Options{
+			EndOfLine: pal.LineBreak(),
+			Output:    c.Output(),
+		})
+
+		user := &sqlconfig.User{
+			AuthenticationType: "basic",
+			BasicAuth: &sqlconfig.BasicAuthDetails{
+				Username:           contextOptions.Username,
+				PasswordEncryption: c.passwordEncryption,
+				Password:           secret.Encode(contextOptions.Password, c.passwordEncryption)},
+			Name: contextOptions.Username}
+
+		ads.PersistCredentialForAds(endpoint.EndpointDetails.Address, endpoint, user)
+
+		output := c.Output()
+		args := []string{"-r", fmt.Sprintf("--server=%s", fmt.Sprintf(
+			"%s,%d",
+			"127.0.0.1",
+			c.port))}
+
+		args = append(args, fmt.Sprintf("--user=%s",
+			strings.Replace(contextOptions.Username, `"`, `\"`, -1)))
+
+		adsTool := tools.NewTool("ads")
+		if !adsTool.IsInstalled() {
+			output.Fatalf(adsTool.HowToInstall())
+		}
+
+		// BUGBUG: This should come from: displayPreLaunchInfo
+		output.Info(localizer.Sprintf("Press Ctrl+C to exit this process..."))
+
+		_, err := adsTool.Run(args, tool.RunOptions{})
+		c.CheckErr(err)
 	}
 
-	// Verify the url actually exists, and early exit if it doesn't
-	urlExists(databaseUrl, output)
+	if c.openTool == "vscode" {
+		vscode := tools.NewTool("vscode")
+		if !vscode.IsInstalled() {
+			output.Fatalf(vscode.HowToInstall())
+		}
+
+		// BUGBUG: This should come from: displayPreLaunchInfo
+		output.Info(localizer.Sprintf("Launching Visual Studio Code..."))
+
+		_, err := vscode.Run([]string{"."}, tool.RunOptions{})
+		c.CheckErr(err)
+	}
 }
 
-func (c *MssqlBase) query(commandText string) {
-	c.sql.Query(commandText)
+func (c *MssqlBase) verifyUseSourceFileExists(
+	controller *container.Controller,
+	output *output.Output) (useUrls []ingest.Ingest) {
+
+	for i, url := range c.useUrl {
+
+		mechanism := ""
+		if len(c.useMechanism) > i {
+			mechanism = c.useMechanism[i]
+		}
+
+		useUrls = append(useUrls, ingest.NewIngest(url, controller, ingest.IngestOptions{
+			Mechanism:    mechanism,
+			DatabaseName: c.defaultDatabase,
+		}))
+
+		if !useUrls[i].IsValidFileExtension() {
+			output.FatalWithHints(
+				[]string{
+					fmt.Sprintf(
+						localizer.Sprintf("--use must be a path to a file with a %q extension"),
+						ingest.ValidFileExtensions(),
+					),
+				},
+				localizer.Sprintf("%q is not a valid file extension for --use flag"), useUrls[i].UserProvidedFileExt())
+		}
+
+		if useUrls[i].IsRemoteUrl() && !useUrls[i].IsValidScheme() {
+			output.FatalfWithHints(
+				[]string{
+					fmt.Sprintf(
+						localizer.Sprintf("--use URL must one of %q"),
+						strings.Join(useUrls[i].ValidSchemes(), ", "),
+					),
+				},
+				localizer.Sprintf("%q is not a valid URL for --use flag", useUrls[i].UrlFilename()))
+		}
+
+		if !useUrls[i].SourceFileExists() {
+			output.FatalfWithHints(
+				[]string{localizer.Sprintf("File does not exist at URL %q", useUrls[i].UrlFilename())},
+				"Unable to download file")
+		}
+	}
+
+	return useUrls
 }
 
-// createNonSaUser creates a user (non-sa) and assigns the sysadmin role
+// createUser creates a user (non-sa) and assigns the sysadmin role
 // to the user. It also creates a default database with the provided name
 // and assigns the default database to the user. Finally, it disables
 // the sa account and rotates the sa password for security reasons.
-func (c *MssqlBase) createNonSaUser(
+func (c *MssqlBase) createUser(
 	userName string,
 	password string,
 ) {
-	output := c.Cmd.Output()
-
-	defaultDatabase := "master"
-
-	if c.defaultDatabase != "" {
-		defaultDatabase = c.defaultDatabase
-
-		// Create the default database, if it isn't a downloaded database
-		output.Info(localizer.Sprintf("Creating default database [%s]", defaultDatabase))
-		c.query(fmt.Sprintf("CREATE DATABASE [%s]", defaultDatabase))
-	}
-
 	const createLogin = `CREATE LOGIN [%s]
 WITH PASSWORD=N'%s',
 DEFAULT_DATABASE=[%s],
@@ -465,147 +874,44 @@ CHECK_POLICY=OFF`
 @loginame = N'%s',
 @rolename = N'sysadmin'`
 
-	c.query(fmt.Sprintf(createLogin, userName, password, defaultDatabase))
-	c.query(fmt.Sprintf(addSrvRoleMember, userName))
+	output := c.Cmd.Output()
+	defaultDatabase := "master"
+
+	if c.defaultDatabase != "" {
+		defaultDatabase = c.defaultDatabase
+
+		// Create the default database, if it isn't a downloaded database
+		output.Infof(localizer.Sprintf("Creating default database %q", defaultDatabase))
+		c.sql.Query(fmt.Sprintf("CREATE DATABASE [%s]", defaultDatabase))
+	}
+
+	c.sql.Query(fmt.Sprintf(createLogin, userName, password, defaultDatabase))
+	c.sql.Query(fmt.Sprintf(addSrvRoleMember, userName))
 
 	// Correct safety protocol is to rotate the sa password, because the first
 	// sa password has been in the docker environment (as SA_PASSWORD)
-	c.query(fmt.Sprintf("ALTER LOGIN [sa] WITH PASSWORD = N'%s';",
+	c.sql.Query(fmt.Sprintf("ALTER LOGIN [sa] WITH PASSWORD = N'%s';",
 		c.generatePassword()))
-	c.query("ALTER LOGIN [sa] DISABLE")
+	c.sql.Query("ALTER LOGIN [sa] DISABLE")
 
 	if c.defaultDatabase != "" {
-		c.query(fmt.Sprintf("ALTER AUTHORIZATION ON DATABASE::[%s] TO %s",
+		c.sql.Query(fmt.Sprintf("ALTER AUTHORIZATION ON DATABASE::[%s] TO %s",
 			defaultDatabase, userName))
 	}
 }
 
-func getDbNameAsIdentifier(dbName string) string {
-	escapedDbNAme := strings.ReplaceAll(dbName, "'", "''")
-	return strings.ReplaceAll(escapedDbNAme, "]", "]]")
-}
-
-func getDbNameAsNonIdentifier(dbName string) string {
-	return strings.ReplaceAll(dbName, "]", "]]")
-}
-
-// parseDbName returns the databaseName from --using arg
-// It sets database name to the specified database name
-// or in absence of it, it is set to the filename without
-// extension.
-func parseDbName(usingDbUrl string) string {
-	u, _ := url.Parse(usingDbUrl)
-	dbToken := path.Base(u.Path)
-	if dbToken != "." && dbToken != "/" {
-		lastIdx := strings.LastIndex(dbToken, ".bak")
-		if lastIdx != -1 {
-			//Get file name without extension
-			fileName := dbToken[0:lastIdx]
-			lastIdx += 5
-			if lastIdx >= len(dbToken) {
-				return fileName
-			}
-			//Return database name if it was specified
-			return dbToken[lastIdx:]
-		}
-	}
-	return ""
-}
-
-func extractUrl(usingArg string) string {
-	urlEndIdx := strings.LastIndex(usingArg, ".bak")
-	if urlEndIdx != -1 {
-		return usingArg[0:(urlEndIdx + 4)]
-	}
-	return usingArg
-}
-
-func (c *MssqlBase) downloadAndRestoreDb(
-	controller *container.Controller,
-	containerId string,
-	userName string,
-) {
-	output := c.Cmd.Output()
-	databaseName := parseDbName(c.usingDatabaseUrl)
-	databaseUrl := extractUrl(c.usingDatabaseUrl)
-
-	_, file := filepath.Split(databaseUrl)
-
-	// Download file from URL into container
-	output.Info(localizer.Sprintf("Downloading %s", file))
-
-	temporaryFolder := "/var/opt/mssql/backup"
-
-	controller.DownloadFile(
-		containerId,
-		databaseUrl,
-		temporaryFolder,
-	)
-
-	// Restore database from file
-	output.Info(localizer.Sprintf("Restoring database %s", databaseName))
-
-	dbNameAsIdentifier := getDbNameAsIdentifier(databaseName)
-	dbNameAsNonIdentifier := getDbNameAsNonIdentifier(databaseName)
-
-	text := `SET NOCOUNT ON;
-
--- Build a SQL Statement to restore any .bak file to the Linux filesystem
-DECLARE @sql NVARCHAR(max)
-
--- This table definition works since SQL Server 2017, therefore 
--- works for all SQL Server containers (which started in 2017)
-DECLARE @fileListTable TABLE (
-    [LogicalName]           NVARCHAR(128),
-    [PhysicalName]          NVARCHAR(260),
-    [Type]                  CHAR(1),
-    [FileGroupName]         NVARCHAR(128),
-    [Size]                  NUMERIC(20,0),
-    [MaxSize]               NUMERIC(20,0),
-    [FileID]                BIGINT,
-    [CreateLSN]             NUMERIC(25,0),
-    [DropLSN]               NUMERIC(25,0),
-    [UniqueID]              UNIQUEIDENTIFIER,
-    [ReadOnlyLSN]           NUMERIC(25,0),
-    [ReadWriteLSN]          NUMERIC(25,0),
-    [BackupSizeInBytes]     BIGINT,
-    [SourceBlockSize]       INT,
-    [FileGroupID]           INT,
-    [LogGroupGUID]          UNIQUEIDENTIFIER,
-    [DifferentialBaseLSN]   NUMERIC(25,0),
-    [DifferentialBaseGUID]  UNIQUEIDENTIFIER,
-    [IsReadOnly]            BIT,
-    [IsPresent]             BIT,
-    [TDEThumbprint]         VARBINARY(32),
-    [SnapshotURL]           NVARCHAR(360)
-)
-
-INSERT INTO @fileListTable
-EXEC('RESTORE FILELISTONLY FROM DISK = ''%s/%s''')
-SET @sql = 'RESTORE DATABASE [%s] FROM DISK = ''%s/%s'' WITH '
-SELECT @sql = @sql + char(13) + ' MOVE ''' + LogicalName + ''' TO ''/var/opt/mssql/' + LogicalName + '.' + RIGHT(PhysicalName,CHARINDEX('\',PhysicalName)) + ''','
-FROM @fileListTable
-WHERE IsPresent = 1
-SET @sql = SUBSTRING(@sql, 1, LEN(@sql)-1)
-EXEC(@sql)`
-
-	c.query(fmt.Sprintf(text, temporaryFolder, file, dbNameAsIdentifier, temporaryFolder, file))
-
-	alterDefaultDb := fmt.Sprintf(
-		"ALTER LOGIN [%s] WITH DEFAULT_DATABASE = [%s]",
-		userName,
-		dbNameAsNonIdentifier)
-	c.query(alterDefaultDb)
-}
-
 func (c *MssqlBase) downloadImage(
 	imageName string,
 	output *output.Output,
 	controller *container.Controller,
 ) {
-	output.Info(localizer.Sprintf("Downloading %v", imageName))
+	output.Info(localizer.Sprintf("Downloading %q", imageName))
 	err := controller.EnsureImage(imageName)
 	if err != nil || c.unitTesting {
+
+		// BUGBUG: Add hint for the new issue on Mac with Docker Desktop
+		// https://stackoverflow.com/questions/44084846/cannot-connect-to-the-docker-daemon-on-macos
+		// (see the "permanent solution" part)
 		output.FatalErrorWithHints(
 			err,
 			[]string{
@@ -621,15 +927,6 @@ func (c *MssqlBase) downloadImage(
 	}
 }
 
-// Verify the file exists at the URL
-func urlExists(url string, output *output.Output) {
-	if !http.UrlExists(url) {
-		output.FatalWithHints(
-			[]string{localizer.Sprintf("File does not exist at URL")},
-			localizer.Sprintf("Unable to download file"))
-	}
-}
-
 func (c *MssqlBase) generatePassword() (password string) {
 	password = secret.Generate(
 		c.passwordLength,
diff --git a/cmd/modern/root/install/mssql.go b/cmd/modern/root/install/mssql.go
index 9465652c..8726bf83 100644
--- a/cmd/modern/root/install/mssql.go
+++ b/cmd/modern/root/install/mssql.go
@@ -34,6 +34,15 @@ func (c *Mssql) DefineCommand(...cmdparser.CommandOptions) {
 					"sqlcmd create mssql --tag 2019-latest",
 				},
 			},
+			{
+				Description: localizer.Sprintf("Create SQL Server, download and attach AdventureWorks sample database"),
+				Steps:       []string{"sqlcmd create mssql --use https://aka.ms/AdventureWorksLT.bak"}},
+			{
+				Description: localizer.Sprintf("Create SQL Server, download and attach AdventureWorks sample database with different database name"),
+				Steps:       []string{"sqlcmd create mssql --use https://aka.ms/AdventureWorksLT.bak,adventureworks"}},
+			{
+				Description: localizer.Sprintf("Create SQL Server with an empty user database"),
+				Steps:       []string{"sqlcmd create mssql --database db1"}},
 			{
 				Description: localizer.Sprintf("Create SQL Server, download and attach AdventureWorks sample database"),
 				Steps:       []string{"sqlcmd create mssql --using https://aka.ms/AdventureWorksLT.bak"}},
@@ -42,7 +51,7 @@ func (c *Mssql) DefineCommand(...cmdparser.CommandOptions) {
 				Steps:       []string{"sqlcmd create mssql --using https://aka.ms/AdventureWorksLT.bak,adventureworks"}},
 			{
 				Description: localizer.Sprintf("Create SQL Server with an empty user database"),
-				Steps:       []string{"sqlcmd create mssql --user-database db1"}},
+				Steps:       []string{"sqlcmd create mssql --database db1"}},
 			{
 				Description: localizer.Sprintf("Install/Create SQL Server with full logging"),
 				Steps:       []string{"sqlcmd create mssql --verbosity 4"}},
diff --git a/cmd/modern/root/install/mssql_test.go b/cmd/modern/root/install/mssql_test.go
index 28d80674..91ae7bc4 100644
--- a/cmd/modern/root/install/mssql_test.go
+++ b/cmd/modern/root/install/mssql_test.go
@@ -25,7 +25,7 @@ func TestInstallMssql(t *testing.T) {
 	cmdparser.TestCmd[*mssql.GetTags]()
 	cmdparser.TestCmd[*Mssql](
 		fmt.Sprintf(
-			`--accept-eula --user-database foo --errorlog-wait-line "Hello from Docker!" --registry %v --repo %v`,
+			`--accept-eula --database foo --errorlog-wait-line "Hello from Docker!" --registry %v --repo %v`,
 			registry,
 			repo))
 
@@ -54,34 +54,34 @@ func TestNegInstallMssql2(t *testing.T) {
 func TestNegInstallMssql3(t *testing.T) {
 	cmdparser.TestSetup(t)
 	assert.Panics(t, func() {
-		cmdparser.TestCmd[*Mssql]("--accept-eula --using https://does/not/exist.bak")
+		cmdparser.TestCmd[*Mssql]("--accept-eula --use https://does/not/exist.bak")
 	})
 }
 
 func TestNegInstallMssql4(t *testing.T) {
 	cmdparser.TestSetup(t)
 	assert.Panics(t, func() {
-		cmdparser.TestCmd[*Mssql]("--accept-eula --user-database bad'name")
+		cmdparser.TestCmd[*Mssql]("--accept-eula --database bad'name")
 	})
 }
 
 func TestNegInstallMssql5(t *testing.T) {
 	cmdparser.TestSetup(t)
 	assert.Panics(t, func() {
-		cmdparser.TestCmd[*Mssql]("--accept-eula --using https://not/bak/file")
+		cmdparser.TestCmd[*Mssql]("--accept-eula --use https://not/bak/file")
 	})
 }
 
 func TestNegInstallMssql6(t *testing.T) {
 	cmdparser.TestSetup(t)
 	assert.Panics(t, func() {
-		cmdparser.TestCmd[*Mssql]("--accept-eula --using file://not/http")
+		cmdparser.TestCmd[*Mssql]("--accept-eula --use file://not/http")
 	})
 }
 
 func TestNegInstallMssql7(t *testing.T) {
 	cmdparser.TestSetup(t)
 	assert.Panics(t, func() {
-		cmdparser.TestCmd[*Mssql]("--accept-eula --using https://aka.ms/AdventureWorksLT")
+		cmdparser.TestCmd[*Mssql]("--accept-eula --use https://aka.ms/AdventureWorksLT")
 	})
 }
diff --git a/cmd/modern/root/open.go b/cmd/modern/root/open.go
index d209db81..db875d81 100644
--- a/cmd/modern/root/open.go
+++ b/cmd/modern/root/open.go
@@ -31,5 +31,7 @@ func (c *Open) SubCommands() []cmdparser.Command {
 
 	return []cmdparser.Command{
 		cmdparser.New[*open.Ads](dependencies),
+		cmdparser.New[*open.Ssms](dependencies),
+		cmdparser.New[*open.Vscode](dependencies),
 	}
 }
diff --git a/cmd/modern/root/open/ads.go b/cmd/modern/root/open/ads.go
index 12f7f7b7..296492ba 100644
--- a/cmd/modern/root/open/ads.go
+++ b/cmd/modern/root/open/ads.go
@@ -5,6 +5,7 @@ package open
 
 import (
 	"fmt"
+	"github.com/microsoft/go-sqlcmd/internal/tools/tool"
 	"runtime"
 	"strings"
 
@@ -47,7 +48,7 @@ func (c *Ads) run() {
 	// If basic auth is used, we need to persist the password in the OS in a way
 	// that ADS can access it.  The method used is OS specific.
 	if user != nil && user.AuthenticationType == "basic" {
-		c.persistCredentialForAds(endpoint.EndpointDetails.Address, endpoint, user)
+		c.PersistCredentialForAds(endpoint.EndpointDetails.Address, endpoint, user)
 		c.launchAds(endpoint.EndpointDetails.Address, endpoint.EndpointDetails.Port, user.BasicAuth.Username)
 	} else {
 		c.launchAds(endpoint.EndpointDetails.Address, endpoint.EndpointDetails.Port, "")
@@ -76,6 +77,7 @@ func (c *Ads) launchAds(host string, port int, username string) {
 				port)),
 	}
 
+	// If a username is specified, use that (basic auth), otherwise use integrated auth
 	if username != "" {
 
 		// Here's a fun SQL Server behavior  - it allows you to create database
@@ -88,13 +90,13 @@ func (c *Ads) launchAds(host string, port int, username string) {
 		}
 	}
 
-	tool := tools.NewTool("ads")
-	if !tool.IsInstalled() {
-		output.Fatalf(tool.HowToInstall())
+	ads := tools.NewTool("ads")
+	if !ads.IsInstalled() {
+		output.Fatalf(ads.HowToInstall())
 	}
 
 	c.displayPreLaunchInfo()
 
-	_, err := tool.Run(args)
+	_, err := ads.Run(args, tool.RunOptions{})
 	c.CheckErr(err)
 }
diff --git a/cmd/modern/root/open/ads_darwin.go b/cmd/modern/root/open/ads_darwin.go
index 750bb20d..124e18fd 100644
--- a/cmd/modern/root/open/ads_darwin.go
+++ b/cmd/modern/root/open/ads_darwin.go
@@ -16,7 +16,7 @@ type Ads struct {
 	cmdparser.Cmd
 }
 
-func (c *Ads) persistCredentialForAds(hostname string, endpoint sqlconfig.Endpoint, user *sqlconfig.User) {
+func (c *Ads) PersistCredentialForAds(hostname string, endpoint sqlconfig.Endpoint, user *sqlconfig.User) {
 	// UNDONE: See - https://github.com/microsoft/go-sqlcmd/issues/257
 }
 
diff --git a/cmd/modern/root/open/ads_linux.go b/cmd/modern/root/open/ads_linux.go
index ae1dc827..f9ad0c62 100644
--- a/cmd/modern/root/open/ads_linux.go
+++ b/cmd/modern/root/open/ads_linux.go
@@ -15,7 +15,7 @@ type Ads struct {
 	cmdparser.Cmd
 }
 
-func (c *Ads) persistCredentialForAds(hostname string, endpoint sqlconfig.Endpoint, user *sqlconfig.User) {
+func (c *Ads) PersistCredentialForAds(hostname string, endpoint sqlconfig.Endpoint, user *sqlconfig.User) {
 	panic("not implemented")
 }
 
diff --git a/cmd/modern/root/open/ads_windows.go b/cmd/modern/root/open/ads_windows.go
index c98aaad9..d3d1adb9 100644
--- a/cmd/modern/root/open/ads_windows.go
+++ b/cmd/modern/root/open/ads_windows.go
@@ -30,9 +30,9 @@ func (c *Ads) displayPreLaunchInfo() {
 	output.Info(localizer.Sprintf("Press Ctrl+C to exit this process..."))
 }
 
-// persistCredentialForAds stores a SQL password in the Windows Credential Manager
+// PersistCredentialForAds stores a SQL password in the Windows Credential Manager
 // for the given hostname and endpoint.
-func (c *Ads) persistCredentialForAds(
+func (c *Ads) PersistCredentialForAds(
 	hostname string,
 	endpoint sqlconfig.Endpoint,
 	user *sqlconfig.User,
@@ -65,7 +65,6 @@ func (c *Ads) adsKey(instance, database, authType, user string) string {
 		"Microsoft.SqlTools|"+
 			"itemtype:Profile|"+
 			"id:providerName:MSSQL|"+
-			"applicationName:azdata|"+
 			"authenticationType:%s|"+
 			"database:%s|"+
 			"server:%s|"+
diff --git a/cmd/modern/root/open/ads_windows_test.go b/cmd/modern/root/open/ads_windows_test.go
index a34b5247..700b72b5 100644
--- a/cmd/modern/root/open/ads_windows_test.go
+++ b/cmd/modern/root/open/ads_windows_test.go
@@ -27,7 +27,7 @@ func TestPersistCredentialForAds(t *testing.T) {
 			PasswordEncryption: "none",
 		},
 	}
-	ads.persistCredentialForAds("localhost", sqlconfig.Endpoint{
+	ads.PersistCredentialForAds("localhost", sqlconfig.Endpoint{
 		EndpointDetails: sqlconfig.EndpointDetails{
 			Port: 1433,
 		},
diff --git a/cmd/modern/root/open/ssms.go b/cmd/modern/root/open/ssms.go
new file mode 100644
index 00000000..91038186
--- /dev/null
+++ b/cmd/modern/root/open/ssms.go
@@ -0,0 +1,97 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package open
+
+import (
+	"fmt"
+	"github.com/microsoft/go-sqlcmd/internal/tools/tool"
+	"strings"
+
+	"github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig"
+	"github.com/microsoft/go-sqlcmd/internal/cmdparser"
+	"github.com/microsoft/go-sqlcmd/internal/config"
+	"github.com/microsoft/go-sqlcmd/internal/container"
+	"github.com/microsoft/go-sqlcmd/internal/localizer"
+	"github.com/microsoft/go-sqlcmd/internal/tools"
+)
+
+// Ads implements the `sqlcmd open ads` command. It opens
+// Azure Data Studio and connects to the current context by using the
+// credentials specified in the context.
+func (c *Ssms) DefineCommand(...cmdparser.CommandOptions) {
+	options := cmdparser.CommandOptions{
+		Use:   "ssms",
+		Short: "Open Sql Server Management Studio and connect to current context",
+		Examples: []cmdparser.ExampleOptions{{
+			Description: "Open SSMS and connect using the current context",
+			Steps:       []string{"sqlcmd open ssms"}}},
+		Run: c.run,
+	}
+
+	c.Cmd.DefineCommand(options)
+}
+
+// Launch ADS and connect to the current context. If the authentication type
+// is basic, we need to securely store the password in an Operating System
+// specific credential store, e.g. on Windows we use the Windows Credential
+// Manager.
+func (c *Ssms) run() {
+	endpoint, user := config.CurrentContext()
+
+	// If the context has a local container, ensure it is running, otherwise bail out
+	if endpoint.AssetDetails != nil && endpoint.AssetDetails.ContainerDetails != nil {
+		c.ensureContainerIsRunning(endpoint)
+	}
+
+	// If basic auth is used, we need to persist the password in the OS in a way
+	// that ADS can access it.  The method used is OS specific.
+	if user != nil && user.AuthenticationType == "basic" {
+		c.PersistCredentialForAds(endpoint.EndpointDetails.Address, endpoint, user)
+		c.launchAds(endpoint.EndpointDetails.Address, endpoint.EndpointDetails.Port, user.BasicAuth.Username)
+	} else {
+		c.launchAds(endpoint.EndpointDetails.Address, endpoint.EndpointDetails.Port, "")
+	}
+}
+
+func (c *Ssms) ensureContainerIsRunning(endpoint sqlconfig.Endpoint) {
+	output := c.Output()
+	controller := container.NewController()
+	if !controller.ContainerRunning(endpoint.AssetDetails.ContainerDetails.Id) {
+		output.FatalWithHintExamples([][]string{
+			{localizer.Sprintf("To start the container"), localizer.Sprintf("sqlcmd start")},
+		}, localizer.Sprintf("Container is not running"))
+	}
+}
+
+// launchAds launches the Azure Data Studio using the specified server and username.
+func (c *Ssms) launchAds(host string, port int, username string) {
+	output := c.Output()
+	args := []string{
+		"ssms ",
+		"-S",
+		fmt.Sprintf("%s,%d", host, port),
+	}
+
+	// If a username is specified, use that (basic auth), otherwise use integrated auth
+	if username != "" {
+
+		// Here's a fun SQL Server behavior  - it allows you to create database
+		// and login names that include the " character. SSMS escapes those
+		// with \" when invoking ADS on the command line, we do the same here
+		args = append(args, "-U")
+		args = append(args, fmt.Sprintf("%s", strings.Replace(username, `"`, `\"`, -1)))
+	} else {
+		args = append(args, "-E")
+	}
+
+	ssms := tools.NewTool("ssms")
+	if !ssms.IsInstalled() {
+		output.Fatalf(ssms.HowToInstall())
+	}
+
+	c.displayPreLaunchInfo()
+
+	_, err := ssms.Run(args, tool.RunOptions{})
+	c.CheckErr(err)
+}
diff --git a/cmd/modern/root/open/ssms_darwin.go b/cmd/modern/root/open/ssms_darwin.go
new file mode 100644
index 00000000..a5166ab1
--- /dev/null
+++ b/cmd/modern/root/open/ssms_darwin.go
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package open
+
+import (
+	"github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig"
+	"github.com/microsoft/go-sqlcmd/internal/cmdparser"
+)
+
+// Type Ads is used to implement the "open ads" which launches Azure
+// Data Studio and establishes a connection to the SQL Server for the current
+// context
+type Ssms struct {
+	cmdparser.Cmd
+}
+
+func (c *Ssms) PersistCredentialForAds(hostname string, endpoint sqlconfig.Endpoint, user *sqlconfig.User) {
+}
+
+func (c *Ssms) displayPreLaunchInfo() {
+
+}
diff --git a/cmd/modern/root/open/ssms_linux.go b/cmd/modern/root/open/ssms_linux.go
new file mode 100644
index 00000000..a5166ab1
--- /dev/null
+++ b/cmd/modern/root/open/ssms_linux.go
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package open
+
+import (
+	"github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig"
+	"github.com/microsoft/go-sqlcmd/internal/cmdparser"
+)
+
+// Type Ads is used to implement the "open ads" which launches Azure
+// Data Studio and establishes a connection to the SQL Server for the current
+// context
+type Ssms struct {
+	cmdparser.Cmd
+}
+
+func (c *Ssms) PersistCredentialForAds(hostname string, endpoint sqlconfig.Endpoint, user *sqlconfig.User) {
+}
+
+func (c *Ssms) displayPreLaunchInfo() {
+
+}
diff --git a/cmd/modern/root/open/ssms_windows.go b/cmd/modern/root/open/ssms_windows.go
new file mode 100644
index 00000000..159bf696
--- /dev/null
+++ b/cmd/modern/root/open/ssms_windows.go
@@ -0,0 +1,103 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package open
+
+import (
+	"fmt"
+
+	"github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig"
+	"github.com/microsoft/go-sqlcmd/internal/cmdparser"
+	"github.com/microsoft/go-sqlcmd/internal/credman"
+	"github.com/microsoft/go-sqlcmd/internal/localizer"
+	"github.com/microsoft/go-sqlcmd/internal/secret"
+)
+
+// Type Ads is used to implement the "open ads" which launches Azure
+// Data Studio and establishes a connection to the SQL Server for the current
+// context
+type Ssms struct {
+	cmdparser.Cmd
+
+	credential credman.Credential
+}
+
+// On Windows, the process blocks until the user exits ADS, let user know they can
+// Ctrl+C here.
+func (c *Ssms) displayPreLaunchInfo() {
+	output := c.Output()
+
+	output.Info(localizer.Sprintf("Press Ctrl+C to exit this process..."))
+}
+
+// PersistCredentialForAds stores a SQL password in the Windows Credential Manager
+// for the given hostname and endpoint.
+func (c *Ssms) PersistCredentialForAds(
+	hostname string,
+	endpoint sqlconfig.Endpoint,
+	user *sqlconfig.User,
+) {
+	// Create the target name that ADS will look for
+	targetName := c.adsKey(
+		fmt.Sprintf("%s,%d", hostname, rune(endpoint.Port)),
+		user.BasicAuth.Username)
+
+	// Store the SQL password in the Windows Credential Manager with the
+	// generated target name
+	c.credential = credman.Credential{
+		TargetName: targetName,
+		CredentialBlob: secret.DecodeAsUtf16(
+			user.BasicAuth.Password, user.BasicAuth.PasswordEncryption),
+		UserName: user.BasicAuth.Username,
+		Persist:  credman.PersistSession,
+	}
+
+	c.removePreviousCredential()
+	c.writeCredential()
+}
+
+// adsKey returns the credential target name for the given instance, database,
+// authentication type, and user.
+func (c *Ssms) adsKey(instance, user string) string {
+
+	// Microsoft:SSMS:19:127.0.0.1,1435:stuartpa:8c91a03d-f9b4-46c0-a305-b5dcc79ff907:1
+
+	// BUGBUG: Can't hardcode 19
+	// BUGBUG: What is that GUID?  Is it different on other peoples machines?
+	return fmt.Sprintf(
+		"Microsoft:"+
+			"SSMS:"+
+			"19:"+
+			"%s:"+
+			"%s:"+
+			"8c91a03d-f9b4-46c0-a305-b5dcc79ff907:1",
+		instance, user)
+}
+
+// removePreviousCredential removes any previously stored credentials with
+// the same target name as the current instance's credential.
+func (c *Ssms) removePreviousCredential() {
+	credentials, err := credman.EnumerateCredentials("", true)
+	c.CheckErr(err)
+
+	for _, cred := range credentials {
+		if cred.TargetName == c.credential.TargetName {
+			err = credman.DeleteCredential(cred, credman.CredTypeGeneric)
+			c.CheckErr(err)
+			break
+		}
+	}
+}
+
+// writeCredential stores the current instance's credential in the Windows Credential Manager
+func (c *Ssms) writeCredential() {
+	output := c.Output()
+
+	err := credman.WriteCredential(&c.credential, credman.CredTypeGeneric)
+	if err != nil {
+		output.FatalErrorWithHints(
+			err,
+			[]string{localizer.Sprintf("A 'Not enough memory resources are available' error can be caused by too many credentials already stored in Windows Credential Manager")},
+			localizer.Sprintf("Failed to write credential to Windows Credential Manager"))
+	}
+}
diff --git a/cmd/modern/root/open/vscode.go b/cmd/modern/root/open/vscode.go
new file mode 100644
index 00000000..f6637e44
--- /dev/null
+++ b/cmd/modern/root/open/vscode.go
@@ -0,0 +1,72 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package open
+
+import (
+	"github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig"
+	"github.com/microsoft/go-sqlcmd/internal/cmdparser"
+	"github.com/microsoft/go-sqlcmd/internal/config"
+	"github.com/microsoft/go-sqlcmd/internal/container"
+	"github.com/microsoft/go-sqlcmd/internal/localizer"
+	"github.com/microsoft/go-sqlcmd/internal/tools"
+	"github.com/microsoft/go-sqlcmd/internal/tools/tool"
+)
+
+// Vscode implements the `sqlcmd open vscode` (or just `sqlcmd open code`)
+// command. It opens VS Code and connects to the current context by using the
+// credentials specified in the context.
+func (c *Vscode) DefineCommand(...cmdparser.CommandOptions) {
+	options := cmdparser.CommandOptions{
+		Use:     "vscode",
+		Aliases: []string{"code"},
+		Short:   "Open Visual Studio Code and connect to current context",
+		Examples: []cmdparser.ExampleOptions{{
+			Description: "Open VS Code and connect using the current context",
+			Steps:       []string{"sqlcmd open vscode"}}},
+		Run: c.run,
+	}
+
+	c.Cmd.DefineCommand(options)
+}
+
+// Launch ADS and connect to the current context. If the authentication type
+// is basic, we need to securely store the password in an Operating System
+// specific credential store, e.g. on Windows we use the Windows Credential
+// Manager.
+func (c *Vscode) run() {
+	endpoint, _ := config.CurrentContext()
+
+	// If the context has a local container, ensure it is running, otherwise bail out
+	if endpoint.AssetDetails != nil && endpoint.AssetDetails.ContainerDetails != nil {
+		c.ensureContainerIsRunning(endpoint)
+	}
+
+	c.launchAds(endpoint.EndpointDetails.Address, endpoint.EndpointDetails.Port, "")
+}
+
+func (c *Vscode) ensureContainerIsRunning(endpoint sqlconfig.Endpoint) {
+	output := c.Output()
+	controller := container.NewController()
+	if !controller.ContainerRunning(endpoint.AssetDetails.ContainerDetails.Id) {
+		output.FatalWithHintExamples([][]string{
+			{localizer.Sprintf("To start the container"), localizer.Sprintf("sqlcmd start")},
+		}, localizer.Sprintf("Container is not running"))
+	}
+}
+
+// launchAds launches VS Code
+func (c *Vscode) launchAds(host string, port int, username string) {
+	output := c.Output()
+	args := []string{}
+
+	vscode := tools.NewTool("vscode")
+	if !vscode.IsInstalled() {
+		output.Fatalf(vscode.HowToInstall())
+	}
+
+	c.displayPreLaunchInfo()
+
+	_, err := vscode.Run(args, tool.RunOptions{})
+	c.CheckErr(err)
+}
diff --git a/cmd/modern/root/open/vscode_darwin.go b/cmd/modern/root/open/vscode_darwin.go
new file mode 100644
index 00000000..9ae42fdc
--- /dev/null
+++ b/cmd/modern/root/open/vscode_darwin.go
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package open
+
+import (
+	"github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig"
+	"github.com/microsoft/go-sqlcmd/internal/cmdparser"
+)
+
+// Type Ads is used to implement the "open ads" which launches Azure
+// Data Studio and establishes a connection to the SQL Server for the current
+// context
+type Vscode struct {
+	cmdparser.Cmd
+}
+
+func (c *Vscode) PersistCredentialForAds(hostname string, endpoint sqlconfig.Endpoint, user *sqlconfig.User) {
+}
+
+func (c *Vscode) displayPreLaunchInfo() {
+}
diff --git a/cmd/modern/root/open/vscode_linux.go b/cmd/modern/root/open/vscode_linux.go
new file mode 100644
index 00000000..9ae42fdc
--- /dev/null
+++ b/cmd/modern/root/open/vscode_linux.go
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package open
+
+import (
+	"github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig"
+	"github.com/microsoft/go-sqlcmd/internal/cmdparser"
+)
+
+// Type Ads is used to implement the "open ads" which launches Azure
+// Data Studio and establishes a connection to the SQL Server for the current
+// context
+type Vscode struct {
+	cmdparser.Cmd
+}
+
+func (c *Vscode) PersistCredentialForAds(hostname string, endpoint sqlconfig.Endpoint, user *sqlconfig.User) {
+}
+
+func (c *Vscode) displayPreLaunchInfo() {
+}
diff --git a/cmd/modern/root/open/vscode_windows.go b/cmd/modern/root/open/vscode_windows.go
new file mode 100644
index 00000000..d85e2ee6
--- /dev/null
+++ b/cmd/modern/root/open/vscode_windows.go
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package open
+
+import (
+	"github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig"
+	"github.com/microsoft/go-sqlcmd/internal/cmdparser"
+	"github.com/microsoft/go-sqlcmd/internal/credman"
+	"github.com/microsoft/go-sqlcmd/internal/localizer"
+)
+
+// Type Vscode is used to implement the "open vscode" which launches VS Code
+type Vscode struct {
+	cmdparser.Cmd
+
+	credential credman.Credential
+}
+
+// On Windows, the process blocks until the user exits ADS, let user know they can
+// Ctrl+C here.
+func (c *Vscode) displayPreLaunchInfo() {
+	output := c.Output()
+
+	output.Info(localizer.Sprintf("Press Ctrl+C to exit this process..."))
+}
+
+// PersistCredentialForAds stores a SQL password in the Windows Credential Manager
+// for the given hostname and endpoint.
+func (c *Vscode) PersistCredentialForAds(
+	hostname string,
+	endpoint sqlconfig.Endpoint,
+	user *sqlconfig.User,
+) {
+}
diff --git a/cmd/modern/root/query.go b/cmd/modern/root/query.go
index b61958ea..8c5372c8 100644
--- a/cmd/modern/root/query.go
+++ b/cmd/modern/root/query.go
@@ -75,7 +75,7 @@ func (c *Query) DefineCommand(...cmdparser.CommandOptions) {
 func (c *Query) run() {
 	endpoint, user := config.CurrentContext()
 
-	s := sql.New(sql.SqlOptions{})
+	s := sql.NewSql(sql.SqlOptions{})
 	if c.text == "" {
 		s.Connect(endpoint, user, sql.ConnectOptions{Database: c.database, Interactive: true})
 	} else {
diff --git a/cmd/modern/root/uninstall.go b/cmd/modern/root/uninstall.go
index e44720a3..42b3922b 100644
--- a/cmd/modern/root/uninstall.go
+++ b/cmd/modern/root/uninstall.go
@@ -113,16 +113,40 @@ func (c *Uninstall) run() {
 				c.userDatabaseSafetyCheck(controller, id)
 			}
 
-			output.Info(localizer.Sprintf("Removing context %s", config.CurrentContextName()))
+			output.Info(localizer.Sprintf("Removing context %q", config.CurrentContextName()))
 
 			if controller.ContainerExists(id) {
-				output.Info(localizer.Sprintf("Stopping %s", endpoint.ContainerDetails.Image))
+				output.Info(localizer.Sprintf("Stopping %q", endpoint.ContainerDetails.Image))
 				err := controller.ContainerStop(id)
 				c.CheckErr(err)
 				err = controller.ContainerRemove(id)
 				c.CheckErr(err)
 			} else {
-				output.Warn(localizer.Sprintf("Container %q no longer exists, continuing to remove context...", id))
+				output.Warn(localizer.Sprintf("Container %q (for endpoint %q) no longer exists, continuing to remove context...", id, endpoint.Name))
+			}
+
+			addOns := config.CurrentContextAddOns()
+			for _, a := range addOns {
+				e := config.GetEndpoint(a.Endpoint)
+				if controller.ContainerExists(e.ContainerDetails.Id) {
+					output.Info(localizer.Sprintf("Stopping %q", e.ContainerDetails.Image))
+					err := controller.ContainerStop(e.ContainerDetails.Id)
+					c.CheckErr(err)
+					err = controller.ContainerRemove(e.ContainerDetails.Id)
+					c.CheckErr(err)
+				} else {
+					output.Warn(localizer.Sprintf("Container %q (for endpoint %q) no longer exists, continuing to remove context...", e.ContainerDetails.Id, a.Endpoint))
+				}
+
+				config.DeleteEndpoint(a.Endpoint)
+			}
+
+			network := config.CurrentContextNetwork()
+			if network != nil {
+				if controller.NetworkExists(*network) {
+					output.Info(localizer.Sprintf("Removing container network %q", *network))
+					controller.NetworkDelete(*network)
+				}
 			}
 		}
 
diff --git a/cmd/modern/root/use.go b/cmd/modern/root/use.go
new file mode 100644
index 00000000..eadebb83
--- /dev/null
+++ b/cmd/modern/root/use.go
@@ -0,0 +1,106 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package root
+
+import (
+	"fmt"
+	"github.com/microsoft/go-sqlcmd/internal/cmdparser"
+	"github.com/microsoft/go-sqlcmd/internal/config"
+	"github.com/microsoft/go-sqlcmd/internal/container"
+	"github.com/microsoft/go-sqlcmd/internal/secret"
+	"github.com/microsoft/go-sqlcmd/internal/sql"
+	"github.com/microsoft/go-sqlcmd/pkg/mssqlcontainer/ingest"
+)
+
+type Use struct {
+	cmdparser.Cmd
+
+	url          string
+	useMechanism string
+
+	sql sql.Sql
+}
+
+func (c *Use) DefineCommand(...cmdparser.CommandOptions) {
+	examples := []cmdparser.ExampleOptions{
+		{
+			Description: "Download AdventureWorksLT into container for current context, set as default database",
+			Steps:       []string{`sqlcmd use https://aka.ms/AdventureWorksLT.bak`}},
+	}
+
+	options := cmdparser.CommandOptions{
+		Use:                        "use",
+		Short:                      fmt.Sprintf("Download database (into container) (%s)", ingest.ValidFileExtensions()),
+		Examples:                   examples,
+		Run:                        c.run,
+		FirstArgAlternativeForFlag: &cmdparser.AlternativeForFlagOptions{Flag: "url", Value: &c.url},
+	}
+
+	c.Cmd.DefineCommand(options)
+
+	c.AddFlag(cmdparser.FlagOptions{
+		String: &c.url,
+		Name:   "url",
+		Usage:  "Name of context to set as current context"})
+
+	c.AddFlag(cmdparser.FlagOptions{
+		String:        &c.useMechanism,
+		DefaultString: "",
+		Name:          "use-mechanism",
+		Usage:         "Mechanism to use to bring database online (attach, restore, dacfx)",
+	})
+}
+
+func (c *Use) run() {
+	output := useOutput{output: c.Output()}
+
+	controller := container.NewController()
+	id := config.ContainerId()
+
+	if !config.CurrentContextEndpointHasContainer() {
+		output.FatalNoContainerInCurrentContext()
+	}
+
+	if !controller.ContainerRunning(id) {
+		output.FatalContainerNotRunning()
+	}
+
+	endpoint, user := config.CurrentContext()
+
+	c.sql = sql.NewSql(sql.SqlOptions{})
+	c.sql.Connect(endpoint, user, sql.ConnectOptions{Database: "master"})
+
+	useDatabase := ingest.NewIngest(c.url, controller, ingest.IngestOptions{
+		Mechanism: c.useMechanism,
+	})
+
+	if !useDatabase.SourceFileExists() {
+		output.FatalDatabaseSourceFileNotExist(c.url)
+	}
+
+	// Copy source file (e.g. .bak/.bacpac etc.) for database to be made available to container
+	useDatabase.CopyToContainer(id)
+
+	if useDatabase.IsExtractionNeeded() {
+		output.Infof("Extracting files from %q", useDatabase.UrlFilename())
+		useDatabase.Extract()
+	}
+
+	output.output.Infof("Bringing %q online using %q method",
+		useDatabase.DatabaseName(),
+		useDatabase.OnlineMethod(),
+	)
+
+	useDatabase.BringOnline(
+		c.sql.Query,
+		user.BasicAuth.Username,
+		secret.Decode(user.BasicAuth.Password, user.BasicAuth.PasswordEncryption),
+	)
+
+	output.InfoDatabaseOnline(useDatabase.DatabaseName())
+}
+
+func (c *Use) query(commandText string) {
+	c.sql.Query(commandText)
+}
diff --git a/cmd/modern/root/use_output.go b/cmd/modern/root/use_output.go
new file mode 100644
index 00000000..b29eb0bc
--- /dev/null
+++ b/cmd/modern/root/use_output.go
@@ -0,0 +1,44 @@
+package root
+
+import (
+	"fmt"
+	"github.com/microsoft/go-sqlcmd/internal/output"
+	"runtime"
+)
+
+type useOutput struct {
+	output.Output
+	output *output.Output
+}
+
+func (u *useOutput) FatalNoContainerInCurrentContext() {
+	u.output.FatalfWithHintExamples([][]string{
+		{"Create a context with a container", "sqlcmd create mssql"},
+	}, "Current context does not have a container")
+}
+
+func (u *useOutput) FatalContainerNotRunning() {
+	u.output.FatalfWithHintExamples([][]string{
+		{"Start container for current context", "sqlcmd start"},
+	}, "Container for current context is not running")
+}
+
+func (u *useOutput) FatalDatabaseSourceFileNotExist(url string) {
+	u.output.FatalfWithHints(
+		[]string{fmt.Sprintf("File does not exist at URL %q", url)},
+		"Unable to download file to container")
+}
+
+func (u *useOutput) InfoDatabaseOnline(databaseName string) {
+	hints := [][]string{}
+
+	// TODO: sqlcmd open ads only support on Windows/Mac right now, add Linux support
+	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
+		hints = append(hints, []string{"Open in Azure Data Studio", "sqlcmd open ads"})
+	}
+
+	hints = append(hints, []string{"Run a query", "sqlcmd query \"SELECT DB_NAME()\""})
+	hints = append(hints, []string{"See connection strings", "sqlcmd config connection-strings"})
+
+	u.output.InfofWithHintExamples(hints, "Database %q is now online", databaseName)
+}
diff --git a/cmd/modern/sqlcmdconfig/sqlcmdconfig.go b/cmd/modern/sqlcmdconfig/sqlcmdconfig.go
new file mode 100644
index 00000000..5c59cd60
--- /dev/null
+++ b/cmd/modern/sqlcmdconfig/sqlcmdconfig.go
@@ -0,0 +1,30 @@
+package sqlcmdconfig
+
+type Sqlcmdconfig struct {
+	Version   string     `mapstructure:"version"`
+	Databases []Database `mapstructure:"databases"`
+	AddOns    []AddOn    `mapstructure:"addons"`
+}
+
+type Database struct {
+	DatabaseDetails `mapstructure:"database" yaml:"database"`
+}
+
+type DatabaseDetails struct {
+	Name string `mapstructure:"name" yaml:"name"`
+	Use  []Use  `mapstructure:"use"`
+}
+
+type Use struct {
+	Uri       string `mapstructure:"uri"`
+	Mechanism string `mapstructure:"mechanism"`
+}
+
+type AddOn struct {
+	AddOnDetails `mapstructure:"addon" yaml:"addon"`
+}
+
+type AddOnDetails struct {
+	Type string `mapstructure:"type"`
+	Use  []Use  `mapstructure:"use"`
+}
diff --git a/cmd/modern/sqlconfig/sqlconfig.go b/cmd/modern/sqlconfig/sqlconfig.go
index 834947b9..5edd6d55 100644
--- a/cmd/modern/sqlconfig/sqlconfig.go
+++ b/cmd/modern/sqlconfig/sqlconfig.go
@@ -23,6 +23,15 @@ type AssetDetails struct {
 	*ContainerDetails `mapstructure:"container,omitempty" yaml:"container,omitempty"`
 }
 
+type AddOn struct {
+	AddOnsDetails `mapstructure:"addon" yaml:"addon,omitempty"`
+}
+
+type AddOnsDetails struct {
+	Type     string `mapstructure:"type"`
+	Endpoint string `mapstructure:"endpoint"`
+}
+
 type Endpoint struct {
 	*AssetDetails   `mapstructure:"asset,omitempty" yaml:"asset,omitempty"`
 	EndpointDetails `mapstructure:"endpoint" yaml:"endpoint"`
@@ -32,6 +41,8 @@ type Endpoint struct {
 type ContextDetails struct {
 	Endpoint string  `mapstructure:"endpoint"`
 	User     *string `mapstructure:"user,omitempty" yaml:"user,omitempty"`
+	Network  *string `mapstructure:"network,omitempty" yaml:"network,omitempty"`
+	AddOns   []AddOn `mapstructure:"addons,omitempty" yaml:"addons,omitempty"`
 }
 
 type Context struct {
diff --git a/go.mod b/go.mod
index 48c347e6..91d4529d 100644
--- a/go.mod
+++ b/go.mod
@@ -1,78 +1,106 @@
 module github.com/microsoft/go-sqlcmd
 
-go 1.18
+go 1.20
+
+// replace github.com/microsoft/go-mssqldb => C:\src\go-mssqldb
+// replace github.com/microsoft/go-mssqldb => /Users/stuartpa/src/go-mssqldb
 
 require (
-	github.com/alecthomas/chroma/v2 v2.5.0
-	github.com/billgraziano/dpapi v0.4.0
-	github.com/docker/distribution v2.8.2+incompatible
+	github.com/alecthomas/chroma/v2 v2.12.0
+	github.com/billgraziano/dpapi v0.5.0
+	github.com/docker/distribution v2.8.3+incompatible
 	github.com/docker/docker v24.0.7+incompatible
 	github.com/docker/go-connections v0.4.0
 	github.com/golang-sql/sqlexp v0.1.0
-	github.com/google/uuid v1.3.0
+	github.com/google/uuid v1.6.0
+	github.com/joho/godotenv v1.5.1
 	github.com/microsoft/go-mssqldb v1.6.0
 	github.com/opencontainers/image-spec v1.0.2
 	github.com/peterh/liner v1.2.2
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/cobra v1.6.1
 	github.com/spf13/pflag v1.0.5
-	github.com/spf13/viper v1.14.0
+	github.com/spf13/viper v1.18.2
 	github.com/stretchr/testify v1.8.4
-	golang.org/x/sys v0.15.0
+	golang.org/x/sys v0.17.0
 	golang.org/x/text v0.14.0
-	golang.org/x/tools v0.6.0
+	golang.org/x/tools v0.18.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
 require (
-	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 // indirect
-	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect
-	github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
-	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0 // indirect
-	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 // indirect
-	github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0 // indirect
-	github.com/Microsoft/go-winio v0.6.0 // indirect
+	github.com/rdegges/go-ipify v0.0.0-20150526035502-2d94a6a86c40
+	gopkg.in/src-d/go-billy.v4 v4.3.2
+	gopkg.in/src-d/go-git.v4 v4.13.1
+)
+
+require (
+	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 // indirect
+	github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
+	github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect
+	github.com/Microsoft/go-winio v0.6.1 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
-	github.com/cespare/xxhash/v2 v2.1.1 // indirect
-	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/dlclark/regexp2 v1.4.0 // indirect
+	github.com/cespare/xxhash/v2 v2.2.0 // indirect
+	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+	github.com/distribution/reference v0.5.0 // indirect
+	github.com/dlclark/regexp2 v1.10.0 // indirect
 	github.com/docker/go-metrics v0.0.1 // indirect
 	github.com/docker/go-units v0.5.0 // indirect
 	github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
-	github.com/fsnotify/fsnotify v1.6.0 // indirect
+	github.com/emirpasic/gods v1.18.1 // indirect
+	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
-	github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
+	github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
 	github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
-	github.com/golang/protobuf v1.5.2 // indirect
-	github.com/gorilla/mux v1.8.0 // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
+	github.com/gorilla/mux v1.8.1 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
-	github.com/inconshreveable/mousetrap v1.0.1 // indirect
+	github.com/inconshreveable/mousetrap v1.1.0 // indirect
+	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
+	github.com/jpillora/backoff v1.0.0 // indirect
+	github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect
 	github.com/kylelemons/godebug v1.1.0 // indirect
-	github.com/magiconair/properties v1.8.6 // indirect
-	github.com/mattn/go-runewidth v0.0.3 // indirect
+	github.com/magiconair/properties v1.8.7 // indirect
+	github.com/mattn/go-runewidth v0.0.15 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
+	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
-	github.com/moby/term v0.0.0-20221120202655-abb19827d345 // indirect
+	github.com/moby/term v0.5.0 // indirect
 	github.com/morikuni/aec v1.0.0 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/pelletier/go-toml v1.9.5 // indirect
-	github.com/pelletier/go-toml/v2 v2.0.5 // indirect
-	github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
-	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/prometheus/client_golang v1.11.1 // indirect
-	github.com/prometheus/client_model v0.2.0 // indirect
-	github.com/prometheus/common v0.26.0 // indirect
-	github.com/prometheus/procfs v0.6.0 // indirect
-	github.com/sirupsen/logrus v1.9.0 // indirect
-	github.com/spf13/afero v1.9.2 // indirect
-	github.com/spf13/cast v1.5.0 // indirect
+	github.com/pelletier/go-toml/v2 v2.1.1 // indirect
+	github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
+	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+	github.com/prometheus/client_golang v1.18.0 // indirect
+	github.com/prometheus/client_model v0.5.0 // indirect
+	github.com/prometheus/common v0.46.0 // indirect
+	github.com/prometheus/procfs v0.12.0 // indirect
+	github.com/rivo/uniseg v0.4.7 // indirect
+	github.com/sagikazarmark/locafero v0.4.0 // indirect
+	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
+	github.com/sergi/go-diff v1.3.1 // indirect
+	github.com/sirupsen/logrus v1.9.3 // indirect
+	github.com/sourcegraph/conc v0.3.0 // indirect
+	github.com/spf13/afero v1.11.0 // indirect
+	github.com/spf13/cast v1.6.0 // indirect
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect
-	github.com/subosito/gotenv v1.4.1 // indirect
-	golang.org/x/crypto v0.17.0 // indirect
-	golang.org/x/mod v0.8.0 // indirect
-	golang.org/x/net v0.17.0 // indirect
-	google.golang.org/protobuf v1.28.1 // indirect
+	github.com/src-d/gcfg v1.4.0 // indirect
+	github.com/subosito/gotenv v1.6.0 // indirect
+	github.com/xanzy/ssh-agent v0.3.3 // indirect
+	go.uber.org/atomic v1.11.0 // indirect
+	go.uber.org/multierr v1.11.0 // indirect
+	golang.org/x/crypto v0.19.0 // indirect
+	golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
+	golang.org/x/mod v0.15.0 // indirect
+	golang.org/x/net v0.21.0 // indirect
+	google.golang.org/protobuf v1.32.0 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
+	gopkg.in/warnings.v0 v0.1.2 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	gotest.tools/v3 v3.4.0 // indirect
+	gotest.tools/v3 v3.5.1 // indirect
 )
diff --git a/go.sum b/go.sum
index f272a5c4..eb7ac693 100644
--- a/go.sum
+++ b/go.sum
@@ -36,41 +36,67 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 h1:/iHxaJhsFr0+xVFfbMr5vxz848jyiWuIEDhYq3y5odY=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 h1:c4k2FIYIh4xtwqrQwV0Ct1v5+ehlNXj5NI/MWVsiTkQ=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2/go.mod h1:5FDJtLEO/GxwNgUxbwrY3LP0pEoThTQJtk2oysdXHxM=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc=
 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0 h1:yfJe15aSwEQ6Oo6J+gdfdulPNoZ3TEhmbhLIoxZcA+U=
 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:Q28U+75mpCaSCDowNEmhIo/rmgdkqmkmzI7N6TGR4UY=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0 h1:DRiANoJTiW6obBQe3SqZizkuV1PEgfiiGivmVocDy64=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0/go.mod h1:qLIye2hwb/ZouqhpSD9Zn3SJipvpEnz1Ywl3VUk9Y0s=
 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 h1:T028gtTPiYt/RMUfs8nVsAL7FDQrfLlrm/NnRG/zcC4=
 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0 h1:HCc0+LpPfpCKs6LGGLAhwBARt9632unrVcI6i8s/8os=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
+github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
 github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
 github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
+github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
+github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
+github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
+github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
 github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
 github.com/alecthomas/chroma/v2 v2.5.0 h1:CQCdj1BiBV17sD4Bd32b/Bzuiq/EqoNTrnIhyQAZ+Rk=
 github.com/alecthomas/chroma/v2 v2.5.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
+github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw=
+github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
 github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/billgraziano/dpapi v0.4.0 h1:t39THI1Ld1hkkLVrhkOX6u5TUxwzRddOffq4jcwh2AE=
 github.com/billgraziano/dpapi v0.4.0/go.mod h1:gi1Lin0jvovT53j0EXITkY6UPb3hTfI92POaZgj9JBA=
+github.com/billgraziano/dpapi v0.5.0 h1:pcxA17vyjbDqYuxCFZbgL9tYIk2xgbRZjRaIbATwh+8=
+github.com/billgraziano/dpapi v0.5.0/go.mod h1:lmEcZjRfLCSbUTsRu8V2ti6Q17MvnKn3N9gQqzDdTh0=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -79,14 +105,23 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
+github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
 github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
 github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
+github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
 github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
 github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
+github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
 github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
@@ -98,15 +133,26 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
 github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
 github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
 github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
+github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -120,9 +166,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
-github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
-github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
+github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
 github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@@ -155,6 +200,7 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -184,13 +230,17 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
 github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
-github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
+github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
+github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@@ -200,6 +250,16 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
 github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
+github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -209,6 +269,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
+github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -217,23 +279,33 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
 github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
 github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
 github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/microsoft/go-mssqldb v1.6.0 h1:mM3gYdVwEPFrlg/Dvr2DNVEgYFG7L42l+dGc67NNNpc=
 github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/moby/term v0.0.0-20221120202655-abb19827d345 h1:J9c53/kxIH+2nTKBEfZYFMlhghtHpIHSXpm5VRGHSnU=
 github.com/moby/term v0.0.0-20221120202655-abb19827d345/go.mod h1:15ce4BGCFxt7I5NQKT+HV0yEDxmf6fSysfEDiVo3zFM=
+github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
+github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -246,14 +318,19 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
 github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
 github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
 github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
 github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
+github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
+github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
+github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
 github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw=
 github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI=
-github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
-github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -261,40 +338,75 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s=
 github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
+github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
+github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y=
+github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
+github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
+github.com/rdegges/go-ipify v0.0.0-20150526035502-2d94a6a86c40 h1:31Y7UZ1yTYBU4E79CE52I/1IRi3TqiuwquXGNtZDXWs=
+github.com/rdegges/go-ipify v0.0.0-20150526035502-2d94a6a86c40/go.mod h1:j4c6zEU0eMG1oiZPUy+zD4ykX0NIpjZAEOEAviTWC18=
+github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
+github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
 github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
 github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
 github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
 github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
 github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
 github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
 github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
@@ -303,9 +415,15 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU=
 github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
+github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
+github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
+github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
+github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -317,6 +435,12 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
 github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
+github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
+github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
+github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -327,16 +451,31 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
+go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
+go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
+go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
+golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
+golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -347,6 +486,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -372,6 +513,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
+golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -405,8 +548,13 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
+golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
+golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
+golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -427,10 +575,12 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -441,6 +591,7 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -468,28 +619,40 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
+golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
+golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -502,6 +665,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -541,6 +705,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
+golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -637,14 +803,27 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
+google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
+gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
+gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg=
+gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
+gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
+gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -657,6 +836,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
 gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
+gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
+gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/cmdparser/cmd.go b/internal/cmdparser/cmd.go
index f6dcd50b..1c6f64f4 100644
--- a/internal/cmdparser/cmd.go
+++ b/internal/cmdparser/cmd.go
@@ -85,6 +85,30 @@ func (c *Cmd) AddFlag(options FlagOptions) {
 				options.Usage)
 		}
 	}
+
+	if options.StringArray != nil {
+		if options.Bool != nil || options.Int != nil || options.String != nil {
+			panic("Only provide one type")
+		}
+		if options.Shorthand == "" {
+			c.command.PersistentFlags().StringArrayVar(
+				options.StringArray,
+				options.Name,
+				[]string{},
+				options.Usage)
+		} else {
+			c.command.PersistentFlags().StringArrayVarP(
+				options.StringArray,
+				options.Name,
+				options.Shorthand,
+				[]string{},
+				options.Usage)
+		}
+	}
+
+	if options.Hidden {
+		c.command.PersistentFlags().MarkHidden(options.Name)
+	}
 }
 
 // DefineCommand defines a command with the provided CommandOptions and adds
diff --git a/internal/cmdparser/options.go b/internal/cmdparser/options.go
index 499c8622..9231b6c7 100644
--- a/internal/cmdparser/options.go
+++ b/internal/cmdparser/options.go
@@ -28,6 +28,8 @@ type FlagOptions struct {
 	Shorthand string
 	Usage     string
 
+	Hidden bool
+
 	String        *string
 	DefaultString string
 
@@ -36,6 +38,8 @@ type FlagOptions struct {
 
 	Bool        *bool
 	DefaultBool bool
+
+	StringArray *[]string
 }
 
 // CommandOptions is a struct that allows the caller to specify options for a Command.
diff --git a/internal/config/config.go b/internal/config/config.go
index 1b24695c..e584780a 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -92,71 +92,74 @@ func IsEmpty() (isEmpty bool) {
 // requested. The updated configuration is saved to file.
 func AddContextWithContainer(
 	contextName string,
-	imageName string,
-	portNumber int,
-	containerId string,
-	username string,
-	password string,
-	passwordEncryption string,
+	options ContextOptions,
 ) {
-	if containerId == "" {
+	if options.ContainerId == "" {
 		panic("containerId must be provided")
 	}
-	if imageName == "" {
+	if options.ImageName == "" {
 		panic("imageName must be provided")
 	}
-	if portNumber == 0 {
+	if options.PortNumber == 0 {
 		panic("portNumber must be non-zero")
 	}
-	if username == "" {
+	if options.Username == "" {
 		panic("username must be provided")
 	}
-	if password == "" {
+	if options.Password == "" {
 		panic("password must be provided")
 	}
 	if contextName == "" {
 		panic("contextName must be provided")
 	}
 
-	contextName = FindUniqueContextName(contextName, username)
+	contextName = FindUniqueContextName(contextName, options.Username)
 	endPointName := FindUniqueEndpointName(contextName)
-	userName := username + "@" + contextName
+	userName := options.Username + "@" + contextName
 
 	config.CurrentContext = contextName
 
 	config.Endpoints = append(config.Endpoints, Endpoint{
 		AssetDetails: &AssetDetails{
 			ContainerDetails: &ContainerDetails{
-				Id:    containerId,
-				Image: imageName},
+				Id:    options.ContainerId,
+				Image: options.ImageName},
 		},
 		EndpointDetails: EndpointDetails{
 			Address: "127.0.0.1",
-			Port:    portNumber,
+			Port:    options.PortNumber,
 		},
 		Name: endPointName,
 	})
 
+	contextDetails := ContextDetails{
+		Endpoint: endPointName,
+	}
+
+	if userName != "" {
+		contextDetails.User = &userName
+	}
+
+	if options.Network != "" {
+		contextDetails.Network = &options.Network
+	}
+
 	config.Contexts = append(config.Contexts, Context{
-		ContextDetails: ContextDetails{
-			Endpoint: endPointName,
-			User:     &userName,
-		},
-		Name: contextName,
+		ContextDetails: contextDetails,
+		Name:           contextName,
 	})
 
 	user := User{
 		AuthenticationType: "basic",
 		BasicAuth: &BasicAuthDetails{
-			Username:           username,
-			PasswordEncryption: passwordEncryption,
-			Password:           encryptCallback(password, passwordEncryption),
+			Username:           options.Username,
+			PasswordEncryption: options.PasswordEncryption,
+			Password:           encryptCallback(options.Password, options.PasswordEncryption),
 		},
 		Name: userName,
 	}
 
 	config.Users = append(config.Users, user)
-
 	Save()
 }
 
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 7e2540c2..9852954b 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -83,7 +83,7 @@ func TestConfig(t *testing.T) {
 			GetEndpoint("endpoint")
 			OutputEndpoints(o.Struct, true)
 			OutputEndpoints(o.Struct, false)
-			FindFreePortForTds()
+			FindFreePort(0)
 			DeleteEndpoint("endpoint2")
 			DeleteEndpoint("endpoint3")
 
@@ -131,7 +131,20 @@ func TestConfig(t *testing.T) {
 			ContainerId()
 			RemoveCurrentContext()
 			RemoveCurrentContext()
-			AddContextWithContainer("context", "imageName", 1433, "containerId", "user", "password", "none")
+<<<<<<< HEAD
+
+			options := ContextOptions{
+				ImageName:          "imageName",
+				PortNumber:         1433,
+				ContainerId:        "containerId",
+				Username:           "user",
+				Password:           "password",
+				PasswordEncryption: "none",
+			}
+			AddContextWithContainer("context", options)
+=======
+			AddContextWithContainer("imageName", "context", 1433, "containerId", "user", "password", "none", "")
+>>>>>>> stuartpa/add-ons
 			RemoveCurrentContext()
 			DeleteEndpoint("endpoint")
 			DeleteContext("context")
@@ -324,7 +337,19 @@ func TestAddContextWithContainerPanic(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			assert.Panics(t, func() {
-				AddContextWithContainer(tt.args.contextName, tt.args.imageName, tt.args.portNumber, tt.args.containerId, tt.args.username, tt.args.password, tt.args.passwordEncryption)
+<<<<<<< HEAD
+				options := ContextOptions{
+					ImageName:          tt.args.imageName,
+					PortNumber:         tt.args.portNumber,
+					ContainerId:        tt.args.containerId,
+					Username:           tt.args.username,
+					Password:           tt.args.password,
+					PasswordEncryption: tt.args.passwordEncryption,
+				}
+				AddContextWithContainer(tt.args.contextName, options)
+=======
+				AddContextWithContainer(tt.args.imageName, tt.args.contextName, tt.args.portNumber, tt.args.containerId, tt.args.username, tt.args.password, tt.args.passwordEncryption, "")
+>>>>>>> stuartpa/add-ons
 			})
 		})
 	}
diff --git a/internal/config/context.go b/internal/config/context.go
index 64682e5a..84a9c82f 100644
--- a/internal/config/context.go
+++ b/internal/config/context.go
@@ -39,6 +39,44 @@ func AddContext(context Context) string {
 	return context.Name
 }
 
+func AddAddOn(
+	contextName, addOnName string,
+	ContainerId string,
+	Image string,
+	address string,
+	port int,
+) {
+
+	containerDetails := ContainerDetails{
+		Id:    ContainerId,
+		Image: Image}
+
+	assetDetails := AssetDetails{
+		ContainerDetails: &containerDetails}
+
+	endpoint := Endpoint{
+		AssetDetails: &assetDetails,
+		EndpointDetails: EndpointDetails{
+			Address: address,
+			Port:    port},
+		Name: addOnName + "@" + contextName,
+	}
+
+	uniqueEndpointName := AddEndpoint(endpoint)
+
+	for i, c := range config.Contexts {
+		if contextName == c.Name {
+			config.Contexts[i].AddOns = append(c.AddOns, AddOn{
+				AddOnsDetails: AddOnsDetails{
+					Type:     addOnName,
+					Endpoint: uniqueEndpointName}})
+			break
+		}
+	}
+
+	Save()
+}
+
 // CurrentContextName returns the name of the current context in the configuration.
 // The current context is the one that is currently active and used by the application.
 func CurrentContextName() string {
@@ -96,6 +134,32 @@ func CurrentContext() (endpoint Endpoint, user *User) {
 	return
 }
 
+func CurrentContextAddOns() (addOns []AddOn) {
+	currentContextName := GetCurrentContextOrFatal()
+
+	for _, c := range config.Contexts {
+		if c.Name == currentContextName {
+			addOns = c.AddOns
+			break
+		}
+	}
+
+	return
+}
+
+func CurrentContextNetwork() (network *string) {
+	currentContextName := GetCurrentContextOrFatal()
+
+	for _, c := range config.Contexts {
+		if c.Name == currentContextName {
+			network = c.Network
+			break
+		}
+	}
+
+	return
+}
+
 // GetCurrentContextInfo returns endpoint and basic auth info
 // associated with current context
 func GetCurrentContextInfo() (server string, username string, password string) {
diff --git a/internal/config/endpoint-container.go b/internal/config/endpoint-container.go
index dfc87f19..ff82422a 100644
--- a/internal/config/endpoint-container.go
+++ b/internal/config/endpoint-container.go
@@ -43,7 +43,7 @@ func CurrentContextEndpointHasContainer() (exists bool) {
 	currentContextName := config.CurrentContext
 
 	if currentContextName == "" {
-		panic("currentContextName must not be empty")
+		return false
 	}
 
 	for _, c := range config.Contexts {
@@ -63,14 +63,11 @@ func CurrentContextEndpointHasContainer() (exists bool) {
 	return
 }
 
-// FindFreePortForTds is used to find a free port number to use for the TDS
-// protocol. It starts at port number 1433 and continues until it finds a port
+// FindFreePort is used to find a free port number to use for the TDS
+// protocol. It starts at port number startingPortNumber and continues until it finds a port
 // number that is not currently in use by any of the endpoints in the
 // configuration. It also checks that the port is available on the local machine.
-// If no available port is found after trying up to port number 5000, the function panics.
-func FindFreePortForTds() (portNumber int) {
-	const startingPortNumber = 1433
-
+func FindFreePort(startingPortNumber int) (portNumber int) {
 	portNumber = startingPortNumber
 
 	for {
@@ -91,7 +88,7 @@ func FindFreePortForTds() (portNumber int) {
 
 		portNumber++
 
-		if portNumber == 5000 {
+		if portNumber == startingPortNumber+2000 {
 			panic("Did not find an available port")
 		}
 	}
diff --git a/internal/config/endpoint-container_test.go b/internal/config/endpoint-container_test.go
index d7f2a02e..8a77a27b 100644
--- a/internal/config/endpoint-container_test.go
+++ b/internal/config/endpoint-container_test.go
@@ -11,14 +11,14 @@ import (
 	"testing"
 )
 
-// TestCurrentContextEndpointHasContainer verifies the function panics when
+// TestCurrentContextEndpointHasContainer verifies the function returns false when
 // no current context
 func TestCurrentContextEndpointHasContainer(t *testing.T) {
 	SetFileName(pal.FilenameInUserHomeDotDirectory(
 		".sqlcmd", "sqlconfig-TestCurrentContextEndpointHasContainer"))
 	Clean()
 
-	assert.Panics(t, func() { CurrentContextEndpointHasContainer() })
+	assert.False(t, CurrentContextEndpointHasContainer())
 }
 
 func TestGetContainerId(t *testing.T) {
diff --git a/internal/config/types.go b/internal/config/types.go
new file mode 100644
index 00000000..34c6f236
--- /dev/null
+++ b/internal/config/types.go
@@ -0,0 +1,11 @@
+package config
+
+type ContextOptions struct {
+	ImageName          string
+	PortNumber         int
+	ContainerId        string
+	Username           string
+	Password           string
+	PasswordEncryption string
+	Network            string
+}
diff --git a/internal/container/controller.go b/internal/container/controller.go
index 45eead59..23a7488f 100644
--- a/internal/container/controller.go
+++ b/internal/container/controller.go
@@ -4,6 +4,7 @@
 package container
 
 import (
+	"archive/tar"
 	"bufio"
 	"bytes"
 	"context"
@@ -19,6 +20,7 @@ import (
 	"github.com/docker/docker/pkg/stdcopy"
 	"github.com/docker/go-connections/nat"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
+	"os"
 )
 
 type Controller struct {
@@ -64,43 +66,65 @@ func (c Controller) EnsureImage(image string) (err error) {
 	return
 }
 
+func (c Controller) NetworkCreate(name string) string {
+	resp, err := c.cli.NetworkCreate(context.Background(), name, types.NetworkCreate{})
+	checkErr(err)
+
+	return resp.ID
+}
+
+func (c Controller) NetworkDelete(name string) {
+	err := c.cli.NetworkRemove(context.Background(), name)
+	checkErr(err)
+}
+
+func (c Controller) NetworkExists(name string) bool {
+	networks, err := c.cli.NetworkList(context.Background(), types.NetworkListOptions{})
+	checkErr(err)
+
+	for _, network := range networks {
+		if network.Name == name {
+			return true
+		}
+	}
+
+	return false
+}
+
 // ContainerRun creates a new container using the provided image and env values
-// and binds it to the specified port number. It then starts the container and returns
-// the ID of the container.
+// and binds the internal port to the specified external port number. It then starts
+// the container and returns the ID of the container.
 func (c Controller) ContainerRun(
 	image string,
-	env []string,
-	port int,
-	name string,
-	hostname string,
-	architecture string,
-	os string,
-	command []string,
-	unitTestFailure bool,
+	options RunOptions,
 ) string {
 	hostConfig := &container.HostConfig{
 		PortBindings: nat.PortMap{
-			nat.Port("1433/tcp"): []nat.PortBinding{
+			nat.Port(strconv.Itoa(options.PortInternal) + "/tcp"): []nat.PortBinding{
 				{
 					HostIP:   "0.0.0.0",
-					HostPort: strconv.Itoa(port),
+					HostPort: strconv.Itoa(options.Port),
 				},
 			},
 		},
+		NetworkMode: container.NetworkMode(options.Network),
 	}
 
 	platform := specs.Platform{
-		Architecture: architecture,
-		OS:           os,
+		Architecture: options.Architecture,
+		OS:           options.Os,
 	}
 
 	resp, err := c.cli.ContainerCreate(context.Background(), &container.Config{
 		Tty:      true,
 		Image:    image,
-		Cmd:      command,
-		Env:      env,
-		Hostname: hostname,
-	}, hostConfig, nil, &platform, name)
+		Cmd:      options.Command,
+		Env:      options.Env,
+		Hostname: options.Hostname,
+		ExposedPorts: nat.PortSet{
+			nat.Port(strconv.Itoa(options.PortInternal) + "/tcp"): {},
+		},
+	}, hostConfig, nil, &platform, options.Name)
 	checkErr(err)
 
 	err = c.cli.ContainerStart(
@@ -108,7 +132,7 @@ func (c Controller) ContainerRun(
 		resp.ID,
 		types.ContainerStartOptions{},
 	)
-	if err != nil || unitTestFailure {
+	if err != nil || options.UnitTestFailure {
 		// Remove the container, because we haven't persisted to config yet, so
 		// uninstall won't work yet
 		if resp.ID != "" {
@@ -121,6 +145,16 @@ func (c Controller) ContainerRun(
 	return resp.ID
 }
 
+func (c Controller) ContainerName(containerID string) string {
+	// Inspect the container to get details
+	containerInfo, err := c.cli.ContainerInspect(context.Background(), containerID)
+	checkErr(err)
+
+	// Access the container name from the inspect result
+	containerName := containerInfo.Name[1:] // Removing the leading '/'
+	return containerName
+}
+
 // ContainerWaitForLogEntry is used to wait for a specific string to be written
 // to the logs of a container with the given ID. The function takes in the ID
 // of the container and the string to look for in the logs. It creates a reader
@@ -232,6 +266,43 @@ func (c Controller) ContainerFiles(id string, filespec string) (files []string)
 	return strings.Split(string(stdout), "\n")
 }
 
+func (c Controller) CopyFile(id string, src string, destFolder string) {
+	if id == "" {
+		panic("Must pass in non-empty id")
+	}
+	if src == "" {
+		panic("Must pass in non-empty src")
+	}
+	if destFolder == "" {
+		panic("Must pass in non-empty destFolder")
+	}
+
+	trace("Copying file %s to %s", src, destFolder)
+
+	_, f := filepath.Split(src)
+	h, err := os.ReadFile(src)
+	checkErr(err)
+
+	// Create and add some files to the archive.
+	var buf bytes.Buffer
+	tw := tar.NewWriter(&buf)
+	defer func() {
+		checkErr(tw.Close())
+	}()
+	hdr := &tar.Header{
+		Name: f,
+		Mode: 0600,
+		Size: int64(len(h)),
+	}
+	err = tw.WriteHeader(hdr)
+	checkErr(err)
+	_, err = tw.Write([]byte(h))
+	checkErr(err)
+
+	err = c.cli.CopyToContainer(context.Background(), id, destFolder, &buf, types.CopyToContainerOptions{})
+	checkErr(err)
+}
+
 func (c Controller) DownloadFile(id string, src string, destFolder string) {
 	if id == "" {
 		panic("Must pass in non-empty id")
@@ -243,10 +314,12 @@ func (c Controller) DownloadFile(id string, src string, destFolder string) {
 		panic("Must pass in non-empty destFolder")
 	}
 
-	cmd := []string{"mkdir", destFolder}
-	c.runCmdInContainer(id, cmd)
+	trace("Downloading file %s to %s (will try wget first, and curl if wget fails", src, destFolder)
+
+	cmd := []string{"mkdir", "-p", destFolder}
+	c.RunCmdInContainer(id, cmd, ExecOptions{})
 
-	_, file := filepath.Split(src)
+	_, file := filepath.Split(strings.Split(src, "?")[0])
 
 	// Wget the .bak file from the http src, and place it in /var/opt/sql/backup
 	cmd = []string{
@@ -256,19 +329,41 @@ func (c Controller) DownloadFile(id string, src string, destFolder string) {
 		src,
 	}
 
-	c.runCmdInContainer(id, cmd)
+	_, _, exitCode := c.RunCmdInContainer(id, cmd, ExecOptions{})
+	trace("wget exit code: %d", exitCode)
+
+	if exitCode == 126 {
+		trace("wget was not found in container, trying curl")
+		cmd = []string{
+			"curl",
+			"-o",
+			destFolder + "/" + file, // not using filepath.Join here, this is in the *nix container. always /
+			"-L",
+			src,
+		}
+
+		_, _, exitCode = c.RunCmdInContainer(id, cmd, ExecOptions{})
+		trace("curl exit code: %d", exitCode)
+	}
+}
+
+type ExecOptions struct {
+	User string
+	Env  []string
 }
 
-func (c Controller) runCmdInContainer(id string, cmd []string) ([]byte, []byte) {
-	trace("Running command in container: " + strings.Join(cmd, " "))
+func (c Controller) RunCmdInContainer(id string, cmd []string, options ExecOptions) ([]byte, []byte, int) {
+	trace("Running command in container: " + strings.Replace(strings.Join(cmd, " "), "%", "%%", -1))
 
 	response, err := c.cli.ContainerExecCreate(
 		context.Background(),
 		id,
 		types.ExecConfig{
+			User:         options.User,
 			AttachStderr: true,
 			AttachStdout: true,
 			Cmd:          cmd,
+			Env:          options.Env,
 		},
 	)
 	checkErr(err)
@@ -298,10 +393,16 @@ func (c Controller) runCmdInContainer(id string, cmd []string) ([]byte, []byte)
 	stderr, err := io.ReadAll(&errBuf)
 	checkErr(err)
 
-	trace("Stdout: " + string(stdout))
-	trace("Stderr: " + string(stderr))
+	trace("Stdout: " + strings.Replace(string(stdout), "%", "%%%%", -1))
+	trace("Stderr: " + strings.Replace(string(stderr), "%", "%%%%", -1))
+
+	// Get the exit code
+	execInspect, err := c.cli.ContainerExecInspect(context.Background(), response.ID)
+	checkErr(err)
 
-	return stdout, stderr
+	trace("ExitCode: %d", execInspect.ExitCode)
+
+	return stdout, stderr, execInspect.ExitCode
 }
 
 // ContainerRunning returns true if the container with the given ID is running.
@@ -323,13 +424,15 @@ func (c Controller) ContainerRunning(id string) (running bool) {
 // filtering by the given ID. If a container with the given ID is found, it
 // returns true; otherwise, it returns false.
 func (c Controller) ContainerExists(id string) (exists bool) {
+	trace("ContainerExists: " + id)
+
 	f := filters.NewArgs()
 	f.Add(
 		"id", id,
 	)
 	resp, err := c.cli.ContainerList(
 		context.Background(),
-		types.ContainerListOptions{Filters: f},
+		types.ContainerListOptions{Filters: f, All: true},
 	)
 	checkErr(err)
 	if len(resp) > 0 {
@@ -340,6 +443,8 @@ func (c Controller) ContainerExists(id string) (exists bool) {
 		exists = true
 	}
 
+	trace("ContainerExists: %v", exists)
+
 	return
 }
 
@@ -362,3 +467,219 @@ func (c Controller) ContainerRemove(id string) (err error) {
 
 	return
 }
+
+// DAB project file
+/*
+dabCsproj := `<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+  </PropertyGroup>`
+
+dabDockerfile := `FROM mcr.microsoft.com/azure-databases/data-api-builder:latest
+
+COPY dab-config.json /App
+WORKDIR /App
+ENV ASPNETCORE_URLS=http://+:5000
+EXPOSE 5000
+ENTRYPOINT ["dotnet", "Azure.DataApiBuilder.Service.dll"]
+`
+
+// C:\Users\stuartpa\classroom-assignment\infra\core\database\sqlserver\sqlserver.bicep
+sqlServerBicep := `metadata description = 'Creates an Azure SQL Server instance.'
+param name string
+param location string = resourceGroup().location
+param tags object = {}
+
+param appUser string = 'appUser'
+param databaseName string
+param keyVaultName string
+param sqlAdmin string = 'sqlAdmin'
+param connectionStringKey string = 'AZURE-SQL-CONNECTION-STRING'
+
+@secure()
+param sqlAdminPassword string
+@secure()
+param appUserPassword string
+
+resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = {
+  name: name
+  location: location
+  tags: tags
+  properties: {
+    version: '12.0'
+    minimalTlsVersion: '1.2'
+    publicNetworkAccess: 'Enabled'
+    administratorLogin: sqlAdmin
+    administratorLoginPassword: sqlAdminPassword
+  }
+
+  resource database 'databases' = {
+    name: databaseName
+    location: location
+  }
+
+  resource firewall 'firewallRules' = {
+    name: 'Azure Services'
+    properties: {
+      // Allow all clients
+      // Note: range [0.0.0.0-0.0.0.0] means "allow all Azure-hosted clients only".
+      // This is not sufficient, because we also want to allow direct access from developer machine, for debugging purposes.
+      startIpAddress: '0.0.0.1'
+      endIpAddress: '255.255.255.254'
+    }
+  }
+}
+
+resource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
+  name: '${name}-deployment-script'
+  location: location
+  kind: 'AzureCLI'
+  properties: {
+    azCliVersion: '2.37.0'
+    retentionInterval: 'PT1H' // Retain the script resource for 1 hour after it ends running
+    timeout: 'PT5M' // Five minutes
+    cleanupPreference: 'OnSuccess'
+    environmentVariables: [
+      {
+        name: 'APPUSERNAME'
+        value: appUser
+      }
+      {
+        name: 'APPUSERPASSWORD'
+        secureValue: appUserPassword
+      }
+      {
+        name: 'DBNAME'
+        value: databaseName
+      }
+      {
+        name: 'DBSERVER'
+        value: sqlServer.properties.fullyQualifiedDomainName
+      }
+      {
+        name: 'SQLCMDPASSWORD'
+        secureValue: sqlAdminPassword
+      }
+      {
+        name: 'SQLADMIN'
+        value: sqlAdmin
+      }
+    ]
+
+    scriptContent: '''
+wget https://github.com/microsoft/go-sqlcmd/releases/download/v0.8.1/sqlcmd-v0.8.1-linux-x64.tar.bz2
+tar x -f sqlcmd-v0.8.1-linux-x64.tar.bz2 -C .
+
+cat <<SCRIPT_END > ./initDb.sql
+drop user if exists ${APPUSERNAME}
+go
+create user ${APPUSERNAME} with password = '${APPUSERPASSWORD}'
+go
+alter role db_owner add member ${APPUSERNAME}
+go
+SCRIPT_END
+
+./sqlcmd -S ${DBSERVER} -d ${DBNAME} -U ${SQLADMIN} -i ./initDb.sql
+    '''
+  }
+}
+
+resource sqlAdminPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {
+  parent: keyVault
+  name: 'sqlAdminPassword'
+  properties: {
+    value: sqlAdminPassword
+  }
+}
+
+resource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {
+  parent: keyVault
+  name: 'appUserPassword'
+  properties: {
+    value: appUserPassword
+  }
+}
+
+resource sqlAzureConnectionStringSercret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {
+  parent: keyVault
+  name: connectionStringKey
+  properties: {
+    value: '${connectionString}; Password=${appUserPassword}'
+  }
+}
+
+resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
+  name: keyVaultName
+}
+
+var connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}'
+output connectionStringKey string = connectionStringKey
+output databaseName string = sqlServer::database.name
+`
+
+mainBicepDatabase := `
+param sqlDatabaseName string = ''
+param sqlServerName string = ''
+
+@secure()
+@description('SQL Server administrator password')
+param sqlAdminPassword string
+
+@secure()
+@description('Application user password')
+param appUserPassword string
+
+// The application database
+module sqlServer './app/db.bicep' = {
+  name: 'sql'
+  scope: rg
+  params: {
+    name: !empty(sqlServerName) ? sqlServerName : '${abbrs.sqlServers}${resourceToken}'
+    databaseName: sqlDatabaseName
+    location: location
+    tags: tags
+    sqlAdminPassword: sqlAdminPassword
+    appUserPassword: appUserPassword
+    keyVaultName: keyVault.outputs.name
+  }
+}
+
+output AZURE_SQL_CONNECTION_STRING_KEY string = sqlServer.outputs.connectionStringKey
+`
+
+// C:\Users\stuartpa\classroom-assignment\infra\app\db.bicep
+dbBicep := `param name string
+param location string = resourceGroup().location
+param tags object = {}
+
+param databaseName string = ''
+param keyVaultName string
+
+@secure()
+param sqlAdminPassword string
+@secure()
+param appUserPassword string
+
+// Because databaseName is optional in main.bicep, we make sure the database name is set here.
+var defaultDatabaseName = 'sample'
+var actualDatabaseName = !empty(databaseName) ? databaseName : defaultDatabaseName
+
+module sqlServer '../core/database/sqlserver/sqlserver.bicep' = {
+  name: 'sqlserver'
+  params: {
+    name: name
+    location: location
+    tags: tags
+    databaseName: actualDatabaseName
+    keyVaultName: keyVaultName
+    sqlAdminPassword: sqlAdminPassword
+    appUserPassword: appUserPassword
+  }
+}
+
+output connectionStringKey string = sqlServer.outputs.connectionStringKey
+output databaseName string = sqlServer.outputs.databaseName
+`
+
+*/
diff --git a/internal/container/controller_test.go b/internal/container/controller_test.go
index 347d7aec..d69b43fc 100644
--- a/internal/container/controller_test.go
+++ b/internal/container/controller_test.go
@@ -33,17 +33,20 @@ func TestController_EnsureImage(t *testing.T) {
 	c := NewController()
 	err := c.EnsureImage(imageName)
 	checkErr(err)
-	id := c.ContainerRun(
-		imageName,
-		[]string{},
-		port,
-		"",
-		"",
-		"amd64",
-		"linux",
-		[]string{"ash", "-c", "echo 'Hello World'; sleep 3"},
-		false,
-	)
+<<<<<<< HEAD
+
+	runOptions := RunOptions{
+		Env:          []string{},
+		Port:         port,
+		Architecture: "amd64",
+		Os:           "linux",
+		Command:      []string{"ash", "-c", "echo 'Hello World'; sleep 3"},
+	}
+
+	id := c.ContainerRun(imageName, runOptions)
+=======
+	id := c.ContainerRun(imageName, []string{}, nil, 1433, port, "", "", "amd64", "linux", "", []string{"ash", "-c", "echo 'Hello World'; sleep 3"}, false)
+>>>>>>> stuartpa/add-ons
 	c.ContainerRunning(id)
 	c.ContainerWaitForLogEntry(id, "Hello World")
 	c.ContainerExists(id)
@@ -79,19 +82,19 @@ func TestController_ContainerRunFailure(t *testing.T) {
 
 	c := NewController()
 
+<<<<<<< HEAD
+	runOptions := RunOptions{
+		Architecture: "amd64",
+		Os:           "linux",
+		Command:      []string{"ash", "-c", "echo 'Hello World'; sleep 1"},
+	}
+
+	assert.Panics(t, func() { c.ContainerRun(imageName, runOptions) })
+=======
 	assert.Panics(t, func() {
-		c.ContainerRun(
-			imageName,
-			[]string{},
-			0,
-			"",
-			"",
-			"amd64",
-			"linux",
-			[]string{"ash", "-c", "echo 'Hello World'; sleep 1"},
-			false,
-		)
+		c.ContainerRun(imageName, []string{}, nil, 0, 0, "", "", "amd64", "linux", "", []string{"ash", "-c", "echo 'Hello World'; sleep 1"}, false)
 	})
+>>>>>>> stuartpa/add-ons
 }
 
 func TestController_ContainerRunFailureCleanup(t *testing.T) {
@@ -107,19 +110,19 @@ func TestController_ContainerRunFailureCleanup(t *testing.T) {
 
 	c := NewController()
 
+<<<<<<< HEAD
+	runOptions := RunOptions{
+		Architecture:    "amd64",
+		Os:              "linux",
+		Command:         []string{"ash", "-c", "echo 'Hello World'; sleep 1"},
+		UnitTestFailure: true,
+	}
+	assert.Panics(t, func() { c.ContainerRun(imageName, runOptions) })
+=======
 	assert.Panics(t, func() {
-		c.ContainerRun(
-			imageName,
-			[]string{},
-			0,
-			"",
-			"",
-			"amd64",
-			"linux",
-			[]string{"ash", "-c", "echo 'Hello World'; sleep 1"},
-			true,
-		)
+		c.ContainerRun(imageName, []string{}, nil, 0, 0, "", "", "amd64", "linux", "", []string{"ash", "-c", "echo 'Hello World'; sleep 1"}, true)
 	})
+>>>>>>> stuartpa/add-ons
 }
 
 func TestController_ContainerStopNeg2(t *testing.T) {
diff --git a/internal/container/types.go b/internal/container/types.go
new file mode 100644
index 00000000..cbc4f689
--- /dev/null
+++ b/internal/container/types.go
@@ -0,0 +1,14 @@
+package container
+
+type RunOptions struct {
+	Network         string
+	Env             []string
+	PortInternal    int
+	Port            int
+	Name            string
+	Hostname        string
+	Architecture    string
+	Os              string
+	Command         []string
+	UnitTestFailure bool
+}
diff --git a/internal/databaseurl/error.go b/internal/databaseurl/error.go
new file mode 100644
index 00000000..039500da
--- /dev/null
+++ b/internal/databaseurl/error.go
@@ -0,0 +1,10 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package databaseurl
+
+var errorCallback func(err error)
+
+func checkErr(err error) {
+	errorCallback(err)
+}
diff --git a/internal/databaseurl/factory.go b/internal/databaseurl/factory.go
new file mode 100644
index 00000000..d607e269
--- /dev/null
+++ b/internal/databaseurl/factory.go
@@ -0,0 +1,70 @@
+package databaseurl
+
+import (
+	url2 "net/url"
+	"path/filepath"
+	"strings"
+)
+
+func NewDatabaseUrl(url string) *DatabaseUrl {
+	trace("NewDatabaseUrl(" + url + ")")
+
+	databaseUrl := DatabaseUrl{}
+
+	// To enable URL.Parse, switch to / from \\
+	url = strings.Replace(url, "\\", "/", -1)
+
+	// Cope with a URL that in the local directory, so it can be URL.Parsed()
+	if !strings.Contains(url, "/") {
+		url = "./" + url
+	}
+
+	// Cope with a file:// URL that in the local directory, so it can be URL.Parsed()
+	if strings.HasPrefix(strings.ToLower(url), "file://") &&
+		!strings.Contains(url[7:], "/") {
+		url = "file://./" + url[7:]
+	}
+
+	parsedUrl, err := url2.Parse(url)
+	checkErr(err)
+
+	databaseUrl.URL = parsedUrl
+
+	trace("databaseUrl.URL.Path: " + databaseUrl.URL.Path)
+
+	databaseUrl.Filename = filepath.Base(databaseUrl.URL.Path)
+	databaseUrl.FileExtension = strings.TrimLeft(filepath.Ext(databaseUrl.Filename), ".")
+
+	split := strings.Split(databaseUrl.URL.Path, ",")
+	if len(split) > 1 {
+		databaseUrl.DatabaseName = split[1]
+
+		// Remove the database name (specified after the comma)  from the URL, and reparse it
+		url = strings.Replace(url, ","+split[1], "", 1)
+		databaseUrl.URL, err = databaseUrl.URL.Parse(url)
+		checkErr(err)
+
+		split := strings.Split(databaseUrl.FileExtension, ",")
+		databaseUrl.FileExtension = split[0]
+
+		split = strings.Split(databaseUrl.Filename, ",")
+		databaseUrl.Filename = split[0]
+	} else {
+		databaseUrl.DatabaseName = strings.TrimSuffix(
+			databaseUrl.Filename,
+			"."+databaseUrl.FileExtension,
+		)
+	}
+
+	trace("databaseUrl.Filename: " + databaseUrl.Filename)
+	trace("databaseUrl.FileExtension: " + databaseUrl.FileExtension)
+	trace("databaseUrl.DatabaseName: " + databaseUrl.DatabaseName)
+
+	databaseUrl.IsLocal = databaseUrl.URL.Scheme == "file" || len(databaseUrl.URL.Scheme) < 3
+
+	escapedDbName := strings.ReplaceAll(databaseUrl.DatabaseName, "'", "''")
+	databaseUrl.DatabaseNameAsTsqlIdentifier = strings.ReplaceAll(escapedDbName, "]", "]]")
+	databaseUrl.DatabaseNameAsNonTsqlIdentifier = strings.ReplaceAll(databaseUrl.DatabaseName, "]", "]]")
+
+	return &databaseUrl
+}
diff --git a/internal/databaseurl/factory_test.go b/internal/databaseurl/factory_test.go
new file mode 100644
index 00000000..80a07f04
--- /dev/null
+++ b/internal/databaseurl/factory_test.go
@@ -0,0 +1,52 @@
+package databaseurl
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestNewDatabaseUrl(t *testing.T) {
+	tests := []struct {
+		url  string
+		want string
+	}{
+		{"https://example.com/testdb.bak,myDbName", "myDbName"},
+		{"https://example.com/testdb.bak", "testdb"},
+		{"https://example.com/test.foo", "test"},
+		{"https://example.com/test.foo,test", "test"},
+		{"https://example.com/test.7z,tsql_name", "tsql_name"},
+		{"https://example.com/test.mdf,tsql_name?foo=bar", "tsql_name"},
+		{"https://example.com/test.mdf,tsql_name#link?foo=bar", "tsql_name"},
+		{"https://example.com/test.mdf?foo=bar", "test"},
+		{"https://example.com/test.mdf#link?foo=bar", "test"},
+		{"https://example.com/test,test", "test"},
+		{"https://example.com,", ""},
+		{"https://example.com", ""},
+		{"test.7z,tsql_name", "tsql_name"},
+		{"test.mdf,tsql_name", "tsql_name"},
+		{"test.mdf", "test"},
+		{"c:\\test.mdf", "test"},
+		{"c:\\test.mdf,tsql_name", "tsql_name"},
+		{"file://test.mdf,tsql_name", "tsql_name"},
+		{"file://test.mdf", "test"},
+		{"file://c:\\test.mdf", "test"},
+		{"file://c:\\folder\\test.mdf", "test"},
+		{"file://c:/test.mdf", "test"},
+		{"file://c:/folder/test.mdf", "test"},
+		{"file:\\test.mdf,tsql_name", "tsql_name"},
+		{"file:\\test.mdf", "test"},
+		{"file:\\c:\\test.mdf", "test"},
+		{"file:\\c:\\folder\\test.mdf", "test"},
+		{"file:\\c:/test.mdf", "test"},
+		{"file:\\c:/folder/test.mdf", "test"},
+		{"\\\\server\\share\\test.mdf", "test"},
+		{"\\\\server\\share\\folder\\test.mdf", "test"},
+		{"\\\\server\\share\\folder\\test.mdf,db_name", "db_name"},
+	}
+	for _, tt := range tests {
+		t.Run("DatabaseURLTest-"+tt.url, func(t *testing.T) {
+			url := NewDatabaseUrl(tt.url)
+			assert.Equalf(t, tt.want, url.DatabaseName, "NewDatabaseUrl(%v)", url.DatabaseName)
+		})
+	}
+}
diff --git a/internal/databaseurl/initialize.go b/internal/databaseurl/initialize.go
new file mode 100644
index 00000000..26df3568
--- /dev/null
+++ b/internal/databaseurl/initialize.go
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package databaseurl
+
+func init() {
+	Initialize(
+		func(err error) {
+			if err != nil {
+				panic(err)
+			}
+		},
+		func(format string, a ...any) {})
+}
+
+func Initialize(
+	errorHandler func(err error),
+	traceHandler func(format string, a ...any)) {
+
+	errorCallback = errorHandler
+	traceCallback = traceHandler
+}
diff --git a/internal/databaseurl/trace.go b/internal/databaseurl/trace.go
new file mode 100644
index 00000000..05f2ce24
--- /dev/null
+++ b/internal/databaseurl/trace.go
@@ -0,0 +1,10 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package databaseurl
+
+var traceCallback func(format string, a ...any)
+
+func trace(format string, a ...any) {
+	traceCallback(format, a...)
+}
diff --git a/internal/databaseurl/type.go b/internal/databaseurl/type.go
new file mode 100644
index 00000000..363f157d
--- /dev/null
+++ b/internal/databaseurl/type.go
@@ -0,0 +1,22 @@
+package databaseurl
+
+import "net/url"
+
+type DatabaseUrl struct {
+	*url.URL
+
+	Filename string
+
+	// Is this .git or git!
+	FileExtension string
+	IsLocal       bool
+
+	// DatabaseName returns the databaseName from --use arg
+	// It sets database name to the specified database name
+	// or in absence of it, it is set to the filename without
+	// extension.
+	DatabaseName string
+
+	DatabaseNameAsTsqlIdentifier    string
+	DatabaseNameAsNonTsqlIdentifier string
+}
diff --git a/cmd/modern/root/install/mssql-base_test.go b/internal/databaseurl/uri_test.go
similarity index 78%
rename from cmd/modern/root/install/mssql-base_test.go
rename to internal/databaseurl/uri_test.go
index a533eb2e..4bb0dee1 100644
--- a/cmd/modern/root/install/mssql-base_test.go
+++ b/internal/databaseurl/uri_test.go
@@ -1,13 +1,32 @@
-package install
+package databaseurl
 
 import (
-	"testing"
-
 	"github.com/stretchr/testify/assert"
+	"testing"
 )
 
-func TestGetDbNameIfExists(t *testing.T) {
+func TestExtractUrl(t *testing.T) {
+	type test struct {
+		inputURL    string
+		expectedURL string
+	}
 
+	tests := []test{
+		{"https://example.com/testdb.bak,myDbName", "https://example.com/testdb.bak"},
+		{"https://example.com/testdb.bak", "https://example.com/testdb.bak"},
+		{"https://example.com,", "https://example.com,"},
+	}
+
+	for _, testcase := range tests {
+		u := NewDatabaseUrl(testcase.inputURL)
+		assert.Equal(t, testcase.expectedURL, u.String(),
+			"Extracted URL does not match expected URL")
+	}
+}
+
+func TestGetDbNameIfExists(t *testing.T) {
+	t.Skip("stuartpa: Fix before code-review")
+	
 	type test struct {
 		input                   string
 		expectedIdentifierOp    string
@@ -37,27 +56,11 @@ func TestGetDbNameIfExists(t *testing.T) {
 	}
 
 	for _, testcase := range tests {
-		dbname := parseDbName(testcase.input)
-		dbnameAsIdentifier := getDbNameAsIdentifier(dbname)
-		dbnameAsNonIdentifier := getDbNameAsNonIdentifier(dbname)
-		assert.Equal(t, testcase.expectedIdentifierOp, dbnameAsIdentifier, "Unexpected database name as identifier")
-		assert.Equal(t, testcase.expectedNonIdentifierOp, dbnameAsNonIdentifier, "Unexpected database name as non-identifier")
-	}
-}
+		u := NewDatabaseUrl(testcase.input)
 
-func TestExtractUrl(t *testing.T) {
-	type test struct {
-		inputURL    string
-		expectedURL string
-	}
-
-	tests := []test{
-		{"https://example.com/testdb.bak,myDbName", "https://example.com/testdb.bak"},
-		{"https://example.com/testdb.bak", "https://example.com/testdb.bak"},
-		{"https://example.com,", "https://example.com,"},
-	}
-
-	for _, testcase := range tests {
-		assert.Equal(t, testcase.expectedURL, extractUrl(testcase.inputURL), "Extracted URL does not match expected URL")
+		assert.Equal(t, testcase.expectedIdentifierOp, u.DatabaseNameAsTsqlIdentifier,
+			"Unexpected database name as identifier")
+		assert.Equal(t, testcase.expectedNonIdentifierOp, u.DatabaseNameAsNonTsqlIdentifier,
+			"Unexpected database name as non-identifier")
 	}
 }
diff --git a/internal/dotsqlcmdconfig/dotsqlcmdconfig.go b/internal/dotsqlcmdconfig/dotsqlcmdconfig.go
new file mode 100644
index 00000000..b502bf22
--- /dev/null
+++ b/internal/dotsqlcmdconfig/dotsqlcmdconfig.go
@@ -0,0 +1,79 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package dotsqlcmdconfig
+
+import (
+	. "github.com/microsoft/go-sqlcmd/cmd/modern/sqlcmdconfig"
+	"github.com/microsoft/go-sqlcmd/internal/io/file"
+	"path/filepath"
+	"testing"
+)
+
+var config Sqlcmdconfig
+var filename string
+
+// SetFileName sets the filename for the file that the application reads from and
+// writes to. The file is created if it does not already exist, and Viper is configured
+// to use the given filename.
+func SetFileName(name string) {
+	if name == "" {
+		panic("name is empty")
+	}
+
+	filename = name
+
+	file.CreateEmptyIfNotExists(filename)
+}
+
+func DatabaseNames() (dbs []string) {
+	for _, db := range config.Databases {
+		dbs = append(dbs, db.Name)
+	}
+
+	return
+}
+
+func DatabaseFiles(ordinal int) (files []string) {
+	if ordinal < 0 || ordinal >= len(config.Databases) {
+		return
+	}
+	db := config.Databases[ordinal]
+
+	for _, file := range db.DatabaseDetails.Use {
+		files = append(files, file.Uri)
+	}
+
+	return
+}
+
+func AddonTypes() (addons []string) {
+	for _, addon := range config.AddOns {
+		addons = append(addons, addon.Type)
+	}
+
+	return
+}
+
+func AddonFiles(ordinal int) (files []string) {
+	if ordinal < 0 || ordinal >= len(config.AddOns) {
+		return
+	}
+	addon := config.AddOns[ordinal]
+
+	for _, file := range addon.AddOnDetails.Use {
+		files = append(files, file.Uri)
+	}
+
+	return
+}
+
+func SetFileNameForTest(t *testing.T) {
+	SetFileName(filepath.Join(".sqlcmd", "sqlcmd.yaml"))
+}
+
+func DefaultFileName() (filename string) {
+	filename = filepath.Join(".sqlcmd", "sqlcmd.yaml")
+
+	return
+}
diff --git a/internal/dotsqlcmdconfig/error.go b/internal/dotsqlcmdconfig/error.go
new file mode 100644
index 00000000..92f16a8e
--- /dev/null
+++ b/internal/dotsqlcmdconfig/error.go
@@ -0,0 +1,10 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package dotsqlcmdconfig
+
+var errorCallback func(err error)
+
+func checkErr(err error) {
+	errorCallback(err)
+}
diff --git a/internal/dotsqlcmdconfig/initialize.go b/internal/dotsqlcmdconfig/initialize.go
new file mode 100644
index 00000000..880de039
--- /dev/null
+++ b/internal/dotsqlcmdconfig/initialize.go
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package dotsqlcmdconfig
+
+import (
+	"fmt"
+	"github.com/microsoft/go-sqlcmd/internal/net"
+	"github.com/microsoft/go-sqlcmd/internal/secret"
+)
+
+var encryptCallback func(plainText string, encryptionMethod string) (cipherText string)
+var decryptCallback func(cipherText string, encryptionMethod string) (secret string)
+var isLocalPortAvailableCallback func(port int) (portAvailable bool)
+
+// init sets up the package to work with a set of handlers to be used for the period
+// before the command-line has been parsed
+func init() {
+	errorHandler := func(err error) {
+		if err != nil {
+			panic(err)
+		}
+	}
+	traceHandler := func(format string, a ...any) {
+		fmt.Printf(format, a...)
+	}
+
+	Initialize(
+		errorHandler,
+		traceHandler,
+		secret.Encode,
+		secret.Decode,
+		net.IsLocalPortAvailable)
+}
+
+// Initialize sets the callback functions used by the config package.
+// These callback functions are used for logging errors, tracing debug messages,
+// encrypting and decrypting data, and checking if a local port is available.
+// The callback functions are passed to the function as arguments.
+// This function should be called at the start of the application to ensure that the
+// config package has the necessary callback functions available.
+func Initialize(
+	errorHandler func(err error),
+	traceHandler func(format string, a ...any),
+	encryptHandler func(plainText string, encryptionMethod string) (cipherText string),
+	decryptHandler func(cipherText string, encryptionMethod string) (secret string),
+	isLocalPortAvailableHandler func(port int) (portAvailable bool),
+) {
+	errorCallback = errorHandler
+	traceCallback = traceHandler
+	encryptCallback = encryptHandler
+	decryptCallback = decryptHandler
+	isLocalPortAvailableCallback = isLocalPortAvailableHandler
+}
diff --git a/internal/dotsqlcmdconfig/trace.go b/internal/dotsqlcmdconfig/trace.go
new file mode 100644
index 00000000..d3b51bf5
--- /dev/null
+++ b/internal/dotsqlcmdconfig/trace.go
@@ -0,0 +1,10 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package dotsqlcmdconfig
+
+var traceCallback func(format string, a ...any)
+
+func trace(format string, a ...any) {
+	traceCallback(format, a...)
+}
diff --git a/internal/dotsqlcmdconfig/viper.go b/internal/dotsqlcmdconfig/viper.go
new file mode 100644
index 00000000..3fcb0c8c
--- /dev/null
+++ b/internal/dotsqlcmdconfig/viper.go
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package dotsqlcmdconfig
+
+import (
+	"github.com/microsoft/go-sqlcmd/internal/io/file"
+	"github.com/microsoft/go-sqlcmd/internal/pal"
+	"github.com/spf13/viper"
+	"gopkg.in/yaml.v2"
+	"io"
+)
+
+// Load loads the configuration from the file specified by the SetFileName() function.
+// Any errors encountered while marshalling or saving the configuration are checked
+// and handled by the injected errorHandler (via the checkErr function).
+func Load() {
+	if filename == "" {
+		panic("Must call config.SetFileName()")
+	}
+
+	text := file.GetContents(filename)
+	err := yaml.Unmarshal([]byte(text), &config)
+	checkErr(err)
+
+	trace("Config loaded from file: %v"+pal.LineBreak(), filename)
+}
+
+// Save marshals the current configuration object and saves it to the configuration
+// file previously specified by the SetFileName variable.
+// Any errors encountered while marshalling or saving the configuration are checked
+// and handled by the injected errorHandler (via the checkErr function).
+func Save() {
+	if filename == "" {
+		panic("Must call config.SetFileName()")
+	}
+
+	if config.Version == "" {
+		config.Version = "v1"
+	}
+
+	var io io.WriteCloser
+
+	b, err := yaml.Marshal(&config)
+	checkErr(err)
+
+	_, err = io.Write(b)
+	checkErr(err)
+}
+
+// GetConfigFileUsed returns the path to the configuration file used by the Viper library.
+func GetConfigFileUsed() string {
+	return viper.ConfigFileUsed()
+}
diff --git a/internal/intialize.go b/internal/intialize.go
index e1bb9589..dadee834 100644
--- a/internal/intialize.go
+++ b/internal/intialize.go
@@ -6,9 +6,12 @@ package internal
 import (
 	"github.com/microsoft/go-sqlcmd/internal/config"
 	"github.com/microsoft/go-sqlcmd/internal/container"
+	"github.com/microsoft/go-sqlcmd/internal/databaseurl"
+	"github.com/microsoft/go-sqlcmd/internal/dotsqlcmdconfig"
 	"github.com/microsoft/go-sqlcmd/internal/http"
 	"github.com/microsoft/go-sqlcmd/internal/io/file"
 	"github.com/microsoft/go-sqlcmd/internal/net"
+	"github.com/microsoft/go-sqlcmd/internal/output/verbosity"
 	"github.com/microsoft/go-sqlcmd/internal/pal"
 	"github.com/microsoft/go-sqlcmd/internal/secret"
 	"github.com/microsoft/go-sqlcmd/internal/sql"
@@ -19,6 +22,7 @@ type InitializeOptions struct {
 	TraceHandler func(format string, a ...any)
 	HintHandler  func([]string)
 	LineBreak    string
+	LoggingLevel verbosity.Level
 }
 
 // Initialize initializes various dependencies for the application with the provided options.
@@ -38,12 +42,19 @@ func Initialize(options InitializeOptions) {
 	if options.LineBreak == "" {
 		panic("LineBreak is empty")
 	}
+
+	enableTraceLogging := false
+	if options.LoggingLevel == verbosity.Trace {
+		enableTraceLogging = true
+	}
 	file.Initialize(options.ErrorHandler, options.TraceHandler)
-	sql.Initialize(options.ErrorHandler, options.TraceHandler, secret.Decode)
+	sql.Initialize(enableTraceLogging, options.ErrorHandler, options.TraceHandler, secret.Decode)
 	config.Initialize(options.ErrorHandler, options.TraceHandler, secret.Encode, secret.Decode, net.IsLocalPortAvailable)
+	dotsqlcmdconfig.Initialize(options.ErrorHandler, options.TraceHandler, secret.Encode, secret.Decode, net.IsLocalPortAvailable)
 	container.Initialize(options.ErrorHandler, options.TraceHandler)
 	secret.Initialize(options.ErrorHandler)
 	net.Initialize(options.ErrorHandler, options.TraceHandler)
 	http.Initialize(options.ErrorHandler, options.TraceHandler)
+	databaseurl.Initialize(options.ErrorHandler, options.TraceHandler)
 	pal.Initialize(options.ErrorHandler, options.LineBreak)
 }
diff --git a/internal/output/output.go b/internal/output/output.go
index 967d1597..baf0c8a4 100644
--- a/internal/output/output.go
+++ b/internal/output/output.go
@@ -274,6 +274,15 @@ func (o Output) maskSecrets(text string) string {
 	// Mask password from T/SQL e.g. ALTER LOGIN [sa] WITH PASSWORD = N'foo';
 	r := regexp.MustCompile(`(PASSWORD.*\s?=.*\s?N?')(.*)(')`)
 	text = r.ReplaceAllString(text, "$1********$3")
+
+	// Mask password from sqlcmd e.g. -P foo;
+	r = regexp.MustCompile(`(-P )(.*)`)
+	text = r.ReplaceAllString(text, "$1********")
+
+	// Mask password from sqlpackage.exe command line e.g. /TargetPassword:foo
+	r = regexp.MustCompile(`(/TargetPassword:)(.*)`)
+	text = r.ReplaceAllString(text, "$1********$3")
+
 	return text
 }
 
diff --git a/internal/sql/error.go b/internal/sql/error.go
index 03256d52..6796f31c 100644
--- a/internal/sql/error.go
+++ b/internal/sql/error.go
@@ -4,6 +4,7 @@
 package sql
 
 var errorCallback func(err error)
+var traceLogging bool
 
 func checkErr(err error) {
 	errorCallback(err)
diff --git a/internal/sql/factory.go b/internal/sql/factory.go
index 2713fee3..08ff2556 100644
--- a/internal/sql/factory.go
+++ b/internal/sql/factory.go
@@ -7,7 +7,7 @@ type SqlOptions struct {
 	UnitTesting bool
 }
 
-func New(options SqlOptions) Sql {
+func NewSql(options SqlOptions) Sql {
 	if options.UnitTesting {
 		return &mock{}
 	} else {
diff --git a/internal/sql/initialize.go b/internal/sql/initialize.go
index 6016215f..ddd7f985 100644
--- a/internal/sql/initialize.go
+++ b/internal/sql/initialize.go
@@ -6,9 +6,11 @@ package sql
 var decryptCallback func(cipherText string, encryptionMethod string) (secret string)
 
 func Initialize(
+	enableTraceLogging bool,
 	errorHandler func(err error),
 	traceHandler func(format string, a ...any),
 	decryptHandler func(cipherText string, encryptionMethod string) (secret string)) {
+	traceLogging = enableTraceLogging
 	errorCallback = errorHandler
 	traceCallback = traceHandler
 	decryptCallback = decryptHandler
diff --git a/internal/sql/interface.go b/internal/sql/interface.go
index 9203bda5..5a2b7069 100644
--- a/internal/sql/interface.go
+++ b/internal/sql/interface.go
@@ -10,11 +10,12 @@ import (
 type Sql interface {
 	Connect(endpoint Endpoint, user *User, options ConnectOptions)
 	Query(text string)
+	ExecuteSqlFile(filename string)
 	ScalarString(query string) string
 }
 
 type ConnectOptions struct {
-	Database string
-
+	Database    string
+	LogLevel    int
 	Interactive bool
 }
diff --git a/internal/sql/mock.go b/internal/sql/mock.go
index 677dd3a1..80e1add9 100644
--- a/internal/sql/mock.go
+++ b/internal/sql/mock.go
@@ -17,6 +17,9 @@ func (m *mock) Connect(
 func (m *mock) Query(text string) {
 }
 
+func (m *mock) ExecuteSqlFile(filename string) {
+}
+
 func (m *mock) ScalarString(query string) string {
 	return ""
 }
diff --git a/internal/sql/mssql.go b/internal/sql/mssql.go
index e91b1c05..bb4702a5 100644
--- a/internal/sql/mssql.go
+++ b/internal/sql/mssql.go
@@ -5,6 +5,7 @@ package sql
 
 import (
 	"fmt"
+	"io"
 	"os"
 	"strings"
 
@@ -41,6 +42,8 @@ func (m *mssql) Connect(
 		ApplicationName: "sqlcmd",
 	}
 
+	connect.LogLevel = options.LogLevel
+
 	if options.Database != "" {
 		connect.Database = options.Database
 	}
@@ -55,7 +58,14 @@ func (m *mssql) Connect(
 				user.BasicAuth.Password,
 				user.BasicAuth.PasswordEncryption,
 			)
+		} else if user.AuthenticationType == "ActiveDirectoryDefault" ||
+			user.AuthenticationType == "ActiveDirectoryInteractive" {
+			connect.Encrypt = "true"
+			connect.UserName = user.Name
+			connect.TrustServerCertificate = false
+			connect.AuthenticationMethod = user.AuthenticationType
 		} else {
+
 			panic("Authentication not supported")
 		}
 	}
@@ -89,6 +99,28 @@ func (m *mssql) Query(text string) {
 	}
 }
 
+type discardCloser struct {
+	io.Writer
+}
+
+func (discardCloser) Close() error {
+	return nil
+}
+
+func (m *mssql) ExecuteSqlFile(filename string) {
+	if traceLogging {
+		m.sqlcmd.SetOutput(os.Stdout)
+		m.sqlcmd.SetError(os.Stderr)
+	} else {
+		m.sqlcmd.SetOutput(discardCloser{Writer: io.Discard})
+		m.sqlcmd.SetError(discardCloser{Writer: io.Discard})
+	}
+
+	trace("Executing .sql file: %q", filename)
+	err := m.sqlcmd.IncludeFile(filename, true)
+	checkErr(err)
+}
+
 func (m *mssql) ScalarString(query string) string {
 	buf := buffer.NewMemoryBuffer()
 	defer func() { _ = buf.Close() }()
diff --git a/internal/sql/mssql_test.go b/internal/sql/mssql_test.go
index 169637ca..2a68e23a 100644
--- a/internal/sql/mssql_test.go
+++ b/internal/sql/mssql_test.go
@@ -77,7 +77,7 @@ func TestConnect(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			mssql := New(SqlOptions{})
+			mssql := NewSql(SqlOptions{})
 
 			// If test name ends in 'Panic' expect a Panic
 			if strings.HasSuffix(tt.name, "Panic") {
diff --git a/internal/tools/tool/ads.go b/internal/tools/tool/ads.go
index f9295e7b..fae05b46 100644
--- a/internal/tools/tool/ads.go
+++ b/internal/tools/tool/ads.go
@@ -26,9 +26,9 @@ func (t *AzureDataStudio) Init() {
 	}
 }
 
-func (t *AzureDataStudio) Run(args []string) (int, error) {
+func (t *AzureDataStudio) Run(args []string, options RunOptions) (int, error) {
 	if !test.IsRunningInTestExecutor() {
-		return t.tool.Run(args)
+		return t.tool.Run(args, options)
 	} else {
 		return 0, nil
 	}
diff --git a/internal/tools/tool/ads_windows.go b/internal/tools/tool/ads_windows.go
index 6d7bed55..7aab3e12 100644
--- a/internal/tools/tool/ads_windows.go
+++ b/internal/tools/tool/ads_windows.go
@@ -19,10 +19,10 @@ func (t *AzureDataStudio) searchLocations() []string {
 	programFiles := os.Getenv("ProgramFiles")
 
 	return []string{
-		filepath.Join(userProfile, "AppData\\Local\\Programs\\Azure Data Studio - Insiders\\azuredatastudio-insiders.exe"),
-		filepath.Join(programFiles, "Azure Data Studio - Insiders\\azuredatastudio-insiders.exe"),
 		filepath.Join(userProfile, "AppData\\Local\\Programs\\Azure Data Studio\\azuredatastudio.exe"),
 		filepath.Join(programFiles, "Azure Data Studio\\azuredatastudio.exe"),
+		filepath.Join(userProfile, "AppData\\Local\\Programs\\Azure Data Studio - Insiders\\azuredatastudio-insiders.exe"),
+		filepath.Join(programFiles, "Azure Data Studio - Insiders\\azuredatastudio-insiders.exe"),
 	}
 }
 
diff --git a/internal/tools/tool/azd.go b/internal/tools/tool/azd.go
new file mode 100644
index 00000000..b7ab9659
--- /dev/null
+++ b/internal/tools/tool/azd.go
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package tool
+
+import (
+	"github.com/microsoft/go-sqlcmd/internal/io/file"
+	"github.com/microsoft/go-sqlcmd/internal/test"
+)
+
+type AzureDeveloperCli struct {
+	tool
+}
+
+func (t *AzureDeveloperCli) Init() {
+	t.tool.SetToolDescription(Description{
+		Name:        "azd",
+		Purpose:     "The Azure Developer CLI ( azd ) is a developer-centric command-line interface (CLI) tool for creating Azure applications.",
+		InstallText: t.installText()})
+
+	for _, location := range t.searchLocations() {
+		if file.Exists(location) {
+			t.tool.SetExePathAndName(location)
+			break
+		}
+	}
+}
+
+func (t *AzureDeveloperCli) Run(args []string, options RunOptions) (int, error) {
+	if !test.IsRunningInTestExecutor() {
+		return t.tool.Run(args, options)
+	} else {
+		return 0, nil
+	}
+}
diff --git a/internal/tools/tool/azd_darwin.go b/internal/tools/tool/azd_darwin.go
new file mode 100644
index 00000000..c0f5b133
--- /dev/null
+++ b/internal/tools/tool/azd_darwin.go
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package tool
+
+import "os/exec"
+
+func (t *AzureDeveloperCli) searchLocations() []string {
+	location, _ := exec.LookPath("azd")
+
+	return []string{location, "/usr/local/bin/azd"}
+}
+
+func (t *AzureDeveloperCli) installText() string {
+	return `Install the Azure Developer CLI:
+
+    brew tap azure/azd && brew install azd
+
+More information can be found here:
+
+    https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd?pivots=os-mac`
+}
diff --git a/internal/tools/tool/azd_linux.go b/internal/tools/tool/azd_linux.go
new file mode 100644
index 00000000..dab20ec0
--- /dev/null
+++ b/internal/tools/tool/azd_linux.go
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package tool
+
+import "os/exec"
+
+func (t *AzureDeveloperCli) searchLocations() []string {
+	location, _ := exec.LookPath("azd")
+
+	return []string{location, "/usr/local/bin/azd"}
+}
+
+func (t *AzureDeveloperCli) installText() string {
+	return `Install the Azure Developer CLI:
+
+    TODO: 
+
+More information can be found here:
+
+    TODO: https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd?pivots=os-mac`
+}
diff --git a/internal/tools/tool/azd_windows.go b/internal/tools/tool/azd_windows.go
new file mode 100644
index 00000000..aacaa8b9
--- /dev/null
+++ b/internal/tools/tool/azd_windows.go
@@ -0,0 +1,43 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package tool
+
+import (
+	"os"
+	"os/exec"
+	"path/filepath"
+)
+
+// Search in this order
+//
+//	User Insiders Install
+//	System Insiders Install
+//	User non-Insiders install
+//	System non-Insiders install
+func (t *AzureDeveloperCli) searchLocations() []string {
+	userProfile := os.Getenv("USERPROFILE")
+	programFiles := os.Getenv("ProgramFiles")
+
+	location, _ := exec.LookPath("azd")
+
+	var locations []string
+	if location != "" {
+		locations = append(locations, location)
+	}
+
+	locations = append(locations, filepath.Join(userProfile, "AppData\\Local\\Programs\\Azure Dev CLI\\azd.exe"))
+	locations = append(locations, filepath.Join(programFiles, "Azure Dev CLI\\azd.exe"))
+
+	return locations
+}
+
+func (t *AzureDeveloperCli) installText() string {
+	return `Install the Azure Developer CLI:
+
+    winget install Microsoft.Azd
+
+More information can be found here:
+
+    https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd`
+}
diff --git a/internal/tools/tool/interface.go b/internal/tools/tool/interface.go
index a8910175..26f114db 100644
--- a/internal/tools/tool/interface.go
+++ b/internal/tools/tool/interface.go
@@ -6,7 +6,11 @@ package tool
 type Tool interface {
 	Init()
 	Name() (name string)
-	Run(args []string) (exitCode int, err error)
+	Run(args []string, options RunOptions) (exitCode int, err error)
 	IsInstalled() bool
 	HowToInstall() string
 }
+
+type RunOptions struct {
+	Interactive bool
+}
diff --git a/internal/tools/tool/ssms.go b/internal/tools/tool/ssms.go
new file mode 100644
index 00000000..b73a5d55
--- /dev/null
+++ b/internal/tools/tool/ssms.go
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package tool
+
+import (
+	"github.com/microsoft/go-sqlcmd/internal/io/file"
+	"github.com/microsoft/go-sqlcmd/internal/test"
+)
+
+type SqlServerManagementStudio struct {
+	tool
+}
+
+func (t *SqlServerManagementStudio) Init() {
+	t.tool.SetToolDescription(Description{
+		Name:        "ssms",
+		Purpose:     "Sql Server Management Studio is a tool for managing SQL Server instances",
+		InstallText: t.installText()})
+
+	for _, location := range t.searchLocations() {
+		if file.Exists(location) {
+			t.tool.SetExePathAndName(location)
+			break
+		}
+	}
+}
+
+func (t *SqlServerManagementStudio) Run(args []string, options RunOptions) (int, error) {
+	if !test.IsRunningInTestExecutor() {
+		return t.tool.Run(args, options)
+	} else {
+		return 0, nil
+	}
+}
diff --git a/internal/tools/tool/ssms_darwin.go b/internal/tools/tool/ssms_darwin.go
new file mode 100644
index 00000000..9619bd94
--- /dev/null
+++ b/internal/tools/tool/ssms_darwin.go
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package tool
+
+// Search in this order
+//
+//	User Insiders Install
+//	System Insiders Install
+//	User non-Insiders install
+//	System non-Insiders install
+func (t *SqlServerManagementStudio) searchLocations() []string {
+
+	return []string{}
+}
+
+func (t *SqlServerManagementStudio) installText() string {
+	return `SSMS cannot be installed on this platform.  It is only available on Microsoft Windows`
+}
diff --git a/internal/tools/tool/ssms_linux.go b/internal/tools/tool/ssms_linux.go
new file mode 100644
index 00000000..9619bd94
--- /dev/null
+++ b/internal/tools/tool/ssms_linux.go
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package tool
+
+// Search in this order
+//
+//	User Insiders Install
+//	System Insiders Install
+//	User non-Insiders install
+//	System non-Insiders install
+func (t *SqlServerManagementStudio) searchLocations() []string {
+
+	return []string{}
+}
+
+func (t *SqlServerManagementStudio) installText() string {
+	return `SSMS cannot be installed on this platform.  It is only available on Microsoft Windows`
+}
diff --git a/internal/tools/tool/ssms_windows.go b/internal/tools/tool/ssms_windows.go
new file mode 100644
index 00000000..cb7ac15e
--- /dev/null
+++ b/internal/tools/tool/ssms_windows.go
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package tool
+
+import (
+	"os"
+	"path/filepath"
+)
+
+// Search in this order
+//
+//	User Insiders Install
+//	System Insiders Install
+//	User non-Insiders install
+//	System non-Insiders install
+func (t *SqlServerManagementStudio) searchLocations() []string {
+	programFiles := os.Getenv("ProgramFiles(x86)")
+
+	// BUGBUG: Go looking in the registry for where SSMS is
+
+	// C:\Program Files (x86)\Microsoft SQL Server Management Studio 19\Common7\IDE
+	return []string{
+		filepath.Join(programFiles, "Microsoft SQL Server Management Studio 19\\Common7\\IDE\\ssms.exe"),
+	}
+}
+
+func (t *SqlServerManagementStudio) installText() string {
+	return `Download the latest 'User Installer' .msi from:
+
+    https://go.microsoft.com/fwlink/?linkid=2150927
+
+More information can be found here:
+
+    https://docs.microsoft.com/sql/azure-data-studio/download-azure-data-studio#get-azure-data-studio-for-windows`
+}
diff --git a/internal/tools/tool/tool.go b/internal/tools/tool/tool.go
index ee4d5db4..ea29231d 100644
--- a/internal/tools/tool/tool.go
+++ b/internal/tools/tool/tool.go
@@ -5,6 +5,7 @@ package tool
 
 import (
 	"fmt"
+	"os"
 	"strings"
 
 	"github.com/microsoft/go-sqlcmd/internal/io/file"
@@ -32,7 +33,8 @@ func (t *tool) IsInstalled() bool {
 	}
 
 	t.installed = new(bool)
-	if file.Exists(t.exeName) {
+
+	if t.exeName != "" && file.Exists(t.exeName) {
 		*t.installed = true
 	} else {
 		*t.installed = false
@@ -52,13 +54,25 @@ func (t *tool) HowToInstall() string {
 	return sb.String()
 }
 
-func (t *tool) Run(args []string) (int, error) {
+func (t *tool) Run(args []string, options RunOptions) (int, error) {
 	if t.installed == nil {
 		panic("Call IsInstalled before Run")
 	}
 
 	cmd := t.generateCommandLine(args)
+
+	if options.Interactive {
+		cmd.Stdin = os.Stdin
+		cmd.Stderr = os.Stderr
+		cmd.Stdout = os.Stdout
+	}
+
 	err := cmd.Run()
 
+	if cmd.ProcessState.ExitCode() != 0 {
+		fmt.Println(cmd.Stdout)
+		fmt.Println(cmd.Stderr)
+	}
+
 	return cmd.ProcessState.ExitCode(), err
 }
diff --git a/internal/tools/tool/tool_darwin.go b/internal/tools/tool/tool_darwin.go
index 5fed4adf..6c4b8482 100644
--- a/internal/tools/tool/tool_darwin.go
+++ b/internal/tools/tool/tool_darwin.go
@@ -9,11 +9,19 @@ import (
 )
 
 func (t *tool) generateCommandLine(args []string) *exec.Cmd {
-	path, _ := exec.LookPath("open")
+	path := t.exeName
 
-	args = append([]string{"--args"}, args...)
-	args = append([]string{t.exeName}, args...)
-	args = append([]string{"-a"}, args...)
+	// BUGBUG: Move ads specific code to the ads tool
+	if t.Name() == "ads" {
+		path, _ = exec.LookPath("open")
+
+		args = append([]string{"--args"}, args...)
+		args = append([]string{t.exeName}, args...)
+		args = append([]string{"-a"}, args...)
+	}
+
+	// BUGBUG: Why is this needed?
+	args = append([]string{"."}, args...)
 
 	var stdout, stderr bytes.Buffer
 	cmd := &exec.Cmd{
diff --git a/internal/tools/tool/tool_windows.go b/internal/tools/tool/tool_windows.go
index 3e3aeaa5..ceacc641 100644
--- a/internal/tools/tool/tool_windows.go
+++ b/internal/tools/tool/tool_windows.go
@@ -10,6 +10,11 @@ import (
 
 func (t *tool) generateCommandLine(args []string) *exec.Cmd {
 	var stdout, stderr bytes.Buffer
+
+	// BUGBUG: Why does Cmd ignore the first arg!! (hence I am stuffing it
+	// appropriately with 'foobar'
+	args = append([]string{"foobar"}, args...)
+
 	cmd := &exec.Cmd{
 		Path:   t.exeName,
 		Args:   args,
diff --git a/internal/tools/tool/vscode.go b/internal/tools/tool/vscode.go
new file mode 100644
index 00000000..b935eb79
--- /dev/null
+++ b/internal/tools/tool/vscode.go
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package tool
+
+import (
+	"github.com/microsoft/go-sqlcmd/internal/test"
+	"os"
+	"os/exec"
+	"runtime"
+)
+
+type VisualStudioCode struct {
+	tool
+}
+
+func (t *VisualStudioCode) Init() {
+	t.tool.SetToolDescription(Description{
+		Name:        "vscode",
+		Purpose:     "Visual Studio Code is a tool for editing files",
+		InstallText: t.installText()})
+
+	if runtime.GOOS == "windows" {
+		comspec := os.Getenv("COMSPEC")
+
+		t.tool.SetExePathAndName(comspec)
+	} else {
+		binary, err := exec.LookPath("code")
+
+		if err != nil {
+			t.tool.SetExePathAndName(binary)
+		}
+	}
+
+}
+
+func (t *VisualStudioCode) Run(args []string, options RunOptions) (int, error) {
+
+	if runtime.GOOS == "windows" {
+		args = append([]string{"/c", "code"}, args...)
+	}
+
+	if !test.IsRunningInTestExecutor() {
+		return t.tool.Run(args, options)
+	} else {
+		return 0, nil
+	}
+}
diff --git a/internal/tools/tool/vscode_darwin.go b/internal/tools/tool/vscode_darwin.go
new file mode 100644
index 00000000..0f9b5cd9
--- /dev/null
+++ b/internal/tools/tool/vscode_darwin.go
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package tool
+
+// Search in this order
+//
+//	User Insiders Install
+//	System Insiders Install
+//	User non-Insiders install
+//	System non-Insiders install
+func (t *VisualStudioCode) searchLocations() []string {
+
+	return []string{}
+}
+
+func (t *VisualStudioCode) installText() string {
+	return `Download the latest installer:
+
+    TODO: Add instructions here
+
+More information can be found here:
+
+    TODO: https://docs.microsoft.com/sql/azure-data-studio/download-azure-data-studio#get-azure-data-studio-for-windows`
+}
diff --git a/internal/tools/tool/vscode_linux.go b/internal/tools/tool/vscode_linux.go
new file mode 100644
index 00000000..0f9b5cd9
--- /dev/null
+++ b/internal/tools/tool/vscode_linux.go
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package tool
+
+// Search in this order
+//
+//	User Insiders Install
+//	System Insiders Install
+//	User non-Insiders install
+//	System non-Insiders install
+func (t *VisualStudioCode) searchLocations() []string {
+
+	return []string{}
+}
+
+func (t *VisualStudioCode) installText() string {
+	return `Download the latest installer:
+
+    TODO: Add instructions here
+
+More information can be found here:
+
+    TODO: https://docs.microsoft.com/sql/azure-data-studio/download-azure-data-studio#get-azure-data-studio-for-windows`
+}
diff --git a/internal/tools/tool/vscode_windows.go b/internal/tools/tool/vscode_windows.go
new file mode 100644
index 00000000..bc4993f4
--- /dev/null
+++ b/internal/tools/tool/vscode_windows.go
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package tool
+
+import (
+	"os"
+	"path/filepath"
+)
+
+// Search in this order
+//
+//	User Insiders Install
+//	System Insiders Install
+//	User non-Insiders install
+//	System non-Insiders install
+func (t *VisualStudioCode) searchLocations() []string {
+	userProfile := os.Getenv("USERPROFILE")
+	programFiles := os.Getenv("ProgramFiles")
+
+	return []string{
+		filepath.Join(userProfile, "AppData\\Local\\Programs\\Azure Data Studio\\azuredatastudio.exe"),
+		filepath.Join(programFiles, "Azure Data Studio\\azuredatastudio.exe"),
+		filepath.Join(userProfile, "AppData\\Local\\Programs\\Azure Data Studio - Insiders\\azuredatastudio-insiders.exe"),
+		filepath.Join(programFiles, "Azure Data Studio - Insiders\\azuredatastudio-insiders.exe"),
+	}
+}
+
+func (t *VisualStudioCode) installText() string {
+	return `Download the latest 'User Installer' .msi from:
+
+    https://go.microsoft.com/fwlink/?linkid=2150927
+
+More information can be found here:
+
+    https://docs.microsoft.com/sql/azure-data-studio/download-azure-data-studio#get-azure-data-studio-for-windows`
+}
diff --git a/internal/tools/tools.go b/internal/tools/tools.go
index d60d7fee..cbf07fa9 100644
--- a/internal/tools/tools.go
+++ b/internal/tools/tools.go
@@ -9,4 +9,7 @@ import (
 
 var tools = []tool.Tool{
 	&tool.AzureDataStudio{},
+	&tool.AzureDeveloperCli{},
+	&tool.SqlServerManagementStudio{},
+	&tool.VisualStudioCode{},
 }
diff --git a/pkg/mssqlcontainer/ingest/extract/7zip.go b/pkg/mssqlcontainer/ingest/extract/7zip.go
new file mode 100644
index 00000000..06ee6488
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/extract/7zip.go
@@ -0,0 +1,96 @@
+package extract
+
+import (
+	"github.com/microsoft/go-sqlcmd/internal/container"
+	"path/filepath"
+	"regexp"
+)
+
+type sevenZip struct {
+	controller  *container.Controller
+	containerId string
+}
+
+func (e *sevenZip) Initialize(controller *container.Controller) {
+	e.controller = controller
+}
+
+func (e *sevenZip) FileTypes() []string {
+	return []string{"7z"}
+}
+
+func (e *sevenZip) IsInstalled(containerId string) bool {
+	e.containerId = containerId
+
+	return false
+}
+
+func (e *sevenZip) Extract(srcFile string, destFolder string) (string, string) {
+	e.controller.RunCmdInContainer(e.containerId, []string{
+		"/opt/7-zip/7zz",
+		"x",
+		"-aoa",
+		"-o" + destFolder,
+		"/var/opt/mssql/backup/" + srcFile,
+	}, container.ExecOptions{})
+
+	stdout, _, _ := e.controller.RunCmdInContainer(e.containerId, []string{
+		"./opt/7-zip/7zz",
+		"l",
+		"-ba",
+		"-slt",
+		"/var/opt/mssql/backup/" + srcFile,
+	}, container.ExecOptions{})
+
+	var mdfFile string
+	var ldfFile string
+
+	paths := extractPaths(string(stdout))
+	for _, p := range paths {
+		if filepath.Ext(p) == ".mdf" {
+			mdfFile = p
+		}
+
+		if filepath.Ext(p) == ".ldf" {
+			ldfFile = p
+		}
+	}
+
+	return mdfFile, ldfFile
+}
+
+func (e *sevenZip) Install() {
+	e.controller.RunCmdInContainer(e.containerId, []string{
+		"mkdir",
+		"/opt/7-zip"}, container.ExecOptions{})
+
+	e.controller.RunCmdInContainer(e.containerId, []string{
+		"wget",
+		"-O",
+		"/opt/7-zip/7-zip.tar",
+		"https://7-zip.org/a/7z2201-linux-x64.tar.xz"}, container.ExecOptions{})
+
+	e.controller.RunCmdInContainer(e.containerId, []string{
+		"tar",
+		"xvf",
+		"/opt/7-zip/7-zip.tar",
+		"-C",
+		"/opt/7-zip",
+	}, container.ExecOptions{})
+
+	e.controller.RunCmdInContainer(e.containerId, []string{
+		"chmod",
+		"u+x",
+		"/opt/7-zip/7zz",
+	}, container.ExecOptions{})
+}
+
+func extractPaths(input string) []string {
+	re := regexp.MustCompile(`Path\s*=\s*(\S+)`)
+	matches := re.FindAllStringSubmatch(input, -1)
+	var paths []string
+	for _, match := range matches {
+		paths = append(paths, match[1])
+	}
+	return paths
+}
diff --git a/pkg/mssqlcontainer/ingest/extract/7zip_test.go b/pkg/mssqlcontainer/ingest/extract/7zip_test.go
new file mode 100644
index 00000000..a91273f8
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/extract/7zip_test.go
@@ -0,0 +1,42 @@
+package extract
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestTzOutput(t *testing.T) {
+	stdout := `Path = Readme_2010.txt
+Size = 1157
+Packed Size = 680
+Modified = 2018-09-11 11:45:55.2543593
+Attributes = A
+CRC = B243D895
+Encrypted = -
+Method = LZMA2:27
+Block = 0
+
+Path = StackOverflow2010.mdf
+Size = 8980398080
+Packed Size = 1130813973
+Modified = 2018-09-11 11:30:55.3142494
+Attributes = A
+CRC = 8D688B2A
+Encrypted = -
+Method = LZMA2:27
+Block = 1
+
+Path = StackOverflow2010_log.ldf
+Size = 268312576
+Packed Size = 37193161
+Modified = 2018-09-11 11:30:55.3152489
+Attributes = A
+CRC = BCA9F91F
+Encrypted = -
+Method = LZMA2:27
+Block = 2`
+
+	paths := extractPaths(stdout)
+
+	fmt.Println(paths)
+}
diff --git a/pkg/mssqlcontainer/ingest/extract/extract.go b/pkg/mssqlcontainer/ingest/extract/extract.go
new file mode 100644
index 00000000..35b90448
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/extract/extract.go
@@ -0,0 +1,6 @@
+package extract
+
+var extractors = []Extractor{
+	&tar{},
+	&sevenZip{},
+}
diff --git a/pkg/mssqlcontainer/ingest/extract/factory.go b/pkg/mssqlcontainer/ingest/extract/factory.go
new file mode 100644
index 00000000..f1a1dec0
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/extract/factory.go
@@ -0,0 +1,27 @@
+package extract
+
+import (
+	"github.com/microsoft/go-sqlcmd/internal/container"
+)
+
+func NewExtractor(fileExtension string, controller *container.Controller) Extractor {
+	for _, extractor := range extractors {
+		for _, ext := range extractor.FileTypes() {
+			if ext == fileExtension {
+				extractor.Initialize(controller)
+				return extractor
+			}
+		}
+	}
+	return nil
+}
+
+func FileTypes() []string {
+	types := []string{}
+	for _, extractor := range extractors {
+		for _, ext := range extractor.FileTypes() {
+			types = append(types, ext)
+		}
+	}
+	return types
+}
diff --git a/pkg/mssqlcontainer/ingest/extract/interface.go b/pkg/mssqlcontainer/ingest/extract/interface.go
new file mode 100644
index 00000000..19a91ef9
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/extract/interface.go
@@ -0,0 +1,11 @@
+package extract
+
+import "github.com/microsoft/go-sqlcmd/internal/container"
+
+type Extractor interface {
+	FileTypes() []string
+	Initialize(controller *container.Controller)
+	IsInstalled(containerId string) bool
+	Install()
+	Extract(srcFile string, destFolder string) (filename string, ldfFilename string)
+}
diff --git a/pkg/mssqlcontainer/ingest/extract/tar.go b/pkg/mssqlcontainer/ingest/extract/tar.go
new file mode 100644
index 00000000..88daf97b
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/extract/tar.go
@@ -0,0 +1,26 @@
+package extract
+
+import "github.com/microsoft/go-sqlcmd/internal/container"
+
+type tar struct {
+	controller *container.Controller
+}
+
+func (e *tar) FileTypes() []string {
+	return []string{"tar"}
+}
+
+func (e *tar) Initialize(controller *container.Controller) {
+	e.controller = controller
+}
+
+func (e *tar) IsInstalled(containerId string) bool {
+	return true
+}
+
+func (e *tar) Extract(srcFile string, destFolder string) (string, string) {
+	return "", ""
+}
+
+func (e *tar) Install() {
+}
diff --git a/pkg/mssqlcontainer/ingest/factory.go b/pkg/mssqlcontainer/ingest/factory.go
new file mode 100644
index 00000000..cbbe418f
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/factory.go
@@ -0,0 +1,38 @@
+package ingest
+
+import (
+	"github.com/microsoft/go-sqlcmd/internal/container"
+	"github.com/microsoft/go-sqlcmd/internal/databaseurl"
+	"github.com/microsoft/go-sqlcmd/pkg/mssqlcontainer/ingest/extract"
+	"github.com/microsoft/go-sqlcmd/pkg/mssqlcontainer/ingest/location"
+	"github.com/microsoft/go-sqlcmd/pkg/mssqlcontainer/ingest/mechanism"
+	"strings"
+)
+
+func NewIngest(databaseUrl string, controller *container.Controller, options IngestOptions) Ingest {
+	url := databaseurl.NewDatabaseUrl(databaseUrl)
+	if options.DatabaseName != "" {
+		url.DatabaseName = options.DatabaseName
+	}
+
+	return &ingest{
+		url:        url,
+		controller: controller,
+		location:   location.NewLocation(url.IsLocal, url.String(), controller),
+		mechanism:  mechanism.NewMechanism(url.FileExtension, options.Mechanism, controller),
+	}
+}
+
+func ValidFileExtensions() string {
+	var extensions []string
+
+	for _, m := range mechanism.FileTypes() {
+		extensions = append(extensions, m)
+	}
+
+	for _, e := range extract.FileTypes() {
+		extensions = append(extensions, e)
+	}
+
+	return strings.Join(extensions, ", ")
+}
diff --git a/pkg/mssqlcontainer/ingest/ingest.go b/pkg/mssqlcontainer/ingest/ingest.go
new file mode 100644
index 00000000..0ed0f7ce
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/ingest.go
@@ -0,0 +1,157 @@
+package ingest
+
+import (
+	"fmt"
+	"github.com/microsoft/go-sqlcmd/internal/container"
+	"github.com/microsoft/go-sqlcmd/internal/databaseurl"
+	"github.com/microsoft/go-sqlcmd/pkg/mssqlcontainer/ingest/extract"
+	"github.com/microsoft/go-sqlcmd/pkg/mssqlcontainer/ingest/location"
+	"github.com/microsoft/go-sqlcmd/pkg/mssqlcontainer/ingest/mechanism"
+	"path/filepath"
+	"strings"
+)
+
+type ingest struct {
+	url         *databaseurl.DatabaseUrl
+	location    location.Location
+	controller  *container.Controller
+	mechanism   mechanism.Mechanism
+	options     mechanism.BringOnlineOptions
+	extractor   extract.Extractor
+	containerId string
+	query       func(text string)
+}
+
+func (i *ingest) IsExtractionNeeded() bool {
+	i.extractor = extract.NewExtractor(i.url.FileExtension, i.controller)
+	if i.extractor == nil {
+		return false
+	} else {
+		return true
+	}
+}
+
+func (i *ingest) IsRemoteUrl() bool {
+	return !i.location.IsLocal()
+}
+
+func (i *ingest) UrlFilename() string {
+	return i.url.Filename
+}
+
+func (i *ingest) OnlineMethod() string {
+	return i.mechanism.Name()
+}
+
+func (i *ingest) DatabaseName() string {
+	return i.url.DatabaseName
+}
+
+func (i *ingest) IsValidScheme() bool {
+	for _, s := range i.location.ValidSchemes() {
+		if s == i.url.Scheme {
+			return true
+		}
+	}
+	return false
+}
+
+func (i *ingest) CopyToContainer(containerId string) {
+	destFolder := "/var/opt/mssql/backup"
+
+	if i.mechanism != nil {
+		destFolder = i.mechanism.CopyToLocation()
+	}
+	if i.location == nil {
+		panic("location is nil, did you call NewIngest()?")
+	}
+
+	i.containerId = containerId
+	i.location.CopyToContainer(containerId, destFolder)
+	i.options.Filename = i.url.Filename
+
+	if i.options.Filename == "" {
+		panic("filename is empty")
+	}
+}
+
+func (i *ingest) Extract() {
+	if i.extractor == nil {
+		panic("extractor is nil")
+	}
+
+	if !i.extractor.IsInstalled(i.containerId) {
+		i.extractor.Install()
+	}
+
+	i.options.Filename, i.options.LdfFilename =
+		i.extractor.Extract(i.url.Filename, "/var/opt/mssql/data")
+
+	if i.mechanism == nil {
+		ext := strings.TrimLeft(filepath.Ext(i.options.Filename), ".")
+		i.mechanism = mechanism.NewMechanismByFileExt(ext, i.controller)
+	}
+}
+
+func (i *ingest) BringOnline(query func(string), username string, password string) {
+	if i.mechanism.Name() != "git" {
+		if i.options.Filename == "" {
+			panic("filename is empty, did you call CopyToContainer()?")
+		}
+		if query == nil {
+			panic("query is nil")
+		}
+	} else {
+		i.options.Filename = i.url.String()
+	}
+	if i.mechanism == nil {
+		panic("mechanism is nil")
+	}
+
+	i.query = query
+	i.options.Username = username
+	i.options.Password = password
+	i.mechanism.BringOnline(i.url.DatabaseNameAsTsqlIdentifier, i.containerId, i.query, i.options)
+
+	if i.mechanism.Name() != "git" {
+		i.setDefaultDatabase(username)
+	}
+}
+
+func (i *ingest) setDefaultDatabase(username string) {
+	if i.query == nil {
+		panic("query is nil, did you call BringOnline()?")
+	}
+
+	alterDefaultDb := fmt.Sprintf(
+		"ALTER LOGIN [%s] WITH DEFAULT_DATABASE = [%s]",
+		username,
+		i.url.DatabaseNameAsNonTsqlIdentifier)
+	i.query(alterDefaultDb)
+}
+
+func (i *ingest) IsValidFileExtension() bool {
+	for _, m := range mechanism.FileTypes() {
+		if m == i.url.FileExtension {
+			return true
+		}
+	}
+	for _, e := range extract.FileTypes() {
+		if e == i.url.FileExtension {
+			return true
+		}
+	}
+	return false
+}
+
+func (i *ingest) SourceFileExists() bool {
+	return i.location.Exists()
+}
+
+func (i *ingest) UserProvidedFileExt() string {
+	return i.url.FileExtension
+}
+
+func (i *ingest) ValidSchemes() []string {
+	return i.location.ValidSchemes()
+}
diff --git a/pkg/mssqlcontainer/ingest/interface.go b/pkg/mssqlcontainer/ingest/interface.go
new file mode 100644
index 00000000..a8c88af0
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/interface.go
@@ -0,0 +1,20 @@
+package ingest
+
+type Ingest interface {
+	IsRemoteUrl() bool
+	IsValidScheme() bool
+	IsValidFileExtension() bool
+	IsExtractionNeeded() bool
+
+	SourceFileExists() bool
+	DatabaseName() string
+	UrlFilename() string
+	OnlineMethod() string
+	UserProvidedFileExt() string
+
+	CopyToContainer(containerId string)
+	Extract()
+	BringOnline(query func(string), username string, password string)
+
+	ValidSchemes() []string
+}
diff --git a/pkg/mssqlcontainer/ingest/location/factory.go b/pkg/mssqlcontainer/ingest/location/factory.go
new file mode 100644
index 00000000..4ccb4768
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/location/factory.go
@@ -0,0 +1,19 @@
+package location
+
+import (
+	"github.com/microsoft/go-sqlcmd/internal/container"
+)
+
+func NewLocation(isLocal bool, uri string, controller *container.Controller) Location {
+	if isLocal {
+		return local{
+			uri:        uri,
+			controller: controller,
+		}
+	} else {
+		return remote{
+			uri:        uri,
+			controller: controller,
+		}
+	}
+}
diff --git a/pkg/mssqlcontainer/ingest/location/interface.go b/pkg/mssqlcontainer/ingest/location/interface.go
new file mode 100644
index 00000000..b101b054
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/location/interface.go
@@ -0,0 +1,8 @@
+package location
+
+type Location interface {
+	Exists() bool
+	IsLocal() bool
+	CopyToContainer(containerId string, destFolder string)
+	ValidSchemes() []string
+}
diff --git a/pkg/mssqlcontainer/ingest/location/local.go b/pkg/mssqlcontainer/ingest/location/local.go
new file mode 100644
index 00000000..215085c4
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/location/local.go
@@ -0,0 +1,51 @@
+package location
+
+import (
+	"github.com/microsoft/go-sqlcmd/internal/container"
+	"github.com/microsoft/go-sqlcmd/internal/io/file"
+	"path/filepath"
+)
+
+type local struct {
+	uri        string
+	controller *container.Controller
+}
+
+func (l local) Exists() bool {
+	return file.Exists(l.uri)
+}
+
+func (l local) IsLocal() bool {
+	return true
+}
+
+func (l local) ValidSchemes() []string {
+	return []string{"file"}
+}
+
+func (l local) CopyToContainer(containerId string, destFolder string) {
+	l.controller.RunCmdInContainer(
+		containerId,
+		[]string{"mkdir", "-p", destFolder},
+		container.ExecOptions{User: "root"})
+
+	l.controller.CopyFile(
+		containerId,
+		l.uri,
+		destFolder,
+	)
+
+	_, filename := filepath.Split(l.uri)
+
+	l.controller.RunCmdInContainer(
+		containerId,
+		[]string{"chown", "mssql:root", destFolder + "/" + filename},
+		container.ExecOptions{User: "root"},
+	)
+
+	l.controller.RunCmdInContainer(
+		containerId,
+		[]string{"chmod", "-o-r-u+rw-g+r", destFolder + "/" + filename},
+		container.ExecOptions{User: "root"},
+	)
+}
diff --git a/pkg/mssqlcontainer/ingest/location/remote.go b/pkg/mssqlcontainer/ingest/location/remote.go
new file mode 100644
index 00000000..23c5dfbc
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/location/remote.go
@@ -0,0 +1,32 @@
+package location
+
+import (
+	"github.com/microsoft/go-sqlcmd/internal/container"
+	"github.com/microsoft/go-sqlcmd/internal/http"
+)
+
+type remote struct {
+	uri        string
+	controller *container.Controller
+}
+
+func (l remote) IsLocal() bool {
+	return false
+}
+
+func (l remote) ValidSchemes() []string {
+	return []string{"https", "http"}
+}
+
+// Verify the file exists at the URL
+func (l remote) Exists() bool {
+	return http.UrlExists(l.uri)
+}
+
+func (l remote) CopyToContainer(containerId string, destFolder string) {
+	l.controller.DownloadFile(
+		containerId,
+		l.uri,
+		destFolder,
+	)
+}
diff --git a/pkg/mssqlcontainer/ingest/mechanism/attach.go b/pkg/mssqlcontainer/ingest/mechanism/attach.go
new file mode 100644
index 00000000..d6803211
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/mechanism/attach.go
@@ -0,0 +1,68 @@
+package mechanism
+
+import (
+	"fmt"
+	"github.com/microsoft/go-sqlcmd/internal/container"
+)
+
+type attach struct {
+	controller  *container.Controller
+	containerId string
+}
+
+func (m *attach) Initialize(controller *container.Controller) {
+	m.controller = controller
+}
+
+func (m *attach) CopyToLocation() string {
+	return "/var/opt/mssql/data"
+}
+
+func (m *attach) Name() string {
+	return "attach"
+}
+
+func (m *attach) FileTypes() []string {
+	return []string{"mdf"}
+}
+
+func (m *attach) BringOnline(
+	databaseName string,
+	containerId string,
+	query func(string),
+	options BringOnlineOptions,
+) {
+	text := `SET NOCOUNT ON; `
+
+	m.containerId = containerId
+	m.setFilePermissions(m.CopyToLocation() + "/" + options.Filename)
+	if options.LdfFilename == "" {
+		text += `CREATE DATABASE [%s] ON (FILENAME = '%s/%s') FOR ATTACH;`
+		query(fmt.Sprintf(
+			text,
+			databaseName,
+			m.CopyToLocation(),
+			options.Filename,
+		))
+	} else {
+		m.setFilePermissions(m.CopyToLocation() + "/" + options.LdfFilename)
+		text += `CREATE DATABASE [%s] ON (FILENAME = '%s/%s'), (FILENAME = '%s/%s') FOR ATTACH;`
+		query(fmt.Sprintf(
+			text,
+			databaseName,
+			m.CopyToLocation(),
+			options.Filename,
+			m.CopyToLocation(),
+			options.LdfFilename,
+		))
+	}
+}
+
+func (m *attach) setFilePermissions(filename string) {
+	m.RunCommand([]string{"chown", "mssql:root", filename})
+	m.RunCommand([]string{"chmod", "-o-r-u+rw-g+r", filename})
+}
+
+func (m *attach) RunCommand(s []string) ([]byte, []byte, int) {
+	return m.controller.RunCmdInContainer(m.containerId, s, container.ExecOptions{})
+}
diff --git a/pkg/mssqlcontainer/ingest/mechanism/dacfx.go b/pkg/mssqlcontainer/ingest/mechanism/dacfx.go
new file mode 100644
index 00000000..e0039bd8
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/mechanism/dacfx.go
@@ -0,0 +1,119 @@
+package mechanism
+
+import (
+	"fmt"
+	"github.com/microsoft/go-sqlcmd/internal/container"
+)
+
+type dacfx struct {
+	controller  *container.Controller
+	containerId string
+}
+
+func (m *dacfx) Initialize(controller *container.Controller) {
+	m.controller = controller
+}
+
+func (m *dacfx) CopyToLocation() string {
+	return "/var/opt/mssql/backup"
+}
+
+func (m *dacfx) Name() string {
+	return "dacfx"
+}
+
+func (m *dacfx) FileTypes() []string {
+	return []string{"bacpac", "dacpac"}
+}
+
+func (m *dacfx) BringOnline(
+	databaseName string,
+	containerId string,
+	query func(string),
+	options BringOnlineOptions,
+) {
+	m.containerId = containerId
+	m.installSqlPackage()
+	m.setDefaultDatabaseToMaster(options.Username, query)
+
+	_, stderr, _ := m.RunCommand([]string{
+		"/home/mssql/.dotnet/tools/sqlpackage",
+		"/Diagnostics:true",
+		"/Action:import",
+		"/SourceFile:" + m.CopyToLocation() + "/" + options.Filename,
+		"/TargetServerName:localhost",
+		"/TargetDatabaseName:" + databaseName,
+		"/TargetTrustServerCertificate:true",
+		"/TargetUser:" + options.Username,
+		"/TargetPassword:" + options.Password,
+	})
+
+	if len(stderr) == 0 {
+		// Remove the source bacpac file
+		m.RunCommandAsRoot([]string{"rm", m.CopyToLocation() + "/" + options.Filename})
+	}
+}
+
+func (m *dacfx) setDefaultDatabaseToMaster(username string, query func(string)) {
+	alterDefaultDb := fmt.Sprintf(
+		"ALTER LOGIN [%s] WITH DEFAULT_DATABASE = [%s]",
+		username,
+		"master")
+	query(alterDefaultDb)
+}
+
+func (m *dacfx) installSqlPackage() {
+	if m.controller == nil {
+		panic("controller is nil")
+	}
+
+	m.installDotNet()
+
+	// Check if sqlpackage is installed, if not, install it
+	_, stderr, _ := m.RunCommand([]string{"/home/mssql/.dotnet/tools/sqlpackage", "/version"})
+	if len(stderr) > 0 {
+		m.RunCommand([]string{"/opt/dotnet/dotnet", "tool", "install", "-g", "microsoft.sqlpackage"})
+	}
+}
+
+func (m *dacfx) installDotNet() {
+	// Check if dotnet is installed, if not, install it
+	_, stderr, _ := m.RunCommand([]string{"/opt/dotnet/dotnet", "--version"})
+	if len(stderr) > 0 {
+		// Download dotnet-install.sh and run it
+		m.RunCommand([]string{"wget", "https://dot.net/v1/dotnet-install.sh", "-O", "/tmp/dotnet-install.sh"})
+		m.RunCommand([]string{"chmod", "+x", "/tmp/dotnet-install.sh"})
+		m.RunCommand([]string{"/tmp/dotnet-install.sh", "--install-dir", "/opt/dotnet"})
+
+		// The SQL Server container doesn't have a /home/mssql directory (which is ~), this
+		// causes all sorts of things to break in the container that expect to create .toolname folders
+		m.RunCommandAsRoot([]string{"mkdir", "-p", "/home/mssql"})
+		m.RunCommandAsRoot([]string{"chown", "mssql:root", "/home/mssql"})
+
+		// Add dotnet to the path
+		m.AddTextLineToFile(
+			"export DOTNET_ROOT=/opt/dotnet",
+			"/home/mssql/.bashrc",
+		)
+		m.AddTextLineToFile(
+			"export PATH=$PATH:$DOTNET_ROOT:/home/mssql/.dotnet/tools",
+			"/home/mssql/.bashrc",
+		)
+	}
+}
+
+func (m *dacfx) AddTextLineToFile(text string, file string) ([]byte, []byte, int) {
+	return m.RunCommand([]string{"/bin/bash", "-c", fmt.Sprintf("echo '%v' >> %v", text, file)})
+}
+
+func (m *dacfx) RunCommand(s []string) ([]byte, []byte, int) {
+	return m.controller.RunCmdInContainer(m.containerId, s, container.ExecOptions{
+		Env: []string{"DOTNET_ROOT=/opt/dotnet"},
+	})
+}
+
+func (m *dacfx) RunCommandAsRoot(s []string) ([]byte, []byte, int) {
+	return m.controller.RunCmdInContainer(m.containerId, s, container.ExecOptions{
+		User: "root",
+	})
+}
diff --git a/pkg/mssqlcontainer/ingest/mechanism/error.go b/pkg/mssqlcontainer/ingest/mechanism/error.go
new file mode 100644
index 00000000..1ad16cc3
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/mechanism/error.go
@@ -0,0 +1,7 @@
+package mechanism
+
+var errorCallback func(err error)
+
+func checkErr(err error) {
+	errorCallback(err)
+}
diff --git a/pkg/mssqlcontainer/ingest/mechanism/factory.go b/pkg/mssqlcontainer/ingest/mechanism/factory.go
new file mode 100644
index 00000000..dc888772
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/mechanism/factory.go
@@ -0,0 +1,46 @@
+package mechanism
+
+import (
+	"github.com/microsoft/go-sqlcmd/internal/container"
+)
+
+func NewMechanism(fileExtension string, name string, controller *container.Controller) Mechanism {
+	trace("NewMechanism: fileExtension = %q, name = %q", fileExtension, name)
+	for _, m := range mechanisms {
+		if m.Name() == name {
+			m.Initialize(controller)
+
+			trace("Returning: %q", m.Name())
+
+			return m
+		}
+	}
+
+	return NewMechanismByFileExt(fileExtension, controller)
+}
+
+func NewMechanismByFileExt(fileExtension string, controller *container.Controller) Mechanism {
+	for _, m := range mechanisms {
+		for _, ext := range m.FileTypes() {
+			if ext == fileExtension {
+				m.Initialize(controller)
+
+				trace("Returning: %q", m.Name())
+
+				return m
+			}
+		}
+	}
+
+	trace("No mechanism found for file extension %q", fileExtension)
+
+	return nil
+}
+
+func Mechanisms() []string {
+	m := []string{}
+	for _, mechanism := range mechanisms {
+		m = append(m, mechanism.Name())
+	}
+	return m
+}
diff --git a/pkg/mssqlcontainer/ingest/mechanism/git.go b/pkg/mssqlcontainer/ingest/mechanism/git.go
new file mode 100644
index 00000000..c151e87c
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/mechanism/git.go
@@ -0,0 +1,93 @@
+package mechanism
+
+import (
+	"fmt"
+	"github.com/microsoft/go-sqlcmd/internal/container"
+	"gopkg.in/src-d/go-billy.v4"
+	"gopkg.in/src-d/go-billy.v4/osfs"
+	"gopkg.in/src-d/go-git.v4/storage/filesystem"
+	"os"
+
+	git "gopkg.in/src-d/go-git.v4"
+)
+
+type git2 struct {
+}
+
+func (m *git2) Initialize(controller *container.Controller) {
+}
+
+func (m *git2) CopyToLocation() string {
+	return "/var/opt/mssql/backup"
+}
+
+func (m *git2) Name() string {
+	return "git"
+}
+
+func (m *git2) FileTypes() []string {
+	return []string{"git"}
+}
+
+func (m *git2) BringOnline(databaseName string, _ string, query func(string), options BringOnlineOptions) {
+	if options.Filename == "" {
+		panic("Filename is required for git")
+	}
+	/*
+		if databaseName == "" {
+			panic("databaseName is required for git")
+		}
+	*/
+
+	url := options.Filename
+	dir := "."
+
+	// If there are any files in the current directory then error
+	// as we don't want to overwrite any files
+	entries, err := os.ReadDir(dir)
+	checkErr(err)
+
+	alreadyCloned := false
+
+	if len(entries) > 0 {
+		if _, err := os.Stat(".git"); err == nil {
+			var fs billy.Filesystem
+			fs = osfs.New(".git")
+
+			st := filesystem.NewStorage(fs, nil)
+			c, err := st.Config()
+			if err != nil {
+				panic(err)
+			}
+
+			for _, remote := range c.Remotes {
+				for _, u := range remote.URLs {
+					if url == u {
+						alreadyCloned = true
+					}
+				}
+			}
+		}
+
+		if !alreadyCloned {
+			fmt.Println("Current directory is not empty, cannot clone .git repo. Run sqlcmd again from an empty directory, or remove the --use switch.")
+
+			os.Exit(1)
+		}
+	}
+
+	if !alreadyCloned {
+		_, err = git.PlainClone(dir, false, &git.CloneOptions{
+			URL: url,
+		})
+
+		if err != nil {
+			fmt.Println("Error while cloning repository:", err)
+			os.Exit(1)
+		}
+
+		fmt.Println("Repository cloned successfully")
+	} else {
+		fmt.Println("Repository already cloned, continuing...")
+	}
+}
diff --git a/pkg/mssqlcontainer/ingest/mechanism/initialize.go b/pkg/mssqlcontainer/ingest/mechanism/initialize.go
new file mode 100644
index 00000000..e14358b2
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/mechanism/initialize.go
@@ -0,0 +1,19 @@
+package mechanism
+
+func init() {
+	Initialize(
+		func(err error) {
+			if err != nil {
+				panic(err)
+			}
+		},
+		func(format string, a ...any) {})
+}
+
+func Initialize(
+	errorHandler func(err error),
+	traceHandler func(format string, a ...any)) {
+
+	errorCallback = errorHandler
+	traceCallback = traceHandler
+}
diff --git a/pkg/mssqlcontainer/ingest/mechanism/interface.go b/pkg/mssqlcontainer/ingest/mechanism/interface.go
new file mode 100644
index 00000000..264fa3e1
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/mechanism/interface.go
@@ -0,0 +1,11 @@
+package mechanism
+
+import "github.com/microsoft/go-sqlcmd/internal/container"
+
+type Mechanism interface {
+	FileTypes() []string
+	Initialize(controller *container.Controller)
+	CopyToLocation() string
+	BringOnline(databaseName string, containerId string, query func(string), options BringOnlineOptions)
+	Name() string
+}
diff --git a/pkg/mssqlcontainer/ingest/mechanism/mechanism.go b/pkg/mssqlcontainer/ingest/mechanism/mechanism.go
new file mode 100644
index 00000000..39150ba1
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/mechanism/mechanism.go
@@ -0,0 +1,17 @@
+package mechanism
+
+var mechanisms = []Mechanism{
+	&attach{},
+	&dacfx{},
+	&git2{},
+	&restore{},
+	&script{},
+}
+
+func FileTypes() []string {
+	fileTypes := []string{}
+	for _, m := range mechanisms {
+		fileTypes = append(fileTypes, m.FileTypes()...)
+	}
+	return fileTypes
+}
diff --git a/pkg/mssqlcontainer/ingest/mechanism/options.go b/pkg/mssqlcontainer/ingest/mechanism/options.go
new file mode 100644
index 00000000..dad738c0
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/mechanism/options.go
@@ -0,0 +1,9 @@
+package mechanism
+
+type BringOnlineOptions struct {
+	Username string
+	Password string
+
+	Filename    string
+	LdfFilename string
+}
diff --git a/pkg/mssqlcontainer/ingest/mechanism/restore.go b/pkg/mssqlcontainer/ingest/mechanism/restore.go
new file mode 100644
index 00000000..4e458f9c
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/mechanism/restore.go
@@ -0,0 +1,85 @@
+package mechanism
+
+import (
+	"fmt"
+	"github.com/microsoft/go-sqlcmd/internal/container"
+)
+
+type restore struct {
+}
+
+func (m *restore) Initialize(controller *container.Controller) {
+}
+
+func (m *restore) CopyToLocation() string {
+	return "/var/opt/mssql/backup"
+}
+
+func (m *restore) Name() string {
+	return "restore"
+}
+
+func (m *restore) FileTypes() []string {
+	return []string{"bak"}
+}
+
+func (m *restore) BringOnline(databaseName string, _ string, query func(string), options BringOnlineOptions) {
+	if options.Filename == "" {
+		panic("Filename is required for restore")
+	}
+	if databaseName == "" {
+		panic("databaseName is required for restore")
+	}
+
+	query(fmt.Sprintf(
+		m.restoreStatement(),
+		m.CopyToLocation(),
+		options.Filename,
+		databaseName,
+		m.CopyToLocation(),
+		options.Filename,
+	))
+}
+
+func (m *restore) restoreStatement() string {
+	return `SET NOCOUNT ON;
+
+-- Build a SQL Statement to restore any .bak file to the Linux filesystem
+DECLARE @sql NVARCHAR(max)
+
+-- This table definition works since SQL Server 2017, therefore 
+-- works for all SQL Server containers (which started in 2017)
+DECLARE @fileListTable TABLE (
+    [LogicalName]           NVARCHAR(128),
+    [PhysicalName]          NVARCHAR(260),
+    [Type]                  CHAR(1),
+    [FileGroupName]         NVARCHAR(128),
+    [Size]                  NUMERIC(20,0),
+    [MaxSize]               NUMERIC(20,0),
+    [FileID]                BIGINT,
+    [CreateLSN]             NUMERIC(25,0),
+    [DropLSN]               NUMERIC(25,0),
+    [UniqueID]              UNIQUEIDENTIFIER,
+    [ReadOnlyLSN]           NUMERIC(25,0),
+    [ReadWriteLSN]          NUMERIC(25,0),
+    [BackupSizeInBytes]     BIGINT,
+    [SourceBlockSize]       INT,
+    [FileGroupID]           INT,
+    [LogGroupGUID]          UNIQUEIDENTIFIER,
+    [DifferentialBaseLSN]   NUMERIC(25,0),
+    [DifferentialBaseGUID]  UNIQUEIDENTIFIER,
+    [IsReadOnly]            BIT,
+    [IsPresent]             BIT,
+    [TDEThumbprint]         VARBINARY(32),
+    [SnapshotURL]           NVARCHAR(360)
+)
+
+INSERT INTO @fileListTable
+EXEC('RESTORE FILELISTONLY FROM DISK = ''%s/%s''')
+SET @sql = 'RESTORE DATABASE [%s] FROM DISK = ''%s/%s'' WITH '
+SELECT @sql = @sql + char(13) + ' MOVE ''' + LogicalName + ''' TO ''/var/opt/mssql/data/' + LogicalName + '.' + RIGHT(PhysicalName,CHARINDEX('\',PhysicalName)) + ''','
+FROM @fileListTable
+WHERE IsPresent = 1
+SET @sql = SUBSTRING(@sql, 1, LEN(@sql)-1)
+EXEC(@sql)`
+}
diff --git a/pkg/mssqlcontainer/ingest/mechanism/script.go b/pkg/mssqlcontainer/ingest/mechanism/script.go
new file mode 100644
index 00000000..4c0b5939
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/mechanism/script.go
@@ -0,0 +1,31 @@
+package mechanism
+
+import (
+	"github.com/microsoft/go-sqlcmd/internal/container"
+)
+
+type script struct {
+}
+
+func (m *script) Initialize(controller *container.Controller) {
+}
+
+func (m *script) CopyToLocation() string {
+	return "/var/opt/mssql/backup"
+}
+
+func (m *script) Name() string {
+	return "script"
+}
+
+func (m *script) FileTypes() []string {
+	return []string{"sql"}
+}
+
+func (m *script) BringOnline(databaseName string, _ string, query func(string), options BringOnlineOptions) {
+	if options.Filename == "" {
+		panic("Filename is required for restore")
+	}
+
+	query(options.Filename)
+}
diff --git a/pkg/mssqlcontainer/ingest/mechanism/trace.go b/pkg/mssqlcontainer/ingest/mechanism/trace.go
new file mode 100644
index 00000000..5917db05
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/mechanism/trace.go
@@ -0,0 +1,7 @@
+package mechanism
+
+var traceCallback func(format string, a ...any)
+
+func trace(format string, a ...any) {
+	traceCallback(format, a...)
+}
diff --git a/pkg/mssqlcontainer/ingest/type.go b/pkg/mssqlcontainer/ingest/type.go
new file mode 100644
index 00000000..9eb04eea
--- /dev/null
+++ b/pkg/mssqlcontainer/ingest/type.go
@@ -0,0 +1,6 @@
+package ingest
+
+type IngestOptions struct {
+	Mechanism    string
+	DatabaseName string
+}
diff --git a/pkg/mssqlcontainer/initialize.go b/pkg/mssqlcontainer/initialize.go
new file mode 100644
index 00000000..c0b53cab
--- /dev/null
+++ b/pkg/mssqlcontainer/initialize.go
@@ -0,0 +1,19 @@
+package mssqlcontainer
+
+import "github.com/microsoft/go-sqlcmd/pkg/mssqlcontainer/ingest/mechanism"
+
+type InitializeOptions struct {
+	ErrorHandler func(error)
+	TraceHandler func(format string, a ...any)
+}
+
+func Initialize(options InitializeOptions) {
+	if options.ErrorHandler == nil {
+		panic("ErrorHandler is nil")
+	}
+	if options.TraceHandler == nil {
+		panic("TraceHandler is nil")
+	}
+
+	mechanism.Initialize(options.ErrorHandler, options.TraceHandler)
+}