-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ADDED] system model command #8284
Changes from 33 commits
e1d4211
25389b6
8e516a2
a989e4e
8c78182
59821db
62c8f58
c707a22
d149bff
6377c27
cea9125
f19f89a
dabd8b7
88d8809
6fd6987
c6b2dea
468fa1c
1bd4baf
183e62a
ff6e87c
fbe5ea2
45f0132
5f44d18
4c7b439
20ac6ef
f44ef28
0e5787a
a3e9cc8
d784e1d
d0261a3
a42df4d
e04c3e8
60427ca
0ede87c
f1f2c41
67054c7
eda44e5
aad1845
8db6406
100c595
5e487f2
436f2e8
b3afb62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"meshery-provider":"Meshery","token":"eyJhY2Nlc3NfdG9rZW4iOiJleUpoYkdjaU9pSlNVekkxTmlJc0ltdHBaQ0k2SW5CMVlteHBZenBsT0dWbU5ERmpNeTFpWldWbUxUUmlZakV0T0dVNE1DMHpOakExTVRZeU4yTTJNakVpTENKMGVYQWlPaUpLVjFRaWZRLmV5SmhkV1FpT2x0ZExDSmpiR2xsYm5SZmFXUWlPaUp0WlhOb1pYSjVMV05zYjNWa0lpd2laWGh3SWpveE5qSXlPREk1TlRRMExDSmxlSFFpT250OUxDSnBZWFFpT2pFMk1qSTRNalU1TkRNc0ltbHpjeUk2SW1oMGRIQnpPaTh2YldWemFHVnllUzVzWVhsbGNqVXVhVzh2YUhsa2NtRXZJaXdpYW5ScElqb2lPRGMxT0RGbVpXSXROMlZpTnkwMFlqSTFMV0l3TURndE9XWTJaVEE0WXpabFkyVTJJaXdpYm1KbUlqb3hOakl5T0RJMU9UUXpMQ0p6WTNBaU9sc2liM0JsYm1sa0lpd2liMlptYkdsdVpTSmRMQ0p6ZFdJaU9pSmpSMncxWkZoT2IyTXliSFZhTWtaNVlWaHNhRHBhTW13d1lVaFdhU0o5Lk90aDJwYkJFNmFBcnBfUFVwR3E3b2ZsaEVWYmdsdTAtamdXNG44eWxHeVVTandOc0k4SmdoallIVGU5YjlUSzhWQUhoNVRyT0YwV1VRb0h4QVJGUmN6OHl2ZEdpbm1HcUZEZTd6RVpoSjZHZmNlZFl6bmpCc3FvVWthMTNXYzhvM0J2bGR2T2gtTjFGNzdHM3ZLenI0UEJaM2pXRHVEeWpjSUJnOTJVUzd0Nlg5Ymd6YklrT3lOOVhpWGVVNXQtbEJIamt2cklRazhqdWRKaTliOHVGaVBuMmdIMDVJbnhUdFJtSlFJdUhvSzV2WmxFQW0xN1J6ZER4WVI0cndqeTBqanFWdXdvWnBjbUJQM1dUNjdIVHhkYmo5N3hZM2IzNHh5ZFkxeVFVS09XR1NOckZVeXhMbW9QMmJUM24tQ0dVczJ1SWhnZExXNlZlNVQ1LV9tSGY0Z212X0NGWlFNelRsbjRFVmw2bTUxdjFxNXJzQmdfWmFuVmtXdGNHWF9ZSGs3WHpKdndXRDhvSmt5NzBleGUwYXJ3cmg2bjJkLU9jMi1Jc1F2OTBFM1hYeHBJcWxrckNfU3NiM1NpOU1jM1ptal9HY2JtOHVHbUZEejhaZEYxUEdpeDdKTjM3TzJyQnpaVldRaHFrZTV6MW42VUVITXJGSGJBNXBKVkxzUmE0ZUNBaFdwODVlZVV3ZjlUMnByc3FzNHBaMkh0eVpSMlBTdGFLZVFFai1SUXdvRHpDTEN4Zm85RnBvbEN6WmN3ZzRvLXhrb0Q0aS1MczIzODd0dm5xSTVESl8xaUlMX1hNTHByZXJtcDdxeGV2NEVDOW9abzdWenZmTDd4cDZTcnhIaldZQVpuZS12eURjQlhNZUlSMVVoeVdVZDQtaWJfZmxzdFVEME5XVV9ZIiwidG9rZW5fdHlwZSI6ImJlYXJlciIsInJlZnJlc2hfdG9rZW4iOiJXS3pZWW5BQkVJQkduekNfaWR2VW1IZUtsZlgzLWxjWm12TzBxY2ZCNlRzLm5kNXhXUFFIeWVTcTY0OUV2dy1tX2t3WDdqYWF1RDZiSExXTW9fQVhxZVUiLCJleHBpcnkiOiIyMDIxLTA2LTA0VDE3OjU5OjAzLjg0ODAyODAwOVoifQ"} | ||
alphaX86 marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"page":1,"page_size":1,"total_count":1,"models":[{"name":"spire","version":"0.12.2","displayName":"SPIRE","category":{"name":"Security \u0026 Compliance","metadata":null},"metadata":{"svgColor":"ui/public/static/img/meshmodels/identity-manager/color/workloadidentity-color.svg","svgWhite":"ui/public/static/img/meshmodels/identity-manager/white/workloadidentity-white.svg"},"duplicates":0}]} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,364 @@ | ||
// Copyright 2023 Layer5, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package system | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
"strings" | ||
|
||
"github.com/layer5io/meshery/mesheryctl/internal/cli/root/config" | ||
"github.com/layer5io/meshery/mesheryctl/pkg/utils" | ||
"github.com/layer5io/meshery/server/models" | ||
"github.com/layer5io/meshkit/models/meshmodel/core/v1alpha1" | ||
"github.com/manifoldco/promptui" | ||
"github.com/pkg/errors" | ||
log "github.com/sirupsen/logrus" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
"gopkg.in/yaml.v2" | ||
) | ||
|
||
var ( | ||
// flag used to specify the page number in list command | ||
pageNumberFlag int | ||
// flag used to specify format of output of view {model-name} command | ||
outFormatFlag string | ||
) | ||
|
||
// represents the `mesheryctl system model list` subcommand. | ||
var listModelCmd = &cobra.Command{ | ||
Use: "list", | ||
Short: "list models", | ||
Long: "list name of all registered models", | ||
Example: ` | ||
// View list of models | ||
mesheryctl system model list | ||
|
||
// View list of models with specified page number (25 models per page) | ||
mesheryctl system model list --page 2 | ||
`, | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
//Check prerequisite | ||
hcOptions := &HealthCheckOptions{ | ||
IsPreRunE: true, | ||
PrintLogs: false, | ||
Subcommand: cmd.Use, | ||
} | ||
hc, err := NewHealthChecker(hcOptions) | ||
if err != nil { | ||
return ErrHealthCheckFailed(err) | ||
} | ||
// execute healthchecks | ||
err = hc.RunPreflightHealthChecks() | ||
if err != nil { | ||
cmd.SilenceUsage = true | ||
return err | ||
} | ||
mctlCfg, err := config.GetMesheryCtl(viper.GetViper()) | ||
if err != nil { | ||
return err | ||
} | ||
ctx, err := mctlCfg.GetCurrentContext() | ||
if err != nil { | ||
return err | ||
} | ||
err = ctx.ValidateVersion() | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
if len(args) != 0 { | ||
return errors.New(utils.SystemModelSubError("this command takes no arguments\n", "list")) | ||
} | ||
mctlCfg, err := config.GetMesheryCtl(viper.GetViper()) | ||
if err != nil { | ||
log.Fatalln(err, "error processing config") | ||
} | ||
|
||
baseUrl := mctlCfg.GetBaseMesheryURL() | ||
var url string | ||
if cmd.Flags().Changed("page") { | ||
url = fmt.Sprintf("%s/api/meshmodels/models?page=%d", baseUrl, pageNumberFlag) | ||
} else { | ||
url = fmt.Sprintf("%s/api/meshmodels/models?pagesize=all", baseUrl) | ||
} | ||
req, err := utils.NewRequest(http.MethodGet, url, nil) | ||
if err != nil { | ||
utils.Log.Error(err) | ||
return err | ||
} | ||
|
||
resp, err := utils.MakeRequest(req) | ||
if err != nil { | ||
utils.Log.Error(err) | ||
return err | ||
} | ||
|
||
// defers the closing of the response body after its use, ensuring that the resources are properly released. | ||
defer resp.Body.Close() | ||
|
||
data, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
utils.Log.Error(err) | ||
return err | ||
} | ||
|
||
modelsResponse := &models.MeshmodelsAPIResponse{} | ||
err = json.Unmarshal(data, modelsResponse) | ||
if err != nil { | ||
utils.Log.Error(err) | ||
return err | ||
} | ||
|
||
header := []string{"Category", "Model", "Version"} | ||
rows := [][]string{} | ||
|
||
for _, model := range modelsResponse.Models { | ||
if len(model.DisplayName) > 0 { | ||
rows = append(rows, []string{model.Category.Name, model.Name, model.Version}) | ||
} | ||
} | ||
|
||
if len(rows) == 0 { | ||
// if no model is found | ||
utils.Log.Info("No model(s) found") | ||
} else { | ||
utils.PrintToTable(header, rows) | ||
} | ||
|
||
return nil | ||
}, | ||
} | ||
|
||
// represents the `mesheryctl system model view [model-name]` subcommand. | ||
var viewModelCmd = &cobra.Command{ | ||
Use: "view", | ||
Short: "view model", | ||
Long: "view a model queried by its name", | ||
Example: ` | ||
// View current provider | ||
mesheryctl system model view [model-name] | ||
`, | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
//Check prerequisite | ||
hcOptions := &HealthCheckOptions{ | ||
IsPreRunE: true, | ||
PrintLogs: false, | ||
Subcommand: cmd.Use, | ||
} | ||
hc, err := NewHealthChecker(hcOptions) | ||
if err != nil { | ||
return ErrHealthCheckFailed(err) | ||
} | ||
// execute healthchecks | ||
err = hc.RunPreflightHealthChecks() | ||
if err != nil { | ||
cmd.SilenceUsage = true | ||
return err | ||
} | ||
mctlCfg, err := config.GetMesheryCtl(viper.GetViper()) | ||
if err != nil { | ||
return err | ||
} | ||
ctx, err := mctlCfg.GetCurrentContext() | ||
if err != nil { | ||
return err | ||
} | ||
err = ctx.ValidateVersion() | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
}, | ||
Args: func(_ *cobra.Command, args []string) error { | ||
const errMsg = "Usage: mesheryctl system model view [model-name]\nRun 'mesheryctl system model view [model-name]' to view the model" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One line of usage for two subcommands isn't enough documentation or example, @zakisk There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still not answered... |
||
if len(args) == 0 { | ||
return fmt.Errorf("model name isn't specified\n\n%v", errMsg) | ||
} else if len(args) > 1 { | ||
return fmt.Errorf("too many arguments\n\n%v", errMsg) | ||
} | ||
return nil | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
mctlCfg, err := config.GetMesheryCtl(viper.GetViper()) | ||
if err != nil { | ||
log.Fatalln(err, "error processing config") | ||
} | ||
|
||
baseUrl := mctlCfg.GetBaseMesheryURL() | ||
model := args[0] | ||
|
||
url := fmt.Sprintf("%s/api/meshmodels/models/%s?pagesize=all", baseUrl, model) | ||
req, err := utils.NewRequest(http.MethodGet, url, nil) | ||
if err != nil { | ||
utils.Log.Error(err) | ||
return err | ||
} | ||
|
||
resp, err := utils.MakeRequest(req) | ||
if err != nil { | ||
utils.Log.Error(err) | ||
return err | ||
} | ||
|
||
// defers the closing of the response body after its use, ensuring that the resources are properly released. | ||
defer resp.Body.Close() | ||
|
||
data, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
utils.Log.Error(err) | ||
return err | ||
} | ||
|
||
modelsResponse := &models.MeshmodelsAPIResponse{} | ||
err = json.Unmarshal(data, modelsResponse) | ||
if err != nil { | ||
utils.Log.Error(err) | ||
return err | ||
} | ||
|
||
var selectedModel v1alpha1.Model | ||
|
||
if modelsResponse.Count == 0 { | ||
utils.Log.Info("No model(s) found for the given name ", model) | ||
return nil | ||
} else if modelsResponse.Count == 1 { | ||
selectedModel = modelsResponse.Models[0] | ||
} else { | ||
selectedModel = selectModelPrompt(modelsResponse.Models) | ||
} | ||
|
||
var output []byte | ||
|
||
// user may pass flag in lower or upper case but we have to keep it lower | ||
// in order to make it consistent while checking output format | ||
outFormatFlag = strings.ToLower(outFormatFlag) | ||
if outFormatFlag == "yaml" { | ||
if output, err = yaml.Marshal(selectedModel); err != nil { | ||
return errors.Wrap(err, "failed to format output in YAML") | ||
} | ||
fmt.Print(string(output)) | ||
} else if outFormatFlag == "json" { | ||
return outputJson(selectedModel) | ||
} else { | ||
return errors.New("output-format choice invalid, use [json|yaml]") | ||
} | ||
|
||
return nil | ||
}, | ||
} | ||
|
||
// providerCmd represents the `mesheryctl system model` command | ||
var modelCmd = &cobra.Command{ | ||
Use: "model", | ||
Short: "Generate and update components", | ||
Long: `Generates and updates components in binary form`, | ||
Example: ` | ||
// To view list of components | ||
mesheryctl system model list | ||
|
||
// To view a specific model | ||
mesheryctl system model view [model-name] | ||
zakisk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
`, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
if len(args) == 0 { | ||
return errors.New(utils.SystemModelSubError("please specify a flag or subcommand. Use 'mesheryctl system model --help' to display user guide.\n", "model")) | ||
} | ||
if ok := utils.IsValidSubcommand(availableSubcommands, args[0]); !ok { | ||
return errors.New(utils.SystemModelSubError(fmt.Sprintf("'%s' is an invalid subcommand. Please provide required options from [view]. Use 'mesheryctl system model --help' to display usage guide.\n", args[0]), "model")) | ||
} | ||
_, err := config.GetMesheryCtl(viper.GetViper()) | ||
if err != nil { | ||
log.Fatalln(err, "error processing config") | ||
} | ||
err = viewProviderCmd.RunE(cmd, args) | ||
if err != nil { | ||
return err | ||
} | ||
err = cmd.Usage() | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
}, | ||
} | ||
|
||
func init() { | ||
listModelCmd.Flags().IntVarP(&pageNumberFlag, "page", "p", 1, "(optional) List next set of models with --page (default = 1)") | ||
viewModelCmd.Flags().StringVarP(&outFormatFlag, "output-format", "o", "yaml", "(optional) format to display in [json|yaml]") | ||
availableSubcommands = []*cobra.Command{listModelCmd, viewModelCmd} | ||
modelCmd.AddCommand(availableSubcommands...) | ||
} | ||
|
||
// `selectModelPrompt` lets user to select a model if models are more than one | ||
func selectModelPrompt(models []v1alpha1.Model) v1alpha1.Model { | ||
modelArray := []v1alpha1.Model{} | ||
modelNames := []string{} | ||
|
||
modelArray = append(modelArray, models...) | ||
|
||
for _, model := range modelArray { | ||
modelName := fmt.Sprintf("%s, version: %s", model.DisplayName, model.Version) | ||
modelNames = append(modelNames, modelName) | ||
} | ||
|
||
prompt := promptui.Select{ | ||
Label: "Select a model", | ||
Items: modelNames, | ||
} | ||
|
||
for { | ||
i, _, err := prompt.Run() | ||
if err != nil { | ||
continue | ||
} | ||
|
||
return modelArray[i] | ||
} | ||
} | ||
|
||
func outputJson(model v1alpha1.Model) error { | ||
if err = prettifyJson(model); err != nil { | ||
// if prettifyJson return error, marshal output in conventional way using json.MarshalIndent | ||
// but it doesn't convert unicode to its corresponding HTML string (it is default behavior) | ||
// e.g unicode representation of '&' will be printed as '\u0026' | ||
if output, err := json.MarshalIndent(model, "", " "); err != nil { | ||
return errors.Wrap(err, "failed to format output in JSON") | ||
} else { | ||
fmt.Print(string(output)) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// prettifyJson takes a v1alpha1.Model struct as input, marshals it into a nicely formatted JSON representation, | ||
// and prints it to standard output with proper indentation and without escaping HTML entities. | ||
func prettifyJson(model v1alpha1.Model) error { | ||
// Create a new JSON encoder that writes to the standard output (os.Stdout). | ||
enc := json.NewEncoder(os.Stdout) | ||
// Configure the JSON encoder settings. | ||
// SetEscapeHTML(false) prevents special characters like '<', '>', and '&' from being escaped to their HTML entities. | ||
enc.SetEscapeHTML(false) | ||
enc.SetIndent("", " ") | ||
|
||
// Any errors during the encoding process will be returned as an error. | ||
return enc.Encode(model) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we exposing a secret publicly like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not yet answered...