From 720fbc274c32e05e5f442eab283c426c11b80d06 Mon Sep 17 00:00:00 2001 From: Seth Yates <29878+sethyates@users.noreply.github.com> Date: Thu, 8 Sep 2022 21:17:37 -0700 Subject: [PATCH] feat: add rotate and info to README --- README.md | 111 +++++++++++++++++++++++++++++------ cmd/ketch/main.go | 50 ++++++++-------- pkg/cli/login.go | 4 +- pkg/config/config.go | 28 +++++++-- pkg/flags/flags.go | 17 ++---- pkg/transponder/configure.go | 2 + pkg/transponder/ls.go | 2 +- pkg/transponder/rotate.go | 7 +++ 8 files changed, 161 insertions(+), 60 deletions(-) create mode 100644 pkg/transponder/rotate.go diff --git a/README.md b/README.md index a1dc1c0..933123c 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,121 @@ # ketch-cli -The Ketch Command-Line Interface. +Ketch command line interface. -## Get the code +## Obtaining +### Downloading a release + +The easiest way to obtain the Ketch CLI is to download from the [latest release](https://github.com/ketch-com/ketch-cli/releases/latest). +Binaries for MacOS(Darwin), Linux and Windows are available. Download and unzip the file for your operating system. + +### Building from source code + +You can also build from source code using the following: +```shell +git clone git@github.com:ketch-com/ketch-cli.git +cd ketch-cli +go build -o ketch ./cmd/ketch/main.go +``` + +## Usage + +To obtain a token, run the following command (use `ketch.exe` on Windows): + +```shell +ketch login +``` + +This will output something similar to the following: +```shell +Now, go to https://ketch.us.auth0.com/activate?user_code=HZZK-JZJM and confirm the following code: + + +-----------+ + | HZZK-JZJM | + +-----------+ + +``` + +Open the link in your web browser and enter the code. Once completed, the token is printed to the standard output, such as: ```shell -$ git clone https://github.com/ketch-com/ketch-cli -$ cd ketch-cli/ +eyJhbGciOiJSUzI1NiIsInR5cCI6Ik8239879487298472xmM3pBWDlFU3NGZi05c1V6diJ9.eyJpc3Muytfaf76Af786aof76fo8sdf6osdf6so8f6sf7s6o8f76asofayf78s6fosiyasof87eas6YyNzc1MDk0LCJhenAiOiJqOWdlbWl6c1hpczVJY1VnOTMxc0JqR295R1N4YlQxYSJ9.os987foFLUIluzydflfyldisutflsdiuftslfiutuftdliut736r3l7ltd83l6 ``` -## Getting the development dependencies +For easy use later, you can set the key to an environment variable. For example, using the sample token above: +```shell +export KETCH_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6Ik8239879487298472xmM3pBWDlFU3NGZi05c1V6diJ9.eyJpc3Muytfaf76Af786aof76fo8sdf6osdf6so8f6sf7s6o8f76asofayf78s6fosiyasof87eas6YyNzc1MDk0LCJhenAiOiJqOWdlbWl6c1hpczVJY1VnOTMxc0JqR295R1N4YlQxYSJ9.os987foFLUIluzydflfyldisutflsdiuftslfiutuftdliut736r3l7ltd83l6" +``` +Setting an environment variable saves having to specify the token on subsequent calls to the CLI. + +On Linux/MacOS, you can set an environment variable automatically using the following: ```shell -go get -u ./... +export KETCH_TOKEN=$(ketch login) ``` -## Building +All further examples will assume that a `KETCH_TOKEN` environment variable has been set. + +### Transponder + +The Transponder is multi-tenanted, so you can use a single transponder for multiple organizations in the Ketch platform. +The token obtained via [login](#Login) will be scoped to a single organization (the one you logged into when creating the +token). -You can build this project using Go: +To configure a transponder, you need to know the URL of the transponder and the computer where you run the CLI needs to +be able to access that URL. You can first check if the URL is accessible in your web browser or using `curl`. Once you have +confirmed that the URL is correct, you can then set an environment variable to save from having to specify the transponder URL +on subsequent executions of the CLI. +For example, if your transponder URL is `https://transponder-stage-uswest2.mycompany.com/`, then you can use the following: ```shell -go build ./... +export KETCH_URL="https://transponder-stage-uswest2.mycompany.com/" ``` -You can also produce Linux binaries suitable for Docker Compose using the following: +All further examples will assume that a `KETCH_URL` environment variable has been set. + +#### List Connections + +Connections belong to organizations and provide secrets and configuration details for establishing connections to data systems. + +To list the connections belonging to the current organization, use the following command: ```shell -./scripts/build.sh +ketch transponder ls ``` -## Distribution +#### Configure Connection + +To configure a connection, two preparation steps are required: +1. [Create the connection](https://docs.ketch.com/hc/en-us/articles/5883595869335-Connecting-the-Ketch-Transponder-to-Ketch#install-database-provider-0-2) in the Ketch console and note the connection code (we will use `my_connection` in the examples below) +2. Collect the required [configuration properties](https://docs.ketch.com/hc/en-us/articles/5922260652439-Database-Provider-Configuration-Parameters) for the data system you are configuring + +Once you have the prerequisite information, you can run configure such as: +```shell +ketch transponder configure my_connection -P 'username=myuser' -P 'password=*****' ....other properties.... +``` -The docker containers produced by this repository are contained in the `docker` folder. +Potential problems: +1. The connection hasn't been configured in the Ketch console +2. The connection has been configured in a different organization than the current KETCH_TOKEN +3. Configuration properties are incomplete or incorrect +4. The transponder is unable to access the data system -## Updating dependencies +#### Rotate Organization API Key -To update the dependencies, run the following: +To update an organization's API key used by the Transponder, use the following: ```shell -rm go.sum -go get -u -t ./... +ketch transponder rotate ``` +This command will connect to the transponder and then the transponder will connect to the Ketch platform, attempting to +rotate the current API key configured. If successfully rotated, it will replace the old API key. If there is a problem +rotating the API key, then the previous API key will not be replaced and an error will be displayed. The access token +obtained via login will be used to create the API key. Therefore, your user account must have permissions to manage +API keys. + +Potential problems: +1. The transponder is unable to connect to the Ketch platform +2. The organization code does not exist +3. The token does not have permission to manage API keys +4. The API key does not have the appropriate permissions diff --git a/cmd/ketch/main.go b/cmd/ketch/main.go index 336c258..decd05d 100644 --- a/cmd/ketch/main.go +++ b/cmd/ketch/main.go @@ -9,8 +9,7 @@ import ( "go.ketch.com/cli/ketch-cli/pkg/flags" "go.ketch.com/cli/ketch-cli/pkg/transponder" "go.ketch.com/cli/ketch-cli/version" - "go.ketch.com/lib/orlop/v2/cmd" - stdlog "log" + "log" "os" "path" ) @@ -23,7 +22,7 @@ func main() { PadLevelText: true, }) - stdlog.SetOutput(logrus.New().Writer()) + log.SetOutput(logrus.New().Writer()) var rootCmd = &cobra.Command{ Use: version.Name, @@ -70,16 +69,9 @@ Simply type ` + rootCmd.Name() + ` help [path to command] for full details.`, }, }) - var runner = cmd.NewRunner(version.Name) - - rootCmd.PersistentFlags().String(flags.Token, runner.Getenv("TOKEN"), "auth token") rootCmd.PersistentFlags().String(flags.Config, ".ketchrc", "environment file") - rootCmd.PersistentFlags().String(flags.URL, runner.Getenv("URL"), "url to Ketch API") - rootCmd.PersistentFlags().Bool(flags.TLSInsecure, runner.Getenv("INSECURE") == "true", "set true to skip certificate verification") - rootCmd.PersistentFlags().String(flags.TLSCert, runner.Getenv("TLS_CERT_FILE"), "TLS client certificate") - rootCmd.PersistentFlags().String(flags.TLSKey, runner.Getenv("TLS_KEY_FILE"), "TLS private key") - rootCmd.PersistentFlags().String(flags.TLSCACert, runner.Getenv("TLS_ROOTCA_FILE"), "TLS root CA certificate") - rootCmd.PersistentFlags().String(flags.TLSServerName, runner.Getenv("TLS_OVERRIDE"), "override the TLS server name") + rootCmd.PersistentFlags().String(flags.Token, "", "Ketch API authorization token") + rootCmd.PersistentFlags().String(flags.URL, "", "url to Ketch API") rootCmd.SilenceUsage = true // @@ -107,24 +99,34 @@ Simply type ` + rootCmd.Name() + ` help [path to command] for full details.`, rootCmd.AddCommand(transponderCmd) - var transponderListCmd = &cobra.Command{ - Use: "ls", - Short: "list connections", - RunE: transponder.List, + var transponderRotateCmd = &cobra.Command{ + Use: "rotate", + Short: "rotate API key", + RunE: transponder.Rotate, + } + + transponderCmd.AddCommand(transponderRotateCmd) + + var transponderConnListCmd = &cobra.Command{ + Use: "ls", + Short: "list connections", + RunE: transponder.List, + Aliases: []string{"list"}, } - transponderCmd.AddCommand(transponderListCmd) + transponderCmd.AddCommand(transponderConnListCmd) - var transponderConfigureCmd = &cobra.Command{ - Use: "configure", - Short: "configure a connection", - RunE: transponder.Configure, - Args: cobra.ExactArgs(1), + var transponderConnConfigureCmd = &cobra.Command{ + Use: "configure", + Short: "configure a connection", + RunE: transponder.Configure, + Args: cobra.ExactArgs(1), + Aliases: []string{"conf", "config"}, } - transponderConfigureCmd.Flags().StringToStringP(flags.Parameter, "P", nil, "parameter key/value") + transponderConnConfigureCmd.Flags().StringToStringP(flags.Parameter, "P", nil, "parameter key/value") - transponderCmd.AddCommand(transponderConfigureCmd) + transponderCmd.AddCommand(transponderConnConfigureCmd) if err := rootCmd.ExecuteContext(context.Background()); err != nil { os.Exit(1) diff --git a/pkg/cli/login.go b/pkg/cli/login.go index 8655684..40725fb 100644 --- a/pkg/cli/login.go +++ b/pkg/cli/login.go @@ -84,8 +84,7 @@ func Login(cmd *cobra.Command, args []string) error { return err } - fmt.Printf("Now, go to %s and enter the following code:\n", dc.VerificationUri) - fmt.Printf("%s\n", dc.UserCode) + fmt.Fprintf(os.Stderr, "Now, go to %s and confirm the following code:\n\n +-----------+\n | %s |\n +-----------+\n\n", dc.VerificationUriComplete, dc.UserCode) timeout := time.After(time.Duration(dc.ExpiresInSec) * time.Second) ticker := time.NewTicker(time.Duration(dc.IntervalInSec) * time.Second) @@ -100,6 +99,7 @@ func Login(cmd *cobra.Command, args []string) error { if tok, ok, err := check(ctx, cfg, dc); err != nil { return err } else if ok { + fmt.Fprintf(os.Stderr, "The following is your KETCH_TOKEN:\n") fmt.Println(tok) return nil } diff --git a/pkg/config/config.go b/pkg/config/config.go index e2a97a7..89771c5 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -4,7 +4,9 @@ import ( "github.com/spf13/cobra" "go.ketch.com/cli/ketch-cli/pkg/flags" "go.ketch.com/cli/ketch-cli/version" - "go.ketch.com/lib/orlop/v2" + "go.ketch.com/lib/orlop/v2/config" + "go.ketch.com/lib/orlop/v2/errors" + "net/url" ) type Config struct { @@ -20,11 +22,20 @@ func NewConfig(cmd *cobra.Command) (*Config, error) { Auth0Domain: "ketch.us.auth0.com", ClientID: "j9gemizsXis5IcUg931sBjGoyGSxbT1a", Audience: "https://global.ketchapi.com/rest", + URL: config.GetEnv(version.Name, "URL"), + Token: config.GetEnv(version.Name, "TOKEN"), } - err := orlop.Unmarshal(version.Name, cfg) - if err != nil { - return nil, err + if domain := config.GetEnv(version.Name, "AUTH0_DOMAIN"); len(domain) > 0 { + cfg.Auth0Domain = domain + } + + if clientID := config.GetEnv(version.Name, "CLIENT_ID"); len(clientID) > 0 { + cfg.ClientID = clientID + } + + if audience := config.GetEnv(version.Name, "AUDIENCE"); len(audience) > 0 { + cfg.Audience = audience } if s, err := cmd.Flags().GetString(flags.URL); err != nil { @@ -39,5 +50,14 @@ func NewConfig(cmd *cobra.Command) (*Config, error) { cfg.Token = s } + if len(cfg.URL) == 0 { + return nil, errors.Invalidf("url is required. either specify --url or set KETCH_URL environment variable") + } + + u, err := url.Parse(cfg.URL) + if err != nil || u.Scheme != "https" { + return nil, errors.Invalidf("url is invalid. check the value of --url or KETCH_URL (url provided is '%s')", cfg.URL) + } + return cfg, nil } diff --git a/pkg/flags/flags.go b/pkg/flags/flags.go index 6c7c440..edcbe5d 100644 --- a/pkg/flags/flags.go +++ b/pkg/flags/flags.go @@ -1,16 +1,9 @@ package flags const ( - File = "file" - Token = "token" - Config = "config" - Org = "org" - URL = "url" - Version = "version" - TLSInsecure = "insecure" - TLSCert = "tls-cert" - TLSKey = "tls-key" - TLSCACert = "tls-cacert" - TLSServerName = "servername" - Parameter = "parameter" + ApiKey = "api-key" + Token = "token" + Config = "config" + URL = "url" + Parameter = "parameter" ) diff --git a/pkg/transponder/configure.go b/pkg/transponder/configure.go index a40c42a..10f385c 100644 --- a/pkg/transponder/configure.go +++ b/pkg/transponder/configure.go @@ -44,10 +44,12 @@ func Configure(cmd *cobra.Command, args []string) error { if strings.HasPrefix(value, "@") { // load from file fileName := strings.TrimPrefix(value, "@") + data, err := os.ReadFile(fileName) if err != nil { return err } + in[key] = base64.URLEncoding.EncodeToString(data) } } diff --git a/pkg/transponder/ls.go b/pkg/transponder/ls.go index 101390a..836622a 100644 --- a/pkg/transponder/ls.go +++ b/pkg/transponder/ls.go @@ -27,7 +27,7 @@ func List(cmd *cobra.Command, args []string) error { return err } - u.Path = path.Join(u.Path, "/captain", "connections") + u.Path = path.Join(u.Path, "captain", "connections") client := http.Client{} diff --git a/pkg/transponder/rotate.go b/pkg/transponder/rotate.go new file mode 100644 index 0000000..d6cdcc7 --- /dev/null +++ b/pkg/transponder/rotate.go @@ -0,0 +1,7 @@ +package transponder + +import "github.com/spf13/cobra" + +func Rotate(cmd *cobra.Command, args []string) error { + return nil +}