Skip to content

Commit

Permalink
Subcommands (#8)
Browse files Browse the repository at this point in the history
* changes cli interface to use subcommands

This is the first step to bringing more structure to the CLI and
breaking things down into sub commands and keeping the parsing of them
separate we should have an easier time adding validation and tests.

References:

* https://eli.thegreenplace.net/2020/testing-flag-parsing-in-go-programs/
* https://www.janekbieser.dev/posts/cli-app-with-subcommands-in-go/
  • Loading branch information
jentz authored May 31, 2024
1 parent 15578c0 commit c65ded0
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ jobs:
- name: Get dependencies
run: go mod download
- name: Build
run: go build -v cmd/oidc-cli.go
run: go build -v -o oidc-cli cmd/**
- name: Run tests
run: go test -v ./...
2 changes: 1 addition & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ builds:
- linux
- windows
- darwin
main: ./cmd/oidc-cli.go
main: ./cmd/
binary: oidc-cli

archives:
Expand Down
26 changes: 15 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@ Command-line OIDC client, get a token without all the fuss
## Usage

```bash
Usage: oidc-cli [flags]
setup an openid client with the callback url : http://localhost:9555/callback and set below flags to get a token response
oidc-cli is a command-line OIDC client, get a token without all the fuss

Usage:
oidc-cli [flags] <command> [command-flags]

Commands:
authorization_code: Uses the authorization code flow to get a token response
client_credentials: Uses the client credentials flow to get a token response
help : Prints help

Flags:
--discovery-url OpenID discovery endpoint. If provided, authorization-url and token-url will be ignored.
--authorization-url authorization URL. Default value is https://localhost:9443/oauth2/authorize.
--token-url token URL. Default value is https://localhost:9443/oauth2/token
--client-id client ID.
--client-secret client secret.
--scopes scopes.

Run `oidc-cli <command> -h` to get help for a specific command
```

## Installing

Installing with homebrew
Expand All @@ -27,13 +32,12 @@ You can also download a suitable release for your platform from the [releases pa
## Run

```bash
go run cmd/oidc-cli.go --authorization-url <authorization-url> --token-url <token-url> --client-id <client-id> --client-secret <client-secret>
go run cmd/** authorization_code --authorization-url <authorization-url> --token-url <token-url> --client-id <client-id> --client-secret <client-secret> --scopes "openid profile"
```


## Build

```bash
go build cmd/oidc-cli.go
go build -v -o oidc-cli ./cmd/**
```

15 changes: 15 additions & 0 deletions authorization_code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package oidc

type AuthorizationCodeConfig struct {
DiscoveryEndpoint string
AuthorizationEndpoint string
TokenEndpoint string
ClientID string
ClientSecret string
Scopes string
}

func (c *AuthorizationCodeConfig) Run() error {
HandleOpenIDFlow(c.ClientID, c.ClientSecret, c.Scopes, "http://localhost:9555/callback", c.DiscoveryEndpoint, c.AuthorizationEndpoint, c.TokenEndpoint)
return nil
}
27 changes: 27 additions & 0 deletions cmd/authorization_code_cfg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

import (
"bytes"
"flag"
oidc "github.com/jentz/vigilant-dollop"
)

func parseAuthorizationCodeFlags(name string, args []string) (config oidc.Command, output string, err error) {
flags := flag.NewFlagSet(name, flag.ContinueOnError)
var buf bytes.Buffer
flags.SetOutput(&buf)

var conf oidc.AuthorizationCodeConfig
flags.StringVar(&conf.DiscoveryEndpoint, "discovery-url", "", "set OIDC discovery url")
flags.StringVar(&conf.AuthorizationEndpoint, "authorization-url", "", "set OIDC authorization url")
flags.StringVar(&conf.TokenEndpoint, "token-url", "", "set OIDC token url")
flags.StringVar(&conf.ClientID, "client-id", "", "set client ID")
flags.StringVar(&conf.ClientSecret, "client-secret", "", "set client secret")
flags.StringVar(&conf.Scopes, "scopes", "", "set scopes as a space separated list")
err = flags.Parse(args)
if err != nil {
return nil, buf.String(), err
}

return &conf, buf.String(), nil
}
102 changes: 77 additions & 25 deletions cmd/oidc-cli.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,92 @@
package main

import (
"errors"
"flag"
"fmt"
"log"

oidc "github.com/jentz/vigilant-dollop"
"os"
"slices"
)

func main() {
type Command struct {
Name string
Help string
Configure func(name string, args []string) (config oidc.Command, output string, err error)
}

var commands = []Command{
{Name: "authorization_code", Help: "Uses the authorization code flow to get a token response", Configure: parseAuthorizationCodeFlags},
{Name: "client_credentials", Help: "Uses the client credentials flow to get a token response"},
{Name: "help", Help: "Prints help"},
}

func usage() {
intro := `oidc-cli is a command-line OIDC client, get a token without all the fuss
const callbackURL = "http://localhost:9555/callback"
flag.Usage = func() {
fmt.Println("Usage: oidc-cli\n" +
" setup an openid client with the callback url : " + callbackURL + " and set below flags to get a token response\n" +
"Flags:\n" +
" --discovery-url OpenID discovery endpoint. If provided, authorization-url and token-url will be ignored.\n" +
" --authorization-url authorization URL. Default value is https://localhost:9443/oauth2/authorize.\n" +
" --token-url token URL. Default value is https://localhost:9443/oauth2/token\n" +
" --client-id client ID.\n" +
" --client-secret client secret.\n" +
" --scopes scopes.")
Usage:
oidc-cli [flags] <command> [command-flags]`

fmt.Fprintln(os.Stderr, intro)
fmt.Fprintln(os.Stderr, "\nCommands:")
for _, cmd := range commands {
fmt.Fprintf(os.Stderr, " %-18s: %s\n", cmd.Name, cmd.Help)
}

var discoveryEndpoint = flag.String("discovery-url", "", "OpenID discovery endpoint")
var authorizationEndpoint = flag.String("authorization-url", "https://localhost:9443/oauth2/authorize", "OAuth2 authorization URL")
var tokenEndpoint = flag.String("token-url", "https://localhost:9443/oauth2/token", "OAuth2 token URL")
var clientID = flag.String("client-id", "client", "OAuth2 client ID")
var clientSecret = flag.String("client-secret", "clientSecret", "OAuth2 client secret")
var scopes = flag.String("scopes", "openid", "OAuth2 scopes")
fmt.Fprintln(os.Stderr, "\nFlags:")
// Prints a help string for each flag we defined earlier using
// flag.BoolVar (and related functions)
flag.PrintDefaults()

fmt.Fprintln(os.Stderr)
fmt.Fprintf(os.Stderr, "Run `oidc-cli <command> -h` to get help for a specific command\n\n")
}

func runCommand(name string, args []string) {

cmdIdx := slices.IndexFunc(commands, func(cmd Command) bool {
return cmd.Name == name
})

if cmdIdx < 0 {
fmt.Fprintf(os.Stderr, "command \"%s\" not found\n\n", name)
flag.Usage()
os.Exit(1)
}

cmd := commands[cmdIdx]
if cmd.Name == "help" {
flag.Usage()
os.Exit(0)
}

command, output, err := cmd.Configure(name, args)
if errors.Is(err, flag.ErrHelp) {
fmt.Println(output)
os.Exit(2)
} else if err != nil {
fmt.Println("got error:", err)
fmt.Println("output:\n", output)
os.Exit(1)
}

if err := command.Run(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v", err.Error())
os.Exit(1)
}
}

func main() {
flag.Usage = usage
flag.Parse()
if *clientID == "" {
log.Fatal("client-id is required to run this command")
} else if *clientSecret == "" {
log.Fatal("client-secret is required to run this command")

// If no command is specified, print usage and exit
if flag.NArg() < 1 {
usage()
os.Exit(1)
}
oidc.HandleOpenIDFlow(*clientID, *clientSecret, *scopes, callbackURL, *discoveryEndpoint, *authorizationEndpoint, *tokenEndpoint)

subCmd := flag.Arg(0)
subCmdArgs := flag.Args()[1:]
runCommand(subCmd, subCmdArgs)
}
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
module github.com/jentz/vigilant-dollop

go 1.21

4 changes: 4 additions & 0 deletions oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import (
"time"
)

type Command interface {
Run() error
}

type callbackEndpoint struct {
server *http.Server
code string
Expand Down

0 comments on commit c65ded0

Please sign in to comment.