-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CLI for interacting with the errands service (#8)
Co-authored-by: Joe Dursun <[email protected]>
- Loading branch information
Showing
8 changed files
with
807 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,4 +11,4 @@ | |
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
|
||
vendor/ | ||
vendor/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# CLI Overview | ||
|
||
The `errands` CLI facilitates working with the errands API. It provides commands | ||
such as `list` and `delete` and can even port-forward the errands service in our | ||
k8s cluster so you don't have to! | ||
|
||
# Installation | ||
|
||
If you just want to use the tool as it is, you can install it by running | ||
```bash | ||
go install github.com/polygon-io/errands-go/cmd/errands@latest | ||
errands help | ||
``` | ||
|
||
If you've got the repo cloned and are dev-ing on the tool, you can build and run it locally: | ||
```bash | ||
go run ./cmd/errands/. help # assuming you're in this directory | ||
``` | ||
|
||
or install your local version: | ||
```bash | ||
go install ./cmd/errands/. # assuming you're in this directory | ||
errands help | ||
``` | ||
|
||
# Usage | ||
|
||
By default it will `kubectl port-forward` the errands service on `localhost:5555` but you can change the | ||
port via `--port=XXX` or disable the port-forwarding entirely via `--bootstrap=false`. | ||
|
||
If you disable bootstrapping then you'll need to provide the endpoint via `--endpoint=http://my-running-errands-endpoint.com`. | ||
|
||
```bash | ||
# to list all the failed or inactive sort-pparc errands | ||
errands list --type=sort-pparc --status=failed,inactive | ||
|
||
# if you're already port-forwarding the errands service on port 6000 | ||
errands list --type=sort-pparc --status=failed,inactive --bootstrap=false --port=6000 | ||
|
||
# to perform a dry-run delete of all the failed sort-pparc jobs | ||
errands delete --type=sort-pparc --status=failed --dry-run=true | ||
|
||
# to delete an errand by its ID | ||
errands delete --id=abc-xyz-123 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package cmd | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
|
||
errandz "github.com/polygon-io/errands-go" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func (ec *errandsCmd) newDeleteCommand() (*cobra.Command, error) { | ||
cmd := &cobra.Command{ | ||
Use: "delete", | ||
Short: "deletes errands by ID, type, or status", | ||
RunE: ec.delete, | ||
PreRunE: ec.bindViperFlagsPreRun, | ||
} | ||
|
||
cmd.Flags().String("type", "", "Filter by errand type") | ||
cmd.Flags().String("status", "failed", "Filter by status; comma delimited") | ||
cmd.Flags().String("id", "", "ID of the errand to delete") | ||
cmd.Flags().Bool("dry-run", false, "Don't actually delete anything. Only used for bulk deletion") | ||
|
||
return cmd, nil | ||
} | ||
|
||
func (ec *errandsCmd) delete(cmd *cobra.Command, args []string) error { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
|
||
if ec.viper.GetBool("bootstrap") { | ||
portCmd, err := ec.portForwardErrandsServer(ctx) | ||
if err != nil { | ||
return fmt.Errorf("port-forward: %w", err) | ||
} | ||
|
||
defer func() { | ||
if err := portCmd.Process.Signal(os.Interrupt); err != nil { | ||
fmt.Printf("error killing port-forward: %s\n", err) | ||
} | ||
}() | ||
} | ||
|
||
if id := ec.viper.GetString("id"); id != "" { | ||
if err := deleteErrand(ec.api, id); err != nil { | ||
return fmt.Errorf("failed to delete errand %s: %w", id, err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
jobs, err := listErrandsForTopic(ec.api, ec.viper.GetString("type"), ec.viper.GetString("status")) | ||
if err != nil { | ||
return fmt.Errorf("failed to get errands: %w", err) | ||
} | ||
|
||
for _, job := range jobs { | ||
name := job.Name | ||
if len(name) > 100 { | ||
name = name[:100] | ||
} | ||
|
||
if ec.viper.GetBool("dry-run") { | ||
fmt.Printf("(dry-run) delete %s: (%s) %s\n", job.ID, job.Status, name) | ||
continue | ||
} | ||
|
||
fmt.Printf("deleting %s\n", job.ID) | ||
if err := deleteErrand(ec.api, job.ID); err != nil { | ||
fmt.Printf("failed to delete errand %s: %e", job.ID, err) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func deleteErrand(api *errandz.ErrandsAPI, id string) error { | ||
_, err := api.DeleteErrand(id) | ||
|
||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package cmd | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"sort" | ||
"strings" | ||
"time" | ||
|
||
errandz "github.com/polygon-io/errands-go" | ||
"github.com/polygon-io/errands-server/schemas" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func (ec *errandsCmd) newListCommand() (*cobra.Command, error) { | ||
cmd := &cobra.Command{ | ||
Use: "list", | ||
Short: "returns a list of errands from our errands API", | ||
PreRunE: ec.bindViperFlagsPreRun, | ||
RunE: ec.run, | ||
} | ||
|
||
cmd.Flags().String("type", "", "filter by errand type") | ||
cmd.Flags().String("status", "", "filter by status; comma delimited") | ||
cmd.Flags().Int("port", 5555, "localhost port for the errands server") | ||
|
||
return cmd, nil | ||
} | ||
|
||
func (ec *errandsCmd) run(cmd *cobra.Command, args []string) error { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
|
||
if ec.viper.GetBool("bootstrap") { | ||
portCmd, err := ec.portForwardErrandsServer(ctx) | ||
if err != nil { | ||
return fmt.Errorf("port-forward: %w", err) | ||
} | ||
|
||
defer func() { | ||
if err := portCmd.Process.Signal(os.Interrupt); err != nil { | ||
fmt.Printf("error killing port-forward: %s\n", err) | ||
} | ||
}() | ||
} | ||
|
||
jobs, err := listErrandsForTopic(ec.api, ec.viper.GetString("type"), ec.viper.GetString("status")) | ||
if err != nil { | ||
return fmt.Errorf("get errands: %w", err) | ||
} | ||
|
||
for _, job := range jobs { | ||
name := job.Name | ||
if len(name) > 100 { | ||
name = name[:100] | ||
} | ||
fmt.Printf("%100s: %10s | %s | %s\n", name, job.Status, job.ID, time.UnixMilli(job.Created)) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func filterByStatus(jobs []schemas.Errand, status string) []schemas.Errand { | ||
if status == "" { | ||
return jobs | ||
} | ||
|
||
statusFilter := make(map[schemas.Status]bool) | ||
for _, s := range strings.Split(status, ",") { | ||
statusFilter[schemas.Status(s)] = true | ||
} | ||
|
||
var filtered []schemas.Errand | ||
for _, job := range jobs { | ||
if statusFilter[job.Status] { | ||
filtered = append(filtered, job) | ||
} | ||
} | ||
|
||
return filtered | ||
} | ||
|
||
func listErrandsForTopic(api *errandz.ErrandsAPI, errandType string, status string) ([]schemas.Errand, error) { | ||
if errandType == "" { | ||
return nil, errors.New("errand type is required") | ||
} | ||
|
||
jobs, err := api.ListErrands("type", errandType) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
results := filterByStatus(jobs.Results, status) | ||
|
||
sort.Slice(results, func(i, j int) bool { | ||
return results[i].Created > results[j].Created | ||
}) | ||
|
||
return results, nil | ||
} |
Oops, something went wrong.