Skip to content

Commit

Permalink
feat: add --log-http and --dry-run
Browse files Browse the repository at this point in the history
Enable the ability to just log http requests, along with
a dry run functionality. This allows for crafting HTTP
requests with aepcli, as well as auditing what aepcli is
doing.
  • Loading branch information
toumorokoshi committed Nov 15, 2024
1 parent efefce9 commit f6c1159
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 19 deletions.
10 changes: 7 additions & 3 deletions cmd/aepcli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ func main() {
}

func aepcli(args []string) error {
var dryRun bool
var logHTTP bool
var logLevel string
var fileAliasOrCore string
var additionalArgs []string
var headers []string
var pathPrefix string
var serverURL string
var configFileVar string
var s *service.Service
var s *service.ServiceCommand

rootCmd := &cobra.Command{
Use: "aepcli [host or api alias] [resource or --help]",
Expand All @@ -53,6 +55,8 @@ func aepcli(args []string) error {
rootCmd.Flags().SetInterspersed(false) // allow sub parsers to parse subsequent flags after the resource
rootCmd.PersistentFlags().StringArrayVar(&headers, "header", []string{}, "Specify headers in the format key=value")
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "Set the logging level (debug, info, warn, error)")
rootCmd.PersistentFlags().BoolVar(&logHTTP, "log-http", false, "Set to true to log HTTP requests. This can be helpful when attempting to write your own code or debug.")
rootCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "Set to true to not make any changes. This can be helpful when paired with log-http to just view http requests instead of perform them.")
rootCmd.PersistentFlags().StringVar(&pathPrefix, "path-prefix", "", "Specify a path prefix that is prepended to all paths in the openapi schema. This will strip them when evaluating the resource hierarchy paths.")
rootCmd.PersistentFlags().StringVar(&serverURL, "server-url", "", "Specify a URL to use for the server. If not specified, the first server URL in the OpenAPI definition will be used.")
rootCmd.PersistentFlags().StringVar(&configFileVar, "config", "", "Path to config file")
Expand Down Expand Up @@ -109,9 +113,9 @@ func aepcli(args []string) error {
return fmt.Errorf("unable to parse headers: %w", err)
}

s = service.NewService(api, headersMap)
s = service.NewServiceCommand(api, headersMap, dryRun, logHTTP)

result, err := s.ExecuteCommand(additionalArgs)
result, err := s.Execute(additionalArgs)
fmt.Println(result)
if err != nil {
return err
Expand Down
13 changes: 13 additions & 0 deletions docs/userguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,19 @@ lists are specified as a comma-separated list:
aepcli bookstore book-edition create --book "peter-pan" --publisher "consistent-house" --tags "fantasy,childrens"
```

### Logging HTTP requests and Dry Runs

aepcli supports logging http requests and dry runs. To log http requests, use the
`--log-http` flag. To perform a dry run, use the `--dry-run` flag.

Combined, they can be useful for creating HTTP calls without having to craft the
HTTP request by hand:

```bash
aepcli --dry-run --log-http bookstore book --publisher=standard-house get foo
Request: GET http://localhost:8081/publishers/standard-house/books/foo
```

### core commands

See `aepcli core --help` for commands for aepcli (e.g. config)
Expand Down
5 changes: 3 additions & 2 deletions internal/service/resource_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func ExecuteResourceCommand(r *api.Resource, args []string) (*http.Request, stri
var flagValue string
parents = append(parents, &flagValue)
c.PersistentFlags().StringVar(&flagValue, flagName, "", fmt.Sprintf("The %v of the resource", flagName))
c.MarkPersistentFlagRequired(flagName)
i += 2
}

Expand All @@ -47,10 +48,10 @@ func ExecuteResourceCommand(r *api.Resource, args []string) (*http.Request, stri

if r.CreateMethod != nil {
use := "create [id]"
args := cobra.ExactArgs(0)
args := cobra.ExactArgs(1)
if !r.CreateMethod.SupportsUserSettableCreate {
use = "create"
args = cobra.ExactArgs(1)
args = cobra.ExactArgs(0)
}
createArgs := map[string]interface{}{}
createCmd := &cobra.Command{
Expand Down
8 changes: 5 additions & 3 deletions internal/service/resource_definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ var projectResource = api.Resource{
},
Required: []string{"name"},
},
GetMethod: &api.GetMethod{},
ListMethod: &api.ListMethod{},
CreateMethod: &api.CreateMethod{},
GetMethod: &api.GetMethod{},
ListMethod: &api.ListMethod{},
CreateMethod: &api.CreateMethod{
SupportsUserSettableCreate: true,
},
UpdateMethod: &api.UpdateMethod{},
DeleteMethod: &api.DeleteMethod{},
}
Expand Down
28 changes: 20 additions & 8 deletions internal/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,25 @@ import (
"github.com/aep-dev/aep-lib-go/pkg/api"
)

type Service struct {
type ServiceCommand struct {
API api.API
Headers map[string]string
DryRun bool
LogHTTP bool
Client *http.Client
}

func NewService(api *api.API, headers map[string]string) *Service {
return &Service{
func NewServiceCommand(api *api.API, headers map[string]string, dryRun bool, logHTTP bool) *ServiceCommand {
return &ServiceCommand{
API: *api,
Headers: headers,
DryRun: dryRun,
LogHTTP: logHTTP,
Client: &http.Client{},
}
}

func (s *Service) ExecuteCommand(args []string) (string, error) {
func (s *ServiceCommand) Execute(args []string) (string, error) {
if len(args) == 0 || args[0] == "--help" {
return s.PrintHelp(), nil
}
Expand Down Expand Up @@ -56,7 +60,7 @@ func (s *Service) ExecuteCommand(args []string) (string, error) {
return strings.Join([]string{output, reqOutput}, "\n"), nil
}

func (s *Service) doRequest(r *http.Request) (string, error) {
func (s *ServiceCommand) doRequest(r *http.Request) (string, error) {
r.Header.Set("Content-Type", "application/json")
for k, v := range s.Headers {
r.Header.Set(k, v)
Expand All @@ -70,10 +74,18 @@ func (s *Service) doRequest(r *http.Request) (string, error) {
r.Body = io.NopCloser(bytes.NewBuffer(b))
body = string(b)
}
slog.Debug(fmt.Sprintf("Request: %s %s\n%s", r.Method, r.URL.String(), string(body)))
requestLog := fmt.Sprintf("Request: %s %s\n%s", r.Method, r.URL.String(), string(body))
slog.Debug(requestLog)
if s.LogHTTP {
fmt.Println(requestLog)
}
if s.DryRun {
slog.Debug("Dry run: not making request")
return "", nil
}
resp, err := s.Client.Do(r)
if err != nil {
return "", err
return "", fmt.Errorf("unable to execute request: %v", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
Expand All @@ -88,7 +100,7 @@ func (s *Service) doRequest(r *http.Request) (string, error) {
return prettyJSON.String(), nil
}

func (s *Service) PrintHelp() string {
func (s *ServiceCommand) PrintHelp() string {
var resources []string
for singular := range s.API.Resources {
resources = append(resources, singular)
Expand Down
6 changes: 3 additions & 3 deletions internal/service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import (

func TestService_ExecuteCommand_ListResources(t *testing.T) {
// Test setup
svc := NewService(&api.API{
svc := NewServiceCommand(&api.API{
ServerURL: "http://test.com",
Resources: map[string]*api.Resource{
"project": &projectResource,
"user": {},
"post": {},
"comment": {},
},
}, nil)
}, nil, false, false)

tests := []struct {
name string
Expand Down Expand Up @@ -55,7 +55,7 @@ func TestService_ExecuteCommand_ListResources(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := svc.ExecuteCommand(tt.args)
result, err := svc.Execute(tt.args)
if err != nil {
if !tt.expectAsError {
t.Errorf("ExecuteCommand() error = %v, expected no error", err)
Expand Down

0 comments on commit f6c1159

Please sign in to comment.