-
Notifications
You must be signed in to change notification settings - Fork 55
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
CLI-27: basic integration tests. #122
Changes from 17 commits
e3c2b42
702c5fc
f80812d
1da47e7
c18bdc3
adbe7c6
cde1fcd
e562fb5
1a188e3
4386d3f
28ed458
57ad292
aaaf3f7
c6db4b5
52fa053
9db1711
4f19352
3c3ac6a
ac640aa
da95121
2e59183
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
config: | ||
inherit-env: true | ||
|
||
tests: | ||
auth0 apis list: | ||
exit-code: 0 | ||
|
||
auth0 apps list: | ||
exit-code: 0 | ||
|
||
auth0 logs: | ||
exit-code: 0 | ||
|
||
auth0 actions list: | ||
exit-code: 0 | ||
|
||
auth0 completion bash: | ||
exit-code: 0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Continuous Integration | ||
|
||
## Integration Tests | ||
|
||
Integration tests can be run with: | ||
```bash | ||
make integration | ||
``` | ||
|
||
`make integration` will build and run the `auth0-cli-config-generator` command which is responsible for ensuring that a valid auth0-cli config file exists before the integration tests run. If a valid auth0-cli config file doesn't exist, `auth0-cli-config-generator` will attempt to generate one based off command line flags or/and environment variables. | ||
|
||
`make integration` will then use [commander](https://github.com/commander-cli/commander) to run tests defined in [commander.yaml](./commander.yaml) | ||
|
||
To run intergration tests as part of a CI pipeline, several environment variables need to be exported first. When these variables are set, `auth0-cli-config-generator` will generate a valid auth0-cli config file being retrieving a token for the client, removing the need to run `auth0 login`: | ||
```bash | ||
export AUTH0_CLI_CLIENT_NAME="integration" \ | ||
AUTH0_CLI_CLIENT_DOMAIN="example-test-domain.au.auth0.com" \ | ||
AUTH0_CLI_CLIENT_ID="example-client-id" \ | ||
AUTH0_CLI_CLIENT_SECRET="example-client-secret" \ | ||
AUTH0_CLI_REUSE_CONFIG="false" \ | ||
AUTH0_CLI_OVERWRITE="true" | ||
make integration | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
// auth0-cli-config-generator: A command that generates a valid config file that can be used with auth0-cli. | ||
// | ||
// Currently this command is only used to generator a config using environment variables which is then used for integration tests. | ||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/url" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"time" | ||
|
||
"github.com/lestrrat-go/jwx/jwt" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
"golang.org/x/oauth2/clientcredentials" | ||
) | ||
|
||
type params struct { | ||
filePath string | ||
clientName string | ||
clientDomain string | ||
clientID string | ||
clientSecret string | ||
} | ||
|
||
func (p params) validate() error { | ||
if p.clientName == "" { | ||
return fmt.Errorf("Missing client name") | ||
} | ||
|
||
if p.clientDomain == "" { | ||
return fmt.Errorf("Missing client domain") | ||
} | ||
|
||
u, err := url.Parse(p.clientDomain) | ||
if err != nil { | ||
return fmt.Errorf("Failed to parse client domain: %s", p.clientDomain) | ||
} | ||
|
||
if u.Scheme != "" { | ||
return fmt.Errorf("Client domain cant include a scheme: %s", p.clientDomain) | ||
} | ||
|
||
if p.clientID == "" { | ||
return fmt.Errorf("Missing client id") | ||
} | ||
|
||
if p.clientSecret == "" { | ||
return fmt.Errorf("Missing client secret") | ||
} | ||
return nil | ||
} | ||
|
||
type config struct { | ||
DefaultTenant string `json:"default_tenant"` | ||
Tenants map[string]tenant `json:"tenants"` | ||
} | ||
|
||
type tenant struct { | ||
Name string `json:"name"` | ||
Domain string `json:"domain"` | ||
AccessToken string `json:"access_token,omitempty"` | ||
ExpiresAt time.Time `json:"expires_at"` | ||
} | ||
Comment on lines
+58
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note for the future: we can probably consider re-using some of the same entities we have in the CLI since they resemble each other. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made a comment on keeping it siloed but also happy to re-use entities in the CLI. I didn't want to add the coupling. |
||
|
||
func isLoggedIn(filePath string) bool { | ||
var c config | ||
var buf []byte | ||
var err error | ||
if buf, err = ioutil.ReadFile(filePath); err != nil { | ||
rene00 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return false | ||
} | ||
|
||
if err := json.Unmarshal(buf, &c); err != nil { | ||
return false | ||
} | ||
|
||
if c.Tenants == nil { | ||
return false | ||
} | ||
|
||
if c.DefaultTenant == "" { | ||
return false | ||
} | ||
|
||
t, err := jwt.ParseString(c.Tenants[c.DefaultTenant].AccessToken) | ||
if err != nil { | ||
return false | ||
} | ||
|
||
if err = jwt.Validate(t); err != nil { | ||
return false | ||
} | ||
|
||
return true | ||
} | ||
|
||
func persistConfig(filePath string, c config, overwrite bool) error { | ||
dir := filepath.Dir(filePath) | ||
if _, err := os.Stat(dir); os.IsNotExist(err) { | ||
if err := os.MkdirAll(dir, 0700); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
buf, err := json.MarshalIndent(c, "", " ") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if _, err := os.Stat(filePath); err == nil && !overwrite { | ||
return fmt.Errorf("Not overwriting existing config file: %s", filePath) | ||
} | ||
|
||
if err = ioutil.WriteFile(filePath, buf, 0600); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func main() { | ||
var cmd = &cobra.Command{ | ||
Use: "auth0-cli-config-generator", | ||
Short: "A tool that generates valid auth0-cli config files", | ||
SilenceErrors: true, | ||
SilenceUsage: true, | ||
RunE: func(command *cobra.Command, args []string) error { | ||
reuseConfig := viper.GetBool("REUSE_CONFIG") | ||
overwrite := viper.GetBool("OVERWRITE") | ||
filePath := viper.GetString("FILEPATH") | ||
clientName := viper.GetString("CLIENT_NAME") | ||
clientDomain := viper.GetString("CLIENT_DOMAIN") | ||
clientID := viper.GetString("CLIENT_ID") | ||
clientSecret := viper.GetString("CLIENT_SECRET") | ||
|
||
if reuseConfig { | ||
if !isLoggedIn(filePath) { | ||
return fmt.Errorf("Config file is not valid: %s", filePath) | ||
} | ||
fmt.Printf("Reusing valid config file: %s\n", filePath) | ||
return nil | ||
} | ||
|
||
p := params{filePath, clientName, clientDomain, clientID, clientSecret} | ||
if err := p.validate(); err != nil { | ||
return err | ||
} | ||
|
||
u, err := url.Parse("https://" + p.clientDomain) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
c := &clientcredentials.Config{ | ||
ClientID: p.clientID, | ||
ClientSecret: p.clientSecret, | ||
TokenURL: u.String() + "/oauth/token", | ||
EndpointParams: url.Values{"audience": {u.String() + "/api/v2/"}}, | ||
} | ||
|
||
token, err := c.Token(context.Background()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
t := tenant{p.clientName, p.clientDomain, token.AccessToken, token.Expiry} | ||
|
||
cfg := config{p.clientName, map[string]tenant{p.clientName: t}} | ||
if err := persistConfig(p.filePath, cfg, overwrite); err != nil { | ||
return err | ||
} | ||
fmt.Printf("Config file generated: %s\n", filePath) | ||
|
||
return nil | ||
}, | ||
} | ||
viper.SetEnvPrefix("AUTH0_CLI") | ||
viper.AutomaticEnv() | ||
|
||
flags := cmd.Flags() | ||
flags.String("filepath", path.Join(os.Getenv("HOME"), ".config", "auth0", "config.json"), "Filepath for the auth0 cli config") | ||
_ = viper.BindPFlag("FILEPATH", flags.Lookup("filepath")) | ||
flags.String("client-name", "", "Client name to set within config") | ||
_ = viper.BindPFlag("CLIENT_NAME", flags.Lookup("client-name")) | ||
flags.String("client-id", "", "Client ID to set within config") | ||
_ = viper.BindPFlag("CLIENT_ID", flags.Lookup("client-id")) | ||
flags.String("client-secret", "", "Client secret to use to generate token which is set within config") | ||
_ = viper.BindPFlag("CLIENT_SECRET", flags.Lookup("client-secret")) | ||
flags.String("client-domain", "", "Client domain to use to generate token which is set within config") | ||
_ = viper.BindPFlag("CLIENT_DOMAIN", flags.Lookup("client-domain")) | ||
flags.Bool("reuse-config", true, "Reuse an existing config if found") | ||
_ = viper.BindPFlag("REUSE_CONFIG", flags.Lookup("reuse-config")) | ||
flags.Bool("overwrite", false, "Overwrite an existing config") | ||
_ = viper.BindPFlag("OVERWRITE", flags.Lookup("overwrite")) | ||
|
||
if err := cmd.Execute(); err != nil { | ||
fmt.Fprintln(os.Stderr, err) | ||
os.Exit(1) | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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.
Thanks for these small doc touches. Makes it feel 👌
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.
General question on placement: should we put this in
./cmd/
instead? If not, curious to hear your thought process.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 only real use is to generate a config for integration testing by a CI pipeline (or a noop if a valid config exists already) so storing it outside of where it could add cognitive overhead for contributors not working on tooling for integration tests was the idea. I also wanted to ensure it didnt depend on any code within
./internal/
to keep it siloed and independent. Happy to move it to./cmd/
and though -- just let me know.