From 69da593f3735ae875014d73c3105ecc73debbf88 Mon Sep 17 00:00:00 2001 From: Scott Seago Date: Thu, 15 Dec 2022 16:32:18 -0500 Subject: [PATCH 01/10] Minor updates to async action monitoring design: 1) clarification around Cancel() return values 2) updates to itemoperation json upload to account for progress 3) update to OperationProgress struct to avoid duplicate parameter 4) update new B/R phase name to WaitingForPluginOperationsPartiallyFailed for consistency Signed-off-by: Scott Seago --- design/general-progress-monitoring.md | 67 ++++++++++++++++----------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/design/general-progress-monitoring.md b/design/general-progress-monitoring.md index 43731c08d..cb627d822 100644 --- a/design/general-progress-monitoring.md +++ b/design/general-progress-monitoring.md @@ -145,7 +145,7 @@ allow for data movement to be handled in the "WaitingForPluginOperations" phase, The next phase is either "Completed", "WaitingForPluginOperations", "Failed" or "PartiallyFailed". Backups/restores which would have a final phase of "Completed" or "PartiallyFailed" may move to the -"WaitingForPluginOperations" or "WaitingForPluginOperationsPartialFailure" state. A backup/restore +"WaitingForPluginOperations" or "WaitingForPluginOperationsPartiallyFailed" state. A backup/restore which will be marked "Failed" will go directly to the "Failed" phase. Uploads may continue in the background for snapshots that were taken by a "Failed" backup/restore, but no progress will not be monitored or updated. If there are any operations in progress when a backup is moved to the "Failed" @@ -157,14 +157,14 @@ any uploads still in progress should be aborted. The "WaitingForPluginOperations" phase signifies that the main part of the backup/restore, including snapshotting has completed successfully and uploading and any other asynchronous BIA/RIA plugin operations are continuing. In the event of an error during this phase, the phase will change to -WaitingForPluginOperationsPartialFailure. On success, the phase changes to Completed. Backups +WaitingForPluginOperationsPartiallyFailed. On success, the phase changes to Completed. Backups cannot be restored from when they are in the WaitingForPluginOperations state. -### WaitingForPluginOperationsPartialFailure (new) -The "WaitingForPluginOperationsPartialFailure" phase signifies that the main part of the +### WaitingForPluginOperationsPartiallyFailed (new) +The "WaitingForPluginOperationsPartiallyFailed" phase signifies that the main part of the backup/restore, including snapshotting has completed, but there were partial failures either during the main part or during any async operations, including snapshot uploads. Backups cannot be -restored from when they are in the WaitingForPluginOperationsPartialFailure state. +restored from when they are in the WaitingForPluginOperationsPartiallyFailed state. ### Failed When a backup/restore has had fatal errors it is marked as "Failed" Backups in this state cannot be @@ -203,20 +203,20 @@ Velero will poll the plugins for status on the operations when the backup/restor "InProgress" phase and has no fatal errors. If any operations are not complete, the backup/restore will move to either WaitingForPluginOperations -or WaitingForPluginOperationsPartialFailure or Failed. +or WaitingForPluginOperationsPartiallyFailed or Failed. Post-snapshot and other operations may take a long time and Velero and its plugins may be restarted during this time. Once a backup/restore has moved into the WaitingForPluginOperations or -WaitingForPluginOperationsPartialFailure phase, another backup/restore may be started. +WaitingForPluginOperationsPartiallyFailed phase, another backup/restore may be started. -While in the WaitingForPluginOperations or WaitingForPluginOperationsPartialFailure phase, the +While in the WaitingForPluginOperations or WaitingForPluginOperationsPartiallyFailed phase, the snapshots and item actions will be periodically polled. When all of the snapshots and item actions have reported success, the backup/restore will move to the Completed or PartiallyFailed phase, depending on whether the backup/restore was in the WaitingForPluginOperations or -WaitingForPluginOperationsPartialFailure phase. +WaitingForPluginOperationsPartiallyFailed phase. The Backup resources will not be written to object storage until the backup has entered a final phase: -Completed, Failed or PartialFailure +Completed, Failed or PartiallyFailed ## Reconciliation of InProgress backups @@ -231,7 +231,7 @@ ignored. type OperationProgress struct { Completed bool // True when the operation has completed, either successfully or with a failure Err string // Set when the operation has failed - Completed, Total int64 // Quantity completed so far and the total quanity associated with the operaation in operationUnits + NCompleted, NTotal int64 // Quantity completed so far and the total quanity associated with the operaation in operationUnits // For data mover and volume snapshotter use cases, this would be in bytes // On successful completion, completed and total should be the same. OperationUnits string // Units represented by completed and total -- for data mover and item @@ -267,14 +267,17 @@ process/controller so we will modify BackupItemAction and RestoreItemAction to o operationID in addition to the modified item. Velero can attempt to cancel an operation by calling the Cancel API call on the BIA/RIA. The plugin -can then take any appropriate action as needed. Cancel will be called on backup deletion, and -possibly upon reaching timeouts, if timeout support is included in this feature. Cancel is not -intended to be used to delete/remove the results of completed actions. +can then take any appropriate action as needed. Cancel will be called for unfinished operations on +backup deletion, and possibly reaching timeout. Cancel is not intended to be used to delete/remove +the results of completed actions and will have no effect on a completed action. Cancel has no return +value apart from the standard Error return, but this should only be used for unexpected +failures. Under normal operations, Cancel will simply return a nil error (and nothing else), whether +or not the plugin is able to cancel the operation. -_AsyncOperationsNotSupportedError_ should only be returned (by either Cancel or by Progress) if the +_AsyncOperationsNotSupportedError_ should only be returned (by Progress) if the Backup/RestoreItemAction plugin should not be handling the item. If the Backup/RestoreItemAction plugin should handle the item but, for example, the item/snapshot ID cannot be found to report -progress, Progress or Cancel will return an InvalidOperationIDError error rather than a populated +progress, Progress will return an InvalidOperationIDError error rather than a populated OperationProgress struct. If the item action does not start an asynchronous operation, then operationID will be empty. @@ -368,9 +371,17 @@ of what a record for a datamover plugin might look like: ``` { "itemOperation": { - "plugin": "velero.io/datamover-backup", - "itemID": "", - "operationID": "" + "plugin": "velero.io/datamover-backup", + "itemID": "", + "operationID": "", + "completed": true, + "err": "", + "NCompleted": 12345, + "NTotal": 12345, + "OperationUnits": "byte", + "Description": "", + "Started": "2022-12-14T12:01:00Z", + "Updated": "2022-12-14T12:11:02Z" } } ``` @@ -424,8 +435,8 @@ will be completed and the backup will move to the appropriate terminal phase and If any of the snapshots or backup items are still being processed, the phase of the backup will be set to the appropriate phase (_WaitingForPluginOperations_ or -_WaitingForPluginOperationsPartialFailure_). In the event of any of the progress checks return an -error, the phase will move to _WaitingForPluginOperationsPartialFailure_. The backup will then be +_WaitingForPluginOperationsPartiallyFailed_). In the event of any of the progress checks return an +error, the phase will move to _WaitingForPluginOperationsPartiallyFailed_. The backup will then be requeued and will be rechecked again after some time has passed. ## Restore workflow changes @@ -440,16 +451,16 @@ completed and the restore will move to the appropriate terminal phase and the re complete. If any of the restore items are still being processed, the phase of the restore will be set to the -appropriate phase (_WaitingForPluginOperations_ or _WaitingForPluginOperationsPartialFailure_). In +appropriate phase (_WaitingForPluginOperations_ or _WaitingForPluginOperationsPartiallyFailed_). In the event of any of the progress checks return an error, the phase will move to -_WaitingForPluginOperationsPartialFailure_. The restore will then be requeued and will be rechecked +_WaitingForPluginOperationsPartiallyFailed_. The restore will then be requeued and will be rechecked again after some time has passed. ## Restart workflow On restart, the Velero server will scan all Backup/Restore resources. Any Backup/Restore resources which are in the _InProgress_ phase will be moved to the _Failed_ phase. Any Backup/Restore -resources in the _WaitingForPluginOperations_ or _WaitingForPluginOperationsPartialFailure_ phase +resources in the _WaitingForPluginOperations_ or _WaitingForPluginOperationsPartiallyFailed_ phase will be treated as if they have been requeued and progress checked and the backup/restore will be requeued or moved to a terminal phase as appropriate. @@ -459,9 +470,9 @@ Since this design is modifying a previously-approved design, there is some prepa the earlier upload progress monitoring design that may need modification as a result of these updates. Here is a list of some of these items: -1. Consts for the "Uploading" and "UploadingPartialFailure" phases have already been defined. These +1. Consts for the "Uploading" and "UploadingPartiallyFailed" phases have already been defined. These will need to be removed when the "WaitingForPluginOperations" and - "WaitingForPluginOperationsPartialFailure" phases are defined. + "WaitingForPluginOperationsPartiallyFailed" phases are defined. - https://github.com/vmware-tanzu/velero/pull/3805 1. Remove the ItemSnapshotter plugin APIs (and related code) since the revised design will reuse VolumeSnapshotter and BackupItemAction plugins. @@ -507,9 +518,9 @@ vSphere plug-in BackupItemAction Progress implementation (vSphere plug-in team) required custom cancellation actions, now would be a good time to perform them. For snapshot uploads that are already in proress, there's not really anything else to cancel. 2. Should we actually write the backup *before* moving to the WaitingForPluginOperations or - WaitingForPluginOperationsPartialFailure phase rather than waiting until all operations + WaitingForPluginOperationsPartiallyFailed phase rather than waiting until all operations have completed? The operations in question won't affect what gets written to object storage for the backup, and since we've already written the list of operations we're waiting for to object storage, writing the backup now would make the process resilient to Velero restart if - it happens during WaitingForPluginOperations or WaitingForPluginOperationsPartialFailure + it happens during WaitingForPluginOperations or WaitingForPluginOperationsPartiallyFailed From 3b903e678fe91c858c1f7d2726e9090d746278e1 Mon Sep 17 00:00:00 2001 From: Ming Date: Tue, 20 Dec 2022 02:40:29 +0000 Subject: [PATCH 02/10] [Cherry-Pick]Fix error with Restic backup empty volumes Signed-off-by: Ming --- changelogs/unreleased/5713-qiuming-best | 1 + pkg/uploader/provider/restic.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/5713-qiuming-best diff --git a/changelogs/unreleased/5713-qiuming-best b/changelogs/unreleased/5713-qiuming-best new file mode 100644 index 000000000..c63b47030 --- /dev/null +++ b/changelogs/unreleased/5713-qiuming-best @@ -0,0 +1 @@ +Fix error with Restic backup empty volumes diff --git a/pkg/uploader/provider/restic.go b/pkg/uploader/provider/restic.go index 00fe42057..70ae4823c 100644 --- a/pkg/uploader/provider/restic.go +++ b/pkg/uploader/provider/restic.go @@ -137,7 +137,7 @@ func (rp *resticProvider) RunBackup( summary, stderrBuf, err := restic.RunBackup(backupCmd, log, updater) if err != nil { - if strings.Contains(err.Error(), "snapshot is empty") { + if strings.Contains(stderrBuf, "snapshot is empty") { log.Debugf("Restic backup got empty dir with %s path", path) return "", true, nil } From 32eb8655cc05350e7a35699d67f677994e0c587a Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Wed, 21 Dec 2022 12:27:50 +0800 Subject: [PATCH 03/10] fix issue 5696 Signed-off-by: Lyndon-Li --- changelogs/unreleased/5715-Lyndon-Li | 1 + pkg/repository/manager.go | 10 ++++++++ pkg/repository/provider/provider.go | 5 ++++ pkg/repository/provider/restic.go | 4 ++++ pkg/repository/provider/unified_repo.go | 31 +++++++++++++++++++++++++ 5 files changed, 51 insertions(+) create mode 100644 changelogs/unreleased/5715-Lyndon-Li diff --git a/changelogs/unreleased/5715-Lyndon-Li b/changelogs/unreleased/5715-Lyndon-Li new file mode 100644 index 000000000..05d9c8db1 --- /dev/null +++ b/changelogs/unreleased/5715-Lyndon-Li @@ -0,0 +1 @@ +Fix issue 5696, check if the repo is still openable before running the prune and forget operation, if not, try to reconnect the repo \ No newline at end of file diff --git a/pkg/repository/manager.go b/pkg/repository/manager.go index fae6877dc..c8cabdd81 100644 --- a/pkg/repository/manager.go +++ b/pkg/repository/manager.go @@ -172,6 +172,11 @@ func (m *manager) PruneRepo(repo *velerov1api.BackupRepository) error { if err != nil { return errors.WithStack(err) } + + if err := prd.BoostRepoConnect(context.Background(), param); err != nil { + return errors.WithStack(err) + } + return prd.PruneRepo(context.Background(), param) } @@ -207,6 +212,11 @@ func (m *manager) Forget(ctx context.Context, snapshot SnapshotIdentifier) error if err != nil { return errors.WithStack(err) } + + if err := prd.BoostRepoConnect(context.Background(), param); err != nil { + return errors.WithStack(err) + } + return prd.Forget(context.Background(), snapshot.SnapshotID, param) } diff --git a/pkg/repository/provider/provider.go b/pkg/repository/provider/provider.go index 6579386d6..4b76830f5 100644 --- a/pkg/repository/provider/provider.go +++ b/pkg/repository/provider/provider.go @@ -43,6 +43,11 @@ type Provider interface { // is already initialized, or do nothing if the repository is already connected PrepareRepo(ctx context.Context, param RepoParam) error + // BoostRepoConnect is used to re-ensure the local connection to the repo, + // so that the followed operations could succeed in some environment reset + // scenarios, for example, pod restart + BoostRepoConnect(ctx context.Context, param RepoParam) error + // PruneRepo does a full prune/maintenance of the repository PruneRepo(ctx context.Context, param RepoParam) error diff --git a/pkg/repository/provider/restic.go b/pkg/repository/provider/restic.go index 65038f0f2..a77c950be 100644 --- a/pkg/repository/provider/restic.go +++ b/pkg/repository/provider/restic.go @@ -62,6 +62,10 @@ func (r *resticRepositoryProvider) PrepareRepo(ctx context.Context, param RepoPa return nil } +func (r *resticRepositoryProvider) BoostRepoConnect(ctx context.Context, param RepoParam) error { + return nil +} + func (r *resticRepositoryProvider) PruneRepo(ctx context.Context, param RepoParam) error { return r.svc.PruneRepo(param.BackupLocation, param.BackupRepo) } diff --git a/pkg/repository/provider/unified_repo.go b/pkg/repository/provider/unified_repo.go index 2ba1c7c0d..9161e0621 100644 --- a/pkg/repository/provider/unified_repo.go +++ b/pkg/repository/provider/unified_repo.go @@ -200,6 +200,37 @@ func (urp *unifiedRepoProvider) PrepareRepo(ctx context.Context, param RepoParam return nil } +func (urp *unifiedRepoProvider) BoostRepoConnect(ctx context.Context, param RepoParam) error { + log := urp.log.WithFields(logrus.Fields{ + "BSL name": param.BackupLocation.Name, + "repo name": param.BackupRepo.Name, + "repo UID": param.BackupRepo.UID, + }) + + log.Debug("Start to boost repo connect") + + repoOption, err := udmrepo.NewRepoOptions( + udmrepo.WithPassword(urp, param), + udmrepo.WithConfigFile(urp.workPath, string(param.BackupRepo.UID)), + udmrepo.WithDescription(repoConnectDesc), + ) + + if err != nil { + return errors.Wrap(err, "error to get repo options") + } + + bkRepo, err := urp.repoService.Open(ctx, *repoOption) + if err == nil { + if c := bkRepo.Close(ctx); c != nil { + log.WithError(c).Error("Failed to close repo") + } + + return nil + } + + return urp.ConnectToRepo(ctx, param) +} + func (urp *unifiedRepoProvider) PruneRepo(ctx context.Context, param RepoParam) error { log := urp.log.WithFields(logrus.Fields{ "BSL name": param.BackupLocation.Name, From 544df59f58fbbade440bc4bfee3d3cdc24b6a387 Mon Sep 17 00:00:00 2001 From: "haining.cao" Date: Wed, 21 Dec 2022 18:54:45 +0800 Subject: [PATCH 04/10] fix json serialize name Signed-off-by: haining.cao --- pkg/volume/snapshot.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/volume/snapshot.go b/pkg/volume/snapshot.go index eb115e8cb..747440114 100644 --- a/pkg/volume/snapshot.go +++ b/pkg/volume/snapshot.go @@ -37,7 +37,7 @@ type SnapshotSpec struct { Location string `json:"location"` // PersistentVolumeName is the Kubernetes name for the volume. - PersistentVolumeName string `json:persistentVolumeName` + PersistentVolumeName string `json:"persistentVolumeName"` // ProviderVolumeID is the provider's ID for the volume. ProviderVolumeID string `json:"providerVolumeID"` From 2abb176bd84da96686746e908a190ca46ed12a44 Mon Sep 17 00:00:00 2001 From: Scott Seago Date: Fri, 16 Dec 2022 17:22:02 -0500 Subject: [PATCH 05/10] backup/restore phases for async plugin operations Signed-off-by: Scott Seago --- changelogs/unreleased/5710-sseago | 4 ++++ config/crd/v1/bases/velero.io_backups.yaml | 2 ++ config/crd/v1/bases/velero.io_restores.yaml | 2 ++ config/crd/v1/crds/crds.go | 4 ++-- pkg/apis/velero/v1/backup.go | 24 ++++++++++++-------- pkg/apis/velero/v1/restore.go | 15 +++++++++++- site/content/docs/main/api-types/backup.md | 4 +++- site/content/docs/main/api-types/restore.md | 4 +++- site/content/docs/main/api-types/schedule.md | 3 ++- 9 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 changelogs/unreleased/5710-sseago diff --git a/changelogs/unreleased/5710-sseago b/changelogs/unreleased/5710-sseago new file mode 100644 index 000000000..3146c1185 --- /dev/null +++ b/changelogs/unreleased/5710-sseago @@ -0,0 +1,4 @@ +new backup and restore phases to support async plugin operations: +- WaitingForPluginOperations +- WaitingForPluginOperationsPartiallyFailed + diff --git a/config/crd/v1/bases/velero.io_backups.yaml b/config/crd/v1/bases/velero.io_backups.yaml index 773940789..515a7d051 100644 --- a/config/crd/v1/bases/velero.io_backups.yaml +++ b/config/crd/v1/bases/velero.io_backups.yaml @@ -455,6 +455,8 @@ spec: - New - FailedValidation - InProgress + - WaitingForPluginOperations + - WaitingForPluginOperationsPartiallyFailed - Completed - PartiallyFailed - Failed diff --git a/config/crd/v1/bases/velero.io_restores.yaml b/config/crd/v1/bases/velero.io_restores.yaml index 181a4e5fa..20fe68d66 100644 --- a/config/crd/v1/bases/velero.io_restores.yaml +++ b/config/crd/v1/bases/velero.io_restores.yaml @@ -412,6 +412,8 @@ spec: - New - FailedValidation - InProgress + - WaitingForPluginOperations + - WaitingForPluginOperationsPartiallyFailed - Completed - PartiallyFailed - Failed diff --git a/config/crd/v1/crds/crds.go b/config/crd/v1/crds/crds.go index c83d1071f..65a337806 100644 --- a/config/crd/v1/crds/crds.go +++ b/config/crd/v1/crds/crds.go @@ -30,13 +30,13 @@ import ( var rawCRDs = [][]byte{ []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4WAo\xdc6\x13\xbd\xebW\f\xf2\x1dr\xf9\xa4M\xd0C\v\xddR\xb7\x05\x82&\x86a\a\xbe\x14=P\xe4\xec.c\x8ad\xc9\xe1\xa6ۢ\xff\xbd\x18R\xf2j%\xd9\x1b\a\xa8n\"\x87of\xde\xcc\x1bQU]ו\xf0\xfa\x1eC\xd4ζ \xbc\xc6?\t-\xbf\xc5\xe6\xe1\x87\xd8h\xb79\xbc\xad\x1e\xb4U-\\\xa5H\xae\xbf\xc5\xe8R\x90\xf8\x13n\xb5դ\x9d\xadz$\xa1\x04\x89\xb6\x02\x10\xd6:\x12\xbc\x1c\xf9\x15@:K\xc1\x19\x83\xa1ޡm\x1eR\x87]\xd2Fa\xc8\xe0\xa3\xebÛ\xe6\xfb\xe6M\x05 \x03\xe6\xe3\x9ft\x8f\x91D\xef[\xb0ɘ\n\xc0\x8a\x1e[\xe8\x84|H>\xa0wQ\x93\v\x1acs@\x83\xc15\xdaUѣd\xb7\xbb\xe0\x92o\xe1\xb4QN\x0f!\x95t~\xcc@\xb7#\xd01o\x19\x1d\xe9\xd7\xd5\xed\x0f:R6\xf1&\x05a\xd6\x02\xc9\xdbQ\xdb]2\",\f\xd8A\x94\xcec\v\xd7\x1c\x8b\x17\x12U\x050P\x90c\xabA(\x95I\x15\xe6&hK\x18\xae\x9cI\xfdHf\r\x9f\xa3\xb37\x82\xf6-4#\xed͂\xb2l;\x12\xf6n\x87\xc3;\x1dٹ\x12\x84K0f\xae9\xc5\xfa\xe9\xe8\xf1\f\xe5D\x04L\xf6\nb\xa4\xa0\xed\xae:\x19\x1f\xde\x16*\xe4\x1e{\xd1\x0e\xb6Σ}w\xf3\xfe\xfe\xbb\xbb\xb3e\x00\x1f\x9c\xc7@z,Oy&}9Y\x05P\x18eОr\u05fcf\xc0b\x05\x8a\x1b\x12#\xd0\x1eGNQ\r1\x80\xdb\x02\xedu\x84\x80>`D[Z\xf4\f\x18\xd8HXp\xddg\x94\xd4\xc0\x1d\x06\x86\x81\xb8w\xc9(\xee\xe3\x03\x06\x82\x80\xd2\xed\xac\xfe\xeb\x11;\x02\xb9\xec\xd4\b¡GNO\xae\xa1\x15\x06\x0e\xc2$\xfc?\b\xab\xa0\x17G\b\xc8^ \xd9\t^6\x89\r|t\x01AۭkaO\xe4c\xbb\xd9\xec4\x8dz\x94\xae\xef\x93\xd5t\xdcdi\xe9.\x91\vq\xa3\xf0\x80f\x13\xf5\xae\x16A\xee5\xa1\xa4\x14p#\xbc\xaes\xe86k\xb2\xe9\xd5\xff\u00a0\xe0\xf8\xfa,\xd6E-˓\xc5\xf2L\x05X-\xa0#\x88\xe1h\xc9\xe2D4/1;\xb7?\xdf}\x82\xd1u.Ɯ\xfd\xcc\xfb\xe9`<\x95\x80\t\xd3v\x8b\xa1\x14q\x1b\\\x9f1\xd1*ﴥ\xfc\"\x8dF;\xa7?\xa6\xae\xd7\xc4u\xff#a$\xaeU\x03WyHA\x87\x90<\xabA5\xf0\xde\u0095\xe8\xd1\\\x89\x88\xffy\x01\x98\xe9X3\xb1_W\x82\xe9|\x9d\x1b\x17\xd6&\x1b\xe3\b|\xa2^\xf3\xb1v\xe7Qr\xf9\x98A>\xaa\xb7Zfm\xc0\xd6\x05\x10\v\xfb\xe6\fz]\xba\xfc\x94\xe1wG.\x88\x1d~p\x05sn\xb4\x1a\xdb\xec\xcc\x18\x1cO\x96\"c\\7\\`\x03\xd0^\xd0D\xbf$\xb4}\x1c\x03\xab\xf90\x92-S\bhi\x80\xc97\x88o\x1e\x99FD\x9a\x8c\v\xbe\xcd]\xe8\x80\x0f\xcb\x13c`\f\x06\xc4\v\xd3\xf9\xf2E̿\xba\xb9hk\x93e\xebB/\xa8\\\x17k\x06ZX\xf0\xb5\\t\x06[\xa0\x90\x96\xdb\xcf\xcdQ\x8cQ\xec.e\xf7\xb1X\x95\xcb\xc5p\x04D\xe7\x12=A=\xed\x97Q\xc0\x85r\\\x88\xd4\xefE\xbc\x14\xe7\r۬5\xc4\xec{\xf5\\\bO\xcd\xcck\xfc\xb2\xb2z\x8bB-u\\õ\xa3\xf5\xad'3\\U\xc5b1\xf2=LM\xea\x1c\x8b\x90\xa7+\xa9{\xbcW\xb6\xf0\xf7?\xd5IXBJ\xf4\x84\xeaz\xfe\a6\xcc\xf7\xf1\x87*\xbfJg\xcb\x0fPl\xe1\xb7߫\xe2\n\xd5\xfd\xf8\x93ċ\xff\x06\x00\x00\xff\xff\xc8p\x98۸\x0e\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=Msܸrw\xfd\x8a.\xe5\xe0\xf7\xaa4\xa3u吔n^\xd9[Q\xedƫ\xb2\xf4\x94C\x92\x03\x86\xec\x99\xc1\n\x04\xf8\x00p\xe4I*\xff=\xd5\x00\xf8\r\x92\x98\xb1\xf4\xde\xee+\xe3b\x8b\x04\x1a@w\xa3\xbf\xd0\xec\xb9X\xadV\x17\xac\xe4O\xa8\rW\xf2\x06X\xc9\xf1\xabEI\u007f\x99\xf5\xf3\xbf\x9a5Wׇ\xf7\x17\xcf\\\xe67p[\x19\xab\x8a/hT\xa53\xfc\x88[.\xb9\xe5J^\x14hY\xce,\xbb\xb9\x00`R*\xcb象?\x012%\xadVB\xa0^\xedP\xae\x9f\xab\rn*.r\xd4\x0ex=\xf5\xe1\x87\xf5\xbf\xac\u007f\xb8\x00\xc84\xbaᏼ@cYQހ\xac\x84\xb8\x00\x90\xac\xc0\x1bذ\xec\xb9*\xcd\xfa\x80\x02\xb5ZsuaJ\xcch\xae\x9dVUy\x03\xed\v?$\xac\xc3\xef\xe1G7\xda=\x10\xdc؟;\x0f\u007f\xe1ƺ\x17\xa5\xa84\x13\xcdL\xee\x99\xe1rW\t\xa6\xeb\xa7\x17\x00&S%\xde\xc0g\x9a\xa2d\x19\xe6\x17\x00a;n\xcaUX\xf0\u1f47\x90\xed\xb1`~-\x00\xaaD\xf9\xe1\xfe\xee\xe9\x9f\x1fz\x8f\x01r4\x99\xe6\xa5uH\xf1\v\x03n\x80\xc1\x93\xdb\x16\xe8\x80~\xb0{fAc\xa9Ѡ\xb4\x06\xec\x1e!c\xa5\xad4\x82\xda\xc2\xcf\xd5\x06\xb5D\x8b\xa6\x01\r\x90\x89\xcaX\xd4`,\xb3\b\xcc\x02\x83Rqi\x81K\xb0\xbc@\xf8Ӈ\xfb;P\x9b\xdf0\xb3\x06\x98́\x19\xa32\xce,\xe6pP\xa2*Џ\xfd\xf3\xba\x81ZjU\xa2\xb6\xbcƳo\x1d\xae\xea<\x1dl\xef\x1da\xc0\xf7\x82\x9c\xd8\t\xfd6\x02\x161\x0fH\xa3\xfd\xd8=7\xedv\x1d\x87\xf4\x00\x03ub2,~\r\x0f\xa8\t\f\x98\xbd\xaaDN\\x@M\b\xcb\xd4N\xf2\xffi`\x1b\xb0\xcaM*\x98\xc5\xc0\x00m\xe3Ң\x96L\xc0\x81\x89\n\xaf\x1cJ\nv\x04\x8d4\vT\xb2\x03\xcfu1k\xf8w\xa5\x11\xb8ܪ\x1b\xd8[[\x9a\x9b\xeb\xeb\x1d\xb7\xf5i\xcaTQT\x92\xdb\xe3\xb5;\x18|SY\xa5\xcdu\x8e\a\x14׆\xefVLg{n1#B^\xb3\x92\xaf\xdcҥ;Q\xeb\"\xff\xa7\x9a\x01̻\xdeZ푘\xd1X\xcd\xe5\xae\xf3\xc2q\xfd\f\x05\xe8\x00x\xfe\xf2C\xfd.ZD\xd3#\xc2ΗO\x0f\x8f]\xde\xe3f\x88}\x87\xf7\x0eC\xb6$ \x84q\xb9E퉸ժp0Q\xe6\x9e\xfb\x1c\xeb\n\x8er\x88~Sm\nn\x89\xee\u007f\xad\xd0\x10\x93\xab5\xdc:\x11\x03\x1b\x84\xaả3\xd7p'\xe1\x96\x15(n\x99\xc17'\x00aڬ\b\xb1i$\xe8J\xc7ag\x8f\xb5\u038bZ\x96M\xd0\xcb\v\x84\x87\x12\xb3ށ\xa1Q|\xcb3w,`\xabt+/\xbc\xb8Z\xf7@Ə,\xb5\xcc\xf0\a\xc9J\xb3W\x96䯪\xec\xb0\xc7`A\xb7\x0fw\x83\x01\xf5b\xc2ҜX\xa9\f\xe6t\xce^\x18\xb7\xb4\xbc\x11L @\xf0\xe4$L\r\xcfI\x9aʀ\xad\xb4t\xa7\xf4\v\xb2\xfc\xf8\xa8\xfeb\x10\xf2\xca1k\xad+\xae`\x83[\xa51\x02W#\x8d\xa7Ψ5!Ƹ%\xa9ʮ\xe1q\x8f\x84FV\t\x1b\xf8\x9e\x1bx\xff\x03\x14\\V\x16\xd7#h\x13\x04\xf6Hq`\xfc\x0ẹ\xfa\xc9xR-\xa0\xef\xe3İ\x0e\x12_\xf6h\xf7\xa8\xa1T\xb5\b\x8e\xecr\xcb\x05\x829\x1a\x8bE\xa0x-\xf86\x01\xfb\x8e)\x84\b \fl\x8e\xf5\x9a\xc7\xfb$}\xcb6\x02o\xc0\xeaj<\x9dG\xc3F)\x81l(\x84\x87x\xf8\x82\xc6\xf2l\x01\v\x97C4\xf8Q\x11$\xe8\xf0\xc2\xed-\x82\x87M\xcbk\x96=#\xb0\x1a\x1b\xa4\x1c\x84\xe8 \xb1\x87\x01\xf8/\t\x1fIre$Oƫ\x85 \xb98\n'-\xa5\x02\xa1\xe4\x0e\xb5\x9f\x8d\xb4\xc2\v\x17\x82\xa6\xd7X\xa8\x03\xe6@\x02C\xa3 \xc9\aۊd\xc9\x18\xcf\x00\xc4˓<\xc0\xa5\xb1\xc8\xf2\xf5\xe5k\x12\b\xbff\xa2\xca1o̖\x91\f\x18\x10\xe7\xd3h\x803\xe9\x18\x97\xa45Ȉ\"\xe4\xca\xf6-\x19&\x91\xad2\x8d@r\x9bK\x0f\xcf\xd9\x1c{\x8c\n(j\xdcb\x11Y\xdb\xec)L\xc4\fӚ\x1d'\xf0R\x9b\xb7\xa9hi\xfa\a-*x\xe6\xec\xafFW:\xccxk\x8dE%\xd4\xef\x18){\xa5\x9e\x97\x10\xf1oԧ\xd5\xfb\x909/\x016\xb8g\a\xaet\xd8z+\x8d\xf0+f\x95\xc5\xd8\xe9e\x16r\xbeݢ&8\xe5\x9e\x194\xde\xf4\x9bFȴ*\x03'\xfc'\x899\xdaGKH\xe2T\xb7\U000e99538\x1a\x9e\xab\xba\xd1BI۸\xf3\x9b\xf3\x03\xcf+&\xdcQf2\xf3\xfbaͺb\"a\x86ȣ5{AQ\xaf\x9c(\xd13\r\x94DP\x1a\n\xb2\x87\xc6]\x87\x16\\ۦ\xb6\xbda$\xed\x94gQ]\t4a*\xaf^Z\x19p5\t\xba\xa1\x88\xb7\xa5\x05۠\x00\x83\x023\xabt\x1c\x1dKD\xf6-E\xaeM`1\"\xe1\xfa6L\xbb\xb1\x19\x90\xe0\f\x9c=\xcf\xf6^Y\x10\a98\x90+4\ue533\xb2\x14ǩM\xc2\x12\xe5\xc3$s\a\xbdm\vG~\b/v\xf8ۖ \x1b۶ %\xfb\x98m\xd8\x01\xac\x9a\xdd\xf6?&bk\xb1\u007f\x06\xd3ލ\x86\xbe.\xd3\x12J9\xb9\xb3w[\xc0\xa2\xb4\xc7+\xe0\xb6~\xba\x04\x91L\xadv\xfe?0aN\xe7\xf8\xbb\xe1\xc8W\xe5\xf8Y\xaa,A$\xaa4\xd3\xff\x01\x89\xe2\x94\xc5C\xd0\x15\xc9\x04\xf9\xa5;\xea\n\xf8\xb6!H~E~\x93E=\xa0\xcc7\x9d\x97\xd7@F\x8a\xbe\xa3V0\x9b\xed?}%\xcb˴\x01\xd7D\xbc\f\a{\xfb\xb5\xb6\xe7\xfb\x8ay\x01.\xb8\x00\r\xd7X\xf8\xc0ϣ\xc3f\xfb\xc4YT\x1f>\u007f\xc4|\x0e=\x90\xc6y\xa3\x8d|\x18,\xb6;u0\xcaS\xb7\x11L\x9fƿ\xf1!\xbd+`\xf0\x8cGo\xb10\tD\x1cF\x13Mx:c\xe4\xb8آc\xb2g<:0!X\xb88:\x95\x15|{\xc6cJ\xb7\x01\x02iM܄ (a\x92\x1e8D\xb8\xd8R:\xf2\xc0\x05~kY\xb4\xbc9H\x17$u\xabq\u007f\xc66\x1b\xb2u\x82掰\xef\x8c'\x11\x9d\x82=/\x137Jj\x0e\f\xba\xd3R\x87~\x9f\x98\xe0y3\x91\xe7\xfb;9m\r\xf7\xdbge\xef\xe4\x15|\xfa\xcaM\x88\xbe\u007fTh>+랼\t:\xfd\xc2\xcf@\xa6\x1f莗\xf4b\x9b\xf0Ѝ!'0\xb7ow\xde\xc3k\xc8\xc3\r\xdcI\xf2[\x02>܍\x80\x9fn^?\xf4[Q\x19\x17$\x96J\xae\x9c\xaa\\\xc7f\xf2\xc8N\x04\xa9t\x8f\"\xe3\xa55\x93\xfa\t\x13\xc1>\x92&\xf1\xe3\xfd\x1d\x87`\x19\xe6u\x8c\xd3E\xe6\x99\xc5\x1dϠ@\xbd\x9bS\x1c\xddV\x92|O[B\xa2\xd4\xf5\xedD\x0eKS\xedu\v\xa2;\x1a\x82\xeb\xb7\x15\x9d܄^5\xb1\x17\xbbN\x04䧻.\xefȩXg\u007f,b\x97幻Ke\xe2\xfe\x04\x89\u007f\x02-ƺ\xdf/\xcckȂ\xb9\x10\xe9\xff\x92\x9as\f\xfd\u007fP2\xae\x13\xce\xf0\aw5*\xb076D\xb1\xba\xd3\xd0\f\xdc\x00\xd1\xf7\xc0\xc4\xf8\xaa'\xb29E\xb2\x05\x85W\xe4j;\xb2X\xae\xe0e\xaf\x8cש.4\xbb\b\x92\x1b\xb8|\xc6\xe3\xe5\xd5H\x0e\\\xde\xc9K\xaf\xe0O\x167\x8d\xb5\xa0\xa48¥\x1b{\xf9-FP\"'&usWЩ\xa62\xf9\x92\xb5%@\x03\x9b{W2s\xe7V\x9dć\xa52\x91ۤ\x89\xa5\xdc+c}d\xb1g\x96\x9e\x12\xc5\x02\xcfC!z\x05l\xebo\xbe\x95\xae\xef4I\xec\r\x02\xaeD53/a\x89\x8cMD\xcc\x03%\xc7\xea\xb2=\xc1^\x9e^\xfa\x8bN7\t˜q\xb1\b\xb7\xd4*Cc\xe6Y$AZ/\x04\t\x9b\x00!\xf3\x0e\x8c\xbf0\x9c\x0fJ\xd6-\xdd %$\x9dh\xca\u007f\xfaډ^\xd2᧿\x97\x98\xef\xd4u\x81;\xb3E\xc1\x867\xe3IK\xbc\xf5#\xebc\x12\x00y\xd7@\xef*w\xd4\xd3-\xc8\xc0H\xbf\a5]py\xe7&\x80\xf7\xaf\xae\xd6\x1b!\x89\xe7\x18\xee\xb7\xf5\xd8\x16\xe9\xcd\x03wzS-\"\xe5\"\xf7\x1a{\x94\x1bǹ\xc9PL\x04)\x95\xed\x86\x13\bn\xa9\xf2w\x06\xb6\\\x1b\xdb]h*ST\v\xa7\xbfm\xa7zN\xf2\x93\xd6g9N\xbf\xfa\x91\x9d@\xd6^\xbd\xd4\xf9\x05\x93W\xb1\xb1\xe6.\x85\x10\xf8\x16\xb8\x05\x94\x99\xaa\xa4\v\xbf\xd0QwSx\x12x\x01\x9d\x8c\xb24\x01A\reU\xa4!`帎\xcb\xd98M\xb7\xfbO\x8c\x8b\xb7 \x9b\x9dJÈ\xb5\x1e\xd9\xea|\x8cn\xa2H\xc1\xbe\xf2\xa2*\x80\x15\x84\xfaT\xb7g\xeb\xb38z\x14or9\x1c\\\xa7F\xac\xa2CU\n\xb4\xa9'\xd2gm\xd011<\xc7F1\a.P\x12\x18l\x19\x17\x13\x97\xe7\xe3v\x12nO\xf15\x82\xb0x='\"m\xf2\x95CEB 6\xd1X\x9c\x97֥N7\x15\xef5\xa6\x99gKA\xe9\xda<+5'^R\xafm\xa1\x05\x16c\xf2\xf8\xddD\x1b\xb5\xef&\xdaB\xfbn\xa2M\xb6\xef&\xdar\xfbn\xa2\x85\xf6\xddD\xab\xdbw\x13\xed\xbb\x896\xd7mNZ/\xad\xc8\u007fq2\xf1rq\x15\t\xd7\xd3sK\x9c\x81\x1f\xb2)n\xfd\xd7'\xa9\x19\x96w\xf1Q\x91\xac\xe0\xf0Y\xcb\xca}\x91\x13\xe3\x806\xe9\xa2U%M\xca%\x1d\x90\x9a\xbd}\x02\xfdB\x12\xe67dߦ$\xfc,\xa5\xf9\xf4\xf3L\x9b4\x9b:\xd1TՓD\xf0P\u007f\xd9Cfo7\x87\xa4\x9f\xaf\xe3\xec\xdcz\xa5\u007f\xf7\x1cԄT\x9c\x85\x04\x9c\xf9\xc4\xdc9|\r\\\x8f>\xc2t/a\xf4w\x83\xaf\x85,\x99\xe9ܘp\x13\x84\x96\x1dޯ\xfbo\xac\n\x992\xf0\xc2\xed>\xb2\x95\x97=Jw\x87%wݴך\xdf\xc2'VC<\x82\xd2 \xb9p\xe8\x9c\xe1\xd6\x1ez\xe1\xd7һp'\x9f\xcby\xf7#-\x97\xe6\xec\f\x9a~\x86̄\x88>\xf5\xca(=Q8=Gf>\xa9\xe5\x94̘a\xde\xcb$\xd0\xe5|\x98\x14\xcfq!\xf7匌\x97\xc4l\xc7o\xbe\x18K\xc9i9+\x93e1!01\u007f\xa5\x9f\x992\x0f\U00084b15$\xe4,g\xa8\x9c\x9c\x97\x12\xf2@f\xf7\x91\x9c\x8d\x12\xc93\x99\x05<\x99\x832\x97]\xb2\x10\x95\x1ag\x9e\xa4\xe7\x94̂v\xf9&˙$\xaf\x97/\xfa\x1a6\xf0\xb4\xa8Y\xcc\x06Y\xb4\x91\xe7\u05f7\x98\xefqJ\x96\xc7\"\xc6\xce\xcc\xe8h26&\xe6=5\x8f\xa3\x9f\xa71\x014%{c\";c\x02\xe2l\xceFjN\xc6\x04\xec\x05\xb5;\xcb%3/\xe3\x1f\x02â~\x13\u007f+\x8e:wcJ\xf7\xcc\xc5%\v\xfd\xd7Aw\xa2em5͛\x9f1˓\xdb\xfd\xe9\xe6gQ\t\xcbK\xe1\xc2\xf9\a\x9eG\x9dF\xbb\xc7c\xf3Y\xe7o\xca}\xe6\xb49:H\xbf~i\xd8s=0\xa2\x99\x81\x17\x14\x02X\x8c\xb9F;\xcf\xfc\xb7\xec\x99Z!\xc9|:p\xe1\x83\xd5\xf0\xc9\xfb\x95\xe7`\xf7%W,\xe2i\xf7X\x10\x94\xfa\xcb\xd7\x13\u070fy\x03\xd1۲\xee\xd9_+\xd4GP\aԭŰ\xf0\x1d\x81?h\xa6\x12m\xe2V\x90\x1f\xbe\x82\xc2\xc0pn\x0f\x1c|\x90^\x85E\xc1\x0e\xd6\xe8\xe0Й\x17\r\xadI\xbc\x91\x1f0\xd15\x1e\xf8P\xcd\xe8\xc8\xfb%\xdb35\t\xffm]\x87ӝ\x87E\xb5\xfd&\x0e\xc4\xf9.\xc4\f\xc8Ԥ\xfa\xb4\v\xa8\xc5$\xfa\xb7r%\x96\x9c\x89d+*-I\xfe-\x92\xe3OH\x8a?\xc1\xa98ͭHFSJ\xf2\xfb\x9b8\x17o\xe8^\xbc\x85\x83q\x9e\x8b\xb1\x00r\x90Ԟ\x92\xae\x9et\xb9\x9a|\xbf\x90r9\xba|\x050\x9f\x86\x9e\x90~\x9ep9\xb0\xb4҄4\xf3\xd3\xd2\xcb\x13p\xf8F\xce\xc7\x1b\xb9\x1foြ\xad\v\xb2\xe8\x84,r\xce\xec볣\xcbJ\xe7\xa8g\x83\xf1\xa9\xac6\xcbd\x03\u007f\xa1?\xe7\xe0\x8bں\xc2\v\xf5Ꙧ\xb1\x90r\xf3\xf5g\x06?s\x99{z\x10Su\xf4x\uf1a05,\xe2\t\x02\xad\xd5\x16*`\xf9k\x05\x83%Ӯl\xda\xe6\xe8\xaf&\xcd\x1a>\xb1l?\x80\xbe\x8f\xfa\t[\xa5\vfᲹ\x93\xb9\xf6\xc0\xe9\xef\xcb5\xc0O\xaa\xb9\xf4\xeaVT0\xbc(ő\xfc\x80\b\xcc\xcb.\x88\xf3\x18\"\xcaL&\x94k\n\xf5k\x16|\xbf\x87~\xef\xc8e^]\xba'\x13\xaa\xca\x1b\xe8\x13\xc4d\xf2\b\xf7O\xceFqeC\xb2\xb6\x84J\xb0Bj\x1fpXa\xe5\xc7\u05ff\xdc3Vi\xb6\xc3_\x94\xafõ\x84\x89~\xef^\x11\xb6 ;\xea\xcb\xf6\xfa[\x8c\x98N\r\x15\xc1\x06\xc0\xda\x1c\x9aQ5(ZeL\xa8̜Gk\xc5\xc2f\x1e\x1f\u007f\xf1\x1b\xb0\xbc\xc0\xf5\xc7\xca_\xa4\xaeJ\xa6\r\x126\xeb\x8d\xf9A\x1b\xfa\xef^\xbd\xc4b\x1d*\xec\xf9\xc7\xe1\xba5\xba<\x1dw_{\xd2\xea\x0f\xbd\xaab5\x8a\x96\x18\xf5)>\xaa\xe3\xa8u\x88\xe4\xcf|\xd4A\x9f\x82\xd3)\xac\xe8B\x18\xee;\x9b\xd7-\xfb3%ŧJϹrk\xcb\xc5\xe7|U\xb6Pj2d{U\xda\xd5\xec\t\x15\xdb\\\x8d\x9b\xf3\xea\xcf\xf9\xe4\x94^\xf9\xcfy:ݎG\xb8\"\x8f:\xefԟkʀ\xbd0\xd3$\xc0D\x15k\vΏt\x96-A\xc3\x1c\xf0\x80\x12\x94t\xf9.\xae\x1a\x8e/D:\x1c\x13\x81څ\x12\x12j\xaaR(\x96\xd7'\xbc\xd6a\xa1x壓_\xfa\x80\xfa\x9d\x99\x81\xd9\x14v\x8b a,0\xbdR\xb9\x81\x9cY\\E\x81&ɾ(\xb3e\x86\xf7\x19\xdd|\xb0\x96\xfc\x84\x98\xed<, 85\xb2\xd6\xc7VY&@V\xc5\xc6+xVw\x88\xd1oTFЄ\f\xa8\x99\xe3\xe57ƥ\xc5\xdd(\xc68\xde\xd9m\xcd?'\xef\xac\x199\xb53Se\x19\x1a\xb3\xad\x84\x88\x99\xfa\r\xe7\xbe\xfe6]n\xdfb\xcd3\xd7ɋ@\x97\x18X\x97\xd5\xf3\x99\x81\x05\x1a\xc3vu\xb1\xb3\x17\xd2@;\x94\xe8̠X\xfc\xd1;\x8am&Y\xbfԗ\x8fh\xb1\xccV,LPg\x02tz\xbd\x8b\xd9\x05B\xed|AD^\x97\x83\xadU\xf3\x898\xf9Zr\x9d\xa2\xca?5\x1d\t7.\x18\xed\bі\xefE\xc1w\x9c\xf4 \x11i\xc7\xf4\x86\xedp\x95)!Х\x9d\x8f\xd7\xf5\x96\x875\xe4\xeb}Af\x16\xb7\xf6S\xb7o\x88|xj\xfbJ\x19̗[t\xd5\\-\xd7ؖG\x1e-H\xb9\x89OR\xdd\x1e\v\xd1B\xc2\xe3\x95v\xfb\xd6\a,\xc8\xd5`O\x87\xba\xc2W\xc1\x18\x8c{\xb7\x05\xfbM\xe9+(\xb8\xa4\u007f\xc8\xfaw\xa1\x89z\xf0I\xebw5\xec\x16\xd6}O}\x9a\xb4\xe9\x8e\"\xc5\xfa@L\x99\xaa\xf1T\xd9\x15|Ʊe\xe5\xb3_1w\xc1\xb8X\xf5d\xear'\xef\xb5ڑ\u007f\x1cy\xd9\b\xafȻ{\xa6-gB\x1c\xfd$\x93\xb3G^|DR\\\x93\xd6K\x1c\xada\x95K\x98\r\xddZW\x9cK\xcf\t.ou\xa3*\xdb\x13%\xad(\x8a_\x038`k\xf8\xac,\xd6\x11^އI\xc2\x17\x8d]\xe1v\xab\xb4\xf5\x9e\xffj\x05|\x1b\xac\xa1\x98\xabǸp7T\xbe\x981p\xdb^ҷ\xdc\xeb\x1c\x1d\xed\x0e\xa1\xab\xf8T\xb0\xa3\xcfadYF\xc66^\x1b\xcbDD\xbe}SN\x943;\x89\xfb0\xffK\xc4\x0e\x1b!\xfc\xaeۿ\xf9\x90\xbc\xd1n\x0e\x9cǜ\xcb1\xf7\xb2=\xaa\xe9\xc0e\x1e\xa3\x84\x17ͭ%yڽ\xc2\x03K\x12T\b0$S&\xca\x06\xceIv\xf7\x9et\xef\xddtH\xb1\xef\xdf4\x9d\xa7Tw\u061c\"\xb2l\x1c\n&\xb6\xe5\xbfa\xe2\xa6\x1eK\xa4\xcc\xf6L\ue229\xb4\xaav\xfb\x9a/'4\xe3TD\xae\xa2EA)\xaa\x1d\xb1z\xb8>\xb1\x95\x96\x9d\x90L\xb8P\xc9;\xcbe\xd9\xf3\xe4JC\x88\xb8.\xa8\u007f\x1d\n\x01\xae\xb6Z\x15\xab@\vw\xebq\x15\xc2$\x9a+\xb2\xffɑ\x9f\x00\xdaV\xdcrlP\x96(\x81\x99\xb0\x9e\x84\x0f\xac\xe6\xc9:\x17\xa7\xb0L\xdbT\xaf\xe2\xa1\xd7y\xc1\xa1p\x90\xe3\xeb}\ba \xff\xa1\xd9\xed\xf0\xa7\r\xae\xc0pY\xd7\xf2\xf7A&\xcf\n\x86\xfc\f\x8d\xceW\x8f^h\x8d<\x84\x9e?\xd0_\xfe\xdf\xd6\x1584\x1a\xe6S\x8aM\xf94\xe8>\xc8\xd6u%\xab\x9b.\xc1\x0e\x8c\xe0\xe3O|\xeb\xef\xd82Z\xf5\x9f\xff\xeeY\xb8\x87$\x9b\xe5ݬ\xb9\xe2,\x91\xc6\xeeX(P}/\x90\xec\b\x83ط\x84ޝd\xf2\x1e\xces\xe2^Ӄ\xab\u007ff\xe2u\xfc\x9a\xc3y\xbeۛ9n\xaf\xbb\xbb\x17\xe6J\xdb/\x9d\xb1\xff\b\xdd\"\x9e[\x80\x10\xf1\xdd\"\xdbh\xbc\xb9E߭\xe3\xba\xd5k\x9c\xa8~=p\xe7^\xc9y\x8b\xea\x81\xd1C'@\xf3\xce\xd9\x0e3\x85'm@\x8ce\x19\x12\xbb~\x1e\xfe\x9c̥\xaf\xcd^\xffb\x8c\xfb3Sҫ[s\x03\xff\xf9\xdf\x17\x10\"\xaeO\xf5O\xc3\xd0\xc3\xff\x0f\x00\x00\xff\xff\xe8ٕ\xadzg\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=Ms\xdc:rw\xfd\x8a.\xe5\xe0\xdd*\xcd\xe8\xb9rHJ7?Y\xae\xa8ދ\xad\xb2\xb4\xdaC\x92\x03\x86\xec\x99\xc1\x13\bp\x01p\xe4I*\xff=\xd5\x00\xf8\r\x92\x18Y\xda}/e\\l\x91@\x03\xe8n\xf4\x17\x9a=g\xab\xd5ꌕ\xfc\x11\xb5\xe1J^\x01+9~\xb3(\xe9/\xb3~\xfaW\xb3\xe6\xea\xf2\xf0\xfe\xec\x89\xcb\xfc\n\xae+cU\xf1\x15\x8d\xaat\x86\x1fq\xcb%\xb7\\ɳ\x02-˙eWg\x00LJe\x19=6\xf4'@\xa6\xa4\xd5J\bԫ\x1d\xca\xf5S\xb5\xc1M\xc5E\x8e\xda\x01\xaf\xa7>\xfc\xb4\xfe\x97\xf5Og\x00\x99F7\xfc\x81\x17h,+\xca+\x90\x95\x10g\x00\x92\x15x\x05\x1b\x96=U\xa5Y\x1fP\xa0Vk\xae\xceL\x89\x19͵Ӫ*\xaf\xa0}ᇄu\xf8=\xfc\xecF\xbb\a\x82\x1b\xfbK\xe7\xe1\xaf\xdcX\xf7\xa2\x14\x95f\xa2\x99\xc9=3\\\xee*\xc1t\xfd\xf4\f\xc0d\xaa\xc4+\xf8LS\x94,\xc3\xfc\f l\xc7M\xb9\n\v>\xbc\xf7\x10\xb2=\x16̯\x05@\x95(?\xdc\xdd>\xfe\xf3}\xef1@\x8e&Ӽ\xb4\x0e)~a\xc0\r0xt\xdb\x02\x1d\xd0\x0fv\xcf,h,5\x1a\x94ր\xdd#d\xac\xb4\x95FP[\xf8\xa5ڠ\x96h\xd14\xa0\x012Q\x19\x8b\x1a\x8ce\x16\x81Y`P*.-p\t\x96\x17\b\u007f\xfapw\vj\xf3\x1bf\xd6\x00\x9390cTƙ\xc5\x1c\x0eJT\x05\xfa\xb1\u007f^7PK\xadJԖ\xd7x\xf6\xad\xc3U\x9d\xa7\x83\xed\xbd#\f\xf8^\x90\x13;\xa1\xdfF\xc0\"\xe6\x01i\xb4\x1f\xbb\xe7\xa6ݮ\xe3\x90\x1e`\xa0NL\x86ů\xe1\x1e5\x81\x01\xb3W\x95ȉ\v\x0f\xa8\ta\x99\xdaI\xfe\xdf\rl\x03V\xb9I\x05\xb3\x18\x18\xa0m\\ZԒ\t80Q\xe1\x85CI\xc1\x8e\xa0\x91f\x81Jv\xe0\xb9.f\r\xff\xae4\x02\x97[u\x05{kKsuy\xb9\xe3\xb6>M\x99*\x8aJr{\xbct\a\x83o*\xab\xb4\xb9\xcc\xf1\x80\xe2\xd2\xf0݊\xe9l\xcf-fD\xc8KV\xf2\x95[\xbat'j]\xe4\xffT3\x80y\xd7[\xab=\x123\x1a\xab\xb9\xdcu^8\xae\x9f\xa1\x00\x1d\x00\xcf_~\xa8\xdfE\x8bhzD\xd8\xf9zs\xff\xd0\xe5=n\x86\xd8wx\xef0dK\x02B\x18\x97[Ԟ\x88[\xad\n\a\x13e\xee\xb9ϱ\xae\xe0(\x87\xe87զ\xe0\x96\xe8\xfe\xb7\n\r1\xb9Zõ\x131\xb0A\xa8ʜ8s\r\xb7\x12\xaeY\x81\xe2\x9a\x19|s\x02\x10\xa6͊\x10\x9bF\x82\xaet\x1cv\xf6X뼨e\xd9\x04\xbd\xbc@\xb8/1\xeb\x1d\x18\x1aŷ\xd8V$K\xc6x\x06 ^\x9e\xe4\x01.\x8dE\x96\xaf\xcf_\x93@\xf8-\x13U\x8eyc\xb6\x8cd\xc0\x8087\xa3\x01Τc\\\x92\xd6 #\x8a\x90+۷d\x98D\xb6\xca4\x02\xc9m.=\x97\xf3\xeeGZ.͋3h\xfa\x192\x13\"\xfa\xd4+\xa3\xf4D\xe1\xf4\x1c\x99\xf9\xa4\x96S2c\x86y/\x93@\x97\xf3aR<Džܗ\x17d\xbc$f;~\xf7\xc5XJNˋ2Y\x16\x13\x02\x13\xf3W\xfa\x99)\xf3 O\xc8ZIB\xcer\x86\xca\xc9y)!\x0fdv\x1f\xc9\xd9(\x91<\x93Y\xc0\x939(s\xd9%\vQ\xa9q\xe6IzN\xc9,h\x97o\xb2\x9cI\xf2z\xf9\xa2\xafa\x03O\x8b\x9a\xc5l\x90E\x1by~}\x8b\xf9\x1e\xa7dy,b\xec\x85\x19\x1dM\xc6\xc6ļ\xa7\xe6q\xf4\xf34&\x80\xa6doLdgL@\x9c\xcd\xd9H\xcdɘ\x80\xbd\xa0vg\xb9d\xe6e\xfcC`X\xd4o\xe2\xef\xc5Q/ݘ\xd2=sq\xc9B\xff2\xe8N\xb4\xac\xad\xa6y\xf33fyr\xbb?\xdd\xfc,*ay)\\8\xff\xc0\xf3\xa8\xd3h\xf7xl>\xeb\xfcM\xb9Ϝ6G\a\xe9\xcb׆=\xd7\x03#\x9a\x19xF!\x80Řk\xb4\xf3\xcc\u007f˞\xa9\x15\x92̧\x03\x17>X\r\x9f\xbc_x\x0ev_r\xc5\"\x9ev\x8f\x05A\xa9\xbf|=\xc1\xfd\x987\x10\xbd-\xeb\x9e\xfd\xadB}\x04u@\xddZ\f\v\xdf\x11\xf8\x83f*\xd1&n\x05\xf9\xe1+(\f\f\xe7\xf6\xc0\xc1\a\xe9UX\x14\xec`\x8d\x0e\x0e\x9dy\xd1К\xc4\x1b\xf9\x01\x13]\xe3\x81\x0fՌ\x8e\xbc_\xb2=S\x93\xf0\xdf\xd6u8\xddyXT\xdbo\xe2@\xbc܅\x98\x01\x99\x9aT\x9fv\x01\xb5\x98D\xffV\xaeĒ3\x91lE\xa5%ɿEr\xfc\tI\xf1'8\x15\xa7\xb9\x15\xc9hJI~\u007f\x13\xe7\xe2\r\u074b\xb7p0^\xe6b,\x80\x1c$\xb5\xa7\xa4\xab']\xae&\xdf/\xa4\\\x8e._\x01̧\xa1'\xa4\x9f'\\\x0e,\xad4!\xcd\xfc\xb4\xf4\xf2\x04\x1c\xbe\x91\xf3\xf1F\xee\xc7[8 o\xeb\x82,:!\x8b\x9c3\xfb\xfa\xc5\xd1e\xa5sԳ\xc1\xf8TV\x9be\xb2\x81\xbfПs\xf0Em]\xe1\x85z\xf5L\xd3XH\xb9\xf9\xfa3\x83_\xb8\xcc==\x88\xa9:z\xbcwC\xd0\x1a\x16\xf1\x04\x81\xd6j\v\x15\xb0\xfc\xb5\x82\xc1\x92iW6ms\xf4W\x93f\r7,\xdb\x0f\xa0\xef\xa3~\xc2V\xe9\x82Y8o\xeed.=p\xfa\xfb|\r\xf0I5\x97^݊\n\x86\x17\xa58\x92\x1f\x10\x81y\xde\x05\xf12\x86\x882\x93\t\xe5\x9aB\xfd\x9a\x05\xdf\xef\xbe\xdf;r\x99W\x97\xeeɄ\xaa\xf2\x06\xfa\x041\x99<\xc2ݣ\xb3Q\\ِ\xac-\xa1\x12\xac\x90\xda\a\x1cVX\xf9\xf9\xf5/\xf7\x8cU\x9a\xed\xf0W\xe5\xebp-a\xa2\u07fbW\x84-Ȏ\xfa\xb2\xbd\xfe\x16#\xa6SCE\xb0\x01\xb06\x87fT\r\x8aV\x19\x13*3\xe7\xd1Z\xb1\xb0\x99\x87\x87_\xfd\x06,/p\xfd\xb1\xf2\x17\xa9\xab\x92i\x83\x84\xcdzc~І\xfe\xbbWϱX\x87\n{\xfey\xb8n\x8d.O\xc7\xddמ\xb4\xfaC\xaf\xaaX\x8d\xa2%F}\x8c\x8f\xea8j\x1d\"\xf93\x1fuЧ\xe0t\n+\xba\x10\x86\xfb\xce\xe6u\xcb\xfeLI\xf1\xa9\xd2s\xae\xdc\xdar\xf19_\x95-\x94\x9a\f\xd9^\x95v5{B\xc56W\xe3\xe6e\xf5\xe7|rJ\xaf\xfc\xe7<\x9d\xae\xc7#\\\x91G\x9dw\xea\xcf5e\xc0\x9e\x99i\x12`\xa2\x8a\xb5\x05\xe7G:˖\xa0a\x0ex@\tJ\xba|\x17W\r\xc7\x17\"\x1d\x8e\x89@\xedB\t\t5U)\x14\xcb\xeb\x13^\xeb\xb0P\xbc\xf2\xc1\xc9/}@\xfd\xce\xcc\xc0l\n\xbbE\x900\x16\x98^\xa9\\A\xce,\xae\xa2@\x93d_\x94\xd92\xc3\xfb\x8cn>XK~B\xccv\x1e\x16\x10\x9c\x1aY\xebc\xab,\x13 \xabb\xe3\x15<\xab;\xc4\xe87*#hB\x06\xd4\xcc\xf1\xf2\x1b\xe3\xd2\xe2n\x14c\x1c\xef\xec\xba染w\u058c\x9cڙ\xa9\xb2\f\x8d\xd9VB\xc4L\xfd\x86s_\u007f\x9b.\xb7o\xb1\xe6\x99\xeb\xe4E\xa0K\f\xac\xcb\xea\xf9\xcc\xc0\x02\x8da\xbb\xba\xd8\xd93i\xa0\x1dJtfP,\xfe\xe8\x1d\xc56\x93\xac_\xea\xcbG\xb4Xf+\x16&\xa83\x01:\xbd\xde\xc5\xec\x02\xa1v\xbe \"\xaf\xcb\xc1֪\xf9D\x9c|+\xb9NQ\xe57MG\u008d\vF;B\xb4\xe5{Q\xf0\x1d'=HD\xda1\xbda;\\eJ\bti\xe7\xe3u\xbd\xe5a\r\xf9z_\x91\x99ŭ}\xea\xf6\r\x91\x0fOm_)\x83\xf9r\x8b\xae\x9a\xab\xe5\x1a\xdb\xf2ȣ\x05)7\xf1I\xaa\xdbc!ZHx\xbc\xd2n\xdf\xfa\x80\x05\xb9\x1a\xec\xe9PW\xf8\"\x18\x83q\xef\xb6`\xbf)}\x01\x05\x97\xf4\x0fY\xff.4Q\x0f>i\xfd\xae\x86\xddº\xef\xa8O\x936\xddQ\xa4X\x1f\x88)S5\x9e*\xbb\x82\xcf8\xb6\xac|\xf6+\xe6.\x18\x17\xab\x9eL]n\xe5\x9dV;\xf2\x8f#/\xffʸ\xe5r\xf7I\xe9;Q\xed\xb8\xfcR\x86t\xb9\xd3:\xdf1m9\x13\xe2\xe8\xd7\x13\x19\xdbH\xc9Ȼ\xe5ѓ/>\"i\xc8I3)N\xbf\x80\x8e%\x12\x86n\xad\xcfϥg9\x97 \xbbQ\x95\xedɬV\xe6\xc5\xef\x1b\x1c\xb05|V\x16\xebP2\xef\xc3$)\x8fƮp\xbbU\xda\xfa\x10\xc3j\x05|\x1b̮\x98OɸpWa\xbej2p\xdbf\x03\xb4\xc7\xc4yTڝvWZ\xaa`G\x9f,ɲ\x8c\xacz\xbc4\x96\x89\x88 \xfd\xae\xe4+g\xdf\x12\x9bc\xfe\x97\x88\xc17B\xf8m\xb7\u007f\xf3\xc5z\xa3F\x1d8\x8f9\x97\xcc\xee\x95HT\xa5\x82KqF\tϚ[K\x82\xbb{W\b\x96D\xb5\x10`HxM\xd4'\x9cS!\xee=)\xf9\xdb\xe9\xd8eߑj:O\xd9\bas\x8aȲq(\x98ؖ\xffX\x8a\x9bz,\x912\xdb3\xb9#\xa6Ҫ\xda\xedk\xbe\x9cP\xc1S\xa1\xbf\x8a\x16\x05\xa5;ئ\xbe\xa7\xb1\x95\x96\x9d\xd8O\xb8\xb9\xc9;\xcbe\xd9\xd3\xe4JC,\xba\xae\xdc\u007f\x19*\x0e\xae\xb6Z\x15\xab@\vw\xbdr\x11\xe21\x9a+r4\xec>\x8ar\xf0%\x91Ci/\xc7\x06e\x89\x12\x98\t\xebI\xf8\x92k\x9e\xacs\x01\x11˴Mu_\xee{\x9d\x17<\x17\a9\xbe\xde\xfb\x10o\xf2_\xb4]\x0f\u007fC\xe1\x02\f\x97\xf5\x8f\x06\xf8h\x96g\x05C\x0e\x8dF\x17\x14\x88ޜ\x8d\\\x91\x9e\xe3\xd1_\xfe\xdf\xd7\xe784\xaa\xec&\xc5x}\x1ct\x1f\xa4\x05\xbb\xda\xd8M\x97`pF\xf0\xf1'\xbe\xf5\x97y\x19\xad\xfa\xcf\xff\xf0t\xdfC\x92q\xf4n\xd6.r&Oc\xe0,T¾\x13H\x06\x8bA\xec\x9b\\\xefN\xb2\xad\x0f/\xf3\x16_\xd3U\xac\u007f\xcf\xe2u\x1c\xa8\xc3˜\xc47\xf3\x10_ww\xcf\xcc\xd5\xd0_:c\u007f\r\xdd\".b\x80\x10q\x12#\xdbh\xdc\xc6E'\xb1\xe3#\xd6k\x9c(\xb3=\xf0\x1b_\xc9K\x8c\xea\x81\xd1C'@\xf3\xce\xd9\x0e3\x85'm\xe4\x8de\x19\x12\xbb~\x1e\xfen\u0379/\x02_\xff4\x8d\xfb3Sҫ[s\x05\xff\xf1_g\x10B\xbb\x8f\xf5o\xd0\xd0\xc3\xff\v\x00\x00\xff\xfflA\x99e\xe3g\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKo#\xb9\x11\xbe\xebW\x14f\x0f\xbe\x8cZ\xb3\xc9!\x81.\x81F\x93\x00\x83x\xd6\xc6\xc8q\x0eI\x80\xa5Ȓ\xc45\x9b\xec\xf0!\xad\x12\xe4\xbf\aŇ\xba\xd5ݲ\xe4A\xb2ˋ->\x8aU_\xbdٓ\xe9t:a\x8d|F\xeb\xa4\xd1s`\x8dğ=j\xfa媗\u07fbJ\x9a\xd9\xfe\xfbɋ\xd4b\x0e\xcb༩\xbf\xa23\xc1r\xfc\x84\x1b\xa9\xa5\x97FOj\xf4L0\xcf\xe6\x13\x00\xa6\xb5\xf1\x8c\xa6\x1d\xfd\x04\xe0F{k\x94B;ݢ\xae^\xc2\x1a\xd7A*\x816\x12/W\xef?T\xbf\xab>L\x00\xb8\xc5x\xfcI\xd6\xe8<\xab\x9b9\xe8\xa0\xd4\x04@\xb3\x1a\xe7\xb0f\xfc%4\xce\x1b˶\xa8\fOwU{ThM%\xcd\xc45\xc8\xe9\xea\xad5\xa1\x99C\xbb\x90(d\xb6\x92H\x1f#\xb1U\"v\x9f\x89\xc5u%\x9d\xff\xf3\xe5=\xf7\xd2\xf9\xb8\xafQ\xc12u\x89\xad\xb8\xc5\xed\x8c\xf5?\xb4WOa\xedTZ\x91z\x1b\x14\xb3\x17\x8eO\x00\x1c7\r\xce!\x9en\x18G1\x01ȘEjS`BD-0\xf5h\xa5\xf6h\x97F\x85Z\x9f\xee\x12踕\x8d\x8f('Y \v\x03E\x1ap\x9e\xf9\xe0\xc0\x05\xbe\x03\xe6`\xb1gR\xb1\xb5\xc2\xd9_4+\xffGz\x00?9\xa3\x1f\x99\xdf͡J\xa7\xaaf\xc7\\YM:z\xec\xcc\xf8#\t༕z;\xc6\xd2=s\xfe\x99))NZ\a\xe9\xc0\xef\x10\x14s\x1e\x8c\xb6e,\x1e?\x97ț\x1c(\xfb[ƪ\x82E\xf6\\\xb3\x81\x0f \xa4\xa3\x02\xc0E\xa2C\xb0\xa8<\xa3\xf59x\x1b\xde$>7z#\xb7C\xa1\xbb5\xcd%\x8b\xb9B\xba\x87\xdc2\xdeD\xa1\x89\xac\xa3\xb1f/\x05\xda)\xf9\x87\xdcH\x9e9\t6e\xae\x8dD%\xdcP\xd2\v^\x16E\xb1(ȫ\x99\xba\xa2\xc3\xe5ic,\x8d\x99\xd4ɂ[\x021\xd8\xd8:\xa7T\xedQ\x8bS5rƍ\x89Qˡ\x80\x83\xf4\xbb\x14\x0e\u0558\xdf\xc1\xab\xbeG\xe3\x05\x8fc\xd3=ޟvH;S\x02Ep\xc8-\xfahm\xa8\xc8|Ȕ*\x80/\xc1ŀڏ\x13e\xc4B\xad\x9c~\xc1\xe3\x10h\xb8\xa6\xdc\\\xc2\\g\xf9\x8eJ\xe7°\xc5\rZ\xd4~4\xa8Sgb5z\x8cq]\x18\xee(\xa4sl\xbc\x9b\x99=ڽ\xc4\xc3\xec`\xec\x8b\xd4\xdb)\x01>\xcd\x1e4\x8bm\xc5\xec\xbb\xf8\xe7\x82\xc8O\x0f\x9f\x1e\xe6\xb0\x10\x02\x8cߡ%\xadm\x82*\x86֩o\xde\xc7\x1c\xfb\x1e\x82\x14\u007f\xb8\xfb\x16\\L\x93<\xe7\x06lV\xd1\xfa\x8fT\xa8E\xa6\b\xa2UҊ\xb1@\x99\x92\x94]gm\xa6X3f\x88c\x15fwP`\xa2\f2\x16Q_p\x18L_q\xb3\\\xec^\xf1\xb1RHK-$\xa7B\xec\xdc7J\x83!\xce\xea\xed\x11\xc1\xfa\x15\xf8\xa5\x880.x\x12 \xe7\xc3+\x1c?t\xf7\xb6mY\nO9\xc79\xf4T@9\xd0H9\x90\xd9!r1(p\xa35y\xa37\xc0N\xa1\xee\xce\xf5c\xfc\x1b#\xc4:\xf0\x17\x1c\x01~ \xcaǸ\xb1`\x9c\x8e\x11/\xc1a\f\xbe\xd7\u0600\xeb6\xce\xd9\x12\xed-\xbc,\x17\xb4\xf1\x94&\x19,\x17\xb0\x0eZ(,\x1c\x1dv\xa8\xa9C\x90\x9b\xe3\xf8]4\x9e\xeeW\x05\xd5Xa\xe4\x1a\xbf`;.C\x8a\xe1sX\x1fGj\x82\x1b\x84l,n\xe4\xcf7\b\xf9\x187\x16\xc0\x1b\xe6w \xb5\x93\x02\x81\x8d\xc0\x9f\x8a\xb5\v\x82\x9e\xf2\xffC\x8e\"ߠ\x9e\u05fc=\xb1\xf3\x16\x87/\x18_\xf1\x9fǼ\xed\x84B\xf9\x9d#\xffy-xɏG%ڟ\x1e\f\xfe\x94*,>\x92*Ϙy\x1e\x9ex\xa5R+\xcf\x16c\xceLu\x81\xb1\x16]c\xb4\xa0\xe6\xe9\xb6:\xade\xf9\u007fW\xad\x8d\xabuz\x1e\xe5zkE\v7\xb5*\xf1\x89\xe6\xcd\xcdJz\xb8\xea\xb6\x02f\xed\xa8Sl\xfb\x95\x9e\x8c\xbfH\x9b\xf2\xaeӧP?\xac!\xe8X\xa9Ō_\xc1\xdf5|\xa2ޖ\xb2\x93\x98\x13\xdfv\xcc\x00\xa4\x03m\x0et\xbcC/\x92\x00\xa3S\xbe\xa6n\x8di\x91\x9b\xe1\xb8t\x90JQƶX\x9b\xfdhƦBӢ:\x02sd:\xfb\xdfT\x1f\xaaw\xbfZ\x17\xa4\x98\xf3\xd4Ԡ\xf8\x8a{9|\xe5\x19\xa2{?8Q\x1c\xff\xe4\x0e\xf4\xe3\xc7\xd2,\xcfl\xde\xf6\xe3\b\x18\x1b\xa9\xa8\x16\x1c\x89\x13m\xc50|\x8f\xfc\xb8\xba\xbfs\xb1\x84G\xed\xc7ʾ\x03Z\x8c\x1d\x13\n\xaa\xe2M~\x97\bΣ\x1d1\x80\x93\xf6\xa2\xceA\x19\xbd\xed9N\x1a\xf9\x95\x82*\xb4dPƂ@O\xa9Io\x81\xef\x98\xdeb\xfb\n\x95\xf9\u007f\x9dS2\x9f\x9eʹ\x16\"\xf5%\xf3\xb8I\xa3Or\xacL\x1f\xbc\x00\xb7\x9b\xc7_\u007f\v\xf7E\xb3\x17ۜ+\xb8\x0f\xf6\x97,M\xa0N}\xfb\"\u070eooo\x87\xcf\xcd7 \xf1ַ\xf0W\xde5\xe0\xc0\\\xfb*\xfe\xeb\xe1PS\xb5z\xb5\x04\xfe\x92v\xa5\xe7\xc3|\x04\xd8\xda\x04\xff\x9agލ\x19t~\xee\u007f\v\x8f\xf1#Ƶ\"\x83\xf6\x14\x8d\xf0`\xa9\x95l_\xc5bP\x18\xcb-\xb7?/-z\xdfZ\xbak\xc3/17\xc85\x9ak\a\x93)_v\xf4\x9aA\xee΄\xf5\xe9\xa5x\x0e\xff\xfeϤMה\x13\x1b\x8f\xe2\x87\xfeǵw)d\x94/d\xf1'\xa7:&}\x1d\x84\xbf\xfdc\x92\xaeB\xf1\\>i\xd1\xe4\u007f\x03\x00\x00\xff\xff\x1d\r\x93\v\x97\x1c\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4\x96Ms\xe36\x0f\xc7\xef\xfa\x14\x98}\x0e{y$\xefN\x0f\xed\xe8\xd6\xcd\xee!\xd36\xe3I2\xb9tz\xa0I\xd8\xe2F\"Y\x00t\xeav\xfa\xdd;$%\xbf\xc8v6=\x947\x91 \xf0\xe7\x0f\x04Ī\xae\xebJ\x05\xfb\x84\xc4ֻ\x16T\xb0\xf8\x87\xa0K_\xdc<\xff\xc0\x8d\xf5\x8b\xed\xc7\xea\xd9:\xd3\xc2Md\xf1\xc3=\xb2\x8f\xa4\xf13\xae\xad\xb3b\xbd\xab\x06\x14e\x94\xa8\xb6\x02P\xceyQi\x9a\xd3'\x80\xf6N\xc8\xf7=R\xbdA\xd7<\xc7\x15\xae\xa2\xed\rRv>\x85\xde~h\xbeo>T\x00\x9a0o\u007f\xb4\x03\xb2\xa8!\xb4\xe0b\xdfW\x00N\r\u0602\xc1\x1e\x05WJ?\xc7@\xf8{D\x16n\xb6\xd8#\xf9\xc6\xfa\x8a\x03\xea\x14xC>\x86\x16\x0e\ve\xff(\xaa\x1c\xe8sv\xf5)\xbb\xba/\xae\xf2joY~\xbaf\xf1\xb3\x1d\xadB\x1fI\xf5\x97\x05e\x03\xb6n\x13{E\x17M*\x00\xd6>`\vwIVP\x1aM\x050\xf2\xc82kP\xc6dª_\x92u\x82t\xe3\xfb8Ldk0Țl\x90L\xf0\xb1\xc3|D\xf0k\x90\x0e\xa1\x84\x03\xf1\xb0\xc2Q\x81\xc9\xfb\x00\xbe\xb2wK%]\vM\xe2\xd5\x14\xd3$d4(\xa8?ͧe\x97\x04\xb3\x90u\x9bk\x12X\x94D\x9eD\xe4\xb8\xd6;\xa0#\xbe\xa7\x02\xb2}\x13:ŧ\xd1\x1f\xf2µ\xc8\xc5f\xfb\xb1\x90\xd6\x1d\x0e\xaa\x1dm}@\xf7\xe3\xf2\xf6黇\x93i8\xd5z!\xb5`\x19Ԥ4\x81+\xd4\xc0;\x04O0x\x9a\xa8r\xb3w\x1a\xc8\a$\xb1\xd3\xd5*㨪\x8efg\x12\xde'\x95\xc5\nL*'\xe4\fm\xbc\x04hƃ\x15\x98\x96\x810\x102\xbaR`'\x8e!\x19)\a~\xf5\x15\xb54\xf0\x80\x94\xdc\x00w>\xf6&U\xe1\x16I\x80P\xfb\x8d\xb3\u007f\xee}s:g\n\xda+9\xe4g\x1a\xf9\xd29\xd5\xc3V\xf5\x11\xff\x0f\xca\x19\x18\xd4\x0e\bS\x14\x88\xee\xc8_6\xe1\x06~I\x98\xac[\xfb\x16:\x91\xc0\xedb\xb1\xb12u\x13\xed\x87!:+\xbbEn\fv\x15\xc5\x13/\fn\xb1_\xb0\xddԊtg\x05\xb5D\u0085\n\xb6\xce\xd2]\xee(\xcd`\xfeGc\xff\xe1\xf7'Z\xcf.H\x19\xb9\xd0_\xc9@*\xf3\x92\xf6\xb2\xb5\x9c\xe2\x00:M%:\xf7_\x1e\x1ea\n\x9d\x931\xa7\x9f\xb9\x1f6\xf2!\x05\t\x98uk\xa4\x92\xc45\xf9!\xfbDg\x82\xb7N\xf2\x87\xee-\xba9~\x8e\xab\xc1\nOW2媁\x9b\xdcbSQ\xc7`\x94\xa0i\xe0\xd6\xc1\x8d\x1a\xb0\xbfQ\x8c\xffy\x02\x12i\xae\x13ط\xa5\xe0\xf8\xef07.Ԏ\x16\xa6\xf6}%_\x17\x8a\xf6!\xa0N\x19L\x10\xd3n\xbb\xb6:\x97\a\xac=\xc1Kgu7\x15\xed\x8c\xee\xbe\xc0\x9b\x93\x85\xcb\x05\x9dơM\xceW\xae\x1e\x1er\xee,\xe1\xec\x16\xd6p\xd6s_璛\xe1\xbf$S:\xf1\xc8FG\"trԟեMoe\x81D\x9e\xcefg\xa2\xbed\xa3\xfc\x04P\xd61(\xb7\x1b7\x82tJ\xe0\x05)\x95\x81\xf61\xf5\x194`\xe2\x19\xbf\x11\xcb\xf1\xbf$\x90\xd7\xc8ܜ\xd9Y\xc1ႦW\xb2\x93Fz^\xa8U\x8f-\bE\xbc\x92YE\xa4v\xb3\xb5\xfc\xcf\xfa\x06\x82e\xb2\xb9\x94\x83\xfd\u007f\xfa\x9bIȸ]\x1c\xce#\xd5p\x87/\x17foݒ\xfc\x86\x90\xe7W>-.\v\xbd\xfdc\xe0\r\x94.^ʳIN\xfd\xce\x1cQd\xf1\xa46\xc7\\9\xae\xf6\xfd\xbb\x85\xbf\xfe\xae\x0e\xf7Zi\x8dA\xd0\xdc\xcd_i\xefޝ<\xb7\xf2\xa7\xf6\xae\xbc\x8c\xb8\x85_\u007f\xabJ(4O\xd3\xeb)M\xfe\x13\x00\x00\xff\xff--\nM\xde\n\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4WM\x8f\xdb6\x10\xbd\xfbW\f\xd2CZ \x92\x13\xf4\xd0·v\x93âi\x10\xd8\xe9^\x8a\x1ehj,\xb1K\x91,g\xe8\xcd\xf6\xd7\x17CJ\xfe\x90\xe5\xdd͡\xbc\x89\x1c\x0e\x1f\x1f\xdf.\xbd\u007f[\xffT\xbf]\x00\xe8\x88y\xfa\x17\xd3#\xb1\xea\xc3\n\\\xb2v\x01\xe0T\x8f+h\xfc\x83\xb3^5\x11\xffIHL\xf5\x1e-F_\x1b\xbf\xa0\x80Z\x16m\xa3Oa\x05ǁ2w\x00T6\xf3~H\xb3.i\xf2\x885Ŀ͍~4CD\xb0)*{\t\"\x0f\x92qm\xb2*^\f/\x00H\xfb\x80+\xf8$0\x82\xd2\xd8,\x00\x86\xbdgXհ\xbb\xfd\xbb\x92Jwث\x82\x17\xc0\at\xbf|\xbe\xbd\xfbqs\xd6\r\xd0 \xe9h\x02g\x06'\x98\xc1\x10(\x18\x10\x00\xfb\x03(P\x0eTd\xb3S\x9aa\x17}\x0f[\xa5\xefS8d\x05\xf0ۿQ3\x10\xfb\xa8Z|\x03\x94t\aJ\xf2\x95P\xb0\xbe\x85\x9d\xb1X\x1f&\x85\xe8\x03F6#˥\x9d\x88\xeb\xa4w\x02\xfc\xb5\xec\xadDA#\xaaB\x02\xeep\xe4\a\x9b\x81\x0e\xf0;\xe0\xce\x10D\f\x11\t]\xd1\xd9Yb\x90 \xe5\x86\x1d\u0530\xc1(i\x80:\x9fl#b\xdccd\x88\xa8}\xeb̿\x87\xdc$\fɢV\xf1(\x87c3\x8e1:ea\xafl\xc27\xa0\\\x03\xbdz\x84\x88\x99\xa7\xe4N\xf2\xe5\x10\xaa\xe1w\x1f\x11\x8c\xdb\xf9\x15t́V\xcbekx,*\xed\xfb>9Ï\xcb\\\x1ff\x9b\xd8GZ6\xb8G\xbb$\xd3V*\xea\xce0jN\x11\x97*\x98*Cw\xb9\xb0\xea\xbe\xf9.\x0eeH\xafϰ\xf2\xa3Ȍ8\x1aמ\fd\xcd?q\x02\xa2\xfa\"\x982\xb5\xec\xe2H\xb4t\t;\xeb\x0f\x9b/0.\x9d\x0fc\xca~Q\xcea\"\x1d\x8f@\b3n\x87\xb1\x1cbV\x9e\xe4D\xd7\x04o\x1c\xe7\x0fm\r\xba)\xfd\x94\xb6\xbda\x1a\xc5,gU\xc3Mv\x1a\xd8\"\xa4\xd0(Ʀ\x86[\a7\xaaG{\xa3\b\xff\xf7\x03\x10\xa6\xa9\x12b_v\x04\xa7&9\r.\xac\x9d\f\x8cNv\xe5\xbc&\xa5\xbe\t\xa8\xe5\xf4\x84@\x99ivF\xe7Ҁ\x9d\x8f\xa0\x8e\x95?\x10X\x9fe\x9e\xaf\xdc\fN\xc5\x16y\xda;\xc1\xf2%\a\xc9\xf2\x0f\x9d:7\x9a\xef\xb1nk\xf1\n\x1a\x80\x14\xf7\xf8\xa1\xbe\xc8x\x1d\x03̪w\x16\xc9(b\xa1Ax\x15+\x10\x93:\xc5t\xb9\xb44t\xa9\x9f_\xa0\x82_3揾}r\xfc\xc6;\x16\xb9?\x19t\xe7m\xeaq\xe3T\xa0\xce?\x13{\xcbؿ,r\xbc\x90\x0f\x97\xd4e\xe0\x1a\xc5\xca\xf1\xfa&\x86\x805R\xb2W\x97\xbb\xd9\xdc~\xcb>\xae\x84?\xc9ԕ\xda\x19[\xbe#\x9f\x17\x82ܲ\xa3\x10dJ\xb98\x10\xe4\xed\x11\x1d2\xd2\xd1\xc3\x1e\fw\xb3\x19\x01\x1e:\xa3\xbb<1\xabH\xec\x91\xc8k\x93\xcd\xe6\xdb\xe1K\xf1\x99\x883J\xae\xb2\xc2g\xba\x05\xfcE\xf7\x15˸\xb6@5\x94\xf1\x8bl\x87\x15'\xfa\x06\xe3\xc9\xf1#\xd5:ň\x8e\x87,\xf9\"\x9eNx\xa9\xf3\x8c\xe5\xfa\xc7\xfa\xe33\xf6\xf3\xfe\x18\x99\x9f\x9aʸ\x82&D\xacȴ\xf2|\x9011\xa0l\f\x97d\x94v\xfe\x9c9'j\xf6D\xf1k01\xdb\xec3\x10?\x1c\x02\x8bK\xa2+7\xe0\xf4\xc1\x96\x13\"\xe5ׅV\xd3w\x8d\xb4-B\x83\x16\x19\x1b\xd8>\x16\xbb\u007f$\xc6\xfe\x12\xf7\xce\xc7^\xf1\n\xe4f\xac\xd8\xcc\xc8H\x1e\xd5jkq\x05\x1c\xd35\x95\xcdn\xf38\xfa\x01\xed\xe4\xfa\x8b\x99\xdb\x1e\xd1~[aޞ\xc3\xf4\xee\xfb\xe0@\xcb\x1a[\xb6\x8e+\x95F\xf9\xf6\xf6\xfa\xfe\xcfw\xa3a\x00m\x94F\xe3Dr\xe8\xe1\x1bı\xc1(\x8cY}A\x04\xc3*\xe0\x14\xc0\xd0\x06\xab\bc\xc8#\x86 \x0eaIu\rZ\x94nȒ\xf4\xa9-0\tj\xf3\v\x96\xae\x80;4D&\t\xa6Tr\x87Ɓ\xc1RUR\xfc\xb7\xa7mI\xd7\xe8І9\x8cq\xe5\xf0y\xd7/Y\x03;\xd6t\xf8\x1a\x98\xe4в=\x18\xa4S\xa0\x93\x03z~\x89-\xe0ge\x10\x84ܪ5\xd4\xcei\xbb^\xad*\xe1R\xfc.U\xdbvR\xb8\xfdʇb\xb1\xe9\x9c2v\xc5q\x87\xcdʊj\xc9LY\v\x87\xa5\xeb\f\xae\x98\x16K\x0f]\xfa\x18^\xb4\xfc\x1b\x13#\xbe\xbd\x18a\x9d)F\xf8|x=!\x01\n\xb0 ,\xb0\xb85\xdc\xe2\xc0\xe8\xe4 \xdf\xff\xe3\xee\x03\xa4\xa3\xbd0\xa6\xdc\xf7|?l\xb4\a\x11\x10Ä\xdcbt0[\xa3ZO\x13%\xd7JH\xe7\u007f\x94\x8d@9e\xbf\xed6\xadp$\xf7\xffth\x1dɪ\x80K\x9fԐ\xc3\xec4i./\xe0Z\xc2%k\xb1\xb9d\x16\xbf\xba\x00\x88\xd3vI\x8c}\x9a\b\x86\xf9\xd8tq\xe0\xda`\"%MG\xe45Ʉ\xee4\x96$=b \xed\x14[\x11=\x14\xb9s6]^\x8c\b\xe7\r\x97\xbe\xacw\x9a.\x82\\p\x99\xecI\xd8\xe4\xc0\xa7&\x87\x19VΈ\x024S/\xdb\xef\x19F.\x1b\x1dl1\xa3pD\f\xf4I\xc5\xf1\xcc=n\x14\xc7\x1cl\xda\n\xaefA[)\xe3#\u007f\xd4I9?\x85>%\x9f\x05L+~\x06W<\x91\x81\xc1-\x1a\x94%&\xc7u*\x9d\xc9 \x1b&\x1as\x8cǕ\x02Nx\xf5,ⷷ\xd7ɓ'&F\xecn~\xee\x19\xfeз\x15\xd8p\x1f\xe8Ο}q\xbd\r\x87y\x9f\xe6\x140\xd0\x02Cb\xda\a\t\x10\xd2:d\x1c\xd46K\x91\xca' \xc37\x18w\xbc\x0e\x1e,\xba\xcaCh!\xde\x03#\xdf)8\xfc\xeb\xee\xdd\xcd\xea\x9f9\xd6\xf7\xb7\x00V\x96h}^\xee\xb0E\xe9^\xf7\xa5\x02G+\frJ\xfc\xb1h\x99\x14[\xb4\xae\x88g\xa0\xb1\x9f\xde|\xces\x0f\xe0Ge\x00\xbf\xb0V7\xf8\x1aD\xe0x\uf593\xd2\b\x1b\xd8\xd1S\x84G\xe1j1\r\xa6=\aH\xbd\xe2\xb5\x1f\xfdu\x1d{@P\xf1\xba\x1dB#\x1ep\r\xaf|Zs\x80\xf9+\xd9\xceo\xaf\x8eP\xfdS0\xedW\xb4\xe8U\x00\xd7\xc7\xe1\xa1\xd1\x1d@\x06\xcb3\xa2\xaa\xf0\x90UM?\x1fT\xc8U\u007f\v\xca\x10\a\xa4\x1a\x90\xf0\x84Iz\xc1Q\"\x9f\x81\xfe\xf4\xe6\xf3Q\xc4c~\x81\x90\x1c\xbf\xc0\x1b\x10\xb1\xd8Ҋ\u007f[\xc0\a\xaf\x1d{\xe9\xd8\x17:\xa9\xac\x95\xc5c\x9cU\xb2ه\xf6\x881\tʄyp\xcdL\ueffa*\x13C;C\x88\xf6\xcb\xd8\xf6[2\xc9\xe9\xffVXG\xe3/\xe2`'\x9ed\xbe\x1f\xaf\xaf~\x1f\x05\xefċl\xf5H\x02\x1etd\xd8\xe58\x93\x98\xbd\x1f-N\xa9c&c\xed\xd7<+3t\xacʤb\xc3\xf6䩄\xed$\aƭ\x18VY`\x06\x81A\xcb4I\xee\x01\xf7\xcb\x10\xe25\x13\x14\x9f)\x04\xf7}\x0e`Z7\"\x1b\x8ac \x8fIh\xe4\x04\x15ڬ\xb2\xc7\ue795ð\xafsF\n\x1f\aK\x93\f\xcet\x96\\\x9d\xb3\xd4Q\xbfi\x8e\x16e\xd7Ρ,\xe1Ai\xc12\xe3\x06\xad\x13ef\xe2\xd5<\xd38!\xac\xc0\xcb3<\x88-\xe8L\xf1\x12E\x112\xbd\xbe\x80\xf1]\xc7\\\x85p\xbc<8\n\x91*t\xca[\xc7\x10\x97\xf9Rr\xb2\x86J\xabɐV|1ed\xa6\xf3\x98&G\x9d\xd1!\xd2y}\xed\x1b\xdeϨ\xb0C#?\xf24\xf8S\x97\xda\xfbTL\xbc\xb4\xc6.\x15\xe5\xe9㧕\xd3⽜\xef\xf0\xed,ã\xba\x8b\x96\xacw\xd0\xf6\x8fg\xe4\x8ad\x18\x90\v;}\x04#j\xc8}\x12M9\xfe\x96\x89\x069\xa4\xb7\x9d\xe9\x9e\f\xd5!\x95\rn\xc9\xdd\a\xd3K\xa5i\x84\xd7'\xaa5\x82\xf5}\xa2\v{\x82fg\x91\xfb\x9eF\x86\t\xf3\xe4u\xabL\xcb\\\xe8k.\xb3De\xd74l\xd3\xe0\x1a\x9c\xe9\xe6\xd3',\xb1EkYu\xce\x14\u007f\x0e\xabB\xc5\x1e\xb7\x00ۨ\xce\xf5%\xfb\xc8=^بS\xcf\xeb\x1ad\x8b\xe1\xb1:3*VlLڛ\xc6\xef\x19:\x82Ã\xa0G\xb5\xc1|\xd0\u007f\x89O\x00\xf0\x0fZ\xe7\x10Қ\x9c\x81\xf5\xde뤅\xc1\t\xa7|\x83\x8f\x99\xd1\xd9C\xdcp\xf22\x99Lf\xeeGo\rϺ\u007f<\xe8\x1c\v\xe22\xa8U\x93\x8cY9ր\xec\xda\r\x1a\xe2\xc3f\xefЎ\xddy\xae?\xe3\xeb\xba\x03\x1b\a\xfb\x93\xfc\x02\xa5X\xaa\x96L\xfa>*Y\x97S\xc0\x85\xd5\r\xdbg\b\xa7\x8b\xf8܍\x8c\x8b\\\xc0A\x9f\x93Qk4~\xea\xb9}%\x8f\xe9J\xc9#\x95F\xb2g!\xdd_\xffr\"\xd3\x13\xd2a5\t\x0eq\x9e\xd8\xf9\x03\x9d\xf2uN8\x91\xc4Xɴ\xad\x95\xbb\xbe:\xa3\x05w\xfd\xc2d\r\xb3\xe79\xec\xa9EUȉ\xaa\xf7-\xcf2\xd5\xf1\x13\xf09\xa8\xa3\xc5g\xa2P||\xceŠ;\xd4̐\xa5\xfb7\x81\xcb\xe9\xa3\xd5k\xb0\xc27:)\xf3\f\xa9hhCX\nN\x94Z)\x83\x19\x97\t\xf3\xb02\n\"c\xf8\xbfg\xfc\xc8\xea\xc9l\xd0#\xe7\x03ڱY>\x1c\xe96\xfdC\xd0\x1a~\xfdmqHlXI\xc5\x13\xf2\x9b\xe9\x1fYĔ3\xfdՄ\xffY*\x19*\t\xbb\x86O\x9f\x17\xe9\xd9\xf2>\xfd1\x04\r\xfe/\x00\x00\xff\xff\xb0\xddǼ\x99\"\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs\x1b\xb9\x11\xbe\xf3Wti\x0f\xcaV\x99\xc3]'\x95\xa4x\xb3\xa5lJɮ\xac2e]\\>\x80\x83\xe6\f\x963\x00\x02`H3[\xfb\xdfS\x8d\a9/\x92\xa2*Z\xcf\xc5\x16\xd0h|\xf8\xd0/4'\xd3\xe9t´xBc\x85\x92s`Z\xe0W\x87\x92\xfe\xb2\xd9\xfa\xef6\x13j\xb6\xf9q\xb2\x16\x92\xcfᦱN\xd5\x1fѪ\xc6\xe4x\x8b+!\x85\x13JNjt\x8c3\xc7\xe6\x13\x00&\xa5r\x8c\x86-\xfd\t\x90+錪*4\xd3\x02e\xb6n\x96\xb8lD\xc5\xd1x\xe5i\xeb\xcd\x0f\xd9߲\x1f&\x00\xb9A\xbf\xfcQ\xd4h\x1d\xab\xf5\x1cdSU\x13\x00\xc9j\x9c\x83V|\xa3\xaa\xa6F\x83\xd6)\x836\xdb`\x85FeBM\xacƜv-\x8cj\xf4\x1c\x0e\x13aqD\x14N\xf3\xa0\xf8\x93\xd7\xf31\xe8\xf1S\x95\xb0\xeeߣ\xd3?\v뼈\xae\x1aê\x11\x1c~\xd6\nY4\x153\xc3\xf9\t\x80͕\xc69\xdc\x13\x14\xcdr\xe4\x13\x80H\x80\x876\x05ƹ\xa7\x94U\x0fFH\x87\xe6\x86T$*\xa7\xc0\xd1\xe6Fh\xe7)\xdb\xeb\x01\xb5\x02W\"m\xe9\xe9fB\nY\xf8\xa1\x00\x01\x9c\x82%BD½2\x80_\xad\x92\x0f̕sȈ\xb8L+\x9eɤ3\xca\x04\xce\xef{\xa3nG\xe7\xb0\xce\bY\x1cC\xf6\u007f\x06\xd5\xc1\xf3\xa0\xf83\x91<\x96\xe8e\x12\x9aFW\x8aq4\xb4y\xc9$\xaf\x10\xc8r\xc1\x19&\xed\n\xcd\x11\x14i\xd9\xe3Nw\x91|J\xfaZ3\x97\xb0s\t\x15A\xb6\xb3\xfdS{\xe8ܾ\x0f\x8a\xc7\x05\x10\x8d\x1a\xacc\xae\xb1`\x9b\xbc\x04f\xe1\x1e\xb7\xb3;\xf9`Ta\xd0\xda\x11\x18^<\xd3%\xb3]\x1c\v?\xf1\xba8V\xca\xd4\xcc\xcdAH\xf7\u05ff\x1c\xc7\x16\x17eN9V\xbd\xdf9\xb4\x1d\xa4\x8f\xfdဖ\x9c\xad\x88\xd7\xffM\xe0.\tҭ\x92]^\xdf\xf7F\xc7\xc0\xb6\x94\xa6@\x9c\r\x82hG뻢\xab\x8f3\x17\x06\xc2\xf4\xe6\xc7\x10\xca\xf2\x12k6\x8f\x92J\xa3|\xf7p\xf7\xf4\xe7Eg\x18@\x1b\xa5\xd18\x91\xa2k\xf8ZY\xa55\n]f\xafIa\x90\x02N\xe9\x04mp\x8a0\x86\xa3f5\xff\xce\xc4\xfck\xaf;X\aN\x17>\x9f\xebN\xdc\x00%;\x10\x16X\\\x1aNq :\x85\xec\x8f\xffX\xdc\xcf\xfe9\xc6\xfc\xfe\x14\xc0\xf2\x1c\xad\xf5\xf9\x1ak\x94\xee\xcd>gs\xb4\xc2 \xa7\xc2\x05\xb3\x9aI\xb1B벸\a\x1a\xfb\xf9\xed\x97q\xf6\x00~R\x06\xf0+\xabu\x85o@\x04\xc6\xf7\xe1/ٌ\xb0\x81\x8e\xbdF\xd8\nW\x8a~\xd2\xda3@\xd6\x15\x8f\xbd\xf5\xc7ul\x8d\xa0\xe2q\x1b\x84J\xacq\x0eW\xbe\x12<\xc0\xfc\x8d\x1c\xeb\xf7\xab#Z\xff\x14\x1c芄\xae\x02\xb8}\xbek{\xe4\x01\xa4+\x99\x03gDQ\xe0\xa1\x10\xed\u007f>xSH\xfc\x1e\x94!\x06\xa4j\xa9\xf0\x8a\xe9\xf6B\x00\xfd\xf9헣\x88\xbb|\x81\x90\x1c\xbf\xc2[\x102p\xa3\x15\xff>\x83Go\x1d;\xe9\xd8W\xda)/\x95\xc5c\xcc*Y\xedB\xb5\xbfA\xb0\xaaF\xd8bUMC\xbd\xc1a\xcbv\xc4B\xba8\xb27\x06\x9a\x19w\xd2ZS\x95\xf1\xf8\xe1\xf6\xc3< #\x83*|\xbc\xa3\xec\xb4\x12T5P\xb9\x10r\x9e\xb7\xc6A\xd2L\x9fm\x82\xf98\x05y\xc9d\x81\xe1\xbc\b\xab\x86\xb2Pv\xfd\x12?\x1e\xa6\xfe\xf4\x8d\x94\x00\xfd\xc0\xf1͒\xe83\x0f\xe7+\xd5g\x1c\xae\xfd\xd6:y\xb8u\xb3D#ѡ?\x1fW\xb9\xa5\xa3娝\x9d\xa9\r\x9a\x8d\xc0\xedl\xab\xccZ\xc8bJ\xa69\r6`g\xfe\xc9<\xfb\xce\xff\xf3\xe2\xb3\xf8\xd7\xf5s\x0f\xd4y\xf4\xbf\xe6\xa9h\x1f;{ѡR\xad\xf8\xfc2q5\xacTN0\x11\f\xe0\f\a\xb1\x994\xf22\x8a\xf6\x13*E?B\xaf\x11oE\xe3!\xf6R\xbb\xa2\x874\x95\xbd]\x84\xd3\xf1\xf7^OF+>\xe9\x93\xd6v\xc9\xde\xe4\xc1\xa1\xfa\x13][\xed\xcdv\x9a\x9c\xed\xd3\f\x9fʾ\x83v\xc9c9t\xed\"\xef!f\xbb\xd4ˣ\aˋ\x9f˹\xa2\xc7@\xf7W\x8b\xd36p3\\\xe1{S\x86G\x9f\x105\xfa7hh8n\x99M\x9b\x8c\xdd7\xb4\xf4\x85\xa5>O\x92:\xe4\xbeT\xa7\x97Ċ\x89\n9\xec\u007f7\xf1\xcdq\xeb\x9b4\xd7c\x95iR\xd4X\xe4>n\x8c\x80\x1e\xaeK}O\xce\x1cNI\xc5@B6UŖ\x15\xce\xc1\x99f8}½j\xb4\x96\x15\xe7\xfc\xeb\x97 \x15^\xf1q\t\xb0\xa5j\xdc\xfe\x19\x1f\x1d-Rqm\xa3\x15\\\xd6J(\x99=\a\xe5\x81d\xc6,n\xef\xf2\xa7M\x0eN\x84\xb2{\u070e\x8c\x0e\xfa\xd0\xedɛdB#s?y븈\x80\xb8\xd19\x0e\xa2\x18\x94\xaaJ֭\x1c%\xa5\xa6^\xa2!\"|\xf3;1\x92\x02\xc7X_Ŀ\xa7\x0eL\x1e4\xa4X\x18T\xc5\x17bΤo\x13\x92\xfd:\x05\\X]\xb1݈\xdet\x12_2\x91\xf9\x92\x1f\x1d,&y!\xb9\xbf\x9f\xbb\xb4\x9f\xb3o\xee\x8f\x17tc?\x15\x8c\xddB\xbb\xefߛ\xdf\xff\xaa\xf1:;\x9c(\xe2\xacc\xc6=7\xec-:\xc2\xe7\"\x9eW=\x1e\xefڡk\x18\xa8\xba\xdb\xfc\x911j\x94\xa8\xc1\xa0G\xce[\xbac/\xb4=\xd2,\xf7\x9d\xfe9\xfc\xf6\xfb\xe4\x90\xeeXNU;\xf2\xfb\xfeOڱVI\xbfP\xfb?s%\xc3O\xcav\x0e\x9f\xbfL 6M\x9f\xd2\xcf\xce4\xf8\xbf\x00\x00\x00\xff\xffe\xe5\xd5&\b \x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xdc\x18\x93Z\x98\x92\x9a\x01fAW֬#D{zSI\xa4\xbd\x16ĺv\xacV\x95\x1f{h\xe0:\x14\x8fS\x04V\xcc`\x0e*\x88}%Є\xb5r\xc7\xfeV\xb1\\\x8d\x82n\x90\xf7\xae\x86`+\x14`P`f\x95\x1eR2\x85\x9e\xfeIQ\x96#t\x8c\xa8;\xfc\xb7\x88M\x80\x04\x12\xf3\xe7-϶\xde\v \xd9tp Wh\x9c\xe6 Ou?\x86$\xcc\xf1>,2\xa5;\xdag\xe6L\x1d\u008b\xe9\x93\xf6Iз\xed3\xa3y\a\x9a%\xbc\x8f\x9a\xce\xf6\xf9\xf7$lmJN\x10ڻ\xc1\xd4\xd7\x15Z\x17U\x91\xb7\u007f\xb7\x06,J\xbb\xbf\x02n\xeb\xb7s\x10\x99\x10\x9d\xf5\xffƌ9^\xe2\xef\x0eg\xbe\xaa\xc4Ore\x0e\"q\xa5Y\xfeo\xc8\x14g,\x1e\x83\xadHfȯ\xddYW\xc0\xd7\rC\xf2+XsaQ\x1fp\xe6E\xe7\xe55\x88\x91b\xef\xe8)\x98Ͷ\uffd0gc\xda\fT\"]\x0e'{\x9f\xb8\x0e\x12\xfa\x86y\x06.\xb8\xf8\x95k,|\\\xfc\xc9Q\xb3}\xe3\x02\x8aw\x1f~\xc4|\x8a<\x90&y\x03D\xde\x1dl\xb6\xbbtp\xf4S\xd1\b\xaeO\x134\xf9\x8c\xc7\x150x½\xf7X\x98\x04b\x0e\xb3\xceߍ\x86OC\xe2\xb8ԋw\x8fq\xef\xc0\x84\\\xca\xec\xecTQ\xf0\xcf\x13F\xfc\xfd\xd8\xd3# \xed)D\xb8\x9e\x92\xf4\xc2\x11\xc2E\xde\xe9\xc4\x03\x97\x17\xabu\xd1\xb7]o\x87@\x9c\xd5ۡ\x05\x1aCwJj\xe6\xae\a\x80Le\xed8\xbb\xbd7Rs\x84\xe7\xb3B\nP1\xf7I/2\x9f\xc1\x8f\xf6\xbdK#e\xf0(vǻ.I\x9c\xedNH\xcf\x12}Y\xb4\xed\n\v\x97\x14\xd4;\\T\xf2I\xaag\xb9p1\xa5\x99\xcd\xd67\x8b\x9f\xac8\xbe\xa6\xd2\xe8\x8bW\xba\b\xd4\xf6\xf7\fJ!\x99\xcdG\x05\xc6SR0\xa7\x86|\x1b\xebȟ\xb3\xbb\x98Z\u007fbr\xa89\xde\xfa\xfe\xd3\xd4ަ\xbb\xf8\xac\x8e\xff\xf0\xbcE\xbbE]7\xb6.\\\x0foL\xad\xb6\xa5\xc9\xd6\x15n\x9a\x9dH~jo\xca7\xe1\x1d\xb4?\xc5}eY\tqE\x82\xcd*a}+\xaa\xae\"B\x94\xd4\x03\xb4RJ ;l\x8bM)\xa2ϕ\xce\xfb\xfd`M\xe9\xban\bS\xf5\"\x11\f=/}\xd7g\xb7.ۯ\x81\xbb\xecO\xbdӿ\xbcU,\xa1\xbc=SԞn\xa0\x9b\xa2\xd7Pl\xba\x14ke0\x8c\v\x9d\x95\xdf\x14\xf9f\n\xd1\xe3\xe5\xe7\x90lE\xcbvo\x97\xfd\u007f\xac\n\xc5h\x97Y\x88\xa0\xf2\xbcm\xf2\x04\xce\xf2ʜ\xefx^1ѓ\xc0\x0e\xcdZ҂\xd2 \xb9\x88ա\x88\xe6\xf5\xfc\x1e\x8d\xe1c\xe9\xf3\xd1G\x9f\xd5iw'\xadf}r\xa5\xba_\x89\x1e\xd1\xe0Ǧf\xd3[\xf2\xd2k\xd1\xd3\xc5\xe3c*Ї\xf5\xe5Q\xa0\xf3u\xe7\x14Ou\xa6\xc6|Be9\xb1\xab\xe8\xc5\t\xe8\x94\xda\xf1I\x15\xe3\xd9ƛ\xc4:q\xbf\x02<\r\xf2\x88\xeap\x12q\xe6+\xc1G\xd7\u007fC\xbdu\x12\x8f\xe4\xaao\xa4\x9e;\tx\xb4\xd6;Uŝ&y\xa4\u009b^\xbb\x9d\x04\xed\xea\xba\xf3\x15\xdb\xd7\xeb\xcbz\r\x17y\\\xd5\xccV]_\xe4B'\xd4U\x8f\xa9\xa6\xceR\xec\xc4\xcaiS\x19\x1dY\xf7\xd8zi\xbf\x1e:\x024\xa5J:R\x05\x1d\x818Y\x1bM\xad}\x8e\xc0\x9e1\xbb\x93R2\xf1g\xe3u\xff\xc6ʒ\xcb͐\xf3\xa9\xf21)\x1b\x83\xd2iw͞pt\x9d\xe3^X\x11[\xd2\u007f\x90\x18\tA\xea\xa4\x13\x97V-\xe1\x9d\xdc\x0f\xe0\xbaf\xe8\xa8\xcb\xdd\xffb\x85\xb6\xf5̅\xe8~\x95\xe1\xc0vA\x85\x0f\x9cL<\x10\xa6\x81c\x1f3E\x99\xa2t\xcfߝ\x8b8>\x1e\f鈴\xa6\xfd\xe7\x98\xeb\xcc\xed\xf6D\xff\xb9\xa8\x84\xe5e\xf4\x10\x97Z\xed\xb8K\x8amq\xdf\xd0\xf3\x0f徇X\xed\x1d\xa4\x8f\x0f\xcd\xf9Z\x1e\x84\x02,v*\x9eQ\b`f\x88~\xe6\xbf\t\xcc\xd4\xc2}\xe6C\x9c\xac\xe5!|;x\xe5\xce`,@\x95\xf5\xd7j\x05\x81q\xdf\x15\x9aH\x02`ԺL{\xb8\xde\x19w\xef\xfe\xacP\xefA\xed\\\x154\xb8<3\r\xc7^S\x98J\xb4\x1d\x1eA\x01\xfa/Q\x0f<\xffVc\xc0;\xe9mp\x14\xec\xc1\x1e\x1d\x1cRZm\xb4C\xfa\x99\x02\x99\x91\xa1Q\xa8R5\xb3\xe3\xf20ijR\xbbu\xcf\x1b\xfb\x1c\x1f\xfd\xcc\xfa\x1dg\x89\x80N\x8f\x81&@\xa6vߦe\xecg\xbbm\xcf\x15\v\xcdEC\xc9n`Z7\xed9\xbah\x8f\xe8\x9e=\"*:..J&SJ\x97\xecY\xa2\xa33\xc6G爐N\x8b\x91f@\x1et\xbf\xa6\xf4\xb5&\x15\x99\x92K\x14)E\xa5\xf9\xba\xe6t\xbfjB\x9fjB\xf1cn\xa7\t\xfd\xa8\xc7\xf5\xa1&\xd0\xf0L\xd1ә\xe2\xa7sDP獡f\xa3\xa8Yə\xfc\xfb\xe4\x1cy]M\xfd\xa0r\xbcW\xda\xce9\xfc\xf7\x87\xe3#\x15\xacN\x10\xa4D\x0e\xb2\x1e\x1aA\xca\xf9\xf2\xc1\x8f?\r\xa9x\xb1)\xac\u007f\xffy\x0e\x9f\x87f\xe04\"\xe4\x92\xd6\xf1Y\x04\x0f\x9a\xefp1\x92\x95f\xab,|\xb7\xe3,\\(\xa2\xaa<\x04!\xfa\xfbs`\xf9h\x99\xad\x12\x11\xf5c{\xb8\xf2l۩\xe7G\x10\xb2\x91\x99\x15\xae\fe%J\a<.\xbd\xfbi\xf9o˟.\x00\n\x89n\xfagZ\xa1Ҥ\xaao\x80\x1b\xc6.\x008\xa9\xf0\x06$*-$\xaa\xe5\x0e\x19J\xb1\xa4\xe2B\xd5X\xd8\xc56R\x98\xfa\x06\xda?\xfc\x9c\xb0\x11\x8fă\x9f\xee\xde0\xaa\xf4\xafݷ\xbfQ\xa5\xdd?53\x92\xb0v1\xf7RQ\xbe1\x8c\xc8\xe6\xf5\x05\x80*D\x8d7\xf0\xd1.S\x93\x02\xcb\v\x80\x80\x93[v\x11v\xbd{\xebA\x14[\xac\x88\xdf\x0f\x80\xa8\x91\xbf\xbb\xbf\xfb\U000af3fd\xd7\x00%\xaaB\xd2Z;ʄ\xbd\x01U@\xe0\x8b\xc3\xcdn\xc01\x01\xf4\x96h\x90XKTȵ\x02\xbdE u\xcdh\xe1\x88\xd8@\x04\x10\xebf\x96\x82\xb5\x14U\vmE\x8a'S\x83\x16@@\x13\xb9A\r\xbf\x9a\x15J\x8e\x1a\x15\x14\xcc(\x8dr\xd9\xc0\xaa\xa5\xa8Qj\x1a\t럎\x1cu\xde\x1e\xe0\xf2Ƣ\xebGAi\x05\b\xfd\x96\x03ɰ\f\x14\xb2\xbb\xd5[\xaaZ\xd4\x0e\xd1\t(\x11\x0eb\xf5'\x16z\t\x8f(-\x18P[aXi\xe5n\x87\xd2\x12\xa7\x10\x1bN\xff\xbb\x81\xad,\xa2vQF4\x06~\xb7\x0f\xe5\x1a%'\fv\x84\x19\xbc\x02\xc2K\xa8\xc8\x1e$\xdaU\xc0\xf0\x0e<7D-\xe1w\xc7\x1e\xbe\x167\xb0պV7\xd7\xd7\x1b\xaa\xe3\xf9)DU\x19N\xf5\xfe\xda\x1d\x05\xba2ZHu]\xe2\x0eٵ\xa2\x9b\x05\x91Ŗj,\xb4\x91xMj\xbap[\xe7\xee\f-\xab\xf2_\x1a\xb6\xbd\xe9\xedU\xef\xad\xe4)-)\xdft\xfepb>\xc1\x01+\xf0^\x96\xfcT\x8fEKh\xfb\xcaR\xe7\xe1\xfd\xe3箜QuH}G\xf7\x8e\xf0\xb5,\xb0\x04\xa3|\x8d\xd23\xd1I\x9b\x85\x89\xbc\xac\x05\xe5\xda\xfd(\x18E~H~eV\x15Ֆ\xef\u007f\x19TV\xa0\xc5\x12n\x9dR\x81\x15\x82\xa9K\xa2\xb1\\\xc2\x1d\x87[R!\xbb%\n\xcf\xce\x00Ki\xb5\xb0\x84\xcdcAW\x1f\x1e\x0e\xf6T\xeb\xfc\x11\x95\xd7\b\xbf\xc2\xe9\u007f\xac\xb1\xe8\x9d\x18;\x8d\xae\xc31\x87\xb5\x90=\xe5`\xa7,{@Ӈ\xd6>\xfe\xf4[\rv\xf8\xcf\xc1V\xfe\xa3\x19h\xe5\xc7n\xc2p\xfa\x97A\xa7\xe2\xfc\x89ŁJ\x19\x80\x84\xb8?'\x16\xcb\xc1\xff#4\xb5\x0f~-\x98)\xb1l\xb4\xed\x00\x97\x83\x1d\xbf\x1fLp\xe6\x88Pn\xe5ߪ\u007f\xbbm\xde\xfek\xd5ib\xc7D\"X\t\xa4\xdc\xc3\x03\xca\x1d\xb2IJۇj\xac\x12\x9b\x9b\xc4\x0e\x9c\x9d#+\x867\xa0\xa5\xc1\x11\xca\x10)\xc9~\x840\xd16\xe7ҥ\x19\x1f\x14\x02\xa3\x05v\r\x85'\x8d72D\x0ew\x04\xdf9U\xa8\xb2\xea,by/\x18-\xf6\xb3\xa4IM\x8a\xc7-\x1c\xbe(\xc1+ܒ\x1d\x15F&p\xb2GҎ}j-i\xabM\x85Uf\x01Jy\x1a\xc6Ijm\x85x\x9ac\xfe/vL\xab\xb6\xa1pn]\xc4E\x06v\a+\xbaB\xc0\xafX\x18\x9d\xd8&@i\x9c\x05\x11\x12j\xa1\xf48\xe3Ǖ\x0fx}0&\xb50%5\x03̂\xae\x8c\xac\xb3\x88\xf6\xf4\xa6\xe0h\xf7ZYֵc\xa50~졁\xebPi\x9f\f}\xdb>3\x9aw\xa0Y\xc2\xfb\xa4\xe9l\x9f\xff\x9f\x84\x8d\xa6\xe4\x04\xa1\xbd\x1bL}]\xa1uQ\x95\xf5\xf6\xefրU\xad\xf7W@u|;\a\x910\xd6Y\xff\x1f̘\xe3%\xfe\xeep\xe6\xabJ\xfc$W\xe6 Z\xae4\xcb\xff\x03\x99\xe2\x8c\xc5c\xb0\x15\xd9\f\xf9\xad;\xeb\n\xe8\xbaaHy\x05k\xca4\xca\x03μ輼\x061r\xec\x9d}*\xa2\x8b\xed\xfb\xafֳQm\x06*\x93.\x87\x93\xbdO\x1c\x83\x84\xbea\x9e\x81\v.~\xa5\x12+\x1f\x17\u007fv\xd4l߸\x80\xe2\xddǟ\xb1\x9c\"\x0f\xe4I\xde\x00\x91w\a\x9b\xed.\x1d\x1c\xfd\\4\x82\xeb\xd3\x04M>\xe3q\x05\x04\x9ep\xef=\x16\xc2\xc12\x87h\xe7\xef&ç!q\\\xeaŻǸw`B.evv\xae(\xf8\xe7\t\x13\xfe~\xea\xe9\x11\xd0\xee)D\xb8\x9e\x92\xf6\x85#\x84\x8b\xbc\xf3\x89\a./\x16u\xd1{\xc8a5J\xd5zrk\x8d2$\U0007c88d\x11\xc0\vc\xa3\xb9$]\x93\xa0#Mf\xd5\x12xF*|\xcd#g\x8b\xc7x\x8d\x96.G\xfa\xdb\xef\xbfvr\x8c\xf6\x84\xda\xdf]D^۫-DU\x91\xc3*_\xd6Vo\xfd\xcc(\xd3\x01\x90\xe7\xbe\xdc\x18w.\xf3ݽ(C\xae\xbe\xf7L\xf5\x96r \xf1\xf8\xa3\f\x02E\xa0\x16\xf3\x9a\xc8?[\xa2`\x85ț\xdc\xf8\xf7`\xaf+\xca\xef\xdc\x02\xf0\xf6\xd5\xed;\xb4\xe4:\x89\x9d\x91\xd4\rC\x9b\x17\xce\xe2\xe4\xbaF\xa2\x84\xe7-J\xecI\xc50\xe1m=\xc6L\x90\\\xe8n^\xc1\u00adE\xf9F\xc1\x9aJ\xa5\xbb\x1b\xcd\x158\xa3r\xc5\xe1H\x0e[\xec>\xd3\n\x85\xd1'\xf0\xe0};\xbbW\xa0\xad\xc8WZ\x99\nH%L\x86q\xf7\x8f\xb5/\xb4j\xaa\xa8\x81\x03τꦞ\xe42,ZX.\xd5\fu.\x8bW\xb8\xb6\xea\xa8\x10\\\xd1\x12e\xac\xf2{\xceRa\x0f\xee\x9aPfR\xe5\x9b\xd4sl\x98\xca\xdfKyR\x94\xfa\xc9\xcf\xecd\r\xb7\xe2\xb9O\xa0l\x12l\xc9\x0e\x81\xae\x81j@^X\xbe\xa0\xf4*\xdb-\x11\x88\xe1H\x93-\x96y\n\xde>\xc8M\x95G\x80\x85;ٔO&ź\xc3?\x10\xca\xce\xc16+y\xa7\x1f\x8d?\xda\xd9\xdf\xe4h4J%߄\xad\x10\x1e\x90\x94\xfbx>\x88\xd66Tu2 @\x1a\xdeՈg8\x19\xc7\xc4wa\x17\xaf\x19\xb8QN3\x18{\x90ϧ\xba\xeb\xedX\x10g\xf5v\xec\x02\x8d\xa1;%5s\xd7\x03`Met\x9c\xdd\xde\x1b\xa99\xc2\xf3Y\xa1\rP\xb1\xf4I/k>\x83\x1f\xed{\x97F\xca\xe0I\xec\x8ew]\xb28\u06dd\x90\x9f%\xfa\xbah\xdb\x15\x16.)(w\xb80\xfc\x89\x8bg\xbep1\xa5\x9a\xcd\xd67\x8b\x9f\xac8\xbe\xa5\xd2\xe8\x8bW\xbe\bD\xfb{\x06\xa5\x90\xcd\xe6\xa3\x02\xe3))\x98SC\xbe\x8du\xe4\xcf\xd9]L\xad?19\xd4\x1co}\xffino\xd3]zV\xc7\u007fxޢޢ\x8c\x8d\xad\v\xd7ÛR\xabmi\xb2u\x85\x9bf'+?ћ\xf2Mx\a\xedOi_\x99\x1bƮ\xac`\x13ôoE\x95&!DY=@+!\x18\x92ö\u061c\"\xfa\\\xe9\xbc\xdf\x0f֔\xaecC\x98\x88\x8b$0\xf4\xbc\xf4]\x9fݺl\xbf\x06\xee\xb2?q\xa7\u007f{\xabXFy{\xa6\xa8=\xdd@7E\xaf\xa1\xd8t)\xd6\xca`\x18\x17:+\xbf+\xf2\xcd\x14\xa2\xc7\xcb\xcf!ي\x9a\xec\xde.\xfb\xffh\x11\x8a\xd1.\xb3\x90@\xe5y\xdb\xe4\t\x9c\xe5\xe5%\xdd\xd1\xd2\x10֓\xc0\x0e\xcdZ҂\x90\xc0)Kա,\xcd\xe3\xfc\x1e\x8d\xe1S\xed\xf3\xd1G\x9f\xd5iw'\xaff}r\xa5\xba_\x89\x1e\xd1\xe0Ǧf\xf3[\xf2\xf2k\xd1\xd3\xc5\xe3c*Ї\xf5\xe5Q\xa0\xf3u\xe7\x1cOu\xa6\xc6|Be9\xb3\xab\xe8\xc5\t\xe8\x9c\xda\xf1I\x15\xe3\xd9ƛ\xcc:q\xbf\x02<\r\xf2\x88\xeap\x16q\xe6+\xc1G\xd7\u007fC\xbdu\x12\x8f\xec\xaao\xa2\x9e;\tx\xb4\xd6;Uŝ&y\xa2\u009b_\xbb\x9d\x04\xed\xea\xba\xf3\x15\xdb\xd7\xeb\xcbz\r\x17y\\\xd5\xccV]_\xe4Bg\xd4U\x8f\xa9\xa6\xceR\xec\xc4\xcaiS\x19\x1dY\xf7\xd8zi\xbf\x1e:\x024\xa7J:R\x05\x1d\x818Y\x1bͭ}\x8e\xc0\x9e1\xbb\x93R2\xf1g\xe3u\xffN\xea\x9a\xf2͐\xf3\xb9\xf21)\x1b\x83\xd2iw͞pt\x9d\xe3^X\x91Z\xd2\u007f\x90\x98\bAb҉r-\x96\xf0\x8e\xef\ap]3t\xd2\xe5\xee\u007f\xb1b\xb7\xf5L\x19\xeb~\x95\xe1\xc0vA\x85\x0f\x9cT:\x10\xb6\x03\xc7>fJ2EȞ\xbf;\x17q|:\x18\xdeMcM\xfb\xcf)י\xea\xed\x89\xfese\x98\xa6u\xf2\x10\xd7R\xec\xa8K\x8amq\xdf\xd0\xf3OᾇX\xed\x1d\xa4O\x0f\xcd\xf9Z\x1e\x84\x02$u*\x9e\x911 j\x88~\xe1\xbf\t,\xc4\xc2}\xe6c9\x19\xe5!|;x\xe5\xce`*@\xe5\xf1k\xb5ʂq\xdf\x15\xaaD\x02`ԺL{\xb8\xde\x19w\xef\xfe2(\xf7 v\xae\n\x1a\\\x9e\x99\x86c\xaf)\x94am\x87GP\x80\xfeK\xd4\x03Ͽ\xd5\x18\xf0\x8e{\x1b\x9c\x04{\xb0G\a\xc7*\xad6ڱ\xfa\xd9\x062#C\x93P\xb9hf\xa7\xe5a\xd2\xd4\xe4v\xeb\x9e7\xf69>\xfa\x99\xf5;\xce\x12\x01\x9d\x1e\x03M\x80\xcc\xed\xbe\xcd\xcb\xd8\xcfv۞+\x16\x9a\x8b\x86\xb2\xdd\xc0\xbcn\xdast\xd1\x1e\xd1={DTt\\\\\x94M\xa6\x9c.ٳDGg\x8c\x8f\xce\x11!\x9d\x16#̀<\xe8~\xcd\xe9k\xcd*2e\x97(r\x8aJ\xf3u\xcd\xe9~Ռ>Ռ\xe2\xc7\xdcN3\xfaQ\x8f\xebC͠ᙢ\xa73\xc5O爠\xce\x1bC\xcdFQ\xb3\x923\xf9\xf7\xc99\xf2XM\xfd(J\xbc\x17R\xcf9\xfc\xf7\x87\xe3\x13\x15\xacN\x10$X\t<\x0eM \xe5|\xf9\xe0ǟ\x86T\xba\xd8\x14ֿ\xff2\x87\xcfC3p\x1a\x11\xeb\x92\xc6\xf8,\x81\x87\x9d\xefpQ\x9c\xd4j+4\xfc\xb0\xa3$\\(\"L\x19\x82\x10\xf9\xe39\xb0|\xd4D\x9bLD\xfd\xd8\x1e\xae\xb4\xd8v\xea9\xcf\x18ˊ\xb2s'\xd1\xc1\x9e\xac'\xe4\x01\xb9\x83\xe6\x8c$\xa7\xec\xaa\xe3z\u007f\x9b2E\xe6'\xcf'\u007f\xec\xec\xc93r`],iý($-]N(P\xcc*\xe9\x8c\xef\x12\xa7-K\xe6ײ'\u007f';O\xac\x04\xa1\xc6>\x91\xcd\xf9\f\xf6o\xa5\xe7\x84>V\xc5\x16K\xc30\xe3\xf6\x9a\xc7\xce\xd0\xf9\xfbk\"\xe0ԙ\x14\xfd\x1bl,];\x9d\xae֤\xf5o\xca\tD\x0f\x90-{\x12P\xbb \xbd\xed\xf67j\x14\xd6R+S\x14\xa8\xd4ڰX\xfeu\xb7\xa4a\x19\x87'\xbb\x15#\x0eGܱ\x93v\xdb\x16\x9d;\x82.28\xa3\x12jrBE\x16\xa4\xd6F\x061/\x8c\x94\x0ee\xff\x9fX\x0f\xee5\xea\x81\x1dWZ\xa1?\xa7w\x93ܴ\x84\xdc\x0eg\xb8\xdb\xc3d\xe9\xb7\xe6:\x89\xba\u05ffx?gx/\x19\xb8\xceIմ\b\x95\xcb\x0el\x0f\xc6\xf9\xfd\x164\x96\x80;\xe4 \xb8\xeb\xbf\xc5r\xaa}\xec\xb3\v\x8c\xe5\x0e\xe5\x1b\xd5\xc01*t\xa3\x12\u007f\xfeA\xa8\xa6|\xf3A\xc8{f6\x94\u007f\x8ag\xf2\xb8\xc1\xf7DjJ\x18\xdb\xfb\xfd$\xe6\xde\xc6Ü\xf8o~\xf6\xc8\x1fSL\n8φ\t~X\x1b5Q\xee\x0f\xbak\xea_\t\xa3\xbb\xa7\xe2\x8dj\x0fL:e\xeb\xa0-\xe1\xa3\xd0\x18\xb3q\xb4\x0f\x94*X\xa1\xd2\v\\\xaf\x85\xd4>J[,\x80\xae\x83\xa2NE\x1f\x842\xe7k\xf8\v\xfc\xac\x03\xd2T\x84\x1b\xcb'$\x10\xbe\a\xe9N\x85sR*\xb2\xf7=w\xa4(\x8c\xd5\x03\xd7J\x93\x94A{\x91k뜛 \xcd#\t\x8b\xbe\xa7\xd6\x1d\xdf|\x1eh\xaa\x15J\xd7Tl\xff\xf6\xa4s\xdf\x1bx\x15\x94\xacD\x80\xfb*\xa1\xf3\xb9\x13({\x9cӁ\xf3\x94\xf2q\xff\vM\xd8ݸ\xa3\xd6\xef\xb0m\x06G\x04\xdc\xf4!\x1a\xbd\x9b\xca\xc6\xfb\x87\xa8\x8aS-ϊ-\xe1\x1b+>R\x98\xcd6\x8a\xe0\x98\xa6\x1eK\x94\x18wQZ\xedN\xaa\x8aYmm$\xef$eB\x9e\xbbl\xb7;\x05t\x9a\x84S~fϴ\xcey\x9a\xbd\xc1/s!\xdc\xc2ց\xf8~M\xff\xae\xd1\xdd\xefs\x9c\x80/\a\xc3\x0f:'\xad;\xd0B\f\x86;A\x9c\x1f\xe8:^t\xbbb\xf8\xe3`\xc47\xee\x80|&\x92S\xbe\x99C\xfe\x8f0,\xe1\x03\x05\b\t/(\x81D\xe3\x17\x1d\xe5\x05\xc5M\x8e\xdc\xe5\xd8xF/\xf0\x83\x92gh\xf0\xd2\tr\xd9!rX)\xbci\xe3\aR\x14X\xebЙܽ\xc6\xf9\xf2\xd2\xfd\x88\xf74\xbb\x9f\x85\xe0^-\xa8\x1b\xf8\xcf\xff\xba\x88\b}\x89\xd71ۗ\xff\x17\x00\x00\xff\xffF\xa1\x18'\xf3Z\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=]s\x1c7l\xef\xfa\x15\x18\xf5\xc1\xed\x8c\xee\x14O\x1f\xdaћ++SM\\\xdbc)zi\xfb\xc0\xdb\xc5\xe9\x18\xed\x92\x1b\x92{\xf25\x93\xff\xde\x01\xc8\xfd\xbc\xfd\xe0\x9e\xa5i\x9a\x11_\x12\xed\x91 \b\x80\xf8 A\xf8l\xb5Z\x9d\x89B>\xa0\xb1R\xab+\x10\x85\xc4\xef\x0e\x15\xfde\xd7O\xffj\xd7R_\xeeߟ=I\x95^\xc1ui\x9dο\xa1եI\xf0#n\xa5\x92Nju\x96\xa3\x13\xa9p\xe2\xea\f@(\xa5\x9d\xa0ϖ\xfe\x04H\xb4rFg\x19\x9a\xd5#\xaa\xf5S\xb9\xc1M)\xb3\x14\r\x03\xaf\xa6\xde\xff\xb4\xfe\x97\xf5Og\x00\x89A\x1e~/s\xb4N\xe4\xc5\x15\xa82\xcb\xce\x00\x94\xc8\xf1\nl\xb2ô\xccЮ\xf7\x98\xa1\xd1k\xa9\xcfl\x81\t\xcd\xf6htY\\A\xf3\x83\x1f\x140\xf1\xab\xb8\v\xe3\xf9S&\xad\xfb\xa5\xf3\xf9\x93\xb4\x8e\u007f*\xb2҈\xac5\x1f\u007f\xb5R=\x96\x990\xcd\xf73\x00\x9b\xe8\x02\xaf\xe03MU\x88\x04\xd33\x80\xb00\x9ez\x05\"M\x99T\"\xfbj\xa4rh\xaeuV\xe6\x15\x89V\x90\xa2M\x8c,\x1c\x93\xe2\xce\tWZ\xd0[p;l\xcfC\xed7\xab\xd5W\xe1vW\xb0\xb6\xdco]섭~\xf5$\xf2\x00\xc2'w ܬ3R=\x0e\xcd\xf6\x01\xae\x8dV\x80\xdf\v\x83\x96P\x86\x949\xab\x1e\xe1y\x87\n\x9c\x06S*F\xe5\xdfD\xf2T\x16\x03\x88\x14\x98\xac{x\x06L\xba\x1f\xe7p\xb9\xdf!d\xc2:p2G\x10aBx\x16\x96q\xd8j\x03n'\xedh\x1ao`%2؋\xac\xc4\v\x10*\x85\\\x1c\xc0 \xcd\x02\xa5j\xc1\xe3.v\r\xff\xa1\r\x82T[}\x05;\xe7\n{uy\xf9(]\xa5b\x13\x9d祒\xeep\xc9\xdaRnJ\xa7\x8d\xbdLq\x8f٥\x95\x8f+a\x92\x9dt\x98\xb8\xd2\xe0\xa5(\xe4\x8aQW\xacf\xd7y\xfa\x0f\x15G\xed\xbb\x0e\xaeG\xfb\xcd7V\x84\x13\x1c \x8d\xe8\x05\xc6\x0f\xf5\xabh\bM\x9f\x88:\xdfn\xee\xee\xdb\xc2$m\x9f\xfaL\xf7\x96\x845, \x82I\xb5Ű\xa3\xb7F\xe7\f\x13UZh\xa9\x1c\xff\x91d\x12U\x9f\xfc\xb6\xdc\xe4\xd2\x11\xdf\u007f/\xd1:\xe2\xd5\x1a\xae\xd9\xee\x90\x1c\x96\x05\xed\xc0t\r\xb7\n\xaeE\x8eٵ\xb0\xf8\xea\f J\xdb\x15\x116\x8e\x05m\x93\xd9\xef\xec\xa9\xd6\xfa\xa12o#\xfc\xaa\xf6\xf8]\x81Ig\xcb\xd08\xb9\x95\to\f֞\xb5\n\xe8iP߆w-\xff\xc2j\xaa\xff\xb5\x87\x87\xd7eլh\xc9~\xb8\x1ds\xb81c$W\x1e\x1a\xe9\x14\xa5\xfb\xdc\x1d҂-J\x04(3\x98t\xb5^\xac};\x82\tAխGp<\xe2*\xff\x84yAjc\x06\xc5\xfbЍP$\xfa\xa4\xb5;U\x19\xfeJ\xcd\xea\xa0]\xe1H\xb9\xf1t;$\xbe\xede\x1a\xb4\xd7\x11Wa\x92\xb3\xd4\x12+\xef\x94(\xecN;\xb2q\xbatC\xbdz\v\xb8\xbe\xbb\xed\rjq\x9e\xb0b\x1bΌv\x1a\x9e\x85<\xe6\xb4o$\x97\xd7w\xb7\xf0@.\x11V0\xc1[rp\xa5Q\xac\x8e\xbf\xa1H\x0f\xf7\xfaW\x8b\x90\x96\xac\x95*\xbb|1\x02x\x83[\xda\xf4\x06\t\x06\r@ch\x0fXFM\x97n\xcd\x0eG\x8a[Qf.(9i\xe1\xfdO\x90KU:<\xe6;L\xf3\xde\x13\x89\xc1\xf9\xd5\xd8{\xfd\xb3\xf5\x8c\x8c \xe9Ǒ\xa1\x03[\xaa\xd0)\xec\xb9\xdf\x18Ue\x86`\x0f\xd6a\x0e\x9b\x00\xa5\xb6\xd5\xcc\x15\xd6\aY\x16\xc0X\xd8\x1c*܇\xd7M^\xb8\xd8dx\x05Δ\xc3\xd3Nm\xdd!\xda|C\xebd\x12A\x99\xf3>i\xfc\xc8\x01\xc2\x18\xfea\x84(=\n\x90\x91\x17O\xe4h\x06\n\x91\xb7\x90e-\xe2\xceS\x05\xe0\xbf\x14|$\x03\x97\x90ٹ\n\xe6Lb\xc6&TiȴzD\xe3g$W\xe1Yf\x19oi\xcc\xf5\xbe\xe3d\xb5\x1b\xd9\x16\x83\x19\x19Iؖdv\xd6@\xb2?*#RY\x87\"]\x9f\xbf\x16\xf3\xf0{\x92\x95)\xa6u\x983\xa8Kz\x8c\xbb9\x1a\xc4\x01\xa1\x90\x8a43\x85_DtU\xff:B\r\xf65\x85A2\x18 \x95\x87I\xa4!E\xb3\x19Q\xd2Ԥ\xc3|\x04\xcfٝ\xbc\x80j\xc2\x18q\x98\xa0Y\x154/!Y=&\xb8b\x99L\x90\x88U;\\L5&\xcd\xc8\xfa\xfe\x1f\x12l\xa7\xf5S\f\x91\xfe\x9d\xfa5\x8e%$|6\x01\x1b܉\xbd\xd4\xc6\xf6\xa3\x13\xfc\x8eI\xe9Fw\x9bp\x90\xca\xed\x16\r\xc1›\x8e\xbf\xa7\x885mV\xa9\x99i\xc6\x1f\xad\xaba:1\x8f\xa91\xb6\x14v_F\xa1\x02#NV\x8fuC*\xf72-E\xc6jB\xa8įO\xd4\xf8\x8d)\xb7\x19\x818\xc2\xdf+\xa3j\x15ĥ\x8eW\xaa\x15\x92ۗk3f\xb7|;\x063N\x86\x8d`gr̅k\x9a)3\xb4\x01\x15o\xfe\x1a\xbds\xd1p\xca\at\x99\xd8`\x06\x163L\x9c6\xe3\xe4\x89\x11\x02\xdfb\xf5\xe7\be\a4i\xd7ߚU\xa2M#\x87l'\x93\x9d7V$e\f\vR\x8d\x965\x86(\x8a\xec0\xb5h\x88\x91\x8c0ٜ\xd2hZ\x84\xfa\xe8\xc3\x1dS$M\x8b\xd4\xc1M\x9b\xd1\xc6]\xaa\xd7b\xf3F\xf4\x0e\x9aꇄ\xfd\xf6h\xf8\xcb\v;\x91[\xa2]\xc3\xed\x160/\xdc\xe1\x02\xa4\xab\xbe\xc6@%W\xb1\xc1\xe3oƸ\xd3v\xcbm\u007f\xf4\x8b\xef\x96\x17\xe1Z\x8d\xc6߄il\xac\ue0adZİO\xed\x91\x17 \xb75\xc3\xd2\v\x8a!\x1d\xb2/5\x87h\xcbљ\xe5\xdcK\x12(\xd6\xf6R˅Kv7\xf51PĈ\x1e\xad\xfa\x00\xbc_^\xc50̃\b\x90P;\x15|\x82)\r\xe6\xfed\xf4\x9e\xf7G\xf3\x85=\xc0\x0f\x9f?b:G2\x88\x97ԣE}\xe8y:m\x14x\x81Q [\x8bb7\xad\x8e\xf1\xfc\xf9\xf7\x05\bx\u0083\xf7\xac\x06\x83ˡF\xac\x155H\x83|\x18\xcfj\xe4\t\x0f\f*\x9c\xaeG\xc1[\"*\xbe=\xe1!\xb6k\x8f\xa8\x84_8\xd7\xf3ԥ\x0f\xbc\x8a\x98\xadԴ\x9a\xa8a\xef\x80\xd3q\x8b\x85eJ\xa9j\x15\xc5O\\vͰΕ\xd2\x13\x1e\xdeY\xcf>\xda5;Y,\xa0\x00)l\xb0\xc8;\xac\xbaKy\x10\x99L\xeb\xc9x\x9f,\x80x\xab.\xe0\xb3v\xf4\x9f\x9b\xef\xd2\x12\x8a*\x85\x8f\x1a\xedg\xed\xf8˫\x92\xd8/\xe2D\x02\xfb\xc1\xbc-\x957\vD\x97E\xf378\xb0\t%\x11\xad\xd9&-\xdc*\x8a\xcf<}\x96\xb0i\x87\x15r\x1e\xad\xbc\xb4|\x1b\xa3\xb4Z\xb1\x99\xaef[\x00\xb4\x8dW`\x956\x1dN],\x848\x88b@\uf7ac\x95\xff\xe5\xe8\x1ek\xaa\x19,2\x91`Z\x9dJ\xf3\xa5\x99p\xf8(\x13\xc8\xd1<\"\x14d7\xe2\x85j\x81&\xf7\xed\x04)\x8cw-\xaa\x16\xcc\xc2\xc0\x1d\xd0P[Ѯ\x8f\xecY\xb19\xaa\xfb\xc8\r\xd9t\xf7\xb8U\xb2yg\u007f(\x8a\xfa픎e\x96e!\xbf\x8e}\x10\x8f\xa4w?r\xc1\xc7\xd6\u007f\x90ye\xf1\xfe3\xce\x1a\ni\xec\x1a>pBK\x86\xed\xf1\xd5)ak\xaa(\x90\x84\x89\xb4@r\xb2\x17\x19\xb9\x0f\xa4\xbc\x15`\xe6\x9d\t\xbd=\xf2\xa0\xe2T\xcc\xf3N[o\xf3\xebc\xf5\xf3'<\x9c_\x1ci\xaf\xf3[u\x1e\a\x93t\xfe\x91Ҫ\xbd\x16\xad\xb2\x03\x9c\xf3o\xe7\xec\x98-\xd9\"'8o\v\xa4:\xba+\xa7\x8e,\t\x05(֮\xbc\x16\x1a\\'X\x90\v?\xb7\x8ah\x99.\xb4\x1d\xb9]\x1cA뫶\xce\x1f\x00v\xdc\xed\x81\x13\u0098\xe8/\x9c\x1a\x82\xd8:4`\x9d6U2\x03\xa9\xdd\xde\x019q\xde\xce\xf3\x9eX]\x9fFz\xc0\x14d\x9e7\x1a\xc2\xeb\xf4s\x9f\xe5@\xff?\x0f3ag\x89a\x17F'h\xed\xbc(EZ\x8e\x99\x03\xdb\xfa\xb0V\xf8\xe0m\x1b\xa5\x9ac\x8e\x92\xab\xb6\xcc\x15'Ҟ\x10\xd8\xdc|o\x9d;\x93\x1a\xa2\xbfcD\xf9\x14\x1c\x81\x13\x1d\xf3\\\xf4\x13k\xa2ѽ\xf6\xa3\xab\r\x18\x80\xf9\x80\xc9<\x96\xacT\x96\xf9\xcdA$\xffj\x8eG.\xd5-O\x04\xef_\xcdY\x81J\x95㩡\xccu5\xbeaH\xfd!6~\x85*=C\xf3]\x8d\xc1\x0eg\x8fo2\xe29\x05\xe4L+\xedڇ5a\xa6w\x16\xb6\xd2X\xd7 \xbc\x00\xaa\xb4|M\xfd\xba1\xa6\xba1\xe6\xe4\x10\xf3\x8b\x1f\xdd:V\xdc\xe9\xe7\x90Դ$\xb0\xae\x88\xbf\x13{\x04\xb9\x05\xe9\x00U\xa2K\xc5\a^\xa4.h\x9a\x05\x10=\x13\xbd1\x89\xb4\x99\xad\xc1\xaa\xcc\xe3\t\xb2b\xe9\x94j\xf6t\xac=\xe4g!\xe3N\xa7\xe04\xb6\xba\xa9ġ\xa1\xd6͆\n\x19D\xed\xec\xb5\\|\x97y\x99\x83ȉ-K\xe2ƭ\xcf=\xaaR\xdd<\xaf\x9f\x85t!\x83\xd8_\xac.Ӧ\x89\u038b\f\x1dVYE\x89VV\xa6X\xbb\x0f\x81\xff\x839ZcM\xc0VȬ4\vt\xf4b\xce,\x8dۂzz\xf9`,\x1e\x91\x15\x133\xf2\xd0}\x81\xd3\x1f\xa8\xc5\xe7\xa5-\xcdF\xab\xf3\x87\xe6\xad\xca\x0f\xe4\xa0-{ 0\x9bo\x16\x834\xc4d\x99\r\xe7\x8f\xcd@]\x92[\x16\x1b\x83G\xe4\x91\xc5g\x8fő\a\xf8\xb5}l\xceX\xb4\xd7\x16\x9b\x1f\xf6:Ya\x91\xb9`\xad\f\xafY\x90'f\x80E\x13,.\xdb+:ǫ\x95\xb95O\xad\x89̮\xe1|\xadY\x90C\xf9\\1YZQ\xb8F\xe7f\xd5\x19W\xf3'\x89?\x94\x91\xf5\xf2\xb9\xdf/\xe9\xe7O\xe7WEeUE\xc5\x02\xf38G\xe5M-͖\x8a\xa2\xea\xd2̨:\xebib\xe2\xa8|\xa8\xe3\\\xa7\xa9\xa5\xccfA\x8dg8M\x81\x1d\xca}\x8a\xc8k\x9a\x00\xd9\xcexZ\xec\x06\xccJ\xd3L\x87\xe1\x8a\x18U\x9b\xb7\xb5\xd9\xff\x85\x04\xfe袵\xe9\xb8\xc01Qɗ\xde\x10\xe2}\xe5\xf5\r\xb9\xd5\xe31\x9ew\xb6Op\xabG@\xden!/3'\x8b\xacU\x92\xc2\xed\xf0P?y\xffM\xf3\xd3\xcb́\xa1}\xf9V\v\xf0\x18\xc8n\x80 ,\xe8\a]?\xfc\xcc%\xfa\x1a\u007f\xd1]R\xec\x15|\xdc5\xcf\xfc\xf3\x95\xc8g+\x91\x97@1\xd8G>OY\xfe,%\x92\xce'\x06[\x93SG>?Y\x14n\x9d\x18pMB\x9czn2\x1drM\x1f\xa7\xf5\x9f\x99\x9c\xe0NDH\xd8l\x97\x1f\xbe\x11\xd0&E3{\xb9\xb2D4g\x85\xb2\x17\x13u\xe7\xefU\x1d\xa8\xaa{Q\xaf\xf6\xc5\xcd\x18wt\xfd\n>\x81_\xa4J=oH\b[\xfeE\xe7\xf6\xa7q~\xc6\xee\x80\xda\x1eg(\x8b鯍,\x16\x82\x14)\x87E|\xbdm\xd7p#\x92]=\xc3\bH\x9ew',l\xb5Ʌ\x83\xf3\xfa>\xee\xd2O@\u007f\x9f\xaf\x01~\xd6\xf5Eh\xab\xca\xcd\bT+\xf3\";P\xf4\x03\xe7m0?&8\xa3\xc2gC\xb9\xbfP\xcf,\"\xfe\xbd\xeb\x8e\x18*7\x19ʺ%\x99.\xd3z\x86\tf\vu\x80\xaf\x0f\xecKq\t\xa8\xa4)\x95\x15<\xa5*\x0e\xeeU\xd2\x1a\x019V5r\x11\xc9Ư\x86\xad\xd3F<\xe2'\xed\v{\xc6Ь;\xa2S\xdb5h\xaa*Q$\xbc\xfc\x1aYYU\x92\xb9\x0f\xb0\xc9\x1f;\xaa5H؎\xa9\xb0\x99]\xee\\\x16\xb1\xb8\xfb\xfbO~AN\xe6\xb8\xfeX\xfak\xfaU!\x8cE\xa2t\xb5P?h3n\xe5v\xfa\x99\x8b\xf5\xb5\xabo\xb6\xea\x1b#\xe7\xabqF\xc0I\xab\xd9w\xea[V\xa4\x8b\x11\xf9\x87\xe1\x91-\xa5\xd2b\xe2\xd4žގ\xc2\x12\xd6\xeaD\xb2\x1e\xe2\x03!N\x13{\xbdBqS\xf6dBe\x94\x16\xbf<+4ߪ\x8djo\xd5Xy\xcd\x0e\t\u007f=\x1a8ZZ\xd3i\xd6~\xbd\xeeCVO\x05\x02Y_\x8a\xb4:ْ\xb6.@{L\xba\x99\xfd?\xbe\xf7\x87=\xd7\xd5p\xcd\xd7U]\x86\xf6,\x82\xb2\xbe\xd4jL\x85a_\x935\x11\x85+M0\xaeIi\xb8j\x1e\x01A_T\xee\xb4\x1a\xc3M\xcd\xf6\x19^6UܛX\u007f\xb6f\xfc\x00\xff\xea*\xc1\xa3es\xbde\xf55\xddW\x04\xff4v\x0e\xee\x03\xae28WQ\x99\xfa\xd4)\xbe\x81\xd0<\xb0\xaaNx7\x86\xfap\xce\xe6\n>\xe3\xf3\xc0\xd7\x1bE\x8b8\xbeQ\U000c9658\xf2\t\xc1P}\xf5\xc9%\xee\xebQ\x9c\x15;\xa0-\xbaj\xae\u05fd\x97nõj\xeb.>\x03v\x88\xad\xff(\xb7\xfe\xf8&\xa15\xfd\xd3Q\x8fQ\xc55\xa9\xb4\xc6\x14\xd6\xe0\x96:\xfah\xd1\xec\xb98l%$\xc1\x86\xb7\xbf\x94\x9b\xa6X$\xfc\xf1\xe7Y\xb3+E\x92`\xe1BZW\xfb߲8\xf7\xc5^\xab\u007f\xaa\x82\xffL\xb4\xf2n\xb6\xbd\x82\xff\xfc\xef3\b\x06\xf8\xa1\xfa\xf7(\xe8\xe3\xff\x06\x00\x00\xff\xff6\x91\a\xfc\xf9c\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VO\x8f\xeb4\x10\xbf\xe7S\x8c\x1e\x87w!\xe9{\xe2\x00\xca\r\x15\x0e+`\xb5\xda>\xed\x05qp\x9di;\xacc\x9b\xf1\xb8K\xf9\xf4\xc8v\xb2m\x93\x94]\x90\xf0-\xf6\xfc\xf9\xcdo\xfed\xaa\xba\xae+\xe5\xe9\t9\x90\xb3-(O\xf8\xa7\xa0M_\xa1y\xfe.4\xe4V\xc7\xcf\xd53ٮ\x85u\f\xe2\xfaG\f.\xb2\xc6\x1fpG\x96\x84\x9c\xadz\x14\xd5)Qm\x05\xa0\xacu\xa2\xd2uH\x9f\x00\xdaYag\fr\xbdG\xdb<\xc7-n#\x99\x0e9\x1b\x1f]\x1f?5\xdf6\x9f*\x00͘տP\x8fAT\xef[\xb0ј\n\xc0\xaa\x1e[\b\xc8II\x94\xc4\xc0\xf8G\xc4 \xa19\xa2Av\r\xb9*x\xd4\xc9\xf1\x9e]\xf4-\x9c\x1f\x8a\xfe\x00\xaa\x04\xb4ɦ6\xd9\xd4c1\x95_\r\x05\xf9\xe9\x96\xc4\xcf4Hy\x13Y\x99e@Y \x1c\x1c\xcb\xfd\xd9i\r!py!\xbb\x8fF\xf1\xa2r\x05\x10\xb4\xf3\xd8B\xd6\xf5JcW\x01\fLe[\xf5\xc0\xc5\xf1s1\xa7\x0fث\xe2\x04\xc0y\xb4\xdf?\xdc=}\xb3\xb9\xba\x06\xe80h&/\x99\xef\x85Ȁ\x02(\x18P\x808PZc\b\xa0#3Z\x81\x82\x12\xc8\xee\x1c\xf79G\xaf\xa6\x01\xd4\xd6E\x019 d\x05\xd9*\x03Ge\"~\r\xcavЫ\x130&/\x10텽,\x12\x1a\xf8\xc51f2[8\x88\xf8ЮV{\x92\xb1\xeb\xb4\xeb\xfbhIN\xab\xdc@\xb4\x8d\xe28\xac:<\xa2Y\x05\xda\u05ca\xf5\x81\x04\xb5Dƕ\xf2Tg\xe86w^\xd3w_\xf1Ч\xe1\xe3\x15V9\xa5\xca\n\xc2d\xf7\x17\x0f\xb9!\xfe!\x03\xa9\x1dJ}\x14\xd5\x12ř\xe8t\x95\xd8y\xfcq\xf3\x05F\xd79\x19S\xf63\xefg\xc5pNA\"\x8c\xec\x0e\xb9$qǮ\xcf6\xd1vޑ-ե\r\xa1\x9d\xd2\x1f\xe2\xb6'\tc\xed\xa6\\5\xb0Σ\b\xb6\b\xd1wJ\xb0k\xe0\xce\xc2Z\xf5h\xd6*\xe0\xff\x9e\x80\xc4t\xa8\x13\xb1\xefK\xc1\xe5\x14\x9d\n\x17\xd6.\x1e\xc61w#_\vݽ\xf1\xa8S\x06\x13\x89I\x9bv\xa4s{\xc0\xce1\xa8%\x95\xe6]H\xb2ƿ\xc42L\x92\x82f2_R\u007f\xbe\x8dfy\x9c䗃\n8\xbd\x9c`zH2S\xff\x86v\xa8O\xda`1Q\xa6\t\xbe\r%\x1d\xb4\xb1\x9f\xfb\xac\xe1\x1e_\x16n\x1fإɚ\xe7\xfa\xf5\xb9Q\x1bP\xfe7{\xb2\xb3p\xa7\x91\x15\xa9\xfc\x0f\xbb\x1c\xd5\x17\x03z0\x04\x1c\xadM};\x9b\x90\x19\xc8t\x92\xcfdH\xb0_@\xb3\x88\xe7\xce\xee\\\xde\x04Tr\xac\xa4\xf4\x13\x0e\xc9\x1e\xfc\x14\\\v\x06o纜\xf9\xf0z\x17\xa1\xe5\xe4?\xe9\u007fSN\xe3\x86\x18\x17}\xd7\x19\xd5\xe2C\xf2\xb8\xc4\xf8r\u007f\r(\xa31jk\xb0\x05\xe18\xd7.\xba\x8aY\x9d\xa6U3\x96\xday\x9fz\xa3\x80f\n\xa9O^\x0ehou\x03\xbc\xa8锿\xf2\f\xdb\xd3-\xd5\xf5\xebr8o\xa9R\xba-\xa4\xd9]\v-p\xf6.R\x16\xb3WJzq\xf3\x98\x11\xb2\xb9\x94\x1dg\xc6Uk\x8c\x8b\xc8<\x86\x9b\x10\x16\x93=\xbb\xcc滋\xf0\x828V\xfb1\xe0\xf3\xe8M\x9b\x9a\x17\xec\xee\xa7+\xee\x87\x0fW\xbbj\xfe\xd4\xcevT6t\xf8\xf5\xb7\xaaX\xc5\xeei\\0\xd3\xe5\xdf\x01\x00\x00\xff\xff\xfb\xb1p\x12\x1b\f\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4WO\x8f۶\x13\xbd\xfbS\f\x92\xc3^\"9\xc1\xef\xf0+t)\x82M\x0fA\xf3g\x11o\xf7R\xf4@\x93#\x8b]\x8aTgHm\xddO_\f)\xad\xbd\xb6\x9cl\x8aV\x17C\x149|\xf3\u07bc!\xbd\xaa\xaaj\xa5\x06{\x87\xc46\xf8\x06\xd4`\xf1ψ^\u07b8\xbe\xff\x81k\x1b\xd6\xe3\x9bս\xf5\xa6\x81\xeb\xc41\xf4_\x90C\"\x8dﰵ\xdeF\x1b\xfc\xaaǨ\x8c\x8a\xaaY\x01(\xefCT2\xcc\xf2\n\xa0\x83\x8f\x14\x9cC\xaav\xe8\xeb\xfb\xb4\xc5m\xb2\xce \xe5\xe0\xf3\xd6\xe3\xeb\xfa\xff\xf5\xeb\x15\x80&\xcc\xcbom\x8f\x1cU?4\xe0\x93s+\x00\xafzl`\f.\xf5\xc8^\r܅\xe8\x82.\x9b\xd5#:\xa4P۰\xe2\x01\xb5콣\x90\x86\x06\x0e\x1fJ\x88\tW\xc9\xe9.G\xdbL\xd1>L\xd1\xf2\x04g9\xfe\xfc\x95I\x1f,\xc7\xe8\xcdK\x9a,\xcaWO\xb0ƽT\x11G\xb2~w\xf4!\x1b\xe1+\n\x88\aJ!\x94\xa5%\x8b\x03\xd12$\xec|\xf9is\v\xf3\xd6Y\x8cS\xf63\uf1c5|\x90@\b\xb3\xbeE*\"\xb6\x14\xfa\x1c\x13\xbd\x19\x82\xf51\xbfhgџ\xd2\xcfi\xdb\xdb(\xba\xff\x91\x90\xa3hU\xc3u\xeeB\xb0EH\x83Q\x11M\r\xef=\\\xab\x1eݵb\xfc\xcf\x05\x10\xa6\xb9\x12b\x9f'\xc1q\x03=\x9d\\X;6\xd8\xd4\xde.\xe8\xb5\xec\xe4̀\xfa\x89\x81$\x8am\xed\xe4\xec6\xd0\t\xafj\xf6\xf9r\xbc\xfa\xc9\xf4e\x83C\xe9\xfe\xadݝ\x8e\x02(c\xf2١\xdc\xcdŵ_!l!\xef뼓\x14j\x1bH\x10\x8d\xd6 Us\x9e\x13\x92DS\xc2\x16\x9d\xe1\xfa,\xe4\x05\xces*\x84F4V\xee\x1c\xe8S$\x8f\x13\xf3᧬/\x94\x1f\x02\xe4ң~\xea\xb1>\xa27\xb9\xa9\x9f\xa1\t\xb9\x86\x19\r<\xd8\xd8\x15s\xb8\xe3C\xeay*\xc8s\x8f\xfb\xa5\xe1\x13\xec\xb7\x1d\xca\xcc\xd2N\x11\x185a\x14\x1c\x8cN\xcc+ά\x01>&\xce\xf6R\x8b\x11AZ\x845\xf3\xea{ܟ\x13\r\xdf\x12w:\xef\xbf\r\xf9J\xce\xc5\x190a\x8b\x84>.Z\\\xee\x1e\xe41bv\xb9\t\x9a\xc5\xe0\x1a\x87\xc8\xeb0\"\x8d\x16\x1f\xd6\x0f\x81\xee\xad\xdfUBxU\n\x81\xd7\xf9ް~\x99\u007f.\xa4|\xfb\xf9\xdd\xe7\x06\xde\x1a\x03!vH\xa2Z\x9b\xdc\\hG\xa7ݫ\xdcq_A\xb2\xe6ǫ\u007f\xc2K\x18\x8as\x9e\xc1\xcd&W\xff^N\xee\fJ(\xda\x14U\x02\x81\xf4M\x11\xbb\x9f\xd4,\xfda\xa9\x10gL\xdb\x10\x1c\xaa\xf3ғ\xeek\t\xcd9\xa4Jv\xf8\x1e\x9b\xcd\xce\xfd\x86\xc9n\xa6ibx\xc9j^6\x17B\xb9\x97\xe4[\x8a\xda\xe1%\xa3/p\xbc\x9cJ\xf5\xb8\xc1\xb3ZtT1\xf1\xf77\xe9\xbcl\x9a\xb9\x9d\x1a\xb5N$\x05=\xc5\\\xb8\xd0\xfc;\x8dz\xe8\x14/\xb8\xed\x19\xa8od\xe5,\x83\xb3-\xea\xbdvX\x02Bh\x17\xaa\xe9\xbb ˃>\xf5K\xa5\xf5vT֩\xadÅo\xbfxu\xf1\xebE\xf1\x17\xf5<\x1bd\xb9\xb5\x98\x06\"\xa5\x12{\xaa\xb2i䠾\xd2\xd2\\\xd0|:\xfd\xdb\xf1\xe2œ\u007f\x0e\xf9U\a_\xceDn\xe0\xd7\xdfV%*\x9a\xbb\xf9\xa2/\x83\u007f\a\x00\x00\xff\xff\xe4\xf3S\x85\xb2\r\x00\x00"), diff --git a/pkg/apis/velero/v1/backup.go b/pkg/apis/velero/v1/backup.go index 6568f830f..ee3ce9340 100644 --- a/pkg/apis/velero/v1/backup.go +++ b/pkg/apis/velero/v1/backup.go @@ -221,7 +221,7 @@ const ( // BackupPhase is a string representation of the lifecycle phase // of a Velero backup. -// +kubebuilder:validation:Enum=New;FailedValidation;InProgress;Completed;PartiallyFailed;Failed;Deleting +// +kubebuilder:validation:Enum=New;FailedValidation;InProgress;WaitingForPluginOperations;WaitingForPluginOperationsPartiallyFailed;Completed;PartiallyFailed;Failed;Deleting type BackupPhase string const ( @@ -236,16 +236,20 @@ const ( // BackupPhaseInProgress means the backup is currently executing. BackupPhaseInProgress BackupPhase = "InProgress" - // BackupPhaseUploading means the backups of Kubernetes resources - // and creation of snapshots was successful and snapshot data - // is currently uploading. The backup is not usable yet. - BackupPhaseUploading BackupPhase = "Uploading" + // BackupPhaseWaitingForPluginOperations means the backup of + // Kubernetes resources, creation of snapshots, and other + // async plugin operations was successful and snapshot data is + // currently uploading or other plugin operations are still + // ongoing. The backup is not usable yet. + BackupPhaseWaitingForPluginOperations BackupPhase = "WaitingForPluginOperations" - // BackupPhaseUploadingPartialFailure means the backup of Kubernetes - // resources and creation of snapshots partially failed (final phase - // will be PartiallyFailed) and snapshot data is currently uploading. - // The backup is not usable yet. - BackupPhaseUploadingPartialFailure BackupPhase = "UploadingPartialFailure" + // BackupPhaseWaitingForPluginOperationsPartiallyFailed means + // the backup of Kubernetes resources, creation of snapshots, + // and other async plugin operations partially failed (final + // phase will be PartiallyFailed) and snapshot data is + // currently uploading or other plugin operations are still + // ongoing. The backup is not usable yet. + BackupPhaseWaitingForPluginOperationsPartiallyFailed BackupPhase = "WaitingForPluginOperationsPartiallyFailed" // BackupPhaseCompleted means the backup has run successfully without // errors. diff --git a/pkg/apis/velero/v1/restore.go b/pkg/apis/velero/v1/restore.go index f36e7348c..9f2fbd58e 100644 --- a/pkg/apis/velero/v1/restore.go +++ b/pkg/apis/velero/v1/restore.go @@ -220,7 +220,7 @@ type InitRestoreHook struct { // RestorePhase is a string representation of the lifecycle phase // of a Velero restore -// +kubebuilder:validation:Enum=New;FailedValidation;InProgress;Completed;PartiallyFailed;Failed +// +kubebuilder:validation:Enum=New;FailedValidation;InProgress;WaitingForPluginOperations;WaitingForPluginOperationsPartiallyFailed;Completed;PartiallyFailed;Failed type RestorePhase string const ( @@ -235,6 +235,19 @@ const ( // RestorePhaseInProgress means the restore is currently executing. RestorePhaseInProgress RestorePhase = "InProgress" + // RestorePhaseWaitingForPluginOperations means the restore of + // Kubernetes resources and other async plugin operations was + // successful and plugin operations are still ongoing. The + // restore is not complete yet. + RestorePhaseWaitingForPluginOperations RestorePhase = "WaitingForPluginOperations" + + // RestorePhaseWaitingForPluginOperationsPartiallyFailed means + // the restore of Kubernetes resources and other async plugin + // operations partially failed (final phase will be + // PartiallyFailed) and other plugin operations are still + // ongoing. The restore is not complete yet. + RestorePhaseWaitingForPluginOperationsPartiallyFailed RestorePhase = "WaitingForPluginOperationsPartiallyFailed" + // RestorePhaseCompleted means the restore has run successfully // without errors. RestorePhaseCompleted RestorePhase = "Completed" diff --git a/site/content/docs/main/api-types/backup.md b/site/content/docs/main/api-types/backup.md index a93da8e27..65bf8104a 100644 --- a/site/content/docs/main/api-types/backup.md +++ b/site/content/docs/main/api-types/backup.md @@ -145,7 +145,9 @@ status: version: 1 # The date and time when the Backup is eligible for garbage collection. expiration: null - # The current phase. Valid values are New, FailedValidation, InProgress, Completed, PartiallyFailed, Failed. + # The current phase. + # Valid values are New, FailedValidation, InProgress, WaitingForPluginOperations, + # WaitingForPluginOperationsPartiallyFailed, Completed, PartiallyFailed, Failed. phase: "" # An array of any validation errors encountered. validationErrors: null diff --git a/site/content/docs/main/api-types/restore.md b/site/content/docs/main/api-types/restore.md index a6495c825..69eb78010 100644 --- a/site/content/docs/main/api-types/restore.md +++ b/site/content/docs/main/api-types/restore.md @@ -179,7 +179,9 @@ spec: onError: Continue # RestoreStatus captures the current status of a Velero restore. Users should not set any data here. status: - # The current phase. Valid values are New, FailedValidation, InProgress, Completed, PartiallyFailed, Failed. + # The current phase. + # Valid values are New, FailedValidation, InProgress, WaitingForPluginOperations, + # WaitingForPluginOperationsPartiallyFailed, Completed, PartiallyFailed, Failed. phase: "" # An array of any validation errors encountered. validationErrors: null diff --git a/site/content/docs/main/api-types/schedule.md b/site/content/docs/main/api-types/schedule.md index 4b759501a..3c2a2d163 100644 --- a/site/content/docs/main/api-types/schedule.md +++ b/site/content/docs/main/api-types/schedule.md @@ -143,7 +143,8 @@ spec: post: # Same content as pre above. status: - # The current phase of the latest scheduled backup. Valid values are New, FailedValidation, InProgress, Completed, PartiallyFailed, Failed. + # The current phase. + # Valid values are New, Enabled, FailedValidation. phase: "" # Date/time of the last backup for a given schedule lastBackup: From 62c00ba841931791075391f3ec646a66e13dc54e Mon Sep 17 00:00:00 2001 From: yanggang Date: Tue, 3 Jan 2023 10:37:19 +0800 Subject: [PATCH 06/10] fix log info : pkg/util/kube/isProvisionedByCSI Signed-off-by: yanggang --- pkg/util/kube/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/util/kube/utils.go b/pkg/util/kube/utils.go index 73603b059..d23597397 100644 --- a/pkg/util/kube/utils.go +++ b/pkg/util/kube/utils.go @@ -187,7 +187,7 @@ func isProvisionedByCSI(log logrus.FieldLogger, pv *corev1api.PersistentVolume, } for _, driver := range list.Items { if driverName == driver.Name || migratedDriver == driver.Name { - log.Debugf("the annotation %s or %s equals to %s indicates the volume is provisioned by a CSI driver", KubeAnnDynamicallyProvisioned, KubeAnnMigratedTo, driverName) + log.Debugf("the annotation %s or %s equals to %s indicates the volume is provisioned by a CSI driver", KubeAnnDynamicallyProvisioned, KubeAnnMigratedTo, driver.Name) return true, nil } } From 95fcd8f63cfe71c7acbb6168c215a115dd134c46 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Jan 2023 11:00:05 +0800 Subject: [PATCH 07/10] Bump github.com/Azure/azure-sdk-for-go (#5709) Bumps [github.com/Azure/azure-sdk-for-go](https://github.com/Azure/azure-sdk-for-go) from 61.4.0+incompatible to 67.2.0+incompatible. - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/v61.4.0...v67.2.0) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8107fe7a0..54e1e44b2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( cloud.google.com/go/storage v1.21.0 github.com/Azure/azure-pipeline-go v0.2.3 - github.com/Azure/azure-sdk-for-go v61.4.0+incompatible + github.com/Azure/azure-sdk-for-go v67.2.0+incompatible github.com/Azure/azure-storage-blob-go v0.14.0 github.com/Azure/go-autorest/autorest v0.11.21 github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 diff --git a/go.sum b/go.sum index e5f0f99e9..7f3beb463 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKu dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= -github.com/Azure/azure-sdk-for-go v61.4.0+incompatible h1:BF2Pm3aQWIa6q9KmxyF1JYKYXtVw67vtvu2Wd54NGuY= -github.com/Azure/azure-sdk-for-go v61.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v67.2.0+incompatible h1:Uu/Ww6ernvPTrpq31kITVTIm/I5jlJ1wjtEH/bmSB2k= +github.com/Azure/azure-sdk-for-go v67.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1 h1:qoVeMsc9/fh/yhxVaA0obYjVH/oI/ihrOoMwsLS9KSA= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3 h1:E+m3SkZCN0Bf5q7YdTs5lSm2CYY3CK4spn5OmUIiQtk= From b10503b351097a4719c42cb4f0f7bab056c71a1b Mon Sep 17 00:00:00 2001 From: Xun Jiang Date: Thu, 5 Jan 2023 17:29:05 +0800 Subject: [PATCH 08/10] Add Trivy nightly scan. Signed-off-by: Xun Jiang --- .github/workflows/nightly-trivy-scan.yml | 36 ++++++++++++++++++++++++ changelogs/unreleased/5740-jxun | 1 + 2 files changed, 37 insertions(+) create mode 100644 .github/workflows/nightly-trivy-scan.yml create mode 100644 changelogs/unreleased/5740-jxun diff --git a/.github/workflows/nightly-trivy-scan.yml b/.github/workflows/nightly-trivy-scan.yml new file mode 100644 index 000000000..f4b2d9694 --- /dev/null +++ b/.github/workflows/nightly-trivy-scan.yml @@ -0,0 +1,36 @@ +name: Trivy Nightly Scan +on: + schedule: + - cron: '0 2 * * *' # run at 2 AM UTC + +jobs: + nightly-scan: + name: Trivy nightly scan + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # maintain the versions of Velero those need security scan + versions: [main] + # list of images that need scan + images: [velero, velero-restore-helper] + permissions: + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + image-ref: 'docker.io/velero/${{ matrix.images }}:${{ matrix.versions }}' + severity: 'CRITICAL,HIGH,MEDIUM' + format: 'template' + template: '@/contrib/sarif.tpl' + output: 'trivy-results.sarif' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: 'trivy-results.sarif' \ No newline at end of file diff --git a/changelogs/unreleased/5740-jxun b/changelogs/unreleased/5740-jxun new file mode 100644 index 000000000..c1e359da9 --- /dev/null +++ b/changelogs/unreleased/5740-jxun @@ -0,0 +1 @@ +Add Trivy nightly scan. \ No newline at end of file From 9a541422571ab1ed6322afc0c4172060f7c2230e Mon Sep 17 00:00:00 2001 From: Scott Seago Date: Thu, 1 Sep 2022 14:36:35 -0400 Subject: [PATCH 09/10] BackupItemAction v2 API implementation Signed-off-by: Scott Seago --- changelogs/unreleased/5442-sseago | 1 + design/biav2-design.md | 4 +- hack/build-image/Dockerfile | 4 + hack/update-2proto.sh | 2 +- pkg/backup/backup.go | 12 +- pkg/backup/backup_test.go | 109 ++- pkg/backup/item_backupper.go | 4 +- pkg/backup/request.go | 2 +- pkg/controller/backup_controller.go | 4 +- pkg/controller/backup_controller_test.go | 12 +- .../v2/restartable_backup_item_action.go | 171 ++++ .../v2/restartable_backup_item_action_test.go | 164 ++++ pkg/plugin/clientmgmt/manager.go | 46 + pkg/plugin/clientmgmt/manager_test.go | 110 +++ .../clientmgmt/process/client_builder.go | 16 +- .../clientmgmt/process/client_builder_test.go | 16 +- pkg/plugin/framework/action_resolver.go | 39 + .../backupitemaction/v2/backup_item_action.go | 45 + .../v2/backup_item_action_client.go | 169 ++++ .../v2/backup_item_action_server.go | 212 +++++ .../v2/backup_item_action_test.go | 202 +++++ pkg/plugin/framework/common/plugin_kinds.go | 8 +- pkg/plugin/framework/server.go | 72 +- pkg/plugin/generated/Shared.pb.go | 239 ++++- .../v2/BackupItemAction.pb.go | 851 ++++++++++++++++++ pkg/plugin/mocks/manager.go | 48 + pkg/plugin/proto/Shared.proto | 15 +- .../v2/BackupItemAction.proto | 50 + .../backupitemaction/v2/backup_item_action.go | 63 ++ .../backupitemaction/v2/BackupItemAction.go | 127 +++ pkg/plugin/velero/shared.go | 28 +- 31 files changed, 2694 insertions(+), 151 deletions(-) create mode 100644 changelogs/unreleased/5442-sseago create mode 100644 pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action.go create mode 100644 pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action_test.go create mode 100644 pkg/plugin/framework/backupitemaction/v2/backup_item_action.go create mode 100644 pkg/plugin/framework/backupitemaction/v2/backup_item_action_client.go create mode 100644 pkg/plugin/framework/backupitemaction/v2/backup_item_action_server.go create mode 100644 pkg/plugin/framework/backupitemaction/v2/backup_item_action_test.go create mode 100644 pkg/plugin/generated/backupitemaction/v2/BackupItemAction.pb.go create mode 100644 pkg/plugin/proto/backupitemaction/v2/BackupItemAction.proto create mode 100644 pkg/plugin/velero/backupitemaction/v2/backup_item_action.go create mode 100644 pkg/plugin/velero/mocks/backupitemaction/v2/BackupItemAction.go diff --git a/changelogs/unreleased/5442-sseago b/changelogs/unreleased/5442-sseago new file mode 100644 index 000000000..ce6c9a01f --- /dev/null +++ b/changelogs/unreleased/5442-sseago @@ -0,0 +1 @@ +BackupItemAction v2 API implementation diff --git a/design/biav2-design.md b/design/biav2-design.md index 4294e53cc..a638b6b14 100644 --- a/design/biav2-design.md +++ b/design/biav2-design.md @@ -69,8 +69,8 @@ One new shared message type will be added, as this will also be needed for v2 Re message OperationProgress { bool completed = 1; string err = 2; - int64 completed = 3; - int64 total = 4; + int64 nCompleted = 3; + int64 nTotal = 4; string operationUnits = 5; string description = 6; google.protobuf.Timestamp started = 7; diff --git a/hack/build-image/Dockerfile b/hack/build-image/Dockerfile index c543a932a..50997c659 100644 --- a/hack/build-image/Dockerfile +++ b/hack/build-image/Dockerfile @@ -48,6 +48,10 @@ RUN apt-get update && apt-get install -y unzip RUN wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protoc-3.14.0-linux-x86_64.zip && \ unzip protoc-3.14.0-linux-x86_64.zip && \ mv bin/protoc /usr/bin/protoc && \ + mv include/google /usr/include && \ + chmod a+x /usr/include/google && \ + chmod a+x /usr/include/google/protobuf && \ + chmod a+r -R /usr/include/google && \ chmod +x /usr/bin/protoc RUN go install github.com/golang/protobuf/protoc-gen-go@v1.4.3 diff --git a/hack/update-2proto.sh b/hack/update-2proto.sh index 43afa8d4c..8ad696e16 100755 --- a/hack/update-2proto.sh +++ b/hack/update-2proto.sh @@ -19,6 +19,6 @@ HACK_DIR=$(dirname "${BASH_SOURCE}") echo "Updating plugin proto" echo protoc --version -protoc pkg/plugin/proto/*.proto --go_out=plugins=grpc:pkg/plugin/generated/ --go_opt=module=github.com/vmware-tanzu/velero/pkg/plugin/generated -I pkg/plugin/proto/ +protoc pkg/plugin/proto/*.proto pkg/plugin/proto/*/*/*.proto --go_out=plugins=grpc:pkg/plugin/generated/ --go_opt=module=github.com/vmware-tanzu/velero/pkg/plugin/generated -I pkg/plugin/proto/ -I /usr/include echo "Updating plugin proto - done!" diff --git a/pkg/backup/backup.go b/pkg/backup/backup.go index cdb86bf65..9db76b1b5 100644 --- a/pkg/backup/backup.go +++ b/pkg/backup/backup.go @@ -44,7 +44,7 @@ import ( velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1" "github.com/vmware-tanzu/velero/pkg/kuberesource" "github.com/vmware-tanzu/velero/pkg/plugin/framework" - biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1" "github.com/vmware-tanzu/velero/pkg/podexec" "github.com/vmware-tanzu/velero/pkg/podvolume" @@ -63,9 +63,9 @@ const BackupFormatVersion = "1.1.0" type Backupper interface { // Backup takes a backup using the specification in the velerov1api.Backup and writes backup and log data // to the given writers. - Backup(logger logrus.FieldLogger, backup *Request, backupFile io.Writer, actions []biav1.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error + Backup(logger logrus.FieldLogger, backup *Request, backupFile io.Writer, actions []biav2.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error BackupWithResolvers(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer, - backupItemActionResolver framework.BackupItemActionResolver, itemSnapshotterResolver framework.ItemSnapshotterResolver, + backupItemActionResolver framework.BackupItemActionResolverV2, itemSnapshotterResolver framework.ItemSnapshotterResolver, volumeSnapshotterGetter VolumeSnapshotterGetter) error } @@ -174,8 +174,8 @@ type VolumeSnapshotterGetter interface { // back up individual resources that don't prevent the backup from continuing to be processed) are logged // to the backup log. func (kb *kubernetesBackupper) Backup(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer, - actions []biav1.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error { - backupItemActions := framework.NewBackupItemActionResolver(actions) + actions []biav2.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error { + backupItemActions := framework.NewBackupItemActionResolverV2(actions) itemSnapshotters := framework.NewItemSnapshotterResolver(nil) return kb.BackupWithResolvers(log, backupRequest, backupFile, backupItemActions, itemSnapshotters, volumeSnapshotterGetter) @@ -184,7 +184,7 @@ func (kb *kubernetesBackupper) Backup(log logrus.FieldLogger, backupRequest *Req func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer, - backupItemActionResolver framework.BackupItemActionResolver, + backupItemActionResolver framework.BackupItemActionResolverV2, itemSnapshotterResolver framework.ItemSnapshotterResolver, volumeSnapshotterGetter VolumeSnapshotterGetter) error { gzippedData := gzip.NewWriter(backupFile) diff --git a/pkg/backup/backup_test.go b/pkg/backup/backup_test.go index 56cbfe214..aae4dfecd 100644 --- a/pkg/backup/backup_test.go +++ b/pkg/backup/backup_test.go @@ -47,7 +47,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/discovery" "github.com/vmware-tanzu/velero/pkg/kuberesource" "github.com/vmware-tanzu/velero/pkg/plugin/velero" - biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1" "github.com/vmware-tanzu/velero/pkg/podvolume" "github.com/vmware-tanzu/velero/pkg/test" @@ -1139,23 +1139,32 @@ type recordResourcesAction struct { ids []string backups []velerov1.Backup additionalItems []velero.ResourceIdentifier + operationID string } -func (a *recordResourcesAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { +func (a *recordResourcesAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { metadata, err := meta.Accessor(item) if err != nil { - return item, a.additionalItems, err + return item, a.additionalItems, a.operationID, err } a.ids = append(a.ids, kubeutil.NamespaceAndName(metadata)) a.backups = append(a.backups, *backup) - return item, a.additionalItems, nil + return item, a.additionalItems, a.operationID, nil } func (a *recordResourcesAction) AppliesTo() (velero.ResourceSelector, error) { return a.selector, nil } +func (a *recordResourcesAction) Progress(operationID string, backup *velerov1.Backup) (velero.OperationProgress, error) { + return velero.OperationProgress{}, nil +} + +func (a *recordResourcesAction) Cancel(operationID string, backup *velerov1.Backup) error { + return nil +} + func (a *recordResourcesAction) ForResource(resource string) *recordResourcesAction { a.selector.IncludedResources = append(a.selector.IncludedResources, resource) return a @@ -1362,7 +1371,7 @@ func TestBackupActionsRunForCorrectItems(t *testing.T) { h.addItems(t, resource) } - actions := []biav1.BackupItemAction{} + actions := []biav2.BackupItemAction{} for action := range tc.actions { actions = append(actions, action) } @@ -1388,7 +1397,7 @@ func TestBackupWithInvalidActions(t *testing.T) { name string backup *velerov1.Backup apiResources []*test.APIResource - actions []biav1.BackupItemAction + actions []biav2.BackupItemAction }{ { name: "action with invalid label selector results in an error", @@ -1404,7 +1413,7 @@ func TestBackupWithInvalidActions(t *testing.T) { builder.ForPersistentVolume("baz").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ new(recordResourcesAction).ForLabelSelector("=invalid-selector"), }, }, @@ -1422,7 +1431,7 @@ func TestBackupWithInvalidActions(t *testing.T) { builder.ForPersistentVolume("baz").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &appliesToErrorAction{}, }, }, @@ -1453,7 +1462,15 @@ func (a *appliesToErrorAction) AppliesTo() (velero.ResourceSelector, error) { return velero.ResourceSelector{}, errors.New("error calling AppliesTo") } -func (a *appliesToErrorAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { +func (a *appliesToErrorAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { + panic("not implemented") +} + +func (a *appliesToErrorAction) Progress(operationID string, backup *velerov1.Backup) (velero.OperationProgress, error) { + panic("not implemented") +} + +func (a *appliesToErrorAction) Cancel(operationID string, backup *velerov1.Backup) error { panic("not implemented") } @@ -1466,16 +1483,16 @@ func TestBackupActionModifications(t *testing.T) { // method modifies the item being passed in by calling the 'modify' function on it. modifyingActionGetter := func(modify func(*unstructured.Unstructured)) *pluggableAction { return &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { obj, ok := item.(*unstructured.Unstructured) if !ok { - return nil, nil, errors.Errorf("unexpected type %T", item) + return nil, nil, "", errors.Errorf("unexpected type %T", item) } res := obj.DeepCopy() modify(res) - return res, nil, nil + return res, nil, "", nil }, } } @@ -1484,7 +1501,7 @@ func TestBackupActionModifications(t *testing.T) { name string backup *velerov1.Backup apiResources []*test.APIResource - actions []biav1.BackupItemAction + actions []biav2.BackupItemAction want map[string]unstructuredObject }{ { @@ -1495,7 +1512,7 @@ func TestBackupActionModifications(t *testing.T) { builder.ForPod("ns-1", "pod-1").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ modifyingActionGetter(func(item *unstructured.Unstructured) { item.SetLabels(map[string]string{"updated": "true"}) }), @@ -1512,7 +1529,7 @@ func TestBackupActionModifications(t *testing.T) { builder.ForPod("ns-1", "pod-1").ObjectMeta(builder.WithLabels("should-be-removed", "true")).Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ modifyingActionGetter(func(item *unstructured.Unstructured) { item.SetLabels(nil) }), @@ -1529,7 +1546,7 @@ func TestBackupActionModifications(t *testing.T) { builder.ForPod("ns-1", "pod-1").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ modifyingActionGetter(func(item *unstructured.Unstructured) { item.Object["spec"].(map[string]interface{})["nodeName"] = "foo" }), @@ -1547,7 +1564,7 @@ func TestBackupActionModifications(t *testing.T) { builder.ForPod("ns-1", "pod-1").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ modifyingActionGetter(func(item *unstructured.Unstructured) { item.SetName(item.GetName() + "-updated") item.SetNamespace(item.GetNamespace() + "-updated") @@ -1588,7 +1605,7 @@ func TestBackupActionAdditionalItems(t *testing.T) { name string backup *velerov1.Backup apiResources []*test.APIResource - actions []biav1.BackupItemAction + actions []biav2.BackupItemAction want []string }{ { @@ -1601,16 +1618,16 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPod("ns-3", "pod-3").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ selector: velero.ResourceSelector{IncludedNamespaces: []string{"ns-1"}}, - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.Pods, Namespace: "ns-2", Name: "pod-2"}, {GroupResource: kuberesource.Pods, Namespace: "ns-3", Name: "pod-3"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1633,15 +1650,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPod("ns-3", "pod-3").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.Pods, Namespace: "ns-2", Name: "pod-2"}, {GroupResource: kuberesource.Pods, Namespace: "ns-3", Name: "pod-3"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1663,15 +1680,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPersistentVolume("pv-2").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.PersistentVolumes, Name: "pv-1"}, {GroupResource: kuberesource.PersistentVolumes, Name: "pv-2"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1696,15 +1713,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPersistentVolume("pv-2").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.PersistentVolumes, Name: "pv-1"}, {GroupResource: kuberesource.PersistentVolumes, Name: "pv-2"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1726,15 +1743,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPersistentVolume("pv-2").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.PersistentVolumes, Name: "pv-1"}, {GroupResource: kuberesource.PersistentVolumes, Name: "pv-2"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1757,15 +1774,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPersistentVolume("pv-2").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.PersistentVolumes, Name: "pv-1"}, {GroupResource: kuberesource.PersistentVolumes, Name: "pv-2"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1787,16 +1804,16 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPod("ns-3", "pod-3").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ selector: velero.ResourceSelector{IncludedNamespaces: []string{"ns-1"}}, - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.Pods, Namespace: "ns-4", Name: "pod-4"}, {GroupResource: kuberesource.Pods, Namespace: "ns-5", Name: "pod-5"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -2727,12 +2744,12 @@ func TestBackupWithPodVolume(t *testing.T) { // function body at runtime. type pluggableAction struct { selector velero.ResourceSelector - executeFunc func(runtime.Unstructured, *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) + executeFunc func(runtime.Unstructured, *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) } -func (a *pluggableAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { +func (a *pluggableAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { if a.executeFunc == nil { - return item, nil, nil + return item, nil, "", nil } return a.executeFunc(item, backup) @@ -2742,6 +2759,14 @@ func (a *pluggableAction) AppliesTo() (velero.ResourceSelector, error) { return a.selector, nil } +func (a *pluggableAction) Progress(operationID string, backup *velerov1.Backup) (velero.OperationProgress, error) { + return velero.OperationProgress{}, nil +} + +func (a *pluggableAction) Cancel(operationID string, backup *velerov1.Backup) error { + return nil +} + type harness struct { *test.APIServer backupper *kubernetesBackupper diff --git a/pkg/backup/item_backupper.go b/pkg/backup/item_backupper.go index aefbb85ce..efa4303ae 100644 --- a/pkg/backup/item_backupper.go +++ b/pkg/backup/item_backupper.go @@ -320,7 +320,9 @@ func (ib *itemBackupper) executeActions( } log.Info("Executing custom action") - updatedItem, additionalItemIdentifiers, err := action.Execute(obj, ib.backupRequest.Backup) + // Note: we're ignoring the operationID returned from Execute for now, it will be used + // with the async plugin action implementation + updatedItem, additionalItemIdentifiers, _, err := action.Execute(obj, ib.backupRequest.Backup) if err != nil { return nil, errors.Wrapf(err, "error executing custom action (groupResource=%s, namespace=%s, name=%s)", groupResource.String(), namespace, name) } diff --git a/pkg/backup/request.go b/pkg/backup/request.go index 69dbeca70..a94faa2d7 100644 --- a/pkg/backup/request.go +++ b/pkg/backup/request.go @@ -45,7 +45,7 @@ type Request struct { NamespaceIncludesExcludes *collections.IncludesExcludes ResourceIncludesExcludes *collections.IncludesExcludes ResourceHooks []hook.ResourceHook - ResolvedActions []framework.BackupItemResolvedAction + ResolvedActions []framework.BackupItemResolvedActionV2 ResolvedItemSnapshotters []framework.ItemSnapshotterResolvedAction VolumeSnapshots []*volume.Snapshot PodVolumeBackups []*velerov1api.PodVolumeBackup diff --git a/pkg/controller/backup_controller.go b/pkg/controller/backup_controller.go index 2765d076a..67874c7e5 100644 --- a/pkg/controller/backup_controller.go +++ b/pkg/controller/backup_controller.go @@ -620,7 +620,7 @@ func (c *backupController) runBackup(backup *pkgbackup.Request) error { defer pluginManager.CleanupClients() backupLog.Info("Getting backup item actions") - actions, err := pluginManager.GetBackupItemActions() + actions, err := pluginManager.GetBackupItemActionsV2() if err != nil { return err } @@ -645,7 +645,7 @@ func (c *backupController) runBackup(backup *pkgbackup.Request) error { return errors.Errorf("backup already exists in object storage") } - backupItemActionsResolver := framework.NewBackupItemActionResolver(actions) + backupItemActionsResolver := framework.NewBackupItemActionResolverV2(actions) itemSnapshottersResolver := framework.NewItemSnapshotterResolver(itemSnapshotters) var fatalErrs []error diff --git a/pkg/controller/backup_controller_test.go b/pkg/controller/backup_controller_test.go index 696397b72..56d31ab90 100644 --- a/pkg/controller/backup_controller_test.go +++ b/pkg/controller/backup_controller_test.go @@ -53,7 +53,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt" "github.com/vmware-tanzu/velero/pkg/plugin/framework" pluginmocks "github.com/vmware-tanzu/velero/pkg/plugin/mocks" - biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" velerotest "github.com/vmware-tanzu/velero/pkg/test" "github.com/vmware-tanzu/velero/pkg/util/boolptr" "github.com/vmware-tanzu/velero/pkg/util/logging" @@ -63,13 +63,13 @@ type fakeBackupper struct { mock.Mock } -func (b *fakeBackupper) Backup(logger logrus.FieldLogger, backup *pkgbackup.Request, backupFile io.Writer, actions []biav1.BackupItemAction, volumeSnapshotterGetter pkgbackup.VolumeSnapshotterGetter) error { +func (b *fakeBackupper) Backup(logger logrus.FieldLogger, backup *pkgbackup.Request, backupFile io.Writer, actions []biav2.BackupItemAction, volumeSnapshotterGetter pkgbackup.VolumeSnapshotterGetter) error { args := b.Called(logger, backup, backupFile, actions, volumeSnapshotterGetter) return args.Error(0) } func (b *fakeBackupper) BackupWithResolvers(logger logrus.FieldLogger, backup *pkgbackup.Request, backupFile io.Writer, - backupItemActionResolver framework.BackupItemActionResolver, itemSnapshotterResolver framework.ItemSnapshotterResolver, + backupItemActionResolver framework.BackupItemActionResolverV2, itemSnapshotterResolver framework.ItemSnapshotterResolver, volumeSnapshotterGetter pkgbackup.VolumeSnapshotterGetter) error { args := b.Called(logger, backup, backupFile, backupItemActionResolver, itemSnapshotterResolver, volumeSnapshotterGetter) return args.Error(0) @@ -1070,11 +1070,11 @@ func TestProcessBackupCompletions(t *testing.T) { formatFlag: formatFlag, } - pluginManager.On("GetBackupItemActions").Return(nil, nil) + pluginManager.On("GetBackupItemActionsV2").Return(nil, nil) pluginManager.On("CleanupClients").Return(nil) pluginManager.On("GetItemSnapshotters").Return(nil, nil) - backupper.On("Backup", mock.Anything, mock.Anything, mock.Anything, []biav1.BackupItemAction(nil), pluginManager).Return(nil) - backupper.On("BackupWithResolvers", mock.Anything, mock.Anything, mock.Anything, framework.BackupItemActionResolver{}, framework.ItemSnapshotterResolver{}, pluginManager).Return(nil) + backupper.On("Backup", mock.Anything, mock.Anything, mock.Anything, []biav2.BackupItemAction(nil), pluginManager).Return(nil) + backupper.On("BackupWithResolvers", mock.Anything, mock.Anything, mock.Anything, framework.BackupItemActionResolverV2{}, framework.ItemSnapshotterResolver{}, pluginManager).Return(nil) backupStore.On("BackupExists", test.backupLocation.Spec.StorageType.ObjectStorage.Bucket, test.backup.Name).Return(test.backupExists, test.existenceCheckError) // Ensure we have a CompletionTimestamp when uploading and that the backup name matches the backup in the object store. diff --git a/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action.go b/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action.go new file mode 100644 index 000000000..b3739796f --- /dev/null +++ b/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action.go @@ -0,0 +1,171 @@ +/* +Copyright 2018 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/runtime" + + api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + biav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v1" + "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process" + "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" +) + +// AdaptedBackupItemAction is a v1 BackupItemAction adapted to implement the v2 API +type AdaptedBackupItemAction struct { + Kind common.PluginKind + + // Get returns a restartable BackupItemAction for the given name and process, wrapping if necessary + GetRestartable func(name string, restartableProcess process.RestartableProcess) biav2.BackupItemAction +} + +func AdaptedBackupItemActions() []AdaptedBackupItemAction { + return []AdaptedBackupItemAction{ + { + Kind: common.PluginKindBackupItemActionV2, + GetRestartable: func(name string, restartableProcess process.RestartableProcess) biav2.BackupItemAction { + return NewRestartableBackupItemAction(name, restartableProcess) + }, + }, + { + Kind: common.PluginKindBackupItemAction, + GetRestartable: func(name string, restartableProcess process.RestartableProcess) biav2.BackupItemAction { + return NewAdaptedV1RestartableBackupItemAction(biav1cli.NewRestartableBackupItemAction(name, restartableProcess)) + }, + }, + } +} + +// restartableBackupItemAction is a backup item action for a given implementation (such as "pod"). It is associated with +// a restartableProcess, which may be shared and used to run multiple plugins. At the beginning of each method +// call, the restartableBackupItemAction asks its restartableProcess to restart itself if needed (e.g. if the +// process terminated for any reason), then it proceeds with the actual call. +type RestartableBackupItemAction struct { + Key process.KindAndName + SharedPluginProcess process.RestartableProcess +} + +// NewRestartableBackupItemAction returns a new RestartableBackupItemAction. +func NewRestartableBackupItemAction(name string, sharedPluginProcess process.RestartableProcess) *RestartableBackupItemAction { + r := &RestartableBackupItemAction{ + Key: process.KindAndName{Kind: common.PluginKindBackupItemActionV2, Name: name}, + SharedPluginProcess: sharedPluginProcess, + } + return r +} + +// getBackupItemAction returns the backup item action for this restartableBackupItemAction. It does *not* restart the +// plugin process. +func (r *RestartableBackupItemAction) getBackupItemAction() (biav2.BackupItemAction, error) { + plugin, err := r.SharedPluginProcess.GetByKindAndName(r.Key) + if err != nil { + return nil, err + } + + backupItemAction, ok := plugin.(biav2.BackupItemAction) + if !ok { + return nil, errors.Errorf("%T (returned for %v) is not a BackupItemActionV2!", plugin, r.Key) + } + + return backupItemAction, nil +} + +// getDelegate restarts the plugin process (if needed) and returns the backup item action for this restartableBackupItemAction. +func (r *RestartableBackupItemAction) getDelegate() (biav2.BackupItemAction, error) { + if err := r.SharedPluginProcess.ResetIfNeeded(); err != nil { + return nil, err + } + + return r.getBackupItemAction() +} + +// AppliesTo restarts the plugin's process if needed, then delegates the call. +func (r *RestartableBackupItemAction) AppliesTo() (velero.ResourceSelector, error) { + delegate, err := r.getDelegate() + if err != nil { + return velero.ResourceSelector{}, err + } + + return delegate.AppliesTo() +} + +// Execute restarts the plugin's process if needed, then delegates the call. +func (r *RestartableBackupItemAction) Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { + delegate, err := r.getDelegate() + if err != nil { + return nil, nil, "", err + } + + return delegate.Execute(item, backup) +} + +// Progress restarts the plugin's process if needed, then delegates the call. +func (r *RestartableBackupItemAction) Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error) { + delegate, err := r.getDelegate() + if err != nil { + return velero.OperationProgress{}, err + } + + return delegate.Progress(operationID, backup) +} + +// Cancel restarts the plugin's process if needed, then delegates the call. +func (r *RestartableBackupItemAction) Cancel(operationID string, backup *api.Backup) error { + delegate, err := r.getDelegate() + if err != nil { + return err + } + + return delegate.Cancel(operationID, backup) +} + +type AdaptedV1RestartableBackupItemAction struct { + V1Restartable *biav1cli.RestartableBackupItemAction +} + +// NewAdaptedV1RestartableBackupItemAction returns a new v1 RestartableBackupItemAction adapted to v2 +func NewAdaptedV1RestartableBackupItemAction(v1Restartable *biav1cli.RestartableBackupItemAction) *AdaptedV1RestartableBackupItemAction { + r := &AdaptedV1RestartableBackupItemAction{ + V1Restartable: v1Restartable, + } + return r +} + +// AppliesTo delegates to the v1 AppliesTo call. +func (r *AdaptedV1RestartableBackupItemAction) AppliesTo() (velero.ResourceSelector, error) { + return r.V1Restartable.AppliesTo() +} + +// Execute delegates to the v1 Execute call, returning an empty operationID. +func (r *AdaptedV1RestartableBackupItemAction) Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { + updatedItem, additionalItems, err := r.V1Restartable.Execute(item, backup) + return updatedItem, additionalItems, "", err +} + +// Progress returns with an error since v1 plugins will never return an operationID, which means that +// any operationID passed in here will be invalid. +func (r *AdaptedV1RestartableBackupItemAction) Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error) { + return velero.OperationProgress{}, biav2.AsyncOperationsNotSupportedError() +} + +// Cancel just returns without error since v1 plugins don't implement it. +func (r *AdaptedV1RestartableBackupItemAction) Cancel(operationID string, backup *api.Backup) error { + return nil +} diff --git a/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action_test.go b/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action_test.go new file mode 100644 index 000000000..24e3c12a6 --- /dev/null +++ b/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action_test.go @@ -0,0 +1,164 @@ +/* +Copyright 2018 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/vmware-tanzu/velero/internal/restartabletest" + v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process" + "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" + mocksv2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/mocks/backupitemaction/v2" +) + +func TestRestartableGetBackupItemAction(t *testing.T) { + tests := []struct { + name string + plugin interface{} + getError error + expectedError string + }{ + { + name: "error getting by kind and name", + getError: errors.Errorf("get error"), + expectedError: "get error", + }, + { + name: "wrong type", + plugin: 3, + expectedError: "int (returned for {BackupItemActionV2 pod}) is not a BackupItemActionV2!", + }, + { + name: "happy path", + plugin: new(mocksv2.BackupItemAction), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := new(restartabletest.MockRestartableProcess) + defer p.AssertExpectations(t) + + name := "pod" + key := process.KindAndName{Kind: common.PluginKindBackupItemActionV2, Name: name} + p.On("GetByKindAndName", key).Return(tc.plugin, tc.getError) + + r := NewRestartableBackupItemAction(name, p) + a, err := r.getBackupItemAction() + if tc.expectedError != "" { + assert.EqualError(t, err, tc.expectedError) + return + } + require.NoError(t, err) + + assert.Equal(t, tc.plugin, a) + }) + } +} + +func TestRestartableBackupItemActionGetDelegate(t *testing.T) { + p := new(restartabletest.MockRestartableProcess) + defer p.AssertExpectations(t) + + // Reset error + p.On("ResetIfNeeded").Return(errors.Errorf("reset error")).Once() + name := "pod" + r := NewRestartableBackupItemAction(name, p) + a, err := r.getDelegate() + assert.Nil(t, a) + assert.EqualError(t, err, "reset error") + + // Happy path + p.On("ResetIfNeeded").Return(nil) + expected := new(mocksv2.BackupItemAction) + key := process.KindAndName{Kind: common.PluginKindBackupItemActionV2, Name: name} + p.On("GetByKindAndName", key).Return(expected, nil) + + a, err = r.getDelegate() + assert.NoError(t, err) + assert.Equal(t, expected, a) +} + +func TestRestartableBackupItemActionDelegatedFunctions(t *testing.T) { + b := new(v1.Backup) + + pv := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "color": "blue", + }, + } + + oid := "operation1" + + pvToReturn := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "color": "green", + }, + } + + additionalItems := []velero.ResourceIdentifier{ + { + GroupResource: schema.GroupResource{Group: "velero.io", Resource: "backups"}, + }, + } + + restartabletest.RunRestartableDelegateTests( + t, + common.PluginKindBackupItemAction, + func(key process.KindAndName, p process.RestartableProcess) interface{} { + return &RestartableBackupItemAction{ + Key: key, + SharedPluginProcess: p, + } + }, + func() restartabletest.Mockable { + return new(mocksv2.BackupItemAction) + }, + restartabletest.RestartableDelegateTest{ + Function: "AppliesTo", + Inputs: []interface{}{}, + ExpectedErrorOutputs: []interface{}{velero.ResourceSelector{}, errors.Errorf("reset error")}, + ExpectedDelegateOutputs: []interface{}{velero.ResourceSelector{IncludedNamespaces: []string{"a"}}, errors.Errorf("delegate error")}, + }, + restartabletest.RestartableDelegateTest{ + Function: "Execute", + Inputs: []interface{}{pv, b}, + ExpectedErrorOutputs: []interface{}{nil, ([]velero.ResourceIdentifier)(nil), "", errors.Errorf("reset error")}, + ExpectedDelegateOutputs: []interface{}{pvToReturn, additionalItems, "", errors.Errorf("delegate error")}, + }, + restartabletest.RestartableDelegateTest{ + Function: "Progress", + Inputs: []interface{}{oid, b}, + ExpectedErrorOutputs: []interface{}{velero.OperationProgress{}, errors.Errorf("reset error")}, + ExpectedDelegateOutputs: []interface{}{velero.OperationProgress{}, errors.Errorf("delegate error")}, + }, + restartabletest.RestartableDelegateTest{ + Function: "Cancel", + Inputs: []interface{}{oid, b}, + ExpectedErrorOutputs: []interface{}{errors.Errorf("reset error")}, + ExpectedDelegateOutputs: []interface{}{errors.Errorf("delegate error")}, + }, + ) +} diff --git a/pkg/plugin/clientmgmt/manager.go b/pkg/plugin/clientmgmt/manager.go index b94d986fa..4e3343d6a 100644 --- a/pkg/plugin/clientmgmt/manager.go +++ b/pkg/plugin/clientmgmt/manager.go @@ -25,12 +25,14 @@ import ( "github.com/sirupsen/logrus" biav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v1" + biav2cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v2" "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process" riav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/restoreitemaction/v1" vsv1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/volumesnapshotter/v1" "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" "github.com/vmware-tanzu/velero/pkg/plugin/velero" biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1" riav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v1" vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1" @@ -50,6 +52,12 @@ type Manager interface { // GetBackupItemAction returns the backup item action plugin for name. GetBackupItemAction(name string) (biav1.BackupItemAction, error) + // GetBackupItemActionsV2 returns all v2 backup item action plugins (including those adapted from v1). + GetBackupItemActionsV2() ([]biav2.BackupItemAction, error) + + // GetBackupItemActionV2 returns the backup item action plugin for name. + GetBackupItemActionV2(name string) (biav2.BackupItemAction, error) + // GetRestoreItemActions returns all restore item action plugins. GetRestoreItemActions() ([]riav1.RestoreItemAction, error) @@ -218,6 +226,44 @@ func (m *manager) GetBackupItemAction(name string) (biav1.BackupItemAction, erro return nil, fmt.Errorf("unable to get valid BackupItemAction for %q", name) } +// GetBackupItemActionsV2 returns all v2 backup item actions as RestartableBackupItemActions. +func (m *manager) GetBackupItemActionsV2() ([]biav2.BackupItemAction, error) { + list := m.registry.List(common.PluginKindBackupItemActionV2) + + actions := make([]biav2.BackupItemAction, 0, len(list)) + + for i := range list { + id := list[i] + + r, err := m.GetBackupItemActionV2(id.Name) + if err != nil { + return nil, err + } + + actions = append(actions, r) + } + + return actions, nil +} + +// GetBackupItemActionV2 returns a v2 restartableBackupItemAction for name. +func (m *manager) GetBackupItemActionV2(name string) (biav2.BackupItemAction, error) { + name = sanitizeName(name) + + for _, adaptedBackupItemAction := range biav2cli.AdaptedBackupItemActions() { + restartableProcess, err := m.getRestartableProcess(adaptedBackupItemAction.Kind, name) + // Check if plugin was not found + if errors.As(err, &pluginNotFoundErrType) { + continue + } + if err != nil { + return nil, err + } + return adaptedBackupItemAction.GetRestartable(name, restartableProcess), nil + } + return nil, fmt.Errorf("unable to get valid BackupItemActionV2 for %q", name) +} + // GetRestoreItemActions returns all restore item actions as restartableRestoreItemActions. func (m *manager) GetRestoreItemActions() ([]riav1.RestoreItemAction, error) { list := m.registry.List(common.PluginKindRestoreItemAction) diff --git a/pkg/plugin/clientmgmt/manager_test.go b/pkg/plugin/clientmgmt/manager_test.go index 17b51a52b..541613c73 100644 --- a/pkg/plugin/clientmgmt/manager_test.go +++ b/pkg/plugin/clientmgmt/manager_test.go @@ -28,6 +28,7 @@ import ( "github.com/vmware-tanzu/velero/internal/restartabletest" biav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v1" + biav2cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v2" "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process" riav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/restoreitemaction/v1" vsv1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/volumesnapshotter/v1" @@ -203,6 +204,23 @@ func TestGetBackupItemAction(t *testing.T) { ) } +func TestGetBackupItemActionV2(t *testing.T) { + getPluginTest(t, + common.PluginKindBackupItemActionV2, + "velero.io/pod", + func(m Manager, name string) (interface{}, error) { + return m.GetBackupItemActionV2(name) + }, + func(name string, sharedPluginProcess process.RestartableProcess) interface{} { + return &biav2cli.RestartableBackupItemAction{ + Key: process.KindAndName{Kind: common.PluginKindBackupItemActionV2, Name: name}, + SharedPluginProcess: sharedPluginProcess, + } + }, + false, + ) +} + func TestGetRestoreItemAction(t *testing.T) { getPluginTest(t, common.PluginKindRestoreItemAction, @@ -363,6 +381,98 @@ func TestGetBackupItemActions(t *testing.T) { } } +func TestGetBackupItemActionsV2(t *testing.T) { + tests := []struct { + name string + names []string + newRestartableProcessError error + expectedError string + }{ + { + name: "No items", + names: []string{}, + }, + { + name: "Error getting restartable process", + names: []string{"velero.io/a", "velero.io/b", "velero.io/c"}, + newRestartableProcessError: errors.Errorf("NewRestartableProcess"), + expectedError: "NewRestartableProcess", + }, + { + name: "Happy path", + names: []string{"velero.io/a", "velero.io/b", "velero.io/c"}, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + logger := test.NewLogger() + logLevel := logrus.InfoLevel + + registry := &mockRegistry{} + defer registry.AssertExpectations(t) + + m := NewManager(logger, logLevel, registry).(*manager) + factory := &mockRestartableProcessFactory{} + defer factory.AssertExpectations(t) + m.restartableProcessFactory = factory + + pluginKind := common.PluginKindBackupItemActionV2 + var pluginIDs []framework.PluginIdentifier + for i := range tc.names { + pluginID := framework.PluginIdentifier{ + Command: "/command", + Kind: pluginKind, + Name: tc.names[i], + } + pluginIDs = append(pluginIDs, pluginID) + } + registry.On("List", pluginKind).Return(pluginIDs) + + var expectedActions []interface{} + for i := range pluginIDs { + pluginID := pluginIDs[i] + pluginName := pluginID.Name + + registry.On("Get", pluginKind, pluginName).Return(pluginID, nil) + + restartableProcess := &restartabletest.MockRestartableProcess{} + defer restartableProcess.AssertExpectations(t) + + expected := &biav2cli.RestartableBackupItemAction{ + Key: process.KindAndName{Kind: pluginKind, Name: pluginName}, + SharedPluginProcess: restartableProcess, + } + + if tc.newRestartableProcessError != nil { + // Test 1: error getting restartable process + factory.On("NewRestartableProcess", pluginID.Command, logger, logLevel).Return(nil, errors.Errorf("NewRestartableProcess")).Once() + break + } + + // Test 2: happy path + if i == 0 { + factory.On("NewRestartableProcess", pluginID.Command, logger, logLevel).Return(restartableProcess, nil).Once() + } + + expectedActions = append(expectedActions, expected) + } + + backupItemActions, err := m.GetBackupItemActionsV2() + if tc.newRestartableProcessError != nil { + assert.Nil(t, backupItemActions) + assert.EqualError(t, err, "NewRestartableProcess") + } else { + require.NoError(t, err) + var actual []interface{} + for i := range backupItemActions { + actual = append(actual, backupItemActions[i]) + } + assert.Equal(t, expectedActions, actual) + } + }) + } +} + func TestGetRestoreItemActions(t *testing.T) { tests := []struct { name string diff --git a/pkg/plugin/clientmgmt/process/client_builder.go b/pkg/plugin/clientmgmt/process/client_builder.go index aaa56661a..ffadcc95f 100644 --- a/pkg/plugin/clientmgmt/process/client_builder.go +++ b/pkg/plugin/clientmgmt/process/client_builder.go @@ -27,6 +27,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/features" "github.com/vmware-tanzu/velero/pkg/plugin/framework" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/framework/backupitemaction/v2" "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" ) @@ -68,13 +69,14 @@ func (b *clientBuilder) clientConfig() *hcplugin.ClientConfig { HandshakeConfig: framework.Handshake(), AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC}, Plugins: map[string]hcplugin.Plugin{ - string(common.PluginKindBackupItemAction): framework.NewBackupItemActionPlugin(common.ClientLogger(b.clientLogger)), - string(common.PluginKindVolumeSnapshotter): framework.NewVolumeSnapshotterPlugin(common.ClientLogger(b.clientLogger)), - string(common.PluginKindObjectStore): framework.NewObjectStorePlugin(common.ClientLogger(b.clientLogger)), - string(common.PluginKindPluginLister): &framework.PluginListerPlugin{}, - string(common.PluginKindRestoreItemAction): framework.NewRestoreItemActionPlugin(common.ClientLogger(b.clientLogger)), - string(common.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(common.ClientLogger(b.clientLogger)), - string(common.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(common.ClientLogger(b.clientLogger)), + string(common.PluginKindBackupItemAction): framework.NewBackupItemActionPlugin(common.ClientLogger(b.clientLogger)), + string(common.PluginKindBackupItemActionV2): biav2.NewBackupItemActionPlugin(common.ClientLogger(b.clientLogger)), + string(common.PluginKindVolumeSnapshotter): framework.NewVolumeSnapshotterPlugin(common.ClientLogger(b.clientLogger)), + string(common.PluginKindObjectStore): framework.NewObjectStorePlugin(common.ClientLogger(b.clientLogger)), + string(common.PluginKindPluginLister): &framework.PluginListerPlugin{}, + string(common.PluginKindRestoreItemAction): framework.NewRestoreItemActionPlugin(common.ClientLogger(b.clientLogger)), + string(common.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(common.ClientLogger(b.clientLogger)), + string(common.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(common.ClientLogger(b.clientLogger)), }, Logger: b.pluginLogger, Cmd: exec.Command(b.commandName, b.commandArgs...), //nolint diff --git a/pkg/plugin/clientmgmt/process/client_builder_test.go b/pkg/plugin/clientmgmt/process/client_builder_test.go index 2a3f6df61..c70cf29bf 100644 --- a/pkg/plugin/clientmgmt/process/client_builder_test.go +++ b/pkg/plugin/clientmgmt/process/client_builder_test.go @@ -27,6 +27,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/features" "github.com/vmware-tanzu/velero/pkg/plugin/framework" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/framework/backupitemaction/v2" "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" "github.com/vmware-tanzu/velero/pkg/test" ) @@ -61,13 +62,14 @@ func TestClientConfig(t *testing.T) { HandshakeConfig: framework.Handshake(), AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC}, Plugins: map[string]hcplugin.Plugin{ - string(common.PluginKindBackupItemAction): framework.NewBackupItemActionPlugin(common.ClientLogger(logger)), - string(common.PluginKindVolumeSnapshotter): framework.NewVolumeSnapshotterPlugin(common.ClientLogger(logger)), - string(common.PluginKindObjectStore): framework.NewObjectStorePlugin(common.ClientLogger(logger)), - string(common.PluginKindPluginLister): &framework.PluginListerPlugin{}, - string(common.PluginKindRestoreItemAction): framework.NewRestoreItemActionPlugin(common.ClientLogger(logger)), - string(common.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(common.ClientLogger(logger)), - string(common.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(common.ClientLogger(logger)), + string(common.PluginKindBackupItemAction): framework.NewBackupItemActionPlugin(common.ClientLogger(logger)), + string(common.PluginKindBackupItemActionV2): biav2.NewBackupItemActionPlugin(common.ClientLogger(logger)), + string(common.PluginKindVolumeSnapshotter): framework.NewVolumeSnapshotterPlugin(common.ClientLogger(logger)), + string(common.PluginKindObjectStore): framework.NewObjectStorePlugin(common.ClientLogger(logger)), + string(common.PluginKindPluginLister): &framework.PluginListerPlugin{}, + string(common.PluginKindRestoreItemAction): framework.NewRestoreItemActionPlugin(common.ClientLogger(logger)), + string(common.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(common.ClientLogger(logger)), + string(common.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(common.ClientLogger(logger)), }, Logger: cb.pluginLogger, Cmd: exec.Command(cb.commandName, cb.commandArgs...), diff --git a/pkg/plugin/framework/action_resolver.go b/pkg/plugin/framework/action_resolver.go index 5664d08c2..f9ad62ab5 100644 --- a/pkg/plugin/framework/action_resolver.go +++ b/pkg/plugin/framework/action_resolver.go @@ -17,6 +17,7 @@ limitations under the License. package framework import ( + "github.com/pkg/errors" "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -25,6 +26,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/discovery" "github.com/vmware-tanzu/velero/pkg/plugin/velero" biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1" riav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v1" "github.com/vmware-tanzu/velero/pkg/util/collections" @@ -109,6 +111,17 @@ func NewBackupItemActionResolver(actions []biav1.BackupItemAction) BackupItemAct } } +type BackupItemResolvedActionV2 struct { + biav2.BackupItemAction + resolvedAction +} + +func NewBackupItemActionResolverV2(actions []biav2.BackupItemAction) BackupItemActionResolverV2 { + return BackupItemActionResolverV2{ + actions: actions, + } +} + func NewRestoreItemActionResolver(actions []riav1.RestoreItemAction) RestoreItemActionResolver { return RestoreItemActionResolver{ actions: actions, @@ -155,6 +168,32 @@ func (recv BackupItemActionResolver) ResolveActions(helper discovery.Helper, log return resolved, nil } +type BackupItemActionResolverV2 struct { + actions []biav2.BackupItemAction +} + +func (recv BackupItemActionResolverV2) ResolveActions(helper discovery.Helper, log logrus.FieldLogger) ([]BackupItemResolvedActionV2, error) { + var resolved []BackupItemResolvedActionV2 + for _, action := range recv.actions { + log.Debugf("resolving BackupItemAction for: %v", action) + resources, namespaces, selector, err := resolveAction(helper, action) + if err != nil { + log.WithError(errors.WithStack(err)).Debugf("resolveAction error, action: %v", action) + return nil, err + } + res := BackupItemResolvedActionV2{ + BackupItemAction: action, + resolvedAction: resolvedAction{ + ResourceIncludesExcludes: resources, + NamespaceIncludesExcludes: namespaces, + Selector: selector, + }, + } + resolved = append(resolved, res) + } + return resolved, nil +} + type RestoreItemResolvedAction struct { riav1.RestoreItemAction resolvedAction diff --git a/pkg/plugin/framework/backupitemaction/v2/backup_item_action.go b/pkg/plugin/framework/backupitemaction/v2/backup_item_action.go new file mode 100644 index 000000000..a4efa9a2e --- /dev/null +++ b/pkg/plugin/framework/backupitemaction/v2/backup_item_action.go @@ -0,0 +1,45 @@ +/* +Copyright the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + plugin "github.com/hashicorp/go-plugin" + "golang.org/x/net/context" + "google.golang.org/grpc" + + "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" + protobiav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2" +) + +// BackupItemActionPlugin is an implementation of go-plugin's Plugin +// interface with support for gRPC for the backup/ItemAction +// interface. +type BackupItemActionPlugin struct { + plugin.NetRPCUnsupportedPlugin + *common.PluginBase +} + +// GRPCClient returns a clientDispenser for BackupItemAction gRPC clients. +func (p *BackupItemActionPlugin) GRPCClient(_ context.Context, _ *plugin.GRPCBroker, clientConn *grpc.ClientConn) (interface{}, error) { + return common.NewClientDispenser(p.ClientLogger, clientConn, newBackupItemActionGRPCClient), nil +} + +// GRPCServer registers a BackupItemAction gRPC server. +func (p *BackupItemActionPlugin) GRPCServer(_ *plugin.GRPCBroker, server *grpc.Server) error { + protobiav2.RegisterBackupItemActionServer(server, &BackupItemActionGRPCServer{mux: p.ServerMux}) + return nil +} diff --git a/pkg/plugin/framework/backupitemaction/v2/backup_item_action_client.go b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_client.go new file mode 100644 index 000000000..925cf037c --- /dev/null +++ b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_client.go @@ -0,0 +1,169 @@ +/* +Copyright 2017, 2019 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "encoding/json" + + "github.com/pkg/errors" + "golang.org/x/net/context" + "google.golang.org/grpc" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" + protobiav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" +) + +// NewBackupItemActionPlugin constructs a BackupItemActionPlugin. +func NewBackupItemActionPlugin(options ...common.PluginOption) *BackupItemActionPlugin { + return &BackupItemActionPlugin{ + PluginBase: common.NewPluginBase(options...), + } +} + +// BackupItemActionGRPCClient implements the backup/ItemAction interface and uses a +// gRPC client to make calls to the plugin server. +type BackupItemActionGRPCClient struct { + *common.ClientBase + grpcClient protobiav2.BackupItemActionClient +} + +func newBackupItemActionGRPCClient(base *common.ClientBase, clientConn *grpc.ClientConn) interface{} { + return &BackupItemActionGRPCClient{ + ClientBase: base, + grpcClient: protobiav2.NewBackupItemActionClient(clientConn), + } +} + +func (c *BackupItemActionGRPCClient) AppliesTo() (velero.ResourceSelector, error) { + req := &protobiav2.BackupItemActionAppliesToRequest{ + Plugin: c.Plugin, + } + + res, err := c.grpcClient.AppliesTo(context.Background(), req) + if err != nil { + return velero.ResourceSelector{}, common.FromGRPCError(err) + } + + if res.ResourceSelector == nil { + return velero.ResourceSelector{}, nil + } + + return velero.ResourceSelector{ + IncludedNamespaces: res.ResourceSelector.IncludedNamespaces, + ExcludedNamespaces: res.ResourceSelector.ExcludedNamespaces, + IncludedResources: res.ResourceSelector.IncludedResources, + ExcludedResources: res.ResourceSelector.ExcludedResources, + LabelSelector: res.ResourceSelector.Selector, + }, nil +} + +func (c *BackupItemActionGRPCClient) Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { + itemJSON, err := json.Marshal(item.UnstructuredContent()) + if err != nil { + return nil, nil, "", errors.WithStack(err) + } + + backupJSON, err := json.Marshal(backup) + if err != nil { + return nil, nil, "", errors.WithStack(err) + } + + req := &protobiav2.ExecuteRequest{ + Plugin: c.Plugin, + Item: itemJSON, + Backup: backupJSON, + } + + res, err := c.grpcClient.Execute(context.Background(), req) + if err != nil { + return nil, nil, "", common.FromGRPCError(err) + } + + var updatedItem unstructured.Unstructured + if err := json.Unmarshal(res.Item, &updatedItem); err != nil { + return nil, nil, "", errors.WithStack(err) + } + + var additionalItems []velero.ResourceIdentifier + + for _, itm := range res.AdditionalItems { + newItem := velero.ResourceIdentifier{ + GroupResource: schema.GroupResource{ + Group: itm.Group, + Resource: itm.Resource, + }, + Namespace: itm.Namespace, + Name: itm.Name, + } + + additionalItems = append(additionalItems, newItem) + } + + return &updatedItem, additionalItems, res.OperationID, nil +} + +func (c *BackupItemActionGRPCClient) Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error) { + backupJSON, err := json.Marshal(backup) + if err != nil { + return velero.OperationProgress{}, errors.WithStack(err) + } + req := &protobiav2.BackupItemActionProgressRequest{ + Plugin: c.Plugin, + OperationID: operationID, + Backup: backupJSON, + } + + res, err := c.grpcClient.Progress(context.Background(), req) + if err != nil { + return velero.OperationProgress{}, common.FromGRPCError(err) + } + + return velero.OperationProgress{ + Completed: res.Progress.Completed, + Err: res.Progress.Err, + NCompleted: res.Progress.NCompleted, + NTotal: res.Progress.NTotal, + OperationUnits: res.Progress.OperationUnits, + Description: res.Progress.Description, + Started: res.Progress.Started.AsTime(), + Updated: res.Progress.Updated.AsTime(), + }, nil +} + +func (c *BackupItemActionGRPCClient) Cancel(operationID string, backup *api.Backup) error { + backupJSON, err := json.Marshal(backup) + if err != nil { + return errors.WithStack(err) + } + req := &protobiav2.BackupItemActionCancelRequest{ + Plugin: c.Plugin, + OperationID: operationID, + Backup: backupJSON, + } + + _, err = c.grpcClient.Cancel(context.Background(), req) + if err != nil { + return common.FromGRPCError(err) + } + + return nil +} diff --git a/pkg/plugin/framework/backupitemaction/v2/backup_item_action_server.go b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_server.go new file mode 100644 index 000000000..2de33ed7c --- /dev/null +++ b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_server.go @@ -0,0 +1,212 @@ +/* +Copyright the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "encoding/json" + + "github.com/pkg/errors" + "golang.org/x/net/context" + "google.golang.org/protobuf/types/known/emptypb" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + + api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" + proto "github.com/vmware-tanzu/velero/pkg/plugin/generated" + protobiav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" +) + +// BackupItemActionGRPCServer implements the proto-generated BackupItemAction interface, and accepts +// gRPC calls and forwards them to an implementation of the pluggable interface. +type BackupItemActionGRPCServer struct { + mux *common.ServerMux +} + +func (s *BackupItemActionGRPCServer) getImpl(name string) (biav2.BackupItemAction, error) { + impl, err := s.mux.GetHandler(name) + if err != nil { + return nil, err + } + + itemAction, ok := impl.(biav2.BackupItemAction) + if !ok { + return nil, errors.Errorf("%T is not a backup item action", impl) + } + + return itemAction, nil +} + +func (s *BackupItemActionGRPCServer) AppliesTo( + ctx context.Context, req *protobiav2.BackupItemActionAppliesToRequest) ( + response *protobiav2.BackupItemActionAppliesToResponse, err error) { + defer func() { + if recoveredErr := common.HandlePanic(recover()); recoveredErr != nil { + err = recoveredErr + } + }() + + impl, err := s.getImpl(req.Plugin) + if err != nil { + return nil, common.NewGRPCError(err) + } + + resourceSelector, err := impl.AppliesTo() + if err != nil { + return nil, common.NewGRPCError(err) + } + + return &protobiav2.BackupItemActionAppliesToResponse{ + ResourceSelector: &proto.ResourceSelector{ + IncludedNamespaces: resourceSelector.IncludedNamespaces, + ExcludedNamespaces: resourceSelector.ExcludedNamespaces, + IncludedResources: resourceSelector.IncludedResources, + ExcludedResources: resourceSelector.ExcludedResources, + Selector: resourceSelector.LabelSelector, + }, + }, nil +} + +func (s *BackupItemActionGRPCServer) Execute( + ctx context.Context, req *protobiav2.ExecuteRequest) (response *protobiav2.ExecuteResponse, err error) { + defer func() { + if recoveredErr := common.HandlePanic(recover()); recoveredErr != nil { + err = recoveredErr + } + }() + + impl, err := s.getImpl(req.Plugin) + if err != nil { + return nil, common.NewGRPCError(err) + } + + var item unstructured.Unstructured + var backup api.Backup + + if err := json.Unmarshal(req.Item, &item); err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + if err := json.Unmarshal(req.Backup, &backup); err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + + updatedItem, additionalItems, operationID, err := impl.Execute(&item, &backup) + if err != nil { + return nil, common.NewGRPCError(err) + } + + // If the plugin implementation returned a nil updatedItem (meaning no modifications), reset updatedItem to the + // original item. + var updatedItemJSON []byte + if updatedItem == nil { + updatedItemJSON = req.Item + } else { + updatedItemJSON, err = json.Marshal(updatedItem.UnstructuredContent()) + if err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + } + + res := &protobiav2.ExecuteResponse{ + Item: updatedItemJSON, + OperationID: operationID, + } + + for _, item := range additionalItems { + res.AdditionalItems = append(res.AdditionalItems, backupResourceIdentifierToProto(item)) + } + + return res, nil +} + +func (s *BackupItemActionGRPCServer) Progress( + ctx context.Context, req *protobiav2.BackupItemActionProgressRequest) ( + response *protobiav2.BackupItemActionProgressResponse, err error) { + defer func() { + if recoveredErr := common.HandlePanic(recover()); recoveredErr != nil { + err = recoveredErr + } + }() + + impl, err := s.getImpl(req.Plugin) + if err != nil { + return nil, common.NewGRPCError(err) + } + + var backup api.Backup + if err := json.Unmarshal(req.Backup, &backup); err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + + progress, err := impl.Progress(req.OperationID, &backup) + if err != nil { + return nil, common.NewGRPCError(err) + } + + res := &protobiav2.BackupItemActionProgressResponse{ + Progress: &proto.OperationProgress{ + Completed: progress.Completed, + Err: progress.Err, + NCompleted: progress.NCompleted, + NTotal: progress.NTotal, + OperationUnits: progress.OperationUnits, + Description: progress.Description, + Started: timestamppb.New(progress.Started), + Updated: timestamppb.New(progress.Updated), + }, + } + return res, nil +} + +func (s *BackupItemActionGRPCServer) Cancel( + ctx context.Context, req *protobiav2.BackupItemActionCancelRequest) ( + response *emptypb.Empty, err error) { + defer func() { + if recoveredErr := common.HandlePanic(recover()); recoveredErr != nil { + err = recoveredErr + } + }() + + impl, err := s.getImpl(req.Plugin) + if err != nil { + return nil, common.NewGRPCError(err) + } + + var backup api.Backup + if err := json.Unmarshal(req.Backup, &backup); err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + + err = impl.Cancel(req.OperationID, &backup) + if err != nil { + return nil, common.NewGRPCError(err) + } + + return &emptypb.Empty{}, nil +} + +func backupResourceIdentifierToProto(id velero.ResourceIdentifier) *proto.ResourceIdentifier { + return &proto.ResourceIdentifier{ + Group: id.Group, + Resource: id.Resource, + Namespace: id.Namespace, + Name: id.Name, + } +} diff --git a/pkg/plugin/framework/backupitemaction/v2/backup_item_action_test.go b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_test.go new file mode 100644 index 000000000..d9e2d2cea --- /dev/null +++ b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_test.go @@ -0,0 +1,202 @@ +/* +Copyright 2018 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "encoding/json" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/net/context" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" + proto "github.com/vmware-tanzu/velero/pkg/plugin/generated" + protobiav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" + mocks "github.com/vmware-tanzu/velero/pkg/plugin/velero/mocks/backupitemaction/v2" + velerotest "github.com/vmware-tanzu/velero/pkg/test" +) + +func TestBackupItemActionGRPCServerExecute(t *testing.T) { + invalidItem := []byte("this is gibberish json") + validItem := []byte(` + { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "namespace": "myns", + "name": "myconfigmap" + }, + "data": { + "key": "value" + } + }`) + var validItemObject unstructured.Unstructured + err := json.Unmarshal(validItem, &validItemObject) + require.NoError(t, err) + + updatedItem := []byte(` + { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "namespace": "myns", + "name": "myconfigmap" + }, + "data": { + "key": "changed!" + } + }`) + var updatedItemObject unstructured.Unstructured + err = json.Unmarshal(updatedItem, &updatedItemObject) + require.NoError(t, err) + + invalidBackup := []byte("this is gibberish json") + validBackup := []byte(` + { + "apiVersion": "velero.io/v1", + "kind": "Backup", + "metadata": { + "namespace": "myns", + "name": "mybackup" + }, + "spec": { + "includedNamespaces": ["*"], + "includedResources": ["*"], + "ttl": "60m" + } + }`) + var validBackupObject v1.Backup + err = json.Unmarshal(validBackup, &validBackupObject) + require.NoError(t, err) + + tests := []struct { + name string + backup []byte + item []byte + implUpdatedItem runtime.Unstructured + implAdditionalItems []velero.ResourceIdentifier + implOperationID string + implError error + expectError bool + skipMock bool + }{ + { + name: "error unmarshaling item", + item: invalidItem, + backup: validBackup, + expectError: true, + skipMock: true, + }, + { + name: "error unmarshaling backup", + item: validItem, + backup: invalidBackup, + expectError: true, + skipMock: true, + }, + { + name: "error running impl", + item: validItem, + backup: validBackup, + implError: errors.New("impl error"), + expectError: true, + }, + { + name: "nil updatedItem / no additionalItems", + item: validItem, + backup: validBackup, + }, + { + name: "same updatedItem / some additionalItems", + item: validItem, + backup: validBackup, + implUpdatedItem: &validItemObject, + implAdditionalItems: []velero.ResourceIdentifier{ + { + GroupResource: schema.GroupResource{Group: "v1", Resource: "pods"}, + Namespace: "myns", + Name: "mypod", + }, + }, + }, + { + name: "different updatedItem", + item: validItem, + backup: validBackup, + implUpdatedItem: &updatedItemObject, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + itemAction := &mocks.BackupItemAction{} + defer itemAction.AssertExpectations(t) + + if !test.skipMock { + itemAction.On("Execute", &validItemObject, &validBackupObject).Return(test.implUpdatedItem, test.implAdditionalItems, test.implOperationID, test.implError) + } + + s := &BackupItemActionGRPCServer{mux: &common.ServerMux{ + ServerLog: velerotest.NewLogger(), + Handlers: map[string]interface{}{ + "xyz": itemAction, + }, + }} + + req := &protobiav2.ExecuteRequest{ + Plugin: "xyz", + Item: test.item, + Backup: test.backup, + } + + resp, err := s.Execute(context.Background(), req) + + // Verify error + assert.Equal(t, test.expectError, err != nil) + if err != nil { + return + } + require.NotNil(t, resp) + + // Verify updated item + updatedItem := test.implUpdatedItem + if updatedItem == nil { + // If the impl returned nil for its updatedItem, we should expect the plugin to return the original item + updatedItem = &validItemObject + } + + var respItem unstructured.Unstructured + err = json.Unmarshal(resp.Item, &respItem) + require.NoError(t, err) + + assert.Equal(t, updatedItem, &respItem) + + // Verify additional items + var expectedAdditionalItems []*proto.ResourceIdentifier + for _, item := range test.implAdditionalItems { + expectedAdditionalItems = append(expectedAdditionalItems, backupResourceIdentifierToProto(item)) + } + assert.Equal(t, expectedAdditionalItems, resp.AdditionalItems) + }) + } +} diff --git a/pkg/plugin/framework/common/plugin_kinds.go b/pkg/plugin/framework/common/plugin_kinds.go index 2e0119e2f..c6e25e5b6 100644 --- a/pkg/plugin/framework/common/plugin_kinds.go +++ b/pkg/plugin/framework/common/plugin_kinds.go @@ -35,6 +35,9 @@ const ( // PluginKindBackupItemAction represents a backup item action plugin. PluginKindBackupItemAction PluginKind = "BackupItemAction" + // PluginKindBackupItemActionV2 represents a v2 backup item action plugin. + PluginKindBackupItemActionV2 PluginKind = "BackupItemActionV2" + // PluginKindRestoreItemAction represents a restore item action plugin. PluginKindRestoreItemAction PluginKind = "RestoreItemAction" @@ -51,7 +54,9 @@ const ( // If there are plugin kinds that are adaptable to newer API versions, list them here. // The older (adaptable) version is the key, and the value is the full list of newer // plugin kinds that are capable of adapting it. -var PluginKindsAdaptableTo = map[PluginKind][]PluginKind{} +var PluginKindsAdaptableTo = map[PluginKind][]PluginKind{ + PluginKindBackupItemAction: {PluginKindBackupItemActionV2}, +} // AllPluginKinds contains all the valid plugin kinds that Velero supports, excluding PluginLister because that is not a // kind that a developer would ever need to implement (it's handled by Velero and the Velero plugin library code). @@ -60,6 +65,7 @@ func AllPluginKinds() map[string]PluginKind { allPluginKinds[PluginKindObjectStore.String()] = PluginKindObjectStore allPluginKinds[PluginKindVolumeSnapshotter.String()] = PluginKindVolumeSnapshotter allPluginKinds[PluginKindBackupItemAction.String()] = PluginKindBackupItemAction + allPluginKinds[PluginKindBackupItemActionV2.String()] = PluginKindBackupItemActionV2 allPluginKinds[PluginKindRestoreItemAction.String()] = PluginKindRestoreItemAction allPluginKinds[PluginKindDeleteItemAction.String()] = PluginKindDeleteItemAction allPluginKinds[PluginKindItemSnapshotter.String()] = PluginKindItemSnapshotter diff --git a/pkg/plugin/framework/server.go b/pkg/plugin/framework/server.go index 20c5e48e0..d9a1af387 100644 --- a/pkg/plugin/framework/server.go +++ b/pkg/plugin/framework/server.go @@ -25,6 +25,7 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/pflag" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/framework/backupitemaction/v2" "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" "github.com/vmware-tanzu/velero/pkg/util/logging" ) @@ -46,6 +47,13 @@ type Server interface { // RegisterBackupItemActions registers multiple backup item actions. RegisterBackupItemActions(map[string]common.HandlerInitializer) Server + // RegisterBackupItemActionV2 registers a v2 backup item action. Accepted format + // for the plugin name is /. + RegisterBackupItemActionV2(pluginName string, initializer common.HandlerInitializer) Server + + // RegisterBackupItemActionsV2 registers multiple v2 backup item actions. + RegisterBackupItemActionsV2(map[string]common.HandlerInitializer) Server + // RegisterVolumeSnapshotter registers a volume snapshotter. Accepted format // for the plugin name is /. RegisterVolumeSnapshotter(pluginName string, initializer common.HandlerInitializer) Server @@ -85,15 +93,16 @@ type Server interface { // server implements Server. type server struct { - log *logrus.Logger - logLevelFlag *logging.LevelFlag - flagSet *pflag.FlagSet - backupItemAction *BackupItemActionPlugin - volumeSnapshotter *VolumeSnapshotterPlugin - objectStore *ObjectStorePlugin - restoreItemAction *RestoreItemActionPlugin - deleteItemAction *DeleteItemActionPlugin - itemSnapshotter *ItemSnapshotterPlugin + log *logrus.Logger + logLevelFlag *logging.LevelFlag + flagSet *pflag.FlagSet + backupItemAction *BackupItemActionPlugin + backupItemActionV2 *biav2.BackupItemActionPlugin + volumeSnapshotter *VolumeSnapshotterPlugin + objectStore *ObjectStorePlugin + restoreItemAction *RestoreItemActionPlugin + deleteItemAction *DeleteItemActionPlugin + itemSnapshotter *ItemSnapshotterPlugin } // NewServer returns a new Server @@ -101,14 +110,15 @@ func NewServer() Server { log := newLogger() return &server{ - log: log, - logLevelFlag: logging.LogLevelFlag(log.Level), - backupItemAction: NewBackupItemActionPlugin(common.ServerLogger(log)), - volumeSnapshotter: NewVolumeSnapshotterPlugin(common.ServerLogger(log)), - objectStore: NewObjectStorePlugin(common.ServerLogger(log)), - restoreItemAction: NewRestoreItemActionPlugin(common.ServerLogger(log)), - deleteItemAction: NewDeleteItemActionPlugin(common.ServerLogger(log)), - itemSnapshotter: NewItemSnapshotterPlugin(common.ServerLogger(log)), + log: log, + logLevelFlag: logging.LogLevelFlag(log.Level), + backupItemAction: NewBackupItemActionPlugin(common.ServerLogger(log)), + backupItemActionV2: biav2.NewBackupItemActionPlugin(common.ServerLogger(log)), + volumeSnapshotter: NewVolumeSnapshotterPlugin(common.ServerLogger(log)), + objectStore: NewObjectStorePlugin(common.ServerLogger(log)), + restoreItemAction: NewRestoreItemActionPlugin(common.ServerLogger(log)), + deleteItemAction: NewDeleteItemActionPlugin(common.ServerLogger(log)), + itemSnapshotter: NewItemSnapshotterPlugin(common.ServerLogger(log)), } } @@ -132,6 +142,18 @@ func (s *server) RegisterBackupItemActions(m map[string]common.HandlerInitialize return s } +func (s *server) RegisterBackupItemActionV2(name string, initializer common.HandlerInitializer) Server { + s.backupItemActionV2.Register(name, initializer) + return s +} + +func (s *server) RegisterBackupItemActionsV2(m map[string]common.HandlerInitializer) Server { + for name := range m { + s.RegisterBackupItemActionV2(name, m[name]) + } + return s +} + func (s *server) RegisterVolumeSnapshotter(name string, initializer common.HandlerInitializer) Server { s.volumeSnapshotter.Register(name, initializer) return s @@ -216,6 +238,7 @@ func (s *server) Serve() { var pluginIdentifiers []PluginIdentifier pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindBackupItemAction, s.backupItemAction)...) + pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindBackupItemActionV2, s.backupItemActionV2)...) pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindVolumeSnapshotter, s.volumeSnapshotter)...) pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindObjectStore, s.objectStore)...) pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindRestoreItemAction, s.restoreItemAction)...) @@ -227,13 +250,14 @@ func (s *server) Serve() { plugin.Serve(&plugin.ServeConfig{ HandshakeConfig: Handshake(), Plugins: map[string]plugin.Plugin{ - string(common.PluginKindBackupItemAction): s.backupItemAction, - string(common.PluginKindVolumeSnapshotter): s.volumeSnapshotter, - string(common.PluginKindObjectStore): s.objectStore, - string(common.PluginKindPluginLister): NewPluginListerPlugin(pluginLister), - string(common.PluginKindRestoreItemAction): s.restoreItemAction, - string(common.PluginKindDeleteItemAction): s.deleteItemAction, - string(common.PluginKindItemSnapshotter): s.itemSnapshotter, + string(common.PluginKindBackupItemAction): s.backupItemAction, + string(common.PluginKindBackupItemActionV2): s.backupItemActionV2, + string(common.PluginKindVolumeSnapshotter): s.volumeSnapshotter, + string(common.PluginKindObjectStore): s.objectStore, + string(common.PluginKindPluginLister): NewPluginListerPlugin(pluginLister), + string(common.PluginKindRestoreItemAction): s.restoreItemAction, + string(common.PluginKindDeleteItemAction): s.deleteItemAction, + string(common.PluginKindItemSnapshotter): s.itemSnapshotter, }, GRPCServer: plugin.DefaultGRPCServer, }) diff --git a/pkg/plugin/generated/Shared.pb.go b/pkg/plugin/generated/Shared.pb.go index 1c84177dc..55a829927 100644 --- a/pkg/plugin/generated/Shared.pb.go +++ b/pkg/plugin/generated/Shared.pb.go @@ -10,6 +10,7 @@ import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) @@ -323,47 +324,171 @@ func (x *ResourceSelector) GetSelector() string { return "" } +type OperationProgress struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Completed bool `protobuf:"varint,1,opt,name=completed,proto3" json:"completed,omitempty"` + Err string `protobuf:"bytes,2,opt,name=err,proto3" json:"err,omitempty"` + NCompleted int64 `protobuf:"varint,3,opt,name=nCompleted,proto3" json:"nCompleted,omitempty"` + NTotal int64 `protobuf:"varint,4,opt,name=nTotal,proto3" json:"nTotal,omitempty"` + OperationUnits string `protobuf:"bytes,5,opt,name=operationUnits,proto3" json:"operationUnits,omitempty"` + Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` + Started *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=started,proto3" json:"started,omitempty"` + Updated *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=updated,proto3" json:"updated,omitempty"` +} + +func (x *OperationProgress) Reset() { + *x = OperationProgress{} + if protoimpl.UnsafeEnabled { + mi := &file_Shared_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OperationProgress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OperationProgress) ProtoMessage() {} + +func (x *OperationProgress) ProtoReflect() protoreflect.Message { + mi := &file_Shared_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OperationProgress.ProtoReflect.Descriptor instead. +func (*OperationProgress) Descriptor() ([]byte, []int) { + return file_Shared_proto_rawDescGZIP(), []int{5} +} + +func (x *OperationProgress) GetCompleted() bool { + if x != nil { + return x.Completed + } + return false +} + +func (x *OperationProgress) GetErr() string { + if x != nil { + return x.Err + } + return "" +} + +func (x *OperationProgress) GetNCompleted() int64 { + if x != nil { + return x.NCompleted + } + return 0 +} + +func (x *OperationProgress) GetNTotal() int64 { + if x != nil { + return x.NTotal + } + return 0 +} + +func (x *OperationProgress) GetOperationUnits() string { + if x != nil { + return x.OperationUnits + } + return "" +} + +func (x *OperationProgress) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *OperationProgress) GetStarted() *timestamppb.Timestamp { + if x != nil { + return x.Started + } + return nil +} + +func (x *OperationProgress) GetUpdated() *timestamppb.Timestamp { + if x != nil { + return x.Updated + } + return nil +} + var File_Shared_proto protoreflect.FileDescriptor var file_Shared_proto_rawDesc = []byte{ 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, - 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x22, 0x36, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x12, 0x2d, 0x0a, 0x06, 0x66, - 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x65, - 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x46, 0x72, 0x61, - 0x6d, 0x65, 0x52, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x50, 0x0a, 0x0a, 0x53, 0x74, - 0x61, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, - 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x78, 0x0a, 0x12, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, - 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x2e, 0x0a, 0x12, 0x69, - 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, - 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x65, - 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, - 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x69, - 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x65, 0x78, 0x63, - 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x74, 0x61, 0x6e, 0x7a, 0x75, 0x2f, 0x76, - 0x65, 0x6c, 0x65, 0x72, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, - 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0x36, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x12, 0x2d, 0x0a, 0x06, + 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x46, 0x72, + 0x61, 0x6d, 0x65, 0x52, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x50, 0x0a, 0x0a, 0x53, + 0x74, 0x61, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x6c, 0x69, 0x6e, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x78, 0x0a, + 0x12, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x2e, 0x0a, 0x12, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x12, + 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, + 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x65, 0x78, + 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x22, 0xb1, 0x02, 0x0a, 0x11, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, + 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x72, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x6e, 0x43, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, + 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x54, + 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6e, 0x54, 0x6f, 0x74, + 0x61, 0x6c, 0x12, 0x26, 0x0a, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, + 0x6e, 0x69, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x07, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x65, 0x64, 0x12, 0x34, 0x0a, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x74, 0x61, + 0x6e, 0x7a, 0x75, 0x2f, 0x76, 0x65, 0x6c, 0x65, 0x72, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -378,21 +503,25 @@ func file_Shared_proto_rawDescGZIP() []byte { return file_Shared_proto_rawDescData } -var file_Shared_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_Shared_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_Shared_proto_goTypes = []interface{}{ - (*Empty)(nil), // 0: generated.Empty - (*Stack)(nil), // 1: generated.Stack - (*StackFrame)(nil), // 2: generated.StackFrame - (*ResourceIdentifier)(nil), // 3: generated.ResourceIdentifier - (*ResourceSelector)(nil), // 4: generated.ResourceSelector + (*Empty)(nil), // 0: generated.Empty + (*Stack)(nil), // 1: generated.Stack + (*StackFrame)(nil), // 2: generated.StackFrame + (*ResourceIdentifier)(nil), // 3: generated.ResourceIdentifier + (*ResourceSelector)(nil), // 4: generated.ResourceSelector + (*OperationProgress)(nil), // 5: generated.OperationProgress + (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp } var file_Shared_proto_depIdxs = []int32{ 2, // 0: generated.Stack.frames:type_name -> generated.StackFrame - 1, // [1:1] is the sub-list for method output_type - 1, // [1:1] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 6, // 1: generated.OperationProgress.started:type_name -> google.protobuf.Timestamp + 6, // 2: generated.OperationProgress.updated:type_name -> google.protobuf.Timestamp + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_Shared_proto_init() } @@ -461,6 +590,18 @@ func file_Shared_proto_init() { return nil } } + file_Shared_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OperationProgress); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -468,7 +609,7 @@ func file_Shared_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_Shared_proto_rawDesc, NumEnums: 0, - NumMessages: 5, + NumMessages: 6, NumExtensions: 0, NumServices: 0, }, diff --git a/pkg/plugin/generated/backupitemaction/v2/BackupItemAction.pb.go b/pkg/plugin/generated/backupitemaction/v2/BackupItemAction.pb.go new file mode 100644 index 000000000..bbcaa50e6 --- /dev/null +++ b/pkg/plugin/generated/backupitemaction/v2/BackupItemAction.pb.go @@ -0,0 +1,851 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.23.0 +// protoc v3.14.0 +// source: backupitemaction/v2/BackupItemAction.proto + +package v2 + +import ( + context "context" + proto "github.com/golang/protobuf/proto" + generated "github.com/vmware-tanzu/velero/pkg/plugin/generated" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type ExecuteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plugin string `protobuf:"bytes,1,opt,name=plugin,proto3" json:"plugin,omitempty"` + Item []byte `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` + Backup []byte `protobuf:"bytes,3,opt,name=backup,proto3" json:"backup,omitempty"` +} + +func (x *ExecuteRequest) Reset() { + *x = ExecuteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecuteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecuteRequest) ProtoMessage() {} + +func (x *ExecuteRequest) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecuteRequest.ProtoReflect.Descriptor instead. +func (*ExecuteRequest) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{0} +} + +func (x *ExecuteRequest) GetPlugin() string { + if x != nil { + return x.Plugin + } + return "" +} + +func (x *ExecuteRequest) GetItem() []byte { + if x != nil { + return x.Item + } + return nil +} + +func (x *ExecuteRequest) GetBackup() []byte { + if x != nil { + return x.Backup + } + return nil +} + +type ExecuteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item []byte `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + AdditionalItems []*generated.ResourceIdentifier `protobuf:"bytes,2,rep,name=additionalItems,proto3" json:"additionalItems,omitempty"` + OperationID string `protobuf:"bytes,3,opt,name=operationID,proto3" json:"operationID,omitempty"` +} + +func (x *ExecuteResponse) Reset() { + *x = ExecuteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecuteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecuteResponse) ProtoMessage() {} + +func (x *ExecuteResponse) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecuteResponse.ProtoReflect.Descriptor instead. +func (*ExecuteResponse) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{1} +} + +func (x *ExecuteResponse) GetItem() []byte { + if x != nil { + return x.Item + } + return nil +} + +func (x *ExecuteResponse) GetAdditionalItems() []*generated.ResourceIdentifier { + if x != nil { + return x.AdditionalItems + } + return nil +} + +func (x *ExecuteResponse) GetOperationID() string { + if x != nil { + return x.OperationID + } + return "" +} + +type BackupItemActionAppliesToRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plugin string `protobuf:"bytes,1,opt,name=plugin,proto3" json:"plugin,omitempty"` +} + +func (x *BackupItemActionAppliesToRequest) Reset() { + *x = BackupItemActionAppliesToRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionAppliesToRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionAppliesToRequest) ProtoMessage() {} + +func (x *BackupItemActionAppliesToRequest) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionAppliesToRequest.ProtoReflect.Descriptor instead. +func (*BackupItemActionAppliesToRequest) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{2} +} + +func (x *BackupItemActionAppliesToRequest) GetPlugin() string { + if x != nil { + return x.Plugin + } + return "" +} + +type BackupItemActionAppliesToResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ResourceSelector *generated.ResourceSelector `protobuf:"bytes,1,opt,name=ResourceSelector,proto3" json:"ResourceSelector,omitempty"` +} + +func (x *BackupItemActionAppliesToResponse) Reset() { + *x = BackupItemActionAppliesToResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionAppliesToResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionAppliesToResponse) ProtoMessage() {} + +func (x *BackupItemActionAppliesToResponse) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionAppliesToResponse.ProtoReflect.Descriptor instead. +func (*BackupItemActionAppliesToResponse) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{3} +} + +func (x *BackupItemActionAppliesToResponse) GetResourceSelector() *generated.ResourceSelector { + if x != nil { + return x.ResourceSelector + } + return nil +} + +type BackupItemActionProgressRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plugin string `protobuf:"bytes,1,opt,name=plugin,proto3" json:"plugin,omitempty"` + OperationID string `protobuf:"bytes,2,opt,name=operationID,proto3" json:"operationID,omitempty"` + Backup []byte `protobuf:"bytes,3,opt,name=backup,proto3" json:"backup,omitempty"` +} + +func (x *BackupItemActionProgressRequest) Reset() { + *x = BackupItemActionProgressRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionProgressRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionProgressRequest) ProtoMessage() {} + +func (x *BackupItemActionProgressRequest) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionProgressRequest.ProtoReflect.Descriptor instead. +func (*BackupItemActionProgressRequest) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{4} +} + +func (x *BackupItemActionProgressRequest) GetPlugin() string { + if x != nil { + return x.Plugin + } + return "" +} + +func (x *BackupItemActionProgressRequest) GetOperationID() string { + if x != nil { + return x.OperationID + } + return "" +} + +func (x *BackupItemActionProgressRequest) GetBackup() []byte { + if x != nil { + return x.Backup + } + return nil +} + +type BackupItemActionProgressResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Progress *generated.OperationProgress `protobuf:"bytes,1,opt,name=progress,proto3" json:"progress,omitempty"` +} + +func (x *BackupItemActionProgressResponse) Reset() { + *x = BackupItemActionProgressResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionProgressResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionProgressResponse) ProtoMessage() {} + +func (x *BackupItemActionProgressResponse) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionProgressResponse.ProtoReflect.Descriptor instead. +func (*BackupItemActionProgressResponse) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{5} +} + +func (x *BackupItemActionProgressResponse) GetProgress() *generated.OperationProgress { + if x != nil { + return x.Progress + } + return nil +} + +type BackupItemActionCancelRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plugin string `protobuf:"bytes,1,opt,name=plugin,proto3" json:"plugin,omitempty"` + OperationID string `protobuf:"bytes,2,opt,name=operationID,proto3" json:"operationID,omitempty"` + Backup []byte `protobuf:"bytes,3,opt,name=backup,proto3" json:"backup,omitempty"` +} + +func (x *BackupItemActionCancelRequest) Reset() { + *x = BackupItemActionCancelRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionCancelRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionCancelRequest) ProtoMessage() {} + +func (x *BackupItemActionCancelRequest) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionCancelRequest.ProtoReflect.Descriptor instead. +func (*BackupItemActionCancelRequest) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{6} +} + +func (x *BackupItemActionCancelRequest) GetPlugin() string { + if x != nil { + return x.Plugin + } + return "" +} + +func (x *BackupItemActionCancelRequest) GetOperationID() string { + if x != nil { + return x.OperationID + } + return "" +} + +func (x *BackupItemActionCancelRequest) GetBackup() []byte { + if x != nil { + return x.Backup + } + return nil +} + +var File_backupitemaction_v2_BackupItemAction_proto protoreflect.FileDescriptor + +var file_backupitemaction_v2_BackupItemAction_proto_rawDesc = []byte{ + 0x0a, 0x2a, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x69, 0x74, 0x65, 0x6d, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x2f, 0x76, 0x32, 0x2f, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x76, 0x32, + 0x1a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, + 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x54, 0x0a, 0x0e, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x62, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x22, 0x90, 0x01, 0x0a, 0x0f, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x47, 0x0a, 0x0f, 0x61, 0x64, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, + 0x72, 0x52, 0x0f, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x49, 0x74, 0x65, + 0x6d, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, + 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x3a, 0x0a, 0x20, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, + 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x73, 0x54, + 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x22, 0x6c, 0x0a, 0x21, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x10, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x73, + 0x0a, 0x1f, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x62, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x62, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x22, 0x5c, 0x0a, 0x20, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, + 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, + 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, + 0x73, 0x22, 0x71, 0x0a, 0x1d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, + 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x62, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x32, 0xbc, 0x02, 0x0a, 0x10, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, + 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x58, 0x0a, 0x09, 0x41, 0x70, 0x70, + 0x6c, 0x69, 0x65, 0x73, 0x54, 0x6f, 0x12, 0x24, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x76, + 0x32, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x07, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, 0x12, + 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x67, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x23, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, + 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, + 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, + 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x21, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x42, 0x49, 0x5a, 0x47, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x74, 0x61, 0x6e, 0x7a, 0x75, 0x2f, 0x76, + 0x65, 0x6c, 0x65, 0x72, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x69, 0x74, 0x65, 0x6d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x32, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_backupitemaction_v2_BackupItemAction_proto_rawDescOnce sync.Once + file_backupitemaction_v2_BackupItemAction_proto_rawDescData = file_backupitemaction_v2_BackupItemAction_proto_rawDesc +) + +func file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP() []byte { + file_backupitemaction_v2_BackupItemAction_proto_rawDescOnce.Do(func() { + file_backupitemaction_v2_BackupItemAction_proto_rawDescData = protoimpl.X.CompressGZIP(file_backupitemaction_v2_BackupItemAction_proto_rawDescData) + }) + return file_backupitemaction_v2_BackupItemAction_proto_rawDescData +} + +var file_backupitemaction_v2_BackupItemAction_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_backupitemaction_v2_BackupItemAction_proto_goTypes = []interface{}{ + (*ExecuteRequest)(nil), // 0: v2.ExecuteRequest + (*ExecuteResponse)(nil), // 1: v2.ExecuteResponse + (*BackupItemActionAppliesToRequest)(nil), // 2: v2.BackupItemActionAppliesToRequest + (*BackupItemActionAppliesToResponse)(nil), // 3: v2.BackupItemActionAppliesToResponse + (*BackupItemActionProgressRequest)(nil), // 4: v2.BackupItemActionProgressRequest + (*BackupItemActionProgressResponse)(nil), // 5: v2.BackupItemActionProgressResponse + (*BackupItemActionCancelRequest)(nil), // 6: v2.BackupItemActionCancelRequest + (*generated.ResourceIdentifier)(nil), // 7: generated.ResourceIdentifier + (*generated.ResourceSelector)(nil), // 8: generated.ResourceSelector + (*generated.OperationProgress)(nil), // 9: generated.OperationProgress + (*emptypb.Empty)(nil), // 10: google.protobuf.Empty +} +var file_backupitemaction_v2_BackupItemAction_proto_depIdxs = []int32{ + 7, // 0: v2.ExecuteResponse.additionalItems:type_name -> generated.ResourceIdentifier + 8, // 1: v2.BackupItemActionAppliesToResponse.ResourceSelector:type_name -> generated.ResourceSelector + 9, // 2: v2.BackupItemActionProgressResponse.progress:type_name -> generated.OperationProgress + 2, // 3: v2.BackupItemAction.AppliesTo:input_type -> v2.BackupItemActionAppliesToRequest + 0, // 4: v2.BackupItemAction.Execute:input_type -> v2.ExecuteRequest + 4, // 5: v2.BackupItemAction.Progress:input_type -> v2.BackupItemActionProgressRequest + 6, // 6: v2.BackupItemAction.Cancel:input_type -> v2.BackupItemActionCancelRequest + 3, // 7: v2.BackupItemAction.AppliesTo:output_type -> v2.BackupItemActionAppliesToResponse + 1, // 8: v2.BackupItemAction.Execute:output_type -> v2.ExecuteResponse + 5, // 9: v2.BackupItemAction.Progress:output_type -> v2.BackupItemActionProgressResponse + 10, // 10: v2.BackupItemAction.Cancel:output_type -> google.protobuf.Empty + 7, // [7:11] is the sub-list for method output_type + 3, // [3:7] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_backupitemaction_v2_BackupItemAction_proto_init() } +func file_backupitemaction_v2_BackupItemAction_proto_init() { + if File_backupitemaction_v2_BackupItemAction_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecuteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecuteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionAppliesToRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionAppliesToResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionProgressRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionProgressResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionCancelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_backupitemaction_v2_BackupItemAction_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_backupitemaction_v2_BackupItemAction_proto_goTypes, + DependencyIndexes: file_backupitemaction_v2_BackupItemAction_proto_depIdxs, + MessageInfos: file_backupitemaction_v2_BackupItemAction_proto_msgTypes, + }.Build() + File_backupitemaction_v2_BackupItemAction_proto = out.File + file_backupitemaction_v2_BackupItemAction_proto_rawDesc = nil + file_backupitemaction_v2_BackupItemAction_proto_goTypes = nil + file_backupitemaction_v2_BackupItemAction_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// BackupItemActionClient is the client API for BackupItemAction service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type BackupItemActionClient interface { + AppliesTo(ctx context.Context, in *BackupItemActionAppliesToRequest, opts ...grpc.CallOption) (*BackupItemActionAppliesToResponse, error) + Execute(ctx context.Context, in *ExecuteRequest, opts ...grpc.CallOption) (*ExecuteResponse, error) + Progress(ctx context.Context, in *BackupItemActionProgressRequest, opts ...grpc.CallOption) (*BackupItemActionProgressResponse, error) + Cancel(ctx context.Context, in *BackupItemActionCancelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) +} + +type backupItemActionClient struct { + cc grpc.ClientConnInterface +} + +func NewBackupItemActionClient(cc grpc.ClientConnInterface) BackupItemActionClient { + return &backupItemActionClient{cc} +} + +func (c *backupItemActionClient) AppliesTo(ctx context.Context, in *BackupItemActionAppliesToRequest, opts ...grpc.CallOption) (*BackupItemActionAppliesToResponse, error) { + out := new(BackupItemActionAppliesToResponse) + err := c.cc.Invoke(ctx, "/v2.BackupItemAction/AppliesTo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *backupItemActionClient) Execute(ctx context.Context, in *ExecuteRequest, opts ...grpc.CallOption) (*ExecuteResponse, error) { + out := new(ExecuteResponse) + err := c.cc.Invoke(ctx, "/v2.BackupItemAction/Execute", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *backupItemActionClient) Progress(ctx context.Context, in *BackupItemActionProgressRequest, opts ...grpc.CallOption) (*BackupItemActionProgressResponse, error) { + out := new(BackupItemActionProgressResponse) + err := c.cc.Invoke(ctx, "/v2.BackupItemAction/Progress", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *backupItemActionClient) Cancel(ctx context.Context, in *BackupItemActionCancelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/v2.BackupItemAction/Cancel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// BackupItemActionServer is the server API for BackupItemAction service. +type BackupItemActionServer interface { + AppliesTo(context.Context, *BackupItemActionAppliesToRequest) (*BackupItemActionAppliesToResponse, error) + Execute(context.Context, *ExecuteRequest) (*ExecuteResponse, error) + Progress(context.Context, *BackupItemActionProgressRequest) (*BackupItemActionProgressResponse, error) + Cancel(context.Context, *BackupItemActionCancelRequest) (*emptypb.Empty, error) +} + +// UnimplementedBackupItemActionServer can be embedded to have forward compatible implementations. +type UnimplementedBackupItemActionServer struct { +} + +func (*UnimplementedBackupItemActionServer) AppliesTo(context.Context, *BackupItemActionAppliesToRequest) (*BackupItemActionAppliesToResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AppliesTo not implemented") +} +func (*UnimplementedBackupItemActionServer) Execute(context.Context, *ExecuteRequest) (*ExecuteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Execute not implemented") +} +func (*UnimplementedBackupItemActionServer) Progress(context.Context, *BackupItemActionProgressRequest) (*BackupItemActionProgressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Progress not implemented") +} +func (*UnimplementedBackupItemActionServer) Cancel(context.Context, *BackupItemActionCancelRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Cancel not implemented") +} + +func RegisterBackupItemActionServer(s *grpc.Server, srv BackupItemActionServer) { + s.RegisterService(&_BackupItemAction_serviceDesc, srv) +} + +func _BackupItemAction_AppliesTo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BackupItemActionAppliesToRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackupItemActionServer).AppliesTo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v2.BackupItemAction/AppliesTo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackupItemActionServer).AppliesTo(ctx, req.(*BackupItemActionAppliesToRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _BackupItemAction_Execute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExecuteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackupItemActionServer).Execute(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v2.BackupItemAction/Execute", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackupItemActionServer).Execute(ctx, req.(*ExecuteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _BackupItemAction_Progress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BackupItemActionProgressRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackupItemActionServer).Progress(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v2.BackupItemAction/Progress", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackupItemActionServer).Progress(ctx, req.(*BackupItemActionProgressRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _BackupItemAction_Cancel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BackupItemActionCancelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackupItemActionServer).Cancel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v2.BackupItemAction/Cancel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackupItemActionServer).Cancel(ctx, req.(*BackupItemActionCancelRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _BackupItemAction_serviceDesc = grpc.ServiceDesc{ + ServiceName: "v2.BackupItemAction", + HandlerType: (*BackupItemActionServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "AppliesTo", + Handler: _BackupItemAction_AppliesTo_Handler, + }, + { + MethodName: "Execute", + Handler: _BackupItemAction_Execute_Handler, + }, + { + MethodName: "Progress", + Handler: _BackupItemAction_Progress_Handler, + }, + { + MethodName: "Cancel", + Handler: _BackupItemAction_Cancel_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "backupitemaction/v2/BackupItemAction.proto", +} diff --git a/pkg/plugin/mocks/manager.go b/pkg/plugin/mocks/manager.go index c99e1bf28..5031729a1 100644 --- a/pkg/plugin/mocks/manager.go +++ b/pkg/plugin/mocks/manager.go @@ -10,6 +10,8 @@ import ( v1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + v2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" + velero "github.com/vmware-tanzu/velero/pkg/plugin/velero" volumesnapshotterv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1" @@ -48,6 +50,29 @@ func (_m *Manager) GetBackupItemAction(name string) (v1.BackupItemAction, error) return r0, r1 } +// GetBackupItemActionV2 provides a mock function with given fields: name +func (_m *Manager) GetBackupItemActionV2(name string) (v2.BackupItemAction, error) { + ret := _m.Called(name) + + var r0 v2.BackupItemAction + if rf, ok := ret.Get(0).(func(string) v2.BackupItemAction); ok { + r0 = rf(name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(v2.BackupItemAction) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetBackupItemActions provides a mock function with given fields: func (_m *Manager) GetBackupItemActions() ([]v1.BackupItemAction, error) { ret := _m.Called() @@ -71,6 +96,29 @@ func (_m *Manager) GetBackupItemActions() ([]v1.BackupItemAction, error) { return r0, r1 } +// GetBackupItemActionsV2 provides a mock function with given fields: +func (_m *Manager) GetBackupItemActionsV2() ([]v2.BackupItemAction, error) { + ret := _m.Called() + + var r0 []v2.BackupItemAction + if rf, ok := ret.Get(0).(func() []v2.BackupItemAction); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]v2.BackupItemAction) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetDeleteItemAction provides a mock function with given fields: name func (_m *Manager) GetDeleteItemAction(name string) (velero.DeleteItemAction, error) { ret := _m.Called(name) diff --git a/pkg/plugin/proto/Shared.proto b/pkg/plugin/proto/Shared.proto index c1c298e3a..dd12a7ee7 100644 --- a/pkg/plugin/proto/Shared.proto +++ b/pkg/plugin/proto/Shared.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package generated; option go_package = "github.com/vmware-tanzu/velero/pkg/plugin/generated"; +import "google/protobuf/timestamp.proto"; + message Empty {} message Stack { @@ -27,4 +29,15 @@ message ResourceSelector { repeated string includedResources = 3; repeated string excludedResources = 4; string selector = 5; -} \ No newline at end of file +} + +message OperationProgress { + bool completed = 1; + string err = 2; + int64 nCompleted = 3; + int64 nTotal = 4; + string operationUnits = 5; + string description = 6; + google.protobuf.Timestamp started = 7; + google.protobuf.Timestamp updated = 8; +} diff --git a/pkg/plugin/proto/backupitemaction/v2/BackupItemAction.proto b/pkg/plugin/proto/backupitemaction/v2/BackupItemAction.proto new file mode 100644 index 000000000..5f7bfe7d3 --- /dev/null +++ b/pkg/plugin/proto/backupitemaction/v2/BackupItemAction.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; +package v2; +option go_package = "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2"; + +import "Shared.proto"; +import "google/protobuf/empty.proto"; + + +message ExecuteRequest { + string plugin = 1; + bytes item = 2; + bytes backup = 3; +} + +message ExecuteResponse { + bytes item = 1; + repeated generated.ResourceIdentifier additionalItems = 2; + string operationID = 3; +} + +service BackupItemAction { + rpc AppliesTo(BackupItemActionAppliesToRequest) returns (BackupItemActionAppliesToResponse); + rpc Execute(ExecuteRequest) returns (ExecuteResponse); + rpc Progress(BackupItemActionProgressRequest) returns (BackupItemActionProgressResponse); + rpc Cancel(BackupItemActionCancelRequest) returns (google.protobuf.Empty); +} + +message BackupItemActionAppliesToRequest { + string plugin = 1; +} + +message BackupItemActionAppliesToResponse { + generated.ResourceSelector ResourceSelector = 1; +} + +message BackupItemActionProgressRequest { + string plugin = 1; + string operationID = 2; + bytes backup = 3; +} + +message BackupItemActionProgressResponse { + generated.OperationProgress progress = 1; +} + +message BackupItemActionCancelRequest { + string plugin = 1; + string operationID = 2; + bytes backup = 3; +} diff --git a/pkg/plugin/velero/backupitemaction/v2/backup_item_action.go b/pkg/plugin/velero/backupitemaction/v2/backup_item_action.go new file mode 100644 index 000000000..a4a70234e --- /dev/null +++ b/pkg/plugin/velero/backupitemaction/v2/backup_item_action.go @@ -0,0 +1,63 @@ +/* +Copyright 2017 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + + "github.com/pkg/errors" + + api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" +) + +// BackupItemAction is an actor that performs an operation on an individual item being backed up. +type BackupItemAction interface { + // AppliesTo returns information about which resources this action should be invoked for. + // A BackupItemAction's Execute function will only be invoked on items that match the returned + // selector. A zero-valued ResourceSelector matches all resources. + AppliesTo() (velero.ResourceSelector, error) + + // Execute allows the BackupItemAction to perform arbitrary logic with the item being backed up, + // including mutating the item itself prior to backup. The item (unmodified or modified) + // should be returned, along with an optional slice of ResourceIdentifiers specifying + // additional related items that should be backed up. + Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) + + // Progress allows the BackupItemAction to report on progress of an asynchronous action. + // For the passed-in operation, the plugin will return an OperationProgress struct, indicating + // whether the operation has completed, whether there were any errors, a plugin-specific + // indication of how much of the operation is done (items completed out of items-to-complete), + // and started/updated timestamps + Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error) + + // Cancel allows the BackupItemAction to cancel an asynchronous action (if possible). + // Velero will call this if the wait timeout for asynchronous actions has been reached. + // If operation cancel is not supported, then the plugin just needs to return. No error + // return is expected in this case, since cancellation is optional here. + Cancel(operationID string, backup *api.Backup) error +} + +func AsyncOperationsNotSupportedError() error { + return errors.New("Plugin does not support asynchronous operations") +} + +func InvalidOperationIDError(operationID string) error { + return errors.New(fmt.Sprintf("Operation ID %v is invalid.", operationID)) +} diff --git a/pkg/plugin/velero/mocks/backupitemaction/v2/BackupItemAction.go b/pkg/plugin/velero/mocks/backupitemaction/v2/BackupItemAction.go new file mode 100644 index 000000000..63be81d4d --- /dev/null +++ b/pkg/plugin/velero/mocks/backupitemaction/v2/BackupItemAction.go @@ -0,0 +1,127 @@ +/* +Copyright the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package v2 + +import ( + mock "github.com/stretchr/testify/mock" + runtime "k8s.io/apimachinery/pkg/runtime" + + v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + + velero "github.com/vmware-tanzu/velero/pkg/plugin/velero" +) + +// BackupItemAction is an autogenerated mock type for the BackupItemAction type +type BackupItemAction struct { + mock.Mock +} + +// AppliesTo provides a mock function with given fields: +func (_m *BackupItemAction) AppliesTo() (velero.ResourceSelector, error) { + ret := _m.Called() + + var r0 velero.ResourceSelector + if rf, ok := ret.Get(0).(func() velero.ResourceSelector); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(velero.ResourceSelector) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Cancel provides a mock function with given fields: operationID, backup +func (_m *BackupItemAction) Cancel(operationID string, backup *v1.Backup) error { + ret := _m.Called(operationID, backup) + + var r0 error + if rf, ok := ret.Get(0).(func(string, *v1.Backup) error); ok { + r0 = rf(operationID, backup) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Execute provides a mock function with given fields: item, backup +func (_m *BackupItemAction) Execute(item runtime.Unstructured, backup *v1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { + ret := _m.Called(item, backup) + + var r0 runtime.Unstructured + if rf, ok := ret.Get(0).(func(runtime.Unstructured, *v1.Backup) runtime.Unstructured); ok { + r0 = rf(item, backup) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(runtime.Unstructured) + } + } + + var r1 []velero.ResourceIdentifier + if rf, ok := ret.Get(1).(func(runtime.Unstructured, *v1.Backup) []velero.ResourceIdentifier); ok { + r1 = rf(item, backup) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]velero.ResourceIdentifier) + } + } + + var r2 string + if rf, ok := ret.Get(2).(func(runtime.Unstructured, *v1.Backup) string); ok { + r2 = rf(item, backup) + } else { + r2 = ret.Get(2).(string) + } + + var r3 error + if rf, ok := ret.Get(3).(func(runtime.Unstructured, *v1.Backup) error); ok { + r3 = rf(item, backup) + } else { + r3 = ret.Error(3) + } + + return r0, r1, r2, r3 +} + +// Progress provides a mock function with given fields: operationID, backup +func (_m *BackupItemAction) Progress(operationID string, backup *v1.Backup) (velero.OperationProgress, error) { + ret := _m.Called(operationID, backup) + + var r0 velero.OperationProgress + if rf, ok := ret.Get(0).(func(string, *v1.Backup) velero.OperationProgress); ok { + r0 = rf(operationID, backup) + } else { + r0 = ret.Get(0).(velero.OperationProgress) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, *v1.Backup) error); ok { + r1 = rf(operationID, backup) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/pkg/plugin/velero/shared.go b/pkg/plugin/velero/shared.go index a0d3d884c..6316a1e0b 100644 --- a/pkg/plugin/velero/shared.go +++ b/pkg/plugin/velero/shared.go @@ -20,7 +20,11 @@ limitations under the License. // plugins of any type can be implemented. package velero -import "k8s.io/apimachinery/pkg/runtime/schema" +import ( + "time" + + "k8s.io/apimachinery/pkg/runtime/schema" +) // ResourceSelector is a collection of included/excluded namespaces, // included/excluded resources, and a label-selector that can be used @@ -63,3 +67,25 @@ type ResourceIdentifier struct { Namespace string Name string } + +// OperationProgress describes progress of an asynchronous plugin operation. +type OperationProgress struct { + // True when the operation has completed, either successfully or with a failure + Completed bool + // Set when the operation has failed + Err string + // Quantity completed so far and the total quantity associated with the operation + // in OperationUnits. For data mover and volume snapshotter use cases, this will + // usually be in bytes. On successful completion, NCompleted and NTotal should be + // the same + NCompleted, NTotal int64 + // Units represented by NCompleted and NTotal -- for data mover and item + // snapshotters, this will usually be bytes. + OperationUnits string + // Optional description of operation progress (i.e. "Current phase: Running") + Description string + // When the operation was started and when the last update was seen. Not all + // systems retain when the upload was begun, return Time 0 (time.Unix(0, 0)) + // if unknown. + Started, Updated time.Time +} From 700d9dcc36fa5084bd9bb30673059dd5a0c8a3ee Mon Sep 17 00:00:00 2001 From: Daniel Jiang Date: Tue, 10 Jan 2023 17:25:19 +0800 Subject: [PATCH 10/10] Update the default reviewers (#5751) Update the group members of "maintainers" and "tech-writer" to reflect the change in the team. As for the group "tech-writer" I just selected a few members from maintains team who has been working on velero for a relatively longer time. Signed-off-by: Daniel Jiang --- .github/auto-assignees.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/auto-assignees.yml b/.github/auto-assignees.yml index 8f5a75a57..d98cefe57 100644 --- a/.github/auto-assignees.yml +++ b/.github/auto-assignees.yml @@ -9,7 +9,6 @@ reviewers: groups: maintainers: - - dsu-igeek - sseago - reasonerjt - ywk253100 @@ -19,7 +18,10 @@ reviewers: - Lyndon-Li tech-writer: - - a-mccarthy + - sseago + - reasonerjt + - ywk253100 + - Lyndon-Li files: 'site/**':