diff --git a/go.mod b/go.mod index e89e88c..f4d7fd5 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,9 @@ module github.com/G-core/gcore-cli go 1.21.5 - require ( - github.com/G-Core/FastEdge-client-sdk-go v0.0.0-20240304075046-db0c8c3d17e7 + github.com/G-Core/FastEdge-client-sdk-go v0.2.0 github.com/alecthomas/assert v1.0.0 - github.com/docker/go-units v0.5.0 github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.16.0 github.com/golang-module/carbon/v2 v2.3.10 @@ -23,8 +21,8 @@ require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/getkin/kin-openapi v0.123.0 // indirect - github.com/go-openapi/jsonpointer v0.20.2 // indirect - github.com/go-openapi/swag v0.22.9 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -37,7 +35,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/oapi-codegen/runtime v1.1.1 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.0 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -47,7 +45,7 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 // indirect + golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect golang.org/x/sys v0.18.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 0865fc5..376c02e 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ -github.com/G-Core/FastEdge-client-sdk-go v0.0.0-20240214130448-d87df1e38764 h1:7CATrk7BJpU1t4wr2avczZaX1KrwsjhArBKBaiSQN2M= -github.com/G-Core/FastEdge-client-sdk-go v0.0.0-20240214130448-d87df1e38764/go.mod h1:ggyUVhy8/OCMBY4nbm7n9qDoPioROCk4vHhDJq9w7qE= -github.com/G-Core/FastEdge-client-sdk-go v0.0.0-20240304075046-db0c8c3d17e7 h1:99yyAfaF6OV2ghz75yE72LwdzxP6gUYa3hL7XkObb5s= -github.com/G-Core/FastEdge-client-sdk-go v0.0.0-20240304075046-db0c8c3d17e7/go.mod h1:ggyUVhy8/OCMBY4nbm7n9qDoPioROCk4vHhDJq9w7qE= +github.com/G-Core/FastEdge-client-sdk-go v0.2.0 h1:OHibIpVO/7kEG1eKy+5i0jcb1urPNm9EKUdH84entpk= +github.com/G-Core/FastEdge-client-sdk-go v0.2.0/go.mod h1:ggyUVhy8/OCMBY4nbm7n9qDoPioROCk4vHhDJq9w7qE= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/alecthomas/assert v1.0.0 h1:3XmGh/PSuLzDbK3W2gUbRXwgW5lqPkuqvRgeQ30FI5o= github.com/alecthomas/assert v1.0.0/go.mod h1:va/d2JC+M7F6s+80kl/R3G7FUiW6JzUO+hPhLyJ36ZY= @@ -17,8 +15,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= @@ -29,10 +25,10 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8= github.com/getkin/kin-openapi v0.123.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= -github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= -github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= -github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= -github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/golang-module/carbon/v2 v2.3.10 h1:C25x4A4UrIch6bisV3j37eU+op5+cp4gw/Fffv5c/FA= @@ -72,8 +68,8 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= +github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -104,20 +100,22 @@ github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKk github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo= -golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= diff --git a/internal/commands/fastedge/app.go b/internal/commands/fastedge/app.go index f0a8d00..f4ea89e 100644 --- a/internal/commands/fastedge/app.go +++ b/internal/commands/fastedge/app.go @@ -38,9 +38,6 @@ uploading binary using "--file ". To load file from stdin, use "-" as if err != nil { return err } - if app.Plan == nil { - return errors.New("plan must be specified") - } if app.Binary == nil { file, err := cmd.Flags().GetString("file") if err != nil { @@ -61,7 +58,7 @@ uploading binary using "--file ". To load file from stdin, use "-" as return fmt.Errorf("adding the app: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("adding the app: %s", string(rsp.Body)) + return fmt.Errorf("adding the app: %s", extractErrorMessage(rsp.Body)) } if output.Format(cmd) == output.FmtJSON { @@ -124,7 +121,7 @@ uploading binary using "--file ". To load file from stdin, use "-" as return fmt.Errorf("updating the app: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("updating the app: %s", string(rsp.Body)) + return fmt.Errorf("updating the app: %s", extractErrorMessage(rsp.Body)) } if output.Format(cmd) == output.FmtJSON { @@ -150,12 +147,12 @@ uploading binary using "--file ". To load file from stdin, use "-" as Short: "Show list of client's apps", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - rsp, err := client.ListAppsWithResponse(context.Background()) + rsp, err := client.ListAppsWithResponse(context.Background(), &sdk.ListAppsParams{}) if err != nil { return fmt.Errorf("getting the list of apps: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("getting the list of apps: %s", string(rsp.Body)) + return fmt.Errorf("getting the list of apps: %s", extractErrorMessage(rsp.Body)) } if output.Format(cmd) == output.FmtJSON { @@ -163,14 +160,14 @@ uploading binary using "--file ". To load file from stdin, use "-" as return nil } - if len(*rsp.JSON200) == 0 { + if len(rsp.JSON200.Apps) == 0 { fmt.Printf("you have no apps\n") return nil } - table := make([][]string, len(*rsp.JSON200)+1) + table := make([][]string, len(rsp.JSON200.Apps)+1) table[0] = []string{"ID", "Status", "Name", "Url"} - for i, app := range *rsp.JSON200 { + for i, app := range rsp.JSON200.Apps { table[i+1] = []string{ strconv.FormatInt(app.Id, 10), appStatusToString(app.Status), @@ -187,8 +184,8 @@ uploading binary using "--file ". To load file from stdin, use "-" as Use: "show ", Aliases: []string{"get"}, Short: "Show app details", - Long: `Show app properties. This command doesn't show app call statisrics. -To see statistics, use "fastedge stat app_calls" and "fastedge stat app_duration" + Long: `Show app properties. This command doesn't show app call statistics. +To see statistics, use "fastedge stats app_calls" and "fastedge stats app_duration" commands.`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { @@ -204,7 +201,7 @@ commands.`, return fmt.Errorf("getting app detail: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("getting app details: %s", string(rsp.Body)) + return fmt.Errorf("getting app details: %s", extractErrorMessage(rsp.Body)) } if output.Format(cmd) == output.FmtJSON { @@ -247,7 +244,7 @@ commands.`, return fmt.Errorf("enabling app: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("enabling app: %s", string(rsp.Body)) + return fmt.Errorf("enabling app: %s", extractErrorMessage(rsp.Body)) } if output.Format(cmd) == output.FmtJSON { @@ -278,7 +275,7 @@ commands.`, return fmt.Errorf("disabling app: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("disabling app: %s", string(rsp.Body)) + return fmt.Errorf("disabling app: %s", extractErrorMessage(rsp.Body)) } if output.Format(cmd) == output.FmtJSON { @@ -314,7 +311,7 @@ so if you don't want this to happen, consider disabling the app to keep binary r return fmt.Errorf("deleting app: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("deleting app: %s", string(rsp.Body)) + return fmt.Errorf("deleting app: %s", extractErrorMessage(rsp.Body)) } if output.Format(cmd) == output.FmtJSON { @@ -343,7 +340,6 @@ func appPropertiesFlags(cmd *cobra.Command) { cmd.Flags().String("name", "", "App name") cmd.Flags().Int64("binary", 0, "Wasm binary id") cmd.Flags().String("file", "", "Wasm binary filename ('-' means stdin)") - cmd.Flags().String("plan", "", "Plan name") cmd.Flags().Bool("disabled", false, "Set status to 'disabled'") cmd.Flags().StringArray("env", nil, "Environment, in name=value format") cmd.Flags().StringSlice("rsp_headers", nil, "Response headers to add, in name=value format") @@ -360,14 +356,6 @@ func parseAppProperties(cmd *cobra.Command) (sdk.App, error) { app.Name = &name } - plan, err := cmd.Flags().GetString("plan") - if err != nil { - return app, err - } - if plan != "" { - app.Plan = &plan - } - binID, err := cmd.Flags().GetInt64("binary") if err != nil { return app, err @@ -447,7 +435,7 @@ func outputMap(m *map[string]string, title string) { } func getAppIdByName(appName string) (int64, error) { - idRsp, err := client.GetAppIdByNameWithResponse(context.Background(), appName) + idRsp, err := client.ListAppsWithResponse(context.Background(), &sdk.ListAppsParams{Name: &appName}) if err != nil { return 0, fmt.Errorf("api response: %w", err) } @@ -457,5 +445,25 @@ func getAppIdByName(appName string) (int64, error) { if idRsp.JSON200 == nil { return 0, fmt.Errorf("app '%s' not found", appName) } - return *idRsp.JSON200, nil + if len(idRsp.JSON200.Apps) != 1 { + return 0, fmt.Errorf("app '%s' not found", appName) + } + return idRsp.JSON200.Apps[0].Id, nil +} + +func getAppByName(appName string) (sdk.AppShort, error) { + idRsp, err := client.ListAppsWithResponse(context.Background(), &sdk.ListAppsParams{Name: &appName}) + if err != nil { + return sdk.AppShort{}, fmt.Errorf("api response: %w", err) + } + if idRsp.StatusCode() != http.StatusOK { + return sdk.AppShort{}, fmt.Errorf("%s", string(idRsp.Body)) + } + if idRsp.JSON200 == nil { + return sdk.AppShort{}, fmt.Errorf("app '%s' not found", appName) + } + if len(idRsp.JSON200.Apps) != 1 { + return sdk.AppShort{}, fmt.Errorf("app '%s' not found", appName) + } + return idRsp.JSON200.Apps[0], nil } diff --git a/internal/commands/fastedge/binary.go b/internal/commands/fastedge/binary.go index c489bce..4a822ef 100644 --- a/internal/commands/fastedge/binary.go +++ b/internal/commands/fastedge/binary.go @@ -10,6 +10,7 @@ import ( "github.com/spf13/cobra" + sdk "github.com/G-Core/FastEdge-client-sdk-go" "github.com/G-core/gcore-cli/internal/output" ) @@ -31,12 +32,12 @@ func binary() *cobra.Command { Short: "Show list of client's binaries", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - rsp, err := client.ListBinariesWithResponse(context.Background()) + rsp, err := client.ListBinariesWithResponse(context.Background(), &sdk.ListBinariesParams{}) if err != nil { return fmt.Errorf("getting the list of binaries: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("getting the list of binaries: %s", string(rsp.Body)) + return fmt.Errorf("getting the list of binaries: %s", extractErrorMessage(rsp.Body)) } if output.Format(cmd) == output.FmtJSON { @@ -44,17 +45,18 @@ func binary() *cobra.Command { return nil } - if len(*rsp.JSON200) == 0 { + if len(rsp.JSON200.Binaries) == 0 { fmt.Printf("you have no binaries\n") return nil } - table := make([][]string, len(*rsp.JSON200)+1) - table[0] = []string{"ID", "Status", "Unreferenced since"} - for i, bin := range *rsp.JSON200 { + table := make([][]string, len(rsp.JSON200.Binaries)+1) + table[0] = []string{"ID", "Status", "Name", "Unreferenced since"} + for i, bin := range rsp.JSON200.Binaries { table[i+1] = []string{ strconv.FormatInt(bin.Id, 10), binStatusToString(bin.Status), + unrefString(bin.Name), unrefString(bin.UnrefSince), } } @@ -105,7 +107,7 @@ If this flag is omitted, file contant is read from stdin.`, return fmt.Errorf("getting the list of plans: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("getting the list of plans: %s", string(rsp.Body)) + return fmt.Errorf("getting the list of plans: %s", extractErrorMessage(rsp.Body)) } if output.Format(cmd) == output.FmtJSON { @@ -116,8 +118,14 @@ If this flag is omitted, file contant is read from stdin.`, fmt.Printf( "Status:\t\t%s\nSource lang:\t%s\n", binStatusToString(rsp.JSON200.Status), - srcLangToString(rsp.JSON200.Type), + srcLangToString(rsp.JSON200.Source), ) + if rsp.JSON200.Name != nil && *rsp.JSON200.Name != "" { + fmt.Printf("Name:\t\t%s\n", *rsp.JSON200.Name) + } + if rsp.JSON200.Descr != nil && *rsp.JSON200.Descr != "" { + fmt.Printf("Description:\t%s\n", *rsp.JSON200.Descr) + } if rsp.JSON200.UnrefSince != nil { fmt.Printf("Unref since:\t%s\n", *rsp.JSON200.UnrefSince) } @@ -146,7 +154,7 @@ If this flag is omitted, file contant is read from stdin.`, return fmt.Errorf("getting the list of plans: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("getting the list of plans: %s", string(rsp.Body)) + return fmt.Errorf("getting the list of plans: %s", extractErrorMessage(rsp.Body)) } if output.Format(cmd) == output.FmtJSON { @@ -184,10 +192,10 @@ func uploadBinary(src string) (int64, error) { return 0, fmt.Errorf("cannot upload the binary: %w", err) } if rsp.StatusCode() != http.StatusOK { - return 0, fmt.Errorf("cannot upload the binary: %s", string(rsp.Body)) + return 0, fmt.Errorf("cannot upload the binary: %s", extractErrorMessage(rsp.Body)) } - return *rsp.JSON200, nil + return rsp.JSON200.Id, nil } func binStatusToString(s int) string { diff --git a/internal/commands/fastedge/fastedge.go b/internal/commands/fastedge/fastedge.go index cd0caff..82982bd 100644 --- a/internal/commands/fastedge/fastedge.go +++ b/internal/commands/fastedge/fastedge.go @@ -2,8 +2,11 @@ package fastedge import ( "context" + "encoding/json" "fmt" "net/http" + "runtime/debug" + "strings" "github.com/golang-module/carbon/v2" "github.com/spf13/cobra" @@ -11,6 +14,11 @@ import ( sdk "github.com/G-Core/FastEdge-client-sdk-go" ) +const ( + versionHeaderName = "Fastedge-Sdk-Version" + SDKpackage = "github.com/G-Core/FastEdge-client-sdk-go" +) + var client *sdk.ClientWithResponses // top-level FastEdge command @@ -30,6 +38,7 @@ func Commands(baseUrl string, authFunc func(ctx context.Context, req *http.Reque client, err = sdk.NewClientWithResponses( url, sdk.WithRequestEditorFn(authFunc), + sdk.WithRequestEditorFn(addSDKversionHeader), ) if err != nil { return fmt.Errorf("cannot init SDK: %w", err) @@ -46,10 +55,36 @@ func Commands(baseUrl string, authFunc func(ctx context.Context, req *http.Reque cmdFastedge.PersistentFlags().BoolVar(&local, "local", false, "local testing") cmdFastedge.PersistentFlags().MarkHidden("local") - cmdFastedge.AddCommand(app(), binary(), plan(), stat(), logs()) + cmdFastedge.AddCommand(app(), binary(), stat(), logs()) return cmdFastedge, nil } func newPointer[T any](val T) *T { return &val } + +func addSDKversionHeader(ctx context.Context, req *http.Request) error { + bi, ok := debug.ReadBuildInfo() + if ok { + for _, dep := range bi.Deps { + if dep.Path == SDKpackage { + ver := strings.SplitN(dep.Version, "-", 2) // drop revision info + req.Header.Set(versionHeaderName, ver[0]) + return nil + } + } + } + return nil +} + +type errResponse struct { + Error string `json:"error"` +} + +func extractErrorMessage(rspBuf []byte) string { + var rsp errResponse + if err := json.Unmarshal(rspBuf, &rsp); err == nil { + return rsp.Error + } + return string(rspBuf) +} diff --git a/internal/commands/fastedge/logs.go b/internal/commands/fastedge/logs.go index 809f012..f7aec02 100644 --- a/internal/commands/fastedge/logs.go +++ b/internal/commands/fastedge/logs.go @@ -29,7 +29,7 @@ func appLogsFilterFlags(cmd *cobra.Command) { func logs() *cobra.Command { var ( from, to time.Time - sort *sdk.GetV1AppsIdLogsParamsSort + sort *sdk.ListLogsParamsSort edge *string clientIp *string ) @@ -72,7 +72,7 @@ This command allows you filtering by edge name, client ip and time range.`, } if sortFlag != "" { - logParamSort := sdk.GetV1AppsIdLogsParamsSort(sortFlag) + logParamSort := sdk.ListLogsParamsSort(sortFlag) if logParamSort != sdk.Asc && logParamSort != sdk.Desc { return errors.New("invalid value for `sort` expected asc or desc") } @@ -94,10 +94,10 @@ This command allows you filtering by edge name, client ip and time range.`, return fmt.Errorf("cannot find app by name: %w", err) } - rsp, err := client.GetV1AppsIdLogsWithResponse( + rsp, err := client.ListLogsWithResponse( context.Background(), id, - &sdk.GetV1AppsIdLogsParams{ + &sdk.ListLogsParams{ From: &from, To: &to, Edge: edge, @@ -109,7 +109,7 @@ This command allows you filtering by edge name, client ip and time range.`, return fmt.Errorf("getting app logs: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("getting app logs: %s", string(rsp.Body)) + return fmt.Errorf("getting app logs: %s", extractErrorMessage(rsp.Body)) } if output.Format(cmd) == output.FmtJSON { @@ -126,8 +126,8 @@ This command allows you filtering by edge name, client ip and time range.`, if rsp.JSON200.Logs != nil { printLogs(rsp.JSON200.Logs) - for *rsp.JSON200.CurrentPage < *rsp.JSON200.TotalPages { - fmt.Printf("Displaying %d/%d logs, load next page? (Y/n) ", *rsp.JSON200.CurrentPage**rsp.JSON200.PageSize, *rsp.JSON200.TotalPages**rsp.JSON200.PageSize) + for *rsp.JSON200.Offset < *rsp.JSON200.TotalCount { + fmt.Printf("Displaying %d/%d logs, load next page? (Y/n) ", *rsp.JSON200.Offset, *rsp.JSON200.TotalCount) text, _ := reader.ReadString('\n') text = strings.ToLower(strings.TrimSpace(text)) @@ -139,19 +139,23 @@ This command allows you filtering by edge name, client ip and time range.`, fmt.Print("\033[2K\033[1A\033[2K\033[1A\n") // Increment the page number - page := int64(*rsp.JSON200.CurrentPage + 1) + var ( + offset = int32(*rsp.JSON200.Offset + 25) + limit = int32(25) + ) // Call the API again with the new page number - rsp, err = client.GetV1AppsIdLogsWithResponse( + rsp, err = client.ListLogsWithResponse( context.Background(), id, - &sdk.GetV1AppsIdLogsParams{ - From: &from, - To: &to, - Edge: edge, - Sort: sort, - ClientIp: clientIp, - CurrentPage: &page, + &sdk.ListLogsParams{ + From: &from, + To: &to, + Edge: edge, + Sort: sort, + ClientIp: clientIp, + Offset: &offset, + Limit: &limit, }, ) if err != nil { @@ -187,7 +191,7 @@ This command allows you filtering by edge name, client ip and time range.`, return fmt.Errorf("enabling logging: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("enabling logging: %s", string(rsp.Body)) + return fmt.Errorf("enabling logging: %s", extractErrorMessage(rsp.Body)) } rsp1, err := client.GetAppWithResponse( @@ -198,7 +202,7 @@ This command allows you filtering by edge name, client ip and time range.`, return fmt.Errorf("getting app detail: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("getting app details: %s", string(rsp.Body)) + return fmt.Errorf("getting app details: %s", extractErrorMessage(rsp.Body)) } if rsp1.JSON200.DebugUntil == nil { @@ -228,7 +232,7 @@ This command allows you filtering by edge name, client ip and time range.`, return fmt.Errorf("disabling logging: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("disabling logging: %s", string(rsp.Body)) + return fmt.Errorf("disabling logging: %s", extractErrorMessage(rsp.Body)) } fmt.Printf("Logging for app %d disabled\n", id) diff --git a/internal/commands/fastedge/plan.go b/internal/commands/fastedge/plan.go deleted file mode 100644 index e5cd01f..0000000 --- a/internal/commands/fastedge/plan.go +++ /dev/null @@ -1,94 +0,0 @@ -package fastedge - -import ( - "context" - "fmt" - "net/http" - "time" - - "github.com/docker/go-units" - "github.com/spf13/cobra" - - "github.com/G-core/gcore-cli/internal/output" -) - -func plan() *cobra.Command { - var cmdPlan = &cobra.Command{ - Use: "plan ", - Short: "Plan-related commands", - Long: `Plan is a set of limits for the application. Different plans may imply different rates, -so chose the plan to minimise costs. However, using plan smaller than needed -may result in excessive timeouts and out-of-memory errors.`, - Args: cobra.MinimumNArgs(1), - } - - var cmdList = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "Show list of available plans", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - rsp, err := client.ListPlansWithResponse(context.Background()) - if err != nil { - return fmt.Errorf("getting the list of plans: %w", err) - } - if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("getting the list of plans: %s", string(rsp.Body)) - } - - if output.Format(cmd) == output.FmtJSON { - fmt.Println(string(rsp.Body)) - return nil - } - - if len(*rsp.JSON200) == 0 { - fmt.Printf("there are no plans\n") - return nil - } - - fmt.Println("Plan name") - for _, plan := range *rsp.JSON200 { - fmt.Println(plan) - } - - return nil - }, - } - - var cmdGet = &cobra.Command{ - Use: "show ", - Aliases: []string{"get"}, - Short: "Show plan details", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - rsp, err := client.GetPlanWithResponse( - context.Background(), - args[0], - ) - if err != nil { - return fmt.Errorf("cannot get plan details: %w", err) - } - if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("cannot get plan details: %s", string(rsp.Body)) - } - - if output.Format(cmd) == output.FmtJSON { - fmt.Println(string(rsp.Body)) - return nil - } - - fmt.Printf( - "Memory limit:\t%s\nMax duration:\t%s\nMax requests:\t%d\n", - units.HumanSize(float64(rsp.JSON200.MemLimit)), - time.Duration(rsp.JSON200.MaxDuration)*time.Millisecond, - rsp.JSON200.MaxSubrequests, - ) - - return nil - }, - } - - cmdPlan.AddCommand(cmdList, cmdGet) - - return cmdPlan -} diff --git a/internal/commands/fastedge/stats.go b/internal/commands/fastedge/stats.go index 56644d4..23d1c1d 100644 --- a/internal/commands/fastedge/stats.go +++ b/internal/commands/fastedge/stats.go @@ -26,7 +26,7 @@ func stat() *cobra.Command { return fmt.Errorf("getting the statistics: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("getting the statistics: %s", string(rsp.Body)) + return fmt.Errorf("getting the statistics: %s", extractErrorMessage(rsp.Body)) } if output.Format(cmd) == output.FmtJSON { @@ -56,7 +56,7 @@ func stat() *cobra.Command { } var cmdCalls = &cobra.Command{ - Use: "calls ", + Use: "calls []", Aliases: []string{"calls"}, Short: "Show app calls statistic", Long: `Show number of app calls, grouped by time slots and HTTP statuses. @@ -65,11 +65,15 @@ but you can change reporting interval using "--from" and "--to" flags (specifying date/time in format "YYYY-MM-DD HH:mm:SS", where either date or time, can be omitted, or as UNIX timestamp) and reporting step duration with flag "--step" (in seconds).`, - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - id, err := getAppIdByName(args[0]) - if err != nil { - return fmt.Errorf("cannot find app by name: %w", err) + var appId *int64 + if len(args) > 0 { + id, err := getAppIdByName(args[0]) + if err != nil { + return fmt.Errorf("cannot find app by name: %w", err) + } + appId = &id } from, err := parseTimeFlag(cmd, "from") @@ -87,10 +91,10 @@ can be omitted, or as UNIX timestamp) and reporting step duration with flag return fmt.Errorf("cannot parse reporting step: %w", err) } - rsp, err := client.AppCallsWithResponse( + rsp, err := client.StatsCallsWithResponse( context.Background(), - id, - &sdk.AppCallsParams{ + &sdk.StatsCallsParams{ + Id: appId, From: from, To: to, Step: step, @@ -100,7 +104,7 @@ can be omitted, or as UNIX timestamp) and reporting step duration with flag return fmt.Errorf("cannot get statistics: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("cannot get statistics: %s", string(rsp.Body)) + return fmt.Errorf("cannot get statistics: %s", extractErrorMessage(rsp.Body)) } if output.Format(cmd) == output.FmtJSON { @@ -108,7 +112,7 @@ can be omitted, or as UNIX timestamp) and reporting step duration with flag return nil } - if len(*rsp.JSON200) == 0 { + if len(rsp.JSON200.Stats) == 0 { fmt.Println("No data to report") return nil } @@ -116,10 +120,9 @@ can be omitted, or as UNIX timestamp) and reporting step duration with flag // we don't know which statuses we see, so collect the info about statuses // and make sparse matrix for counts by status statusCols := make(map[int]int) - counts := make([][]int, len(*rsp.JSON200)) - for i := range *rsp.JSON200 { + counts := make([][]int, len(rsp.JSON200.Stats)) + for i, slot := range rsp.JSON200.Stats { line := make([]int, len(statusCols)) - slot := (*rsp.JSON200)[i] for _, count := range slot.CountByStatus { col, ok := statusCols[count.Status] if ok { @@ -148,11 +151,11 @@ can be omitted, or as UNIX timestamp) and reporting step duration with flag } // convert matrix to output table, observing correct column index - table := make([][]string, len(*rsp.JSON200)+1) + table := make([][]string, len(rsp.JSON200.Stats)+1) table[0] = titles - for i := range *rsp.JSON200 { + for i := range rsp.JSON200.Stats { line := make([]string, len(statusCols)+1) - line[0] = (*rsp.JSON200)[i].Time.Format("2006-01-02T15:04:05") + line[0] = rsp.JSON200.Stats[i].Time.Format("2006-01-02T15:04:05") for j, count := range counts[i] { line[index[j]+1] = strconv.Itoa(count) } @@ -169,7 +172,7 @@ can be omitted, or as UNIX timestamp) and reporting step duration with flag statFlags(cmdCalls) var cmdDuration = &cobra.Command{ - Use: "duration ", + Use: "duration []", Aliases: []string{"duration", "time", "timing"}, Short: "Show app execution duration", Long: `Show duration of app calls, grouped by time slots. All times are in msec @@ -178,11 +181,15 @@ but you can change reporting interval using "--from" and "--to" flags (specifying date/time in format "YYYY-MM-DD HH:mm:SS", where either date or time, can be omitted, or as UNIX timestamp) and reporting step duration with flag "--step" (in seconds).`, - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - id, err := getAppIdByName(args[0]) - if err != nil { - return fmt.Errorf("cannot find app by name: %w", err) + var appId *int64 + if len(args) > 0 { + id, err := getAppIdByName(args[0]) + if err != nil { + return fmt.Errorf("cannot find app by name: %w", err) + } + appId = &id } from, err := parseTimeFlag(cmd, "from") @@ -200,10 +207,10 @@ can be omitted, or as UNIX timestamp) and reporting step duration with flag return fmt.Errorf("cannot parse reporting step: %w", err) } - rsp, err := client.AppDurationWithResponse( + rsp, err := client.StatsDurationWithResponse( context.Background(), - id, - &sdk.AppDurationParams{ + &sdk.StatsDurationParams{ + Id: appId, From: from, To: to, Step: step, @@ -213,7 +220,7 @@ can be omitted, or as UNIX timestamp) and reporting step duration with flag return fmt.Errorf("cannot get statistics: %w", err) } if rsp.StatusCode() != http.StatusOK { - return fmt.Errorf("cannot get statistics: %s", string(rsp.Body)) + return fmt.Errorf("cannot get statistics: %s", extractErrorMessage(rsp.Body)) } if output.Format(cmd) == output.FmtJSON { @@ -221,14 +228,14 @@ can be omitted, or as UNIX timestamp) and reporting step duration with flag return nil } - if len(*rsp.JSON200) == 0 { + if len(rsp.JSON200.Stats) == 0 { fmt.Println("No data to report") return nil } - table := make([][]string, len(*rsp.JSON200)+1) + table := make([][]string, len(rsp.JSON200.Stats)+1) table[0] = []string{"Period start (UTC)", "Min", "Avg", "Median", "75%", "90%", "Max"} - for i, d := range *rsp.JSON200 { + for i, d := range rsp.JSON200.Stats { table[i+1] = []string{ d.Time.Format("2006-01-02T15:04:05"), scaleToMsec(d.Min), diff --git a/internal/core/root.go b/internal/core/root.go index 2409a9f..1eddf46 100644 --- a/internal/core/root.go +++ b/internal/core/root.go @@ -69,6 +69,16 @@ func Execute() { } } + if !strings.Contains(*apiKey, "$") { + return &errors.CliError{ + Message: "Malformed API key", + Hint: "If you specified API key using '-a' option and GCORE_APIKEY env variable,\n" + + "please make sure that you are using single quotes to prevent shell\n" + + "parameter expansion", + Code: 1, + } + } + return nil } @@ -79,6 +89,7 @@ func Execute() { } rootCmd.AddCommand(fastedgeCmd) + cobra.EnableTraverseRunHooks = true // make sure all parentPersistentPreRun executed err = rootCmd.Execute() if err != nil { cliErr, ok := err.(*errors.CliError)