mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
Feat: get raw Application yaml, json or jsonpath (#4415)
* Feat: get raw Application Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com> * Fix: add gvk Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com> * Feat: git rid of managedFields Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com> * Style: fix typos Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com> * Test: add test and make changes according to comments Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com> * Feat: more help text Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com> * Style: format imports Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>
This commit is contained in:
+59
-11
@@ -19,6 +19,7 @@ package cli
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -28,6 +29,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
pkgtypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
@@ -94,11 +96,22 @@ const (
|
||||
// NewAppStatusCommand creates `status` command for showing status
|
||||
func NewAppStatusCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
ctx := context.Background()
|
||||
var outputFormat string
|
||||
cmd := &cobra.Command{
|
||||
Use: "status APP_NAME",
|
||||
Short: "Show status of an application.",
|
||||
Long: "Show status of vela application.",
|
||||
Example: `vela status APP_NAME`,
|
||||
Use: "status APP_NAME",
|
||||
Short: "Show status of an application.",
|
||||
Long: "Show status of vela application.",
|
||||
Example: ` # Get basic app info
|
||||
vela status APP_NAME
|
||||
|
||||
# Show detailed info in tree
|
||||
vela status first-vela-app --tree --detail --detail-format list
|
||||
|
||||
# Get raw Application yaml (without managedFields)
|
||||
vela status first-vela-app -o yaml
|
||||
|
||||
# Get raw Application status using jsonpath
|
||||
vela status first-vela-app -o jsonpath='{.status}'`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// check args
|
||||
argsLength := len(args)
|
||||
@@ -118,6 +131,9 @@ func NewAppStatusCommand(c common.Args, order string, ioStreams cmdutil.IOStream
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if outputFormat != "" {
|
||||
return printRawApplication(context.Background(), c, outputFormat, cmd.OutOrStdout(), namespace, appName)
|
||||
}
|
||||
showEndpoints, err := cmd.Flags().GetBool("endpoint")
|
||||
if showEndpoints && err == nil {
|
||||
component, _ := cmd.Flags().GetString("component")
|
||||
@@ -137,8 +153,9 @@ func NewAppStatusCommand(c common.Args, order string, ioStreams cmdutil.IOStream
|
||||
cmd.Flags().BoolP("endpoint", "p", false, "show all service endpoints of the application")
|
||||
cmd.Flags().StringP("component", "c", "", "filter service endpoints by component name")
|
||||
cmd.Flags().BoolP("tree", "t", false, "display the application resources into tree structure")
|
||||
cmd.Flags().BoolP("detail", "d", false, "display the realtime details of application resources")
|
||||
cmd.Flags().StringP("detail-format", "", "inline", "the format for displaying details. Can be one of inline (default), wide, list, table, raw.")
|
||||
cmd.Flags().BoolP("detail", "d", false, "display the realtime details of application resources, must be used with --tree")
|
||||
cmd.Flags().StringP("detail-format", "", "inline", "the format for displaying details, must be used with --detail. Can be one of inline, wide, list, table, raw.")
|
||||
cmd.Flags().StringVarP(&outputFormat, "output", "o", "", "raw Application output format. One of: (json, yaml, jsonpath)")
|
||||
addNamespaceAndEnvArg(cmd)
|
||||
return cmd
|
||||
}
|
||||
@@ -224,11 +241,11 @@ func printWorkflowStatus(c client.Client, ioStreams cmdutil.IOStreams, appName s
|
||||
ioStreams.Infof(" Terminated: %t\n", workflowStatus.Terminated)
|
||||
ioStreams.Info(" Steps")
|
||||
for _, step := range workflowStatus.Steps {
|
||||
ioStreams.Infof(" - id:%s\n", step.ID)
|
||||
ioStreams.Infof(" name:%s\n", step.Name)
|
||||
ioStreams.Infof(" type:%s\n", step.Type)
|
||||
ioStreams.Infof(" phase:%s \n", getWfStepColor(step.Phase).Sprint(step.Phase))
|
||||
ioStreams.Infof(" message:%s\n", step.Message)
|
||||
ioStreams.Infof(" - id: %s\n", step.ID)
|
||||
ioStreams.Infof(" name: %s\n", step.Name)
|
||||
ioStreams.Infof(" type: %s\n", step.Type)
|
||||
ioStreams.Infof(" phase: %s \n", getWfStepColor(step.Phase).Sprint(step.Phase))
|
||||
ioStreams.Infof(" message: %s\n", step.Message)
|
||||
}
|
||||
ioStreams.Infof("\n")
|
||||
}
|
||||
@@ -431,3 +448,34 @@ func printApplicationTree(c common.Args, cmd *cobra.Command, appName string, app
|
||||
options.PrintResourceTree(cmd.OutOrStdout(), placements, currentRT, historyRTs)
|
||||
return nil
|
||||
}
|
||||
|
||||
// printRawApplication prints raw Application in yaml/json/jsonpath (without managedFields).
|
||||
func printRawApplication(ctx context.Context, c common.Args, format string, out io.Writer, ns, appName string) error {
|
||||
var err error
|
||||
app := &v1beta1.Application{}
|
||||
|
||||
k8sClient, err := c.GetClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get k8s client: %w", err)
|
||||
}
|
||||
|
||||
err = k8sClient.Get(ctx, pkgtypes.NamespacedName{
|
||||
Namespace: ns,
|
||||
Name: appName,
|
||||
}, app)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get application %s in namespace %s: %w", appName, ns, err)
|
||||
}
|
||||
|
||||
// Set GVK, we need it
|
||||
// because the object returned from client.Get() has empty GVK
|
||||
// (since the type info is inherent in the typed object, so GVK is empty)
|
||||
app.SetGroupVersionKind(v1beta1.ApplicationKindVersionKind)
|
||||
str, err := formatApplicationString(format, app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = out.Write([]byte(str))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@@ -30,9 +32,13 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/util/jsonpath"
|
||||
"k8s.io/kubectl/pkg/cmd/get"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
common2 "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
@@ -115,3 +121,62 @@ func (ui *UserInput) read() (string, error) {
|
||||
resultStr := strings.TrimSuffix(line, "\n")
|
||||
return resultStr, err
|
||||
}
|
||||
|
||||
// formatApplicationString formats an Application to string in yaml/json/jsonpath for printing (without managedFields).
|
||||
//
|
||||
// format = "yaml" / "json" / "jsonpath={.field}"
|
||||
func formatApplicationString(format string, app *v1beta1.Application) (string, error) {
|
||||
var ret string
|
||||
|
||||
if format == "" {
|
||||
return "", fmt.Errorf("no format provided")
|
||||
}
|
||||
|
||||
// No, we don't want managedFields, get rid of it.
|
||||
app.ManagedFields = nil
|
||||
|
||||
switch format {
|
||||
case "yaml":
|
||||
b, err := yaml.Marshal(app)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret = string(b)
|
||||
case "json":
|
||||
b, err := json.MarshalIndent(app, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret = string(b)
|
||||
default:
|
||||
// format is not any of json/yaml/jsonpath, not supported
|
||||
if !strings.HasPrefix(format, "jsonpath") {
|
||||
return "", fmt.Errorf("output %s is not supported", format)
|
||||
}
|
||||
|
||||
// format = jsonpath
|
||||
s := strings.Split(format, "=")
|
||||
if len(s) < 2 {
|
||||
return "", fmt.Errorf("jsonpath template format specified but no template given")
|
||||
}
|
||||
path, err := get.RelaxedJSONPathExpression(s[1])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
jp := jsonpath.New("").AllowMissingKeys(true)
|
||||
err = jp.Parse(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err = jp.Execute(buf, app)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret = buf.String()
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
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 cli
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
)
|
||||
|
||||
func TestFormatApplicationString(t *testing.T) {
|
||||
var (
|
||||
str string
|
||||
err error
|
||||
)
|
||||
|
||||
app := &v1beta1.Application{}
|
||||
app.SetGroupVersionKind(v1beta1.ApplicationKindVersionKind)
|
||||
// This should not be preset in the formatted string
|
||||
app.ManagedFields = []v1.ManagedFieldsEntry{
|
||||
{
|
||||
Manager: "",
|
||||
Operation: "",
|
||||
APIVersion: "",
|
||||
Time: nil,
|
||||
FieldsType: "",
|
||||
FieldsV1: nil,
|
||||
Subresource: "",
|
||||
},
|
||||
}
|
||||
app.SetName("app-name")
|
||||
|
||||
_, err = formatApplicationString("", app)
|
||||
assert.ErrorContains(t, err, "no format", "no format provided, should error out")
|
||||
|
||||
_, err = formatApplicationString("invalid", app)
|
||||
assert.ErrorContains(t, err, "not supported", "invalid format provided, should error out")
|
||||
|
||||
str, err = formatApplicationString("yaml", app)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, true, strings.Contains(str, `apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: app-name
|
||||
spec:
|
||||
components: null
|
||||
status: {}
|
||||
`), "formatted yaml is not correct")
|
||||
|
||||
str, err = formatApplicationString("json", app)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, true, strings.Contains(str, `{
|
||||
"kind": "Application",
|
||||
"apiVersion": "core.oam.dev/v1beta1",
|
||||
"metadata": {
|
||||
"name": "app-name",
|
||||
"creationTimestamp": null
|
||||
},
|
||||
"spec": {
|
||||
"components": null
|
||||
},
|
||||
"status": {}
|
||||
}`), "formatted json is not correct")
|
||||
|
||||
_, err = formatApplicationString("jsonpath", app)
|
||||
assert.ErrorContains(t, err, "jsonpath template", "no jsonpath template provided, should not pass")
|
||||
|
||||
str, err = formatApplicationString("jsonpath={.apiVersion}", app)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, str, "core.oam.dev/v1beta1")
|
||||
}
|
||||
Reference in New Issue
Block a user