Skip to content

Conversation

stuartpa
Copy link
Collaborator

Main package (main.go) is the entry point for the sqlcmd CLI application.

To follow the flow of this code:

  1. enter through main.go, (TEMPORARY: decision made whether to invoke the modern
    cobra CLI
  2. Then cmd/cmd.go, see the init() func New the Root cmd (and all its
    subcommands)
  3. The command-line is then parsed and internal.Initialize() runs (with
    the logging level, config file path, error handling and trace support passed
    into internal packages)
  4. Now go to the cmd/root/… folder structure, and read the DefineCommand
    function for the command (sqlcmd install, sqlcmd query etc.) being run
  5. Each cmd/root/... command has a run method that performs the action
  6. All the commands (cmd/root/…) use the /internal packages to abstract from error
    handling and trace (non-localized) logging (as can be seen from the import
    for each command (in /cmd/root/...)).

This code follows the Go Style Guide

@stuartpa stuartpa force-pushed the stuartpa/modern-cli branch 6 times, most recently from cb6c769 to 9e5e33f Compare November 22, 2022 14:19
// main is the entrypoint function for sqlcmd.
//
// TEMPORARY: While we have both the new cobra and old kong CLI
// implementations, main decides which CLI framework to use
Copy link
Collaborator

@shueybubbles shueybubbles Nov 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i suspect some version of this code is permanent. We aren't likely to reproduce all the legacy switches in the new CLI are we? Perhaps we should use an environment variable to only allow modern mode where it uses query as the default command but only with whatever switches that command supports.
#Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can solve the problem of the legacy switches by writing a small "canonicalizer" called at the start of main, which scans through the supplied args, and modifies them to be parsable by Cobra, then call cobra.command.SetArgs to override what's passed in from env.

I'll look at this staight after this PR is in. I've entered an issue and assigned it to myself.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added to:

#154

LoggingLevel: 2,
}

config.SetFileName(configFilename)
Copy link
Collaborator

@shueybubbles shueybubbles Nov 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

configFilename

configFilename isn't assigned any value. Is it supposed to be nul for the default with an option for the user to pass it along as a command line parameter or environment variable? #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's set the default value for the --sqlconfig flag, so it's ~.sqlcmd\sqlconfig (this func is called by the cobra framework after the flag defaults have been set

