338 lines
8.8 KiB
Go
338 lines
8.8 KiB
Go
|
package terminal
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"git.myservermanager.com/varakh/upda/api"
|
||
|
"git.myservermanager.com/varakh/upda/server"
|
||
|
"git.myservermanager.com/varakh/upda/util"
|
||
|
"github.com/go-resty/resty/v2"
|
||
|
"github.com/urfave/cli/v2"
|
||
|
"log"
|
||
|
"os"
|
||
|
"text/tabwriter"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
name = "upda-cli"
|
||
|
desc = "a commandline helper for upda"
|
||
|
version = server.Version
|
||
|
|
||
|
envServerUrl = "UPDA_SERVER_URL"
|
||
|
envAdminUser = "UPDA_ADMIN_USER"
|
||
|
envAdminPassword = "UPDA_ADMIN_PASSWORD"
|
||
|
envWebhookId = "UPDA_WEBHOOK_ID"
|
||
|
envWebhookToken = "UPDA_WEBHOOK_TOKEN"
|
||
|
|
||
|
flagServerUrl = "server-url"
|
||
|
flagAdminUser = "admin-user"
|
||
|
flagAdminPass = "admin-pass"
|
||
|
flagWebhookId = "webhook-id"
|
||
|
flagWebhookToken = "webhook-token"
|
||
|
flagUpdatePageSize = "page-size"
|
||
|
|
||
|
flagRaw = "raw"
|
||
|
|
||
|
webhooksUrlPath = "/api/v1/webhooks"
|
||
|
updatesUrlPath = "/api/v1/updates"
|
||
|
)
|
||
|
|
||
|
func Start() {
|
||
|
var raw bool
|
||
|
var serverUrl string
|
||
|
var adminUser string
|
||
|
var adminPassword string
|
||
|
var webhookId string
|
||
|
var webhookToken string
|
||
|
var updatePageSize int64
|
||
|
|
||
|
rawFlag := &cli.BoolFlag{
|
||
|
Name: flagRaw,
|
||
|
Usage: "on success raw JSON data from response is returned",
|
||
|
Aliases: []string{"r"},
|
||
|
Value: false,
|
||
|
Destination: &raw,
|
||
|
}
|
||
|
|
||
|
serverUrlFlag := &cli.StringFlag{
|
||
|
Name: flagServerUrl,
|
||
|
Usage: "the server url (FQDN without context path)",
|
||
|
Required: true,
|
||
|
Aliases: []string{"s"},
|
||
|
EnvVars: []string{envServerUrl},
|
||
|
Destination: &serverUrl,
|
||
|
}
|
||
|
adminUserFlag := &cli.StringFlag{
|
||
|
Name: flagAdminUser,
|
||
|
Usage: "admin user",
|
||
|
Required: true,
|
||
|
Aliases: []string{"u"},
|
||
|
EnvVars: []string{envAdminUser},
|
||
|
Destination: &adminUser,
|
||
|
}
|
||
|
adminPasswordFlag := &cli.StringFlag{
|
||
|
Name: flagAdminPass,
|
||
|
Usage: "admin password",
|
||
|
Required: true,
|
||
|
Aliases: []string{"p"},
|
||
|
EnvVars: []string{envAdminPassword},
|
||
|
Destination: &adminPassword,
|
||
|
}
|
||
|
webhookIdFlag := &cli.StringFlag{
|
||
|
Name: flagWebhookId,
|
||
|
Usage: "webhook id",
|
||
|
Required: true,
|
||
|
Aliases: []string{"i"},
|
||
|
EnvVars: []string{envWebhookId},
|
||
|
Destination: &webhookId,
|
||
|
}
|
||
|
webhookTokenFlag := &cli.StringFlag{
|
||
|
Name: flagWebhookToken,
|
||
|
Usage: "webhook token",
|
||
|
Required: true,
|
||
|
Aliases: []string{"t"},
|
||
|
EnvVars: []string{envWebhookToken},
|
||
|
Destination: &webhookToken,
|
||
|
}
|
||
|
updatePageSizeFlag := &cli.Int64Flag{
|
||
|
Name: flagUpdatePageSize,
|
||
|
Usage: "update show page size",
|
||
|
Value: 10000,
|
||
|
Required: false,
|
||
|
Aliases: []string{"ps"},
|
||
|
Destination: &updatePageSize,
|
||
|
}
|
||
|
|
||
|
cli.VersionFlag = &cli.BoolFlag{
|
||
|
Name: "version",
|
||
|
Aliases: []string{"v"},
|
||
|
Usage: "show version",
|
||
|
}
|
||
|
app := &cli.App{
|
||
|
Name: name,
|
||
|
Usage: desc,
|
||
|
Version: version,
|
||
|
EnableBashCompletion: true,
|
||
|
Commands: []*cli.Command{
|
||
|
{
|
||
|
Name: "webhook",
|
||
|
Aliases: []string{"w"},
|
||
|
Usage: "Options for webhook",
|
||
|
Subcommands: []*cli.Command{
|
||
|
{
|
||
|
Name: "create",
|
||
|
Usage: "Creates a webhook",
|
||
|
Aliases: []string{"c"},
|
||
|
Flags: []cli.Flag{
|
||
|
serverUrlFlag,
|
||
|
adminUserFlag,
|
||
|
adminPasswordFlag,
|
||
|
rawFlag,
|
||
|
},
|
||
|
ArgsUsage: "<label> [<type (generic|diun, default: generic)>] [<ignore-host (true|false, default: false)>]",
|
||
|
Action: webhookCreate,
|
||
|
},
|
||
|
{
|
||
|
Name: "send",
|
||
|
Usage: "Sends data to a webhook",
|
||
|
Aliases: []string{"s"},
|
||
|
Flags: []cli.Flag{
|
||
|
serverUrlFlag,
|
||
|
webhookIdFlag,
|
||
|
webhookTokenFlag,
|
||
|
},
|
||
|
ArgsUsage: "<json payload>",
|
||
|
Action: webhookSend,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
Name: "update",
|
||
|
Aliases: []string{"u"},
|
||
|
Usage: "Options for update",
|
||
|
Subcommands: []*cli.Command{
|
||
|
{
|
||
|
Name: "show",
|
||
|
Usage: "Shows updates",
|
||
|
Aliases: []string{"s"},
|
||
|
Flags: []cli.Flag{
|
||
|
serverUrlFlag,
|
||
|
adminUserFlag,
|
||
|
adminPasswordFlag,
|
||
|
updatePageSizeFlag,
|
||
|
rawFlag,
|
||
|
},
|
||
|
Action: updateShow,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
if err := app.Run(os.Args); err != nil {
|
||
|
log.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func webhookCreate(cCtx *cli.Context) error {
|
||
|
if err := failIfFlagsNotPresent(cCtx, []string{flagServerUrl, flagAdminUser, flagAdminPass}); err != nil {
|
||
|
return cli.Exit(err, 1)
|
||
|
}
|
||
|
if !cCtx.Args().Present() {
|
||
|
return cli.Exit(errors.New("args required - try 'webhook create help'"), 1)
|
||
|
}
|
||
|
// validate label
|
||
|
label := cCtx.Args().First()
|
||
|
if label == "" || len(label) > 255 {
|
||
|
return cli.Exit(errors.New("label cannot be blank or only be 255 characters long"), 1)
|
||
|
}
|
||
|
|
||
|
// validate type
|
||
|
t := cCtx.Args().Get(1)
|
||
|
validTypes := []string{api.WebhookTypeGeneric.Value(), api.WebhookTypeDiun.Value()}
|
||
|
if t == "" {
|
||
|
t = api.WebhookTypeGeneric.Value()
|
||
|
}
|
||
|
|
||
|
if !util.FindInSlice(validTypes, t) {
|
||
|
return cli.Exit(errors.New(fmt.Sprintf("type must be one of %v", validTypes)), 1)
|
||
|
}
|
||
|
|
||
|
ignoreHost := cCtx.Args().Get(2) == "true"
|
||
|
|
||
|
// fully constructed payload
|
||
|
payload := api.CreateWebhookRequest{
|
||
|
Label: label,
|
||
|
Type: t,
|
||
|
IgnoreHost: ignoreHost,
|
||
|
}
|
||
|
|
||
|
var successRes api.WebhookSingleResponse
|
||
|
var errorRes api.ErrorResponse
|
||
|
url := cCtx.String(flagServerUrl) + webhooksUrlPath
|
||
|
client := resty.New()
|
||
|
client.SetDisableWarn(true)
|
||
|
res, err := client.R().
|
||
|
SetBasicAuth(cCtx.String(flagAdminUser), cCtx.String(flagAdminPass)).
|
||
|
SetHeader("Content-Type", "application/json").
|
||
|
SetBody(&payload).
|
||
|
SetResult(&successRes).
|
||
|
SetError(&errorRes).
|
||
|
Post(url)
|
||
|
|
||
|
if err != nil {
|
||
|
return cli.Exit(fmt.Errorf("error during webhook creation: %w", err), 1)
|
||
|
}
|
||
|
if !res.IsSuccess() {
|
||
|
return cli.Exit(fmt.Sprintf("error during webhook creation: (%d) %+v", res.StatusCode(), errorRes), 1)
|
||
|
}
|
||
|
|
||
|
if cCtx.Bool(flagRaw) {
|
||
|
fmt.Println(string(res.Body()))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
fmt.Printf("ID\t%v\n", successRes.Data.ID)
|
||
|
fmt.Printf("URL\t%v\n", fmt.Sprintf("%s/%s", url, successRes.Data.ID))
|
||
|
fmt.Printf("Token\t%v\n", successRes.Data.Token)
|
||
|
fmt.Printf("Type\t%v\n", successRes.Data.Type)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func webhookSend(cCtx *cli.Context) error {
|
||
|
if err := failIfFlagsNotPresent(cCtx, []string{flagServerUrl, flagWebhookId, flagWebhookToken}); err != nil {
|
||
|
return cli.Exit(err, 1)
|
||
|
}
|
||
|
|
||
|
if !cCtx.Args().Present() || cCtx.Args().Len() < 1 {
|
||
|
return cli.Exit(errors.New("args required - try 'webhook send help'"), 1)
|
||
|
}
|
||
|
// validate payload is valid json
|
||
|
payloadArg := cCtx.Args().First()
|
||
|
if payloadArg == "" {
|
||
|
return cli.Exit(errors.New("payload cannot be blank"), 1)
|
||
|
}
|
||
|
if !json.Valid([]byte(payloadArg)) {
|
||
|
return cli.Exit(errors.New("payload is not valid JSON"), 1)
|
||
|
}
|
||
|
|
||
|
var errorRes api.ErrorResponse
|
||
|
url := cCtx.String(flagServerUrl) + webhooksUrlPath + "/" + cCtx.String(flagWebhookId)
|
||
|
client := resty.New()
|
||
|
client.SetDisableWarn(true)
|
||
|
res, err := client.R().
|
||
|
SetHeader("Content-Type", "application/json").
|
||
|
SetHeader(server.HeaderWebhookToken, cCtx.String(flagWebhookToken)).
|
||
|
SetBody(payloadArg).
|
||
|
SetError(&errorRes).
|
||
|
Post(url)
|
||
|
|
||
|
if err != nil {
|
||
|
return cli.Exit(fmt.Errorf("error during webhook invocation: %w", err), 1)
|
||
|
}
|
||
|
if !res.IsSuccess() {
|
||
|
return cli.Exit(fmt.Sprintf("error during webhook invocation: (%d) %+v", res.StatusCode(), errorRes), 1)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func updateShow(cCtx *cli.Context) error {
|
||
|
if err := failIfFlagsNotPresent(cCtx, []string{flagServerUrl, flagAdminUser, flagAdminPass}); err != nil {
|
||
|
return cli.Exit(err, 1)
|
||
|
}
|
||
|
|
||
|
var successRes api.UpdateDataPageResponse
|
||
|
var errorRes api.ErrorResponse
|
||
|
url := cCtx.String(flagServerUrl) + updatesUrlPath + fmt.Sprintf("?pageSize=%d", cCtx.Int64(flagUpdatePageSize))
|
||
|
client := resty.New()
|
||
|
client.SetDisableWarn(true)
|
||
|
res, err := client.R().
|
||
|
SetBasicAuth(cCtx.String(flagAdminUser), cCtx.String(flagAdminPass)).
|
||
|
SetHeader("Content-Type", "application/json").
|
||
|
SetResult(&successRes).
|
||
|
SetError(&errorRes).
|
||
|
Get(url)
|
||
|
|
||
|
if err != nil {
|
||
|
return cli.Exit(fmt.Errorf("error during showing updates: %w", err), 1)
|
||
|
}
|
||
|
if !res.IsSuccess() {
|
||
|
return cli.Exit(fmt.Sprintf("error during showing updates: (%d) %+v", res.StatusCode(), errorRes), 1)
|
||
|
}
|
||
|
|
||
|
if cCtx.Bool(flagRaw) {
|
||
|
fmt.Println(string(res.Body()))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
w := tabwriter.NewWriter(os.Stdout, 10, 1, 1, ' ', tabwriter.Debug)
|
||
|
|
||
|
for _, u := range successRes.Data.Content {
|
||
|
if _, err = fmt.Fprintf(w, "%v\t %v\t %v\t %v\t %v\n", u.Application, u.Host, u.Provider, u.Version, u.State); err != nil {
|
||
|
return cli.Exit(fmt.Sprintf("error during showing updates: %+v", errorRes), 1)
|
||
|
}
|
||
|
}
|
||
|
if err = w.Flush(); err != nil {
|
||
|
return cli.Exit(fmt.Sprintf("error during showing updates: %+v", errorRes), 1)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func failIfFlagsNotPresent(cCtx *cli.Context, flagKeys []string) error {
|
||
|
if flagKeys == nil {
|
||
|
return errors.New("flagKeys cannot be null")
|
||
|
}
|
||
|
|
||
|
for _, key := range flagKeys {
|
||
|
if cCtx.String(key) == "" {
|
||
|
return errors.New(fmt.Sprintf("'%v' is required but blank", key))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|