mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-15 19:56:06 +00:00
Merge branch 'main' into enhance/Make/help
This commit is contained in:
@@ -14,7 +14,7 @@ jobs:
|
||||
if: github.repository == 'velero-io/velero'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: jpmcb/prow-github-actions@v1.1.3
|
||||
- uses: jpmcb/prow-github-actions@f4d01dd4b13f289014c23fe5a19878a2479cb35b # v1.1.3
|
||||
with:
|
||||
# TODO: before allowing the /lgtm command, see if we can block merging if changelog labels are missing.
|
||||
prow-commands: |
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
on:
|
||||
name: Automatic Rebase
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
name: Automatic Rebase
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
rebase:
|
||||
name: Rebase
|
||||
if: github.repository == 'velero-io/velero' && github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
|
||||
if: >-
|
||||
github.repository == 'velero-io/velero' &&
|
||||
github.event.issue.pull_request != null &&
|
||||
github.event.comment.body == '/rebase' &&
|
||||
contains(
|
||||
fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'),
|
||||
github.event.comment.author_association
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# contents: write is required because updating the pull request branch
|
||||
# pushes commits to the head branch.
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@1.8
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Rebase pull request
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.issue.number }}
|
||||
run: gh pr update-branch "$PR_NUMBER" --repo "$GH_REPO" --rebase
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Replace rebase action with GitHub CLI
|
||||
@@ -0,0 +1 @@
|
||||
Use "" as parentSnapshot for DU when BackupType is incremental.
|
||||
@@ -536,14 +536,10 @@ func newDataUpload(
|
||||
vsc *snapshotv1api.VolumeSnapshotContent,
|
||||
fsType string,
|
||||
) *velerov2alpha1.DataUpload {
|
||||
var parentSnapshot string
|
||||
switch backup.Spec.BackupType {
|
||||
case velerov1api.BackupTypeFull:
|
||||
parentSnapshot := ""
|
||||
|
||||
if backup.Spec.BackupType == velerov1api.BackupTypeFull {
|
||||
parentSnapshot = veleroshared.DataUploadParentSnapshotNone
|
||||
case velerov1api.BackupTypeIncremental:
|
||||
parentSnapshot = veleroshared.DataUploadParentSnapshotAuto
|
||||
default:
|
||||
parentSnapshot = veleroshared.DataUploadParentSnapshotAuto
|
||||
}
|
||||
|
||||
dataUpload := &velerov2alpha1.DataUpload{
|
||||
|
||||
@@ -45,7 +45,6 @@ import (
|
||||
crclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/apis/velero/shared"
|
||||
veleroshared "github.com/vmware-tanzu/velero/pkg/apis/velero/shared"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
@@ -162,7 +161,7 @@ func TestExecute(t *testing.T) {
|
||||
SourcePVC: "testPVC",
|
||||
SourceNamespace: "velero",
|
||||
OperationTimeout: metav1.Duration{Duration: 1 * time.Minute},
|
||||
ParentSnapshot: veleroshared.DataUploadParentSnapshotAuto,
|
||||
ParentSnapshot: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -2199,7 +2198,7 @@ func TestNewDataUpload(t *testing.T) {
|
||||
backupType: velerov1api.BackupTypeIncremental,
|
||||
vsClassName: ptr.To("test-vs-class"),
|
||||
uploaderConfig: &velerov1api.UploaderConfigForBackup{ParallelFilesUpload: 10},
|
||||
expectedParentSnap: "auto",
|
||||
expectedParentSnap: "",
|
||||
expectedDataMoverCfg: map[string]string{
|
||||
uploaderUtil.ParallelFilesUpload: "10",
|
||||
},
|
||||
@@ -2209,7 +2208,7 @@ func TestNewDataUpload(t *testing.T) {
|
||||
backupType: "",
|
||||
vsClassName: ptr.To("test-vs-class"),
|
||||
uploaderConfig: &velerov1api.UploaderConfigForBackup{ParallelFilesUpload: 0},
|
||||
expectedParentSnap: "auto",
|
||||
expectedParentSnap: "",
|
||||
expectedDataMoverCfg: nil,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -86,6 +86,12 @@ func (s *ServiceImpl) GetAllocatedBlocks(ctx context.Context, snapshot string, r
|
||||
return err
|
||||
}
|
||||
|
||||
saNamespace := ""
|
||||
if s.SAName != "" {
|
||||
// The SA is created in the same namespace as Velero server. vsNamespace is the namespace of Velero server.
|
||||
saNamespace = s.vsNamespace
|
||||
}
|
||||
|
||||
args := iterator.Args{
|
||||
SnapshotName: snapshot,
|
||||
Emitter: &emitterImpl{
|
||||
@@ -95,7 +101,7 @@ func (s *ServiceImpl) GetAllocatedBlocks(ctx context.Context, snapshot string, r
|
||||
|
||||
Clients: clients,
|
||||
Namespace: s.vsNamespace, // DataUpload is created in the same namespace as Velero server. vsNamespace is the namespace of the Velero server.
|
||||
SANamespace: s.vsNamespace, // The SA is created in the same namespace as Velero server. vsNamespace is the namespace of Velero server.
|
||||
SANamespace: saNamespace,
|
||||
SAName: s.SAName,
|
||||
TokenExpirySecs: iterator.DefaultTokenExpirySeconds,
|
||||
MaxResults: 0, // If 0 then the CSI driver decides the value.
|
||||
|
||||
@@ -331,6 +331,7 @@ func (s *dataMoverBackup) createDataPathService() (dataPathService, error) {
|
||||
s.config.changeID,
|
||||
s.config.volumeID,
|
||||
s.config.snapshotID,
|
||||
s.cbtService,
|
||||
s.logger,
|
||||
), nil
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov2alpha1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||
"github.com/vmware-tanzu/velero/pkg/cbtservice"
|
||||
"github.com/vmware-tanzu/velero/pkg/datapath"
|
||||
"github.com/vmware-tanzu/velero/pkg/repository"
|
||||
"github.com/vmware-tanzu/velero/pkg/uploader"
|
||||
@@ -71,6 +72,7 @@ type BackupMicroService struct {
|
||||
changeID string
|
||||
volumeID string
|
||||
snapshotID string
|
||||
cbtService cbtservice.Service
|
||||
}
|
||||
|
||||
type dataPathResult struct {
|
||||
@@ -80,7 +82,7 @@ type dataPathResult struct {
|
||||
|
||||
func NewBackupMicroService(ctx context.Context, client client.Client, kubeClient kubernetes.Interface, dataUploadName string, namespace string, nodeName string,
|
||||
sourceTargetPath datapath.AccessPoint, dataPathMgr *datapath.Manager, repoEnsurer *repository.Ensurer, cred *credentials.CredentialGetter,
|
||||
duInformer cache.Informer, changeID string, volumeID string, snapshotID string, log logrus.FieldLogger) *BackupMicroService {
|
||||
duInformer cache.Informer, changeID string, volumeID string, snapshotID string, cbtService cbtservice.Service, log logrus.FieldLogger) *BackupMicroService {
|
||||
return &BackupMicroService{
|
||||
ctx: ctx,
|
||||
client: client,
|
||||
@@ -98,6 +100,7 @@ func NewBackupMicroService(ctx context.Context, client client.Client, kubeClient
|
||||
changeID: changeID,
|
||||
volumeID: volumeID,
|
||||
snapshotID: snapshotID,
|
||||
cbtService: cbtService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,6 +213,7 @@ func (r *BackupMicroService) RunCancelableDataPath(ctx context.Context) (string,
|
||||
VolumeID: r.volumeID,
|
||||
ChangeID: r.changeID,
|
||||
SnapshotID: r.snapshotID,
|
||||
CBTService: r.cbtService,
|
||||
}); err != nil {
|
||||
return "", errors.Wrap(err, "error starting data path backup")
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ type BackupStartParam struct {
|
||||
VolumeID string
|
||||
ChangeID string
|
||||
SnapshotID string
|
||||
CBTService cbtservice.Service
|
||||
}
|
||||
|
||||
// RestoreStartParam define the input param for restore start
|
||||
@@ -203,6 +204,7 @@ func (dp *generalDataPath) StartBackup(source AccessPoint, uploaderConfig map[st
|
||||
VolumeID: backupParam.VolumeID,
|
||||
ChangeID: backupParam.ChangeID,
|
||||
},
|
||||
Service: backupParam.CBTService,
|
||||
},
|
||||
source.VolMode,
|
||||
uploaderConfig,
|
||||
|
||||
@@ -19,7 +19,6 @@ package kopialib
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -913,8 +912,7 @@ func (kow *kopiaObjectWriterEx) Write(p []byte) (int, error) {
|
||||
kow.entryLock.Unlock()
|
||||
|
||||
buffOffset := curPos - offset
|
||||
objName := fmt.Sprintf("%s-b%v", kow.description, entryID)
|
||||
kow.writeObjectAsync(objName, entryID, p[buffOffset:buffOffset+kow.blockSize])
|
||||
kow.writeObjectAsync(entryID, p[buffOffset:buffOffset+kow.blockSize])
|
||||
|
||||
curPos += kow.blockSize
|
||||
}
|
||||
@@ -922,38 +920,38 @@ func (kow *kopiaObjectWriterEx) Write(p []byte) (int, error) {
|
||||
return length, nil
|
||||
}
|
||||
|
||||
func (kow *kopiaObjectWriterEx) writeObject(objName string, p []byte) (object.ID, error) {
|
||||
func (kow *kopiaObjectWriterEx) writeObject(p []byte) (object.ID, error) {
|
||||
writer := kow.rawRepoWriter.NewObjectWriter(kopia.SetupKopiaLog(kow.ctx, kow.logger), object.WriterOptions{
|
||||
Description: objName,
|
||||
Description: kow.description,
|
||||
Compressor: kow.compressor,
|
||||
Splitter: kow.splitter,
|
||||
})
|
||||
|
||||
if writer == nil {
|
||||
return object.EmptyID, errors.Errorf("error opening writer for %s", objName)
|
||||
return object.EmptyID, errors.New("error opening writer")
|
||||
}
|
||||
|
||||
defer writer.Close()
|
||||
|
||||
written, err := writer.Write(p)
|
||||
if err != nil {
|
||||
return object.EmptyID, errors.Wrapf(err, "error writing for %s", objName)
|
||||
return object.EmptyID, errors.Wrap(err, "error writing data")
|
||||
}
|
||||
|
||||
if written != len(p) {
|
||||
return object.EmptyID, errors.Errorf("short write for %s", objName)
|
||||
return object.EmptyID, errors.New("short write")
|
||||
}
|
||||
|
||||
objID, err := writer.Result()
|
||||
if err != nil {
|
||||
return object.EmptyID, errors.Wrapf(err, "error flushing data for %s", objName)
|
||||
return object.EmptyID, errors.Wrap(err, "error flushing data")
|
||||
}
|
||||
|
||||
return objID, nil
|
||||
}
|
||||
|
||||
func (kow *kopiaObjectWriterEx) writeObjectSync(objName string, entry int, p []byte) error {
|
||||
objID, err := kow.writeObject(objName, p)
|
||||
func (kow *kopiaObjectWriterEx) writeObjectSync(entry int, p []byte) error {
|
||||
objID, err := kow.writeObject(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -965,10 +963,10 @@ func (kow *kopiaObjectWriterEx) writeObjectSync(objName string, entry int, p []b
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kow *kopiaObjectWriterEx) writeObjectAsync(objName string, entryID int, p []byte) {
|
||||
func (kow *kopiaObjectWriterEx) writeObjectAsync(entryID int, p []byte) {
|
||||
if kow.asyncWritesSem == nil {
|
||||
if err := kow.writeObjectSync(objName, entryID, p); err != nil {
|
||||
kow.saveWriteError(errors.Wrapf(err, "error writing object for %s", objName))
|
||||
if err := kow.writeObjectSync(entryID, p); err != nil {
|
||||
kow.saveWriteError(errors.Wrapf(err, "error writing object for %s, entry %d", kow.description, entryID))
|
||||
}
|
||||
} else {
|
||||
kow.asyncWritesSem <- struct{}{}
|
||||
@@ -977,8 +975,8 @@ func (kow *kopiaObjectWriterEx) writeObjectAsync(objName string, entryID int, p
|
||||
copy(buffer, p)
|
||||
|
||||
kow.asyncWritesGroup.Go(func() {
|
||||
if err := kow.writeObjectSync(objName, entryID, buffer); err != nil {
|
||||
kow.saveWriteError(errors.Wrapf(err, "error writing object for %s", objName))
|
||||
if err := kow.writeObjectSync(entryID, buffer); err != nil {
|
||||
kow.saveWriteError(errors.Wrapf(err, "error writing object for %s, entry %d", kow.description, entryID))
|
||||
}
|
||||
|
||||
kow.asyncBuffer.Return(buffer)
|
||||
@@ -987,10 +985,10 @@ func (kow *kopiaObjectWriterEx) writeObjectAsync(objName string, entryID int, p
|
||||
}
|
||||
}
|
||||
|
||||
func (kow *kopiaObjectWriterEx) writeZeroObject(objName string, entryID int) error {
|
||||
func (kow *kopiaObjectWriterEx) writeZeroObject(entryID int) error {
|
||||
if kow.zeroObject == object.EmptyID {
|
||||
zeroBuffer := make([]byte, kow.blockSize)
|
||||
objectID, err := kow.writeObject(objName, zeroBuffer)
|
||||
objectID, err := kow.writeObject(zeroBuffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1071,9 +1069,8 @@ func (kow *kopiaObjectWriterEx) WriteAt(p []byte, offset int64) (int, error) {
|
||||
})
|
||||
kow.entryLock.Unlock()
|
||||
|
||||
objName := fmt.Sprintf("%s-b%v", kow.description, entryID)
|
||||
if err := kow.writeZeroObject(objName, entryID); err != nil {
|
||||
return 0, errors.Wrapf(err, "error writing zero object for %s", objName)
|
||||
if err := kow.writeZeroObject(entryID); err != nil {
|
||||
return 0, errors.Wrapf(err, "error writing zero object for %s, entry %v", kow.description, entryID)
|
||||
}
|
||||
|
||||
curPos += kow.blockSize
|
||||
@@ -1093,8 +1090,7 @@ func (kow *kopiaObjectWriterEx) WriteAt(p []byte, offset int64) (int, error) {
|
||||
kow.entryLock.Unlock()
|
||||
|
||||
buffOffset := curPos - offset
|
||||
objName := fmt.Sprintf("%s-b%v", kow.description, entryID)
|
||||
kow.writeObjectAsync(objName, entryID, p[buffOffset:buffOffset+kow.blockSize])
|
||||
kow.writeObjectAsync(entryID, p[buffOffset:buffOffset+kow.blockSize])
|
||||
|
||||
curPos += kow.blockSize
|
||||
}
|
||||
|
||||
@@ -291,7 +291,7 @@ func TestKopiaObjectWriterEx_Write(t *testing.T) {
|
||||
t.Helper()
|
||||
err := kow.getWriteError()
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "error opening writer for -b0")
|
||||
assert.Contains(t, err.Error(), "error writing object for , entry 0: error opening writer")
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -936,7 +936,7 @@ func TestKopiaObjectWriterEx_WriteAt(t *testing.T) {
|
||||
},
|
||||
inputData: make([]byte, 1024),
|
||||
offset: 1024,
|
||||
expectedErr: "error writing zero object for -b0: error writing for -b0: simulated zero object write error",
|
||||
expectedErr: "error writing zero object for , entry 0: error writing data: simulated zero object write error",
|
||||
},
|
||||
{
|
||||
name: "writeObject short write",
|
||||
@@ -964,7 +964,7 @@ func TestKopiaObjectWriterEx_WriteAt(t *testing.T) {
|
||||
t.Helper()
|
||||
err := kow.getWriteError()
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "short write for -b0")
|
||||
assert.Contains(t, err.Error(), "error writing object for , entry 0: short write")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -19,12 +19,14 @@ package block
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -61,10 +63,11 @@ type Uploader interface {
|
||||
}
|
||||
|
||||
type blockUploader struct {
|
||||
ctx context.Context
|
||||
repoWriter udmrepo.BackupRepo
|
||||
progress uploader.ProgressUpdater
|
||||
log logrus.FieldLogger
|
||||
ctx context.Context
|
||||
repoWriter udmrepo.BackupRepo
|
||||
progress uploader.ProgressUpdater
|
||||
log logrus.FieldLogger
|
||||
lastProgressUpdate time.Time
|
||||
}
|
||||
|
||||
func NewUploader(ctx context.Context, repoWriter udmrepo.BackupRepo, progress uploader.ProgressUpdater, log logrus.FieldLogger) Uploader {
|
||||
@@ -89,7 +92,7 @@ func (blkup *blockUploader) Backup(source sourceInfo, parentObject udmrepo.ID, b
|
||||
}
|
||||
|
||||
destObj, err := blkup.repoWriter.NewObjectWriter(blkup.ctx, udmrepo.ObjectWriteOptions{
|
||||
Description: "BDEV:" + getObjectName(source.realSource),
|
||||
Description: fmt.Sprintf("BDEV:%s-%s", getObjectName(source.realSource), snapStart.Format("2006-01-02-15-04-05")),
|
||||
DataType: udmrepo.ObjectDataTypeData,
|
||||
AccessMode: udmrepo.ObjectDataAccessModeBlock,
|
||||
ParentObject: parentObject,
|
||||
@@ -197,6 +200,17 @@ func (blkup *blockUploader) backupObject(dev *os.File, dest udmrepo.ObjectWriter
|
||||
return id, backupSize, objectSize, err
|
||||
}
|
||||
|
||||
func (blkup *blockUploader) UpdateProgress(p *uploader.Progress) {
|
||||
if blkup.progress == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if time.Since(blkup.lastProgressUpdate) >= 10*time.Second || p.BytesDone == p.TotalBytes {
|
||||
blkup.progress.UpdateProgress(p)
|
||||
blkup.lastProgressUpdate = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
type readResult struct {
|
||||
buffer []byte
|
||||
offset int64
|
||||
@@ -232,7 +246,7 @@ func (blkup *blockUploader) backupData(reader io.ReaderAt, writer udmrepo.Object
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer close(quit)
|
||||
written, lastPos, writeErr = backupWriteProc(blkup.ctx, writer, resultChan, list, aligned, totalCount, int(blockSize), blkup.progress)
|
||||
written, lastPos, writeErr = backupWriteProc(blkup.ctx, writer, resultChan, list, aligned, totalCount, int(blockSize), blkup)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
@@ -249,7 +263,7 @@ func (blkup *blockUploader) backupData(reader io.ReaderAt, writer udmrepo.Object
|
||||
|
||||
written += s
|
||||
|
||||
blkup.progress.UpdateProgress(&uploader.Progress{BytesDone: aligned, TotalBytes: aligned})
|
||||
blkup.UpdateProgress(&uploader.Progress{BytesDone: aligned, TotalBytes: aligned})
|
||||
}
|
||||
|
||||
return written, aligned, nil
|
||||
@@ -418,7 +432,7 @@ func (blkup *blockUploader) restoreData(reader io.ReadSeeker, dest *os.File, bit
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer close(quit)
|
||||
written, writeErr = restoreWriteProc(blkup.ctx, dest, resultChan, list, totalLength, totalCount, int(blockSize), destPath, blkup.progress, blkup.log)
|
||||
written, writeErr = restoreWriteProc(blkup.ctx, dest, resultChan, list, totalLength, totalCount, int(blockSize), destPath, blkup, blkup.log)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -357,7 +358,7 @@ func TestBlockUploaderBackup(t *testing.T) {
|
||||
}
|
||||
|
||||
repoWriter.On("NewObjectWriter", mock.Anything, mock.MatchedBy(func(opt udmrepo.ObjectWriteOptions) bool {
|
||||
return opt.Description == "BDEV:data-volume1" && opt.BackupMode == backupMode
|
||||
return strings.HasPrefix(opt.Description, "BDEV:data-volume1-") && opt.BackupMode == backupMode
|
||||
})).Return(objWriter, tc.createObjErr)
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -8,7 +8,7 @@ frontmatter:
|
||||
params:
|
||||
author: Velero Authors
|
||||
logo: Velero.svg
|
||||
cncf_logo: cncf-white.svg
|
||||
cncf_logo: cncf-color.svg
|
||||
hero:
|
||||
backgroundColor: med-blue
|
||||
versioning: true
|
||||
@@ -63,6 +63,9 @@ params:
|
||||
- title: Twitter
|
||||
fa_icon: fab fa-twitter
|
||||
url: https://twitter.com/projectvelero
|
||||
- title: LinkedIn
|
||||
fa_icon: fab fa-linkedin
|
||||
url: https://www.linkedin.com/company/project-velero
|
||||
- title: Slack
|
||||
fa_icon: fab fa-slack
|
||||
url: https://kubernetes.slack.com/messages/velero
|
||||
|
||||
@@ -32,7 +32,7 @@ secondary_ctas:
|
||||
url: /blog/Velero-is-an-Open-Source-Tool-to-Back-up-and-Migrate-Kubernetes-Clusters/ # Velero.io word list : ignore
|
||||
content: Learn about Velero and how to protect your Kubernetes resources and volumes.
|
||||
cta2:
|
||||
title: How Do You Use Velero?
|
||||
url: https://github.com/velero-io/velero/issues/1327
|
||||
content: See how Velero is helping others and tell the world how you use Velero.
|
||||
title: Join the Velero Community
|
||||
url: /community/
|
||||
content: Connect with other Velero users on Slack, attend community meetings, and contribute to the project.
|
||||
---
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: "Velero Joins the CNCF Sandbox"
|
||||
excerpt: Velero has been accepted into the Cloud Native Computing Foundation as a Sandbox project, bringing Kubernetes-native backup and disaster recovery under vendor-neutral, community-driven governance.
|
||||
author_name: Shubham Pampattiwar
|
||||
slug: Velero-Joins-CNCF-Sandbox
|
||||
categories: ['velero','announcements']
|
||||
image: /img/cncf-color.svg
|
||||
tags: ['Velero Team', 'Shubham Pampattiwar', 'CNCF']
|
||||
---
|
||||
|
||||

|
||||
|
||||
We are excited to announce that Velero has been accepted into the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io/) as a Sandbox project. This marks a significant milestone for the project, placing Velero under vendor-neutral, community-driven governance alongside other foundational cloud native tools.
|
||||
|
||||
The CNCF Technical Oversight Committee (TOC) accepted the [Sandbox application](https://github.com/cncf/sandbox/issues/457), and the transition was formally announced at KubeCon + CloudNativeCon Europe 2026 in Amsterdam.
|
||||
|
||||
## What This Means
|
||||
|
||||
Joining the CNCF Sandbox means Velero is now governed by the same open, vendor-neutral principles that guide projects like Kubernetes, Prometheus, and Envoy. In practice, this means:
|
||||
|
||||
- **Vendor-neutral governance**: No single company controls the project roadmap. Decisions are made through consensus-based processes with supermajority voting.
|
||||
- **Community-driven development**: The project's direction is shaped by its maintainers and contributors, who represent multiple organizations.
|
||||
- **Long-term sustainability**: CNCF provides a neutral home that ensures the project's continuity regardless of changes in any single company's priorities.
|
||||
|
||||
For existing Velero users, nothing changes in how you use the tool. Velero continues to operate at the Kubernetes API layer, providing backup, restore, disaster recovery, and migration capabilities for your clusters and applications.
|
||||
|
||||
## Our Journey
|
||||
|
||||
Velero's journey began at Heptio, the Kubernetes company founded by Joe Beda and Craig McLuckie, where it was originally known as Ark. After VMware acquired Heptio in 2019, the project continued to grow under VMware's stewardship. Following Broadcom's acquisition of VMware, the decision was made to contribute Velero to the CNCF, ensuring the project's future under community governance.
|
||||
|
||||
Throughout these transitions, one thing has remained constant: a growing and engaged open source community. Today, Velero has over 10,000 GitHub stars, 1,500+ forks, 500M+ Docker Hub pulls, and is used by organizations across industries for Kubernetes data protection.
|
||||
|
||||
We are grateful to Broadcom for contributing Velero to the CNCF and to everyone who has contributed to the project over the years.
|
||||
|
||||
## Current Maintainers
|
||||
|
||||
Velero is maintained by engineers from multiple organizations, reflecting the project's vendor-neutral nature:
|
||||
|
||||
| Maintainer | GitHub | Affiliation |
|
||||
|---|---|---|
|
||||
| Daniel Jiang | [@reasonerjt](https://github.com/reasonerjt) | Broadcom |
|
||||
| Wenkai Yin | [@ywk253100](https://github.com/ywk253100) | Broadcom |
|
||||
| Xun Jiang | [@blackpiglet](https://github.com/blackpiglet) | Broadcom |
|
||||
| Yonghui Li | [@Lyndon-Li](https://github.com/Lyndon-Li) | Broadcom |
|
||||
| Scott Seago | [@sseago](https://github.com/sseago) | Red Hat (OpenShift) |
|
||||
| Shubham Pampattiwar | [@shubham-pampattiwar](https://github.com/shubham-pampattiwar) | Red Hat (OpenShift) |
|
||||
| Tiger Kaovilai | [@kaovilai](https://github.com/kaovilai) | Red Hat (OpenShift) |
|
||||
| Anshul Ahuja | [@anshulahuja98](https://github.com/anshulahuja98) | Microsoft (Azure) |
|
||||
|
||||
## What's Next
|
||||
|
||||
Joining the CNCF Sandbox is the beginning of a new chapter for Velero. Here is what we are focused on:
|
||||
|
||||
- **Growing the community**: We want more contributors, more adopters, and more voices shaping the project's direction. Whether you are a user, operator, or developer, there is a place for you in the Velero community.
|
||||
- **Strengthening the project**: We are continuing to improve Velero's core capabilities around backup performance, data protection, and ecosystem integration.
|
||||
- **Path to Incubation**: Our goal is to demonstrate the community health, adoption, and maturity needed to advance to CNCF Incubation status.
|
||||
|
||||
## Get Involved
|
||||
|
||||
We welcome contributions of all kinds -- code, documentation, bug reports, feature requests, and feedback.
|
||||
|
||||
- **Slack**: Join [#velero-users](https://kubernetes.slack.com/messages/velero) and [#velero-dev](https://kubernetes.slack.com/messages/velero-dev) on Kubernetes Slack
|
||||
- **GitHub**: [github.com/velero-io/velero](https://github.com/velero-io/velero)
|
||||
- **Community Meetings**: We hold bi-weekly community meetings alternating between US/Europe and US/Asia-friendly time zones. See the [community page](https://velero.io/community/) for details.
|
||||
- **LinkedIn**: Follow us at [Project Velero](https://www.linkedin.com/company/project-velero)
|
||||
- **Twitter/X**: [@projectvelero](https://twitter.com/projectvelero)
|
||||
- **Contributing**: Check out our [contribution guide](https://velero.io/docs/main/start-contributing/) to get started.
|
||||
|
||||
We are excited about this new chapter and look forward to building the future of Kubernetes data protection together with the community.
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="a" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 399.1 76.1" style="enable-background:new 0 0 399.1 76.1;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0086FF;}
|
||||
.st1{fill:#93EAFF;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M98.9,33.4c1.5,0,2.9-0.3,4.3-0.9c1.3-0.6,2.5-1.6,3.4-2.8l4.1,4.2c-3.2,3.6-6.9,5.4-11.3,5.4c-2,0.1-3.9-0.2-5.8-1
|
||||
c-1.8-0.7-3.5-1.8-5-3.1c-1.4-1.3-2.5-3-3.2-4.7c-0.7-1.8-1.1-3.7-1-5.6c-0.1-1.9,0.3-3.9,1-5.7c0.7-1.8,1.8-3.4,3.2-4.8
|
||||
c1.5-1.4,3.3-2.5,5.2-3.2c1.9-0.7,4-1,6.1-0.9c2.1,0.1,4.1,0.6,6,1.5c1.9,0.9,3.5,2.2,4.9,3.8l-3.9,4.5c-0.9-1.2-2-2.1-3.3-2.7
|
||||
c-1.3-0.6-2.7-1-4.2-1c-2.2,0-4.4,0.8-6.1,2.3c-0.9,0.8-1.5,1.8-2,2.9c-0.4,1.1-0.6,2.3-0.6,3.4c-0.1,1.2,0.1,2.3,0.5,3.4
|
||||
c0.4,1.1,1,2.1,1.9,2.9C94.7,32.6,96.8,33.4,98.9,33.4L98.9,33.4z M115.5,38.9V10.7h6.3v22.6h12.1v5.6H115.5z M161.5,35
|
||||
c-2.9,2.7-6.6,4.2-10.6,4.2s-7.7-1.5-10.6-4.2c-1.4-1.3-2.5-2.9-3.2-4.7c-0.7-1.8-1.1-3.7-1.1-5.6c-0.1-1.9,0.3-3.9,1-5.6
|
||||
c0.7-1.8,1.8-3.4,3.2-4.7c2.9-2.7,6.6-4.2,10.6-4.2s7.7,1.5,10.6,4.2c1.4,1.3,2.5,2.9,3.2,4.7c0.7,1.8,1.1,3.7,1,5.6
|
||||
c0.1,1.9-0.3,3.9-1,5.6C164,32.1,162.9,33.7,161.5,35z M159.3,24.7c0-2.4-0.9-4.7-2.5-6.5c-0.8-0.8-1.7-1.5-2.7-2
|
||||
c-1-0.5-2.2-0.7-3.3-0.7c-1.1,0-2.2,0.2-3.3,0.7c-1,0.5-2,1.1-2.7,2c-1.6,1.8-2.5,4.1-2.5,6.5c0,2.4,0.9,4.7,2.5,6.5
|
||||
c0.8,0.8,1.7,1.5,2.7,2c1,0.5,2.2,0.7,3.3,0.7c1.1,0,2.2-0.2,3.3-0.7c1-0.5,2-1.1,2.7-2c0.8-0.9,1.5-1.9,1.9-3
|
||||
C159.2,27,159.4,25.9,159.3,24.7z M178.6,31.7c0.5,0.7,1.2,1.2,1.9,1.5c0.7,0.3,1.6,0.5,2.4,0.5c0.8,0,1.6-0.1,2.3-0.5
|
||||
c0.7-0.3,1.4-0.9,1.8-1.5c1.1-1.6,1.6-3.4,1.5-5.3V10.8h6.3v15.8c0,4.1-1.1,7.2-3.4,9.4c-1.2,1.1-2.5,2-4,2.5
|
||||
c-1.5,0.6-3.1,0.8-4.7,0.8c-1.6,0-3.2-0.2-4.7-0.8c-1.5-0.6-2.9-1.4-4-2.5c-2.3-2.2-3.4-5.3-3.4-9.4V10.8h6.3v15.6
|
||||
C176.9,28.3,177.5,30.2,178.6,31.7z M223.1,14.4c2.7,2.5,4.1,5.9,4.1,10.3s-1.3,7.9-3.9,10.5c-2.6,2.6-6.7,3.8-12,3.8h-9.8V10.7
|
||||
h10C216.5,10.7,220.4,11.9,223.1,14.4z M218.5,31.2c1.5-1.4,2.3-3.6,2.3-6.4s-0.8-4.9-2.3-6.4s-3.9-2.3-7.1-2.3h-3.5v17.2h4
|
||||
C214.3,33.5,216.7,32.8,218.5,31.2z M263.1,10.7h6.3v28.3h-6.3l-13.5-17.7v17.7h-6.3V10.7h5.9l13.9,18.2V10.7z M296.5,38.9
|
||||
l-2.7-6.1h-11.9l-2.7,6.1h-6.8l12.2-28.3h6.1L303,38.9H296.5z M288,19.1l-3.5,8.2h7L288,19.1z M316.5,16.2V39h-6.3V16.2h-8v-5.4
|
||||
h22.4v5.4C324.6,16.2,316.5,16.2,316.5,16.2z M329,10.7h6.3v28.3H329C329,38.9,329,10.7,329,10.7z M352.8,28.7l7.2-18h6.9
|
||||
l-11.3,28.3h-5.3l-11.3-28.3h6.9L352.8,28.7z M390.5,10.7v5.6h-14.1v5.8h12.7v5.4h-12.7v5.9H391V39h-20.8V10.8L390.5,10.7
|
||||
C390.5,10.8,390.5,10.7,390.5,10.7z M91.6,63c0.8,0,1.6-0.2,2.4-0.5c0.7-0.4,1.4-0.9,1.9-1.6l2.3,2.4c-0.8,0.9-1.7,1.7-2.9,2.2
|
||||
c-1.1,0.5-2.3,0.8-3.5,0.8c-1.1,0-2.2-0.1-3.2-0.5c-1-0.4-2-1-2.8-1.8c-0.8-0.7-1.4-1.6-1.8-2.6c-0.4-1-0.6-2.1-0.6-3.2
|
||||
c0-1.1,0.2-2.2,0.6-3.2c0.4-1,1-1.9,1.8-2.7c0.8-0.8,1.7-1.4,2.7-1.8c1-0.4,2.1-0.6,3.2-0.6c1.2,0,2.5,0.2,3.6,0.7
|
||||
c1.1,0.5,2.1,1.3,3,2.2l-2.2,2.5c-0.5-0.7-1.1-1.2-1.8-1.6c-0.7-0.4-1.5-0.5-2.4-0.5c-1.3,0-2.5,0.5-3.4,1.3
|
||||
c-0.5,0.4-0.9,1-1.1,1.6c-0.2,0.6-0.4,1.3-0.3,1.9c0,0.6,0.1,1.3,0.3,1.9c0.2,0.6,0.6,1.2,1,1.6c0.4,0.4,0.9,0.8,1.5,1
|
||||
S91,63,91.6,63L91.6,63z M113.9,63.9c-1.6,1.5-3.7,2.4-5.9,2.4c-1.1,0-2.2-0.2-3.2-0.6c-1-0.4-1.9-1-2.7-1.8
|
||||
c-1.5-1.5-2.4-3.6-2.4-5.8s0.9-4.3,2.4-5.8c1.6-1.5,3.7-2.4,5.9-2.4c1.1,0,2.2,0.2,3.2,0.6s1.9,1,2.7,1.8
|
||||
c1.5,1.5,2.4,3.6,2.4,5.8S115.4,62.4,113.9,63.9z M112.8,58.1c0-1.3-0.4-2.6-1.3-3.6c-0.4-0.5-0.9-0.8-1.5-1.1
|
||||
c-0.6-0.3-1.2-0.4-1.8-0.4c-0.6,0-1.3,0.1-1.8,0.4c-0.6,0.3-1.1,0.7-1.5,1.2c-0.5,0.5-0.8,1-1,1.7c-0.2,0.6-0.3,1.3-0.3,1.9
|
||||
c0,1.3,0.5,2.6,1.3,3.6c0.4,0.5,0.9,0.8,1.5,1.1c0.6,0.3,1.2,0.4,1.8,0.4c0.6,0,1.3-0.1,1.8-0.4c0.6-0.3,1.1-0.7,1.5-1.2
|
||||
C112.3,60.8,112.8,59.5,112.8,58.1L112.8,58.1z M133.5,56.1l-4.3,8.7h-2.1l-4.3-8.7v10h-3.5V50.3h4.8l4.1,8.7l4.1-8.7h4.8v15.9
|
||||
h-3.5L133.5,56.1L133.5,56.1z M151.9,51.8c0.6,0.5,1.1,1.2,1.4,2c0.3,0.8,0.4,1.6,0.3,2.4c0,2-0.6,3.4-1.7,4.3
|
||||
c-1.1,1-2.9,1.3-5.2,1.3h-2.1v4.4H141V50.3h5.6C149,50.3,150.7,50.8,151.9,51.8z M149.3,58c0.5-0.6,0.7-1.3,0.7-2.1
|
||||
c0-0.4,0-0.7-0.2-1.1c-0.2-0.3-0.4-0.6-0.7-0.8c-0.8-0.4-1.7-0.6-2.6-0.6h-2v5.3h2.4c0.4,0,0.9,0,1.3-0.1
|
||||
C148.6,58.4,149,58.2,149.3,58z M160.6,62c0.3,0.4,0.6,0.6,1.1,0.8c0.4,0.2,0.9,0.3,1.3,0.3c0.5,0,0.9-0.1,1.3-0.3
|
||||
c0.4-0.2,0.8-0.5,1.1-0.8c0.6-0.9,0.9-2,0.9-3v-8.8h3.5v8.8c0.1,1-0.1,1.9-0.4,2.9c-0.3,0.9-0.8,1.8-1.5,2.5
|
||||
c-1.3,1.2-3.1,1.8-4.9,1.8c-1.8,0-3.5-0.7-4.8-1.9c-0.7-0.7-1.2-1.6-1.5-2.5c-0.3-0.9-0.5-1.9-0.4-2.9v-8.8h3.5v8.8
|
||||
C159.6,60,159.9,61.1,160.6,62z M180.1,53.4v12.7h-3.5V53.4h-4.5v-3h12.6v3C184.6,53.4,180.1,53.4,180.1,53.4z M187.2,50.3h3.5
|
||||
v15.9h-3.5C187.2,66.2,187.2,50.3,187.2,50.3z M205.7,50.3h3.5v15.9h-3.5l-7.5-9.9v9.9h-3.5V50.3h3.3l7.8,10.2L205.7,50.3
|
||||
C205.8,50.3,205.7,50.3,205.7,50.3z M223.5,58.1h3.5v5.6c-0.8,0.9-1.9,1.6-3,2c-1.1,0.5-2.4,0.7-3.6,0.6c-2.2,0-4.3-0.8-5.9-2.3
|
||||
c-0.8-0.7-1.4-1.6-1.8-2.6c-0.4-1-0.6-2.1-0.6-3.2c0-1.1,0.2-2.2,0.6-3.2c0.4-1,1-1.9,1.8-2.7s1.7-1.4,2.7-1.8
|
||||
c1-0.4,2.1-0.6,3.2-0.6c2.2,0,4.3,0.8,5.9,2.3l-1.8,2.7c-0.5-0.6-1.2-1-2-1.2c-0.6-0.2-1.3-0.4-1.9-0.4c-1.3,0-2.5,0.5-3.4,1.3
|
||||
c-0.5,0.5-0.9,1-1.1,1.7s-0.4,1.3-0.3,2c0,1.3,0.4,2.6,1.3,3.6c0.4,0.4,0.9,0.8,1.4,1c0.5,0.2,1.1,0.3,1.7,0.3
|
||||
c1.1,0.1,2.1-0.2,3-0.7v-4.5H223.5z M247,50.3v3.1h-7.3v3.4h6.9V60h-6.9v6.2h-3.5V50.3C236.1,50.3,247,50.3,247,50.3z
|
||||
M263.4,63.9c-1.6,1.5-3.7,2.4-5.9,2.4c-1.1,0-2.2-0.2-3.2-0.6c-1-0.4-1.9-1-2.7-1.8c-1.5-1.5-2.4-3.6-2.4-5.8s0.9-4.3,2.4-5.8
|
||||
c1.6-1.5,3.7-2.4,5.9-2.4c1.1,0,2.2,0.2,3.2,0.6s1.9,1,2.7,1.8c1.5,1.5,2.4,3.6,2.4,5.8S264.9,62.4,263.4,63.9z M262.1,58.1
|
||||
c0-1.3-0.4-2.6-1.3-3.6c-0.4-0.5-0.9-0.8-1.5-1.1c-0.6-0.3-1.2-0.4-1.8-0.4c-0.6,0-1.3,0.1-1.8,0.4c-0.6,0.3-1.1,0.7-1.5,1.2
|
||||
c-0.5,0.5-0.8,1-1,1.7s-0.3,1.3-0.3,1.9c0,1.3,0.5,2.6,1.3,3.6c0.4,0.5,0.9,0.8,1.5,1.1c0.6,0.3,1.2,0.4,1.8,0.4
|
||||
c0.6,0,1.3-0.1,1.8-0.4c0.6-0.3,1.1-0.7,1.5-1.2c0.5-0.5,0.8-1,1-1.7S262.2,58.8,262.1,58.1L262.1,58.1z M273,62
|
||||
c0.3,0.4,0.6,0.6,1.1,0.8c0.4,0.2,0.9,0.3,1.3,0.3s0.9-0.1,1.3-0.3c0.4-0.2,0.8-0.5,1.1-0.8c0.6-0.9,0.9-2,0.9-3v-8.8h3.5v8.8
|
||||
c0.1,1-0.1,1.9-0.4,2.9c-0.3,0.9-0.8,1.8-1.5,2.5c-1.3,1.2-3.1,1.8-4.9,1.8c-1.8,0-3.5-0.7-4.8-1.9c-0.7-0.7-1.2-1.6-1.5-2.5
|
||||
c-0.3-0.9-0.5-1.9-0.4-2.9v-8.8h3.5v8.8C272,60,272.3,61.2,273,62z M296.9,50.3h3.5v15.9h-3.5l-7.5-9.9v9.9h-3.5V50.3h3.3
|
||||
l7.8,10.2L296.9,50.3C297,50.3,296.9,50.3,296.9,50.3z M316.4,52.4c0.8,0.7,1.4,1.7,1.8,2.7s0.6,2.1,0.5,3.1
|
||||
c0.1,1.1-0.1,2.1-0.5,3.1c-0.4,1-1,1.9-1.7,2.7c-1.4,1.4-3.7,2.2-6.8,2.2h-5.4V50.3h5.6C312.8,50.3,315,51,316.4,52.4z
|
||||
M313.8,61.9c0.4-0.5,0.8-1,1-1.6c0.2-0.6,0.3-1.3,0.3-1.9c0-0.7-0.1-1.3-0.3-1.9s-0.6-1.2-1-1.7c-0.6-0.5-1.2-0.8-1.9-1
|
||||
s-1.4-0.3-2.1-0.2h-2v9.6h2.3C311.4,63.1,312.8,62.7,313.8,61.9z M332.6,66.1l-1.4-3.4h-6.7l-1.4,3.4h-3.8l6.9-15.9h3.4l6.9,15.9
|
||||
H332.6z M327.8,55l-2,4.6h4L327.8,55z M343.8,53.4v12.7h-3.5V53.4h-4.6v-3h12.6v3C348.3,53.4,343.8,53.4,343.8,53.4z M350.8,50.3
|
||||
h3.5v15.9h-3.5C350.8,66.2,350.8,50.3,350.8,50.3z M371.6,63.9c-1.6,1.5-3.7,2.4-5.9,2.4c-1.1,0-2.2-0.2-3.2-0.6s-1.9-1-2.7-1.8
|
||||
c-1.5-1.5-2.4-3.6-2.4-5.8s0.9-4.3,2.4-5.8c1.6-1.5,3.7-2.4,5.9-2.4c1.1,0,2.2,0.2,3.2,0.6c1,0.4,1.9,1,2.7,1.8
|
||||
c1.5,1.5,2.4,3.6,2.4,5.8S373.1,62.4,371.6,63.9z M370.5,58.1c0-1.3-0.5-2.6-1.3-3.6c-0.4-0.5-0.9-0.8-1.5-1.1
|
||||
c-0.6-0.3-1.2-0.4-1.8-0.4c-0.6,0-1.3,0.1-1.8,0.4c-0.6,0.3-1.1,0.7-1.5,1.2c-0.5,0.5-0.8,1-1,1.7s-0.3,1.3-0.3,1.9
|
||||
c0,1.3,0.5,2.6,1.3,3.6c0.4,0.5,0.9,0.8,1.5,1.1c0.6,0.3,1.2,0.4,1.8,0.4c0.6,0,1.3-0.1,1.8-0.4c0.6-0.3,1.1-0.7,1.5-1.2
|
||||
C370,60.8,370.5,59.5,370.5,58.1z M388.1,50.3h3.5v15.9h-3.5l-7.5-9.9v9.9H377V50.3h3.3l7.8,10.2
|
||||
C388.2,60.5,388.1,50.3,388.1,50.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M16.2,47.3H7.5v20.3h20.3v-8.7H16.2V47.3z"/>
|
||||
<path class="st0" d="M59.8,47.4v11.5H48.2v8.7h20.3V47.3L59.8,47.4L59.8,47.4z"/>
|
||||
<path class="st0" d="M7.5,27.1h8.8L16.2,27V15.5h11.6V6.8H7.5V27.1z"/>
|
||||
<path class="st0" d="M48.2,6.8v8.7h11.6v11.6h8.7V6.8C68.4,6.8,48.2,6.8,48.2,6.8z"/>
|
||||
<path class="st1" d="M47,27.1L35.4,15.5h12.7V6.8H27.8v8.7l11.6,11.6H47z"/>
|
||||
<path class="st1" d="M36.6,47.3H29l9.6,9.6l1.9,2H27.8v8.7h20.4v-8.8l-5.8-5.7L36.6,47.3z"/>
|
||||
<path class="st1" d="M59.8,27.1v12.6l-2-2l-9.6-9.6v7.7l5.7,5.7l5.8,5.8h8.8V27.1H59.8L59.8,27.1z"/>
|
||||
<path class="st1" d="M27.8,38.6L16.3,27.1H7.5v20.2h8.7V34.7l11.6,11.6V38.6z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.3 KiB |
Reference in New Issue
Block a user