diff --git a/internal/cmd/skupper/common/utils/debug.go b/internal/cmd/skupper/common/utils/debug.go new file mode 100644 index 000000000..0d01c5101 --- /dev/null +++ b/internal/cmd/skupper/common/utils/debug.go @@ -0,0 +1,60 @@ +package utils + +import ( + "archive/tar" + "bytes" + "fmt" + "os/exec" + "time" + + "github.com/skupperproject/skupper/pkg/generated/client/clientset/versioned/scheme" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer/json" +) + +func RunCommand(name string, args ...string) ([]byte, error) { + cmd := exec.Command(name, args...) + var out bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &out + err := cmd.Run() + if err != nil { + return nil, err + } + return out.Bytes(), nil +} + +func WriteTar(name string, data []byte, ts time.Time, tw *tar.Writer) error { + hdr := &tar.Header{ + Name: name, + Mode: 0600, + Size: int64(len(data)), + ModTime: ts, + } + err := tw.WriteHeader(hdr) + if err != nil { + return fmt.Errorf("Failed to write tar file header: %w", err) + } + _, err = tw.Write(data) + if err != nil { + return fmt.Errorf("Failed to write to tar archive: %w", err) + } + return nil +} + +func WriteObject(rto runtime.Object, name string, tw *tar.Writer) error { + var b bytes.Buffer + s := json.NewYAMLSerializer(json.DefaultMetaFactory, scheme.Scheme, scheme.Scheme) + if err := s.Encode(rto, &b); err != nil { + return err + } + err := WriteTar(name+".yaml", b.Bytes(), time.Now(), tw) + if err != nil { + return err + } + err = WriteTar(name+".yaml.txt", b.Bytes(), time.Now(), tw) + if err != nil { + return err + } + return nil +} diff --git a/internal/cmd/skupper/connector/nonkube/connector_status.go b/internal/cmd/skupper/connector/nonkube/connector_status.go index 0a536a150..375b5952f 100644 --- a/internal/cmd/skupper/connector/nonkube/connector_status.go +++ b/internal/cmd/skupper/connector/nonkube/connector_status.go @@ -78,7 +78,7 @@ func (cmd *CmdConnectorStatus) ValidateInput(args []string) error { func (cmd *CmdConnectorStatus) Run() error { opts := fs.GetOptions{RuntimeFirst: true, LogWarning: true} if cmd.connectorName == "" { - resources, err := cmd.connectorHandler.List() + resources, err := cmd.connectorHandler.List(fs.GetOptions{RuntimeFirst: true}) if err != nil || resources == nil || len(resources) == 0 { fmt.Println("No connectors found") return err diff --git a/internal/cmd/skupper/debug/kube/debug.go b/internal/cmd/skupper/debug/kube/debug.go index e61468ea3..7d0978a49 100644 --- a/internal/cmd/skupper/debug/kube/debug.go +++ b/internal/cmd/skupper/debug/kube/debug.go @@ -2,19 +2,18 @@ package kube import ( "archive/tar" - "bytes" "compress/gzip" "context" "errors" "fmt" "os" - "os/exec" "path/filepath" "time" "sigs.k8s.io/yaml" "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" "github.com/skupperproject/skupper/internal/kube/client" internalclient "github.com/skupperproject/skupper/internal/kube/client" "github.com/skupperproject/skupper/internal/utils/validator" @@ -24,10 +23,8 @@ import ( crdClient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" restclient "k8s.io/client-go/rest" @@ -142,16 +139,16 @@ func (cmd *CmdDebug) Run() error { tw := tar.NewWriter(gz) defer tw.Close() - kv, err := runCommand("kubectl", "version", "-o", "yaml") + kv, err := utils.RunCommand("kubectl", "version", "-o", "yaml") if err == nil { - writeTar("/versions/kubernetes.yaml", kv, time.Now(), tw) - writeTar("/versions/kubernetes.yaml.txt", kv, time.Now(), tw) + utils.WriteTar("/versions/kubernetes.yaml", kv, time.Now(), tw) + utils.WriteTar("/versions/kubernetes.yaml.txt", kv, time.Now(), tw) } - manifest, err := runCommand("skupper", "version", "-o", "yaml") + manifest, err := utils.RunCommand("skupper", "version", "-o", "yaml") if err == nil { - writeTar("/versions/skupper.yaml", manifest, time.Now(), tw) - writeTar("/versions/skupper.yaml.txt", manifest, time.Now(), tw) + utils.WriteTar("/versions/skupper.yaml", manifest, time.Now(), tw) + utils.WriteTar("/versions/skupper.yaml.txt", manifest, time.Now(), tw) } // get resources for skupper-router @@ -159,16 +156,16 @@ func (cmd *CmdDebug) Run() error { if site != nil && err == nil { path := "/site-namespace/" rPath := path + "resources/" - events, err := runCommand("kubectl", "events") + events, err := utils.RunCommand("kubectl", "events") if err == nil { - writeTar(path+"events.txt", events, time.Now(), tw) + utils.WriteTar(path+"events.txt", events, time.Now(), tw) } - endpoints, err := runCommand("kubectl", "get", "endpoints", "-o", "yaml") + endpoints, err := utils.RunCommand("kubectl", "get", "endpoints", "-o", "yaml") if err == nil { ePath := rPath + "Endpoints-skupper-router-" + cmd.Namespace + ".yaml" - writeTar(ePath, endpoints, time.Now(), tw) - writeTar(ePath+".txt", endpoints, time.Now(), tw) + utils.WriteTar(ePath, endpoints, time.Now(), tw) + utils.WriteTar(ePath+".txt", endpoints, time.Now(), tw) } err = getDeployments(cmd, path, "skupper-router", tw) @@ -188,7 +185,7 @@ func (cmd *CmdDebug) Run() error { } encodedOutput, err = yaml.Marshal(crds) if err == nil { - writeTar(path+"crds.txt", encodedOutput, time.Now(), tw) + utils.WriteTar(path+"crds.txt", encodedOutput, time.Now(), tw) } } } @@ -196,7 +193,7 @@ func (cmd *CmdDebug) Run() error { for i := range configMaps { cm, err := cmd.KubeClient.CoreV1().ConfigMaps(cmd.Namespace).Get(context.TODO(), configMaps[i], metav1.GetOptions{}) if err == nil { - err := writeObject(cm, rPath+"Configmap-"+cm.Name, tw) + err := utils.WriteObject(cm, rPath+"Configmap-"+cm.Name, tw) if err != nil { return err } @@ -206,7 +203,7 @@ func (cmd *CmdDebug) Run() error { for _, service := range routerServices { service, err := cmd.KubeClient.CoreV1().Services(cmd.Namespace).Get(context.TODO(), service, metav1.GetOptions{}) if err == nil { - err := writeObject(service, rPath+"Services-"+service.Name, tw) + err := utils.WriteObject(service, rPath+"Services-"+service.Name, tw) if err != nil { return err } @@ -217,7 +214,7 @@ func (cmd *CmdDebug) Run() error { if accessGrantList != nil && err == nil { for _, grant := range accessGrantList.Items { g := grant.DeepCopy() - err := writeObject(g, rPath+"Accessgrant-"+g.Name, tw) + err := utils.WriteObject(g, rPath+"Accessgrant-"+g.Name, tw) if err != nil { return err } @@ -228,7 +225,7 @@ func (cmd *CmdDebug) Run() error { if accessTokenList != nil && err == nil { for _, token := range accessTokenList.Items { t := token.DeepCopy() - err := writeObject(t, rPath+"AccessTokens-"+t.Name, tw) + err := utils.WriteObject(t, rPath+"AccessTokens-"+t.Name, tw) if err != nil { return err } @@ -239,7 +236,7 @@ func (cmd *CmdDebug) Run() error { if attachedConnectorBindingList != nil && err == nil { for _, binding := range attachedConnectorBindingList.Items { b := binding.DeepCopy() - err := writeObject(b, rPath+"AttachedConnectorBinding-"+b.Name, tw) + err := utils.WriteObject(b, rPath+"AttachedConnectorBinding-"+b.Name, tw) if err != nil { return err } @@ -250,7 +247,7 @@ func (cmd *CmdDebug) Run() error { if attachedConnectorList != nil && err == nil { for _, attachedConnector := range attachedConnectorList.Items { a := attachedConnector.DeepCopy() - err := writeObject(a, rPath+"AttachedConnector-"+a.Name, tw) + err := utils.WriteObject(a, rPath+"AttachedConnector-"+a.Name, tw) if err != nil { return err } @@ -261,7 +258,7 @@ func (cmd *CmdDebug) Run() error { if certificateList != nil && err == nil { for _, certificate := range certificateList.Items { c := certificate.DeepCopy() - err := writeObject(c, rPath+"Certificate-"+c.Name, tw) + err := utils.WriteObject(c, rPath+"Certificate-"+c.Name, tw) if err != nil { return err } @@ -272,7 +269,7 @@ func (cmd *CmdDebug) Run() error { if connectorList != nil && err == nil { for _, connector := range connectorList.Items { c := connector.DeepCopy() - err := writeObject(c, rPath+"Connector-"+c.Name, tw) + err := utils.WriteObject(c, rPath+"Connector-"+c.Name, tw) if err != nil { return err } @@ -283,7 +280,7 @@ func (cmd *CmdDebug) Run() error { if linkList != nil && err == nil { for _, link := range linkList.Items { l := link.DeepCopy() - err := writeObject(l, rPath+"Link-"+l.Name, tw) + err := utils.WriteObject(l, rPath+"Link-"+l.Name, tw) if err != nil { return err } @@ -294,7 +291,7 @@ func (cmd *CmdDebug) Run() error { if listenerList != nil && err == nil { for _, listener := range listenerList.Items { l := listener.DeepCopy() - err := writeObject(l, rPath+"Listener-"+l.Name, tw) + err := utils.WriteObject(l, rPath+"Listener-"+l.Name, tw) if err != nil { return err } @@ -305,7 +302,7 @@ func (cmd *CmdDebug) Run() error { if siteList != nil && err == nil { for _, site := range siteList.Items { s := site.DeepCopy() - err := writeObject(s, rPath+"Site-"+s.Name, tw) + err := utils.WriteObject(s, rPath+"Site-"+s.Name, tw) if err != nil { return err } @@ -316,7 +313,7 @@ func (cmd *CmdDebug) Run() error { if routerAccessList != nil && err == nil { for _, site := range routerAccessList.Items { s := site.DeepCopy() - err := writeObject(s, rPath+"RouterAccess-"+s.Name, tw) + err := utils.WriteObject(s, rPath+"RouterAccess-"+s.Name, tw) if err != nil { return err } @@ -327,7 +324,7 @@ func (cmd *CmdDebug) Run() error { if securedAccessList != nil && err == nil { for _, site := range securedAccessList.Items { s := site.DeepCopy() - err := writeObject(s, rPath+"SecuredAccess-"+s.Name, tw) + err := utils.WriteObject(s, rPath+"SecuredAccess-"+s.Name, tw) if err != nil { return err } @@ -341,16 +338,16 @@ func (cmd *CmdDebug) Run() error { path := "/controller-namespace/" rPath := path + "resources/" - events, err := runCommand("kubectl", "events") + events, err := utils.RunCommand("kubectl", "events") if err == nil { - writeTar(path+"events.txt", events, time.Now(), tw) + utils.WriteTar(path+"events.txt", events, time.Now(), tw) } - endpoints, err := runCommand("kubectl", "get", "endpoints", "-o", "yaml") + endpoints, err := utils.RunCommand("kubectl", "get", "endpoints", "-o", "yaml") if err == nil { ePath := rPath + "Endpoints-skuper-controller.yaml" - writeTar(ePath, endpoints, time.Now(), tw) - writeTar(ePath+".txt", endpoints, time.Now(), tw) + utils.WriteTar(ePath, endpoints, time.Now(), tw) + utils.WriteTar(ePath+".txt", endpoints, time.Now(), tw) } err = getDeployments(cmd, path, "skupper-controller", tw) @@ -361,7 +358,7 @@ func (cmd *CmdDebug) Run() error { for i := range controllerServices { service, err := cmd.KubeClient.CoreV1().Services(cmd.Namespace).Get(context.TODO(), controllerServices[i], metav1.GetOptions{}) if err == nil { - err := writeObject(service, rPath+"Services-"+service.Name, tw) + err := utils.WriteObject(service, rPath+"Services-"+service.Name, tw) if err != nil { return err } @@ -372,55 +369,8 @@ func (cmd *CmdDebug) Run() error { return nil } -func runCommand(name string, args ...string) ([]byte, error) { - cmd := exec.Command(name, args...) - var out bytes.Buffer - cmd.Stdout = &out - cmd.Stderr = &out - err := cmd.Run() - if err != nil { - return nil, err - } - return out.Bytes(), nil -} - // helper functions -func writeTar(name string, data []byte, ts time.Time, tw *tar.Writer) error { - hdr := &tar.Header{ - Name: name, - Mode: 0600, - Size: int64(len(data)), - ModTime: ts, - } - err := tw.WriteHeader(hdr) - if err != nil { - return fmt.Errorf("Failed to write tar file header: %w", err) - } - _, err = tw.Write(data) - if err != nil { - return fmt.Errorf("Failed to write to tar archive: %w", err) - } - return nil -} - -func writeObject(rto runtime.Object, name string, tw *tar.Writer) error { - var b bytes.Buffer - s := json.NewYAMLSerializer(json.DefaultMetaFactory, scheme.Scheme, scheme.Scheme) - if err := s.Encode(rto, &b); err != nil { - return err - } - err := writeTar(name+".yaml", b.Bytes(), time.Now(), tw) - if err != nil { - return err - } - err = writeTar(name+".yaml.txt", b.Bytes(), time.Now(), tw) - if err != nil { - return err - } - return nil -} - func hasRestartedContainer(pod v1.Pod) bool { for _, containerStatus := range pod.Status.ContainerStatuses { if containerStatus.RestartCount > 0 { @@ -449,7 +399,7 @@ func getDeployments(cmd *CmdDebug, path string, deploymentType string, tw *tar.W continue } - err = writeObject(deployment, rPath+"Deployment-"+deployment.Name, tw) + err = utils.WriteObject(deployment, rPath+"Deployment-"+deployment.Name, tw) if err != nil { return err } @@ -464,14 +414,14 @@ func getDeployments(cmd *CmdDebug, path string, deploymentType string, tw *tar.W if err != nil { continue } else { - err := writeObject(pod, rPath+"Pod-"+pod.Name, tw) + err := utils.WriteObject(pod, rPath+"Pod-"+pod.Name, tw) if err != nil { return err } } - top, err := runCommand("kubectl", "top", "pod", pod.Name) + top, err := utils.RunCommand("kubectl", "top", "pod", pod.Name) if err == nil { - writeTar(rPath+pod.Name+"/top-pod.txt", top, time.Now(), tw) + utils.WriteTar(rPath+pod.Name+"/top-pod.txt", top, time.Now(), tw) } for container := range pod.Spec.Containers { @@ -480,7 +430,7 @@ func getDeployments(cmd *CmdDebug, path string, deploymentType string, tw *tar.W for x := range flags { qdr, err := client.ExecCommandInContainer([]string{"skstat", flags[x]}, pod.Name, "router", cmd.Namespace, cmd.KubeClient, cmd.Rest) if err == nil { - writeTar(rPath+"skstat/"+pod.Name+"-skstat"+flags[x]+".txt", qdr.Bytes(), time.Now(), tw) + utils.WriteTar(rPath+"skstat/"+pod.Name+"-skstat"+flags[x]+".txt", qdr.Bytes(), time.Now(), tw) } else { continue } @@ -489,13 +439,13 @@ func getDeployments(cmd *CmdDebug, path string, deploymentType string, tw *tar.W log, err := internalclient.GetPodContainerLogs(pod.Name, pod.Spec.Containers[container].Name, cmd.Namespace, cmd.KubeClient) if err == nil { - writeTar(path+"logs/"+pod.Name+"-"+pod.Spec.Containers[container].Name+".txt", []byte(log), time.Now(), tw) + utils.WriteTar(path+"logs/"+pod.Name+"-"+pod.Spec.Containers[container].Name+".txt", []byte(log), time.Now(), tw) } if hasRestartedContainer(*pod) { prevLog, err := internalclient.GetPodContainerLogsWithOpts(pod.Name, pod.Spec.Containers[container].Name, cmd.Namespace, cmd.KubeClient, v1.PodLogOptions{Previous: true}) if err == nil { - writeTar(path+"logs/"+pod.Name+"-"+pod.Spec.Containers[container].Name+"-previous.txt", []byte(prevLog), time.Now(), tw) + utils.WriteTar(path+"logs/"+pod.Name+"-"+pod.Spec.Containers[container].Name+"-previous.txt", []byte(prevLog), time.Now(), tw) } } } @@ -503,7 +453,7 @@ func getDeployments(cmd *CmdDebug, path string, deploymentType string, tw *tar.W role, err := cmd.KubeClient.RbacV1().Roles(cmd.Namespace).Get(context.TODO(), deployments[i], metav1.GetOptions{}) if err == nil && role != nil { - err = writeObject(role, rPath+"Role-"+deployment.Name, tw) + err = utils.WriteObject(role, rPath+"Role-"+deployment.Name, tw) if err != nil { return err } @@ -511,7 +461,7 @@ func getDeployments(cmd *CmdDebug, path string, deploymentType string, tw *tar.W roleBinding, err := cmd.KubeClient.RbacV1().RoleBindings(cmd.Namespace).Get(context.TODO(), deployments[i], metav1.GetOptions{}) if err == nil && roleBinding != nil { - err = writeObject(roleBinding, rPath+"RoleBinding-"+deployment.Name, tw) + err = utils.WriteObject(roleBinding, rPath+"RoleBinding-"+deployment.Name, tw) if err != nil { return err } @@ -521,7 +471,7 @@ func getDeployments(cmd *CmdDebug, path string, deploymentType string, tw *tar.W if err == nil && replicaSetList != nil { for _, replicaSet := range replicaSetList.Items { r := replicaSet.DeepCopy() - err = writeObject(r, rPath+"ReplicaSet-"+replicaSet.Name, tw) + err = utils.WriteObject(r, rPath+"ReplicaSet-"+replicaSet.Name, tw) if err != nil { return err } diff --git a/internal/cmd/skupper/debug/nonkube/debug.go b/internal/cmd/skupper/debug/nonkube/debug.go index 976a80ca1..18e41d99a 100644 --- a/internal/cmd/skupper/debug/nonkube/debug.go +++ b/internal/cmd/skupper/debug/nonkube/debug.go @@ -1,16 +1,39 @@ package nonkube import ( + "archive/tar" + "compress/gzip" + "errors" "fmt" + "os" + + "path/filepath" + "time" "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" + internalclient "github.com/skupperproject/skupper/internal/nonkube/client/compat" + "github.com/skupperproject/skupper/internal/nonkube/client/fs" + "github.com/skupperproject/skupper/internal/nonkube/client/runtime" + nk_common "github.com/skupperproject/skupper/internal/nonkube/common" + "github.com/skupperproject/skupper/internal/utils/validator" + "github.com/skupperproject/skupper/pkg/nonkube/api" "github.com/spf13/cobra" ) type CmdDebug struct { - CobraCmd *cobra.Command - Flags *common.CommandDebugFlags - Namespace string + CobraCmd *cobra.Command + Flags *common.CommandDebugFlags + namespace string + fileName string + connectorHandler *fs.ConnectorHandler + listenerHandler *fs.ListenerHandler + siteHandler *fs.SiteHandler + linkHandler *fs.LinkHandler + routerAccessHandler *fs.RouterAccessHandler + certificateHandler *fs.CertificateHandler + secretHandler *fs.SecretHandler + configMapHandler *fs.ConfigMapHandler } func NewCmdDebug() *CmdDebug { @@ -21,13 +44,399 @@ func NewCmdDebug() *CmdDebug { } func (cmd *CmdDebug) NewClient(cobraCommand *cobra.Command, args []string) { + if cmd.CobraCmd != nil && cmd.CobraCmd.Flag(common.FlagNameNamespace) != nil && cmd.CobraCmd.Flag(common.FlagNameNamespace).Value.String() != "" { + cmd.namespace = cmd.CobraCmd.Flag(common.FlagNameNamespace).Value.String() + } + + cmd.connectorHandler = fs.NewConnectorHandler(cmd.namespace) + cmd.listenerHandler = fs.NewListenerHandler(cmd.namespace) + cmd.siteHandler = fs.NewSiteHandler(cmd.namespace) + cmd.linkHandler = fs.NewLinkHandler(cmd.namespace) + cmd.routerAccessHandler = fs.NewRouterAccessHandler(cmd.namespace) + cmd.certificateHandler = fs.NewCertificateHandler(cmd.namespace) + cmd.secretHandler = fs.NewSecretHandler(cmd.namespace) + cmd.configMapHandler = fs.NewConfigMapHandler(cmd.namespace) +} + +func (cmd *CmdDebug) ValidateInput(args []string) error { + var validationErrors []error + fileStringValidator := validator.NewFilePathStringValidator() + + // Validate dump file name + if len(args) < 1 { + cmd.fileName = "skupper-dump" + } else if len(args) > 1 { + validationErrors = append(validationErrors, fmt.Errorf("only one argument is allowed for this command")) + } else if args[0] == "" { + validationErrors = append(validationErrors, fmt.Errorf("filename must not be empty")) + } else { + ok, err := fileStringValidator.Evaluate(args[0]) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("filename is not valid: %s", err)) + } else { + cmd.fileName = args[0] + } + } + return errors.Join(validationErrors...) } -func (cmd *CmdDebug) ValidateInput(args []string) error { return nil } +func (cmd *CmdDebug) InputToOptions() { + if cmd.namespace == "" { + cmd.namespace = "default" + } + datetime := time.Now().Format("20060102150405") + cmd.fileName = fmt.Sprintf("%s-%s-%s", cmd.fileName, cmd.namespace, datetime) +} + +func (cmd *CmdDebug) Run() error { + dumpFile := cmd.fileName + rpath := "/runtime/" + inpath := "/input/" + intpath := "/internal/" + certFiles := []string{"ca.crt", "tls.crt"} + flags := []string{"-g", "-c", "-l", "-n", "-e", "-a", "-m", "-p"} + + //check if namespace exists + path := api.GetInternalOutputPath(cmd.namespace, api.InputSiteStatePath) + if _, err := os.ReadDir(path); err != nil { + return fmt.Errorf("Namespace %s has not been configured, cannot run debug dump command", cmd.namespace) + } + + // Add extension if not present + if filepath.Ext(dumpFile) == "" { + dumpFile = dumpFile + ".tar.gz" + } + + tarFile, err := os.Create(dumpFile) + if err != nil { + return fmt.Errorf("Unable to save skupper dump details: %w", err) + } + + // compress tar + gz := gzip.NewWriter(tarFile) + defer gz.Close() + tw := tar.NewWriter(gz) + defer tw.Close() + + nsl := &nk_common.NamespacePlatformLoader{} + platform, err := nsl.Load(cmd.namespace) + + pv, err := utils.RunCommand("podman", "version") + if err == nil { + utils.WriteTar("/versions/podman.txt", pv, time.Now(), tw) + } + pv, err = utils.RunCommand("docker", "version") + if err == nil { + utils.WriteTar("/versions/docker.txt", pv, time.Now(), tw) + } + pv, err = utils.RunCommand("skrouterd", "--version") + if err == nil { + utils.WriteTar("/versions/skrouterd.txt", pv, time.Now(), tw) + } + + manifest, err := utils.RunCommand("skupper", "version", "-o", "yaml", "-n", cmd.namespace) + if err == nil { + utils.WriteTar("/versions/skupper.yaml", manifest, time.Now(), tw) + utils.WriteTar("/versions/skupper.yaml.txt", manifest, time.Now(), tw) + } + + //input/certs + path = inpath + "certs/" + certPath := api.GetInternalOutputPath(cmd.namespace, api.InputCertificatesPath) + certDirs, err := os.ReadDir(certPath) + if err == nil && certDirs != nil && len(certDirs) != 0 { + for _, certDir := range certDirs { + for x := range certFiles { + fileName := certDir.Name() + "/" + certFiles[x] + file, err := os.ReadFile(certPath + "/" + fileName) + if file != nil && err == nil { + utils.WriteTar(path+fileName, file, time.Now(), tw) + } + } + } + } + + //input/resources + opts := fs.GetOptions{RemoveKey: true, LogWarning: false} + path = inpath + "resources/" + sites, err := cmd.siteHandler.List(opts) + if err == nil && sites != nil && len(sites) != 0 { + for _, site := range sites { + err := utils.WriteObject(site, path+"Site-"+site.Name, tw) + if err != nil { + return err + } + } + } + + routerAccesses, err := cmd.routerAccessHandler.List(opts) + if err == nil && routerAccesses != nil && len(routerAccesses) != 0 { + for _, routerAccess := range routerAccesses { + err = utils.WriteObject(routerAccess, path+"RouterAccess-"+routerAccess.Name, tw) + if err != nil { + return err + } + } + } + + listeners, err := cmd.listenerHandler.List(opts) + if err == nil && listeners != nil && len(listeners) != 0 { + for _, listener := range listeners { + err := utils.WriteObject(listener, path+"Listener-"+listener.Name, tw) + if err != nil { + return err + } + } + } + + connectors, err := cmd.connectorHandler.List(opts) + if err == nil && connectors != nil && len(connectors) != 0 { + for _, connector := range connectors { + err := utils.WriteObject(connector, path+"Connector-"+connector.Name, tw) + if err != nil { + return err + } + } + } -func (cmd *CmdDebug) InputToOptions() {} + secrets, err := cmd.secretHandler.List(opts) + if err == nil && secrets != nil && len(secrets) != 0 { + for _, secret := range secrets { + err := utils.WriteObject(secret, path+"Secret-"+secret.Name, tw) + if err != nil { + return err + } + } + } -func (cmd *CmdDebug) Run() error { fmt.Println("not yet implemented"); return nil } + links, err := cmd.linkHandler.List(opts) + if err == nil && links != nil && len(links) != 0 { + for _, link := range links { + err := utils.WriteObject(link, path+"Link-"+link.Name, tw) + if err != nil { + return err + } + } + } + + //internal/scripts + path = intpath + "scripts/" + scriptsPath := api.GetInternalOutputPath(cmd.namespace, api.ScriptsPath) + scripts, err := os.ReadDir(scriptsPath) + if err == nil && scripts != nil && len(scripts) != 0 { + for _, script := range scripts { + fileName := script.Name() + file, err := os.ReadFile(scriptsPath + "/" + fileName) + if file != nil && err == nil { + utils.WriteTar(path+fileName, file, time.Now(), tw) + } + } + } + + // runtime/certs + path = rpath + "certs/" + certPath = api.GetInternalOutputPath(cmd.namespace, api.CertificatesPath) + certDirs, err = os.ReadDir(certPath) + if err == nil && certDirs != nil && len(certDirs) != 0 { + for _, certDir := range certDirs { + for x := range certFiles { + fileName := certDir.Name() + "/" + certFiles[x] + file, err := os.ReadFile(certPath + "/" + fileName) + if file != nil && err == nil { + utils.WriteTar(path+fileName, file, time.Now(), tw) + } + } + } + } + + // runtime/issuers + path = rpath + "issuers/" + iPath := api.GetInternalOutputPath(cmd.namespace, api.IssuersPath) + iDirs, err := os.ReadDir(iPath) + if err == nil && iDirs != nil && len(iDirs) != 0 { + for _, iDir := range iDirs { + for x := range certFiles { + fileName := iDir.Name() + "/" + certFiles[x] + file, err := os.ReadFile(iPath + "/" + fileName) + if file != nil && err == nil { + utils.WriteTar(path+fileName, file, time.Now(), tw) + } + } + } + } + + // runtime/links + path = rpath + "links/" + lpath := api.GetInternalOutputPath(cmd.namespace, api.RuntimeTokenPath) + lopts := fs.GetOptions{ResourcesPath: lpath, RuntimeFirst: true, LogWarning: false} + links, err = cmd.linkHandler.List(lopts) + if err == nil && links != nil && len(links) != 0 { + for _, link := range links { + err := utils.WriteObject(link, path+link.Name+"-"+link.Spec.Endpoints[0].Host, tw) + if err != nil { + return err + } + } + } + + //runtime/resources + path = rpath + "resources/" + opts = fs.GetOptions{RuntimeFirst: true, LogWarning: false, RemoveKey: true} + sites, err = cmd.siteHandler.List(opts) + if err == nil && sites != nil && len(sites) != 0 { + for _, site := range sites { + err := utils.WriteObject(site, path+"Site-"+site.Name, tw) + if err != nil { + return err + } + } + } + + routerAccesses, err = cmd.routerAccessHandler.List(opts) + if err == nil && routerAccesses != nil && len(routerAccesses) != 0 { + for _, routerAccess := range routerAccesses { + err = utils.WriteObject(routerAccess, path+"RouterAccess-"+routerAccess.Name, tw) + if err != nil { + return err + } + } + } + + listeners, err = cmd.listenerHandler.List(opts) + if err == nil && listeners != nil && len(listeners) != 0 { + for _, listener := range listeners { + err := utils.WriteObject(listener, path+"Listener-"+listener.Name, tw) + if err != nil { + return err + } + } + } + + connectors, err = cmd.connectorHandler.List(opts) + if err == nil && connectors != nil && len(connectors) != 0 { + for _, connector := range connectors { + err := utils.WriteObject(connector, path+"Connector-"+connector.Name, tw) + if err != nil { + return err + } + } + } + + certificates, err := cmd.certificateHandler.List() + if err == nil && certificates != nil && len(certificates) != 0 { + for _, certificate := range certificates { + err := utils.WriteObject(certificate, path+"Certificate-"+certificate.Name, tw) + if err != nil { + return err + } + } + } + + secrets, err = cmd.secretHandler.List(opts) + if err == nil && secrets != nil && len(secrets) != 0 { + for _, secret := range secrets { + err := utils.WriteObject(secret, path+"Secret-"+secret.Name, tw) + if err != nil { + return err + } + } + } + + configMaps, err := cmd.configMapHandler.List() + if err == nil && configMaps != nil && len(configMaps) != 0 { + for _, configMap := range configMaps { + err := utils.WriteObject(configMap, path+"ConfigMap-"+configMap.Name, tw) + if err != nil { + return err + } + } + } + + //runtime/router + path = rpath + "router/" + skrPath := api.GetInternalOutputPath(cmd.namespace, api.RouterConfigPath+"/skrouterd.json") + //skrPath := pathProvider.GetRuntimeNamespace()+ api. + skrouterd, err := os.ReadFile(skrPath) + if err == nil && skrouterd != nil { + utils.WriteTar(path+"skrouterd.json", skrouterd, time.Now(), tw) + } + + //logs and skupper-router statistics + if platform == "linux" { + user := "--user" + if os.Getuid() == 0 { + user = "" + } + rtrName := "skupper-" + cmd.namespace + ".service" + pv, err = utils.RunCommand("journalctl", user, "-u", rtrName, "--no-pager", "--all") + if err == nil { + utils.WriteTar(path+"logs/"+rtrName+".txt", pv, time.Now(), tw) + } + localRouterAddress, err := runtime.GetLocalRouterAddress(cmd.namespace) + if err == nil { + certs := runtime.GetRuntimeTlsCert(cmd.namespace, "skupper-local-client") + if err == nil { + for x := range flags { + pv, err = utils.RunCommand("/usr/bin/skstat", flags[x], + "-b", localRouterAddress, + "--ssl-certificate", certs.CertPath, + "--ssl-key", certs.KeyPath, + "--ssl-trustfile", certs.CaPath) + if err == nil { + utils.WriteTar(path+"skstat/"+"skrouterd"+"-skstat"+flags[x]+".txt", pv, time.Now(), tw) + } + } + } + } + } else { + if err := os.Setenv("SKUPPER_PLATFORM", platform); err == nil { + cli, err := internalclient.NewCompatClient(os.Getenv("CONTAINER_ENDPOINT"), "") + if err == nil { + rtrContainerName := cmd.namespace + "-skupper-router" + if container, err := cli.ContainerInspect(rtrContainerName); err == nil { + encodedOutput, _ := utils.Encode("yaml", container) + utils.WriteTar(rpath+"Container-"+container.Name+".yaml", []byte(encodedOutput), time.Now(), tw) + } + + localRouterAddress, err := runtime.GetLocalRouterAddress(cmd.namespace) + if err == nil { + for x := range flags { + skStatCommand := []string{ + "/bin/skstat", flags[x], + "-b", localRouterAddress, + "--ssl-certificate", "/etc/skupper-router/runtime/certs/skupper-local-client/tls.crt", + "--ssl-key", "/etc/skupper-router/runtime/certs/skupper-local-client/tls.key", + "--ssl-trustfile", "/etc/skupper-router/runtime/certs/skupper-local-client/ca.crt", + } + out, err := cli.ContainerExec(rtrContainerName, skStatCommand) //strings.Split(skStatCommand, " ")) + if err == nil { + utils.WriteTar(path+"skstat/"+rtrContainerName+"-skstat"+flags[x]+".txt", []byte(out), time.Now(), tw) + } + } + } + + logs, err := cli.ContainerLogs(rtrContainerName) + if err == nil { + utils.WriteTar(rpath+"logs/"+rtrContainerName+".txt", []byte(logs), time.Now(), tw) + } + + ctlContainerName := "system-controller" + if container, err := cli.ContainerInspect(ctlContainerName); err == nil { + encodedOutput, _ := utils.Encode("yaml", container) + utils.WriteTar(rpath+"Container-"+container.Name+".yaml", []byte(encodedOutput), time.Now(), tw) + } + + logs, err = cli.ContainerLogs(ctlContainerName) + if err == nil { + utils.WriteTar(rpath+"logs/"+ctlContainerName+".txt", []byte(logs), time.Now(), tw) + } + } + } + } + + fmt.Println("Skupper dump details written to compressed archive: ", dumpFile) + return nil +} func (cmd *CmdDebug) WaitUntil() error { return nil } diff --git a/internal/cmd/skupper/debug/nonkube/debug_test.go b/internal/cmd/skupper/debug/nonkube/debug_test.go index 6072c5b0e..83f87f19f 100644 --- a/internal/cmd/skupper/debug/nonkube/debug_test.go +++ b/internal/cmd/skupper/debug/nonkube/debug_test.go @@ -1 +1,370 @@ package nonkube + +import ( + "fmt" + "os" + "path/filepath" + "testing" + "time" + + "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/testutils" + "github.com/skupperproject/skupper/internal/nonkube/client/fs" + "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" + "github.com/skupperproject/skupper/pkg/nonkube/api" + "github.com/spf13/cobra" + "gotest.tools/v3/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestCmdDebug_ValidateInput(t *testing.T) { + type test struct { + name string + args []string + flags common.CommandDebugFlags + cobraGenericFlags map[string]string + expectedError string + } + + if os.Getuid() == 0 { + api.DefaultRootDataHome = t.TempDir() + } else { + t.Setenv("XDG_DATA_HOME", t.TempDir()) + } + + testTable := []test{ + { + name: "too many args", + flags: common.CommandDebugFlags{}, + args: []string{"test", "not-valid"}, + expectedError: "only one argument is allowed for this command", + }, + { + name: "empty name", + flags: common.CommandDebugFlags{}, + args: []string{""}, + expectedError: "filename must not be empty", + }, + { + name: "invalid name", + flags: common.CommandDebugFlags{}, + args: []string{"!Bad"}, + expectedError: "filename is not valid: value does not match this regular expression: ^[A-Za-z0-9./~-]+$", + }, + { + name: "ok", + flags: common.CommandDebugFlags{}, + args: []string{"test"}, + }, + { + name: "ok default name", + flags: common.CommandDebugFlags{}, + args: []string{}, + }, + } + + command := &CmdDebug{Flags: &common.CommandDebugFlags{}} + command.CobraCmd = &cobra.Command{Use: "test"} + command.namespace = "test" + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + + command.Flags = &test.flags + + if test.cobraGenericFlags != nil && len(test.cobraGenericFlags) > 0 { + for name, value := range test.cobraGenericFlags { + command.CobraCmd.Flags().String(name, value, "") + } + } + + testutils.CheckValidateInput(t, command, test.expectedError, test.args) + + }) + } +} + +func TestCmdDebug_InputToOptions(t *testing.T) { + type test struct { + name string + namespace string + filename string + args []string + flags common.CommandDebugFlags + } + + testTable := []test{ + { + name: "default name", + namespace: "default", + filename: "skupper-dump", + args: []string{}, + flags: common.CommandDebugFlags{}, + }, + { + name: "name", + namespace: "test", + filename: "dump", + args: []string{}, + flags: common.CommandDebugFlags{}, + }, + { + name: "name", + filename: "skupper-dump", + args: []string{}, + flags: common.CommandDebugFlags{}, + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + command := &CmdDebug{Flags: &common.CommandDebugFlags{}} + command.CobraCmd = &cobra.Command{Use: "test"} + namespace := test.namespace + if namespace == "" { + namespace = "default" + } + command.namespace = test.namespace + command.fileName = test.filename + name := fmt.Sprintf("%s-%s-%s", test.filename, namespace, time.Now().Format("20060102150405")) + command.InputToOptions() + + assert.Check(t, command.fileName == name) + }) + } +} + +func TestCmdDebug_Run(t *testing.T) { + type test struct { + name string + namespace string + DebugName string + errorMessage string + } + + testTable := []test{ + { + name: "run namespace exists", + namespace: "test2", + }, + { + name: "no namespace", + namespace: "default", + errorMessage: "Namespace default has not been configured, cannot run debug dump command", + }, + } + // Add a temp file so listener/connector/site exists for delete tests + listenerResource := v2alpha1.Listener{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "Listener", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-listener", + Namespace: "test2", + }, + } + connectorResource := v2alpha1.Connector{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "Connector", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-connector", + Namespace: "test2", + }, + } + siteResource := v2alpha1.Site{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "Site", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-site", + Namespace: "test2", + }, + } + routerAccessResource := v2alpha1.RouterAccess{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "RouterAccess", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-routerAccess", + Namespace: "test2", + }, + } + certificateResource := v2alpha1.Certificate{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "Certificate", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-certificate", + Namespace: "test2", + }, + } + secretResource := v1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-secret", + Namespace: "test2", + }, + } + linkResource := v2alpha1.Link{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "Link", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-link", + Namespace: "test2", + }, + Spec: v2alpha1.LinkSpec{ + Endpoints: []v2alpha1.Endpoint{ + { + Name: "inter-router", + Host: "127.0.0.1", + Port: "55671", + }, + }, + }, + } + configMapResource := v1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-map", + Namespace: "test2", + }, + } + + if os.Getuid() == 0 { + api.DefaultRootDataHome = t.TempDir() + } else { + t.Setenv("XDG_DATA_HOME", t.TempDir()) + } + tmpDir := api.GetDataHome() + ipath := filepath.Join(tmpDir, "/namespaces/test2/", string(api.InputSiteStatePath)) + rpath := filepath.Join(tmpDir, "/namespaces/test2/", string(api.RuntimeSiteStatePath)) + lpath := filepath.Join(tmpDir, "/namespaces/test2/", string(api.RuntimeTokenPath)) + + for _, test := range testTable { + command := &CmdDebug{Flags: &common.CommandDebugFlags{}} + command.CobraCmd = &cobra.Command{Use: "test"} + command.namespace = test.namespace + command.fileName = "/tmp/test" + command.siteHandler = fs.NewSiteHandler(command.namespace) + command.routerAccessHandler = fs.NewRouterAccessHandler(command.namespace) + command.listenerHandler = fs.NewListenerHandler(command.namespace) + command.connectorHandler = fs.NewConnectorHandler(command.namespace) + command.linkHandler = fs.NewLinkHandler(command.namespace) + command.certificateHandler = fs.NewCertificateHandler(command.namespace) + command.secretHandler = fs.NewSecretHandler(command.namespace) + command.configMapHandler = fs.NewConfigMapHandler(command.namespace) + + content, err := command.siteHandler.EncodeToYaml(siteResource) + assert.Check(t, err == nil) + err = command.siteHandler.WriteFile(ipath, "my-site.yaml", content, common.Sites) + assert.Check(t, err == nil) + err = command.siteHandler.WriteFile(rpath, "my-site.yaml", content, common.Sites) + assert.Check(t, err == nil) + //defer command.siteHandler.Delete("my-site") + + content, err = command.routerAccessHandler.EncodeToYaml(routerAccessResource) + assert.Check(t, err == nil) + err = command.routerAccessHandler.WriteFile(ipath, "my-routerAccess.yaml", content, common.RouterAccesses) + assert.Check(t, err == nil) + err = command.routerAccessHandler.WriteFile(rpath, "my-routerAccess.yaml", content, common.RouterAccesses) + assert.Check(t, err == nil) + //defer command.routerAccessHandler.Delete("my-routerAccess") + + content, err = command.listenerHandler.EncodeToYaml(listenerResource) + assert.Check(t, err == nil) + err = command.listenerHandler.WriteFile(ipath, "my-listener.yaml", content, common.Listeners) + assert.Check(t, err == nil) + err = command.listenerHandler.WriteFile(rpath, "my-listener.yaml", content, common.Listeners) + assert.Check(t, err == nil) + //defer command.listenerHandler.Delete("my-listener") + + content, err = command.connectorHandler.EncodeToYaml(connectorResource) + assert.Check(t, err == nil) + err = command.connectorHandler.WriteFile(ipath, "my-connector.yaml", content, common.Connectors) + assert.Check(t, err == nil) + err = command.connectorHandler.WriteFile(rpath, "my-connector.yaml", content, common.Connectors) + assert.Check(t, err == nil) + //defer command.connectorHandler.Delete("my-connector") + + content, err = command.certificateHandler.EncodeToYaml(certificateResource) + assert.Check(t, err == nil) + err = command.certificateHandler.WriteFile(ipath, "", content, common.Certificates) + assert.Check(t, err == nil) + err = command.certificateHandler.WriteFile(rpath, "my-certificate.yaml", content, common.Certificates) + assert.Check(t, err == nil) + //defer command.certificateHandler.Delete("my-certificate") + + content, err = command.secretHandler.EncodeToYaml(secretResource) + assert.Check(t, err == nil) + err = command.secretHandler.WriteFile(ipath, "my-secret.yaml", content, common.Secrets) + assert.Check(t, err == nil) + err = command.secretHandler.WriteFile(rpath, "my-secret.yaml", content, common.Secrets) + assert.Check(t, err == nil) + //defer command.secretHandler.Delete("my-secret") + + content, err = command.linkHandler.EncodeToYaml(linkResource) + assert.Check(t, err == nil) + err = command.linkHandler.WriteFile(ipath, "my-link.yaml", content, common.Links) + assert.Check(t, err == nil) + err = command.linkHandler.WriteFile(lpath, "my-link.yaml", content, "link") + assert.Check(t, err == nil) + //defer command.siteHandler.Delete("my-link") + + content, err = command.configMapHandler.EncodeToYaml(configMapResource) + assert.Check(t, err == nil) + err = command.configMapHandler.WriteFile(rpath, "my-configmap.yaml", content, common.ConfigMaps) + assert.Check(t, err == nil) + //defer command.secretHandler.Delete("my-secret") + + certPath := api.GetDefaultOutputPath(command.namespace) + "/" + string(api.InputCertificatesPath) + err = os.MkdirAll(certPath, 0775) + if err == nil { + _, _ = os.Create(certPath + "/ca.crt") + } + + certPath = api.GetDefaultOutputPath(command.namespace) + "/" + string(api.CertificatesPath) + "/link-router-access-test2/" + err = os.MkdirAll(certPath, 0775) + if err == nil { + _, _ = os.Create(certPath + "ca.crt") + } + + issuerPath := api.GetDefaultOutputPath(command.namespace) + "/" + string(api.IssuersPath) + "/skupper-local-ca/" + err = os.MkdirAll(issuerPath, 0775) + if err == nil { + _, _ = os.Create(issuerPath + "ca.crt") + } + + scriptPath := api.GetDefaultOutputPath(command.namespace) + "/" + string(api.ScriptsPath) + err = os.MkdirAll(scriptPath, 0775) + if err == nil { + _, _ = os.Create(scriptPath + "/start.sh") + } + + defer os.Remove("/tmp/test.tar.gz") //clean up + + t.Run(test.name, func(t *testing.T) { + + err := command.Run() + if err != nil { + assert.Check(t, test.errorMessage == err.Error()) + } else { + assert.Check(t, err == nil) + } + }) + } +} diff --git a/internal/cmd/skupper/link/nonkube/link_status.go b/internal/cmd/skupper/link/nonkube/link_status.go index 0f41879f8..7a3bca7f0 100644 --- a/internal/cmd/skupper/link/nonkube/link_status.go +++ b/internal/cmd/skupper/link/nonkube/link_status.go @@ -41,6 +41,7 @@ func (cmd *CmdLinkStatus) NewClient(cobraCommand *cobra.Command, args []string) func (cmd *CmdLinkStatus) ValidateInput(args []string) error { var validationErrors []error resourceStringValidator := validator.NewResourceStringValidator() + outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) // Validate arguments name if specified if len(args) > 1 { @@ -57,6 +58,13 @@ func (cmd *CmdLinkStatus) ValidateInput(args []string) error { } } } + + if cmd.Flags.Output != "" { + ok, _ := outputTypeValidator.Evaluate(cmd.Flags.Output) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("format bad-value not supported")) + } + } return errors.Join(validationErrors...) } @@ -76,7 +84,7 @@ func (cmd *CmdLinkStatus) Run() error { } else { - linkList, err := cmd.linkHandler.List(fs.GetOptions{LogWarning: false}) + linkList, err := cmd.linkHandler.List(fs.GetOptions{RuntimeFirst: true, LogWarning: false}) if err != nil { return err } diff --git a/internal/cmd/skupper/link/nonkube/link_status_test.go b/internal/cmd/skupper/link/nonkube/link_status_test.go index 26c034327..f23618faa 100644 --- a/internal/cmd/skupper/link/nonkube/link_status_test.go +++ b/internal/cmd/skupper/link/nonkube/link_status_test.go @@ -61,6 +61,18 @@ func TestCmdLinkStatus_ValidateInput(t *testing.T) { name: "no args", flags: &common.CommandLinkStatusFlags{}, }, + { + name: "good output status", + args: []string{"my-link"}, + flags: &common.CommandLinkStatusFlags{Output: "json"}, + expectedError: "", + }, + { + name: "bad output status", + args: []string{"my-link"}, + flags: &common.CommandLinkStatusFlags{Output: "invalid"}, + expectedError: "format bad-value not supported", + }, } //Add a temp file so link exists for status tests @@ -272,6 +284,7 @@ func TestCmdLinkStatus_Run(t *testing.T) { for _, test := range testTable { command.linkName = test.linkName command.Flags = &test.flags + command.output = command.Flags.Output t.Run(test.name, func(t *testing.T) { err := command.Run() diff --git a/internal/cmd/skupper/listener/nonkube/listener_status.go b/internal/cmd/skupper/listener/nonkube/listener_status.go index 0623178b8..120fd3205 100644 --- a/internal/cmd/skupper/listener/nonkube/listener_status.go +++ b/internal/cmd/skupper/listener/nonkube/listener_status.go @@ -3,10 +3,11 @@ package nonkube import ( "errors" "fmt" - k8serrs "k8s.io/apimachinery/pkg/api/errors" "os" "text/tabwriter" + k8serrs "k8s.io/apimachinery/pkg/api/errors" + "github.com/skupperproject/skupper/internal/cmd/skupper/common" "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" "github.com/skupperproject/skupper/internal/nonkube/client/fs" @@ -79,7 +80,7 @@ func (cmd *CmdListenerStatus) ValidateInput(args []string) error { func (cmd *CmdListenerStatus) Run() error { opts := fs.GetOptions{RuntimeFirst: true, LogWarning: true} if cmd.listenerName == "" { - resources, err := cmd.listenerHandler.List() + resources, err := cmd.listenerHandler.List(fs.GetOptions{RuntimeFirst: true}) if err != nil || resources == nil || len(resources) == 0 { fmt.Println("No listeners found") return err diff --git a/internal/cmd/skupper/site/nonkube/site_delete_test.go b/internal/cmd/skupper/site/nonkube/site_delete_test.go index bc02efd24..4015200ae 100644 --- a/internal/cmd/skupper/site/nonkube/site_delete_test.go +++ b/internal/cmd/skupper/site/nonkube/site_delete_test.go @@ -258,12 +258,12 @@ func TestCmdSiteDelete_Run(t *testing.T) { opts := fs.GetOptions{RuntimeFirst: false, LogWarning: false} site, _ := command.siteHandler.Get(command.siteName, opts) assert.Check(t, site == nil) - listeners, _ := listenerHandler.List() + listeners, _ := listenerHandler.List(opts) for _, listener := range listeners { resource, _ := listenerHandler.Get(listener.Name, opts) assert.Check(t, resource == nil) } - connectors, _ := connectorHandler.List() + connectors, _ := connectorHandler.List(opts) for _, connector := range connectors { resource, _ := connectorHandler.Get(connector.Name, opts) assert.Check(t, resource == nil) diff --git a/internal/nonkube/client/fs/certificate_handler.go b/internal/nonkube/client/fs/certificate_handler.go index 5deba4b05..542a05e1e 100644 --- a/internal/nonkube/client/fs/certificate_handler.go +++ b/internal/nonkube/client/fs/certificate_handler.go @@ -1,6 +1,8 @@ package fs import ( + "fmt" + "github.com/skupperproject/skupper/internal/cmd/skupper/common" "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" ) @@ -48,4 +50,29 @@ func (c *CertificateHandler) Delete(name string) error { return nil } -func (c *CertificateHandler) List() ([]*v2alpha1.Certificate, error) { return nil, nil } +func (c *CertificateHandler) List() ([]*v2alpha1.Certificate, error) { + var certificates []*v2alpha1.Certificate + // First read from runtime directory, where output is found after bootstrap + // has run. If no runtime secrets try and display configured secrets + path := c.pathProvider.GetRuntimeNamespace() + err, files := c.ReadDir(path, common.Certificates) + if err != nil { + fmt.Println("err: reading dir", path) + return nil, err + } + + for _, file := range files { + err, site := c.ReadFile(path, file.Name(), common.Certificates) + if err != nil { + fmt.Println("err reading file", file.Name()) + return nil, err + } + var context v2alpha1.Certificate + if err = c.DecodeYaml(site, &context); err != nil { + return nil, err + } + certificates = append(certificates, &context) + } + + return certificates, nil +} diff --git a/internal/nonkube/client/fs/connector_handler.go b/internal/nonkube/client/fs/connector_handler.go index 81e140430..5100aaf40 100644 --- a/internal/nonkube/client/fs/connector_handler.go +++ b/internal/nonkube/client/fs/connector_handler.go @@ -1,6 +1,7 @@ package fs import ( + "io/fs" "os" "github.com/skupperproject/skupper/internal/cmd/skupper/common" @@ -80,15 +81,27 @@ func (s *ConnectorHandler) Delete(name string) error { return nil } -func (s *ConnectorHandler) List() ([]*v2alpha1.Connector, error) { +func (s *ConnectorHandler) List(opts GetOptions) ([]*v2alpha1.Connector, error) { var connectors []*v2alpha1.Connector + var path string + var files []fs.DirEntry + var err error // First read from runtime directory, where output is found after bootstrap // has run. If no runtime connectors try and display configured connectors - path := s.pathProvider.GetRuntimeNamespace() - err, files := s.ReadDir(path, common.Connectors) - if err != nil { - os.Stderr.WriteString("Site not initialized yet\n") + if opts.RuntimeFirst { + path = s.pathProvider.GetRuntimeNamespace() + err, files = s.ReadDir(path, common.Connectors) + if err != nil { + os.Stderr.WriteString("Site not initialized yet\n") + path = s.pathProvider.GetNamespace() + err, files = s.ReadDir(path, common.Connectors) + if err != nil { + return nil, err + } + } + } else { + // just get configured values path = s.pathProvider.GetNamespace() err, files = s.ReadDir(path, common.Connectors) if err != nil { diff --git a/internal/nonkube/client/fs/custom_resource_handler.go b/internal/nonkube/client/fs/custom_resource_handler.go index a30a6ce57..a7b2d56fe 100644 --- a/internal/nonkube/client/fs/custom_resource_handler.go +++ b/internal/nonkube/client/fs/custom_resource_handler.go @@ -14,9 +14,11 @@ import ( ) type GetOptions struct { - RuntimeFirst bool - LogWarning bool - Attributes map[string]string + RuntimeFirst bool + LogWarning bool + Attributes map[string]string + ResourcesPath string + RemoveKey bool } type CustomResourceHandler[T any] interface { diff --git a/internal/nonkube/client/fs/link_handler.go b/internal/nonkube/client/fs/link_handler.go index 0543fd68b..aeca6f649 100644 --- a/internal/nonkube/client/fs/link_handler.go +++ b/internal/nonkube/client/fs/link_handler.go @@ -1,6 +1,7 @@ package fs import ( + "io/fs" "os" "strings" @@ -93,19 +94,37 @@ func (s *LinkHandler) Delete(name string) error { func (s *LinkHandler) List(opts GetOptions) ([]*v2alpha1.Link, error) { var links []*v2alpha1.Link + var files []fs.DirEntry + var err error + path := s.pathProvider.GetRuntimeNamespace() + prefix := common.Links + + // Based on the opts select the proper directory to get links from + if opts.ResourcesPath != "" { + prefix = "link" + path = opts.ResourcesPath + } // Read from runtime directory - path := s.pathProvider.GetRuntimeNamespace() - err, files := s.ReadDir(path, common.Links) - if err != nil { - if opts.LogWarning { - os.Stderr.WriteString("Site not initialized yet\n") + if opts.RuntimeFirst { + err, files = s.ReadDir(path, prefix) + if err != nil { + if opts.LogWarning { + os.Stderr.WriteString("Site not initialized yet\n") + } + return nil, err + } + } else { + // just get configured values + path = s.pathProvider.GetNamespace() + err, files = s.ReadDir(path, prefix) + if err != nil { + return nil, err } - return nil, err } for _, file := range files { - err, link := s.ReadFile(path, file.Name(), common.Links) + err, link := s.ReadFile(path, file.Name(), prefix) if err != nil { return nil, err } diff --git a/internal/nonkube/client/fs/listener_handler.go b/internal/nonkube/client/fs/listener_handler.go index dd12877d2..9274081e8 100644 --- a/internal/nonkube/client/fs/listener_handler.go +++ b/internal/nonkube/client/fs/listener_handler.go @@ -1,6 +1,7 @@ package fs import ( + "io/fs" "os" "github.com/skupperproject/skupper/internal/cmd/skupper/common" @@ -81,15 +82,27 @@ func (s *ListenerHandler) Delete(name string) error { return nil } -func (s *ListenerHandler) List() ([]*v2alpha1.Listener, error) { +func (s *ListenerHandler) List(opts GetOptions) ([]*v2alpha1.Listener, error) { var listeners []*v2alpha1.Listener + var path string + var files []fs.DirEntry + var err error // First read from runtime directory, where output is found after bootstrap // has run. If no runtime listeners try and display configured listeners - path := s.pathProvider.GetRuntimeNamespace() - err, files := s.ReadDir(path, common.Listeners) - if err != nil { - os.Stderr.WriteString("Site not initialized yet\n") + if opts.RuntimeFirst { + path = s.pathProvider.GetRuntimeNamespace() + err, files = s.ReadDir(path, common.Listeners) + if err != nil { + os.Stderr.WriteString("Site not initialized yet\n") + path = s.pathProvider.GetNamespace() + err, files = s.ReadDir(path, common.Listeners) + if err != nil { + return nil, err + } + } + } else { + // just get configured values path = s.pathProvider.GetNamespace() err, files = s.ReadDir(path, common.Listeners) if err != nil { diff --git a/internal/nonkube/client/fs/router_access_handler.go b/internal/nonkube/client/fs/router_access_handler.go index f1ab2d26f..53830f6fe 100644 --- a/internal/nonkube/client/fs/router_access_handler.go +++ b/internal/nonkube/client/fs/router_access_handler.go @@ -2,6 +2,8 @@ package fs import ( "fmt" + "io/fs" + "github.com/skupperproject/skupper/internal/cmd/skupper/common" "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" ) @@ -83,3 +85,44 @@ func (s *RouterAccessHandler) Update(name string) (*v2alpha1.RouterAccess, error return &context, nil } + +func (s *RouterAccessHandler) List(opts GetOptions) ([]*v2alpha1.RouterAccess, error) { + var routerAccesss []*v2alpha1.RouterAccess + var path string + var files []fs.DirEntry + var err error + + // First read from runtime directory, where output is found after bootstrap + // has run. If no runtime sites try and display configured sites + if opts.RuntimeFirst { + path = s.pathProvider.GetRuntimeNamespace() + err, files = s.ReadDir(path, common.RouterAccesses) + if err != nil { + path = s.pathProvider.GetNamespace() + err, files = s.ReadDir(path, common.RouterAccesses) + if err != nil { + return nil, err + } + } + } else { + // just get configured values + path = s.pathProvider.GetNamespace() + err, files = s.ReadDir(path, common.RouterAccesses) + if err != nil { + return nil, err + } + } + + for _, file := range files { + err, routerAccess := s.ReadFile(path, file.Name(), common.RouterAccesses) + if err != nil { + return nil, err + } + var context v2alpha1.RouterAccess + if err = s.DecodeYaml(routerAccess, &context); err != nil { + return nil, err + } + routerAccesss = append(routerAccesss, &context) + } + return routerAccesss, nil +} diff --git a/internal/nonkube/client/fs/secret_handler.go b/internal/nonkube/client/fs/secret_handler.go index ef8c7a133..0367e9e54 100644 --- a/internal/nonkube/client/fs/secret_handler.go +++ b/internal/nonkube/client/fs/secret_handler.go @@ -1,6 +1,9 @@ package fs import ( + "fmt" + "io/fs" + "github.com/skupperproject/skupper/internal/cmd/skupper/common" v1 "k8s.io/api/core/v1" ) @@ -46,4 +49,45 @@ func (s *SecretHandler) Delete(name string) error { return nil } -func (s *SecretHandler) List() ([]*v1.Secret, error) { return nil, nil } +func (c *SecretHandler) List(opts GetOptions) ([]*v1.Secret, error) { + var secrets []*v1.Secret + var path string + var files []fs.DirEntry + var err error + + // First read from runtime directory, where output is found after bootstrap + // has run. If no runtime secrets try and display configured secrets + if opts.RuntimeFirst { + path = c.pathProvider.GetRuntimeNamespace() + err, files = c.ReadDir(path, common.Secrets) + if err != nil { + return nil, err + } + } else { + // just get configured values + path = c.pathProvider.GetNamespace() + err, files = c.ReadDir(path, common.Secrets) + if err != nil { + return nil, err + } + } + + for _, file := range files { + err, secret := c.ReadFile(path, file.Name(), common.Secrets) + if err != nil { + fmt.Println("err reading file", file.Name()) + return nil, err + } + var context v1.Secret + if err = c.DecodeYaml(secret, &context); err != nil { + return nil, err + } + if opts.RemoveKey && context.Data["tls.key"] != nil { + context.Data["tls.key"] = []byte("") + } + + secrets = append(secrets, &context) + } + + return secrets, nil +} diff --git a/internal/nonkube/client/fs/site_handler.go b/internal/nonkube/client/fs/site_handler.go index 42ceb2647..7c28f8a6d 100644 --- a/internal/nonkube/client/fs/site_handler.go +++ b/internal/nonkube/client/fs/site_handler.go @@ -93,15 +93,27 @@ func (s *SiteHandler) Delete(name string) error { func (s *SiteHandler) List(opts GetOptions) ([]*v2alpha1.Site, error) { var sites []*v2alpha1.Site + var path string + var files []fs.DirEntry + var err error // First read from runtime directory, where output is found after bootstrap // has run. If no runtime sites try and display configured sites - path := s.pathProvider.GetRuntimeNamespace() - err, files := s.ReadDir(path, common.Sites) - if err != nil { - if opts.LogWarning { - os.Stderr.WriteString("Site not initialized yet\n") + if opts.RuntimeFirst { + path = s.pathProvider.GetRuntimeNamespace() + err, files = s.ReadDir(path, common.Sites) + if err != nil { + if opts.LogWarning { + os.Stderr.WriteString("Site not initialized yet\n") + } + path = s.pathProvider.GetNamespace() + err, files = s.ReadDir(path, common.Sites) + if err != nil { + return nil, err + } } + } else { + // just get configured values path = s.pathProvider.GetNamespace() err, files = s.ReadDir(path, common.Sites) if err != nil {