-
Notifications
You must be signed in to change notification settings - Fork 63
/
main.go
165 lines (145 loc) · 5.5 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// Package main is the entrypoint for sqlcmd. This package first initializes
// a new instance of the Root cmd then checks if the new cobra-based
// command-line interface (CLI) should be used based on if the first argument provided
// by the user is a valid sub-command for the new CLI, if so it executes the
// new cobra CLI; otherwise, it falls back to the old kong-based CLI.
//go:generate go-winres make --file-version=git-tag --product-version=git-tag
package main
import (
"github.com/microsoft/go-sqlcmd/internal"
"github.com/microsoft/go-sqlcmd/internal/cmdparser"
"github.com/microsoft/go-sqlcmd/internal/cmdparser/dependency"
"github.com/microsoft/go-sqlcmd/internal/config"
"github.com/microsoft/go-sqlcmd/internal/io/file"
"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/sqlcmd"
"github.com/spf13/cobra"
"path"
"os"
legacyCmd "github.com/microsoft/go-sqlcmd/cmd/sqlcmd"
)
var rootCmd *Root
var outputter *output.Output
var version = "local-build" // overridden in pipeline builds with: -ldflags="-X main.version=$(VersionTag)"
// main is the entry point for the sqlcmd command line interface.
// It parses command line options and initializes the command parser.
// If the first argument is a modern CLI subcommand, the modern CLI is
// executed. Otherwise, the legacy CLI is executed.
func main() {
dependencies := dependency.Options{
Output: output.New(output.Options{
StandardWriter: os.Stdout,
ErrorHandler: checkErr,
HintHandler: displayHints})}
rootCmd = cmdparser.New[*Root](dependencies)
if isFirstArgModernCliSubCommand() {
cmdparser.Initialize(initializeCallback)
rootCmd.Execute()
} else {
initializeEnvVars()
legacyCmd.Execute(version)
}
}
// initializeEnvVars intializes SQLCMDSERVER, SQLCMDUSER and SQLCMDPASSWORD
// if the currentContext is set and if these env vars are not already set.
// In terms of precedence, command line switches/flags take higher precedence
// than env variables and env variables take higher precedence over config
// file info.
func initializeEnvVars() {
home, err := os.UserHomeDir()
// Special case, some shells don't have any home dir env var set, see:
// https://github.com/microsoft/go-sqlcmd/issues/279
// in this case, we early exit here and don't initialize anything, there is nothing
// else we can do
if err != nil {
return
}
// The only place we can check for the existence of the sqlconfig file is in the
// default location, because this code path is only used for the legacy kong CLI,
// if the sqlconfig file doesn't exist at the default location we just return so
// because the initializeCallback() function below will create the sqlconfig file,
// which legacy sqlcmd users might not want.
if !file.Exists(path.Join(home, ".sqlcmd", "sqlconfig")) {
return
}
initializeCallback()
if config.CurrentContextName() != "" {
server, username, password := config.GetCurrentContextInfo()
if os.Getenv("SQLCMDSERVER") == "" {
os.Setenv("SQLCMDSERVER", server)
}
// Username and password should come together, either from the environment
// variables set by the user before the sqlcmd process started, or from the sqlconfig
// for the current context, but if just the environment variable SQLCMDPASSWORD
// is set before the process starts we do not use it, if the user and password is set in sqlconfig.
if username != "" && password != "" { // If not trusted auth
if os.Getenv("SQLCMDUSER") == "" {
os.Setenv("SQLCMDUSER", username)
os.Setenv("SQLCMDPASSWORD", password)
}
}
}
}
// isFirstArgModernCliSubCommand is TEMPORARY code, to be removed when
// we remove the Kong based CLI
func isFirstArgModernCliSubCommand() (isNewCliCommand bool) {
if len(os.Args) > 1 {
if rootCmd.IsValidSubCommand(os.Args[1]) {
isNewCliCommand = true
}
}
return
}
// initializeCallback is called after the command line has been parsed and
// all values provided by the user are now available
func initializeCallback() {
// Assigns a new outputter now that we have the outputType and loggingLevel
// provided to us from the user
outputter = output.New(
output.Options{
StandardWriter: os.Stdout,
ErrorHandler: checkErr,
HintHandler: displayHints,
OutputType: rootCmd.outputType,
LoggingLevel: verbosity.Level(rootCmd.loggingLevel),
})
internal.Initialize(
internal.InitializeOptions{
ErrorHandler: checkErr,
TraceHandler: outputter.Tracef,
HintHandler: displayHints,
LineBreak: sqlcmd.SqlcmdEol,
})
config.SetFileName(rootCmd.configFilename)
config.Load()
}
// checkErr uses Cobra to check err, and halts the application if err is not
// nil. Pass (inject) checkErr into all dependencies (internal helpers etc.) as an
// errorHandler.
//
// To aid debugging issues, if the logging level is > 2 (e.g. --verbosity 3 or --verbosity 4), we
// panic which outputs a stacktrace.
func checkErr(err error) {
if rootCmd != nil && rootCmd.loggingLevel > 2 {
if err != nil {
panic(err)
}
} else {
cobra.CheckErr(err)
}
}
// displayHints displays helpful information on what the user should do next
// to make progress. displayHints is injected into dependencies (helpers etc.)
func displayHints(hints []string) {
if len(hints) > 0 {
outputter.Infof("%vHINT:", pal.LineBreak())
for i, hint := range hints {
outputter.Infof(" %d. %v", i+1, hint)
}
outputter.Infof("")
}
}