Skip to content
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

support egctl get describe different namespace #1197

Merged
merged 8 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
egctl describe cmd support multi namespace, fix previous bugs when qu…
…ery status of non-traffic object
  • Loading branch information
suchen-sci committed Jan 25, 2024
commit a68ea1ae60b6fae3cffd60f4aa49b5942093855e
11 changes: 10 additions & 1 deletion cmd/client/commandv2/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
"github.com/spf13/cobra"
)

var describeFlags resources.ObjectNamespaceFlags

// DescribeCmd returns describe command.
func DescribeCmd() *cobra.Command {
examples := []general.Example{
Expand All @@ -37,6 +39,8 @@ func DescribeCmd() *cobra.Command {
{Desc: "Describe all members", Command: "egctl describe member"},
{Desc: "Describe a customdata kind", Command: "egctl describe customdatakind <name>"},
{Desc: "Describe a customdata of given kind", Command: "egctl describe customdata <kind> <name>"},
{Desc: "Describe pipeline resources from all namespaces, including httpservers and pipelines created by IngressController, MeshController and GatewayController", Command: "egctl describe pipeline --all-namespaces"},
{Desc: "Describe httpserver resources from a certain namespace", Command: "egctl describe httpserver --namespace <namespace>"},
{Desc: "Check all possible api resources", Command: "egctl api-resources"},
}
cmd := &cobra.Command{
Expand All @@ -47,6 +51,11 @@ func DescribeCmd() *cobra.Command {
Run: describeCmdRun,
}
cmd.Flags().BoolVarP(&general.CmdGlobalFlags.Verbose, "verbose", "v", false, "Print verbose information")
cmd.Flags().StringVar(&describeFlags.Namespace, "namespace", "",
"namespace is used to describe httpservers and pipelines created by IngressController, MeshController or GatewayController"+
"(these objects create httpservers and pipelines in an independent namespace)")
cmd.Flags().BoolVar(&describeFlags.AllNamespace, "all-namespaces", false,
"describe all resources in all namespaces (including the ones created by IngressController, MeshController and GatewayController that are in an independent namespace)")
return cmd
}

Expand All @@ -71,7 +80,7 @@ func describeCmdRun(cmd *cobra.Command, args []string) {
case resources.Member().Kind:
err = resources.DescribeMember(cmd, a)
default:
err = resources.DescribeObject(cmd, a, kind)
err = resources.DescribeObject(cmd, a, kind, &describeFlags)
}
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/client/commandv2/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
"github.com/spf13/cobra"
)

var getFlags resources.GetObjectFlags
var getFlags resources.ObjectNamespaceFlags

// GetCmd returns get command.
func GetCmd() *cobra.Command {
Expand Down
157 changes: 127 additions & 30 deletions cmd/client/resources/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,19 @@ import (
"github.com/spf13/cobra"
)

// GetObjectFlags contains the flags for get objects.
type GetObjectFlags struct {
// ObjectNamespaceFlags contains the flags for get objects.
type ObjectNamespaceFlags struct {
Namespace string
AllNamespace bool
}

var globalAPIResources []*api.APIResource

// ObjectAPIResources returns the object api resources.
func ObjectAPIResources() ([]*api.APIResource, error) {
if globalAPIResources != nil {
return globalAPIResources, nil
}
url := makePath(general.ObjectAPIResources)
body, err := handleReq(http.MethodGet, url, nil)
if err != nil {
Expand All @@ -55,14 +60,17 @@ func ObjectAPIResources() ([]*api.APIResource, error) {
if err != nil {
return nil, err
}
globalAPIResources = res
return res, nil
}

func defaultObjectNameSpace() string {
return cluster.TrafficNamespace(cluster.NamespaceDefault)
// trafficObjectStatusNamespace return namespace of traffic object status.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't understand this comment, what does it mean? And the following line also contains confusing grammar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for non-traffic objects, like autocertmanager, it is in namespace default, and its status is in namespace default.

but for traffic objects, they are managed by traffic controller, and traffic controller use specially prefix for status. for example, if a pipeline in namesapce default, then its status in namespace eg-traffic-default.

// to get traffic object status of httpserver of pipeline, we need this function.
func trafficObjectStatusNamespace(objectNamespace string) string {
return cluster.TrafficNamespace(objectNamespace)
}

func httpGetObject(name string, flags *GetObjectFlags) ([]byte, error) {
func httpGetObject(name string, flags *ObjectNamespaceFlags) ([]byte, error) {
objectURL := func(name string) string {
if len(name) == 0 {
return makePath(general.ObjectsURL)
Expand All @@ -79,7 +87,7 @@ func httpGetObject(name string, flags *GetObjectFlags) ([]byte, error) {
}

// GetAllObject gets all objects.
func GetAllObject(cmd *cobra.Command, flags *GetObjectFlags) error {
func GetAllObject(cmd *cobra.Command, flags *ObjectNamespaceFlags) error {
getErr := func(err error) error {
return general.ErrorMsg(general.GetCmd, err, "resource")
}
Expand Down Expand Up @@ -172,7 +180,7 @@ func getObjectYaml(objectName string) (string, error) {
}

// GetObject gets an object.
func GetObject(cmd *cobra.Command, args *general.ArgInfo, kind string, flags *GetObjectFlags) error {
func GetObject(cmd *cobra.Command, args *general.ArgInfo, kind string, flags *ObjectNamespaceFlags) error {
msg := fmt.Sprintf("all %s", kind)
if args.ContainName() {
msg = fmt.Sprintf("%s %s", kind, args.Name)
Expand Down Expand Up @@ -321,7 +329,7 @@ func printMetaSpec(metas []*supervisor.MetaSpec) {
}

// DescribeObject describes an object.
func DescribeObject(cmd *cobra.Command, args *general.ArgInfo, kind string) error {
func DescribeObject(cmd *cobra.Command, args *general.ArgInfo, kind string, flags *ObjectNamespaceFlags) error {
msg := fmt.Sprintf("all %s", kind)
if args.ContainName() {
msg = fmt.Sprintf("%s %s", kind, args.Name)
Expand All @@ -330,27 +338,50 @@ func DescribeObject(cmd *cobra.Command, args *general.ArgInfo, kind string) erro
return general.ErrorMsg(general.GetCmd, err, msg)
}

body, err := httpGetObject(args.Name, nil)
body, err := httpGetObject(args.Name, flags)
if err != nil {
return getErr(err)
}

specs, err := general.UnmarshalMapInterface(body, !args.ContainName())
if err != nil {
return getErr(err)
namespaceSpecs := map[string][]map[string]interface{}{}
if flags.AllNamespace {
err := codectool.Unmarshal(body, &namespaceSpecs)
if err != nil {
return getErr(err)
}
} else {
specs, err := general.UnmarshalMapInterface(body, !args.ContainName())
if err != nil {
return getErr(err)
}
namespace := DefaultNamespace
if flags.Namespace != "" {
namespace = flags.Namespace
}
namespaceSpecs[namespace] = specs
}

specs = general.Filter(specs, func(m map[string]interface{}) bool {
return m["kind"] == kind
})
for namespace, specs := range namespaceSpecs {
namespaceSpecs[namespace] = general.Filter(specs, func(m map[string]interface{}) bool {
return m["kind"] == kind
})
}

err = addObjectStatusToSpec(specs, args)
err = addObjectStatusToSpec(namespaceSpecs, args, flags)
if err != nil {
return getErr(err)
}

if !general.CmdGlobalFlags.DefaultFormat() {
body, err = codectool.MarshalJSON(specs)
var input interface{}
if flags.AllNamespace {
input = namespaceSpecs
} else {
for _, v := range namespaceSpecs {
input = v
}
}
body, err = codectool.MarshalJSON(input)
if err != nil {
return getErr(err)
}
Expand Down Expand Up @@ -384,7 +415,34 @@ func DescribeObject(cmd *cobra.Command, args *general.ArgInfo, kind string) erro
// status: ...
// - node: eg2
// status: ...
general.PrintMapInterface(specs, specials, []string{objectStatusKeyInSpec})
printNamespace := func(namespace string) {
msg := fmt.Sprintf("In Namespace %s:", namespace)
fmt.Println(strings.Repeat("=", len(msg)))
fmt.Println(msg)
fmt.Println(strings.Repeat("=", len(msg)))
}
printSpace := false
specs := namespaceSpecs[DefaultNamespace]
if len(specs) > 0 {
printNamespace(DefaultNamespace)
general.PrintMapInterface(specs, specials, []string{objectStatusKeyInSpec})
printSpace = true
}
for k, v := range namespaceSpecs {
if k == DefaultNamespace {
continue
}
if len(v) == 0 {
continue
}
if printSpace {
fmt.Println()
fmt.Println()
}
printNamespace(k)
general.PrintMapInterface(v, specials, []string{objectStatusKeyInSpec})
printSpace = true
}
return nil
}

Expand Down Expand Up @@ -499,7 +557,7 @@ func splitObjectStatusKey(key string) (*objectStatusInfo, error) {
}, nil
}

// ObjectStatus is the status of an object.
// ObjectStatus is the status of an TrafficObject.
type ObjectStatus struct {
Spec map[string]interface{} `json:"spec"`
Status map[string]interface{} `json:"status"`
Expand All @@ -508,6 +566,13 @@ type ObjectStatus struct {
func unmarshalObjectStatus(data []byte) (ObjectStatus, error) {
var status ObjectStatus
err := codectool.Unmarshal(data, &status)
// if status.Spec and status.Status is all nil
// then means the status is not traffic controller object status.
// we need to re-unmarshal to get true status.
if status.Spec == nil && status.Status == nil {
status.Status = map[string]interface{}{}
codectool.Unmarshal(data, &status.Status)
}
return status, err
}

Expand All @@ -524,10 +589,6 @@ func unmarshalObjectStatusInfo(body []byte, name string) ([]*objectStatusInfo, e
if err != nil {
return nil, err
}
// only show objects in default namespace, objects in other namespaces is not created by user.
if info.namespace != defaultObjectNameSpace() {
continue
}
if name != "" && info.name != name {
continue
}
Expand Down Expand Up @@ -555,12 +616,14 @@ type NodeStatus struct {

const objectStatusKeyInSpec = "allStatus"

func addObjectStatusToSpec(specs []map[string]interface{}, args *general.ArgInfo) error {
func addObjectStatusToSpec(allSpecs map[string][]map[string]interface{}, args *general.ArgInfo, flags *ObjectNamespaceFlags) error {
getUrl := func(args *general.ArgInfo) string {
if !args.ContainName() {
// no name, all namespaces, non default namespace, then get all status
if !args.ContainName() || flags.AllNamespace {
return makePath(general.StatusObjectsURL)
}
return makePath(general.StatusObjectItemURL, args.Name)
url := makePath(general.StatusObjectItemURL, args.Name)
return fmt.Sprintf("%s?namespace=%s", url, flags.Namespace)
}

body, err := handleReq(http.MethodGet, getUrl(args), nil)
Expand All @@ -573,18 +636,52 @@ func addObjectStatusToSpec(specs []map[string]interface{}, args *general.ArgInfo
return err
}

keyFn := func(namespace, name string) string {
return namespace + "/" + name
}
// key is name, value is array of node and status
status := map[string][]*NodeStatus{}
for _, info := range infos {
status[info.name] = append(status[info.name], &NodeStatus{
key := keyFn(info.namespace, info.name)
status[key] = append(status[key], &NodeStatus{
Node: info.node,
Status: info.status,
})
}

for _, s := range specs {
name := s["name"].(string)
s[objectStatusKeyInSpec] = status[name]
categoryMap := func() map[string]string {
rs, err := ObjectAPIResources()
if err != nil {
return map[string]string{}
}
res := map[string]string{}
for _, r := range rs {
res[r.Kind] = r.Category
}
return res
}()
getNamespace := func(kind string, namespace string) string {
category := categoryMap[kind]
if category == "" {
switch kind {
case "HTTPServer":
category = supervisor.CategoryTrafficGate
case "Pipeline":
category = supervisor.CategoryPipeline
}
}
if category == supervisor.CategoryPipeline || category == supervisor.CategoryTrafficGate {
return trafficObjectStatusNamespace(namespace)
}
return namespace
}
for namespace, specs := range allSpecs {
for i := range specs {
spec := specs[i]
ns := getNamespace(spec["kind"].(string), namespace)
name := spec["name"].(string)
allSpecs[namespace][i][objectStatusKeyInSpec] = status[keyFn(ns, name)]
}
}
return nil
}
2 changes: 2 additions & 0 deletions docs/02.Tutorials/2.1.egctl-Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ In Easegress, when using `IngressController`, `MeshController`, or `GatewayContr
```bash
egctl get all --all-namespaces # view all resources in all namespace.
egctl get all --namespace default # view all resources in default namespace.
egctl describe pipeline --all-namespaces # describe pipeline resources in all namespace.
egctl describe httpserver demo --namespace default # describe httpserver demo in default namespace.
```

## Updating resources
Expand Down
14 changes: 12 additions & 2 deletions pkg/api/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,18 @@ func (s *Server) _deleteObject(name string) {
}
}

func (s *Server) _getStatusObject(name string) map[string]interface{} {
prefix := s.cluster.Layout().StatusObjectPrefix(cluster.TrafficNamespace(cluster.NamespaceDefault), name)
// _getStatusObject returns the status object of the name.
suchen-sci marked this conversation as resolved.
Show resolved Hide resolved
// in easegress, since TrafficController contain multiply namespaces.
// it need a special prefix to store the status object.
func (s *Server) _getStatusObject(namespace string, name string, isTraffic bool) map[string]interface{} {
ns := namespace
if ns == "" {
ns = cluster.NamespaceDefault
}
prefix := s.cluster.Layout().StatusObjectPrefix(ns, name)
if isTraffic {
prefix = s.cluster.Layout().StatusObjectPrefix(cluster.TrafficNamespace(ns), name)
}
kvs, err := s.cluster.GetPrefix(prefix)
if err != nil {
ClusterPanic(err)
Expand Down
17 changes: 11 additions & 6 deletions pkg/api/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,8 @@ func (s *Server) updateObject(w http.ResponseWriter, r *http.Request) {
}

func parseNamespaces(r *http.Request) (bool, string) {
allNamespaces := r.URL.Query().Get("all-namespaces")
namespace := r.URL.Query().Get("namespace")
allNamespaces := strings.TrimSpace(r.URL.Query().Get("all-namespaces"))
namespace := strings.TrimSpace(r.URL.Query().Get("namespace"))
flag, err := strconv.ParseBool(allNamespaces)
if err != nil {
return false, namespace
Expand Down Expand Up @@ -370,16 +370,21 @@ func (s *Server) listObjects(w http.ResponseWriter, r *http.Request) {

func (s *Server) getStatusObject(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
_, namespace := parseNamespaces(r)

spec := s._getObject(name)

var spec *supervisor.Spec
if namespace == "" || namespace == DefaultNamespace {
spec = s._getObject(name)
} else {
spec = s._getObjectByNamespace(namespace, name)
}
if spec == nil {
HandleAPIError(w, r, http.StatusNotFound, fmt.Errorf("not found"))
return
}

status := s._getStatusObject(name)

_, isTraffic := supervisor.TrafficObjectKinds[spec.Kind()]
status := s._getStatusObject(namespace, name, isTraffic)
WriteBody(w, r, status)
}

Expand Down