Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add db open command #72

Merged
merged 5 commits into from
Nov 26, 2019
Merged

Add db open command #72

merged 5 commits into from
Nov 26, 2019

Conversation

tangrufus
Copy link
Collaborator

@tangrufus tangrufus commented Sep 25, 2019

Open remote databases via SSH tunnels in your favourite database client.

Requirement

An database client on your local machine registered as mysql+ssh:// handler, e.g: TablePlus

How does it work?

In a nutshell, it invokes this command on your local machine:

$ open 'mysql+ssh://web@111.222.333.444:22/my_db_user:my_db_password@localhost/my_db_name?enviroment=production&usePrivateKey=true'

Which database clients are supported?

I've only tested TablePlus.

Sqeual Pro (nightly) supports mysql:// but seems not support SSH tunnels yet

Does it support vagrant / local databases?

No.


See: #7

@tangrufus
Copy link
Collaborator Author

tangrufus commented Sep 25, 2019

Discussion:

How should we handle app-specific paramters (e.g: ?enviroment=production&usePrivateKey=true)?

ping @swalkinshaw

@swalkinshaw
Copy link
Member

How should we handle app-specific paramters

By app I'm guessing you mean TablePlus and Sequel Pro for example? I guess we'd have to have some we officially support and hardcode their necessary options? Then we could also let people pass-through whatever extra params they need.

@tangrufus
Copy link
Collaborator Author

tangrufus commented Sep 26, 2019

By app I'm guessing you mean TablePlus and Sequel Pro for example?

Yes.

What do you think about this style?

trellis db open production example.com tableplus
trellis db open production example.com sequel-pro 

Putting the app name last because we can make them optional when #70 is done.

The reason is that they can be open in very different ways:

TablePlus: $ open 'mysql+ssh://web@111.222.333.444:22/my_db_user:my_db_password@localhost/my_db_name?enviroment=production&usePrivateKey=true'

Sequal Pro: $ open xxx.spf
(see: https://github.com/TypistTech/vagrant-trellis-sequel/blob/cc93696de180ba8e72e80039c9837f0de26bd77c/lib/vagrant_plugins/trellis_sequel/template.spf.erb)

@swalkinshaw
Copy link
Member

We don't really have other options than your suggestion but I think they should be option flags instead?

@tangrufus
Copy link
Collaborator Author

Help wanted

10ad118 introduces $ trellis db open --app=tableplus production mysite. However, the implementation is so bad that writting tests is painful.

Whoever can't stand with my code smell, free feel to make pull requests to my branch.


assuming we'd get the output from STDOUT and parse it so there wouldn't be a need for any temp files.
#72 (comment)

I have trouble getting ansible outputs from STDOUT.


There are lots of boilerplate code . Example:

However, refactoring them affects almost every existing commands.
Perphas leave it to another pull request.


Bash autocompletion doesn't work on my machine. Thus, omitted.


Tried to put cmd/tableplus.go into subdirectory - cmd/db/openers/tableplus.go.
However, execCommand & logCmd become unavaible.

Same goes to cmd/db/openers/factory.go (https://github.com/roots/trellis-cli/pull/72/files#diff-a08b51a976e893b57653c908b219ffbcR44-R53)

@tangrufus tangrufus force-pushed the db-open branch 3 times, most recently from 3511fcd to 4f445ad Compare November 3, 2019 03:18
@swalkinshaw
Copy link
Member

🤷‍♂ I think it looks mostly good overall. Not sure we can really avoid a lot of boilerplate here (and it's Go after all 😛 ).

re: subdirectory, Go is weird about this and I don't know the best solution. But for now I'd just name the files cmd/db_opener_tableplus.go etc.

@tangrufus
Copy link
Collaborator Author

Ignore this PR until #88 is merged

@tangrufus tangrufus changed the title [WIP] Add db open command Add db open command Nov 16, 2019
@tangrufus
Copy link
Collaborator Author

tangrufus commented Nov 16, 2019

Added more tests.

Note:

  • DBOpenCommand is untested (Help wanted)
  • Not sure how to test DBOpenerFactory returns correct struct (Any golang equivalent of instanceof?)
  • DBOpenerSequelPro is not removing its spf file in temporary directory because the spf file might be removed before sequel pro reads db credentials from it. (Warning: The SPF contains credentials in plain text)
  • Intentionally omitting logCmd from DBOpenerTableplus to prevent printing db credentials.

Usage:

$ trells db open --app=tableplus production mysite
$ trells db open --app=sequel-pro production mysite

If you just installed tableplus or sequel-pro, open them normally at least once before running trells db open.

@swalkinshaw
Copy link
Member

For testing the struct type, I think you want https://golang.org/pkg/reflect/#TypeOf

Copy link
Member

@swalkinshaw swalkinshaw left a comment

Choose a reason for hiding this comment

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

There's already some tests for DBOpenerSequelPro; wouldn't those work the same way for DBOpenCommand as well? Just assert on the mock exec command?

cmd/db_open.go Outdated Show resolved Hide resolved
cmd/db_open.go Outdated Show resolved Hide resolved

// Template playbook files from package to Trellis
playbookPath := "dump_db_credentials.yml"
writeFile(playbookPath, templates.TrimSpace(dumpDbCredentialsYmlTemplate))
Copy link
Member

Choose a reason for hiding this comment

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

Every usage of writeFile deletes the file after. Maybe we should just make this a more specific writeTempFile function which also handles the defer delete?

Copy link
Collaborator Author

@tangrufus tangrufus Nov 18, 2019

Choose a reason for hiding this comment

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

How do we defer the defer?

If we put defer inside writeTempFile, it deletes the files at the end of writeTempFile instead of the end of func (c *DBOpenCommand) Run(args []string) int

Copy link
Member

Choose a reason for hiding this comment

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

Oh yes good call. We do this:

return func() {
if err := os.Chdir(old); err != nil {
t.Fatalf("err: %s", err)
}
os.RemoveAll(tempDir)
}

Then it would be called as:

defer writeFile(path, foo)()

cmd/db_open.go Outdated Show resolved Hide resolved
if sequelProSpfErr != nil {
return fmt.Errorf("Error creating temporary SequelPro SPF file: %s", sequelProSpfErr)
}
// TODO: [Help Wanted] There is a chance that the SPF file got deleted before SequelPro establish db connection.
Copy link
Member

Choose a reason for hiding this comment

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

Can you explain this more? Was it getting deleted by the defer first?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Was it getting deleted by the defer first?

That is my theory.

If we uncomment the defer line, sequel pro will open but doesn't pre-fill any db info.
If we comment the defer line, sequel pro will open with db info pre-filled and try to connect to the db.

I suspect sequel pro needs to read that spf file after launch, but golang deleted the spf too soon.

Copy link
Member

Choose a reason for hiding this comment

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

Makes some sense. You could verify by logging like this:

func (o *DBOpenerSequelPro) open(c DBCredentials) (err error) {
         # etc
	open := execCommand("open", sequelProSpf.Name())
	err := cmd.Start()
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("running sequel pro...")
	err = cmd.Wait()
	log.Printf("Command finished with error: %v", err)
}

And then add a log printf to deleteFile to see when it's actually run compared to the other log lines

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed my theroy - the SPF file is read twice:

  • once by OS to open sequel pro
  • once by sequel pro to get db credentials

Newer version of sequel pro shows this error message if SPF is deleted too early:

Error while reading connection data file

Connection data file couldn't be read. (The file “804911772.spf” couldn’t be opened because there is no such file.)

@swalkinshaw
Copy link
Member

I can work on some tests as well if you want

@tangrufus
Copy link
Collaborator Author

For testing the struct type, I think you want golang.org/pkg/reflect/#TypeOf

It works!

There's already some tests for DBOpenerSequelPro; wouldn't those work the same way for DBOpenCommand as well? Just assert on the mock exec command?

Not sure how to mock the json file - https://github.com/roots/trellis-cli/pull/72/files#diff-a08b51a976e893b57653c908b219ffbcR135

I can work on some tests as well if you want

Please do! Thanks!

@swalkinshaw
Copy link
Member

I looked into testing and it's going to be very hard... it is possible to have more control over the mock exec command but it's not nice. We'd have to hardcode a conditional in TestHelperProcess to do what we want for the first ansible command. But that doesn't even solve the issue with writing to a tempfile that we wouldn't know anything about.

I think there's two solutions:

  1. write an interface which models writing the temp file, running ansible cmd and reading the temp file then we could make a test implementation of the struct
  2. make this an integration test that runs on Docker

@tangrufus
Copy link
Collaborator Author

write an interface which models writing the temp file, running ansible cmd and reading the temp file then we could make a test implementation of the struct

Something like tangrufus/trellis-cli@b83f85e...TangRufus:playbook-struct ?

@swalkinshaw
Copy link
Member

Yep that looks like it should do it 👍 . The test struct would mostly be concerned with implementing Run and would just dump some fixture JSON to the tempfile.

@tangrufus
Copy link
Collaborator Author

tangrufus commented Nov 26, 2019

I presume that we want to inject Playbook in main.go.

 	trellis := trellis.NewTrellis(project)
+   playbook := &Playbook{}
	 c.Commands = map[string]cli.CommandFactory{
		 "alias": func() (cli.Command, error) {
-			 return cmd.NewAliasCommand(ui, trellis), nil
+			 return cmd.NewAliasCommand(ui, trellis, playbook), nil
		 },

Am I correct?

@swalkinshaw
Copy link
Member

Correct yeah. That's the only place available and you can see examples in ssh and vault edit for the sys command executor.

@tangrufus
Copy link
Collaborator Author

Question: What are the differences between exec.Command and unix.Exec?

@swalkinshaw
Copy link
Member

The way I understand it:

  • os/exec: the go process is still "managing" the new command/process
  • syscall (or unix.Exec): runs a new process which completely takes over from the current one

I found it was needed for ssh for that process to take over and with vault edit it launches an editor. Maybe there is a way to do that with os/exec but I couldn't get it working.

@tangrufus tangrufus merged commit b1afd5e into roots:master Nov 26, 2019
@tangrufus tangrufus deleted the db-open branch November 26, 2019 16:24
@tangrufus
Copy link
Collaborator Author

The merge was a mistake. Sorry everyone. Reverted in #90

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