-
Notifications
You must be signed in to change notification settings - Fork 20
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 dce auth
; Refactor Config file mgmt; Add integration tests
#38
Conversation
cmd/root.go
Outdated
isAuthCurrentCommand := len(os.Args) == 2 && os.Args[1] == authCmd.Name() | ||
if !hasValidCreds && !isAuthCurrentCommand { | ||
log.Print("No valid DCE credentials found") | ||
service.Authenticate() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice 👍
junit-report/ | ||
|
||
dce-cli |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ignore the compiled binary, from running make build
@@ -33,7 +33,7 @@ var accountsDescribeCmd = &cobra.Command{ | |||
Short: "describe an account", | |||
Args: cobra.ExactValidArgs(1), | |||
Run: func(cmd *cobra.Command, args []string) { | |||
service.GetAccount(args[0]) | |||
Service.GetAccount(args[0]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've exposed this global service objects. This is the only way we inject mocks into these service containers (at least without a bigger refactor to how we do DI)
func init() { | ||
RootCmd.AddCommand(authCmd) | ||
authCmd.Flags().StringVarP(&authUrl, "url-override", "u", "", "Override the DCE login url") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no longer used (auth endpoint is always at <dce-api>/auth
)
Run: func(cmd *cobra.Command, args []string) { | ||
service.Authenticate(authUrl) | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
return Service.Authenticate() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to move towards CLI commands returning errors.
This allows us to make assertions against these errors in integration tests
@@ -12,6 +12,6 @@ var initCmd = &cobra.Command{ | |||
Use: "init", | |||
Short: "First time DCE cli setup. Creates config file at ~/.dce.yaml", | |||
Run: func(cmd *cobra.Command, args []string) { | |||
service.InitializeDCE(cfgFile) | |||
Service.InitializeDCE() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The location of the config file is now globally configured within our FileSystemer
object.
This provides a common path for services to access the config.
|
||
func init() { | ||
cobra.OnInitialize(initObservation, initConfig, initUtil, initService) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved initialization to PreRun
hook, just because the init
functions don't return errors, which makes testing them difficult.
// If config file does not exist, | ||
// run the `dce init` command | ||
if !fsUtil.IsExistingFile(cfgFile) { | ||
if cmd.Name() != initCmd.Name() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to actually just run dce init
here, similar to how to do a hook to verify dce auth
.
I was just running into some circular reasoning around initialization.
return serialized, nil | ||
} | ||
|
||
func onInit(cmd *cobra.Command, args []string) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
most of this logic was just moved from the original initConfig
function
@@ -2,17 +2,17 @@ package configs | |||
|
|||
// Root contains config | |||
type Root struct { | |||
System struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
system.auth.loginurl is no longer used
@@ -19,15 +19,18 @@ replace github.com/ugorji/go v1.1.4 => github.com/ugorji/go v0.0.0-2018120416352 | |||
|
|||
require ( | |||
github.com/aws/aws-sdk-go v1.25.16 | |||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e | |||
github.com/coreos/bbolt v1.3.2 // indirect |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed viper
.
Added ptr
(tiny utility for creating pointers)
token *string | ||
} | ||
|
||
func NewAPIClient(input *NewAPIClientInput) *operations.Client { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored into a constructor method.
Some new logic being dealt with here, around how creds are passed in.
// WriteConfig writes the Config objects as YAML | ||
// to the config file location (dce.yml) | ||
func (u *FileSystemUtil) WriteConfig() error { | ||
return u.writeToYAMLFile(u.ConfigFile, u.Config) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these are replacing what viper
was doing,
but in a way that's easily exposed to all our services
@@ -87,28 +103,28 @@ func (s *FileSystemUtil) MvToTempDir(prefix string) (string, string) { | |||
return destinationDir, originDir | |||
} | |||
|
|||
func (s *FileSystemUtil) RemoveAll(path string) { | |||
func (u *FileSystemUtil) RemoveAll(path string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just standardizing the var name for receivers (my IDE was mad at me about it)
// First, we'll check for credentials in the | ||
// dce.yaml's `api.token` config. | ||
// then we'll use AWS's standard chain (env vars, ~/aws/credentials file) | ||
creds := credentials.NewChainCredentials([]credentials.Provider{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how we're setting up the AWS credentials chain, which allows us to either use the configured API token (from cognito), or regular IAM creds
var session = session.New(&aws.Config{ | ||
Region: config.Region, | ||
}) | ||
var awsSession *session.Session |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting up a common session object, using our new creds chain,
so that both the DCE API and AWS services are using the same creds
} | ||
|
||
// Execute adds all child commands to the root command and sets flags appropriately. | ||
func Execute() { | ||
if err := RootCmd.Execute(); err != nil { | ||
fmt.Println(err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was actually printing error messages twice (I guess cobra prints them for us?)
Stuff going on here - Add `dce auth` command - pops you out to a browser at the `/auth` endpoint that @joshmarsh is working on - see #33 - Remove dce.yml configs we aren't using - Configure a AWS creds provider that will load creds from the auth token, but fallback to env vars or the `~/.aws/credentials` file - Run `dce auth` before any command, if we don't have any valid creds in our chain - Refactor how to load and save our config YAML, so it's easier to work with from inside multiple commands - Remove viper dependency (wasn't really being used) - Some other cleanup/refactor around init logic, to get this all wired up properly - I'll try to point these out in the code - New setup for integration tests - These test call our cobra CLI programmatically. For some reasons: - Lets us use debugger to run through tests - Don't need to rebuild for every test run - Can still use mocks at boundaries (eg. mock out web browser call) - I'm hoping this new testing pattern makes it easier to write tests for CLI commands going forward.
serialized = []byte(entry.Message) | ||
if err != nil { | ||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) | ||
return serialized, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was breaking tests (we're inspecting the logger in tests, to do assertions against CLI outputs)
if config.API.Host != nil { | ||
initializedApiClient = apiUtil.InitApiClient() | ||
var apiClient APIer | ||
if config.API.Host != nil && config.API.BasePath != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed some logic here, that would break if the dce.yml didn't have API configs
s.promptUserForConfig(s.Config) | ||
|
||
// Write the config to dce.yml | ||
err := s.Util.WriteConfig() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using our new common config file mgmt methods
newConfig.API.Host = s.Util.PromptBasic("Host name of the DCE API (example: abcde12345.execute-api.us-east-1.amazonaws.com)", nil) | ||
newConfig.API.BasePath = s.Util.PromptBasic("Base path of the DCE API (example: /apigw-stage-name)", nil) | ||
|
||
newConfig.GithubToken = s.Util.PromptBasic("Github token used to download releases from github (Leave blank to use GITHUB_TOKEN env variable)", nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to remove prompts for values that aren't required.
I'd rather keep my github token in an env var, then have to keep it in sync with the dce.yml file.
So I don't want dce init
to force me to enter a value here.
|
||
newConfig.GithubToken = s.Util.PromptBasic("Github token used to download releases from github (Leave blank to use GITHUB_TOKEN env variable)", nil) | ||
return &newConfig | ||
if config.API.Host == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you run dce init
a second time, and everythings already configured, it will just be a no-op
"testing" | ||
) | ||
|
||
func TestAuthCommand(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check out the new integration tests
}) | ||
|
||
// Mock Weber.OpenURL() | ||
mockWeber := &mocks.Weber{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's an integration test, but we can still mock at useful boundaries (like, for opening web pages)
mockWeber.On("OpenURL", "https://dce.example.com/api/auth") | ||
|
||
// Enter the API Token (IRL, would be provided by the web page) | ||
cli.AnswerBasic("Enter API Token: ", "my-api-token") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote a stub Prompter, that will allow you to make assertions against CLI prompts, and provide answers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice
cli.AssertAllPrompts() | ||
mockWeber.AssertExpectations(t) | ||
|
||
output := cli.Output() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cli.Output()
grabs the logs sent to our logger
|
||
func TestInitCommand(t *testing.T) { | ||
|
||
t.Run("GIVEN custom config path flag is provided", func(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These were all migrated from being functional tests (as per a conversation with @joshmarsh )
|
||
func NewCLITest(t *testing.T) *cliTest { | ||
// Reset globals, so they don't leak between tests | ||
cmd.Config = &configs.Root{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bunch of bootstrapping logic here for CLI integration tests.
Most of the complexity here comes from how we're using globals for dependency management,
and that these globals aren't initialized until you execute a command.
This makes it kind of tricky to inject dependencies into our command runner.
We might want to reconsider how we're setup with dependency injection to make this
kind of think easier, but that can be for another day...
// cli.Inject(func(input *injectorInput) { | ||
// input.util.Weber = &mocks.Weber{} | ||
// }) | ||
func (test *cliTest) Inject(f injector) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note this Inject()
method, which you'll need to use to inject mocks into global services
(because the global service references are nil until the command is run)
@@ -1,33 +0,0 @@ | |||
package unit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now covered in integration tests
@@ -1,101 +0,0 @@ | |||
package unit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now covered in integration tests
dce auth
; Refactor Config file mgmt; Add integration tests
Stuff going on here
dce auth
command/auth
endpoint that @joshmarsh is working on~/.aws/credentials
filedce auth
before any command, if we don't have any valid creds in our chain