diff --git a/config/crds/troubleshoot.sh_collectors.yaml b/config/crds/troubleshoot.sh_collectors.yaml index a9250ccea..590298328 100644 --- a/config/crds/troubleshoot.sh_collectors.yaml +++ b/config/crds/troubleshoot.sh_collectors.yaml @@ -8823,6 +8823,19 @@ spec: - image - namespace type: object + velero: + properties: + collectorName: + type: string + exclude: + type: BoolString + namespace: + type: string + timeout: + type: string + required: + - namespace + type: object type: object type: array type: object diff --git a/config/crds/troubleshoot.sh_preflights.yaml b/config/crds/troubleshoot.sh_preflights.yaml index fbb0d7b4b..cd2392f6c 100644 --- a/config/crds/troubleshoot.sh_preflights.yaml +++ b/config/crds/troubleshoot.sh_preflights.yaml @@ -10320,6 +10320,19 @@ spec: - image - namespace type: object + velero: + properties: + collectorName: + type: string + exclude: + type: BoolString + namespace: + type: string + timeout: + type: string + required: + - namespace + type: object type: object type: array remoteCollectors: diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index c938845ae..861c708b5 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -10351,6 +10351,19 @@ spec: - image - namespace type: object + velero: + properties: + collectorName: + type: string + exclude: + type: BoolString + namespace: + type: string + timeout: + type: string + required: + - namespace + type: object type: object type: array hostAnalyzers: diff --git a/go.mod b/go.mod index 1beb58555..6ed2195fd 100644 --- a/go.mod +++ b/go.mod @@ -35,9 +35,10 @@ require ( github.com/spf13/viper v1.17.0 github.com/stretchr/testify v1.8.4 github.com/tj/go-spin v1.1.0 + github.com/vmware-tanzu/velero v1.11.1 go.opentelemetry.io/otel v1.18.0 go.opentelemetry.io/otel/sdk v1.18.0 - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 golang.org/x/sync v0.3.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.28.2 diff --git a/go.sum b/go.sum index 15c0f481d..0ddd8deda 100644 --- a/go.sum +++ b/go.sum @@ -192,7 +192,7 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774 h1:SCbEWT58NSt7d2mcFdvxC9uyrdcTfvBbPLThhkDmXzg= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1/go.mod h1:VzwV+t+dZ9j/H867F1M2ziD+yLHtB46oM35FxxMJ4d0= -github.com/Azure/azure-sdk-for-go v56.3.0+incompatible h1:DmhwMrUIvpeoTDiWRDtNHqelNUd3Og8JCkrLHQK795c= +github.com/Azure/azure-sdk-for-go v67.2.0+incompatible h1:Uu/Ww6ernvPTrpq31kITVTIm/I5jlJ1wjtEH/bmSB2k= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 h1:/iHxaJhsFr0+xVFfbMr5vxz848jyiWuIEDhYq3y5odY= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= @@ -800,15 +800,12 @@ github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.2 h1:Cj0yZY6T1Zx1R7AhTbyGSALm44/Mmq+BAPc4B/p/d3M= github.com/nwaples/rardecode v1.1.2/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= @@ -1001,6 +998,8 @@ github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYp github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vladimirvivien/gexe v0.2.0 h1:nbdAQ6vbZ+ZNsolCgSVb9Fno60kzSuvtzVh6Ytqi/xY= github.com/vladimirvivien/gexe v0.2.0/go.mod h1:LHQL00w/7gDUKIak24n801ABp8C+ni6eBht9vGVst8w= +github.com/vmware-tanzu/velero v1.11.1 h1:Jw8P1DetGBhmAZQkMRFu2vBvJS0YOR85XfKNpe4BlV8= +github.com/vmware-tanzu/velero v1.11.1/go.mod h1:ZBLIZSSNb6VzCea+rwcMsixm8AvvxCm6j8UjGiPv7XY= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= diff --git a/pkg/apis/troubleshoot/v1beta2/collector_shared.go b/pkg/apis/troubleshoot/v1beta2/collector_shared.go index a8d389bb6..da6adc2ed 100644 --- a/pkg/apis/troubleshoot/v1beta2/collector_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/collector_shared.go @@ -235,6 +235,12 @@ type Longhorn struct { Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"` } +type Velero struct { + CollectorMeta `json:",inline" yaml:",inline"` + Namespace string `json:"namespace" yaml:"namespace"` + Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"` +} + type RegistryImages struct { CollectorMeta `json:",inline" yaml:",inline"` Images []string `json:"images" yaml:"images"` @@ -279,6 +285,7 @@ type Collect struct { Redis *Database `json:"redis,omitempty" yaml:"redis,omitempty"` Collectd *Collectd `json:"collectd,omitempty" yaml:"collectd,omitempty"` Ceph *Ceph `json:"ceph,omitempty" yaml:"ceph,omitempty"` + Velero *Velero `json:"velero,omitempty" yaml:"velero,omitempty"` Longhorn *Longhorn `json:"longhorn,omitempty" yaml:"longhorn,omitempty"` RegistryImages *RegistryImages `json:"registryImages,omitempty" yaml:"registryImages,omitempty"` Sysctl *Sysctl `json:"sysctl,omitempty" yaml:"sysctl,omitempty"` @@ -573,6 +580,10 @@ func (c *Collect) GetName() string { collector = "ceph" name = c.Ceph.CollectorName } + if c.Velero != nil { + collector = "velero" + name = c.Velero.CollectorName + } if c.Longhorn != nil { collector = "longhorn" name = c.Longhorn.CollectorName @@ -628,3 +639,11 @@ func GetCollector(collector *Collect) interface{} { return nil } + +func labelsToSelector(labels map[string]string) []string { + selector := []string{} + for key, value := range labels { + selector = append(selector, fmt.Sprintf("%s=%s", key, value)) + } + return selector +} diff --git a/pkg/collect/ceph.go b/pkg/collect/ceph.go index 98fff75e5..763d4c9a3 100644 --- a/pkg/collect/ceph.go +++ b/pkg/collect/ceph.go @@ -3,7 +3,6 @@ package collect import ( "context" "fmt" - "os" "path" "strings" @@ -236,29 +235,3 @@ func GetCephCollectorFilepath(name, namespace string) string { parts = append(parts, "ceph") return path.Join(parts...) } - -func labelsToSelector(labels map[string]string) []string { - selector := []string{} - for key, value := range labels { - selector = append(selector, fmt.Sprintf("%s=%s", key, value)) - } - return selector -} - -func copyResult(srcResult CollectorResult, dstResult CollectorResult, bundlePath string, srcKey string, dstKey string) error { - reader, err := srcResult.GetReader(bundlePath, srcKey) - if err != nil { - if os.IsNotExist(errors.Cause(err)) { - return nil - } - return errors.Wrap(err, "failed to get reader") - } - defer reader.Close() - - err = dstResult.SaveResult(bundlePath, dstKey, reader) - if err != nil { - return errors.Wrap(err, "failed to save file") - } - - return nil -} diff --git a/pkg/collect/collect.go b/pkg/collect/collect.go index 2ead4cae8..2b35cfead 100644 --- a/pkg/collect/collect.go +++ b/pkg/collect/collect.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "os" "time" "github.com/pkg/errors" @@ -192,3 +193,21 @@ func CollectRemote(c *troubleshootv1beta2.RemoteCollector, additionalRedactors * collectResult.AllCollectedData = allCollectedData return collectResult, nil } + +func copyResult(srcResult CollectorResult, dstResult CollectorResult, bundlePath string, srcKey string, dstKey string) error { + reader, err := srcResult.GetReader(bundlePath, srcKey) + if err != nil { + if os.IsNotExist(errors.Cause(err)) { + return nil + } + return errors.Wrap(err, "failed to get reader") + } + defer reader.Close() + + err = dstResult.SaveResult(bundlePath, dstKey, reader) + if err != nil { + return errors.Wrap(err, "failed to save file") + } + + return nil +} diff --git a/pkg/collect/collector.go b/pkg/collect/collector.go index f6881daf8..e05f912cb 100644 --- a/pkg/collect/collector.go +++ b/pkg/collect/collector.go @@ -114,6 +114,8 @@ func GetCollector(collector *troubleshootv1beta2.Collect, bundlePath string, nam return &CollectSysctl{collector.Sysctl, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true case collector.Certificates != nil: return &CollectCertificates{collector.Certificates, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true + case collector.Velero != nil: + return &CollectVelero{collector.Velero, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true case collector.Helm != nil: return &CollectHelm{collector.Helm, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true default: @@ -185,6 +187,9 @@ func getCollectorName(c interface{}) string { case *CollectCeph: collector = "ceph" name = v.Collector.CollectorName + case *CollectVelero: + collector = "velero" + name = v.Collector.CollectorName case *CollectLonghorn: collector = "longhorn" name = v.Collector.CollectorName @@ -261,3 +266,11 @@ func DedupCollectors(allCollectors []*troubleshootv1beta2.Collect) []*troublesho } return finalCollectors } + +func labelsToSelector(labels map[string]string) []string { + selector := []string{} + for key, value := range labels { + selector = append(selector, fmt.Sprintf("%s=%s", key, value)) + } + return selector +} diff --git a/pkg/collect/velero.go b/pkg/collect/velero.go new file mode 100644 index 000000000..8b83e29ff --- /dev/null +++ b/pkg/collect/velero.go @@ -0,0 +1,479 @@ +package collect + +import ( + "bytes" + "context" + "fmt" + "net/http" + "path" + "path/filepath" + "strings" + + "github.com/pkg/errors" + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" + veleroclient "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned" + + "gopkg.in/yaml.v2" + corev1 "k8s.io/api/core/v1" + apiErrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/klog/v2" +) + +const ( + DefaultVeleroNamespace = "velero" +) + +type CollectVelero struct { + Collector *troubleshootv1beta2.Velero + BundlePath string + Namespace string + ClientConfig *rest.Config + Client kubernetes.Interface + Context context.Context + RBACErrors +} + +type VeleroCommand struct { + ID string + Command []string + Args []string + Format string + DefaultTimeout string +} + +var VeleroCommands = []VeleroCommand{ + { + ID: "get-backups", + Command: []string{"/velero", "get", "backups"}, + Args: []string{"-o", "yaml"}, + Format: "yaml", + DefaultTimeout: "30s", + }, + { + ID: "get-restores", + Command: []string{"/velero", "get", "restores"}, + Args: []string{"-o", "yaml"}, + Format: "yaml", + DefaultTimeout: "30s", + }, + { + ID: "describe-backups", + Command: []string{"/velero", "describe", "backups"}, + Args: []string{"--details", "--colorized", "false"}, + Format: "txt", + DefaultTimeout: "30s", + }, + { + ID: "describe-restores", + Command: []string{"/velero", "describe", "restores"}, + Args: []string{"--details", "--colorized", "false"}, + Format: "txt", + DefaultTimeout: "30s", + }, +} + +func (c *CollectVelero) Title() string { + return getCollectorName(c) +} + +func (c *CollectVelero) IsExcluded() (bool, error) { + return isExcluded(c.Collector.Exclude) +} + +func (c *CollectVelero) Collect(progressChan chan<- interface{}) (CollectorResult, error) { + ctx := context.TODO() + + ns := DefaultVeleroNamespace + if c.Collector.Namespace != "" { + ns = c.Collector.Namespace + } + + pod, err := findVeleroPod(ctx, c, c.Collector.Namespace) + if err != nil { + return nil, err + } + output := NewResult() + + // collect output from velero binary + if pod != nil { + for _, command := range VeleroCommands { + err := veleroCommandExec(ctx, progressChan, c, c.Collector, pod, command, output) + if err != nil { + pathPrefix := GetVeleroCollectorFilepath(c.Collector.CollectorName, c.Collector.Namespace) + dstFileName := path.Join(pathPrefix, fmt.Sprintf("%s.%s-error", command.ID, command.Format)) + output.SaveResult(c.BundlePath, dstFileName, strings.NewReader(err.Error())) + } + } + } + + veleroclient, err := veleroclient.NewForConfig(c.ClientConfig) + if err != nil { + return nil, errors.Wrap(err, "failed to create velero client") + } + + // collect backupstoragelocations.velero.io + backupStorageLocations, err := veleroclient.VeleroV1().BackupStorageLocations(c.Collector.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + if apiErr, ok := err.(*apiErrors.StatusError); ok { + if apiErr.ErrStatus.Code == http.StatusNotFound { + klog.V(2).Infof("failed to list backup storage locations in namespace %s", c.Collector.Namespace) + return NewResult(), nil + } + } + return nil, errors.Wrap(err, "list backupstoragelocations.velero.io") + } + dir := GetVeleroBackupStorageLocationsDirectory(ns) + for _, backupStorageLocation := range backupStorageLocations.Items { + b, err := yaml.Marshal(backupStorageLocation) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal backup storage location %s", backupStorageLocation.Name) + } + key := filepath.Join(dir, backupStorageLocation.Name+".yaml") + output.SaveResult(c.BundlePath, key, bytes.NewBuffer(b)) + } + + // collect backups.velero.io + backups, err := veleroclient.VeleroV1().Backups(c.Collector.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + if apiErr, ok := err.(*apiErrors.StatusError); ok { + if apiErr.ErrStatus.Code == http.StatusNotFound { + klog.V(2).Infof("failed to list backups in namespace %s", c.Collector.Namespace) + return NewResult(), nil + } + } + return nil, errors.Wrap(err, "list backups.velero.io") + } + dir = GetVeleroBackupsDirectory(ns) + for _, backup := range backups.Items { + b, err := yaml.Marshal(backup) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal backup %s", backup.Name) + } + key := filepath.Join(dir, backup.Name+".yaml") + output.SaveResult(c.BundlePath, key, bytes.NewBuffer(b)) + } + + // collect backuprepositories.velero.io + backupRepositories, err := veleroclient.VeleroV1().BackupRepositories(c.Collector.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + if apiErr, ok := err.(*apiErrors.StatusError); ok { + if apiErr.ErrStatus.Code == http.StatusNotFound { + klog.V(2).Infof("failed to list backuprepositories in namespace %s", c.Collector.Namespace) + return NewResult(), nil + } + } + return nil, errors.Wrap(err, "list backuprepositories.velero.io") + } + dir = GetVeleroBackupRepositoriesDirectory(ns) + for _, backupRepository := range backupRepositories.Items { + b, err := yaml.Marshal(backupRepository) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal backup repository %s", backupRepository.Name) + } + key := filepath.Join(dir, backupRepository.Name+".yaml") + output.SaveResult(c.BundlePath, key, bytes.NewBuffer(b)) + } + + // collect restores.velero.io + restores, err := veleroclient.VeleroV1().Restores(c.Collector.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, errors.Wrap(err, "list restores.velero.io") + } + dir = GetVeleroRestoresDirectory(ns) + for _, restore := range restores.Items { + b, err := yaml.Marshal(restore) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal restore %s", restore.Name) + } + key := filepath.Join(dir, restore.Name+".yaml") + output.SaveResult(c.BundlePath, key, bytes.NewBuffer(b)) + } + + // collect resticrepositories.velero.io + // resticRepositories, err := veleroclient.VeleroV1().ResticRepositories(c.Collector.Namespace).List(ctx, metav1.ListOptions{}) + // if err != nil { + // return nil, errors.Wrap(err, "list resticrepositories.velero.io") + // } + // dir = GetVeleroResticRepositoriesDirectory(ns) + // for _, resticRepository := range resticRepositories.Items { + // b, err := yaml.Marshal(resticRepository) + // if err != nil { + // return nil, errors.Wrapf(err, "failed to marshal restic repository %s", resticRepository.Name) + // } + // key := filepath.Join(dir, resticRepository.Name+".yaml") + // output.SaveResult(c.BundlePath, key, bytes.NewBuffer(b)) + // } + + // collect deletebackuprequests.velero.io + deleteBackupRequests, err := veleroclient.VeleroV1().DeleteBackupRequests(c.Collector.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, errors.Wrap(err, "list deletebackuprequests.velero.io") + } + dir = GetVeleroDeleteBackupRequestsDirectory(ns) + for _, deleteBackupRequest := range deleteBackupRequests.Items { + b, err := yaml.Marshal(deleteBackupRequest) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal delete backup request %s", deleteBackupRequest.Name) + } + key := filepath.Join(dir, deleteBackupRequest.Name+".yaml") + output.SaveResult(c.BundlePath, key, bytes.NewBuffer(b)) + } + + // collect downloadrequests.velero.io + downloadRequests, err := veleroclient.VeleroV1().DownloadRequests(c.Collector.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, errors.Wrap(err, "list downloadrequests.velero.io") + } + dir = GetVeleroDownloadRequestsDirectory(ns) + for _, downloadRequest := range downloadRequests.Items { + b, err := yaml.Marshal(downloadRequest) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal download request %s", downloadRequest.Name) + } + key := filepath.Join(dir, downloadRequest.Name+".yaml") + output.SaveResult(c.BundlePath, key, bytes.NewBuffer(b)) + } + + // collect podvolumebackups.velero.io + podVolumeBackups, err := veleroclient.VeleroV1().PodVolumeBackups(c.Collector.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, errors.Wrap(err, "list podvolumebackups.velero.io") + } + dir = GetVeleroPodVolumeBackupsDirectory(ns) + for _, podVolumeBackup := range podVolumeBackups.Items { + b, err := yaml.Marshal(podVolumeBackup) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal pod volume backup %s", podVolumeBackup.Name) + } + key := filepath.Join(dir, podVolumeBackup.Name+".yaml") + output.SaveResult(c.BundlePath, key, bytes.NewBuffer(b)) + } + + // collect podvolumerestores.velero.io + podVolumeRestores, err := veleroclient.VeleroV1().PodVolumeRestores(c.Collector.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, errors.Wrap(err, "list podvolumerestores.velero.io") + } + dir = GetVeleroPodVolumeRestoresDirectory(ns) + for _, podVolumeRestore := range podVolumeRestores.Items { + b, err := yaml.Marshal(podVolumeRestore) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal pod volume restore %s", podVolumeRestore.Name) + } + key := filepath.Join(dir, podVolumeRestore.Name+".yaml") + output.SaveResult(c.BundlePath, key, bytes.NewBuffer(b)) + } + + // collect restores.velero.io + restores, err = veleroclient.VeleroV1().Restores(c.Collector.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, errors.Wrap(err, "list restores.velero.io") + } + dir = GetVeleroRestoresDirectory(ns) + for _, restore := range restores.Items { + b, err := yaml.Marshal(restore) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal restore %s", restore.Name) + } + key := filepath.Join(dir, restore.Name+".yaml") + output.SaveResult(c.BundlePath, key, bytes.NewBuffer(b)) + } + + // collect schedules.velero.io + schedules, err := veleroclient.VeleroV1().Schedules(c.Collector.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, errors.Wrap(err, "list schedules.velero.io") + } + dir = GetVeleroSchedulesDirectory(ns) + for _, schedule := range schedules.Items { + b, err := yaml.Marshal(schedule) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal schedule %s", schedule.Name) + } + key := filepath.Join(dir, schedule.Name+".yaml") + output.SaveResult(c.BundlePath, key, bytes.NewBuffer(b)) + } + + // collect serverstatusrequests.velero.io + serverStatusRequests, err := veleroclient.VeleroV1().ServerStatusRequests(c.Collector.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, errors.Wrap(err, "list serverstatusrequests.velero.io") + } + dir = GetVeleroServerStatusRequestsDirectory(ns) + for _, serverStatusRequest := range serverStatusRequests.Items { + b, err := yaml.Marshal(serverStatusRequest) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal server status request %s", serverStatusRequest.Name) + } + key := filepath.Join(dir, serverStatusRequest.Name+".yaml") + output.SaveResult(c.BundlePath, key, bytes.NewBuffer(b)) + } + + // collect volumesnapshotlocations.velero.io + volumeSnapshotLocations, err := veleroclient.VeleroV1().VolumeSnapshotLocations(c.Collector.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, errors.Wrap(err, "list volumesnapshotlocations.velero.io") + } + dir = GetVeleroVolumeSnapshotLocationsDirectory(ns) + for _, volumeSnapshotLocation := range volumeSnapshotLocations.Items { + b, err := yaml.Marshal(volumeSnapshotLocation) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal volume snapshot location %s", volumeSnapshotLocation.Name) + } + key := filepath.Join(dir, volumeSnapshotLocation.Name+".yaml") + output.SaveResult(c.BundlePath, key, bytes.NewBuffer(b)) + } + + // collect logs + err = c.collectVeleroLogs(ns, output, progressChan) + if err != nil { + return nil, errors.Wrap(err, "collect velero logs") + } + + return output, nil +} + +func veleroCommandExec(ctx context.Context, progressChan chan<- interface{}, c *CollectVelero, veleroCollector *troubleshootv1beta2.Velero, pod *corev1.Pod, command VeleroCommand, output CollectorResult) error { + timeout := veleroCollector.Timeout + if timeout == "" { + timeout = command.DefaultTimeout + } + + execSpec := &troubleshootv1beta2.Exec{ + Selector: labelsToSelector(pod.Labels), + Namespace: pod.Namespace, + Command: command.Command, + Args: command.Args, + Timeout: timeout, + } + + rbacErrors := c.GetRBACErrors() + execCollector := &CollectExec{execSpec, c.BundlePath, c.Namespace, c.ClientConfig, c.Client, c.Context, rbacErrors} + + results, err := execCollector.Collect(progressChan) + if err != nil { + return errors.Wrap(err, "failed to exec velero command") + } + + pathPrefix := GetVeleroCollectorFilepath(veleroCollector.CollectorName, veleroCollector.Namespace) + for srcFilename := range results { + var dstFileName string + switch { + case strings.HasSuffix(srcFilename, "-stdout.txt"): + dstFileName = path.Join(pathPrefix, fmt.Sprintf("%s.%s", command.ID, command.Format)) + case strings.HasSuffix(srcFilename, "-stderr.txt"): + dstFileName = path.Join(pathPrefix, fmt.Sprintf("%s-stderr.txt", command.ID)) + case strings.HasSuffix(srcFilename, "-errors.json"): + dstFileName = path.Join(pathPrefix, fmt.Sprintf("%s-errors.json", command.ID)) + default: + continue + } + + err := copyResult(results, output, c.BundlePath, srcFilename, dstFileName) + if err != nil { + return errors.Wrap(err, "failed to copy file") + } + } + + return nil +} + +func findVeleroPod(ctx context.Context, c *CollectVelero, namespace string) (*corev1.Pod, error) { + client, err := kubernetes.NewForConfig(c.ClientConfig) + + if err != nil { + return nil, errors.Wrap(err, "failed to create kubernetes client") + } + + pods, _ := listPodsInSelectors(ctx, client, namespace, []string{"deploy=velero"}) + if len(pods) > 0 { + return &pods[0], nil + } + + klog.Info("velero pod not found in namespace %s", namespace) + + return nil, nil +} + +func GetVeleroCollectorFilepath(name, namespace string) string { + parts := []string{} + if name != "" { + parts = append(parts, name) + } + if namespace != "" && namespace != DefaultVeleroNamespace { + parts = append(parts, namespace) + } + parts = append(parts, "velero") + return path.Join(parts...) +} + +func (c *CollectVelero) collectVeleroLogs(namespace string, results CollectorResult, progressChan chan<- interface{}) error { + veleroLogsCollectorSpec := &troubleshootv1beta2.Logs{ + Selector: []string{""}, + Name: GetVeleroLogsDirectory(namespace), + Namespace: namespace, + } + rbacErrors := c.GetRBACErrors() + logsCollector := &CollectLogs{veleroLogsCollectorSpec, c.BundlePath, namespace, c.ClientConfig, c.Client, c.Context, nil, rbacErrors} + logs, err := logsCollector.Collect(progressChan) + if err != nil { + return err + } + results.AddResult(logs) + return nil +} + +func GetVeleroBackupsDirectory(namespace string) string { + return fmt.Sprintf("%s/backups", namespace) +} + +func GetVeleroBackupRepositoriesDirectory(namespace string) string { + return fmt.Sprintf("%s/backuprepositories", namespace) +} + +func GetVeleroBackupStorageLocationsDirectory(namespace string) string { + return fmt.Sprintf("%s/backupstoragelocations", namespace) +} + +func GetVeleroDeleteBackupRequestsDirectory(namespace string) string { + return fmt.Sprintf("%s/deletebackuprequests", namespace) +} + +func GetVeleroDownloadRequestsDirectory(namespace string) string { + return fmt.Sprintf("%s/downloadrequests", namespace) +} + +func GetVeleroLogsDirectory(namespace string) string { + return fmt.Sprintf("%s/logs", namespace) +} + +func GetVeleroPodVolumeBackupsDirectory(namespace string) string { + return fmt.Sprintf("%s/podvolumebackups", namespace) +} + +func GetVeleroPodVolumeRestoresDirectory(namespace string) string { + return fmt.Sprintf("%s/podvolumerestores", namespace) +} + +func GetVeleroRestoresDirectory(namespace string) string { + return fmt.Sprintf("%s/restores", namespace) +} + +func GetVeleroSchedulesDirectory(namespace string) string { + return fmt.Sprintf("%s/schedules", namespace) +} + +func GetVeleroServerStatusRequestsDirectory(namespace string) string { + return fmt.Sprintf("%s/serverstatusrequests", namespace) +} + +func GetVeleroVolumeSnapshotLocationsDirectory(namespace string) string { + return fmt.Sprintf("%s/volumesnapshotlocations", namespace) +} + +func GetVeleroResticRepositoriesDirectory(namespace string) string { + return fmt.Sprintf("%s/resticrepositories", namespace) +} diff --git a/pkg/supportbundle/test/velero.yaml b/pkg/supportbundle/test/velero.yaml new file mode 100644 index 000000000..74551be0f --- /dev/null +++ b/pkg/supportbundle/test/velero.yaml @@ -0,0 +1,7 @@ +apiVersion: troubleshoot.sh/v1beta2 +kind: SupportBundle +metadata: + name: velero +spec: + collectors: + - velero: {} diff --git a/schemas/collector-troubleshoot-v1beta2.json b/schemas/collector-troubleshoot-v1beta2.json index 2cdbfee9f..680e11555 100644 --- a/schemas/collector-troubleshoot-v1beta2.json +++ b/schemas/collector-troubleshoot-v1beta2.json @@ -7107,6 +7107,26 @@ "type": "string" } } + }, + "velero": { + "type": "object", + "required": [ + "namespace" + ], + "properties": { + "collectorName": { + "type": "string" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + }, + "namespace": { + "type": "string" + }, + "timeout": { + "type": "string" + } + } } } } diff --git a/schemas/preflight-troubleshoot-v1beta2.json b/schemas/preflight-troubleshoot-v1beta2.json index 4e2ee222b..6c7601863 100644 --- a/schemas/preflight-troubleshoot-v1beta2.json +++ b/schemas/preflight-troubleshoot-v1beta2.json @@ -9413,6 +9413,26 @@ "type": "string" } } + }, + "velero": { + "type": "object", + "required": [ + "namespace" + ], + "properties": { + "collectorName": { + "type": "string" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + }, + "namespace": { + "type": "string" + }, + "timeout": { + "type": "string" + } + } } } } diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index c667cb53b..cf65462c7 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -9459,6 +9459,26 @@ "type": "string" } } + }, + "velero": { + "type": "object", + "required": [ + "namespace" + ], + "properties": { + "collectorName": { + "type": "string" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + }, + "namespace": { + "type": "string" + }, + "timeout": { + "type": "string" + } + } } } }