// to make progress. displayHints is injected into dependencies (helpers etc.)
func displayHints(hints []string) {
if len(hints) > 0 {
output.Infof("\nHINT:")
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\n

are we going to use \n on all platforms? Do all the output renderers on Windows do the right thing with it? #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I don't know if all the output renderers on Windows do the right thing. Should i replace this with SqlcmdEol to be safe?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've replaced all to sqlcmd.SqlcmdEol for next PR (in sheuybubble/bcp) (which I'll then need to work out how to inject, rather than having everything take an import on sqlcmd package)

func verifyConfigIsEmpty(t *testing.T) {
if !config.IsEmpty() {
bytes := output.Struct(config.GetRedactedConfig(true))
t.Errorf("Config is not empty. Content of config file:\n%s\nConfig file used:%s",
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

t.Errorf

github.com/stretchr/testify/assert is a pretty good assert library #Resolved

Copy link
Collaborator Author

@stuartpa stuartpa Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added to:

#171

Should tests standarize on a specific assert library e.g. github.com/stretchr/testify/assert

Also in the Go Style Guide, what does "invalid local style" / "Use of assertion-based testing libraries" mean:

https://google.github.io/styleguide/go/guide#local-consistency

"
Examples of invalid local style considerations:

Line length restrictions for code
Use of assertion-based testing libraries
"

The following PR will refactor these tests to what ever we decide

}()
cmd.Execute()
}
cmd.Execute()
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is silence considered success? The lack of asserts makes it hard to tell what's being tested. #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, I was focusing on getting to 100% code coverage, but now I need to go back and actually validate the test results. To keep the size of the PR managable I'll follow up now with a Test focused PR.

func (c *Cmd) CheckErr(err error) {
// If we are in a unit test driver, then panic, otherwise pass down to cobra.CheckErr
if strings.HasSuffix(os.Args[0], ".test") || // are we in go test?
(len(os.Args) > 1 && os.Args[1] == "-test.v") { // are we in goland unittest?
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a flimsy convention considering someone else could write a new test framework etc. Can these test frameworks not set some global state instead of relying on command line parsing? #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added issue to track

#168

- container:
id: 0e698e65e19d9c
image: mcr.microsoft.com/mssql/server:2022-latest
endpoint:
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

endpoint:

Let's pretend we will have scenarios involving non-TDS endpoints (eg PowerBI, postgres, SQL AS/RS/IS). Will adding those just be a matter of adding an optional "type" field to "endpoint" to identify it as needing a different driver?

theoretically sqlcmd query "some query" could run any arbitrary query language if the query command were to route it to a package that knows how to connect to the non-TDS endpoint. #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the growth mind set here! 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to this, to be able the best connection strings from:

sqlcmd config connection-strings

Do we need to keep some metadata for the endpoint that tells us it is mssql-server, or mssql-edge, or azsqldb, or postgres! So we know how to generate a good connection string for it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added:

#173

Should ~/.sqlcmd/sqlconfig keep metadata on what the endpoint is

Image string `mapstructure:"image"`
}

type AssetDetails struct {
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

t {

where does one find instructions for using mapstructure and yaml attributes? #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're called "struct tags", and you're right to ask, because they are barely documented in the golang spec:

This answers to this stackoverflow question seems to do the best at gathering the links to learn more:

https://stackoverflow.com/questions/10858787/what-are-the-uses-for-struct-tags-in-go

err, _ := printf("Hope this works)
checkErr(err)
- Do not have an internal package take a dependency on another internal package
unless they are building on each other, instead inject the needed capability in the
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey are building o

this is very Electron-ish. Given the lack of a MEF equivalent it seems good. #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

var loggingLevel verbosity.Enum
var runningUnitTests bool

var standardWriteCloser io.WriteCloser
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

andardWriteCloser io.WriteCloser

don't we need separate error and stdout channels? Most apps don't write everything to stdout, they use stderr too.

Legacy Sqlcmd also lets the user change where stdout and stderr write during app execution. Do you expect we'll keep that functionality with the new API such that this channel needs to support dynamic reassignment? #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started with two channels, but then didn't know what semantics to apply to do the separation, so I removed stderr to keep this PR contained. I've added an Issue, and assigned to myself:

#169

func (f *Xml) Serialize(in interface{}) (bytes []byte) {
var err error

bytes, err = xml.MarshalIndent(in, "", " ")
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

")

2 spaces of indent should be sufficient #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done for both this one (XML) and Json


package verbosity

type Enum int
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enum

how about Level or even Verbosity ? Enum is the name of the mechanism, not the semantic of the type. #Resolved

Copy link
Collaborator Author

@stuartpa stuartpa Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get what you're saying, but I've done it this way so it looks like this when using it:

output.New(verbosity.Enum(options.LoggingLevel))

golang uses the package name to add the context.

But agree this might be me trying to make it familiar to a C# user.

I'll change it to verbosity.Level (I'll do this in the next PR)

var sb strings.Builder
for _, v := range vars {
sb.WriteString(envVarCommand())
sb.WriteString(cliQuoteIdentifier() + v + cliQuoteIdentifier())
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there really value in splitting these up instead of having a single function that takes the variable name?
This function could accept a map instead of an array, and pass the keys as the var names and the values as the var values. The pal function that exports the environment variable would parse the values and do any escaping of special characters.
#Resolved

Copy link
Collaborator Author

@stuartpa stuartpa Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value is code coverage reporting 😄 . I'd like to promote a culture that a lot of the files in this repo are at 100% "go test" code coverage (already "most" files are), (I want to add a "100PercentClub" build pipeline check so a list (or source file metadata tag?) of all files that are at 100% code coverage to never drop below 100%).

There is 2nd reason, the other approach is to use runtime.GOOS == "windows" etc., but this generates a style failure in go vet (and IDEs), because it is flagged as "unreachable" so you never get the little "green tick" for the source file being 100% good.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added issue:

#172

Protect source files already at 100% code coverage - add "100PercentClub" build pipeline check to

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added comment to:

#171

"Another topic around coding guidelines around Platform Abstraction, should we use runtime.GOOS == "windows" etc. or the filename_windows, filename_darwin, filename_linux approach:"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll refactor func to take a map in next PR

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like using the file_linux (or build tags) approach so per-platform code isn't intermixed.
I wasn't asking to merge all the functions into one using the GOOS=="windows" check, I just meant to merge the envVarCommand() and cliQuoteIdentifier() concatenations into a single function. That function would take both the name and the value of the variable so it can properly escape the value for the current platform.

}

inRune := []rune(password.String())
mathRand.Shuffle(len(inRune), func(i, j int) {
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interview question - implement this function! #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄

})
}

func Initialize(handler func(err error)) {
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handler func(err error)

would it be a bit cleaner to define an interface or type for this handler func instead of using raw func(err error) everywhere? #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a more general issue to cover this area of style guidelines. I think I agree with you (this is what I'd do in c#), but would using interface encourage tighter coupling. Is the idomatic golang apporach for the caller to define the interface.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

)
conn, err := net.DialTimeout(
"tcp",
net.JoinHostPort("localhost", strconv.Itoa(port)),
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

net.JoinHostPort("localhost", strconv.Itoa(port)),

just call this once and pass it to both trace and dial #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (added to the follow on PR in shueybubbles/bcp)

}

if c.authType == "basic" {
if os.Getenv("SQLCMD_PASSWORD") == "" {
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if os.Getenv("SQLCMD_PASSWORD") == "" {

we should prompt for the password on the console.
we could also allow reading it from redirected stdin

sqlcmd config add-user --username myuser < somefile.txt
#Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added issue and assigned to myself (to break this PR up a bit):

#167

func (c *ConnectionStrings) run() {
// connectionStringFormats borrowed from "portal.azure.com" "connection strings" pane
var connectionStringFormats = map[string]string{
"ADO.NET": "Server=tcp:%s,%d;Initial Catalog=%s;Persist Security Options=False;User ID=%s;Password=%s;MultipleActiveResultSets=False;Encode=True;TrustServerCertificate=False;Connection Timeout=30;",
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Encode=True

what is "Encode" ? #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more general issue to review these connection strings:

"Review sqlcmd config connection-strings"

#170

func (c *MssqlBase) Run() {
var imageName string

if !c.acceptEula && viper.GetString("ACCEPT_EULA") == "" {
Copy link
Collaborator

@shueybubbles shueybubbles Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if !c.acceptEula && viper.GetString("ACCEPT_EULA") == "

viper has a way to bind its config values to flags. Do you see us using that technique such that code like this would only have to check in viper? #ByDesign

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is I am using the viper feature to bind to env here ( err = viper.BindEnv("ACCEPT_EULA")), but no binding to the deep sub cmd flag. The reason I didn't do that is the flag is deep down in the hierarchy, e.g. sqlcmd install mssql --flag, which would be a long variable name, someting like SQLCMD_INSTALL_MSSQL_ACCEPT_EULA I think)

Copy link
Collaborator

@shueybubbles shueybubbles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@stuartpa
Copy link
Collaborator Author

Thanks for the all the PR feedback. I'll follow up with a quick PR to neaten the unit tests. (I was focused on getting most files to 100% code coverage, need to go back and validate/assert result output).


In reply to: 1196841042

@stuartpa stuartpa merged commit f1c0b5c into main Nov 29, 2022
@shueybubbles
Copy link
Collaborator

shueybubbles commented Nov 29, 2022 via email

@shueybubbles
Copy link
Collaborator

shueybubbles commented Nov 29, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants