From f562a7ce2b811f1015b31094be767a399c790f0f Mon Sep 17 00:00:00 2001 From: Daniel Jiang Date: Tue, 19 Jul 2022 16:06:40 +0800 Subject: [PATCH] Update the CRD for kopia integration This commit adds additional fields to podvolumebackup and podvolumerestore. The resticrepository will be renamed to backuprepository Signed-off-by: Daniel Jiang --- changelogs/unreleased/5135-reasonerjt | 1 + ...yaml => velero.io_backuprepositories.yaml} | 30 ++- .../v1/bases/velero.io_podvolumebackups.yaml | 20 +- .../v1/bases/velero.io_podvolumerestores.yaml | 16 +- config/crd/v1/crds/crds.go | 6 +- config/rbac/role.yaml | 38 ++-- .../unified-repo-and-kopia-integration.md | 18 +- ...ry_types.go => backup_repository_types.go} | 49 +++-- pkg/apis/velero/v1/pod_volume_backup_types.go | 12 +- pkg/apis/velero/v1/pod_volume_restore_type.go | 10 +- pkg/apis/velero/v1/register.go | 2 +- pkg/apis/velero/v1/zz_generated.deepcopy.go | 188 +++++++++--------- pkg/cmd/cli/backuplocation/delete.go | 6 +- pkg/cmd/cli/restic/repo/get.go | 8 +- pkg/cmd/server/server.go | 2 +- pkg/cmd/util/output/output.go | 8 +- pkg/cmd/util/output/restic_repo_printer.go | 6 +- .../restic_repository_controller.go | 50 ++--- .../restic_repository_controller_test.go | 36 ++-- pkg/controller/restore_controller.go | 4 + ...esticrepository.go => backuprepository.go} | 106 +++++----- .../velero/v1/fake/fake_backuprepository.go | 142 +++++++++++++ .../velero/v1/fake/fake_resticrepository.go | 142 ------------- .../velero/v1/fake/fake_velero_client.go | 8 +- .../typed/velero/v1/generated_expansion.go | 4 +- .../typed/velero/v1/velero_client.go | 10 +- .../informers/externalversions/generic.go | 4 +- ...esticrepository.go => backuprepository.go} | 38 ++-- .../externalversions/velero/v1/interface.go | 14 +- .../listers/velero/v1/backuprepository.go | 99 +++++++++ .../listers/velero/v1/expansion_generated.go | 16 +- .../listers/velero/v1/resticrepository.go | 99 --------- pkg/restic/backupper.go | 7 +- pkg/restic/mocks/repository_manager.go | 16 +- pkg/restic/repository_ensurer.go | 35 ++-- pkg/restic/repository_manager.go | 22 +- pkg/restic/restorer.go | 6 +- 37 files changed, 669 insertions(+), 609 deletions(-) create mode 100644 changelogs/unreleased/5135-reasonerjt rename config/crd/v1/bases/{velero.io_resticrepositories.yaml => velero.io_backuprepositories.yaml} (78%) rename pkg/apis/velero/v1/{restic_repository_types.go => backup_repository_types.go} (64%) rename pkg/generated/clientset/versioned/typed/velero/v1/{resticrepository.go => backuprepository.go} (53%) create mode 100644 pkg/generated/clientset/versioned/typed/velero/v1/fake/fake_backuprepository.go delete mode 100644 pkg/generated/clientset/versioned/typed/velero/v1/fake/fake_resticrepository.go rename pkg/generated/informers/externalversions/velero/v1/{resticrepository.go => backuprepository.go} (70%) create mode 100644 pkg/generated/listers/velero/v1/backuprepository.go delete mode 100644 pkg/generated/listers/velero/v1/resticrepository.go diff --git a/changelogs/unreleased/5135-reasonerjt b/changelogs/unreleased/5135-reasonerjt new file mode 100644 index 0000000000..0505ab0468 --- /dev/null +++ b/changelogs/unreleased/5135-reasonerjt @@ -0,0 +1 @@ +Update the CRD for kopia integration \ No newline at end of file diff --git a/config/crd/v1/bases/velero.io_resticrepositories.yaml b/config/crd/v1/bases/velero.io_backuprepositories.yaml similarity index 78% rename from config/crd/v1/bases/velero.io_resticrepositories.yaml rename to config/crd/v1/bases/velero.io_backuprepositories.yaml index 2517661369..fa7e5596ee 100644 --- a/config/crd/v1/bases/velero.io_resticrepositories.yaml +++ b/config/crd/v1/bases/velero.io_backuprepositories.yaml @@ -6,20 +6,23 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.7.0 creationTimestamp: null - name: resticrepositories.velero.io + name: backuprepositories.velero.io spec: group: velero.io names: - kind: ResticRepository - listKind: ResticRepositoryList - plural: resticrepositories - singular: resticrepository + kind: BackupRepository + listKind: BackupRepositoryList + plural: backuprepositories + singular: backuprepository scope: Namespaced versions: - additionalPrinterColumns: - jsonPath: .metadata.creationTimestamp name: Age type: date + - jsonPath: .spec.repositoryType + name: Repository Type + type: string name: v1 schema: openAPIV3Schema: @@ -37,7 +40,7 @@ spec: metadata: type: object spec: - description: ResticRepositorySpec is the specification for a ResticRepository. + description: BackupRepositorySpec is the specification for a BackupRepository. properties: backupStorageLocation: description: BackupStorageLocation is the name of the BackupStorageLocation @@ -47,12 +50,19 @@ spec: description: MaintenanceFrequency is how often maintenance should be run. type: string + repositoryType: + description: RepositoryType indicates the type of the backend repository + enum: + - kopia + - restic + - "" + type: string resticIdentifier: description: ResticIdentifier is the full restic-compatible string for identifying this repository. type: string volumeNamespace: - description: VolumeNamespace is the namespace this restic repository + description: VolumeNamespace is the namespace this backup repository contains pod volume backups for. type: string required: @@ -62,7 +72,7 @@ spec: - volumeNamespace type: object status: - description: ResticRepositoryStatus is the current status of a ResticRepository. + description: BackupRepositoryStatus is the current status of a BackupRepository. properties: lastMaintenanceTime: description: LastMaintenanceTime is the last time maintenance was @@ -72,10 +82,10 @@ spec: type: string message: description: Message is a message about the current status of the - ResticRepository. + BackupRepository. type: string phase: - description: Phase is the current state of the ResticRepository. + description: Phase is the current state of the BackupRepository. enum: - New - Ready diff --git a/config/crd/v1/bases/velero.io_podvolumebackups.yaml b/config/crd/v1/bases/velero.io_podvolumebackups.yaml index 69993f0d4c..6af4ce294e 100644 --- a/config/crd/v1/bases/velero.io_podvolumebackups.yaml +++ b/config/crd/v1/bases/velero.io_podvolumebackups.yaml @@ -37,9 +37,13 @@ spec: jsonPath: .spec.volume name: Volume type: string - - description: Restic repository identifier for this backup + - description: Backup repository identifier for this backup jsonPath: .spec.repoIdentifier - name: Restic Repo + name: Repository ID + type: string + - description: The type of the uploader to handle data transfer + jsonPath: .spec.uploaderType + name: Uploader Type type: string - description: Name of the Backup Storage Location where this backup should be stored @@ -70,7 +74,7 @@ spec: properties: backupStorageLocation: description: BackupStorageLocation is the name of the backup storage - location where the restic repository is stored. + location where the backup repository is stored. type: string node: description: Node is the name of the node that the Pod is running @@ -114,7 +118,7 @@ spec: type: string type: object repoIdentifier: - description: RepoIdentifier is the restic repository identifier. + description: RepoIdentifier is the backup repository identifier. type: string tags: additionalProperties: @@ -122,6 +126,14 @@ spec: description: Tags are a map of key-value pairs that should be applied to the volume backup as tags. type: object + uploaderType: + description: UploaderType is the type of the uploader to handle the + data transfer. + enum: + - kopia + - restic + - "" + type: string volume: description: Volume is the name of the volume within the Pod to be backed up. diff --git a/config/crd/v1/bases/velero.io_podvolumerestores.yaml b/config/crd/v1/bases/velero.io_podvolumerestores.yaml index 6f77cb67c9..036f58a06d 100644 --- a/config/crd/v1/bases/velero.io_podvolumerestores.yaml +++ b/config/crd/v1/bases/velero.io_podvolumerestores.yaml @@ -25,6 +25,10 @@ spec: jsonPath: .spec.pod.name name: Pod type: string + - description: The type of the uploader to handle data transfer + jsonPath: .spec.uploaderType + name: Uploader Type + type: string - description: Name of the volume to be restored jsonPath: .spec.volume name: Volume @@ -67,7 +71,7 @@ spec: properties: backupStorageLocation: description: BackupStorageLocation is the name of the backup storage - location where the restic repository is stored. + location where the backup repository is stored. type: string pod: description: Pod is a reference to the pod containing the volume to @@ -107,11 +111,19 @@ spec: type: string type: object repoIdentifier: - description: RepoIdentifier is the restic repository identifier. + description: RepoIdentifier is the backup repository identifier. type: string snapshotID: description: SnapshotID is the ID of the volume snapshot to be restored. type: string + uploaderType: + description: UploaderType is the type of the uploader to handle the + data transfer. + enum: + - kopia + - restic + - "" + type: string volume: description: Volume is the name of the volume within the Pod to be restored. diff --git a/config/crd/v1/crds/crds.go b/config/crd/v1/crds/crds.go index 4b1197a47f..2d7135ff66 100644 --- a/config/crd/v1/crds/crds.go +++ b/config/crd/v1/crds/crds.go @@ -29,13 +29,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}\xcdr\x1c9r\xf0\x9dO\x91\xc1\xef\xa0\xdd\bvs'\xbe\x83\x1d\xbci(M\xb8c\xc6\x1a\x86(\xcb\a\x87\x0f\xe8\xaa\xecn\fQ@-\x80j\xaa\xed\xf0\xbb;\x90\x00\xea\xafQUh\x0e\xb9;\xeb\x10.RW\x01\x89D\"\xff\x91\x05^\xadV\xab+V\xf3\xaf\xa8\rW\xf2\x0eX\xcd\xf1\x9bE\xe9~\x99\xf5\xd3?\x9b5W\xb7\xc7\x1f\xae\x9e\xb8,\xef\xe0\xbe1VU\x9fѨF\x17\xf8\x01w\\r˕\xbc\xaaв\x92Yvw\x05\xc0\xa4T\x96\xb9\xc7\xc6\xfd\x04(\x94\xb4Z\t\x81z\xb5G\xb9~j\xb6\xb8m\xb8(Q\x13\xf08\xf5\xf1/\xeb\u007fZ\xff\xe5\n\xa0\xd0Hÿ\xf0\n\x8deU}\a\xb2\x11\xe2\n@\xb2\n\xef`ˊ\xa7\xa66\xeb#\n\xd4j\xcdՕ\xa9\xb1ps\xed\xb5j\xea;\xe8^\xf8!\x01\x0f\xbf\x86\x1fi4=\x10\xdc؟{\x0f\u007f\xe1\xc6ҋZ4\x9a\x89v&zf\xb8\xdc7\x82\xe9\xf8\xf4\n\xc0\x14\xaa\xc6;\xf8䦨Y\x81\xe5\x15@X\x0eM\xb9\n\b\x1f\u007f\xf0\x10\x8a\x03V\xcc\xe3\x02\xa0j\x94\xef\x1f6_\xff\xff\xe3\xe01@\x89\xa6м\xb6D\x14\x8f\x18p\x03\f\xbeҲ@\a\xf2\x83=0\v\x1ak\x8d\x06\xa55`\x0f\b\x05\xabm\xa3\x11\xd4\x0e~n\xb6\xa8%Z4-h\x80B4Ƣ\x06c\x99E`\x16\x18ԊK\v\\\x82\xe5\x15\u009f\xde?l@m\u007f\xc3\xc2\x1a`\xb2\x04f\x8c*8\xb3X\xc2Q\x89\xa6B?\xf6\xcf\xeb\x16j\xadU\x8d\xda\xf2Hg\xdfz\\\xd5{:Z\xde;G\x01\xdf\vJ\xc7N\xe8\x97\x11\xa8\x88e \x9a[\x8f=p\xd3-\x978d\x00\x18\\'&\x03\xf2kxD\xed\xc0\x809\xa8F\x94\x8e\v\x8f\xa8\x1d\xc1\n\xb5\x97\xfc\xbfZ\xd8\x06\xac\xa2I\x05\xb3\x18\x18\xa0k\\ZԒ\t82\xd1\xe0\r\x91\xa4b'\xd0\xe8f\x81F\xf6\xe0Q\x17\xb3\x86\u007fU\x1a\x81˝\xba\x83\x83\xb5\xb5\xb9\xbb\xbd\xdds\x1b\xa5\xa9PU\xd5HnO\xb7$\x18|\xdbX\xa5\xcdm\x89G\x14\xb7\x86\xefWL\x17\an\xb1p\x1by\xcbj\xbe\"\xd4%IԺ*\xff_d\x00\xf3n\x80\xab=9f4Vs\xb9\xef\xbd \xae\x9f\xd9\x01'\x00\x9e\xbf\xfcP\xbf\x8a\x8e\xd0\ue463\xce珏_\xfa\xbc\xc7͘\xfaD\xf7\x1eCv[\xe0\b\xc6\xe5\x0e\xb5\xdfĝV\x15\xc1DYz\xee#\xd6\x15\x1c\xe5\x98\xfc\xa6\xd9Vܺ}\xffk\x83\xc61\xb9Z\xc3=\xa9\x18\xd8\"4u\xe98s\r\x1b\t\xf7\xacBq\xcf\f\xbe\xf9\x068J\x9b\x95#l\xde\x16\xf4\xb5㸳\xa7Z\xefE\xd4e\x13\xfb\xe5\x15\xc2c\x8d\xc5@`\xdc(\xbe\xe3\x05\x89\x05\xec\x94\xee\xf4\x85WW\xeb\x01ȴ\xc8\xfa\xc9v\xac\x11\xf6+\x89\xba\xf9\xa2>\xa3\xb1\xbc\x18\xf7\x1a!\xf5!9(\"\x85\x06\x9e\x0fh\x0f\xa8\x1d\xff\xd0\v\x12\xc93\x98@[j\xb0$\x89dO\b,`O\xa2-\x04\xd4*j!\x03\xdbSDv}\x06\xc9\xd3v\xab\x94@6\xd6\x12\xf8\xad\x10M\x89e\xab\xb6\xcfh0Z\xdddz\x01d\xd2\x18\x97Nj\x9c\x11q\xe8\xc9\xee\xadS̉\xc51\x8d\xe0\xf8\x96K\x0f\x8ft\xee\x01\x93\x1b\xe4\x1a\xb7X%p\x9bd3ߜ\xa9d[\x81w`u\x83\x13\x94aZ\xb3\xd3\x04]\xa2y\xcf%K\xdb?h\x11\xc1\v\xb2?\xad\xae \xcaxk\xc5\xf49F\xf0G&\xcaA\xa9\xa7%B\xfc\x8b\xeb\xd3\xe9=(\xc8K\x82-\x1eؑ+\x1d\x96\x1e\xcc\xd0\x16\x01\xbfa\xd1XL\xf1?\xb3P\xf2\xdd\x0e\xb5\x83S\x1f\x98A\xe3M\xdf4A\xa6E\xd95=\xbd\x99g\xeb\xe86\xd2q*\xad|\nu'\xd0c\xb9\x8a\xcd!ꌆs[dɏ\xbcl\x98\x00.\x8de\xb2\xf0\xeba-^\xe7끹M>\xc3٫È\xb9ۉ\x81jT\x12Ai\xa8\x9c=8\xef:\xb6`]\x9bZ\xf6\x969\xed\xa4<\x8b\xeaF\xa0\tS\x95\xa4s;\x1dp3\t\xba\xdd\x11\xefK\b\xb6E\x01\x06\x05\x16V\xe949\x966ٷ\x1c\xbd6Ań\x86\xebt\xb7[j\xb7\xb0\x19\x90\xe0\xd4\xf6\xf3\x81\x17\ao\xe6\x1d\a\x11\x1c(\x15\x1a\x92rV\xd7\xe24\xb5HX\xda\xf90ɜ\xa0wmA\xe4\xc7\xf0R\xc2ߵ\f\xddص\x05-9\xa4l\xcb\x0e`\xd5\xec\xb2\xffo\x126\xaa\xfd\x170\xed\xe6l\xe8\xeb2\xad#)w\xee\xfcf\aX\xd5\xf6t\x03\xdcƧK\x10\x9d\xb3\xd2\xcd\xff\x0f\xbc1\x97s\xfcf<\xf2U9~vW\x96 \xba]i\xa7\xff\a\xdc\x142\x16\x8f\xc1Vdo\xc8/\xfdQ7\xc0w톔7\xb0\xe3¢\x1e\xed\xcc\uf497\xd7 F\x8e\xbds\xadb\xb68|\xfc\xe6\xa5q\x03\f\x9e\xf0\xe4=\x16&\xc1m\x0es\x13MD:\xe7ġ\xdc\n1\xd9\x13\x9e\bLH\x96,\x8e\xcee\x05ߞ\xf0\x94\xd3mD@\x87\x137!\t\xe4(\xe9\x1e\x10!(\xb6\xce'\x1eP\xe2+\xea\xa2\xe5\xc5A\xbe\"\x89-\xd2\xfe\x05\xcbl\xb7\xad\x974\xa4\x8d}g\xfc\x169)8\xf0:s\xa1\xcéA\x92\x96\x98\xfa\xfa\xca\x04/ۉ<\xdfo\xe4\xb47\xbd\xf6\a=4\t+ȹX\x84[kU\xa01\xf3,\x92\xa1\xad\x17\x92\x84m\x82\x90\xf9\x00\xc6\x1f\x98\xcc'%c\xcbwH\x1d\x91.t\xe5?~\xebe/\x9d\xf0\xbb\xdfK\xccw)^@2[Ul|2\x98\x85\xe2\xbd\x1f\x19\xc5$\x00\xf2\xa1\x81\xde7$\xea\xf9\x1ed`\xa4?\x82\x99\xae\xb8\xdc\xd0\x04\xf0ë\x9b\xf5VI\xe2K\x1c\xf7\xfb8\xb6#z\xfb\x80\xa47\xd7#R\x94\xb9\xd78ع\xf3<\xb7s\x143AJe\xfb\xe9\x04\a\xb7V\xe5;\x03;\xae\x8d\xed#\x9a\xcb\x14͂\xf4w\xed\xd2\xc8I~\xd4\xfaE\x81ӯ~d/\x91uP\xcf\xf1|u\xf203\xd5\xe8P\b\x81\xef\x80[@Y\xa8FR\xfaʼn:M\xe1\xb7\xc0+\xe8l\x92\xe5)\b\xd7P6U\x1e\x01V\xc4u\\\xce\xe6i\xfa\xdd\u007fb\\\xbcŶY^\xa1jf\rg\xd7\x06\xdb\xf6ŏ\x1c\x1c\x94W\xec\x1b\xaf\x9a\nX\xe5H\x9f\x1b\xf6\xec|q\xcc`\xc7\xe1\x99qK\x96\xc3\xc1%3b\x95\x13\xaaZ\xa0͕\xc8-\xee\x94&y6\xbc\xc4\xd60\a.P\x12\x18\xec\x18\x17\x8d\xceԐ\x17\xd1\xf6\x92X#(\x8b\xd7\v\"\xf2&_\x11)2\x12\xb1\x99\xce⼶\xaeu\xbe\xab\xf8\xa01\xcf=[JJG\xf7\xac\xd6\xdc\xf1\x92zm\x0f-\xb0\x18\x93\xa7\xef.\xdaY\xfb\xee\xa2-\xb4\xef.\xdad\xfb\xee\xa2-\xb7\xef.Zh\xdf]\xb4ؾ\xbbh\xdf]\xb4\xb9ns\xdaz\t#_q?\xf1r\x11\x8b\x8c\xe3\xe99\x14g\xe0\x87j\x8a{_}\x9f[a\xb9I\x8fJ\xd4Ն\xb2\xfe\x15}\x91\x90\u202e\xe8\xa23%mɥ\x13\x90\xc8\u07be\x80x\xa1\b3\xab\x9c2]}\x9bS\xf0\xb3T\xe63\xac3m\xcblb\xa1\xa9\x8a\x93$\xe8\x10\xbflpno\xbf\x86dX\xafC~n\xc4\xf4\xef^\x83\x9aQ\x8a\xb3P\x803_\x98;G\xafQ\xe81$\x98\x1e\x14\x8c\xfea\xe8\xb5P%3]\x1b\x13N\x82в\xe3\x0f\xeb\xe1\x1b\xabB\xa5\f\x84\xbbX.\xe7Ï\xbcZ\x9a\x17W\xd0\f+d&T\xf4\xa5GF\xf9\x85\xc2\xf952\xf3E-\x97Tƌ\xeb^&\x81.\xd7\xc3\xe4D\x8e\v\xb5//\xa8xɬv\xfc\xdd\ac95-/\xaadY,\b̬_\x19V\xa6̃\xbc\xa0j%\x8b8\xcb\x15*\x17ץ\x84:\x90\xd9udW\xa3$\xeaLf\x01O֠\xccU\x97,d\xa5\xce+O\xf2kJfAS\xbd\xc9r%\xc9\xebՋ\xbe\x86\x0f<\xadj\x16\xabA\x16}\xe4y\xfc\x16\xeb=.\xa9\xf2X\xa4\xd8\v+:ڊ\x8d\x89y/\xad\xe3\x18\xd6iL\x00ͩޘ\xa8Θ\x808[\xb3\x91[\x931\x01{\xc1\xec\xcer\xc9\xcc\xcb\U00107430h\xdf\xc4ߊ\xa3^\xba0\xa5\a\xee⒇\xfe먻\xdb\xcb\xe85ͻ\x9f)ϓ\xdb\xc3\xe5\xeeg\xd5\b\xcbkA\xe9\xfc#/\x93A\xa3=\xe0\t\x9e\xb9\x10N\xad\xfe\xa6\xe83\xa7\xed\x89 \xfd\xfa\xb9e\xcf\xf5ȉf\x06\x9eQ\b`)\xe6:[y\xe1\xbf\xe5-\xd4\n\x9d\xcew\x02\x17>\xf9\f\x9f\xfc\xdex\x0e\xa6/\xb9R\x19O{\xc0\xcaA\x89ߎ^\x10~\xcc;\x88ޗ\xa5g\u007fmP\x9f@\x1dQw\x1e\xc3\xc2w\x04^\xd0L#\xba\u00ad\xa0?\xfc\x17\xe4#ǹ\x138x/\xbd\tK\x82\x1d\xe1Hp\x9c̋v\xaf\x9dzsq\xc0D\xd7t\xe2C\xb5\xa3\x13\xef\x97|\xcf\xdc\"\xfc\xb7\r\x1d.\x0f\x1e\x16\xcd\xf6\x9b\x04\x10/\x0f!f@\xe6\x16\xd5\xe7\x1d@-\x16ѿU(\xb1\x14Ld{QyE\xf2oQ\x1c\u007fAQ\xfc\x05A\xc5eaE6\x99r\x8a\xdf\xdf$\xb8x\xc3\xf0\xe2-\x02\x8c\x97\x85\x18\v GE\xed9\xe5\xeaY\x87\xab\xd9\xe7\v9\x87\xa3\xcbG\x00\xf3e\xe8\x19\xe5\xe7\x19\x87\x03K\x98f\x94\x99_V^\x9eA\xc37\n>\xde(\xfcx\x8b\x00\xe4mC\x90\xc5 d\x91sf_\xbf8\xbb\xact\x89z6\x19\x9f\xcbj\xb3L6\x8a\x17\x86s\x8e\xbe\xa8\x8dw\xa4\xb8^\x03\xd74\x95Rn\xbf\xfe,\xe0g.K\xbf\x1f\x8e\xa9zv\x9c\xee\x04\xa2\xfa\xf7֩\xe8\xfc\xb34\xd0ѡ\x82\xc1\x9ai\xba4j{\xf2\a\x93f\r\x1fYq\x18v\x84\x033\xb0S\xbaJ:L\xd7\xed\x89\xccm\x1c\xe5\x9e\\\xaf\x01~R\xed\xa1W\xffF\x05ëZ\x9c\\\x1c\x00\xd7\xc3!/c\x80$\xf3\x18\xc9jsP\U0007a6c5X\xefq\xd8;qx\x17/\xbb)\x84j\xca\x16\xfa\xc4\xe61y\x82\x87\xaf\xe4\x93\xd05!EweJ\xf0:b\xcc7\xbeQ\xe5\xc7\xd7?\xcc3Vi\xb6\xc7_\x94\xbfwh\x89\x12\xc3ރK\xa7\x82\xae\x88\x87\xeb\xf1ۋ\x94\r\r7 \x8d\x80u53A\x1a\xbasN\x87eJ\x89\xccȟ\xb5ba1_\xbe\xfc\xe2\x17`y\x85\xeb\x0f\x8d?8]\xd5L\x1btԌ\v\xf3\x83\xb6\xee\xbf\a\xf5\x9c\xcam\xa8\xb0\xe6\x1f\xc7xk\xa4\xba\x1c:\x9f\xbd\b{\u007fCRd\xbcH\xa2%F\xfd\x9a\x1e\xd5\v\xccz\x9b\xe4\xa5<\x19\x90O\xc1\xe9]$G)\v\xfa\xae\xe6u\xaf\xf9\x99\xd2\xdaSWmYf\x1b\xb3|\xd9\x16u\x8bW\xeb\x85\xea\xaeF\xd3\x1d=\x1e\x84\xbf\xd3\xe6E\xf7m\x85b\x94\xc1u\x87\xf3\xfbt\u007f>\x82.\xb5ӥG\x8d\xca`ڋ\xb3\x9e\x99i\v^\x92\x86\xb4\x03\xe7G\x92'\xeb\xa0a\txD\tJR}\v\xdd~\xe3/^\x1c\x8fI@\xedC\t\x054M-\x14+\xa3\x84G\x9b\x15.\xeb\xfbB\xfaK\x1fQ\xbf330\xe9r\xb0\x9d\xd2)\"\x9c+LoX\xee\xa0d\x16WI\xa0Y\xba/\xc9l\x85\xe1CF7\xef\xaduqA\xcaW\x1e\xee\xdf\xe3fjd\xb4\xbfVY&@6\xd5\xd6\x1bt\x16;\xa4\xf6\xefq3\x129\x13*\x9ef\xc4\xcb/\x8cK\x8b\xfb\xb3\x9c\xe2\xf9\xca\xee#\xff\\\xbc\xb2v\xe4\xd4\xcaLS\x14ḫ\x11\"\xe5ڷ\x9c\xfb\xfaˤZ\xbe\xc5;Ψ\x93W\x81T\b\x18/\xa2\xf3\x95\x80\x15\x1a\xc3\xf6\xf1r\xb3gg\x81\xf6(\x91\x1c\x9fT\xbe\xd1\a\x86]\xe5\xd8\xf0j/\x9f\xc1b\x85mX\x98 \x9e\xfc\xf7z\xbdK\xf9\x05B\xeda\xc7\x05u\r\xd70\x06\xd3|!M\xbe\xd5\\\xe7\x98\xf2\x8fmGG\x1bJ>\xd3Ftו\xa2\xe0{\xee\xec\xa0ۤ=\xd3[\xb6\xc7U\xa1\x84@*3?\xc7\xeb-\x855\xd4\xe7}Ff\x16\x97\xf6S\xbfo\xc8t\xf8\xdd\xf67c0\u007fA!\xdd^i\xb9\xc6\xee:\xd83\x84\x14M|\x91\xe9\xf6TH^\x9cz\x8ei\xbfo\x14\xb0\xa0W=\x9cx\x8f\xeaMp\x06\xd3\xd1l\xc5~S\xfa\x06*.\xdd?\xce\xe3\xa7TD\x1c|\x11\xfetg\xdd\x02\xde\x0f\xaeO[&\xdd3\xa4\x18\x05b\xcaUM\x97Ʈ\xe0\x13\x9e{V\xbe\xda\x15KJ\xbe\xa5n\x8bu]6\xf2A\xab\xbd\x8b\x87\x13/[\xe5\x95x\xf7\xc0\xb4\xe5L\x88\x93\x9fdr\xf6ċ\x0f\xe8\fפ\xf7\x92&k\xc0r\x89\xb2\xa1[\x17zs\xe99\x81\xeaT\xb7\xaa\xb1\x03Uҩ\xa2tڟ\x80\xadᓲ\x183\xba|\b\xd3)_4v\x85\xbb\x9d\xd2\xd6G\xfa\xab\x15\xf0]\xf0\x86\x12p\x9dLЉ\x94\xbf\xbc\x15\xb8\xed\x0e\xe5;\xee\xa5@G\x93\x10\xd2\rO\x15;\xf9\x9aEV\x14\xce\xd9\xc6[c\x99H\xe8\xb7\xdfU\x03En\xa7\xe3>,\xff-ᇝ\x11|\xd3\xef\xdf~8\xdeZ7\x02\xe7)G5\xe5^\xb7'-\x1dP\xa51Jx\xd6\xdcZ\xa7O\xfbGv`\x9d\x06\x15\x02\x8c\xd3)\x13\xd7\x04\xceivz\xefl\xeff:\x858\x8co\xda\xceS\xa6;,N\xb9m\xd9\x12\t&\x96\xe5\xbfY\xe2&\x8eu[Y\x1c\x98\xdc;\xa6Ҫ\xd9\x1f\"_NXƩ\f\\㐂Z4{\xc7\xea\xe1\xb8\xc46Z\xf6R0\xe1\x00\xa5\xec\xa1ˊ\xa7ILCJ8^ ~\x1b.\xfe[\xed\xb4\xaaVa/\xe8\x94\xe3&\xa4F4W\xce\xffw\x81\xfc\x04\xd0\xee\x86-b\x83\xbaF\t\xcc\x04|2>\xa8\x9a\xdfֹ<\x85e\xda\xe6F\x15\x8f\x83\xce\v\x01\x05AN\xe3\xfb\x18\x12?\xfeò\xfb\xf1U\xee7`\xb8\x8cw\x97\xfbĒg\x05\xe3\xe2\f\x8d\x14\xab'\x0f\xb0\xce\"\x84A<0D\xffo\x1b\n\x1c[\v\xf31ǧ\xfc:\xea>\xaa\xceuR\xdeA\f~`\x82\x1e\u007f\xe2;\u007f\xa6V8\xac\xff\xfcw\xaf\xba=f\xf9,\xeff\xdd\x15\xf2DZ\xbf\x03>`\xad\xb1`\xc9\xc0\x03\xe0A\xa0\xf3#\f\xe2\xd0\x13zw\x91\xcb{|Y\x10\xf7\x9a\x11\\\xbcV\xffu\xe2\x9a\xe3\xcbb\xb77\v\xdc^wu\xcfLK.\xf7K2\xf6\xef\xa1[\"r\v\x10\x12\xb1[b\x19m4\xb7\x18\xbb\xf5B\xb7\x88\xe3\xc4mףp\ue542\xb7\xa4\x1d8{H\n\xb4\xec\xc9v\x98)<\xe9\x12b\xac(б\xeb\xa7\xf1\x9fϸ\xbe\xa6\x1f\xf1/d\xd0\xcfBIon\xcd\x1d\xfc\xc7\u007f^Aȸ~\x8d\u007f\n\xc3=\xfc\xdf\x00\x00\x00\xff\xff\xa6\x16s\x9fjd\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\xcdn4\xfa\xf150\x9b\xcf\xe73\xa6\xc5#\x1a+\x94\\\x02\xd3\x02\xbf8\x94\xf4\xcb\x16O\u007f\xb5\x85P\x8b\xdd\xf7\xb3'!\xf9\x12\xae[\xebT\xf3\x80V\xb5\xa6\xc4\x1b\xdc\b)\x9cPr֠c\x9c9\xb6\x9c\x010)\x95c\xd4l\xe9'@\xa9\xa43\xaa\xae\xd1\xcc+\x94\xc5S\xbb\xc6u+j\x8e\xc6\vOK\xef\xbe+\xfeR|7\x03(\r\xfa\xe9\x1fE\x83ֱF/A\xb6u=\x03\x90\xac\xc1%h\xc5w\xaan\x1b\\\xb3\xf2\xa9ն\xd8a\x8dF\x15Bͬƒ\x16\xad\x8cj\xf5\x12\x8e\x1dan\x04\x146s\xaf\xf8\xa3\x17\xf3ދ\xf1=\xb5\xb0\ue7f9ޟ\x84u~\x84\xae[\xc3\xea)\b\xdfi\x85\xacښ\x99I\xf7\f\xc0\x96J\xe3\x12\xee\b\x86f%\xf2\x19@ܻ\x875\aƹ\xd7&\xab\uf350\x0e\xcd5IHZ\x9c\x03G[\x1a\xa1\x9d\xd7ֽ\xe2\x10\x00B@\b\xd61\xd7Z\xb0m\xb9\x05f\xe1\x0e\xf7\x8b[yoTe\xd0\x06x\x00\xbfX%\xef\x99\xdb.\xa1\b\xc3\v\xbde\x16coP\xef\xcaw\xc4&w \xd0\xd6\x19!\xab\x1c\f:#\xd8oQ\x82\xdb\n\va\xb7\xb0g\x96\xe0\x18\xe7w\x99_\xd8\xf7wG<@pM\x06\xd0M\r\x108s\x98\x03\xd0\xe9\x13\xd4\x06\xdc\x16I\xf3\xde☐BV\xbe)\x9c\x048\x05k\xf4\x10\x91C\xab3\xc84\x96\x85V\xbc\x90I\xe8\x00\xd6ݨ\xf5\x92nh\xfc\xff\x1a\xd5\x00н⯀\xf2\xa2u\xc3\xe0\xc1\xaa\x8f\xfd\xa6K\v?\xa0u\xa2\x04\x83ZY\xe1\x949\x80\xe0(\x9d\xd8\b4\xb0Q\xa6o6' \xd0\xdc\xdbn\xd2\x00J\x94\xfe\x80Z\xbdB\x11\xd1oVN\x19V!\xfc\xa4J\x1fvȜ\r\x0e\xec\xd9nU[sX\xa7]\x03X\xa7Lָ\tq\x98\x15\xe5&\xb1#\x1f\x1b\xaey\x1a}Ov\n\xb2\xc5$@\x0ed\xbf\xab0\xef9\xa1{\xf7}\bU\xe5\x16\x1b\xb6\x8c#\x95F\xf9\xee\xfe\xf6\xf1\x8f\xabA3\x806J\xa3q\"\x85\xce\xf0\xf52F\xaf\x15\x86\xaa\xbe\"\x81a\x14pJ\x15h\x83\xfd\x856\xe4\x11C8\x0ea\xc9H\fZ\x94\xae\xaf\x92\xf4\xa9\r0\tj\xfd\v\x96\xae\x80\x15\x1a\x12\x93\x0e\xa6Tr\x87Ɓ\xc1RUR\xfc\xa7\x93m\xc9\xcciњ9\x8c\x11\xfc\xf8\xf9 +Y\r;V\xb7\xf8\x16\x98\xe4а\x03\x18\xa4U\xa0\x95=y~\x88-\xe0ge\x10\x84ܨ%l\x9d\xd3v\xb9XT¥LY\xaa\xa6i\xa5p\x87\x85Ozb\xdd:e\xec\x82\xe3\x0e\xeb\x85\x15՜\x99r+\x1c\x96\xae5\xb8`Z\xcc=t\xe9\xb3e\xd1\xf0oḼ\xf6j\x80ub\x18\xe1\xf3\x89\xec\xcc\tP*\x03a\x81ũa\x17GE\xa7P\xf4\xf0\xb7\xd5GHK\xfb\xc3\x18k\xdf\xeb\xfd8\xd1\x1e\x8f\x80\x14&\xe4\x06\xa3+o\x8cj\xbcL\x94\\+!\x9d\xffQ\xd6\x02\xe5X\xfd\xb6]7\xc2ѹ\xff\xbbE\xeb\xe8\xac\n\xb8\xf6\xf4\x81BS\xab\xc9ry\x01\xb7\x12\xaeY\x83\xf55\xb3\xf8\xd5\x0f\x804m\xe7\xa4\xd8\xe7\x1dA\x9f\xf9\x8c\a\a\xad\xf5:\x12=9q^#α\xd2X\xd2\xe9\x91\x02i\xa6؈\x18\xa1(p\xb2\xf1\xf0b 8\xef\xb8\xf4e\xa3\xd3x\xd0\b\xd9\xfbܜ\x84M\xf6bj\n\x98a\xe4D(@=\x8e\xb2d\x8e\x93\x1cac\x80-&\x12N\x1c\x03}Rq\xbc\xb0\x8f;\xc51\a\x9b\xa6\x82۲`\xadĭ(\x1e\xb5RNW\xa1O\xc9\x17\x01ӊ_\xc0\x15Wd`p\x83\x06e\x89)p\x9d#\x0e\x19d\xfd\x94>\xc5x\xda(\xe0LT\xcf\"~w\u007f\x9b\"yRb\xc4\xee\xa6\xeb^\xd0\x0f}\x1b\x815\xf7\x89\xee\xf2\xdaW\xb7\x9b\xb0\x98\x8fiN\x01\x03-0P\xc0.I\x80\x90\xd6!\xe3\xa06Y\x89T\xa8\x009\xbe\xc18\xe3m\x88`1T\x1eS\v\xe9\x1e\x18\xc5N\xc1\xe1\x1f\xab\x0fw\x8b\xbf\xe7T\xdf\xed\x02XY\xa2\xf5\f\xd8a\x83ҽ\xedH9G+\fr\xa2\xd8X4L\x8a\rZW\xc45\xd0\xd8O?|\xcek\x0f\xe0Ge\x00\xbf\xb0F\xd7\xf8\x16D\xd0x\x17\x96\x93\xd1\b\x1b\xd4\xd1I\x84\xbdp[1N\xa6\x9d\x06ȼ\xe2\xb6\xf7~\xbb\x8e=!\xa8\xb8\xdd\x16\xa1\x16O\xb8\x847\x9e\xd6\x1ca\xfeJ\xbe\xf3ۛ\x13R\xff\x10\\\xfb\r\rz\x13\xc0uy\xb8\xeftG\x90\xc1\xf3\x8c\xa8*<\xb2\xaa\xf1\xe7\x93\n\x85\xeaoA\x19ҀT=\x11^0\x9d^\b\x94\xc8'\xa0?\xfd\xf0\xf9$⡾@H\x8e_\xe0\a\x10\xb1\xacъ\u007f[\xc0Go\x1d\a\xe9\xd8\x17Z\xa9\xdc*\x8b\xa74\xabd}\xa0=o\xd9\x0e\xc1**\x92\xb0\xae\xe7\x81\aqس\x03i!\x1d\x1c\xd9\x1b\x03͌;k\xad\x89\xfd|\xfcp\xf3a\x19\x90\x91AU>\x12S\xd6\xdc\bb3DcB.\xf6\xd68I\xe6\xe9\xb3m0\x1f\xa7\xa0\xdc2Ya\xd8/¦\xa5\xecX\\\xbdƏ\xa7\x94$}\x19j2\x0e\x1c\xff\xb7\xe4\xfe\xcc\xcdy\x06\xfd\x8c\xcd\xf5\xab\x8c\xb3\x9b{j\xd7h$:\xf4\xfb㪴\xb4\xb5\x12\xb5\xb3\v\xb5C\xb3\x13\xb8_\xec\x95y\x12\xb2\x9a\x93i\u0383\r\u0605/Q\x17\xdf\xf8?\xafދ\xaff\x9f\xbb\xa1A\x95\xfd5wE\xeb\xd8ū6\x958\xec\xf3\xf3\xd8\xd5*2\xab\xf1\\r\x8b\xfdV\x94\xdbT\x9c\xc4\x18{\u0099\x041a\x1eB3\x93\x87\xafnʤ\xd0\xd6\x10\xa2\xc3<^\xb0͙\xe4\xf4\xbf\x15\xd6Q\xfb\xab4؊g\xb9\xef\xbfno~\x1f\x03oū|\xf5\x04\x01\x0f6ҿO\xb8@\xcc\x1e\x06\x83\x13u\xcc0\xd6n̋\x98\xa1cU\x86\x8a\xf5/\x02\xcf\x11\xb6\xb3\x1a\x18^ӱ\xca\x023\b\f\x1a\xa6\xe9\xe4\x9e\xf00\x0f)^3A\xf9\x99Rpw\xcf\x01L\xebZdSqL䑄F\xbeO\x856\xab쩽g\xcf!H\xb8\xa0\xffxř\xa1\xec\x11@\xe07\x1dm\xf7\xb7Z9^|\x9a\x14\x9f\xd4\"ե\xc4ֆ\x10\xe7\xf9\x02j4\x86\n\x8aQ\x93V|Ԓ\xbd\xd9J\x9d\x83\x9b\xb7\xb3\xca\f\x17\xaa/\xa8+\xc3Eq\xd4i\x88\".]\x1f\x13\x85~meY*b\xa7ë\xfb\xf3\xc7{=\x9d\xe1/q\f\x0f\xe0\x9ch\xc8f{\xd7\xcaq\x8d\\i\b=qa\xa6\x8f\xdb$\r\xb9\xa7\x8e\xc4l7L\xd4\xc8!\xbd\x1d\x8c\xe7d\xa4\xf6\xa5\xacqCA\xaeյb<\x15d\x11^GϨ^\xf7\xb7#W\xf6\x8c\xcc\xd6\"\xf7\x95|F\tSʶQ\xa6a.\xdc\xe6ͳBe[\xd7l]\xe3\x12\x9ci\xa7\xddg\x82E\x83ֲ\xea\x92+\xfe\x1cF\x85:5N\x01\xb6V\xad\xeb\n\xd5AP\xb8\xb2Ѧ^V+gK\xc0\xa193\xa2\xe86Rպ\xf6s\xfa\x81\xe0\xf8\xe0\xe4Q\xad1\x9f\xea^\x13\x13\x00\xfc\x83\xc9%\x844&\xe7`]\xf4:\xeba\xf4\xa1l\x9b\xe9*s\xb8\xc3}\xa6u\xf2\xd0\xd3\xef\xbcN.\x93\xe9\xfb\xd1{Ë\xf6\x1f\x17\xba\xa4\x828\f\xb6\xaaNά\x1c\xabA\xb6\xcd\x1a\r\xe9a}ph\x87\xe1n\xe6r\xd0\n53\xe4\xe9\xfe&\xfcz\xfcT\xf3\x16\xac\xf0\xd7{ķ\x02\x01\vŷ\xa5\xe4D\xc4R\x19̄L\x98\xa6\x95A\x12\x19\xc2\xff=\xf3G\xd6N&\x8d\x1e9\xefɎW\xc4\xfd\x96v\xdd=\u007f,\xe1\xd7\xdffGb\xc3J*\x19\x90ߍ\x1f\xf1߄+\x9d\xf4*\xef\u007f\x96J\x06\xfel\x97\xf0\xe9\xf3,=\xd6=\xa6\xc7vj\xfco\x00\x00\x00\xff\xff\xbe\xf4?~\xf9 \x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4Y_s\x1b\xb7\x11\u007f\xe7\xa7\xd8q\x1e\xd4̘\xc7\xc4\xed\xb4\x1d\xbe\xd9R\xd3Q\x9b\xc8\x1aSՋ\xc7\x0f\xe0ay\x87\xe8\x0e\xb8b\x01\xd2l&߽\xb3\xc0\x81\xbc\u007f\x14%M\x94܋-`\xb1\xf8\xedb\xffs6\x9f\xcfg\xa2Q\xf7hI\x19\xbd\x04\xd1(\xfc\xeaP\xf3_\x94=\xfc\x9d2e\x16\xdb\xefg\x0fJ\xcb%\\zr\xa6\xfe\x84d\xbc\xcd\xf1\n7J+\xa7\x8c\x9e\xd5\xe8\x84\x14N,g\x00Bk\xe3\x04/\x13\xff\t\x90\x1b\xed\xac\xa9*\xb4\xf3\x02u\xf6\xe0\u05f8\xf6\xaa\x92h\x03\xf3t\xf5\xf6\xbb\xeco\xd9w3\x80\xdcb8~\xa7j$'\xeaf\t\xdaW\xd5\f@\x8b\x1a\x97\xd0\x18\xb95\x95\xaf\xd1\"9c\x91\xb2-VhM\xa6̌\x1a\xcc\xf9\xd6\xc2\x1a\xdf,\xe1\xb8\x11\x0f\xb7\x88\xa24\xb7F\xde\a>\x9f\"\x9f\xb0U)r\xff\x9e\xdc\xfeQ\x91\v$M孨&p\x84]R\xba\xf0\x95\xb0\xe3\xfd\x19\x00\xe5\xa6\xc1%\xdc0\x94F\xe4(g\x00\xad\x02\x02\xb49\b)\x83JEuk\x95vh/\x99ER\xe5\x1c$RnU\xe3\x82\xca\x0e|\xc0l\xc0\x95\xc8W\x06u\v\xa5\x95.\xc2R\x84\x00\xce\xc0\x1a\xa1E\"\x033\x80\x9f\xc9\xe8[\xe1\xca%d\xac\xb8\xac12ӉgK\x13u~3Xu{\x96\x83\x9cU\xba8\x85\xec7\x06\xd5\xc3sk\xe4\v\x90<\xe7\xdaHۻ\xf4\xbe\xbbt\xee\xde[#\xdb\x03\xd0\x1a\x10\x90\x13\xce\x13\x90\xcfK\x10\x047\xb8[\\\xeb[k\n\x8bD\x130\x02y֔\x82\xfa8Va\xe3uql\x8c\xad\x85[\x82\xd2\xee\xaf\u007f9\x8d\xad=\x949\xe3D\xf5a\xef\x90zH\xef\x86\xcb\x11-\x1bv\x81\xf6\x8f\x83\xbbfHWF\xf7\xf5\xfaa\xb0:\x05\xb6\xc34\x05\xbdl\x14\xb0z\\\xdf\x17}~R\xb8\xb8\x10\xb7\xb7\xdfǰ\x91\x97X\x8beKi\x1a\xd4\xefo\xaf\xef\xff\xbc\xea-\x034\xd64h\x9dJ\x91,~\x9d\b\xdeY\x85\xbef/\x98a\xa4\x02ɡ\x1b):E\\C\xd9b\x88\u03a2\b,6\x16\tu\f\xe6=\xc6\xc0DB\x83Y\xff\x8c\xb9\xcb`\x85\x96\xd9\x00\x95\xc6W\xc1۷h\x1dX\xccM\xa1\xd5\xff\x0e\xbc\x89}\x8f/\xad\x84\xc36\x9c\x1e\xbf\x10ﴨ`+*\x8foAh\t\xb5\u0603E\xbe\x05\xbc\xee\xf0\v$\x94\xc1Ol!Jo\xcc\x12J\xe7\x1aZ.\x16\x85r)s妮\xbdVn\xbf\bIH\xad\xbd3\x96\x16\x12\xb7X-H\x15sa\xf3R9̝\xb7\xb8\x10\x8d\x9a\a\xe8:d\xaf\xac\x96\xdf\xd86\xd7\xd1E\x0f\xeb\xc8\xe9\xe2\x17\xf2\xca#/\xc0\x89\x05\x14\x81h\x8fF)\x8e\x8aN\xe1\xf1\xd3?Vw\x90\xae\x0e\x8f1\xd4~\xd0\xfb\xf1 \x1d\x9f\x80\x15\xa6\xf4\x06m|č5u\xe0\x89Z6Fi\x17\xfe\xc8+\x85z\xa8~\xf2\xebZ9~\xf7\xffz$\xc7o\x95\xc1eH\xe7\x1c/}Ö+3\xb8\xd6p)j\xac.\x05\xe1\xab?\x00k\x9a\xe6\xacا=A\xb7\x12\x19\x12G\xadu6R\xb5p⽆\x15\xc0\xaa\xc1\x9c\x9f\x8f5\xc8G\xd5F\xe5\xc178\xfc\x80\x18\xd1g=\xd6Ӯ\xcb\xdfZ\xe4\x0f\xbeY9cE\x81?\x9a\xc8sH4\xc0\xf6a\xeaL\x02\xa7;9/2\a\x8a\x94#\xa6\x00U:\xbc+\xd1b8éQ\xe5l^\x86\x943vόc\xb6\xccF\x1cN\xbf\xfb2\xad=\x80\x1f\x8c\x05\xfc*\xea\xa6·\xa0\xa2\xc6\x0f\xe1/ٌ\xa2\xa8\x8e\x03G\xd8)W\xaaa\xd2:h\x80\xad\xab\x15{\x17\xc4u\xe2\x01\xc1\xb4\xe2z\x84J=\xe0\x12ބJ\xf0\b\xf3\x17v\xac_ߜ\xe0\xfa\xa7\xe8@o\x98\xe8M\x04w\xc8w]\x8f<\x82t\xa5p\xe0\xac*\n<\x16\xa2\xc3/\x04o\x0e\x89߂\xb1\xac\x01m:,\x02c~\xbd\x18\x8fP\x8e@\u007f~\xf7\xe5$⾾@i\x89_\xe1\x1d(\x1du\xd3\x18\xf9m\x06w\xc1:\xf6ډ\xaf|S^\x1a\xc2S\x9a5\xbaڳ̥\xd8\"\x90\xa9\x11vXU\xf3XoH؉=k!=\x1cۛ\x80FX\xf7\xa8\xb5\xa6*\xe3\xee\xe3\xd5\xc7eD\xc6\x06U\x84x\xc7\xd9i\xa3\xb8j\xe0r!\xe6\xbc`\x8d\xa3\xa4\x99>\xf2\xd1|\x9c\x81\xbc\x14\xba\xc0(/\xc2\xc6s\x16\xca.^\xe2\xc7\xe3ԟ\xbe\x89\x12`\x188\xfe\xb0$\xfaD\xe1B\xa5\xfa\x04ẽ֣\xc2=\xf85Z\x8d\x0e\x83|\xd2\xe4Ģ\xe5\xd88Z\x98-ڭ\xc2\xddbg\xec\x83\xd2ŜMs\x1em\x80\x16\xa1=]|\x13\xfey\xb1,\xa1\x93}\xaa@\xbd\x06\xfb5\xa5\xe2{h\xf1\"\xa1R\xad\xf8\xf4\xc2\xf9t\xe70\xa0i\x8c\x1c\xac\xf4-a\xb0y|\x9a\xc1Fo \xd6\xc5;n\xab´\xe59\x8dU\x9c\U0003468d\xfe\xed\xd2܇\x8b\xdb\x17\xb7V\xb9\xe1±?M~\xfc\x95/\xc7'\xc2\x1c\xc3ʈΩ\x1aC\xbf\x12\x87S;A钩\x17\x85\x0e\xbfx4\xc4Tf\x872\x94u\\un\x84\xaaP\xc2a\x9e\rw\xdca\x86\x86\xfeb\xaa\x8aI\x8c<\xa1\f\xbd\xe7\x04\xe8\xf1\xb94#\xe36~\xce,F\x14\xdaW\x95XW\xb8\x04g\xfdx\xfb\x11\a\xaa\x91H\x14\xe7<\xe8\xa7H\x15;\xbe\xf6\b\x88\xb5\xf1\xee\xd0\xf2\xb5\xaeԪ\xe2\x82Z+x^\xdbY\n:\a\xe5\x96i\xa6,\xee\xe0ԏ\x9b\x1c\u007f\xa8}=\xbef\x0e7\xb8\x9bX\x1d\xcd,\xbb\x9b\x97Ʉ&\xf6~\b\xd6\xf1,\x05\xb4\x17\x9d\xd3AK\x06\xa5\xa9\x92u\x1b'*о^\xa3eE\x84Ai\xd2H\n\rS=t\xa8\xbd\x8f\x9a\xbc_=(#\x1b\xb8\x8a\x81m\xb7\xa1`\xa3\x17\xf4\x13m\x95Q\xac\xacYu\xc4(\x91\xb1Y\x01\xa01\x961\x89CZ\x02\bk\xd8[\xad\xc9W;2\xf5Cl\xa9\x8dJK\xf2\xd9\xf8\xe0\xfa\xf0\xae\xfe\xbe~\xb7\x02\x10\x9e\xf2\xf1Ϫ\xa3\xc0ع\x06L\xd4z\x05`\xb0\xa3\x06<\x05V\u0093\xb3A\xb1\xf5\x8aB} M\xde\xd6ʮ\x82#\x91\xdc\uef0d\xae\x81\xd3F9݇T\xd2\xd9dC\x9b\xc1\xd01oi\x15\xf8\xd7\xc5\xedO*pVq:z\xd4K\x81\xe4\xed\xa0\xcc.j\xf43\x85\xe4 \b먁\x9b\x14\x8bCAr\x05\xd0C\x90c\xab\x00\xa5̠\xa2\xbe\xf5\xca0\xf9+\xabc7\x80Y\xc1\x97`\xcd-\xf2\xbe\x81z\x80\xbd\x9eA\x96u\a\xc0>\xec\xa8_\xf319\x97\xc8EP\xb6\x0f\xefK\xd8bO\x1d6\xbd\xa6ud>\xdc~\xbc\xff\xee\xeeL\f\xe0\xbcu\xe4Y\rP\x96oTC#)\x80\xa4 \xbcr\x9c\x19~\x9b\f\x16-\x90\xa9x(\x00\xefiȟd\x1f\x03\xd8-\xf0^\x05\xf0\xe4<\x052\xa5\x9c\xce\fCRB\x03\xb6\xfdB\x82k\xb8#\x9f\xcc@\xd8ۨe\xaa\xb9\x03y\x06O\xc2\xee\x8c\xfa\xeb\xc9v\x00\xb6٩F\xa6\x9e\xcfӗ\xf16\xa8\xe1\x80:\xd2\xff\x01\x8d\x84\x0e\x8f\xe0)y\x81hF\xf6\xb2J\xa8\xe1\xdaz\x02e\xb6\xb6\x81=\xb3\v\xcdz\xbdS<\xf4\x8e\xb0]\x17\x8d\xe2\xe3:\xb7\x81j#[\x1f֒\x0e\xa4\xd7A\xed*\xf4b\xaf\x98\x04GOkt\xaaʡ\x9b\xdc?u'\xff\xe7\xfbn\vo\xcfb-L\x06\xf6\xca\xecF\x1b\xb9\xb0_` U6\xa8\x00\xd8\x1f-Y\x9c\x80N\xa2\x84\xce\xe6\xe7\xbb\xcf0\xb8\xcedL\xd1ϸ\x9f\x0e\x86\x13\x05\t0e\xb6\xe4\v\x89[o\xbbl\x93\x8ctV\x19\xce\v\xa1\x15\x99)\xfc!\xb6\x9d\xe2\xc4\xfb\x1f\x91\x02'\xaej\xb8\xca\x03\x05Z\x82\xe8R\xe5\xca\x1a>\x1a\xb8\u008e\xf4\x15\x06\xfa\xcf\tHH\x87*\x01\xfbu\x14\x8cg\xe1T\xb9\xa06\xda\x18\xc6\xd53|MGН#\x91\xe8K\b\xa6\xa3j\xabD\xee\r\xd8Z\x0f8ӯ\xcfL/\xb7n\xfaZ\x14\x0f\xd1ݱ\xf5\xb8\xa3O\xb6\u061c*Mb\xfbq\xe9\xcc\x10\\\x9a,\xa5\x8diYqf\x1b\x80\xf7ȣ\xfeeT\xe6i\f,\xe6\xf3\x02\t\x99\bL\xedl\xd0\b\xfa%W\x94\x11\xc7\v9]/\x1cI)\xed\xed#\xd8-\x93\x19\x1b\xedc]Ȥ%\xf0Ѽ*\xd8rU|\x94\xa9\xf0\xb6\x8a\xfc\x85@7\x13\xf5\x01\xf7mԺ\xb7U\t\xdb9d\xd5jZv\x99\xbeT6\xaaX9\x96\xde\xffv\xbc\x0f鞢\xa7\x9b\xedB\x06\xf7\xe7\xda\xe3\xc2)\x82>\x94\x94\n\xf8\xf3;\xfa\xfc\xebk%\x80\xb3\xb2\x0f\xa2/\xe8\x90\xf2{E\x0e\x89r\xe5i2A\xab\xe5\xf6\x98\xe8,U\xdbDe\xca\xf1d{\x82\xdfW\x8d\x0fF\x8e\xe15\x03$\x1f\x18\xc0\x16\xd1{2ܛ\xc97\xea7\x8f\x10\x8d\x81G\xed\x93^\"\x17*\xe0\xd3\xfc\xc4\x10X2\x06\x9c\x04\xe3~{\xc4\xe9-\x94I[괭\xf5\x1dry\xeaT\xc9\xd0L#=)\xb1\xd5\xd4\x00\xfb8\xdf~i\xaeP\b\xb8\xbb\x94\xddu\xd1*\x97m\u007f\x04\xb0\xb5\x91\x9f\x81\x9e\xf7\xf3(\xe0\x02\x1d\x17\"u{\f\x97\xe2\xbcM:K\x05\xf14\xbf/\x87@&vs7\x15\xdc\xd0\xe3\x82tC(\xe7}\\\xc1\x8d\xe5\xe5\xadg3\\슙0\xa4w\x89\x1c\xf1\x1cJ#\x8f%\xb1}zg5\xf0\xf7?\xabSc\xa1\x10\xe4\x98\xe4\xcd\xf4\xef\xe1͛\xb3\x9f\x81\xbc\x14֔\xc7{h\xe0\xb7\xdfW\xc5\x15\xc9\xfbၟ\x84\xff\x06\x00\x00\xff\xff\xe08S\ft\r\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4Z\xdds\x1b\xb7\x11\u007f\xe7_\xb1\xe3<\xa8\x991\x8f\x89\xdbi;|s\xa4\xa6\xa36\x915\x96\xad\x17\x8f\x1f\xc0\xc3\xf2\x0e\xd1\x1d\x80\x028\xcal&\xff{g\xf1q\xbc\x0f\x90\x944ur/6\xf1\xb1\xf8a\xbfw\xa1\xc5r\xb9\\0-\xee\xd1X\xa1\xe4\x1a\x98\x16\xf8š\xa4_\xb6x\xf8\xbb-\x84Z\xed\xbe_<\b\xc9\xd7p\xd9Y\xa7\xda\xf7hUgJ\xbc\u00ad\x90\xc2\t%\x17-:ƙc\xeb\x05\x00\x93R9FÖ~\x02\x94J:\xa3\x9a\x06ͲBY\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&\a\xab\x19`\x82Ɛf\xb6\xf6\xbf\xa7\x1a\x0fr^$%U\xb4\x9e\x8b-\xa0\xd1\xf8\xf0\xa1_hN\xa6\xd3\xe9D\xd4\xea\x01-)\xa3\xe7 j\x85_\x1dj\xfe\x8b\xb2ǿS\xa6\xccl\xf3\xe3\xe4Qi9\x87\xab\x86\x9c\xa9>\"\x99\xc6\xe6x\x8d+\xa5\x95SFO*tB\n'\xe6\x13\x00\xa1\xb5q\x82\x87\x89\xff\x04ȍv֔%\xda\xe9\x1au\xf6\xd8,q٨R\xa2\xf5\xca\xd3֛\x1f\xb2\xbfe?L\x00r\x8b~\xf9\xbd\xaa\x90\x9c\xa8\xea9\xe8\xa6,'\x00ZT8\x87\xdaȍ)\x9b\n-\x923\x16)\xdb`\x89\xd6d\xcaL\xa8Ɯw][\xd3\xd4s8L\x84\xc5\x11Q8͝\x91\x0f^\xcfǠ\xc7O\x95\x8aܿG\xa7\u007fV\xe4\xbcH]6V\x94#8\xfc,)\xbdnJa\x87\xf3\x13\x00\xcaM\x8ds\xb8e(\xb5\xc8QN\x00\"\x01\x1e\xda\x14\x84\x94\x9eRQ\xdeY\xa5\x1d\xda+V\x91\xa8\x9c\x82Dʭ\xaa\x9d\xa7l\xaf\a\xcc\n\\\x81\xbc\xa5\xa7[(\xad\xf4\xda\x0f\x05\b\xe0\f,\x11\"\x12\xe9\x95\x01\xfcJF\xdf\tW\xcc!c\xe2\xb2\xda\xc8L'\x9dQ&p~\xdb\x1bu;>\a9\xab\xf4\xfa\x18\xb2\xff3\xa8\x0e\x9e;#\x9f\x88\xe4\xbe@/\x93\xd04ui\x84D˛\x17B\xcb\x12\x81-\x17\x9c\x15\x9aVh\x8f\xa0H\xcb\xeewu\x17ɧ\xa4\xaf5\xf3\x1cv\x9eCE\x90\xedl\xff\xd0\x1e:\xb7\uf751q\x01D\xa3\x06r\xc25\x04\xd4\xe4\x05\b\x82[\xdc\xcen\xf4\x9d5k\x8bD#0\xbcxV\x17\x82\xba8\x16~\xe2uq\xac\x8c\xad\x84\x9b\x83\xd2\xee\xaf\u007f9\x8e-.ʜq\xa2|\xbfsH\x1d\xa4\xf7\xfdဖ\x9dm\x1d\xaf\xff\x9b\xc0]2\xa4k\xa3\xbb\xbc\xbe\uf34e\x81m)M\x818\x1b\x04ю\xd6w\xeb\xae>)\\\x18\bӛ\x1fC(\xcb\v\xac\xc4ם\xb8\x01Nv\xa0\bD\\\x1aNq :\x85\xec\x8f\xffX\xdcC\xda\xda_F\x9f}\xcf\xfba!\x1d\xae\x80\tSz\xc5A\x97/qeM\xe5u\xa2\x96\xb5Q\xda\xf9?\xf2R\xa1\xee\xd3OͲR\x8e\xef\xfd?\r\x92\xe3\xbb\xca\xe0ʗ\x18\x1c/\x9b\x9a-Wfp\xa3\xe1JTX^\t\xc2W\xbf\x00f\x9a\xa6L\xecӮ\xa0]\x1d\xf5\x85\x03k\xad\x89T\xc1\x1c\xb9\xaf~U\xb2\xa81\xe7\xebc\x06y\xa9Z\xa9\xdc\xfb\x06\x87\x1f\x10\x03\xf9\xac\xa3z\xdcu\xf9[\x8a\xfc\xb1\xa9\x17\xceX\xb1ƟM\xd0\xd9\x17\xeaa{?\xb6&\x81ӭ\x9c\x17\x94\x03\x05ɁR\x802-\xde\x16h\xb1\xbd\xc6bmH9cw\xac8d\xcbl\xa0\xe1\xc8E\xf8#\x1by\xe6\x18\x1c\xee\xbdCX\\\xa1E\x9dc\x8a\x10\xa7*\x99\x91S\xb4\x12\xfa\x10\xe2q\xea\xe1D\xf4\x1c\x05\xfc\xee\xee&E\xcc\xc4p\x84\xee\x86\xfb\x9e\xa1\x87\xbf\x95\xc2R\xfa\x84r~\xef˛U\xd8\xcc\xc7\x0eg@@\xad0T\xa4\xfb`\fJ\x93C!\xc1\xacF5\xf2\xa3\x01\xd8\xc1,\xc6\x15oB\xa4\x88!\xe9\x10\u0099z\x10\x1c\xa3\x94\x84\u007f->\xdc\xce\xfe9\xc6\xfc\xfe\x14 \xf2\x1c\x89|\xbe\xc6\n\xb5{\xb3\xcf\xd9\x12IY\x94\\\xb8`V\t\xadVH.\x8b{\xa0\xa5\xcfo\xbf\x8c\xb3\a\U00013c40_EU\x97\xf8\x06T`|\x1f\xfe\x92\xcd(\nt\xec5\xc2V\xb9B\xf5\x93֞\x01\xb6\xaex\xec\xad?\xae\x13\x8f\b&\x1e\xb7A(\xd5#\xce\xe1\xc2W\x82\a\x98\xbf\xb1c\xfd~qD럂\x03]\xb0\xd0E\x00\xb7\xcfwm\x8f<\x80t\x85p\xe0\xacZ\xaf\xf1P\x88\xf6?\x1f\xbc9$~\x0f\xc62\x03ڴTx\xc5|{!\x1e\xa1\x1c\x80\xfe\xfc\xf6\xcbQ\xc4]\xbe@i\x89_\xe1-(\x1d\xb8\xa9\x8d\xfc>\x83{o\x1d;\xed\xc4W\xde)/\f\xe11f\x8d.w\xa1\xda\xdf \x90\xa9\x10\xb6X\x96\xd3PoH؊\x1d\xb3\x90.\x8e\xedM@-\xac;i\xad\xa9ʸ\xffp\xfda\x1e\x90\xb1A\xad}\xbc\xe3\xec\xb4R\\5p\xb9\x10r\x9e\xb7\xc6A\xd2L\x1f5\xc1|\x9c\x81\xbc\x10z\x8d\xe1\xbc\b\xab\x86\xb3Pv\xf9\x12?\x1e\xa6\xfe\xf4\x8d\x94\x00\xfd\xc0\xf1͒\xe8\x13\x0f\xe7+\xd5'\x1c\xae\xfd\xd6:y\xb8\xc7f\x89V\xa3C\u007f>ir\xe2\xa3\xe5X;\x9a\x99\rڍ\xc2\xedlk\xec\xa3\xd2\xeb)\x9b\xe64\xd8\x00\xcd\xfc\x93y\xf6\x9d\xff\xe7\xc5g\xf1\xaf\xeb\xa7\x1e\xa8\xf3\xe8\u007f\xcdS\xf1>4{ѡR\xad\xf8\xf4\x87\xdf~\x9f\x1cҝȹ\xd6Ey\xdb\xff!8V#\xe9w]\xffgnt\xf8!\x96\xe6\xf0\xf9\xcb\x04b\xab\xf1!\xfdX˃\xff\v\x00\x00\xff\xff\xd7w>\xba>\x1f\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec\xbd}s#\xb7\x910\xfe\xbf?\x05j\xe3\xfaI{\x11)or\xc9\xefn+\xf5\xa4\x94]\xd9Q٫U\xad\x94\xf5\x93r|>p\xa6I\xe24\x03L\x00\f%\xde\xf9\xbe\xfbSh\x00\xf3B\x0e\xc9\x01\x86\xda\x17{pU\x17/5\xd3\x034\x1a\xfd\x8enZ\xb0\xf7 \x15\x13\xfc%\xa1\x05\x83G\r\xdc\xfcKM\xef\xffMM\x998_\xbd\xf8\xe2\x9e\xf1\xf4%yU*-\xf2w\xa0D)\x13x\rsƙf\x82\u007f\x91\x83\xa6)\xd5\xf4\xe5\x17\x84P΅\xa6\xe6ge\xfeIH\"\xb8\x96\"\xcb@N\x16\xc0\xa7\xf7\xe5\ff%\xcbR\x90\b\xdc\u007fz\xf5\xd5\xf4\xff\x9f~\xf5\x05!\x89\x04|\xfd\x8e\xe5\xa04͋\x97\x84\x97Y\xf6\x05!\x9c\xe6\xf0\x92HPZHP\xd3\x15d Ŕ\x89/T\x01\x89\xf9\xd8B\x8a\xb2xI\xea?\xd8w\xdcD\xec\"\xde\xd9\xd7\xf1\x97\x8c)\xfdm\xf3\xd7\xef\x98\xd2\xf8\x97\"+%\xcd\xea\x8fᏊ\xf1E\x99QY\xfd\xfc\x05!*\x11\x05\xbc$\xd7\xe63\x05M \xfd\x82\x10\xb7&\xfc\xec\xc4\xcdz\xf5\u0082H\x96\x90S;\x1fBD\x01\xfc\xe2\xe6\xea\xfd\xefo[?\x13\x92\x82J$+4b\xc6͍0E(y\x8fk3\x13\xc0M zI5\x91PHP\xc0\xb5\"z\t\x84\x16E\xc6\x12Db\x05\x91\x101\xaf\xdeRd.E^C\x9b\xd1\xe4\xbe,\x88\x16\x84\x12M\xe5\x024\xf9\xb6\x9c\x81\xe4\xa0A\x91$+\x95\x069\xad`\x15R\x14 5\U000c8d63AG\x8d_7\xd6rb\x96k\x9f\"\xa9! \xb0Sv(\x83\xd4a\xc8\xccV/\x99\xaa\x97\xb6\xb9\x1c\xb7$ʉ\x98\xfd\x17$zJnA\x1a0D-E\x99\xa5\x86\xeeV \rr\x12\xb1\xe0\xec\xbf+\xd8\xca,\xd4|4\xa3\x1a\xdc~׃q\r\x92ӌ\xachV\xc2\x19\xa1<%9]\x13\t\xe6+\xa4\xe4\rx\xf8\x88\x9a\x927\xb8=|.^\x92\xa5օzy~\xbe`ڟ\x9fD\xe4yə^\x9f\xe3Q`\xb3R\v\xa9\xceSXAv\xae\xd8bBe\xb2d\x1a\x12]J8\xa7\x05\x9b\xe0\xd49\x9e\xa1i\x9e\xfe\xa6ڶ\x93\xd6\\\xf5\xdaP\x9eҒ\xf1E\xe3\x0fH\xe6{v\xc0\x10\xbc\xa5%\xfb\xaa]E\x8dh\xf3\x93\xc1λ\xcbۻ&\x9d1\xb5\x89}\xc4{\x83\xf8\xea-0\bc|\x0e\xd2n\"R\x9b\x81\t<-\x04\xe3\x1a\xff\x91d\f\xf8&\xfaU9˙6\xfb\xfe\xcf\x12\x94!h1%\xaf\x90\xa9\x90\x19\x90\xb2H\xa9\x86tJ\xae8yEs\xc8^Q\x05O\xbe\x01\x06\xd3jb\x10\xdbo\v\x9a\xfcp\xf3a\x8b\xb5\xc6\x1f<\xf3ڱ_\xee\xf4\xdf\x16\x90\xb4N\x8cy\x8d\xcd\xdd1's![\xcc\xc1\xbc2m\x01\xed>\xb4f\xd8\xd3o8\xd8\xe6_6\xa6\xf2\x97\xeaAC?f\x12%g\xff,\x01Y\x9c=\xb1\xb0\xc5R\xb6@\x12??$\x8b\xe9\xd6\xdfw\xe0\xd4\fxL\xb22\x85\xb4\xe2\xb6[k٘\xf1\xe5\xd6\v(\x8e(\xe3\x86\xfe\r\xfb7\xd3\xe6\xf5_\r;\xed\x981\x95@\f\x052n\xe1\x11\xc6q\xb1\x9d\x986\x83i\xc8;&\xb7wu\x04\xe5\x1c\x9de\xf0\x92hY\xc2\x0e\xccP)\xe9z\ab\xbcl\ue2d7\xeay\xc7\x102\x96@SPX\xd4X!C\xe5\xf6\x8c\xc8'\x8e\x15\xa6\f;\xf3\xab\xbc\x11\x19K\xd6\aQ\xd3\xf5\x92?n\xee\xf0y\n\x9e\xc1\x92\xae\x98(eǚ̑4\xcf\xdeג\xb4\xe6\xa6\xc203\a%\x8d[q'\xb6\x96B\xdc\x1f\xda\xfc\xbf\x9agj\xb6M\x12T\xeb\xfcZ\xa4\xdbn'Eg@\xe0\x11\x92RwL\x93\x90\xb4D\t\"$)\x84һ7~7\xf3!\x96\x1f\xec\xa2Z\xb2\x8fj\xb6V\xe6x\xa5\xdf:\xb3\xd0\x16\xdf\x14\x1c\xcc\\s\xb3u\xf5\xb3R\x94\xf6\xd9M\x01\xd7\xc0x7FȌ*H\x89pd_f\xa0ܷR\xdc\xfe\x9a\xb1\x9c\xed\x04]-ު\x1a\x19\x9dAF\x14d\x90h!\xb71\xd9\a\x9fv\xf4a\x96;\xf0\xd8\xc16\xdb\xf4_/l\x0fHb\xc8\xfcaɒ\xa5\xd5\x02\fm\"\x1c\x92\nP\xc89\x8c\xa6\xba\u07b5Hrh\xef\xddG\xf6\xf1\x8ez\x1c8S\x9b\xf0\xba\xf8I=z\xf0\xdbz\x1c\xe0\xbc[\x9c\xc5\xfd\xde):\xeb\xf1\xcbD\xac\x17%\x11D{\xb5\xf5\xeaq\x89\x16\xad*\xa3\xed_\xcd\t\xe4\x85^\x9f\x11\xa6\xfd\xaf\x87 \xd2,k|\xff3ޘp\x8a\xbf\xda|\xf3\xa8\x14\xbfwW\x0eA4\xbbR}\xfe3\xdc\x14\x14\x16\xb7NV\xf4ސ\xef\x9ao\x9d\x116\xaf6$=#s\x96i\x90\x1b;3\xe8\xbc\x1c\x03\x19}\xe4\x9d\x199\xd5\xc9\xf2\xf2\xd1h6\xaa\xf6@\xf5\xc4\xcb\xe6\xcbV'\xf6FB[0\x1f\x80K\xd0~e\x12rk\x17\xdf!6\xeb_Р\xb8\xb8~\r\xe9>\xf4\x90~\x94\xb7\xb5\x90\x8b\x8d\xc96?\xed\x14\xfd\xbe\xcbp\xaaOe4Y\x8f\xc7\x19\xa1\xe4\x1e\xd6Vc\xa1\x9c\x98͡\x1a\xf5\xddN\xf3i\x1b9\xe8z\xb1\xea1\xac\x11\x8c\xf3\xa5\x1c|\xbb/)\xd8q\x0f\x1d\xfa~\xd7h!\xd0\xcc\xc9Y\xb8\x16\x93\xe6\aD\x04Z\xde\xfd\x91G\xd0/\xe6y\xd1\xe1ő\xfe\x8c\xc4\x0f\x8f\xfb\x88eV\xdb\xd6\xf0\x1f\xe2ƞ(\xbbE\xe6\x14,Y\xd1s\xa1\xe8>T\x80\xa7\xc5{\xc6\xdeӌ\xa5Շ,\xdd_\xf1\xdd\xdap{\\\v}\xc5ϬI\xa6\x90J^\vP\xd7B\xe3/O\x82N;\xf1\bd\xda\x17\xf1xq˶\r\x1e\x9a.\xb6\x1e\xc4mǕ\xf5\xa4T\xdb\xc3\x14\xb9\xe2\xc6pq\xf8@\x87\xa9\xfd\xdc~\xf9\xd0\x1ey\xa9Ї\xc6\x05\x9f\xa0\xa8\x9cv}\xc9\"\xbb'H![;\xb2=\xb5\xea\xa3\xf6\x83=\xc1\xde\x19Ib߷.\xe0\x8c&\x90zk\x13\x1d\x97TÂ%$\a\xb9\xd8'8\x9a\xa30\xfc\xbd\xdf\x14zr];\x02)\xac\x9fh\xf7ñ\xee\xf4\xf0d&\xe6\xe4\xf6x\xcao\xf6\xc1Gw\xf8+w?zxE(bQ\xff8\x88]\x9a\xa6\x18\\\xa2\xd9M\x00\xc7\x0f؋m\xd9o'f%dN\vs~\xffLj9$\xe8\xff%\x05e\xb2\xc7\x19\xbe\xc08Q\x06\xadw\x9dg\xac\xf9\x19\xf3\x05\xa6\x88\xd9\xdf\x15Ͷ=\xe1\x1d\x8b\x13\x86\xb7@f\x05\xb9\x98oi,g\xe4a)\x94\x95\xa9s\x06Y\x97˦=\x98\"\xcf\xeea\xfd\xecl\x8b\x0f<\xbb\xe2Ϭ\x80\x0ff7\x95\xb6 x\xb6&\xcf\xf0\xddgC\x94\xa0\x9e\x94\xd8\xeb1\xde\xe9\xe7\xaeG\x8b,\x9a\xbe\xee\xda\xc9\xed\xd4\xdc}\xb3\xeeE\x87\x85P\xfa\xaf\xdd\x0e\xbb\x1d\xf3\xb9\xf1o\xb4u\xd3\x0e\xbf\xd7A\x9d\xdd\xf9\xb0*\xa6j4\xb9\xb9\x06\xe9\x9cx\x96\xd1z\v`\xa0mt\xc8IW9\xe8h\xe5Y5\b>@\x156\xe6\xd1g\x8a!Z\xa3\xc1K\xa0\xbe}\xf9\xd8\xf01\x9a\x13j\xfe\xdd\\ȱ\xb5\xdaD\xe49\u074c\xf2\xf5\x9a\xea+\xfb\xa6\xa7i\a\xc8\xee\xbe\\\x94x.\xfb\xab{\x9e\x860\xbe\xf7\xc0\xf4\x92qB\xfd\xf1\a\xe9\b\x8a\x92B\x1c\xe6Dv,\xa9\"3\x00^\xf9\xc6?\x05y\x9d3~\x85\x1f /\x8e.\xdfI\x8d\xae\xa8\xed\xf4\xa8\xae6\xb4\xfa\x01%N_\xd5H\xa4\xe4a\t\x12ZT\xb1\xed\xf06\x1acO\x90\\\xe8\xa6_\xc1\xc0-Dz\xa2ȜI\xa5\x9b\x13\xedKp\xa5\xeaK\x0e\x81;lVw\xc7r\x10\xa5\x8e\u0603\xcb\xfa\xedV\x806\xa7\x8f,/sBsQ\xf6\x10\xeev\x18\xf9\xc2\xf2*\x8a\xeav\xe0\x812]œ\xd0â\x85٥\"\x03\xddw\x8bg07\xec(\x11\\\xb1\x14\xa4\x8f\xf2\u06dde\xc2\x1c\xdc9eY\xd9\x15\xbe\xe9\x1a\xa1f*\xbf\x942\xcaJ}k\xdflx\r\x97⡍\xa0\xde(X\xd2\x15\x106'L\x13\xe0\x89\xd9\x17\x90\x96e\xe3'\x1c2\x105\xbdɲ\x1f\x837\x03x\x99\xf7C\xc0\x04O6\xe3{\x9db\xcdǿ\xa6,{\x8am3\x94\x17\u007f4\xbe\xaf\xdf\xfe G\xa3b*\xfdE\xd8\f\xc8;\xa0\xe9ڟ\x0f\xaa\xb51U\x91\x06\x04\x91%or\xc4'8\x19!\xf6\x9d\x9b\xc51\r7\xc6Y\x8f\x8d\xdd\xf0\xe73\xdd\xd4v\f\x88'\xd5v\xcc\a*A\x17㚹j\x010\xa2\xd2+\xce8\xf7\x8aj\x024\x9f\x19\x18\x03\x15R\xeb\xf42\xe2\xd3\xe9\xd16wiG\x18\xbcsu!\xaaˆ\x9b\xd7\x19\x9a\x8d|\xbf\xe0#\xe0\x1c\xbckQ\x92\aʵ'\xfaJ\x99+DO\xaa\x0f\xddU;\xa8\\\x04<\xbd\x95L\xe8UV\x9f\xd1\a\\\xcb5f\x98\xf5\x9d\xb4\x1d\xc64MEroԑ\x9c.\xe0\xe4D\x91Wo^\x1bR1Z\x87\x11\x19\x01\x12\xc1\x0ef#\xb1\x85\x14+\x96\x1a\xd5\xe9=\x95\x8c\xce2c\x04\xcfA\x02O@\x91/O\xdf_\xbc\xfb\xe9\xfa\xe2\xcd\xe5\xf3 \xe0\xc6t\x86ǂrC\x83\xa5\xf2Ҽ\xda}\xb3\x00\xe0+&\x057\b\n\xc3\xc6՜P\xb2\xf2\xb3M\xaa\xe4;cje+\xa7\xcd\x05A\xacV\xec\x1d!\x8c\x17\xa5\xf6\xde\xd1\a\x96ed\x16\x06\xb1\xe4ɒ\xf2\x85\xc1\xebkQ\x9ay~\xf9%bEBZ&\xee`\x06At\x87\xe9\xcb3\x17\u03a2Y&\x1e\x14\xca\x16P\t-\x1c\x8e\x83`6\xb6\x97\xa85\xd7\xf4\xf1%aS\x98\x92g_6\xfe\xf4,\b&b\xab\x90\xc2,\xd3\xc6#,\x163\xa6AҌ\xa1\x13\xb5\x84,;\t\x98e\x90\xb8\xb0#\xd8\xdem\xbe\x16\x12`\btL\xd8\xd1\xe6\xe8\x97\x15\x03\xb7_\x9e\x92k\xa1\xf7e\xa0\xed\x1e\x95\bC\x1cO;y\xfc\xe5\xf5ݻ\xbf\u07fc\xbd\xba\xbe\ve\xedM\xb1\xb0\x9b\xd5\xc71ɖX\xe8`\xf5a\xbb\xbfO,\xb4Y}\x10\xdc\x1dba\x8bՇ!\xb6C,l\xb3\xfa0\x16\xbc-\x16v\xb0\xfa \xb0\x9bba'\xab\x0f\x82\xda\x16\v\xbbX}\x10\xc8n\xb1\xd0\xc1\xeaÅжXh\xb3\xfa0\x88\xbb\xc5\xc2\x06\xab\x0f\x02\xdb-\x16FV\xdf\xf1Z\x18\xab\a\xbe\x8af\xf3\xdf9\xf3\xab\xc1\x8a\xaa=\x0f\xa3C-0\xe3\x80\xf16\x9f\xeb\xd2\n\x9e\x16\xf3m\x97 _\xbd\xa7\xed\xb4\n\xde\\l\x10dR\x1f\a\x9f\xb1\x8db\xad\xb2h\xc3XL\x8c\x95fǡ\xc8Y\xf7؎\xa7\xb9\x8b\"\xf1\xf8 \r\x9cL\xc9\x1b\x97a@ɫ\x9f\xae^_^\xdf]}}u\xf9.\f)$\xfe\xec\x10\x9f42\x105'\x1d\xe6a\x04^\xf6k\x0e\xc1\x02َB\u008a\x89Re\xeb*\xbd}\xf8ѵc\xf3五\xb25Q W,\x89\x99m\xe7Ԇ\xa8:v\x1cTx\xe2W\u007f@\xed\x89\x00\xbc\xdb&v\xcaO\fi\x1d\xd32v \x9f\xc0>\xb6〕\x1c\x01\xf1\xb8\nTc\x96{ը\b\xa0\xfbml\xd2;q\xb19P\xfdz\rsZf\xd6\xdb\xf6\xec\xd94D\x97\xb1c(\x8b\xfdZ\x8a\x9e\x01\x94\xe6h\xb1\xd9[{\x01\xcbG\f\x8e#\x84N\\blK\xedP\x81\xf6\xaa\x1d\xcc\xe5Nz\x9b2(o\xae\x1e\xf1R\x9eؐ\xf4\x9c-\xde\xd0\xe2[X\xbf\x83y\f\x88M\xb4cάK/\r5\r\xea\x81Z\x8f\x9dZ\fW\x1c\x8a\x17\x12\x92Q\xdc5Z8\xb9s\xd9Ϩ\xc3\x1a\xf4\xc4-\x89\f;X~\xc4iw~\xb4U\x99\x86\x9a\x17\r\xb1\xf2\x87辆[\"x\x02\x85V\xe7bet\ax8\u007f\x10\xf2\xdeXb\x0fL/'6\x1e\xa6\xce\xf1\x1a\xce\xf9o\xf0\u007f\x06\xcc\xee\xee\xed\xeb\xb7/\xc9E\x9a\x12\x81\xac\xb6T0/3\x9bv\xd7;ӷk\xd4u\x14\xce\xf0.\xff\x19)Y\xfa\xe7pf\xeb\xc7\x11hC\x146\x13\xf3H\xf4q\x8b\x91\xfc\xb5\x97R\x03peXx\xc5\x11\x88\x90\x18~\xeb\x93\x06\xbb{\xf8\x84e\xa7\xe8\x0eD\xfbL\x88\f\xe8f݊~\xa3\u007fh\xb8k\xf4K\a\xee\x1a\x01\xe1㮁'\xe08R\xe3\xa4\x16\x1b\xfd\xd2Y\xbb\x8738\v\x91\xbe$\xaa,\n!\xb5\xaaj4L\r#\b\x8bgԣ\x05\x04\xef\xf6\x9d\x91\xff\xac~Ļ#ꇓ\x93?}{\xf9\xf7\xffsr\xf2\xe3\u007f\xc6~\xa7\x86\xd9(\xafs\f\xc0\xaa\x80d\xcaE\n\x86e\x9f\xd9\u007f:\xcb\xeb\"\xc1\x04\x99\xeb\x01\xe8Q\x9a\xeaRM\x97B髛3\xff\xcfB\xa4W7\x03A\"\f\x15\xa1\x82\x92\xa3(\x01\xbbj݄\x8c\x16\xa5\xfb\x9a8\x83\x85\xa6+\x9fc\xe8\xfdksdn\xa8^\xf6O\xb1\xeb\x1a\x0f\x92i\r\x1cmU\x90\xb9\"b~f\xb8#\x9a\x02Cx\xb7 \xcfV/\x02#\x94-\x00\xc3\x05\xdbܣ\xe8Hۈ\xd8v\xecf\b\xc7\"\u07b5i؟\xf7\x12Tٔ\x03\x80^\xdc\\\xf9ZK\x1f\x11\xf1C%[\xb5m\x1fC\xbe\xf9\x84\xf3\xaf\x9fD\xcey\xe8\xc3D]\x9dSl\xef`\xf4\xbbɻ{d\f\x8b2Q\x9eօ\x99N\xed\x8fӤ(c\x99\xb9\x83\x90C.\xe4\xfa\xcc\xff\x13\x8a%\xe4 i6QZH\xba\x88\x16?~\xaa8\xc5\xfa_\xf6s\xb1\x9c\xbf\x81\x82홆%\xf14\xa0J I)\x8d\xb5\x93\xad\xbd\x8e\x02\xe9G\x93o\x15\xfdtW\x85\xea;\xdaD^'\xab\x0f\xb35k\xfe\x81n\x9c\x95\xc8\xca\x1c\xd4Ye\xa5\f\x00\x8c.M\xbe\"+*\xd5G\xb5\xb8R\xb6b\xaao\xbatנ|\xfd6\x925\x11\xe4\xb1v\x11\x8ckX\f0\xd1&\xc7@F\xa7\xf9\xe8\xcaG\f\xd8lQ\xea\xa2\xc4\xe4\xe1\x9c\xea**\xf5X\x888ϝ\x1f\x8d\x82>i\xd3a\xfa\"ƍmGA\xb5\x06\xc9_\x92\xff8\xfd\xc7o\u007f\x9e<\xff\xf3\xe9\xe9\x0f_M\xfe\xfd\xc7ߞ\xfec\x8a\xff\xf1/\xcf\xff\xfc\xfcg\xff\x8f\xdf>\u007f~z\xfa÷o\xbe\xb9\xbb\xb9\xfc\x91=\xff\xf9\a^\xe6\xf7\xf6_?\x9f\xfe\x00\x97?\xf6\x04\xf2\xfc\xf9\x9f\xbf\x8c\x9e\xf2\xe3\xa4\xf6\xd0L\x18\xd7\x13!'\x96\b\x0e\x16{\xd87\xa57\x1e\x8c!\xff\x84f\x19\xa4\x84\xe59\xa4\x8cj\xc8\xc2\xfd\xe4\xbe\x02h\xb3ܣ\xed\xf0i\x8bIF\xe9\rK\xca\xd3\f$\xd6+t>\xc0\x16|\r2g\x9c\x86\x16\f!Uz\x17\xba,!%4I\x84L]-8_ً\xcap\x8d\xbf\xe2x\xa8+5$\xcff\xbe^0\xe4Y&\x92{EJ\xaeYV\x97\x87\xf4\xb5!]o\xca`\xa8Q,\xa6\xfa\xcfIu&&؏\xec\xfc7\xf5\x9f\xf0\x87P\xe5w\x88\xe5ӯ\x9e\xef\xf6بA\tH\x1a\x98L)\xc2Ŗ\x1f\x98\x17,\x8c\xfab\x88\xaa\xae\xafZ\t\x9a8#\x03\v\x10\xb7\xbb\xeaPd\x9bX\x17\x8d\xde\aV!\xb1ch\x8eBD-\xa0\xe6\xd8S\xb68:\xd6\xe8\v\xdbf\x8cC\xb3~1Ú\xa8\xf1!\xcc\xe6\t\xb6\xfc\xc8Y\xa8\xf1A`&\xb1\xc7ȺQ\xdb\xd2\xce}H2\xbf\x14B\x93ӓ\xf3\x93\xe7[A\xad\x93x\xa8s\x96\x81\x95\xae\xb6\xc8RRmV4H\xc5\xf2\"[\xe3\xfe\x9c\xa4\xd8\xd1\xc9]\x87\x95e\\\x8c\x98 S3\xbb\xec\vB\x9d\x11%\x88\x96\xd4w\x19\x88\x9f\xab\x81f\x80kY:]\xe5\xf4\xe4\xe7\x933\x02:\x89\xcd\a&\xe4A\xf0\x13\x8dd4%w\x82\x94\xaa\x9ex4̵(\t\a[\a\x00\x1e\x8b\x8c%Lgk\x14\xf3\xd10E\xa9m\xf1El\x90\x88\x85\xb6.\x1f\x99v\xf7t\xe2\xc1\xce\xc9Wxڭ\xaa@\xa81\x86Vp\xbe\x04\x9a\xe9e\xfc\xfd>C\x97\\\xf0\xc9\u007f\x83\x14XƋ;\x88\xb1ޝ\x88\xd8Ys\x1c!\xe3$ƍ\xb0\xf9vd\x12\x83Q\x10\xbe\x81`\x95\x93lu$\xbd\xbb\xbb\xf9\x06t[\x84E\xa1\xc3\xcc\xc8\xe7\xe7\xa3K\x1b\xe4\\Ȏ.Ç\xc7P\xf9\xb7\x14*\n3d\xbb_\xabҶ\xfb\x845Rx\x9c\xcf\xd9\x0e-\xdai\xc9.\xa3\x91\\\xdd\xc4'b\xfd]\x94\x06[3:\xcb\xd6U\x15Y\x05\x9a<3S\x8fO{f\x1c\xf7\xf3\xaf@S\xac\xdb˕\x06\x1a\xa9\"\x1d\xe1\xa85\xe6r\x1c\xa5\xc6\xf6\xdd]Z\x90\x03v\xb4Y\x02\xcb\xd1\xfe\x14\xcfT<\x9b\xb4\x15^$\x14\x96\xfd\xba9~$&\xb9\xc5+\xec.\xb8\xdfg\x83r\x13\xa9o\u007fl\x97\xe8j;G\x16\xef\xf0\x83q\x9c&\x1e\x8a\x01\xb3\x1b\x9e%L\x06g\xab\x92\xae\b\x98\xc5\xd5 \x98\xee\xeeexZ\xda\xe68\xca\xfd\x92\xe8\x12^\xcd\xf1\x94h\xc2\xe9}|<\rK\xb5$q\x89\x88\xedׇab\xe0\r\x052X\xdf\xc2\xcb<\xd1\u05cd\xb7/\x1bkAh\x92\xc4\x15\x8a\xb2\xc3u/G\x86\xa5@\xaeB\x13\x1c\xeb1\x98\xc4\n\x11\xee\xbf\xf4cЅ\xb7\xe3\\w;\xcae\xb7\x8ez\x89\x92\xf02\x9f\r\xe0$U\x01\f\xa9k\x82q\x1b?\xc0\x99R\x15ۼ\xc6\xe9\xf9\x10\xee\x10}\x0fU\x18\xca\x17@^\x98\x99\xfe\xf1\x0f\u007f\xf8\xfd\x1f\xa6\x88\x86h\xa8>\xb0L9\xb9\xba\xb8\xbe\xf8\xe9\xf6\xfd+,\xe2\x16K\xe5Or\xb3\r\xcb6D˟vd\x1eA\x19\xec\x95\n\v\x9d\r\xd9ack8\xff\xb7u.\xab\xd88[sh\x81\xec\xe6#\xf1\x99!Bl\x82\x87\xe8C\xdb\xd9:)nEr\u007f\x04K\xfb\xe4\xeeՍ\x05U\x1b\xdbQ\xbb@\xb9w13\xbe\x12\xd9\xca\xf6\v\xbc{u\x83\b\x8a\xdbY\xf36\xc6\a\xd0շ6s\xf47\xe1mjN\x14T\x96\x17\xaec&%\x12hƔf\t~\xab\nSD\xda\xf7\xe2>&\x8b\xe7\x93\xf1+\x9c\xbc\xf5\xe9@\xe8b\x88>\xcd\x1b\xae\x89\x96\x8ba\b\x8bh\xb8&b\xaft\x8d\x1a\xc9\xd15\x12+\xea\x85\x1c\xa6Ǐ\x1aɧ\xad\x91|n22\xfa\xd5B\u00ad\x16\xc5\xc0\xac\t\v\xe4H9\x13\xbe\x13ݮ\xa4\x06\x92Fl\xa9\xed\x1d}qsUy\xc7E+\x11\x01\x93W\x82\xa1\xaa2Y\xfa\xd8\f\a\xa5\xce1=\xa2,\xac\xe7\xcb7\x94\f\x8fX\x15\x12\xb0\v\x9f\xe0gUE\x02D\ap\xfb#\xe8$\xfc\xb4\xa0O\xc6厸x\xa2߮\xa1i\x18\x89\xa4j\tة\x02\x1e\x99V\xbe\xdb5U\x82\xdb\x10\xae\xdb>&\xc2\x03\x98L\x91\x82*e\x03w\xba^\x84\xfdȍHO\"\xa2\xb7\x8d\t\x91\x85\xa4\t\x90\x02$\x13)\xc1\xaa\u007f\xa9x\b\x9f\xe7\f\x16\x8c+O\xbff\xa2\xfe`\x18]\t\xa2\"\xc2u\xe7\xd9w\xad.Rخ\xbcԉ\x88\xe0\xc3\xeeu\x87\xc5\xcd\x04\xa2\u0ad38Ms|J\x9ae\xeb\xfa\xa0\xfa\x9b\x9e\xfa\xf8\x9b\xb4\x9dI\x14\x8b\x84zݛ\x99D\xe1\xd1\xc0V\xe6\x919\nuV\xd2\x10\xf2oQ'S\xe6T%\xcb\x01m\xbeHx\xfa\xf2\x98\xdatp\x8c\xa9M\xbdǘ\xda4\xa66\x8d\xa9McjӘ\xda\xd4\vĘ\xdaT\xcdhLm\xda3\xc6Ԧ1\xb5\xe9\xf0\x18S\x9b\xc6Ԧz\x8c\xa9M\xbdƘ\xda\xd4c\x8c\xa9Mcj\xd3\xce1\x06\x12\xc7Ԧ_c qLm\n{}Lm\n\x18cjӘ\xda\xe4ǘ\xda\x148F\x8ddLm\xfa5j$\x9f\x9b\x8c\x1cT],\xf05\x9f\xc7s#\xc5l@\x99\xb1\x1b\x8cճĥ\x01\x89ytm\x1d;\x9d)y\xd5J\xcfpM\xf8m\x95\x96 \x88.ѧNO\x1aZ\xaf'\xb8&\x93/\n\xa6\xce\va\xff_\x9dS\xd0H&\xb0\xfe\xb5\x10\xe1\x10+|c\xb2\b\x0ee\x10D\xf1\xba\xfd\xd9\x03\x98\t\x10\f\xf3\x98\x99\x03C\xb4\x9b\x01\x19\x03{\xb2\x05<\xd8(\xe6ڝ)\xb0\x11\xf1\x8fN\x05qY\x02\xdb\xd1\xfe!\t\x17\x98\n\xb7\x15鏄Xe\a\xec\x8a\xf2ǩ\xe4\xea\xf8\x11\xfe'\x88\xee\x1f?\xb2\xbf'\xaaO֢\x8c\x82\xb9#\xa2\xef\"\xf3\x91\xb4\xd9\x19\xcd\xf7Q\xf98\x98ݑ\xfcVD>\x96\x98\x06E\xf1\a\x04\xa7\x06*\xd7\xf1\x9e\xe4hM\xc9%\x1b\xdf-%\xa8\xa5Ȃym\x8bϾa\x9c\xe5en\u06042쑭\xaal\xe6p\x1a\xf1yNV\xeb\xb0a8\x03\x98\xa5\x80M,)\xcbbJ\xd5ai\xbd%E\xff\x84*\x93\x04 5r\xf2u\x1d\x02\x0f\x86\xf9\xfbi\xb5r\xdbU\x83)\xf2\"\x94\xf2lCE\xb4\xef~\xff\xbb\xa8ݏ\xb1\f#\x136\x0e'k \xe4`L\x0eO\xd4\x18\xa2n\xc4:R\x9e&9cOb\x06\xf9{\xa4hؓ\x94AX\x9c\x98=RB\xc6 \xce90\x11cO\x12\x86\xc3Q\xa4\x02\xb2\x9d\x80\xb1\x99H\x11\x87\xf2\xf8\xe4\x8b\x01\xb2\xed\xa9\x92.v'\\Ē$\x19\x9cl1<\xd1\xe2\x88\xdd\x0e\xeb́\xc1\xdd\xf1\a\xb9\xe8\x8e\xe09\x1c\x98T\xf1Th9F\n\xc1G\xec>\x1b\xbd\xabC\x92'\x06&N\fI\x9a\x88M\x98ؓ,1\xc4\xd3<0Qb\x10\xf9Ć#\xa2C\x11\xc3\xc3\x10\x83C\x10{\x12\"\x86\xf4\x84\xed\f=\xc4\xf6/\xf4\xa3\x1dv\xd8\b\x1fD\xaa\x85͐\xc3QC\aG\x0f\x1b\xc4'1\xecO`h$\"\xc4\xe2p;yaH\x12\xc2\x00\x8a\x8ee\xfeQA\x95h\xa6\xcd8ӌf\xaf!\xa3\xeb[H\x04O\x835\xa3\x8d\x86H\xd5yU\x16\x9c\xb5\xcc\xc33!Z\xb71\x97\xd4u΄\xd4_\xa8\xf5ѐp֊\xea#\xa1\x18\xa70\xab\xd7\xedۓ\x1f7nA>\x9a\xcb\xc0^)=\x06\x11\xfcU<\x101\xd7\xc0\xc9)\xe3\x9e\x0e\xc2\xfd\xa8\xb5\xb3\xa0\xf6\x17U\xc7\xda\xfc\xf5\xc5W\xe1\x9c\xcbN\xe6\xf3u\xec\xa0kK\xa9\xa7\xf3\xeb\xb9\x0f\x1c߱\xe7\x00\xcf\xcbp\x1f}˹g\x1d\x84m\xfe\x1e\xbcyu\x1b\xbe\x178o\xcfM\xd0K\xed\xca6D\xc0\xfcL\x89*:\xed\xec`\xca\x19\x89\xe8<\xb6/ݬN\x1d\v\x06\xbb#լN\x1b\v\x9f\xe8\xae4\xb3\xa8\x94\xb1\x8f\xee\xe1\xdcH\x13\x8b7?w\xa4\x889\xf5,R\x89\x8fN\x0f\x1b\xed\xb0\xc0\xb1'\rl\xb4\xc3>!;\xec\xf3\xb00\x1a\xb5N\xbe\x914\x81\x9b\xa3\xa9\x99\x9e]\x91\xb4\x94ԉ\f\xaf\xe0E\xd9\x1b\x86\xc9p\x80\xd4r\xaa\xaap\rV\\\x99\x97YD\xf1\xaa\xb2\x10\xbc]\xfd\xc9\xe6T4\x8b\xb8\x04\x03u\xd9.\x1d\xabv\x8aR\xcc\t-\xa4\xb0j\x1f\x91%\xe7F꺳d\x90bl%\x15'!\x9b5{\x14[\x98\xed2*\x16V\xc1a\x11\xf2\xe5a\t\xdck\x99n\xc2fvs!\x136\xcb\xd6dI\xb3\x98\xf0\xcb\x03\xd3KB\xc9=\xcb27\xcd)\xb9\x05M\xf4\x92\xa9Hgj&\xf8\x027\x83\xda\t\xc3c\x01\x89Q;\x92\f(/\x8b\xb8\xf5\x1beu-J\xe9\xd7\xef\xda\xc6\xf9Y\xc6$mp\x96\x9d\xf9\xad>Q\xfb\x0fl\x04bm\x82b\xa9\xc0\xd7iz`\nΆ`ַ\x19\xb5\xe7\xc0\xae\xbb\x90b\xc5RH\xc9l\x1dG\xff\"E\xaduJ\xde#<\xcf\xf7\xb9\xe0\x13\x0e\vjl\xa3\xf0\x93j\x85\xb8=\xf3v\x9e\xb6\x1e\x05OYBu\x84\x8d\xa5\xb0\xb0^]N\x8f\xac\x18E,4(7\x18\xe8)\x17D\xa0R\\r\xa6\xd7\x18\x1b]\x96\x9a\xa4\xe2\x81?\x9fb\xeb\xd9\x18&E\xc9\f4u\xf7Zm+A\x14X\x8a\x00\xa7\xb3,F9\xc1LܻN\x02%s\xa0\xba\x8c\xe8\uede0\x1a:\xfd\x01\x96\x1e\x8e{\x1c\x98r\x11\xd09)\xb9\x82\xe0\xfb3\r\xfb\xf0\x8f\xff\xfa\xe1\xecC\x96\x83(\xf5'\xe5 |X\xb2d\xd9\xf47\xb0\x1c\x14\x11\xe5\x90kkZ\x90\x17nZ\xdd\x14\xf1\xc4\xed#\u007fq^\xc5(\xad14\xc4\xde\x117ڬ\xe6W%N\a\xad\x9b\x1a\x1e\xf6\xfa\xfa\xf6\xa7\xef.\xfer\xf9ݔ\\\xd2d\xd9,C\xca\t5r#\b&ʕ%]\x01\xa1\xa4\xe4쟥\xed\xe0\x9e\xc4E\xa7&m].\xf0]O\x05a\xd6ِ\x8a\x02\xa1\x89έ\x10\xa0\xb1`&9-&\xf7\xb0\x0e\xd2y\xe3\xb1\x14\x85\xa3\xedI\xdb\xc5\xe7\xb4\xffM0\t4e\x9fP1\x05ǥ\xeayuWU\xc8\xc5*\xd0i\x84֞\x87\x0e<-\x04\xe3Zu\x95Z\b\x02\xbbm2~2)\x8bc\xa9\x85\x8e1\x96Z\xd89\xc6R\vc\xa9\x85\xb1\xd4\xc2Xj\xa1{\x8c\xa5\x16\xc6R\v\x9fU\xf2\xf4Xj\xa11\xc6R\v\x11c,\xb5\xb0k\x8c\xa5\x16z\x8d\xb1\xd4\xc2\xf6\x18K-t\x8e\xb1\xd4B\xc7\x18K-\xf4\x1dc\xa9\x85j\xfcr\xae\xf8\x8c\xa5\x16>\xd5+>c\xa9\x85>\xe3\xf3\xb8\b5\x96Z\x18K-x\xbc\x8c\xa5\x16\x82\xc6Xjac\x8c\xa5\x16>W\xa2\x1aK-\x8c\xa5\x16\xc6R\v\xdd\xdf\xfd\xb5\xdbac\xa9\x85O\xd5\x0e\xfb<,\x8c\xb1\xd4\xc2Xja,\xb50\x96Z\x18K-\x8c\xa5\x16\xc6R\vc\xa9\x85\xb1\xd4¯ȫ\x18\xa55JP\xa2\x94I\x98\x1d\xdc&\xb2W\"/J\r\xe4\x9d\aU)\xcbA\vGY\xc2T\xf3F\xff\x87\xedD\x98\b>g\v\xa7\xe8\x9d\xe7\x94\xd3\x05L*\xfcL\xea\xfbw\xe7\x1f\"7>c9\v+\xb2`F]\xb1\xe0f\x80\x87#Ҡ\x1ejN\x0f4\xa6\v\xaa5H\xfe\x92\xfc\xc7\xe9?~\xfb\xf3\xe4\xf9\x9fOO\u007f\xf8j\xf2\xef?\xfe\xf6\xf4\x1fS\xfc\x8f\u007fy\xfe\xe7\xe7?\xfb\u007f\xfc\xf6\xf9\xf3\xd3\xd3\x1f\xbe}\xf3\xcd\xdd\xcd\xe5\x8f\xec\xf9\xcf?\xf02\xbf\xb7\xff\xfa\xf9\xf4\a\xb8\xfc\xb1'\x90\xe7\xcf\xff\xfce\xf0T\x8fl\x9c\xb6\xcf\xe3wH9uJ\x11\xf2\xed\x9c>\x1a\x06\x1bN\n\xb9(\xb9\xb6\xb7l\xec1\xafN\x84M\xc3\n=\x94\xe4S9\x98d\x88\xa1\xed\xf2\xd1\xc6\xf3\x190\xc6\xf3i\xcf\xe7;G;\xed\x13\x1a<\xc7ܩL{Nh0L/\xb8\xd1Э\xe6\xc9\x14\x119\xd3Ɯ\x8e\xb9f\xdc(\xa4\x82WR\x9a.j˫µ\xbc\xb9\xbdM\xc1T\xf3\x82F\xe3R\xb8\xf0\xb6o\x8cZJy\x1d\xa7@\x9e3Ia\xce8\xa4V=\xfd\xf5\xf1\xbb\xa8\xd7\x14$\xa5dz\xfdJp\r\x8fA\x8e\xfd\xf6y\xb9m\x03\"v3\xc2\x0f\x8d\x9f\x10\x11\x85\xbdv\xb4Q!\fo\xfe\x85\xa9\xac@d\xc9џe\xeb\\\x80\xb6\xce\x1d4\xc3\xf1f\xcf\xc6\xe4\x83\xc0{\u05cb\xf5h\xfd\xb3d+\x9a\x01\xd7\r\xe87h\x1c7?\xf0\x14\x1a\xb2\xa6꾦J\x98\x18S\xa9\xc2۹G+\xfe\x04\x8f\xfa\x83hǨz\xdcH\xb6b\x19,\xe0R%4\xc3\xd32\xcc\\\xbe\xd8\x015\xf8\xc0\x1bTH\x91)\xf2\xb0\x04É\b\xf5.D,\xb2\xb0\xa0\x11Iٹ٫\xc2ONY_\xa7Q\xf4\n*\rUx\x1fe0`\xacE4\x13\"s7&\xb3u=\u007f\x16\x17\x82\xe2\xe2'\x0e\x0f?\x99\xd9*2\xcf\xe8\xa2rM*бi\xa2\xf5Q\xadܱG\xdb0\xa6P\xae\x13\x9a=е\xaa\x1d\xdfqW\xcb-ė\xe4\xc5s\xe4\x0fT\x91j\x8e)\xf9\xdds̰zuq\xf3\xd3\xed\xdfo\u007f\xbax\xfd\xe6\xea:\x8e\x8f\x9b=\x83\xc0\x98\u007fB\v:c\x19\x8bQ<\xb72Ǜ\xc0Pp\xa6\xe9y*E\xf8\x95%ķ\x8f\x85\xd4\xf2m\x98w\xa9Y\x11\x0e\xc9nޚp\xb8\xefRR\xae+\xa7w\x83\x1ce\xc95\xcb?\xe8\xa5n\x9a\x0e\xbf\xd0}\x91\xa6\x90\x0eC\xc9\xf1\xee¼\xf2\xd3X\xd7\x05颠\x12r\xf3\xf6\xf6\xea\xffn\xd0\xe6\xba\x18\x92\xa0\xff\x11\xee\xa2\x12b\x0e\xd2\xe0=~g\xebW\x8c\xbb\xbcw|~7\x8e+=`XN⻒\xb7\xcb\xd9\xd6p#\xf4\x93\x14\xa6䦊\x14\xb7\xa05\x98z8\xaf\x93@\fH\xae\x19ͲuS\x13\xd6\x02k2\x84\a5\xf9\x8e\xdc\xf59\xcdT0#\x8f\x97\xc6F\x91yc\xcc\xf7A\xbbXA!)p\xa1\x9d\xc7/\xea4\x889B#֧и,ВxQJf-\x8c\x99\xf28\xbf\xa9f\x8e\x11\xa6\xf0\x94\x0f\x05\x9bf\x9c\x13ƕ\x97!*\xe8/\x81\xa6XS\xa6\xa0zi\xf3Ts\xaa\xee!\xb5?D\xea\xd8UH\xd6̸Z\xfaݺ\x80\xe8x*\xea\xd66\xfb\x17\xe3\xbc\xe1\xde\xd8\x01\xe5\xbdh\xfa\x96g\xebwB诫2&\x83\b\xf9{g-\xb5\xe3@\xc1HYb\xd9n3\xbf\tn\"\x96miVZq\xd4\x17c%|`\x06!K~\xa1\xbe\x91\xa2\fV\x05\xb6\x94\xf5o\xae^#\xbf,]\x86\f\xd7r\x8d\xa5\xa9b\x98D\xfb\xccU\xf6\xd8\xdf\\NST\xb6M\xc5\x1e|\xb8\x9e\xbc\xa1kB3%\x9c\xe1\x18\x11\f\xee\xf2\x90\x10窉\xb9\x19=\x13z\xb9\xe9\xd3A\xf6\xb0\xfd\x9d\xf0\x02Fu\x82M\xe5\xc94K\x18\xe2BB\xb0\xf4\x1e\x14)$$\x90\x02O\x82\xa9\xf7\xe3\xa4A \xe5_\vn\xd8\xcb ڿ\xf2\xf9?\xd6e<̪\xc7L%g\xd3S\xccWB\xe6R*\x9069L\x96\x10\xb7\xf1ߖ3\xc8@[G\t\x16\xb9\xa5\xdaz\xfeXN\x17ᧉ\xeaJ\x14jA\x80\xabR\x82s\x9ak\x92\x8a\b3\xc0Ց2K\xff\xdb\xd5k\xf2\x1595k\u007f\x8e\xe4?\xa7,\x8b\xa9\xfa\x82\xb7?6\xb8\t\x9b\xfb)\x1a\x94\x86\xeb\x04\x1c\x8d}iY\xf5\x19Ⴈ2Yz\x9c\xc6x\x87\xbc\xf3\xcaݐ\xc2+l#k\xfa\x04X\xd3@\xc1\xfa7\x05r\xb0\\\xfd\xdb\a\x90\xabC\xdc`\x867\xb5w\r\x19\n\xc9AӔj\x1a\x13|+y\xa3:\xe2\xc6Q\x88\xa1\xdd\xfdG\x01I;\x18\xe6\xaf\xec(|\x1c)\xad\xe0;\xc6\xcbG{;`\xb8C\xf9\xf6\x12\xc1\x11\x17J\x8a\x91(3 \xb4(2f\xcb\xf7m\xb4\x88\xb9j\x91n\xdc\xdeo\x9b\x9a(\x1eh\x96\t\xa3fD\x84\xc7%\xe5\xa9ȷ\x16o\fQ\xa0\x11Vqc\xc1\x1d\x87s\xd7a\v\x97\xdd\xf5\xe1\xfc\xb5\x1d\xb6!\xae\xfb\fV\x10Q\xa5|\xb3\x89\x92\x81b\fRO5\b6\xca\xfb\x99\xd1\x19dV5\xb4'Gm\x9f\x9ch\xafh\xa4SU\x8alxɋw\"\x03\x9b\x1b\xef\x91d\xc0\xfebp\x84/\x0f\xc5\x11z\x9fZ8\x8a\xf6\xa2\u007f\x8a8*#4<\xb2\x89#\xa3&\xb6qd\xc0\xfeBp\x14\x1d\x82P\x90$\"/n\xa4\x98\xb3\xf0ú%\xfa\x1d\xb8:9'\\\xf4\x97\n\xba\xb2\xc8Q\x8fD\xe0\xe1\x1a\xb9\x9b\f\x95\x8dKOT[\x99\xe7nq\x05\x03\xfd\xff\x1a*\x04r\xed\xb3\r\xbd\xc2}5|\xb6\xcd|\xa1B\xa4\x1e\xd0\a\x95n\"\xa1\x19v$\x8a\xa3\v\xb2I\x1b\x9b\x00\a\xdc\xe7\"\xb6\xb1\x9d\x83\xe3s\xfa\xb0%\v\xfe\x12\xe1\x19 \xbe\xb9\x9fH\xa1Q;\xde^\xc0\xbb\xb3\xf7e\f\xec(\xc0\xfeZ\x9c\xd1S|\xf2U\xeacW\xe6\x8bq\xd3\x15\xaeT\xf6\x9b\xaa\xa7\x92A8\xf04\xb6\x12TA\xf5\xf2\x8cH\xc8\xf0\xe2\x9egh\xf7֡u\x12\xb7O\x8d\x05{\xce\xe07\x0e\xf5l&x\xdc%v\\5\x86\x05\xbcF{K\xccm_\xe2`\xff\xc1\x0e\xf7F\xed\xae\bw\xa6\xecqoXwE0ȏ\xe3\xdeX䊾\x92滚\xd1\xec\xb6\b\xef\xf0C6i\xf9\x9b7\xb7\x17m\x90q\x9c\x1d\xf3z\xa5U\x8f\rLBӜ)\xc5\x04'\x0f0[\nq\x1f\x05\xf7ԧ\xce/\x98^\x96\xb3i\"\xf2F\x16\xfdD\xb1\x85:w'{b\xb0\x13\xd7\xe4\x84\xf1\xcc\xdfz\xb0\x1eB\xae\x95\x8f\x18\x98\xc5\xc4iY\x15V\x91\x00]\xa7B\x97ຍ\xf6\xeb\xd8\"UxcძTۤx\x1dYP\xfc\x009F\xe3\xc5U\x97iT{\xb2\x84Y\xefK\x9c\xb85{iC?\x1f\xbe\x8d\x805\xd5\x12P\xc3\xdb\b\xfc\xb5\x86ER\xb0\xc5!\"\xed>6o5\xf4\xae\x15\x12\x1bю\xb4%O\xb0v\x9b\x9b\xe2I\xd3\xe9\x10Uǃ\xf8\xa3\x82ަ\xacX\xd2\t:\bP\xde\x18\x81\x16\x05\xd1\x1b;K\xc1\x85\xb4\xc7ۨ\xf4\x82G\xf4\xfc\xb6\x03\xfdW6\xdf\fi\xd6)\x1a\x8d\xedz\x15\x9f\xeei\x86K\x87\xc3\xf46\xac\rd\xd4\x16\xeb؉/S\xff\xc0\xf4\x12\xdb7-\xa1\xf5\x81x\xccJP\x98\xb0\xc4\tH)\xa4\xbb7\xe2\x13\rb\xeb*[\xfd\a/\xb7\x18\xa6@ͿNԐ\fZ\xd2j\x1fk>\xa0\fǁ\xf9\x1c\x124\xd9\x1b;\x17\x05܆hN\xeb~c\xeej\xb8aB曑\xc7+g\x8f\x06\x03M60\x10\v\xbe/V7\xc8\xe7SB\xae\xe2\fQ\u007f\xb1\xfb\xccp\x9a&tw\xb3(\x96\x14x\xab35n\xa2\v\xe7\xc5I\x06\xc0\xac^3\xa3x\xc9\x10\x93oA\x9a9\x17G\x11Ø{မ#\xe8\x98P\xacOl;\u007fc+\x1f#\xce9\xb6\x99\xc3\xe1\xfdc\xd11\x84=\xb9\x1c\x84\x85\x87q\x89͙:j>\aّ\xd3\x11\x9f\xddD\x9e:É<]\xb4\x99\x1c!\xe2L>J\x94'\xeeⷭ\xe8<\xb0\xcd\xefm\x03JãiT\x8f\xa0\xb5;qj\xab\xdaWU\xb1\xb3\xb5\xaf\xc6\xcf\xfe;4g\xbe\xdd~\x9e\v[l\xa0Y\xea\xde\xf55\rSSJ\xaeY\xe6\x83Wy\x91\x19\xe3\xb15\xe3\xe0lH\x84\xd5\xe87|V!\xa3no\xec\n\xfd\x87\x9d\x97\xffB1T\xb54\xf6\xf5\xbco\xaaO\xd9\xe8G\xa0\x06\xec\xda\xcfc\xc96-|\xbc\x8d\xa4l>\a\u007f\xc39P\xec\x15T\xd2\xdc\x18\x0e\x8a\xb8\xd4\xdf\x19,\x98\xbdfZ\xa9V\x81\x11\x8a\xaaHؙU\xf7\x98&9[,\xad\x97\x86P,E\x19^nR\v\x92\t\x9a\x12\xe4\xe2B\x92\a*sc\xb1\xd0d\x89\xf5\x1b)'i\x19|\xf0\xb1\x93\xdcz\xa24\xd5@D\x01\xb6\xa4\x84\xdd\x1b\x83o[]+\x8cL\xc7\xe6\xd3c\xf3\xe9\xdecl>=6\x9f\x1e\x9bO\x87\x8e\xb1\xf9\xf4\xd8|\xba\xf7\x18\x9bO\x8fͧ\xc7\xe6\xd3v\x8cͧ#\xc6\xd8|z\xd7\x18\x9bO\xf7\x1ac\xf3\xe9\xed16\x9f\xee\x1cc\xf3\xe9\x8e16\x9f\xee;\xc6\xe6\xd3\xd5\xf8\xe54=\x1b\x9bO\u007f\xaaM\xcf\xc6\xe6\xd3}\xc6\xe7\xd1\x1anl>=6\x9f\xf6x\x19\x9bO\a\x8d\xb1\xf9\xf4\xc6\x18\x9bO\u007f\xaeD56\x9f\x1e\x9bO\x8fͧ\xbb\xbf\xfbk\xb7\xc3\xc6\xe6ӟ\xaa\x1d\xf6yX\x18c\xf3\xe9\xb1\xf9\xf4\xd8|zl>=6\x9f\x1e\x9bO\x8fͧ\xc7\xe6\xd3c\xf3\xe9_\x91W1\xf2\xa6HʂZ\xb6\xf5\xe8\x16\x10S\xfb\xc4\xd7\xee4\x8c\xac\x9c\xcfA\xa2\xf0\xc5\xd9y\xe5(\xee\x12\x98\xef\fU\x89n\x97\x11\x8b\x8d\x02%\xd0\xd4ּ\b\xf3\vvN\xcb\x17!\xc5\xf6e\xf6^j`x\x80\\\xbe\xfd\xbav\xbaƴ:\x88\xbb\x1d\x88\xeby˓\xf8\xbbB5!tTg\ríM\xc0O2\xa1\xdc=YDv\xb2\xa4\x9cC\xe6\x94n\x16\x86\xd9%Ud\x06\xc0\x89(\x80[\xb5\x85\x12\xc5\xf8\"\x03B\xb5\xa6\xc9rjV\x10\xe6=sD\xe0\xba\xd6\xd53UZ\x02\xcd-1H\xc8C\xfb\f\x9a)\x12\x9aH\xa1\x14\xc9\xcbL\xb3\xa2\x9a$Q\x80\xa52\x02\x93\x96\xae\xe6\xf5\x06c\x92x}\x01\xf5\xacZE\xf0\x1cm\x19\xb4ƙ\xd7T\xea3l\a\x9b\x17zm\xefR\x85\t>\xec\xda)\x95&Iƀk\xb7j[\x9f\x11\xe7yFBs\xe3\xf1\xfa\xae\xdd\x05\xe5P\xcbS\xf4\x8a\x14Zٛ>q\x13uSL\x99r\xde7uF\xa8\xf6\x822\x98\xe8=-!\xd9{\x05\xce\xce\xda\xfd\x149ͺ\xa4\xbf\xaa\xaf\x9a\xd5\xccp\x9e\xd10\xc5\xd03\xa5\xb3V-\x87\xda>\xc4$wd\xabA`\xb1\xec\x90\xc5\x02\x1e\x1c\x0e+\xc3? \x01\xb6B\u007f\x90\xe1\x8cA\x107\xb9\xe8\x933ц\xee\xfa\x06\x94\xa2\v\xb8\tL\xb1\xd9\xe5 \xc6,\x9b\x9a\xb8\x02\r.\xacF\xa6EC\x87\xab/\xa0\xb4-\xd0 \xb0\xb9]ces>H\xa65 \x11c\xe7*\xcc<\f\xcc\x03ߚ\\\xf3z\xcc\x1b\xffA\xfb\xa1P\xaa5\xfa\x14O\xed\xa5\x8e\x19\x90\x99d0's\xc6i\xe6\xeea\x84]8\xc2~\x16T\x19ҤJ\x81D\x9f\x8bs;y܄\x11\xec\xf7\x0e\x91Z\x96<\xa1\x8d.\x97X\xfd\x8e\xcd\xc9\x02\xefz\x04\x1a\x13K\xcaɿ~\xf5\xef\u007f$\xb3\xb5т\xd18\xd6BӬ\xda\xc0\f\xf8\"\xb0\xb6\xbf\x13O\xed:d\x15%d,g\xa1n!c\f\xfc\xee~\xd6\x0e4\x9e\xa7\xb0:o\xd0\xe7$\x13\x8b0\x9c\xbe\xf2\xf7+\xab;\x93!\x8a|\x94k\xbf\x83\r\x88\x8c%\xebhF\xe0\x9b琥x\xb0\xae\xbc\x81'\xb6\xbe\xe2X\x88\xa2\xcclJ\xc6\u05fe\xb2d\x10\xc8R\xc1v5\xacN>\x18J\r~j\x9b\x1d\xc2\xed\x95)\xb7\x940\xa5\xc5\x15\x9es\xa1\xf1\xaag\x0e\xfa\x89\xbf\xa6Y6\xa3\xc9\xfd\x9d\xf8N,\xd4[~)e`\x9b}\xa4~\x8f\x8f\x8c\x1a-fY\xf2{ly[\xd7\x1a\x16a\xd2V\x94\xba(\xb5\xbf\xe4\xddt\xef\xfa\xcd\f\xae\aY)h\xde3\\\xcf\x0e\x1e\u0379E\xf7l\x18;p\xc5w,w\xc9Ģ\x9a\xb7\xf2\xcc \xf4F\xd0\xef\xbe\xfa\xd7\u007f\xb3,\x8b\bI\xfe\xed+\xbc2\xaaά\x10C\xdd\xc0(\xb29Ͳ\xd0`V\x93\xc1\x18\xa2\x9fv0\x89'\xe7\x11:\x9e\x1d<\x89\xc9}w\xf7w\xb4\xb7\x99V\x90\xcd\xcfl\xed\x11\xefA\f\x02z\x82J܉\x93\xb2X\xe4\xe6#\x18\xb4+\x91\x959\xbc\x86\x15K\xc2\"\xfc-T\xb7\xa0\xf8HPƔ&\"\xac\n\xc4,\x13\xc9=I\x1d\xa0\xc6\u074c\xcd>\xd6!\x98\x89\xb8\x85\xb2su\xf5\xfd\x13\x12ڌ(\xa7EQ\xd5r\x90\xf4\xa1\xb5X\xe4%\xc1\x17Phl\xa0:>\xab\xc3N7Ta\xf7\xef6\xb0Z\x03\xf2\x04S\x84J?;\xdc\xcd뭆T\xbe\x83^T\xae\x81\xdb\x13\xab\xa7\x99\x9dC\xce\x1c\x1e^\x1f\x90\xf4\x10w\xa7\xa7\x85c^\xe5\n\xe4T;\x9b&2\u007f\x06\xa9\xb6\x00\xa9\x982\n\xcc{<\x13\xaf2\xca\xf2\xf8\x1b\xfe1\r\t\x06\xb4\x80\x8d\xc9K\x984\xe84\xf0\xc5`D\x0f\xaa{\x14v\xb7Ų4l\xe9\x1b\xcf\xf5oD\xea\x00!\xab\xb6m\x98\x8d)\x1bL\x0e\xbb\n=\fR8\x86\xb2\xfd\xf75\x8e\x9a\\߮3\xfc8\xe3\x01\xb20\x1d\xb3\xff\x18\xec\x1b'\u007f\x04\xee\x8d|\xdb-chݹ\xa6\xc3\xc6\x11T\xc3\xf4r>\x92\xa9͍\x8d\x00o(\xc8M\x8f\x9c\xbc<\xf9\xa0<ܢ[\x8a\x82.\xd0\x1a\x19\x88\xf5Mp\xc3\n\xcd\x1a3\x19!V=c\x10.\xa4Um\xf3(\xa0\xf6B}-\x87\xbd\xf9\x84\x95\xc7\" >\xd05\xa1R\x94<\xb5\xb1\x87:(\xf5f\x03\x1dׂ\xc7L\xd9E\xbf]\xa5\xa9\xaa\xa6-\xa6\t0N^L_|\xf5\xb9\t~\\Ɇ\xe0\x8f,\xfc\xdc\xe0[\x1f\x14\v\xbee\xfb@L\xbcq.ֺ\xc3zT\xd9I\x1b\x03B \x0f\x92iG\xcd\x0fL\x019\r\xf5\x9a\xfb!d\xb3\x96\xe5\xf3\xb6K/\xaaw\xfb\xb0\xa2\xa7\xaa\x9c=\x81d\xb0\f=\x02=Ȅ\xba|\xf1*\x1ef\x87Xi\"\xfdYL\xa7\x8fS;\x9b\x13[\xf5\xea\xf9\a=$n\xcb.\x1f\x8b\x88\xceq\xadm\xbb|,(z\xfd\x8bz\xffb\xceI-\xc2w\xef_\x04\xdc\xddj\xc1_`IWQ\xf2O\xb1\x9ceTf\x98Zvk1If\xa5&\xc0WL\n\x1eu\xfb\x82\x90\x15\x95\f\xab\x8dK\xc0Z\x90\t(\xf2\xe5\xe9\xfb\x8bw\x98\xa1\x1dS\xb8˖\xe9t\xfbS*[^|(F\x1b\x8b\xdc<\x045IG\xc0\xb5\x87\xc0\xe3\xd3P&j\x00\x1e\xbf4\"U\x89\x90\xbc\xd4%Ͱ`[\x92\x95\x8a\xad>\xa4,\x8a\xb5\x1c+]\xfb\x17d8\xba\x92\x81\xafY\x10\xbf\xd9(\x8fX3\xf2\xad\n\x84\xc1\t\x1b\xa8\f\xd6\x15n;\xd3j\x02\xe9\xd87\xa8j&\t;\x87\xba\xab\x9ej\xaf\x10\xba\x9eo\xa1\xa9K\x1b\xd9\x069]|\x04\xd7z(M\aQe0=\x86Q\xa2\xcb\xfb\xec7\xf5\xb6Zl\xdft=\u05ec\xd71\xa7\x8f\x98PI\xf1\xb8\xf6\\\xa1\x98\xe3,\xc8{\xc8@\n/\x96\x1e(\xd3\xd5}Sƙ\x0e\xee,\x81\x86\x93\xad\xa7\u070f\x00\x82\xb6\xbe\xf7\xbe\xf4|\xf0\xf0\xb6\x1d\"\xb3\xbddup\x16\xfb\xbe\xbf\xe7eƓ\xacL\xe1UV*\r\xf2\x1d(Q\xca\xce\xe8\xc7Ft\xb9\xf3\xadƥ\xd2\a\x17pJ\xec#\x13\x95\x88\xa2\x93=\xc8\xfa\xe5J\x9fq\x93J}\xc1\t\xbc\x9f\\ݤ\xb1\x95\xbd\x94\x16\x12vT\xd6\xe6e\x96m\\j\xec\xec\x9b`\x9e3\xdaɎ\xbb]\xfb\xec\a?EcH\xaa\x82\xf6FY\xe3\x05\x9b\x80\xaf2\x96\xa0Þ\xfb?\xd8\xff2\xb3v\x1f\xe9X\xa1\xddK\x9b\x84\x8ayY\x18\x9d=\xc3\xe4\n^\u007f\xc1VP\xb0\x1f\xde^\xfeN\xa7\xe0ރ\xd4\vi]t\xe8'\x12Hd\xf5\xf3\x1b\b\xf3\x94\xd3\a_\xdbd\xd3\xc4XM\x83\xee\xb9\x19M\xee\xcb\xe2\xd3B\x1fv\xa0\xbe\x85\fu\x83\x03\xa8\xfb\xae\xf9\xacE[\x0e\x9a\xae^L\xdb\u007f1\xb65\xcb4f!w\xaaf\x0f6\x13\xd2`\xcd^8Kي\xa5%\xcdZ\x14\xd8\xc0Y\x8dZ\xbc\x90ʲ\xae\x04),\x89\xeb\xdeoḺ0\x18|V\xf7{\x81\xd1\xf1c\xd4o\x97\n\xdb͂\xdb\xeeōW,\x16]\x1c\u05f5\x03W\x1e\x8f\x8e\xb5\x1b\xfbag\x9a\xed\x9d+H\xe8\x9fÕ_\\\xbfޥ\xde\xecuٷ\xa6z\xb1g:\xee\xccT\x1b\xbe\xaf\v\x83S\xc4ܝ/uF(\xb9\x87\xf5\x99M~\xe5\xae\f\xbd\x03b\xbb\x06;\xb5\xe1\x1ev\xab*\xe6e\vo\x17b\xfa8\xf0\xefa\xaf鉶\x8e{XWawċ\xf9\xc1\a@kT\xb86\xee\xfbe\xff\xde(g/}\xc3c\xad\xf7\xf4+4K0\xc4gIŬ\xe1D\xb9V͂\xab%+\x0e%\xc7PL\xd9\x16s\x8f\xfd\xaay\xaf\x05o\xe9\uf29f\x91k\xa1\xcd\xff\\>2u\xe0B\x8e\xd9\xcb\xd7\x02Ե\xd0\xf8\xf4`\xe4ة\xf5F\x8d}\x1cI\x9a[\x1e\x89\x97\x94\xf0\x1b\xd52\xaf\x0e\xdf\u007f\xafP\xcc\x14\xb9\xe2\x86Q9\x1cT\x97\x15\x95\x03\u07fcc\x88\\m\xbf&j\xbf݂o\xd1j\xbe\xd1\xc4\\\xf3S\xfbQޚ\x86\x9d\x82\xf5hۿ`\x82v\x91\xd1\x04R\xd7g\xc2l\xbc\x96TÂ\xedo?\x90\x83\\`\xa2A\xb2ܷ\xaa\x1e\xa1Þ\x8a\xf71T\xe4ݬfR\xa1\xfd)Th'CP|\xee\xc0\x86\xef$F\xb3\x9b\x83\x1c\xed ƶe\x91\xfd\xb4\x13\xe6\xb40\x94\xff?\x86=#\x11\xfd/)(\x93jJ.\xdc\r\x95\x1d\xdfm\xbe\xe1t\x9d&p\x03\x97)bvaE3\xb0\x85\x8a)'\xb0\xb7\xfc\x8a\x98oI\xcb3\xf2\xb0\x14\n%C\x1dDzv\x0f\xebgg\xad\x13\xb2\x03\xa2y\xf8\x8a?;\xab\xe2e\xadCY\xc9)\fa<ÿ=\x9bn\t\xd8\x1d\xb0\x0f\x88ݽT\xb2珕\xd6\xfdƦ6m\xef|_\xfa\xd8K\x1b[\x15\x18\x9b\xdfl\x11GS9n\x99\x15]\x9f\xa4r\x01\xba\xcb\x04q\x1a3\xa62L\xc9\x05_o\xc1ŋq\x9d*\xb73\xe2*:+Z티\xa5%fH4@\xb9\xc4%\xd5m\b\x9b\a\xb7wmϦ\b\xd9\xd2w\x0fY\x1co7\x1e\xb7\xa9\xa8V\xe3ۯ?w\xa9\xceL/#\xf5g\u007f\x83\xab\x03ju\x89^/a]\xe1\xf3\xbf\x04\xe3ub\xe0\xdbw\xd5\xf9\x9an\x98\x02\x9d\rS\x1f \xcb\bU\xdb\xcbw\x8d\x86\x121\x01#\xb4\xccNzzp\xa5\xb5\xcf\xf0\fv\x19\xa8U[\x9d\xbc\xd9\x1f\xaa\xbf\x11\xb5_õ\xca8\xfe\xf6\xcf\x12\xe4\x1a\v\x0e\xd4*Oe\xd0u\x9fq\xcb)\xb0\x05\xa8\xe7]\x8e\x01\x1a~\xb3\xa5\xf9\xd7\x1c\x83\\p+\x83;\xc1n\xcc\x11\xe1\x80jZ;\x86?\x1bCfǣ\x9dP\xb9\xa8\xde\ue987\xbd\xa2\xa6\x9f\xe5\xf3ԶO\xb8\xf5sP\xefx\x12\v(\xde\x06\xda\x03\xd2\xc8\xc0\xc3VP\xdfD\xa6\x03\x96\xd0\xd3\xd9B\x87\xac\xa1\xdej`\x1f\x8b(\xd2&:\xb8\x80\xa7\xb0\x8a\xc2\xec\xa2\xdeh:l\x1b=\x91u\xf4\x84\xf6\xd1SXHq6\xd2\x01\x90\x95\x05\xd5\xd7J\xea\x99b\xd9;D\xd1'\nt8n\xb5\xcf^\xeaa1\xf5\n~\x1c\x9a\xe9A\xbb)\xccr\xea\x85\xc3'\xb2\x9e\x9e\xc8~z\n\v\xeaim\xa8\x83V\xd4A\xca\xd9\xfb\xe7h\x1f9\n\a\xb9\x82k\x91\u008d\x90]\xf9\xdc\xed\xfc\xac\xcd\xe7;\"X\r#Hd)ޫ\xc5G;\x16\x85\xba\xbc\xd3\xe3\xe3\x16\xd5\x1dlr߿y\u007fh=\xef\xaa\a\xf7/\x84b/=k\x9fu\xacüo\xef\xd6sZ\xa8\xa5\xd0\xe4\xd4\x17\xb1J2Q\xa6\xce\b\x91\x1d\xf9]\xc3Wy\x8b\x975\xfb-\xd4>\xdbZ+K\x96\x8dx\xceC\x95&\xe5\xa0w\x91\"\x16\xe1@@x\xd0PHbŵZ\xf5\xfe0a\nL\xe8\xd9\x1f!\xdb\xc2\xc4\xe5\xe6;\x1b\xcd\xc0j\\\xf8\xdb\xfc;\x0e\xac\xad\xa0 tE$5^\"\x02\x14\a\x99\xf4\x01D\x91\x83\x92\xa5G8q\vY\xdb!\xc5!\xc8\xea@T#\xb0ȴW\xabQ\b4Ì\x9f >\xf7\xf0c\x95,!-3\xe8n\xfe\xbf\xd5.\xc2?\xea펒\xb3\u007f\x96ЬM[e\x8a\xb8\xa7\xbb\xcedͫ\xaa\x10w#\xb3Ɉ\xb4\xbf \x9f\xf5_rHw\x90w\\Io\x82\xb4\xb2[(\x8d\xf5B\xb8n\x14?\xf7\xe1\xdfĵ\xc9t\x8fw\x96\xbb\xf1k\xd8\xe5\x96\xeaرn\xb5m⾺\x95\x99\xbecgT\a\x9b\xdc\xc3\"\x13Z\xe8R:2OJ\x89}\xb9\xebV\xa2\xd4c\xae\x8bM\xeefZ.?\x87\t~\xc7rP\x9a\xe6\xc5\x01\ny\xb5\xfd\x86\xd9\x00!SUU\x1cm\x86\xea\xeb\xe6\xd7]\x8e+Z\xb7\\O\xa7\r\xd8\x16\f\xea\xfd\x064\xa4\x04V\xc0\x89+\xb6\x01\x954\xe8:\x88wh\x18\xcb\x15\xe6\xe4y8x\xefv.$\xc1\xee\xf6\xd5Է)\u0097pK\xa9\x86IgE\x9f^\x12\xb2\xf3\xa0\xe3u\xf9C\x92\x11k\x108E7\xc1\xfbZf{\xb3̾\xed+\x00\xb8\x92;\x0f \x81,\x80\x1b\x14wj\x02\xce\\\xb2-\x97\rb\xdd\t\xae\x12)\xeel\x97꒺\x0fXIYyw\xbb<\x83H\xc9\xf8Hg\xb5\x93}\x85\xec\\\xe5\x85w@U\xd7\xfd\xa1\x16\"\xben>\xeb\xacb\x8b\x03\\zBm\xbf\xfa%\x10\xe0\x9aIا!\b\xfcr\xc0\x19'\xa4XRu\x88]ޘg\xaa\xd6؍CYq\xcaw;\xe6\x04\xbc̷\x81O\xc85]\x89\xbc\xaaX\xeae\x1f\xd9\xfc~\xe3\xf1\x8d\x84F#\xa5k\x88N\x9ev \xe7\x94ͭi\x91\x98Yo_\xa2\xf9\xc0\x89\x89\x0fTr\xc6\x17\x87\x16\xff\xbd{\xacC5q\x10:\x94\x93\x8eET\xeaJ\x90r\xe2'\xb9\xe3\xceM\xa5\xb0\fPO:\xcf\xd0֏H\xc8i\x03\xc9\xeeK\xee\x97Z\xad\xb7\xe5&]°\xc5\xed=\xe3\xe9K\u007f1\xaf\xc8JI3\xf7\xcfDp\xcb\x16\xd4K\xf2Ï_\xf8\x05\xbd\a\xa9\xaa\x1f\xff_\x00\x00\x00\xff\xff\xc8\aW} \xfd\x01\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec|\xbd}\xf8\u05fbN5\xf4䠚%H\v\x02\x1eX\xa8\xc1T\xcb\x0f\xdcN80H\\C\xe5\xa8Eap\x15(\x93\xd6 \x01\xb4\x81\x02\x8dԩL\x02E\xb9\xb3\xdd\xe92Ka\x83D\xdcuݡ0\xba@\xe3dX6\xbe\xb4\xd4D\xab\xb6\x87\xf1;\x9a\x94o\xe5\xa5\b-\vN\xb5\x180\xad\xe8\xe0e[\xda\x06\u007f&p\a0P#\xa1@o~\xc3ĭ\xe1\x0e\r\x81\tX'Z\xed\xd1\x10\x05\x12\xfd\xa8\xe4\xffְ-I\xaccArX\xad\xe5\xa6\xf0\xe2S\"\x83\xbd\xc8J\xbc\x00\xa1R\xc8\xc5\x01\f\xd2(P\xaa\x16\xf5\x99\xee-\tkX@\x04\x93j\x8b\xd5j\xdc\x1a\x9d3LTi\xa1\xa5r\xfc#\xc9$\xaa>\xf9m\xb9ɥ#\xbe\xff^\xa2uī5\\\xb3\xcd 9,\vZ=\xe9\x1an\x15\\\x8b\x1c\xb3ka\xf1\xd5\x19@\x94\xb6+\"l\x1c\v\xda\xe6\xae\xdf\xd8S\xad\xf5!\x98\xa6\x11~\x855~W`\xd2Y2\xd4One\xc2\v\x835_\xad\x02z\xdaϗ\xe1U\v\x95\xea\xa1\xe6\xfd\xfaIm\x13k\x13\x8e`B\xa5b\xd6G_F\xa8ɟ0/h\xb9Πx_5#\x14\x89Fi\xed\x82\x04c\x19ԛ\xae\xb4\x1a\x1c)\x15\x1en\x87D\xaf\xbdL+\xadqD\xcdi\x8azĶ\xa2\xcc\xdc\x03\xd9q\xb4\xf7\xfa\x1bZ'\x93\xa1\x96\xbdI|\x1c\xec\x18\xf8\x8d\x96(\xecvhhq\xf2\a\xd6w\x83p\x81\u05ccŔU\x9ex\"\x93\xb9\xf1\x14 ݙeP\xe8\x14\xf6~$\xd8\x1c\x02\xd2Ǽi\xf8\xb3\xd1:C1D5\xfc\x9ede\x8ai\xed\xe6\fҥ7ۛ\xa3N\xec\x10\n\xa9H\xca\xc8\xfd\"TU\xfdud\x9el\xaf\x84A E!\x95\x87\tһ%\x9b\x11\x81\xa3\"\x1d\xe6#xNJ\xa4/\xe4x\x8aM\x86W\xe0L9$\xeb\x01\x860F\x1c&h\x16\x9c\xe6%$\xab\xfbT\xea<\x93\t\x12\xb1j\xa5\xcdTcҌ\xcc\xef\x1f\x90`;\xad\x9fb\x88\xf4\x1fԮ1N\x90pl\x02\x1b܉\xbd\xd4\xc6\xf6=\x1c\xfc\x8eI\xe9pl\x1d\t\a\xa9\xdcn\xd1\x10,v\xa8k\xff{\x8aX\xd3*\x82\x8a\x99f\xfcѼ\x1a\xa6\x13\xf3\x98\x1acSaU<\n\x15\x18q\xd2\xd8e\x01R\xa5r/\xd3Rd \x95uB%~~\xa2\xc6ox~0'\x10G\xf8{\x05\x1cfA\\\xeaX6\xad\x90\xdc\xd1\\\x9ba\xe1\b\xe5\x18\xcc8\x196\x824\xa0\x1e3GM1\x14\xc5U\xa8\xa4lR\x1b\xbds\xd1p\xca;\x85\x99\xd8`\x06\x163L\x9c6\xe3\xe4\x89\x11\x02_b\xf5\xe7\be\a4ic3HPg\x95hS\x9c\x86\xe7\x9dLv\xde\u007f#)cX\x90j\xb4\xac1DQd\x87\xa9IC\x8cdT\x83\xcd)\x8d\xa6D\xa8\x8f>\xdc1EҔH\x1dܔ\x19mܥz-6oD\uf829~H\xd8o\x8f\xba\xbf\xbc\xb0\x13\xb9%\xc5w\xb7[\xc0\xbcp\x87\v\x90.\xd4\xc6@%\a\xab\xc1\xe3oƸ\xd3V\xcbm\xbf\xf7\x8b\xaf\x96\x17\xe1Z\x8d\xc6߄il\xac\xee*[\xb5\x88a\x9f\xda=/@nk\x86\xa5\x17\xb0\x95\x99C\xf6\xa5\xe6\x10m9:\xb3\x9c{I\x02\xc5\xda^*\xb9p\xc9\xee\xa6\x0ei#z\xf4h\xd5\a\xe0\xfd\xf2\x10\xc30\x0f\"@B\xedT\xf0.\x884\x98\xfbݕ{^\x1fM\r{\x80\x1f>\u007f\xc4t\x8ed\x10/\xa9G\x93\xfa\xd0\xf3t\xda(\xf0\x04\xa3@\xb6&\xc5nZ\x1d\xe3\xf9=\xb4\v\x10\xf0\x84\a\xefY\r\x06\x97C\x85X+j\x90\x06yC\x8f\xd5\xc8\x13\x1e\x18T\xb5C\x17\x05o\x89\xa8\xf8\xf2\x84\x87ئ=\xa2\x12~\xd5\x1e\x85\xa7.U\xf0,b\x96RSj\xa2Vk\a\x9c\x8e\x9b,,SJ\xa1\x04\x8a\x9f8\xed\x9aa\x9dm\xe9'<\xbc\xb3\x9e}\xb4jv\xb2X@\x01R\xd8`\x91WX؏}\x10\x99L\xeb\xc1x\x9d,\x80x\xab.\xe0\xb3v\xf4\xe7滴\x84\xa2J\xe1\xa3F\xfbY;\xaeyU\x12\xfbI\x9cH`ߙ\x97\xa5\xf2f\x81\xe8\xb2h\xfc\x06\a6\xa1$\xa25ۤ\x85[E\xf1\x99\xa7\xcf\x126\xed0 \xe7\xd1\xcaK\xcb;\xbaJ\xab\x15\x9b\xe90\xda\x02\xa0m\xbc*Vi\xd3\xe1\xd4\xc5B\x88\x83(V\xe8ݓ\xb5\xf2_\x8e\xf6§\x8a\xc1\"\x13\t\xa6\x90\x96\xbc\xdf\xce\x1b\xef\xc2\xe1\xa3L G\xf3\x88P\x90݈\x17\xaa\x05\x9aܗ\x13\xa40\u07b5\b\xa52\vi\x1cb+Z\xf5\x91-\x03\x9b\xa3\x9a\x8f\xec\xb2O7\x8f\x9b%\x9bw\xf6\x87\xa2\xa8\xdf>\xd2]fY\x16\xf2\xeb\xd8\a\xf1Hz\xf7#\x17\xbc\xd9\xfb\a\x99W\x16\xef?㬡\x90Ʈ\xe1\x03\x1fhg\xd8\xee\x1fv\t[CE\x81$L\xa4\x05\x92\x93\xbd\xc8\xc8} \xe5\xad\x003\xefL\xe8\xed\x91\a\x15\xa7b\x9ew\xdaz\x9b\xbf\x95\x98\xf1\xe9\xd6\xf9\x13\x1e\xce/\x8e\xb4\xd7\xf9\xad:\x8f\x83I:\xffHi\xd5^\x8bV\xd9\x01\xce\xf9\xdb9;fK\x96\xc8\t\xce\xdb\x02\xa9\x8en\xca\xc7\xcfKB\x01\x8a\xb5\x83\xd7B\x9d\xebCZr\xe1\xe7f\x11-Ӆ\xb6n\x11Z_\xb5u~\x03\xb0\xe3n\x0f\xec\x10\xc6D\u007fծ!\x88\xadC\x03\xd6i\x13\x0eDI\xed\xf66ȉ\xf3v\x9e\xf7\xc4\xeaz7\xd2\x03\xa6 \xf3\xbc\xd1\x10^\xa7\x9f\xfb\x93R\xfa\u007f\x1ef\xc2\xce\x12\xc3.\x8cN\xd0\xdayQ\x8a\xb4\x1c3\x1b\xb6\xf5f\xad\xf0\xc1\xdb6J5\xc7l%\x87\xb2\xcc\x15'Ҟ\x10\xd8\xdc|o\xed;\x93\x1a\xa2\xdf1\xa2|\n\x8e\xc0\x89Ny.\xfa\x87\xf3\xd1\xe8^\xfb\xdea\x01V\xc0|\xc0d\x1eKV*\xcb\xfc\xe6J$\xffj\x8eG.\xd5-\x0f\x04\xef_\xcdY\x81\xa0\xca\xf1\xd4P\xe6:\xf4o\x18RW\xc4Ư\x10\x8e\x9a5\x9f\xd5\x18\xecp\xf6\xf8$#\x9eS@δҮ\xbdYS\x8d\xf4\xce\xc2V\x1a\xeb\x1a\x84\x17@\x95\x96\x8f\x93_7\xc6T7Ɯ\x1cb~\xf1\xbd[ۊ;\xfd\\%F,\t\xac\x03\xf1wb\x8f \xb7 \x1d\xa0Jt\xa9xË\xd4\x05\r\xb3\x00\xa2g\xa27&\x916\xb3\xd5Y\x95y\x1f\xa8\xc5\xe7\xa5-\xcdF\xab\xf3\x87\xe6\xad\xca\x0f\xe4\xa0-\xbb 0\x9bo\x16\x834\xc4d\x99\r\xe7\x8f\xcd@]\x92[\x16\x1b\x83G\xe4\x91\xc5g\x8fő\a\xf8\xc6nl\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\xe0\xa8|\xa8\xe3\\\xa7\xa9\xa9\xccfA\x8dg8M\x81\x1d\xca}\x8a\xc8k\x9a\x00\xd9\xcexZ\xec\x06\xccJ\xd3L\x83\xe1[\xf5\xa1\xcc\xdb\xda\xec\xffC\x02\u007ft\xd2\xdat\\\xe0\x98\xa8\xe4K\xaf\v\xf1>x}Cn\xf5x\x8c\xe7\x9d\xed\x13\xdc\xea\x11\x90\xb7[\xc8\xcb\xcc\xc9\"k]\xafw;<\xc0\xb3\xcc2R\xe7\xbfi\xbez\xb990\xb4/\xdfj\x01\x1e\x03\xd9\r\x10\x84\x85g\xcc2\xfa{D\x85\xc4?\"\x91\xe8\x15\x92\xcd\x19\xdf\x06\xaf.\xc8W/P\\\xf8\\?\xbe\x97\xca\xf6,'H\xe1^\xfd\t\xe1״\xb3\xeb}t\xae\xfb\xbdDs\x00\xbdGS{5\xa3b\xd6\xdcW\xaa\x96\xa6-\xb3F\x95T:\xc9?e\xd2U-㫡^\xd0\xf0Ay3\xdbǕa\x91\x0ei\x82\xa3)\xd5I\xb1\xd0\x18\b\xa5k\b#\xfdc|\xe9%\x17x^#Tz\x89`)ʭx\x8d\x80\xe9\xb5B\xa6\xa5AӒ\xa3˨\v8\xaf\x11:-\t\x9e\x16y\x80\xf1\x17l^\xebb\xcd+\x04Q'\x87Q\x8bH\x17{qfq0\x151\xbf\x99\x8b2G\x1eW\x04\xc8\xd1\v2\xc3\x01U\x04ģ\x8b1\xb3!U\xcc:\xe8\a]?|\xcd%\xfa\x18\u007f\xd1YR\xec\x11|\xdc1\xcf\xfc\xf5\x95\xc8k+\x91\x87@1\xd8G^OY~-%\x92\xce'\x06[\x93CG^?Y\x14n\x9d\x18pMB\x9c\xban2\x1drMo\xa7\xf5\xaf\x99\x9c\xe0NDH\xd8l\x93\x1f>\x11\xd0&E3{\xb8\xb2D4g\x85\xb2\x17\x13u\xc7\xef\xbd:\x10\xdeĢV탛1\xee\xe8\xfa\x16|\x02\xbfJ\x95zސ\x10\xb6\xfc\v~h\x8f\xef\xc9Ԏϸ\x185\xdef\xef\xd0\xc8b!H\x8drPć\xdbv\r7\"\xd9\xd5\rG \xf2\xc8;aa\xabM.\x1c\x9cקq\x97\xa1'՜\xaf\x01~\xd1\xf5Ah땛\x11\xb8V\xe6Ev\xa0\xe8\aλ\x80~LtF\xc5\xcf*Q\u061d\x0e\x0f\xa2ED\xc0w\xdd\x1e\x03Ǿ\xe19\xb4$\xd3eZ\x8f0\xc1n\xa1\x0e\xf0\xf5\x81\xbd)~\x04*i\x1e˪|\xa5\x10\t\xf7\xde\xd2\x1a\x019\xf6\x06\xde\"\x92\x8d\x1f\x0e[\xa7\x8dx\xc4O\xda?\x0f\x18C\xb3n\x8f\xce\v\x91\x95\xae\n\xa9\"\xd5ݯQI\xf6s\xeb\x03l2Ȫ\xd5֜\xa5\x13\xb6cJlf\x9d;\x97EL\xee\xfe\xfe\x93\x9f\x90\x939\xae?\x96\xfe\xa0~U\bc\x91(\x1d&\xea;m\xc6\xed\xdcN?C\xa6+:\xfcܟ\x87A\xceX㜀\x93f\xe3_\xe2\v\xe2\x1bH\x17#\xf2\x0f\xc3=[\x81l\x8b\x89SG\xfbz;\nKX\xab\x13ɺ\x88\xb7\x848Q\xec\xf5\x9e\x8a\x9b\xb2(\x13*\xa3\xb4\xf8\xe5Y\xa1\xf9\x16\x16\xaa\xbdU\x9eS3\x8fG\xfe\xd7Q\xc7\xc0\xe0!\xf5A\xfa\xaf\xd7|\xc8\uea4a@\xd6?[\x1d\xf6\xb6\xa4\xad\x9f\xd3<&\xdd\xcc\xfa\x1f_\xfbþ\xebj\xf8\x05\xcbU\xfd\xa8\xe6Y\x04e\xfd\xb3\xc91\xef\x94\xfa\xb7\xa9\x13Q\xb8\xd2T\xe65)\r\xbf\x9bG@\xd0?+w\xdaK\xa5ͫ\xcd3\xbcl\xdeqn\xa2\xfd\xd9W\xa3\a\xf8W\xbfy:\xfa\b\xa8\xb7\xae\xfeU\xe7\x15\xc1?\x8d\x9d\x83\xeb\x80\xdf\x19\x9c\x99\xe9WjS'\xf9V\x84\xe6\x8e\xe1}»1ԇ\xb36W\xf0\x19\x9f\ajo\x14M\xe2\xf8Lͧfb\xca{\x04C\xaf4ONq_\xf7\xe2\xbc\xd8\x01m\xd1Us\xbd潄\x1b\x91e-\x88>\av\x88\xad\xff,\xb7~\x03'\xa19\xfd\xcbQ\x8bQ\xc55\xa9\xb4\xc6\x14\xd6\xe0\x92:\xaa\xb4h\xf6\x98\xb6\x84\xa4\xb2\xe1\xed\x9ar\xd3<\x17\t\u007f\xfcy֬J\x91$X\xb8*\xb1\xab\xfd\x9a\xfd\xf99\xff\b\x8f\xd5\xf3\xcfD+\xefh\xdb+\xf8\xef\xff9\x83\xca\x00?\x84\x17\xe9\xa9\xf2\xff\x02\x00\x00\xff\xff\x94\x01\x97\xcd\xfb_\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"), diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 0b43e2f549..6b02727a52 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -27,14 +27,7 @@ rules: - apiGroups: - velero.io resources: - - backups - verbs: - - create - - delete -- apiGroups: - - velero.io - resources: - - backupstoragelocations + - backuprepositories verbs: - create - delete @@ -46,7 +39,7 @@ rules: - apiGroups: - velero.io resources: - - backupstoragelocations/status + - backuprepositories/status verbs: - get - patch @@ -54,7 +47,14 @@ rules: - apiGroups: - velero.io resources: - - deletebackuprequests + - backups + verbs: + - create + - delete +- apiGroups: + - velero.io + resources: + - backupstoragelocations verbs: - create - delete @@ -66,7 +66,7 @@ rules: - apiGroups: - velero.io resources: - - deletebackuprequests/status + - backupstoragelocations/status verbs: - get - patch @@ -74,7 +74,7 @@ rules: - apiGroups: - velero.io resources: - - downloadrequests + - deletebackuprequests verbs: - create - delete @@ -86,7 +86,7 @@ rules: - apiGroups: - velero.io resources: - - downloadrequests/status + - deletebackuprequests/status verbs: - get - patch @@ -94,7 +94,7 @@ rules: - apiGroups: - velero.io resources: - - podvolumebackups + - downloadrequests verbs: - create - delete @@ -106,7 +106,7 @@ rules: - apiGroups: - velero.io resources: - - podvolumebackups/status + - downloadrequests/status verbs: - get - patch @@ -114,7 +114,7 @@ rules: - apiGroups: - velero.io resources: - - podvolumerestores + - podvolumebackups verbs: - create - delete @@ -126,7 +126,7 @@ rules: - apiGroups: - velero.io resources: - - podvolumerestores/status + - podvolumebackups/status verbs: - get - patch @@ -134,7 +134,7 @@ rules: - apiGroups: - velero.io resources: - - resticrepositories + - podvolumerestores verbs: - create - delete @@ -146,7 +146,7 @@ rules: - apiGroups: - velero.io resources: - - resticrepositories/status + - podvolumerestores/status verbs: - get - patch diff --git a/design/unified-repo-and-kopia-integration/unified-repo-and-kopia-integration.md b/design/unified-repo-and-kopia-integration/unified-repo-and-kopia-integration.md index 122f85b43c..5b4a897e26 100644 --- a/design/unified-repo-and-kopia-integration/unified-repo-and-kopia-integration.md +++ b/design/unified-repo-and-kopia-integration/unified-repo-and-kopia-integration.md @@ -297,11 +297,11 @@ Kopia’s debug logs will be written to the same log file as Velero server or Ve As mentioned above, There will be two paths. The related controllers need to identify the path during runtime and adjust its working mode. According to the requirements, path changing is fulfilled at the backup/restore level. In order to let the controllers know the path, we need to add some option values. Specifically, there will be option/mode values for path selection in two places: - Add the “uploader-type” option as a parameter of the Velero server. The parameters will be set by the installation. Currently the option has two values, either "restic" or "kopia" (in future, we may add other file system uploaders, then we will have more values). -- Add a "uploader-type" value in the PodVolume Backup/Restore CR and a "repository-type" value in the BackupRepository CR. "uploader-type" currently has two values , either "restic" or "kopia"; "repository-type" currently has two values, either "restic" or "kopia" (in future, the Unified Repository could opt among multiple backup repository/backup storage, so there may be more values. This is a good reason that repository-type is a multivariate flag, however, in which way to opt among the backup repository/backup storage is not covered in this PR). If the values are missing in the CRs, it by default means "uploader-type=restic" and "repository-type=restic", so the legacy CRs are handled correctly by Restic. +- Add a "uploaderType" value in the PodVolume Backup/Restore CR and a "repositoryType" value in the BackupRepository CR. "uploaderType" currently has two values , either "restic" or "kopia"; "repositoryType" currently has two values, either "restic" or "kopia" (in future, the Unified Repository could opt among multiple backup repository/backup storage, so there may be more values. This is a good reason that repositoryType is a multivariate flag, however, in which way to opt among the backup repository/backup storage is not covered in this PR). If the values are missing in the CRs, it by default means "uploaderType=restic" and "repositoryType=restic", so the legacy CRs are handled correctly by Restic. The corresponding controllers handle the CRs by checking the CRs' path value. Some examples are as below: -- The PodVolume BR controller checks the "uploader-type" value from PodVolume CRs and decide its working path -- The BackupRepository controller checks the "repository-type" value from BackupRepository CRs and decide its working path +- The PodVolume BR controller checks the "uploaderType" value from PodVolume CRs and decide its working path +- The BackupRepository controller checks the "repositoryType" value from BackupRepository CRs and decide its working path - The Backup controller that runs in Velero server checks its “uploader-type” parameter to decide the path for the Backup it is going to create and then create the PodVolume Backup CR and BackupRepository CR - The Restore controller checks the Backup, from which it is going to restore, for the path and then create the PodVolume Restore CR and BackupRepository CR @@ -345,7 +345,7 @@ The BackupRepository CRs and PodVolume Backup/Restore CRs created in this case a spec: backupStorageLocation: default maintenanceFrequency: 168h0m0s - repository-type: restic + repositoryType: restic volumeNamespace: nginx-example ``` ``` @@ -359,7 +359,7 @@ spec: uid: 86aaec56-2b21-4736-9964-621047717133 tags: ... - uploader-type: restic + uploaderType: restic volume: nginx-log ``` ``` @@ -371,7 +371,7 @@ spec: namespace: nginx-example uid: e56d5872-3d94-4125-bfe8-8a222bf0fcf1 snapshotID: 1741e5f1 - uploader-type: restic + uploaderType: restic volume: nginx-log ``` **"Kopia"**: it means Velero will use Kopia uploader to do the pod volume backup (so it will use Unified Repository as the backup target). Therefore, the Velero server deployment will be created as below: @@ -390,7 +390,7 @@ The BackupRepository CRs created in this case are hard set with "kopia" at prese spec: backupStorageLocation: default maintenanceFrequency: 168h0m0s - repository-type: kopia + repositoryType: kopia volumeNamespace: nginx-example ``` ``` @@ -404,7 +404,7 @@ spec: uid: 86aaec56-2b21-4736-9964-621047717133 tags: ... - uploader-type: kopia + uploaderType: kopia volume: nginx-log ``` ``` @@ -416,7 +416,7 @@ spec: namespace: nginx-example uid: e56d5872-3d94-4125-bfe8-8a222bf0fcf1 snapshotID: 1741e5f1 - uploader-type: kopia + uploaderType: kopia volume: nginx-log ``` We will add the flag for both CLI installation and Helm Chart Installation. Specifically: diff --git a/pkg/apis/velero/v1/restic_repository_types.go b/pkg/apis/velero/v1/backup_repository_types.go similarity index 64% rename from pkg/apis/velero/v1/restic_repository_types.go rename to pkg/apis/velero/v1/backup_repository_types.go index 8e315592fd..300ecae9c1 100644 --- a/pkg/apis/velero/v1/restic_repository_types.go +++ b/pkg/apis/velero/v1/backup_repository_types.go @@ -20,9 +20,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// ResticRepositorySpec is the specification for a ResticRepository. -type ResticRepositorySpec struct { - // VolumeNamespace is the namespace this restic repository contains +// BackupRepositorySpec is the specification for a BackupRepository. +type BackupRepositorySpec struct { + // VolumeNamespace is the namespace this backup repository contains // pod volume backups for. VolumeNamespace string `json:"volumeNamespace"` @@ -30,6 +30,11 @@ type ResticRepositorySpec struct { // that should contain this repository. BackupStorageLocation string `json:"backupStorageLocation"` + // RepositoryType indicates the type of the backend repository + // +kubebuilder:validation:Enum=kopia;restic;"" + // +optional + RepositoryType string `json:"repositoryType"` + // ResticIdentifier is the full restic-compatible string for identifying // this repository. ResticIdentifier string `json:"resticIdentifier"` @@ -38,23 +43,23 @@ type ResticRepositorySpec struct { MaintenanceFrequency metav1.Duration `json:"maintenanceFrequency"` } -// ResticRepositoryPhase represents the lifecycle phase of a ResticRepository. +// BackupRepositoryPhase represents the lifecycle phase of a BackupRepository. // +kubebuilder:validation:Enum=New;Ready;NotReady -type ResticRepositoryPhase string +type BackupRepositoryPhase string const ( - ResticRepositoryPhaseNew ResticRepositoryPhase = "New" - ResticRepositoryPhaseReady ResticRepositoryPhase = "Ready" - ResticRepositoryPhaseNotReady ResticRepositoryPhase = "NotReady" + BackupRepositoryPhaseNew BackupRepositoryPhase = "New" + BackupRepositoryPhaseReady BackupRepositoryPhase = "Ready" + BackupRepositoryPhaseNotReady BackupRepositoryPhase = "NotReady" ) -// ResticRepositoryStatus is the current status of a ResticRepository. -type ResticRepositoryStatus struct { - // Phase is the current state of the ResticRepository. +// BackupRepositoryStatus is the current status of a BackupRepository. +type BackupRepositoryStatus struct { + // Phase is the current state of the BackupRepository. // +optional - Phase ResticRepositoryPhase `json:"phase,omitempty"` + Phase BackupRepositoryPhase `json:"phase,omitempty"` - // Message is a message about the current status of the ResticRepository. + // Message is a message about the current status of the BackupRepository. // +optional Message string `json:"message,omitempty"` @@ -72,33 +77,35 @@ type ResticRepositoryStatus struct { // +kubebuilder:object:generate=true // +kubebuilder:storageversion // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="Repository Type",type="string",JSONPath=".spec.repositoryType" +// -type ResticRepository struct { +type BackupRepository struct { metav1.TypeMeta `json:",inline"` // +optional metav1.ObjectMeta `json:"metadata,omitempty"` // +optional - Spec ResticRepositorySpec `json:"spec,omitempty"` + Spec BackupRepositorySpec `json:"spec,omitempty"` // +optional - Status ResticRepositoryStatus `json:"status,omitempty"` + Status BackupRepositoryStatus `json:"status,omitempty"` } // TODO(2.0) After converting all resources to use the runtime-controller client, // the k8s:deepcopy marker will no longer be needed and should be removed. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:object:root=true -// +kubebuilder:rbac:groups=velero.io,resources=resticrepositories,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=velero.io,resources=resticrepositories/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=velero.io,resources=backuprepositories,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=velero.io,resources=backuprepositories/status,verbs=get;update;patch -// ResticRepositoryList is a list of ResticRepositories. -type ResticRepositoryList struct { +// BackupRepositoryList is a list of BackupRepositories. +type BackupRepositoryList struct { metav1.TypeMeta `json:",inline"` // +optional metav1.ListMeta `json:"metadata,omitempty"` - Items []ResticRepository `json:"items"` + Items []BackupRepository `json:"items"` } diff --git a/pkg/apis/velero/v1/pod_volume_backup_types.go b/pkg/apis/velero/v1/pod_volume_backup_types.go index c532f096de..d34e09f6ce 100644 --- a/pkg/apis/velero/v1/pod_volume_backup_types.go +++ b/pkg/apis/velero/v1/pod_volume_backup_types.go @@ -34,12 +34,17 @@ type PodVolumeBackupSpec struct { Volume string `json:"volume"` // BackupStorageLocation is the name of the backup storage location - // where the restic repository is stored. + // where the backup repository is stored. BackupStorageLocation string `json:"backupStorageLocation"` - // RepoIdentifier is the restic repository identifier. + // RepoIdentifier is the backup repository identifier. RepoIdentifier string `json:"repoIdentifier"` + // UploaderType is the type of the uploader to handle the data transfer. + // +kubebuilder:validation:Enum=kopia;restic;"" + // +optional + UploaderType string `json:"uploaderType"` + // Tags are a map of key-value pairs that should be applied to the // volume backup as tags. // +optional @@ -107,7 +112,8 @@ type PodVolumeBackupStatus struct { // +kubebuilder:printcolumn:name="Namespace",type="string",JSONPath=".spec.pod.namespace",description="Namespace of the pod containing the volume to be backed up" // +kubebuilder:printcolumn:name="Pod",type="string",JSONPath=".spec.pod.name",description="Name of the pod containing the volume to be backed up" // +kubebuilder:printcolumn:name="Volume",type="string",JSONPath=".spec.volume",description="Name of the volume to be backed up" -// +kubebuilder:printcolumn:name="Restic Repo",type="string",JSONPath=".spec.repoIdentifier",description="Restic repository identifier for this backup" +// +kubebuilder:printcolumn:name="Repository ID",type="string",JSONPath=".spec.repoIdentifier",description="Backup repository identifier for this backup" +// +kubebuilder:printcolumn:name="Uploader Type",type="string",JSONPath=".spec.uploaderType",description="The type of the uploader to handle data transfer" // +kubebuilder:printcolumn:name="Storage Location",type="string",JSONPath=".spec.backupStorageLocation",description="Name of the Backup Storage Location where this backup should be stored" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // +kubebuilder:object:root=true diff --git a/pkg/apis/velero/v1/pod_volume_restore_type.go b/pkg/apis/velero/v1/pod_volume_restore_type.go index 45bca8e248..e0370da637 100644 --- a/pkg/apis/velero/v1/pod_volume_restore_type.go +++ b/pkg/apis/velero/v1/pod_volume_restore_type.go @@ -30,12 +30,17 @@ type PodVolumeRestoreSpec struct { Volume string `json:"volume"` // BackupStorageLocation is the name of the backup storage location - // where the restic repository is stored. + // where the backup repository is stored. BackupStorageLocation string `json:"backupStorageLocation"` - // RepoIdentifier is the restic repository identifier. + // RepoIdentifier is the backup repository identifier. RepoIdentifier string `json:"repoIdentifier"` + // UploaderType is the type of the uploader to handle the data transfer. + // +kubebuilder:validation:Enum=kopia;restic;"" + // +optional + UploaderType string `json:"uploaderType"` + // SnapshotID is the ID of the volume snapshot to be restored. SnapshotID string `json:"snapshotID"` } @@ -89,6 +94,7 @@ type PodVolumeRestoreStatus struct { // +kubebuilder:storageversion // +kubebuilder:printcolumn:name="Namespace",type="string",JSONPath=".spec.pod.namespace",description="Namespace of the pod containing the volume to be restored" // +kubebuilder:printcolumn:name="Pod",type="string",JSONPath=".spec.pod.name",description="Name of the pod containing the volume to be restored" +// +kubebuilder:printcolumn:name="Uploader Type",type="string",JSONPath=".spec.uploaderType",description="The type of the uploader to handle data transfer" // +kubebuilder:printcolumn:name="Volume",type="string",JSONPath=".spec.volume",description="Name of the volume to be restored" // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.phase",description="Pod Volume Restore status such as New/InProgress" // +kubebuilder:printcolumn:name="TotalBytes",type="integer",format="int64",JSONPath=".status.progress.totalBytes",description="Pod Volume Restore status such as New/InProgress" diff --git a/pkg/apis/velero/v1/register.go b/pkg/apis/velero/v1/register.go index ea7df3b5de..13915293a8 100644 --- a/pkg/apis/velero/v1/register.go +++ b/pkg/apis/velero/v1/register.go @@ -52,7 +52,7 @@ func CustomResources() map[string]typeInfo { "DeleteBackupRequest": newTypeInfo("deletebackuprequests", &DeleteBackupRequest{}, &DeleteBackupRequestList{}), "PodVolumeBackup": newTypeInfo("podvolumebackups", &PodVolumeBackup{}, &PodVolumeBackupList{}), "PodVolumeRestore": newTypeInfo("podvolumerestores", &PodVolumeRestore{}, &PodVolumeRestoreList{}), - "ResticRepository": newTypeInfo("resticrepositories", &ResticRepository{}, &ResticRepositoryList{}), + "BackupRepository": newTypeInfo("backuprepositories", &BackupRepository{}, &BackupRepositoryList{}), "BackupStorageLocation": newTypeInfo("backupstoragelocations", &BackupStorageLocation{}, &BackupStorageLocationList{}), "VolumeSnapshotLocation": newTypeInfo("volumesnapshotlocations", &VolumeSnapshotLocation{}, &VolumeSnapshotLocationList{}), "ServerStatusRequest": newTypeInfo("serverstatusrequests", &ServerStatusRequest{}, &ServerStatusRequestList{}), diff --git a/pkg/apis/velero/v1/zz_generated.deepcopy.go b/pkg/apis/velero/v1/zz_generated.deepcopy.go index c0a8f2c567..a35e5e1ef5 100644 --- a/pkg/apis/velero/v1/zz_generated.deepcopy.go +++ b/pkg/apis/velero/v1/zz_generated.deepcopy.go @@ -107,6 +107,100 @@ func (in *BackupProgress) DeepCopy() *BackupProgress { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupRepository) DeepCopyInto(out *BackupRepository) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupRepository. +func (in *BackupRepository) DeepCopy() *BackupRepository { + if in == nil { + return nil + } + out := new(BackupRepository) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackupRepository) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupRepositoryList) DeepCopyInto(out *BackupRepositoryList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BackupRepository, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupRepositoryList. +func (in *BackupRepositoryList) DeepCopy() *BackupRepositoryList { + if in == nil { + return nil + } + out := new(BackupRepositoryList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackupRepositoryList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupRepositorySpec) DeepCopyInto(out *BackupRepositorySpec) { + *out = *in + out.MaintenanceFrequency = in.MaintenanceFrequency +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupRepositorySpec. +func (in *BackupRepositorySpec) DeepCopy() *BackupRepositorySpec { + if in == nil { + return nil + } + out := new(BackupRepositorySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupRepositoryStatus) DeepCopyInto(out *BackupRepositoryStatus) { + *out = *in + if in.LastMaintenanceTime != nil { + in, out := &in.LastMaintenanceTime, &out.LastMaintenanceTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupRepositoryStatus. +func (in *BackupRepositoryStatus) DeepCopy() *BackupRepositoryStatus { + if in == nil { + return nil + } + out := new(BackupRepositoryStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BackupResourceHook) DeepCopyInto(out *BackupResourceHook) { *out = *in @@ -965,100 +1059,6 @@ func (in *PodVolumeRestoreStatus) DeepCopy() *PodVolumeRestoreStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResticRepository) DeepCopyInto(out *ResticRepository) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResticRepository. -func (in *ResticRepository) DeepCopy() *ResticRepository { - if in == nil { - return nil - } - out := new(ResticRepository) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ResticRepository) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResticRepositoryList) DeepCopyInto(out *ResticRepositoryList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ResticRepository, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResticRepositoryList. -func (in *ResticRepositoryList) DeepCopy() *ResticRepositoryList { - if in == nil { - return nil - } - out := new(ResticRepositoryList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ResticRepositoryList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResticRepositorySpec) DeepCopyInto(out *ResticRepositorySpec) { - *out = *in - out.MaintenanceFrequency = in.MaintenanceFrequency -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResticRepositorySpec. -func (in *ResticRepositorySpec) DeepCopy() *ResticRepositorySpec { - if in == nil { - return nil - } - out := new(ResticRepositorySpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResticRepositoryStatus) DeepCopyInto(out *ResticRepositoryStatus) { - *out = *in - if in.LastMaintenanceTime != nil { - in, out := &in.LastMaintenanceTime, &out.LastMaintenanceTime - *out = (*in).DeepCopy() - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResticRepositoryStatus. -func (in *ResticRepositoryStatus) DeepCopy() *ResticRepositoryStatus { - if in == nil { - return nil - } - out := new(ResticRepositoryStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Restore) DeepCopyInto(out *Restore) { *out = *in diff --git a/pkg/cmd/cli/backuplocation/delete.go b/pkg/cmd/cli/backuplocation/delete.go index daedf77cd6..1222220f51 100644 --- a/pkg/cmd/cli/backuplocation/delete.go +++ b/pkg/cmd/cli/backuplocation/delete.go @@ -151,8 +151,8 @@ func findAssociatedBackups(client kbclient.Client, bslName, ns string) (velerov1 return backups, err } -func findAssociatedResticRepos(client kbclient.Client, bslName, ns string) (velerov1api.ResticRepositoryList, error) { - var repos velerov1api.ResticRepositoryList +func findAssociatedResticRepos(client kbclient.Client, bslName, ns string) (velerov1api.BackupRepositoryList, error) { + var repos velerov1api.BackupRepositoryList err := client.List(context.Background(), &repos, &kbclient.ListOptions{ Namespace: ns, Raw: &metav1.ListOptions{LabelSelector: bslLabelKey + "=" + bslName}, @@ -172,7 +172,7 @@ func deleteBackups(client kbclient.Client, backups velerov1api.BackupList) []err return errs } -func deleteResticRepos(client kbclient.Client, repos velerov1api.ResticRepositoryList) []error { +func deleteResticRepos(client kbclient.Client, repos velerov1api.BackupRepositoryList) []error { var errs []error for _, repo := range repos.Items { if err := client.Delete(context.Background(), &repo, &kbclient.DeleteOptions{}); err != nil { diff --git a/pkg/cmd/cli/restic/repo/get.go b/pkg/cmd/cli/restic/repo/get.go index 8fd848fd4c..24692f3552 100644 --- a/pkg/cmd/cli/restic/repo/get.go +++ b/pkg/cmd/cli/restic/repo/get.go @@ -41,16 +41,16 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command { veleroClient, err := f.Client() cmd.CheckError(err) - var repos *api.ResticRepositoryList + var repos *api.BackupRepositoryList if len(args) > 0 { - repos = new(api.ResticRepositoryList) + repos = new(api.BackupRepositoryList) for _, name := range args { - repo, err := veleroClient.VeleroV1().ResticRepositories(f.Namespace()).Get(context.TODO(), name, metav1.GetOptions{}) + repo, err := veleroClient.VeleroV1().BackupRepositories(f.Namespace()).Get(context.TODO(), name, metav1.GetOptions{}) cmd.CheckError(err) repos.Items = append(repos.Items, *repo) } } else { - repos, err = veleroClient.VeleroV1().ResticRepositories(f.Namespace()).List(context.TODO(), listOptions) + repos, err = veleroClient.VeleroV1().BackupRepositories(f.Namespace()).List(context.TODO(), listOptions) cmd.CheckError(err) } diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index 3c83ba75a3..f257c96e72 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -527,7 +527,7 @@ func (s *server) initRestic() error { s.ctx, s.namespace, s.veleroClient, - s.sharedInformerFactory.Velero().V1().ResticRepositories(), + s.sharedInformerFactory.Velero().V1().BackupRepositories(), s.veleroClient.VeleroV1(), s.mgr.GetClient(), s.kubeClient.CoreV1(), diff --git a/pkg/cmd/util/output/output.go b/pkg/cmd/util/output/output.go index 24188ed0d7..ab3f7a95d6 100644 --- a/pkg/cmd/util/output/output.go +++ b/pkg/cmd/util/output/output.go @@ -177,15 +177,15 @@ func printTable(cmd *cobra.Command, obj runtime.Object) (bool, error) { ColumnDefinitions: scheduleColumns, Rows: printScheduleList(obj.(*velerov1api.ScheduleList)), } - case *velerov1api.ResticRepository: + case *velerov1api.BackupRepository: table = &metav1.Table{ ColumnDefinitions: resticRepoColumns, - Rows: printResticRepo(obj.(*velerov1api.ResticRepository)), + Rows: printResticRepo(obj.(*velerov1api.BackupRepository)), } - case *velerov1api.ResticRepositoryList: + case *velerov1api.BackupRepositoryList: table = &metav1.Table{ ColumnDefinitions: resticRepoColumns, - Rows: printResticRepoList(obj.(*velerov1api.ResticRepositoryList)), + Rows: printResticRepoList(obj.(*velerov1api.BackupRepositoryList)), } case *velerov1api.BackupStorageLocation: table = &metav1.Table{ diff --git a/pkg/cmd/util/output/restic_repo_printer.go b/pkg/cmd/util/output/restic_repo_printer.go index 803a3486f4..fd6766087f 100644 --- a/pkg/cmd/util/output/restic_repo_printer.go +++ b/pkg/cmd/util/output/restic_repo_printer.go @@ -33,7 +33,7 @@ var ( } ) -func printResticRepoList(list *v1.ResticRepositoryList) []metav1.TableRow { +func printResticRepoList(list *v1.BackupRepositoryList) []metav1.TableRow { rows := make([]metav1.TableRow, 0, len(list.Items)) for i := range list.Items { @@ -42,14 +42,14 @@ func printResticRepoList(list *v1.ResticRepositoryList) []metav1.TableRow { return rows } -func printResticRepo(repo *v1.ResticRepository) []metav1.TableRow { +func printResticRepo(repo *v1.BackupRepository) []metav1.TableRow { row := metav1.TableRow{ Object: runtime.RawExtension{Object: repo}, } status := repo.Status.Phase if status == "" { - status = v1.ResticRepositoryPhaseNew + status = v1.BackupRepositoryPhaseNew } var lastMaintenance string diff --git a/pkg/controller/restic_repository_controller.go b/pkg/controller/restic_repository_controller.go index d4d0ef68df..36f0e76a86 100644 --- a/pkg/controller/restic_repository_controller.go +++ b/pkg/controller/restic_repository_controller.go @@ -68,16 +68,16 @@ func NewResticRepoReconciler(namespace string, logger logrus.FieldLogger, client } func (r *ResticRepoReconciler) SetupWithManager(mgr ctrl.Manager) error { - s := kube.NewPeriodicalEnqueueSource(r.logger, mgr.GetClient(), &velerov1api.ResticRepositoryList{}, repoSyncPeriod) + s := kube.NewPeriodicalEnqueueSource(r.logger, mgr.GetClient(), &velerov1api.BackupRepositoryList{}, repoSyncPeriod) return ctrl.NewControllerManagedBy(mgr). - For(&velerov1api.ResticRepository{}). + For(&velerov1api.BackupRepository{}). Watches(s, nil). Complete(r) } func (r *ResticRepoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := r.logger.WithField("resticRepo", req.String()) - resticRepo := &velerov1api.ResticRepository{} + resticRepo := &velerov1api.BackupRepository{} if err := r.Get(ctx, req.NamespacedName, resticRepo); err != nil { if apierrors.IsNotFound(err) { log.Warnf("restic repository %s in namespace %s is not found", req.Name, req.Namespace) @@ -87,7 +87,7 @@ func (r *ResticRepoReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, err } - if resticRepo.Status.Phase == "" || resticRepo.Status.Phase == velerov1api.ResticRepositoryPhaseNew { + if resticRepo.Status.Phase == "" || resticRepo.Status.Phase == velerov1api.BackupRepositoryPhaseNew { if err := r.initializeRepo(ctx, resticRepo, log); err != nil { log.WithError(err).Error("error initialize repository") return ctrl.Result{}, errors.WithStack(err) @@ -105,16 +105,16 @@ func (r *ResticRepoReconciler) Reconcile(ctx context.Context, req ctrl.Request) } switch resticRepo.Status.Phase { - case velerov1api.ResticRepositoryPhaseReady: + case velerov1api.BackupRepositoryPhaseReady: return ctrl.Result{}, r.runMaintenanceIfDue(ctx, resticRepo, log) - case velerov1api.ResticRepositoryPhaseNotReady: + case velerov1api.BackupRepositoryPhaseNotReady: return ctrl.Result{}, r.checkNotReadyRepo(ctx, resticRepo, log) } return ctrl.Result{}, nil } -func (r *ResticRepoReconciler) initializeRepo(ctx context.Context, req *velerov1api.ResticRepository, log logrus.FieldLogger) error { +func (r *ResticRepoReconciler) initializeRepo(ctx context.Context, req *velerov1api.BackupRepository, log logrus.FieldLogger) error { log.Info("Initializing restic repository") // confirm the repo's BackupStorageLocation is valid @@ -129,9 +129,9 @@ func (r *ResticRepoReconciler) initializeRepo(ctx context.Context, req *velerov1 repoIdentifier, err := restic.GetRepoIdentifier(loc, req.Spec.VolumeNamespace) if err != nil { - return r.patchResticRepository(ctx, req, func(rr *velerov1api.ResticRepository) { + return r.patchResticRepository(ctx, req, func(rr *velerov1api.BackupRepository) { rr.Status.Message = err.Error() - rr.Status.Phase = velerov1api.ResticRepositoryPhaseNotReady + rr.Status.Phase = velerov1api.BackupRepositoryPhaseNotReady if rr.Spec.MaintenanceFrequency.Duration <= 0 { rr.Spec.MaintenanceFrequency = metav1.Duration{Duration: r.defaultMaintenanceFrequency} @@ -140,7 +140,7 @@ func (r *ResticRepoReconciler) initializeRepo(ctx context.Context, req *velerov1 } // defaulting - if the patch fails, return an error so the item is returned to the queue - if err := r.patchResticRepository(ctx, req, func(rr *velerov1api.ResticRepository) { + if err := r.patchResticRepository(ctx, req, func(rr *velerov1api.BackupRepository) { rr.Spec.ResticIdentifier = repoIdentifier if rr.Spec.MaintenanceFrequency.Duration <= 0 { @@ -154,8 +154,8 @@ func (r *ResticRepoReconciler) initializeRepo(ctx context.Context, req *velerov1 return r.patchResticRepository(ctx, req, repoNotReady(err.Error())) } - return r.patchResticRepository(ctx, req, func(rr *velerov1api.ResticRepository) { - rr.Status.Phase = velerov1api.ResticRepositoryPhaseReady + return r.patchResticRepository(ctx, req, func(rr *velerov1api.BackupRepository) { + rr.Status.Phase = velerov1api.BackupRepositoryPhaseReady rr.Status.LastMaintenanceTime = &metav1.Time{Time: time.Now()} }) } @@ -163,7 +163,7 @@ func (r *ResticRepoReconciler) initializeRepo(ctx context.Context, req *velerov1 // ensureRepo checks to see if a repository exists, and attempts to initialize it if // it does not exist. An error is returned if the repository can't be connected to // or initialized. -func ensureRepo(repo *velerov1api.ResticRepository, repoManager restic.RepositoryManager) error { +func ensureRepo(repo *velerov1api.BackupRepository, repoManager restic.RepositoryManager) error { if err := repoManager.ConnectToRepo(repo); err != nil { // If the repository has not yet been initialized, the error message will always include // the following string. This is the only scenario where we should try to initialize it. @@ -179,7 +179,7 @@ func ensureRepo(repo *velerov1api.ResticRepository, repoManager restic.Repositor return nil } -func (r *ResticRepoReconciler) runMaintenanceIfDue(ctx context.Context, req *velerov1api.ResticRepository, log logrus.FieldLogger) error { +func (r *ResticRepoReconciler) runMaintenanceIfDue(ctx context.Context, req *velerov1api.BackupRepository, log logrus.FieldLogger) error { log.Debug("resticRepositoryController.runMaintenanceIfDue") now := r.clock.Now() @@ -196,21 +196,21 @@ func (r *ResticRepoReconciler) runMaintenanceIfDue(ctx context.Context, req *vel log.Debug("Pruning repo") if err := r.repositoryManager.PruneRepo(req); err != nil { log.WithError(err).Warn("error pruning repository") - return r.patchResticRepository(ctx, req, func(rr *velerov1api.ResticRepository) { + return r.patchResticRepository(ctx, req, func(rr *velerov1api.BackupRepository) { rr.Status.Message = err.Error() }) } - return r.patchResticRepository(ctx, req, func(rr *velerov1api.ResticRepository) { + return r.patchResticRepository(ctx, req, func(rr *velerov1api.BackupRepository) { rr.Status.LastMaintenanceTime = &metav1.Time{Time: now} }) } -func dueForMaintenance(req *velerov1api.ResticRepository, now time.Time) bool { +func dueForMaintenance(req *velerov1api.BackupRepository, now time.Time) bool { return req.Status.LastMaintenanceTime == nil || req.Status.LastMaintenanceTime.Add(req.Spec.MaintenanceFrequency.Duration).Before(now) } -func (r *ResticRepoReconciler) checkNotReadyRepo(ctx context.Context, req *velerov1api.ResticRepository, log logrus.FieldLogger) error { +func (r *ResticRepoReconciler) checkNotReadyRepo(ctx context.Context, req *velerov1api.BackupRepository, log logrus.FieldLogger) error { // no identifier: can't possibly be ready, so just return if req.Spec.ResticIdentifier == "" { return nil @@ -226,16 +226,16 @@ func (r *ResticRepoReconciler) checkNotReadyRepo(ctx context.Context, req *veler return r.patchResticRepository(ctx, req, repoReady()) } -func repoNotReady(msg string) func(*velerov1api.ResticRepository) { - return func(r *velerov1api.ResticRepository) { - r.Status.Phase = velerov1api.ResticRepositoryPhaseNotReady +func repoNotReady(msg string) func(*velerov1api.BackupRepository) { + return func(r *velerov1api.BackupRepository) { + r.Status.Phase = velerov1api.BackupRepositoryPhaseNotReady r.Status.Message = msg } } -func repoReady() func(*velerov1api.ResticRepository) { - return func(r *velerov1api.ResticRepository) { - r.Status.Phase = velerov1api.ResticRepositoryPhaseReady +func repoReady() func(*velerov1api.BackupRepository) { + return func(r *velerov1api.BackupRepository) { + r.Status.Phase = velerov1api.BackupRepositoryPhaseReady r.Status.Message = "" } } @@ -243,7 +243,7 @@ func repoReady() func(*velerov1api.ResticRepository) { // patchResticRepository mutates req with the provided mutate function, and patches it // through the Kube API. After executing this function, req will be updated with both // the mutation and the results of the Patch() API call. -func (r *ResticRepoReconciler) patchResticRepository(ctx context.Context, req *velerov1api.ResticRepository, mutate func(*velerov1api.ResticRepository)) error { +func (r *ResticRepoReconciler) patchResticRepository(ctx context.Context, req *velerov1api.BackupRepository, mutate func(*velerov1api.BackupRepository)) error { original := req.DeepCopy() mutate(req) if err := r.Patch(ctx, req, client.MergeFrom(original)); err != nil { diff --git a/pkg/controller/restic_repository_controller_test.go b/pkg/controller/restic_repository_controller_test.go index 2e6b4308a3..28e899329c 100644 --- a/pkg/controller/restic_repository_controller_test.go +++ b/pkg/controller/restic_repository_controller_test.go @@ -30,7 +30,7 @@ import ( const defaultMaintenanceFrequency = 10 * time.Minute -func mockResticRepoReconciler(t *testing.T, rr *velerov1api.ResticRepository, mockOn string, arg interface{}, ret interface{}) *ResticRepoReconciler { +func mockResticRepoReconciler(t *testing.T, rr *velerov1api.BackupRepository, mockOn string, arg interface{}, ret interface{}) *ResticRepoReconciler { mgr := &resticmokes.RepositoryManager{} if mockOn != "" { mgr.On(mockOn, arg).Return(ret) @@ -44,13 +44,13 @@ func mockResticRepoReconciler(t *testing.T, rr *velerov1api.ResticRepository, mo ) } -func mockResticRepositoryCR() *velerov1api.ResticRepository { - return &velerov1api.ResticRepository{ +func mockResticRepositoryCR() *velerov1api.BackupRepository { + return &velerov1api.BackupRepository{ ObjectMeta: metav1.ObjectMeta{ Namespace: velerov1api.DefaultNamespace, Name: "repo", }, - Spec: velerov1api.ResticRepositorySpec{ + Spec: velerov1api.BackupRepositorySpec{ MaintenanceFrequency: metav1.Duration{defaultMaintenanceFrequency}, }, } @@ -64,10 +64,10 @@ func TestPatchResticRepository(t *testing.T) { assert.NoError(t, err) err = reconciler.patchResticRepository(context.Background(), rr, repoReady()) assert.NoError(t, err) - assert.Equal(t, rr.Status.Phase, velerov1api.ResticRepositoryPhaseReady) + assert.Equal(t, rr.Status.Phase, velerov1api.BackupRepositoryPhaseReady) err = reconciler.patchResticRepository(context.Background(), rr, repoNotReady("not ready")) assert.NoError(t, err) - assert.NotEqual(t, rr.Status.Phase, velerov1api.ResticRepositoryPhaseReady) + assert.NotEqual(t, rr.Status.Phase, velerov1api.BackupRepositoryPhaseReady) } func TestCheckNotReadyRepo(t *testing.T) { @@ -77,11 +77,11 @@ func TestCheckNotReadyRepo(t *testing.T) { assert.NoError(t, err) err = reconciler.checkNotReadyRepo(context.TODO(), rr, reconciler.logger) assert.NoError(t, err) - assert.Equal(t, rr.Status.Phase, velerov1api.ResticRepositoryPhase("")) + assert.Equal(t, rr.Status.Phase, velerov1api.BackupRepositoryPhase("")) rr.Spec.ResticIdentifier = "s3:test.amazonaws.com/bucket/restic" err = reconciler.checkNotReadyRepo(context.TODO(), rr, reconciler.logger) assert.NoError(t, err) - assert.Equal(t, rr.Status.Phase, velerov1api.ResticRepositoryPhaseReady) + assert.Equal(t, rr.Status.Phase, velerov1api.BackupRepositoryPhaseReady) } func TestRunMaintenanceIfDue(t *testing.T) { @@ -121,23 +121,23 @@ func TestInitializeRepo(t *testing.T) { assert.NoError(t, err) err = reconciler.initializeRepo(context.TODO(), rr, reconciler.logger) assert.NoError(t, err) - assert.Equal(t, rr.Status.Phase, velerov1api.ResticRepositoryPhaseReady) + assert.Equal(t, rr.Status.Phase, velerov1api.BackupRepositoryPhaseReady) } func TestResticRepoReconcile(t *testing.T) { tests := []struct { name string - repo *velerov1api.ResticRepository + repo *velerov1api.BackupRepository expectNil bool }{ { name: "test on api server not found", - repo: &velerov1api.ResticRepository{ + repo: &velerov1api.BackupRepository{ ObjectMeta: metav1.ObjectMeta{ Namespace: velerov1api.DefaultNamespace, Name: "unknown", }, - Spec: velerov1api.ResticRepositorySpec{ + Spec: velerov1api.BackupRepositorySpec{ MaintenanceFrequency: metav1.Duration{defaultMaintenanceFrequency}, }, }, @@ -145,12 +145,12 @@ func TestResticRepoReconcile(t *testing.T) { }, { name: "test on initialize repo", - repo: &velerov1api.ResticRepository{ + repo: &velerov1api.BackupRepository{ ObjectMeta: metav1.ObjectMeta{ Namespace: velerov1api.DefaultNamespace, Name: "repo", }, - Spec: velerov1api.ResticRepositorySpec{ + Spec: velerov1api.BackupRepositorySpec{ MaintenanceFrequency: metav1.Duration{defaultMaintenanceFrequency}, }, }, @@ -158,16 +158,16 @@ func TestResticRepoReconcile(t *testing.T) { }, { name: "test on repo with new phase", - repo: &velerov1api.ResticRepository{ + repo: &velerov1api.BackupRepository{ ObjectMeta: metav1.ObjectMeta{ Namespace: velerov1api.DefaultNamespace, Name: "repo", }, - Spec: velerov1api.ResticRepositorySpec{ + Spec: velerov1api.BackupRepositorySpec{ MaintenanceFrequency: metav1.Duration{defaultMaintenanceFrequency}, }, - Status: velerov1api.ResticRepositoryStatus{ - Phase: velerov1api.ResticRepositoryPhaseNew, + Status: velerov1api.BackupRepositoryStatus{ + Phase: velerov1api.BackupRepositoryPhaseNew, }, }, expectNil: true, diff --git a/pkg/controller/restore_controller.go b/pkg/controller/restore_controller.go index 092b90002f..3799259fad 100644 --- a/pkg/controller/restore_controller.go +++ b/pkg/controller/restore_controller.go @@ -71,10 +71,14 @@ var nonRestorableResources = []string{ // https://github.com/vmware-tanzu/velero/issues/622 "restores.velero.io", + // TODO: Remove this in v1.11 or v1.12 // Restic repositories are automatically managed by Velero and will be automatically // created as needed if they don't exist. // https://github.com/vmware-tanzu/velero/issues/1113 "resticrepositories.velero.io", + + // Backup repositories were renamed from Restic repositories + "backuprepositories.velero.io", } type restoreController struct { diff --git a/pkg/generated/clientset/versioned/typed/velero/v1/resticrepository.go b/pkg/generated/clientset/versioned/typed/velero/v1/backuprepository.go similarity index 53% rename from pkg/generated/clientset/versioned/typed/velero/v1/resticrepository.go rename to pkg/generated/clientset/versioned/typed/velero/v1/backuprepository.go index 44d5c0760b..7ecef6dcf6 100644 --- a/pkg/generated/clientset/versioned/typed/velero/v1/resticrepository.go +++ b/pkg/generated/clientset/versioned/typed/velero/v1/backuprepository.go @@ -30,46 +30,46 @@ import ( rest "k8s.io/client-go/rest" ) -// ResticRepositoriesGetter has a method to return a ResticRepositoryInterface. +// BackupRepositoriesGetter has a method to return a BackupRepositoryInterface. // A group's client should implement this interface. -type ResticRepositoriesGetter interface { - ResticRepositories(namespace string) ResticRepositoryInterface +type BackupRepositoriesGetter interface { + BackupRepositories(namespace string) BackupRepositoryInterface } -// ResticRepositoryInterface has methods to work with ResticRepository resources. -type ResticRepositoryInterface interface { - Create(ctx context.Context, resticRepository *v1.ResticRepository, opts metav1.CreateOptions) (*v1.ResticRepository, error) - Update(ctx context.Context, resticRepository *v1.ResticRepository, opts metav1.UpdateOptions) (*v1.ResticRepository, error) - UpdateStatus(ctx context.Context, resticRepository *v1.ResticRepository, opts metav1.UpdateOptions) (*v1.ResticRepository, error) +// BackupRepositoryInterface has methods to work with BackupRepository resources. +type BackupRepositoryInterface interface { + Create(ctx context.Context, backupRepository *v1.BackupRepository, opts metav1.CreateOptions) (*v1.BackupRepository, error) + Update(ctx context.Context, backupRepository *v1.BackupRepository, opts metav1.UpdateOptions) (*v1.BackupRepository, error) + UpdateStatus(ctx context.Context, backupRepository *v1.BackupRepository, opts metav1.UpdateOptions) (*v1.BackupRepository, error) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error - Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.ResticRepository, error) - List(ctx context.Context, opts metav1.ListOptions) (*v1.ResticRepositoryList, error) + Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.BackupRepository, error) + List(ctx context.Context, opts metav1.ListOptions) (*v1.BackupRepositoryList, error) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.ResticRepository, err error) - ResticRepositoryExpansion + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.BackupRepository, err error) + BackupRepositoryExpansion } -// resticRepositories implements ResticRepositoryInterface -type resticRepositories struct { +// backupRepositories implements BackupRepositoryInterface +type backupRepositories struct { client rest.Interface ns string } -// newResticRepositories returns a ResticRepositories -func newResticRepositories(c *VeleroV1Client, namespace string) *resticRepositories { - return &resticRepositories{ +// newBackupRepositories returns a BackupRepositories +func newBackupRepositories(c *VeleroV1Client, namespace string) *backupRepositories { + return &backupRepositories{ client: c.RESTClient(), ns: namespace, } } -// Get takes name of the resticRepository, and returns the corresponding resticRepository object, and an error if there is any. -func (c *resticRepositories) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.ResticRepository, err error) { - result = &v1.ResticRepository{} +// Get takes name of the backupRepository, and returns the corresponding backupRepository object, and an error if there is any. +func (c *backupRepositories) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.BackupRepository, err error) { + result = &v1.BackupRepository{} err = c.client.Get(). Namespace(c.ns). - Resource("resticrepositories"). + Resource("backuprepositories"). Name(name). VersionedParams(&options, scheme.ParameterCodec). Do(ctx). @@ -77,16 +77,16 @@ func (c *resticRepositories) Get(ctx context.Context, name string, options metav return } -// List takes label and field selectors, and returns the list of ResticRepositories that match those selectors. -func (c *resticRepositories) List(ctx context.Context, opts metav1.ListOptions) (result *v1.ResticRepositoryList, err error) { +// List takes label and field selectors, and returns the list of BackupRepositories that match those selectors. +func (c *backupRepositories) List(ctx context.Context, opts metav1.ListOptions) (result *v1.BackupRepositoryList, err error) { var timeout time.Duration if opts.TimeoutSeconds != nil { timeout = time.Duration(*opts.TimeoutSeconds) * time.Second } - result = &v1.ResticRepositoryList{} + result = &v1.BackupRepositoryList{} err = c.client.Get(). Namespace(c.ns). - Resource("resticrepositories"). + Resource("backuprepositories"). VersionedParams(&opts, scheme.ParameterCodec). Timeout(timeout). Do(ctx). @@ -94,8 +94,8 @@ func (c *resticRepositories) List(ctx context.Context, opts metav1.ListOptions) return } -// Watch returns a watch.Interface that watches the requested resticRepositories. -func (c *resticRepositories) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { +// Watch returns a watch.Interface that watches the requested backupRepositories. +func (c *backupRepositories) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { var timeout time.Duration if opts.TimeoutSeconds != nil { timeout = time.Duration(*opts.TimeoutSeconds) * time.Second @@ -103,34 +103,34 @@ func (c *resticRepositories) Watch(ctx context.Context, opts metav1.ListOptions) opts.Watch = true return c.client.Get(). Namespace(c.ns). - Resource("resticrepositories"). + Resource("backuprepositories"). VersionedParams(&opts, scheme.ParameterCodec). Timeout(timeout). Watch(ctx) } -// Create takes the representation of a resticRepository and creates it. Returns the server's representation of the resticRepository, and an error, if there is any. -func (c *resticRepositories) Create(ctx context.Context, resticRepository *v1.ResticRepository, opts metav1.CreateOptions) (result *v1.ResticRepository, err error) { - result = &v1.ResticRepository{} +// Create takes the representation of a backupRepository and creates it. Returns the server's representation of the backupRepository, and an error, if there is any. +func (c *backupRepositories) Create(ctx context.Context, backupRepository *v1.BackupRepository, opts metav1.CreateOptions) (result *v1.BackupRepository, err error) { + result = &v1.BackupRepository{} err = c.client.Post(). Namespace(c.ns). - Resource("resticrepositories"). + Resource("backuprepositories"). VersionedParams(&opts, scheme.ParameterCodec). - Body(resticRepository). + Body(backupRepository). Do(ctx). Into(result) return } -// Update takes the representation of a resticRepository and updates it. Returns the server's representation of the resticRepository, and an error, if there is any. -func (c *resticRepositories) Update(ctx context.Context, resticRepository *v1.ResticRepository, opts metav1.UpdateOptions) (result *v1.ResticRepository, err error) { - result = &v1.ResticRepository{} +// Update takes the representation of a backupRepository and updates it. Returns the server's representation of the backupRepository, and an error, if there is any. +func (c *backupRepositories) Update(ctx context.Context, backupRepository *v1.BackupRepository, opts metav1.UpdateOptions) (result *v1.BackupRepository, err error) { + result = &v1.BackupRepository{} err = c.client.Put(). Namespace(c.ns). - Resource("resticrepositories"). - Name(resticRepository.Name). + Resource("backuprepositories"). + Name(backupRepository.Name). VersionedParams(&opts, scheme.ParameterCodec). - Body(resticRepository). + Body(backupRepository). Do(ctx). Into(result) return @@ -138,25 +138,25 @@ func (c *resticRepositories) Update(ctx context.Context, resticRepository *v1.Re // UpdateStatus was generated because the type contains a Status member. // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *resticRepositories) UpdateStatus(ctx context.Context, resticRepository *v1.ResticRepository, opts metav1.UpdateOptions) (result *v1.ResticRepository, err error) { - result = &v1.ResticRepository{} +func (c *backupRepositories) UpdateStatus(ctx context.Context, backupRepository *v1.BackupRepository, opts metav1.UpdateOptions) (result *v1.BackupRepository, err error) { + result = &v1.BackupRepository{} err = c.client.Put(). Namespace(c.ns). - Resource("resticrepositories"). - Name(resticRepository.Name). + Resource("backuprepositories"). + Name(backupRepository.Name). SubResource("status"). VersionedParams(&opts, scheme.ParameterCodec). - Body(resticRepository). + Body(backupRepository). Do(ctx). Into(result) return } -// Delete takes name of the resticRepository and deletes it. Returns an error if one occurs. -func (c *resticRepositories) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { +// Delete takes name of the backupRepository and deletes it. Returns an error if one occurs. +func (c *backupRepositories) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { return c.client.Delete(). Namespace(c.ns). - Resource("resticrepositories"). + Resource("backuprepositories"). Name(name). Body(&opts). Do(ctx). @@ -164,14 +164,14 @@ func (c *resticRepositories) Delete(ctx context.Context, name string, opts metav } // DeleteCollection deletes a collection of objects. -func (c *resticRepositories) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { +func (c *backupRepositories) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { var timeout time.Duration if listOpts.TimeoutSeconds != nil { timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second } return c.client.Delete(). Namespace(c.ns). - Resource("resticrepositories"). + Resource("backuprepositories"). VersionedParams(&listOpts, scheme.ParameterCodec). Timeout(timeout). Body(&opts). @@ -179,12 +179,12 @@ func (c *resticRepositories) DeleteCollection(ctx context.Context, opts metav1.D Error() } -// Patch applies the patch and returns the patched resticRepository. -func (c *resticRepositories) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.ResticRepository, err error) { - result = &v1.ResticRepository{} +// Patch applies the patch and returns the patched backupRepository. +func (c *backupRepositories) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.BackupRepository, err error) { + result = &v1.BackupRepository{} err = c.client.Patch(pt). Namespace(c.ns). - Resource("resticrepositories"). + Resource("backuprepositories"). Name(name). SubResource(subresources...). VersionedParams(&opts, scheme.ParameterCodec). diff --git a/pkg/generated/clientset/versioned/typed/velero/v1/fake/fake_backuprepository.go b/pkg/generated/clientset/versioned/typed/velero/v1/fake/fake_backuprepository.go new file mode 100644 index 0000000000..ef9d6b41c8 --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/velero/v1/fake/fake_backuprepository.go @@ -0,0 +1,142 @@ +/* +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 client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeBackupRepositories implements BackupRepositoryInterface +type FakeBackupRepositories struct { + Fake *FakeVeleroV1 + ns string +} + +var backuprepositoriesResource = schema.GroupVersionResource{Group: "velero.io", Version: "v1", Resource: "backuprepositories"} + +var backuprepositoriesKind = schema.GroupVersionKind{Group: "velero.io", Version: "v1", Kind: "BackupRepository"} + +// Get takes name of the backupRepository, and returns the corresponding backupRepository object, and an error if there is any. +func (c *FakeBackupRepositories) Get(ctx context.Context, name string, options v1.GetOptions) (result *velerov1.BackupRepository, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(backuprepositoriesResource, c.ns, name), &velerov1.BackupRepository{}) + + if obj == nil { + return nil, err + } + return obj.(*velerov1.BackupRepository), err +} + +// List takes label and field selectors, and returns the list of BackupRepositories that match those selectors. +func (c *FakeBackupRepositories) List(ctx context.Context, opts v1.ListOptions) (result *velerov1.BackupRepositoryList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(backuprepositoriesResource, backuprepositoriesKind, c.ns, opts), &velerov1.BackupRepositoryList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &velerov1.BackupRepositoryList{ListMeta: obj.(*velerov1.BackupRepositoryList).ListMeta} + for _, item := range obj.(*velerov1.BackupRepositoryList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested backupRepositories. +func (c *FakeBackupRepositories) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(backuprepositoriesResource, c.ns, opts)) + +} + +// Create takes the representation of a backupRepository and creates it. Returns the server's representation of the backupRepository, and an error, if there is any. +func (c *FakeBackupRepositories) Create(ctx context.Context, backupRepository *velerov1.BackupRepository, opts v1.CreateOptions) (result *velerov1.BackupRepository, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(backuprepositoriesResource, c.ns, backupRepository), &velerov1.BackupRepository{}) + + if obj == nil { + return nil, err + } + return obj.(*velerov1.BackupRepository), err +} + +// Update takes the representation of a backupRepository and updates it. Returns the server's representation of the backupRepository, and an error, if there is any. +func (c *FakeBackupRepositories) Update(ctx context.Context, backupRepository *velerov1.BackupRepository, opts v1.UpdateOptions) (result *velerov1.BackupRepository, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(backuprepositoriesResource, c.ns, backupRepository), &velerov1.BackupRepository{}) + + if obj == nil { + return nil, err + } + return obj.(*velerov1.BackupRepository), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeBackupRepositories) UpdateStatus(ctx context.Context, backupRepository *velerov1.BackupRepository, opts v1.UpdateOptions) (*velerov1.BackupRepository, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(backuprepositoriesResource, "status", c.ns, backupRepository), &velerov1.BackupRepository{}) + + if obj == nil { + return nil, err + } + return obj.(*velerov1.BackupRepository), err +} + +// Delete takes name of the backupRepository and deletes it. Returns an error if one occurs. +func (c *FakeBackupRepositories) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(backuprepositoriesResource, c.ns, name), &velerov1.BackupRepository{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeBackupRepositories) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(backuprepositoriesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &velerov1.BackupRepositoryList{}) + return err +} + +// Patch applies the patch and returns the patched backupRepository. +func (c *FakeBackupRepositories) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *velerov1.BackupRepository, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(backuprepositoriesResource, c.ns, name, pt, data, subresources...), &velerov1.BackupRepository{}) + + if obj == nil { + return nil, err + } + return obj.(*velerov1.BackupRepository), err +} diff --git a/pkg/generated/clientset/versioned/typed/velero/v1/fake/fake_resticrepository.go b/pkg/generated/clientset/versioned/typed/velero/v1/fake/fake_resticrepository.go deleted file mode 100644 index aeda0c9cb6..0000000000 --- a/pkg/generated/clientset/versioned/typed/velero/v1/fake/fake_resticrepository.go +++ /dev/null @@ -1,142 +0,0 @@ -/* -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 client-gen. DO NOT EDIT. - -package fake - -import ( - "context" - - velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeResticRepositories implements ResticRepositoryInterface -type FakeResticRepositories struct { - Fake *FakeVeleroV1 - ns string -} - -var resticrepositoriesResource = schema.GroupVersionResource{Group: "velero.io", Version: "v1", Resource: "resticrepositories"} - -var resticrepositoriesKind = schema.GroupVersionKind{Group: "velero.io", Version: "v1", Kind: "ResticRepository"} - -// Get takes name of the resticRepository, and returns the corresponding resticRepository object, and an error if there is any. -func (c *FakeResticRepositories) Get(ctx context.Context, name string, options v1.GetOptions) (result *velerov1.ResticRepository, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(resticrepositoriesResource, c.ns, name), &velerov1.ResticRepository{}) - - if obj == nil { - return nil, err - } - return obj.(*velerov1.ResticRepository), err -} - -// List takes label and field selectors, and returns the list of ResticRepositories that match those selectors. -func (c *FakeResticRepositories) List(ctx context.Context, opts v1.ListOptions) (result *velerov1.ResticRepositoryList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(resticrepositoriesResource, resticrepositoriesKind, c.ns, opts), &velerov1.ResticRepositoryList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &velerov1.ResticRepositoryList{ListMeta: obj.(*velerov1.ResticRepositoryList).ListMeta} - for _, item := range obj.(*velerov1.ResticRepositoryList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested resticRepositories. -func (c *FakeResticRepositories) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(resticrepositoriesResource, c.ns, opts)) - -} - -// Create takes the representation of a resticRepository and creates it. Returns the server's representation of the resticRepository, and an error, if there is any. -func (c *FakeResticRepositories) Create(ctx context.Context, resticRepository *velerov1.ResticRepository, opts v1.CreateOptions) (result *velerov1.ResticRepository, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(resticrepositoriesResource, c.ns, resticRepository), &velerov1.ResticRepository{}) - - if obj == nil { - return nil, err - } - return obj.(*velerov1.ResticRepository), err -} - -// Update takes the representation of a resticRepository and updates it. Returns the server's representation of the resticRepository, and an error, if there is any. -func (c *FakeResticRepositories) Update(ctx context.Context, resticRepository *velerov1.ResticRepository, opts v1.UpdateOptions) (result *velerov1.ResticRepository, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(resticrepositoriesResource, c.ns, resticRepository), &velerov1.ResticRepository{}) - - if obj == nil { - return nil, err - } - return obj.(*velerov1.ResticRepository), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeResticRepositories) UpdateStatus(ctx context.Context, resticRepository *velerov1.ResticRepository, opts v1.UpdateOptions) (*velerov1.ResticRepository, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(resticrepositoriesResource, "status", c.ns, resticRepository), &velerov1.ResticRepository{}) - - if obj == nil { - return nil, err - } - return obj.(*velerov1.ResticRepository), err -} - -// Delete takes name of the resticRepository and deletes it. Returns an error if one occurs. -func (c *FakeResticRepositories) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(resticrepositoriesResource, c.ns, name), &velerov1.ResticRepository{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeResticRepositories) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(resticrepositoriesResource, c.ns, listOpts) - - _, err := c.Fake.Invokes(action, &velerov1.ResticRepositoryList{}) - return err -} - -// Patch applies the patch and returns the patched resticRepository. -func (c *FakeResticRepositories) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *velerov1.ResticRepository, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(resticrepositoriesResource, c.ns, name, pt, data, subresources...), &velerov1.ResticRepository{}) - - if obj == nil { - return nil, err - } - return obj.(*velerov1.ResticRepository), err -} diff --git a/pkg/generated/clientset/versioned/typed/velero/v1/fake/fake_velero_client.go b/pkg/generated/clientset/versioned/typed/velero/v1/fake/fake_velero_client.go index e901158134..444c1f89f1 100644 --- a/pkg/generated/clientset/versioned/typed/velero/v1/fake/fake_velero_client.go +++ b/pkg/generated/clientset/versioned/typed/velero/v1/fake/fake_velero_client.go @@ -32,6 +32,10 @@ func (c *FakeVeleroV1) Backups(namespace string) v1.BackupInterface { return &FakeBackups{c, namespace} } +func (c *FakeVeleroV1) BackupRepositories(namespace string) v1.BackupRepositoryInterface { + return &FakeBackupRepositories{c, namespace} +} + func (c *FakeVeleroV1) BackupStorageLocations(namespace string) v1.BackupStorageLocationInterface { return &FakeBackupStorageLocations{c, namespace} } @@ -52,10 +56,6 @@ func (c *FakeVeleroV1) PodVolumeRestores(namespace string) v1.PodVolumeRestoreIn return &FakePodVolumeRestores{c, namespace} } -func (c *FakeVeleroV1) ResticRepositories(namespace string) v1.ResticRepositoryInterface { - return &FakeResticRepositories{c, namespace} -} - func (c *FakeVeleroV1) Restores(namespace string) v1.RestoreInterface { return &FakeRestores{c, namespace} } diff --git a/pkg/generated/clientset/versioned/typed/velero/v1/generated_expansion.go b/pkg/generated/clientset/versioned/typed/velero/v1/generated_expansion.go index 5deaaa51af..5032fd6a4e 100644 --- a/pkg/generated/clientset/versioned/typed/velero/v1/generated_expansion.go +++ b/pkg/generated/clientset/versioned/typed/velero/v1/generated_expansion.go @@ -20,6 +20,8 @@ package v1 type BackupExpansion interface{} +type BackupRepositoryExpansion interface{} + type BackupStorageLocationExpansion interface{} type DeleteBackupRequestExpansion interface{} @@ -30,8 +32,6 @@ type PodVolumeBackupExpansion interface{} type PodVolumeRestoreExpansion interface{} -type ResticRepositoryExpansion interface{} - type RestoreExpansion interface{} type ScheduleExpansion interface{} diff --git a/pkg/generated/clientset/versioned/typed/velero/v1/velero_client.go b/pkg/generated/clientset/versioned/typed/velero/v1/velero_client.go index 5758967efa..39f85628c2 100644 --- a/pkg/generated/clientset/versioned/typed/velero/v1/velero_client.go +++ b/pkg/generated/clientset/versioned/typed/velero/v1/velero_client.go @@ -27,12 +27,12 @@ import ( type VeleroV1Interface interface { RESTClient() rest.Interface BackupsGetter + BackupRepositoriesGetter BackupStorageLocationsGetter DeleteBackupRequestsGetter DownloadRequestsGetter PodVolumeBackupsGetter PodVolumeRestoresGetter - ResticRepositoriesGetter RestoresGetter SchedulesGetter ServerStatusRequestsGetter @@ -48,6 +48,10 @@ func (c *VeleroV1Client) Backups(namespace string) BackupInterface { return newBackups(c, namespace) } +func (c *VeleroV1Client) BackupRepositories(namespace string) BackupRepositoryInterface { + return newBackupRepositories(c, namespace) +} + func (c *VeleroV1Client) BackupStorageLocations(namespace string) BackupStorageLocationInterface { return newBackupStorageLocations(c, namespace) } @@ -68,10 +72,6 @@ func (c *VeleroV1Client) PodVolumeRestores(namespace string) PodVolumeRestoreInt return newPodVolumeRestores(c, namespace) } -func (c *VeleroV1Client) ResticRepositories(namespace string) ResticRepositoryInterface { - return newResticRepositories(c, namespace) -} - func (c *VeleroV1Client) Restores(namespace string) RestoreInterface { return newRestores(c, namespace) } diff --git a/pkg/generated/informers/externalversions/generic.go b/pkg/generated/informers/externalversions/generic.go index 15770dcebb..6058870247 100644 --- a/pkg/generated/informers/externalversions/generic.go +++ b/pkg/generated/informers/externalversions/generic.go @@ -55,6 +55,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource // Group=velero.io, Version=v1 case v1.SchemeGroupVersion.WithResource("backups"): return &genericInformer{resource: resource.GroupResource(), informer: f.Velero().V1().Backups().Informer()}, nil + case v1.SchemeGroupVersion.WithResource("backuprepositories"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Velero().V1().BackupRepositories().Informer()}, nil case v1.SchemeGroupVersion.WithResource("backupstoragelocations"): return &genericInformer{resource: resource.GroupResource(), informer: f.Velero().V1().BackupStorageLocations().Informer()}, nil case v1.SchemeGroupVersion.WithResource("deletebackuprequests"): @@ -65,8 +67,6 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Velero().V1().PodVolumeBackups().Informer()}, nil case v1.SchemeGroupVersion.WithResource("podvolumerestores"): return &genericInformer{resource: resource.GroupResource(), informer: f.Velero().V1().PodVolumeRestores().Informer()}, nil - case v1.SchemeGroupVersion.WithResource("resticrepositories"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Velero().V1().ResticRepositories().Informer()}, nil case v1.SchemeGroupVersion.WithResource("restores"): return &genericInformer{resource: resource.GroupResource(), informer: f.Velero().V1().Restores().Informer()}, nil case v1.SchemeGroupVersion.WithResource("schedules"): diff --git a/pkg/generated/informers/externalversions/velero/v1/resticrepository.go b/pkg/generated/informers/externalversions/velero/v1/backuprepository.go similarity index 70% rename from pkg/generated/informers/externalversions/velero/v1/resticrepository.go rename to pkg/generated/informers/externalversions/velero/v1/backuprepository.go index f92565554b..59865c8943 100644 --- a/pkg/generated/informers/externalversions/velero/v1/resticrepository.go +++ b/pkg/generated/informers/externalversions/velero/v1/backuprepository.go @@ -32,59 +32,59 @@ import ( cache "k8s.io/client-go/tools/cache" ) -// ResticRepositoryInformer provides access to a shared informer and lister for -// ResticRepositories. -type ResticRepositoryInformer interface { +// BackupRepositoryInformer provides access to a shared informer and lister for +// BackupRepositories. +type BackupRepositoryInformer interface { Informer() cache.SharedIndexInformer - Lister() v1.ResticRepositoryLister + Lister() v1.BackupRepositoryLister } -type resticRepositoryInformer struct { +type backupRepositoryInformer struct { factory internalinterfaces.SharedInformerFactory tweakListOptions internalinterfaces.TweakListOptionsFunc namespace string } -// NewResticRepositoryInformer constructs a new informer for ResticRepository type. +// NewBackupRepositoryInformer constructs a new informer for BackupRepository type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. -func NewResticRepositoryInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredResticRepositoryInformer(client, namespace, resyncPeriod, indexers, nil) +func NewBackupRepositoryInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredBackupRepositoryInformer(client, namespace, resyncPeriod, indexers, nil) } -// NewFilteredResticRepositoryInformer constructs a new informer for ResticRepository type. +// NewFilteredBackupRepositoryInformer constructs a new informer for BackupRepository type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. -func NewFilteredResticRepositoryInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { +func NewFilteredBackupRepositoryInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.VeleroV1().ResticRepositories(namespace).List(context.TODO(), options) + return client.VeleroV1().BackupRepositories(namespace).List(context.TODO(), options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.VeleroV1().ResticRepositories(namespace).Watch(context.TODO(), options) + return client.VeleroV1().BackupRepositories(namespace).Watch(context.TODO(), options) }, }, - &velerov1.ResticRepository{}, + &velerov1.BackupRepository{}, resyncPeriod, indexers, ) } -func (f *resticRepositoryInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredResticRepositoryInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +func (f *backupRepositoryInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredBackupRepositoryInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } -func (f *resticRepositoryInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&velerov1.ResticRepository{}, f.defaultInformer) +func (f *backupRepositoryInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&velerov1.BackupRepository{}, f.defaultInformer) } -func (f *resticRepositoryInformer) Lister() v1.ResticRepositoryLister { - return v1.NewResticRepositoryLister(f.Informer().GetIndexer()) +func (f *backupRepositoryInformer) Lister() v1.BackupRepositoryLister { + return v1.NewBackupRepositoryLister(f.Informer().GetIndexer()) } diff --git a/pkg/generated/informers/externalversions/velero/v1/interface.go b/pkg/generated/informers/externalversions/velero/v1/interface.go index 981470c409..087dd33563 100644 --- a/pkg/generated/informers/externalversions/velero/v1/interface.go +++ b/pkg/generated/informers/externalversions/velero/v1/interface.go @@ -26,6 +26,8 @@ import ( type Interface interface { // Backups returns a BackupInformer. Backups() BackupInformer + // BackupRepositories returns a BackupRepositoryInformer. + BackupRepositories() BackupRepositoryInformer // BackupStorageLocations returns a BackupStorageLocationInformer. BackupStorageLocations() BackupStorageLocationInformer // DeleteBackupRequests returns a DeleteBackupRequestInformer. @@ -36,8 +38,6 @@ type Interface interface { PodVolumeBackups() PodVolumeBackupInformer // PodVolumeRestores returns a PodVolumeRestoreInformer. PodVolumeRestores() PodVolumeRestoreInformer - // ResticRepositories returns a ResticRepositoryInformer. - ResticRepositories() ResticRepositoryInformer // Restores returns a RestoreInformer. Restores() RestoreInformer // Schedules returns a ScheduleInformer. @@ -64,6 +64,11 @@ func (v *version) Backups() BackupInformer { return &backupInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// BackupRepositories returns a BackupRepositoryInformer. +func (v *version) BackupRepositories() BackupRepositoryInformer { + return &backupRepositoryInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // BackupStorageLocations returns a BackupStorageLocationInformer. func (v *version) BackupStorageLocations() BackupStorageLocationInformer { return &backupStorageLocationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} @@ -89,11 +94,6 @@ func (v *version) PodVolumeRestores() PodVolumeRestoreInformer { return &podVolumeRestoreInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } -// ResticRepositories returns a ResticRepositoryInformer. -func (v *version) ResticRepositories() ResticRepositoryInformer { - return &resticRepositoryInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} -} - // Restores returns a RestoreInformer. func (v *version) Restores() RestoreInformer { return &restoreInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/generated/listers/velero/v1/backuprepository.go b/pkg/generated/listers/velero/v1/backuprepository.go new file mode 100644 index 0000000000..ef619baf10 --- /dev/null +++ b/pkg/generated/listers/velero/v1/backuprepository.go @@ -0,0 +1,99 @@ +/* +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 lister-gen. DO NOT EDIT. + +package v1 + +import ( + v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// BackupRepositoryLister helps list BackupRepositories. +// All objects returned here must be treated as read-only. +type BackupRepositoryLister interface { + // List lists all BackupRepositories in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1.BackupRepository, err error) + // BackupRepositories returns an object that can list and get BackupRepositories. + BackupRepositories(namespace string) BackupRepositoryNamespaceLister + BackupRepositoryListerExpansion +} + +// backupRepositoryLister implements the BackupRepositoryLister interface. +type backupRepositoryLister struct { + indexer cache.Indexer +} + +// NewBackupRepositoryLister returns a new BackupRepositoryLister. +func NewBackupRepositoryLister(indexer cache.Indexer) BackupRepositoryLister { + return &backupRepositoryLister{indexer: indexer} +} + +// List lists all BackupRepositories in the indexer. +func (s *backupRepositoryLister) List(selector labels.Selector) (ret []*v1.BackupRepository, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1.BackupRepository)) + }) + return ret, err +} + +// BackupRepositories returns an object that can list and get BackupRepositories. +func (s *backupRepositoryLister) BackupRepositories(namespace string) BackupRepositoryNamespaceLister { + return backupRepositoryNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// BackupRepositoryNamespaceLister helps list and get BackupRepositories. +// All objects returned here must be treated as read-only. +type BackupRepositoryNamespaceLister interface { + // List lists all BackupRepositories in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1.BackupRepository, err error) + // Get retrieves the BackupRepository from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1.BackupRepository, error) + BackupRepositoryNamespaceListerExpansion +} + +// backupRepositoryNamespaceLister implements the BackupRepositoryNamespaceLister +// interface. +type backupRepositoryNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all BackupRepositories in the indexer for a given namespace. +func (s backupRepositoryNamespaceLister) List(selector labels.Selector) (ret []*v1.BackupRepository, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1.BackupRepository)) + }) + return ret, err +} + +// Get retrieves the BackupRepository from the indexer for a given namespace and name. +func (s backupRepositoryNamespaceLister) Get(name string) (*v1.BackupRepository, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1.Resource("backuprepository"), name) + } + return obj.(*v1.BackupRepository), nil +} diff --git a/pkg/generated/listers/velero/v1/expansion_generated.go b/pkg/generated/listers/velero/v1/expansion_generated.go index b57656650d..c0cd576546 100644 --- a/pkg/generated/listers/velero/v1/expansion_generated.go +++ b/pkg/generated/listers/velero/v1/expansion_generated.go @@ -26,6 +26,14 @@ type BackupListerExpansion interface{} // BackupNamespaceLister. type BackupNamespaceListerExpansion interface{} +// BackupRepositoryListerExpansion allows custom methods to be added to +// BackupRepositoryLister. +type BackupRepositoryListerExpansion interface{} + +// BackupRepositoryNamespaceListerExpansion allows custom methods to be added to +// BackupRepositoryNamespaceLister. +type BackupRepositoryNamespaceListerExpansion interface{} + // BackupStorageLocationListerExpansion allows custom methods to be added to // BackupStorageLocationLister. type BackupStorageLocationListerExpansion interface{} @@ -66,14 +74,6 @@ type PodVolumeRestoreListerExpansion interface{} // PodVolumeRestoreNamespaceLister. type PodVolumeRestoreNamespaceListerExpansion interface{} -// ResticRepositoryListerExpansion allows custom methods to be added to -// ResticRepositoryLister. -type ResticRepositoryListerExpansion interface{} - -// ResticRepositoryNamespaceListerExpansion allows custom methods to be added to -// ResticRepositoryNamespaceLister. -type ResticRepositoryNamespaceListerExpansion interface{} - // RestoreListerExpansion allows custom methods to be added to // RestoreLister. type RestoreListerExpansion interface{} diff --git a/pkg/generated/listers/velero/v1/resticrepository.go b/pkg/generated/listers/velero/v1/resticrepository.go deleted file mode 100644 index 96bcfdc7c8..0000000000 --- a/pkg/generated/listers/velero/v1/resticrepository.go +++ /dev/null @@ -1,99 +0,0 @@ -/* -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 lister-gen. DO NOT EDIT. - -package v1 - -import ( - v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" -) - -// ResticRepositoryLister helps list ResticRepositories. -// All objects returned here must be treated as read-only. -type ResticRepositoryLister interface { - // List lists all ResticRepositories in the indexer. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.ResticRepository, err error) - // ResticRepositories returns an object that can list and get ResticRepositories. - ResticRepositories(namespace string) ResticRepositoryNamespaceLister - ResticRepositoryListerExpansion -} - -// resticRepositoryLister implements the ResticRepositoryLister interface. -type resticRepositoryLister struct { - indexer cache.Indexer -} - -// NewResticRepositoryLister returns a new ResticRepositoryLister. -func NewResticRepositoryLister(indexer cache.Indexer) ResticRepositoryLister { - return &resticRepositoryLister{indexer: indexer} -} - -// List lists all ResticRepositories in the indexer. -func (s *resticRepositoryLister) List(selector labels.Selector) (ret []*v1.ResticRepository, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1.ResticRepository)) - }) - return ret, err -} - -// ResticRepositories returns an object that can list and get ResticRepositories. -func (s *resticRepositoryLister) ResticRepositories(namespace string) ResticRepositoryNamespaceLister { - return resticRepositoryNamespaceLister{indexer: s.indexer, namespace: namespace} -} - -// ResticRepositoryNamespaceLister helps list and get ResticRepositories. -// All objects returned here must be treated as read-only. -type ResticRepositoryNamespaceLister interface { - // List lists all ResticRepositories in the indexer for a given namespace. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.ResticRepository, err error) - // Get retrieves the ResticRepository from the indexer for a given namespace and name. - // Objects returned here must be treated as read-only. - Get(name string) (*v1.ResticRepository, error) - ResticRepositoryNamespaceListerExpansion -} - -// resticRepositoryNamespaceLister implements the ResticRepositoryNamespaceLister -// interface. -type resticRepositoryNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all ResticRepositories in the indexer for a given namespace. -func (s resticRepositoryNamespaceLister) List(selector labels.Selector) (ret []*v1.ResticRepository, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1.ResticRepository)) - }) - return ret, err -} - -// Get retrieves the ResticRepository from the indexer for a given namespace and name. -func (s resticRepositoryNamespaceLister) Get(name string) (*v1.ResticRepository, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1.Resource("resticrepository"), name) - } - return obj.(*v1.ResticRepository), nil -} diff --git a/pkg/restic/backupper.go b/pkg/restic/backupper.go index fd366a36bc..589b396f39 100644 --- a/pkg/restic/backupper.go +++ b/pkg/restic/backupper.go @@ -176,8 +176,8 @@ func (b *backupper) BackupPodVolumes(backup *velerov1api.Backup, pod *corev1api. log.Warnf("Volume %s is declared in pod %s/%s but not mounted by any container, skipping", volumeName, pod.Namespace, pod.Name) continue } - - volumeBackup := newPodVolumeBackup(backup, pod, volume, repo.Spec.ResticIdentifier, pvc) + // TODO: Remove the hard-coded uploader type before v1.10 FC + volumeBackup := newPodVolumeBackup(backup, pod, volume, repo.Spec.ResticIdentifier, "restic", pvc) if volumeBackup, err = b.repoManager.veleroClient.VeleroV1().PodVolumeBackups(volumeBackup.Namespace).Create(context.TODO(), volumeBackup, metav1.CreateOptions{}); err != nil { errs = append(errs, err) continue @@ -236,7 +236,7 @@ func isHostPathVolume(volume *corev1api.Volume, pvc *corev1api.PersistentVolumeC return pv.Spec.HostPath != nil, nil } -func newPodVolumeBackup(backup *velerov1api.Backup, pod *corev1api.Pod, volume corev1api.Volume, repoIdentifier string, pvc *corev1api.PersistentVolumeClaim) *velerov1api.PodVolumeBackup { +func newPodVolumeBackup(backup *velerov1api.Backup, pod *corev1api.Pod, volume corev1api.Volume, repoIdentifier, uploaderType string, pvc *corev1api.PersistentVolumeClaim) *velerov1api.PodVolumeBackup { pvb := &velerov1api.PodVolumeBackup{ ObjectMeta: metav1.ObjectMeta{ Namespace: backup.Namespace, @@ -274,6 +274,7 @@ func newPodVolumeBackup(backup *velerov1api.Backup, pod *corev1api.Pod, volume c }, BackupStorageLocation: backup.Spec.StorageLocation, RepoIdentifier: repoIdentifier, + UploaderType: uploaderType, }, } diff --git a/pkg/restic/mocks/repository_manager.go b/pkg/restic/mocks/repository_manager.go index b164cb3e00..de8770c375 100644 --- a/pkg/restic/mocks/repository_manager.go +++ b/pkg/restic/mocks/repository_manager.go @@ -31,11 +31,11 @@ type RepositoryManager struct { } // ConnectToRepo provides a mock function with given fields: repo -func (_m *RepositoryManager) ConnectToRepo(repo *v1.ResticRepository) error { +func (_m *RepositoryManager) ConnectToRepo(repo *v1.BackupRepository) error { ret := _m.Called(repo) var r0 error - if rf, ok := ret.Get(0).(func(*v1.ResticRepository) error); ok { + if rf, ok := ret.Get(0).(func(*v1.BackupRepository) error); ok { r0 = rf(repo) } else { r0 = ret.Error(0) @@ -59,11 +59,11 @@ func (_m *RepositoryManager) Forget(_a0 context.Context, _a1 restic.SnapshotIden } // InitRepo provides a mock function with given fields: repo -func (_m *RepositoryManager) InitRepo(repo *v1.ResticRepository) error { +func (_m *RepositoryManager) InitRepo(repo *v1.BackupRepository) error { ret := _m.Called(repo) var r0 error - if rf, ok := ret.Get(0).(func(*v1.ResticRepository) error); ok { + if rf, ok := ret.Get(0).(func(repository *v1.BackupRepository) error); ok { r0 = rf(repo) } else { r0 = ret.Error(0) @@ -119,11 +119,11 @@ func (_m *RepositoryManager) NewRestorer(_a0 context.Context, _a1 *v1.Restore) ( } // PruneRepo provides a mock function with given fields: repo -func (_m *RepositoryManager) PruneRepo(repo *v1.ResticRepository) error { +func (_m *RepositoryManager) PruneRepo(repo *v1.BackupRepository) error { ret := _m.Called(repo) var r0 error - if rf, ok := ret.Get(0).(func(*v1.ResticRepository) error); ok { + if rf, ok := ret.Get(0).(func(repository *v1.BackupRepository) error); ok { r0 = rf(repo) } else { r0 = ret.Error(0) @@ -133,11 +133,11 @@ func (_m *RepositoryManager) PruneRepo(repo *v1.ResticRepository) error { } // UnlockRepo provides a mock function with given fields: repo -func (_m *RepositoryManager) UnlockRepo(repo *v1.ResticRepository) error { +func (_m *RepositoryManager) UnlockRepo(repo *v1.BackupRepository) error { ret := _m.Called(repo) var r0 error - if rf, ok := ret.Get(0).(func(*v1.ResticRepository) error); ok { + if rf, ok := ret.Get(0).(func(repository *v1.BackupRepository) error); ok { r0 = rf(repo) } else { r0 = ret.Error(0) diff --git a/pkg/restic/repository_ensurer.go b/pkg/restic/repository_ensurer.go index f1f4f168a3..d764a49c7d 100644 --- a/pkg/restic/repository_ensurer.go +++ b/pkg/restic/repository_ensurer.go @@ -38,11 +38,11 @@ import ( // repositoryEnsurer ensures that Velero restic repositories are created and ready. type repositoryEnsurer struct { log logrus.FieldLogger - repoLister velerov1listers.ResticRepositoryLister - repoClient velerov1client.ResticRepositoriesGetter + repoLister velerov1listers.BackupRepositoryLister + repoClient velerov1client.BackupRepositoriesGetter repoChansLock sync.Mutex - repoChans map[string]chan *velerov1api.ResticRepository + repoChans map[string]chan *velerov1api.BackupRepository // repoLocksMu synchronizes reads/writes to the repoLocks map itself // since maps are not threadsafe. @@ -55,20 +55,20 @@ type repoKey struct { backupLocation string } -func newRepositoryEnsurer(repoInformer velerov1informers.ResticRepositoryInformer, repoClient velerov1client.ResticRepositoriesGetter, log logrus.FieldLogger) *repositoryEnsurer { +func newRepositoryEnsurer(repoInformer velerov1informers.BackupRepositoryInformer, repoClient velerov1client.BackupRepositoriesGetter, log logrus.FieldLogger) *repositoryEnsurer { r := &repositoryEnsurer{ log: log, repoLister: repoInformer.Lister(), repoClient: repoClient, - repoChans: make(map[string]chan *velerov1api.ResticRepository), + repoChans: make(map[string]chan *velerov1api.BackupRepository), repoLocks: make(map[repoKey]*sync.Mutex), } repoInformer.Informer().AddEventHandler( cache.ResourceEventHandlerFuncs{ UpdateFunc: func(old, upd interface{}) { - oldObj := old.(*velerov1api.ResticRepository) - newObj := upd.(*velerov1api.ResticRepository) + oldObj := old.(*velerov1api.BackupRepository) + newObj := upd.(*velerov1api.BackupRepository) // we're only interested in phase-changing updates if oldObj.Status.Phase == newObj.Status.Phase { @@ -76,7 +76,7 @@ func newRepositoryEnsurer(repoInformer velerov1informers.ResticRepositoryInforme } // we're only interested in updates where the updated object is either Ready or NotReady - if newObj.Status.Phase != velerov1api.ResticRepositoryPhaseReady && newObj.Status.Phase != velerov1api.ResticRepositoryPhaseNotReady { + if newObj.Status.Phase != velerov1api.BackupRepositoryPhaseReady && newObj.Status.Phase != velerov1api.BackupRepositoryPhaseNotReady { return } @@ -105,7 +105,7 @@ func repoLabels(volumeNamespace, backupLocation string) labels.Set { } } -func (r *repositoryEnsurer) EnsureRepo(ctx context.Context, namespace, volumeNamespace, backupLocation string) (*velerov1api.ResticRepository, error) { +func (r *repositoryEnsurer) EnsureRepo(ctx context.Context, namespace, volumeNamespace, backupLocation string) (*velerov1api.BackupRepository, error) { log := r.log.WithField("volumeNamespace", volumeNamespace).WithField("backupLocation", backupLocation) // It's only safe to have one instance of this method executing concurrently for a @@ -132,7 +132,7 @@ func (r *repositoryEnsurer) EnsureRepo(ctx context.Context, namespace, volumeNam selector := labels.SelectorFromSet(repoLabels(volumeNamespace, backupLocation)) - repos, err := r.repoLister.ResticRepositories(namespace).List(selector) + repos, err := r.repoLister.BackupRepositories(namespace).List(selector) if err != nil { return nil, errors.WithStack(err) } @@ -140,7 +140,7 @@ func (r *repositoryEnsurer) EnsureRepo(ctx context.Context, namespace, volumeNam return nil, errors.Errorf("more than one ResticRepository found for workload namespace %q, backup storage location %q", volumeNamespace, backupLocation) } if len(repos) == 1 { - if repos[0].Status.Phase != velerov1api.ResticRepositoryPhaseReady { + if repos[0].Status.Phase != velerov1api.BackupRepositoryPhaseReady { return nil, errors.Errorf("restic repository is not ready: %s", repos[0].Status.Message) } @@ -151,13 +151,13 @@ func (r *repositoryEnsurer) EnsureRepo(ctx context.Context, namespace, volumeNam log.Debug("No repository found, creating one") // no repo found: create one and wait for it to be ready - repo := &velerov1api.ResticRepository{ + repo := &velerov1api.BackupRepository{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, GenerateName: fmt.Sprintf("%s-%s-", volumeNamespace, backupLocation), Labels: repoLabels(volumeNamespace, backupLocation), }, - Spec: velerov1api.ResticRepositorySpec{ + Spec: velerov1api.BackupRepositorySpec{ VolumeNamespace: volumeNamespace, BackupStorageLocation: backupLocation, }, @@ -169,7 +169,7 @@ func (r *repositoryEnsurer) EnsureRepo(ctx context.Context, namespace, volumeNam close(repoChan) }() - if _, err := r.repoClient.ResticRepositories(namespace).Create(context.TODO(), repo, metav1.CreateOptions{}); err != nil { + if _, err := r.repoClient.BackupRepositories(namespace).Create(context.TODO(), repo, metav1.CreateOptions{}); err != nil { return nil, errors.Wrapf(err, "unable to create restic repository resource") } @@ -181,7 +181,8 @@ func (r *repositoryEnsurer) EnsureRepo(ctx context.Context, namespace, volumeNam case <-ctx.Done(): return nil, errors.New("timed out waiting for restic repository to become ready") case res := <-repoChan: - if res.Status.Phase == velerov1api.ResticRepositoryPhaseNotReady { + + if res.Status.Phase == velerov1api.BackupRepositoryPhaseNotReady { return nil, errors.Errorf("restic repository is not ready: %s", res.Status.Message) } @@ -189,11 +190,11 @@ func (r *repositoryEnsurer) EnsureRepo(ctx context.Context, namespace, volumeNam } } -func (r *repositoryEnsurer) getRepoChan(name string) chan *velerov1api.ResticRepository { +func (r *repositoryEnsurer) getRepoChan(name string) chan *velerov1api.BackupRepository { r.repoChansLock.Lock() defer r.repoChansLock.Unlock() - r.repoChans[name] = make(chan *velerov1api.ResticRepository) + r.repoChans[name] = make(chan *velerov1api.BackupRepository) return r.repoChans[name] } diff --git a/pkg/restic/repository_manager.go b/pkg/restic/repository_manager.go index f3ff735f9d..f0ab633868 100644 --- a/pkg/restic/repository_manager.go +++ b/pkg/restic/repository_manager.go @@ -43,19 +43,19 @@ import ( // RepositoryManager executes commands against restic repositories. type RepositoryManager interface { // InitRepo initializes a repo with the specified name and identifier. - InitRepo(repo *velerov1api.ResticRepository) error + InitRepo(repo *velerov1api.BackupRepository) error // ConnectToRepo runs the 'restic snapshots' command against the // specified repo, and returns an error if it fails. This is // intended to be used to ensure that the repo exists/can be // authenticated to. - ConnectToRepo(repo *velerov1api.ResticRepository) error + ConnectToRepo(repo *velerov1api.BackupRepository) error // PruneRepo deletes unused data from a repo. - PruneRepo(repo *velerov1api.ResticRepository) error + PruneRepo(repo *velerov1api.BackupRepository) error // UnlockRepo removes stale locks from a repo. - UnlockRepo(repo *velerov1api.ResticRepository) error + UnlockRepo(repo *velerov1api.BackupRepository) error // Forget removes a snapshot from the list of // available snapshots in a repo. @@ -83,7 +83,7 @@ type RestorerFactory interface { type repositoryManager struct { namespace string veleroClient clientset.Interface - repoLister velerov1listers.ResticRepositoryLister + repoLister velerov1listers.BackupRepositoryLister repoInformerSynced cache.InformerSynced kbClient kbclient.Client log logrus.FieldLogger @@ -111,8 +111,8 @@ func NewRepositoryManager( ctx context.Context, namespace string, veleroClient clientset.Interface, - repoInformer velerov1informers.ResticRepositoryInformer, - repoClient velerov1client.ResticRepositoriesGetter, + repoInformer velerov1informers.BackupRepositoryInformer, + repoClient velerov1client.BackupRepositoriesGetter, kbClient kbclient.Client, pvcClient corev1client.PersistentVolumeClaimsGetter, pvClient corev1client.PersistentVolumesGetter, @@ -181,7 +181,7 @@ func (rm *repositoryManager) NewRestorer(ctx context.Context, restore *velerov1a return r, nil } -func (rm *repositoryManager) InitRepo(repo *velerov1api.ResticRepository) error { +func (rm *repositoryManager) InitRepo(repo *velerov1api.BackupRepository) error { // restic init requires an exclusive lock rm.repoLocker.LockExclusive(repo.Name) defer rm.repoLocker.UnlockExclusive(repo.Name) @@ -189,7 +189,7 @@ func (rm *repositoryManager) InitRepo(repo *velerov1api.ResticRepository) error return rm.exec(InitCommand(repo.Spec.ResticIdentifier), repo.Spec.BackupStorageLocation) } -func (rm *repositoryManager) ConnectToRepo(repo *velerov1api.ResticRepository) error { +func (rm *repositoryManager) ConnectToRepo(repo *velerov1api.BackupRepository) error { // restic snapshots requires a non-exclusive lock rm.repoLocker.Lock(repo.Name) defer rm.repoLocker.Unlock(repo.Name) @@ -204,7 +204,7 @@ func (rm *repositoryManager) ConnectToRepo(repo *velerov1api.ResticRepository) e return rm.exec(snapshotsCmd, repo.Spec.BackupStorageLocation) } -func (rm *repositoryManager) PruneRepo(repo *velerov1api.ResticRepository) error { +func (rm *repositoryManager) PruneRepo(repo *velerov1api.BackupRepository) error { // restic prune requires an exclusive lock rm.repoLocker.LockExclusive(repo.Name) defer rm.repoLocker.UnlockExclusive(repo.Name) @@ -212,7 +212,7 @@ func (rm *repositoryManager) PruneRepo(repo *velerov1api.ResticRepository) error return rm.exec(PruneCommand(repo.Spec.ResticIdentifier), repo.Spec.BackupStorageLocation) } -func (rm *repositoryManager) UnlockRepo(repo *velerov1api.ResticRepository) error { +func (rm *repositoryManager) UnlockRepo(repo *velerov1api.BackupRepository) error { // restic unlock requires a non-exclusive lock rm.repoLocker.Lock(repo.Name) defer rm.repoLocker.Unlock(repo.Name) diff --git a/pkg/restic/restorer.go b/pkg/restic/restorer.go index 242cc717d7..e747b0b6d5 100644 --- a/pkg/restic/restorer.go +++ b/pkg/restic/restorer.go @@ -139,8 +139,8 @@ func (r *restorer) RestorePodVolumes(data RestoreData) []error { } } } - - volumeRestore := newPodVolumeRestore(data.Restore, data.Pod, data.BackupLocation, volume, snapshot, repo.Spec.ResticIdentifier, pvc) + // TODO: Remove the hard-coded uploader type before v1.10 FC + volumeRestore := newPodVolumeRestore(data.Restore, data.Pod, data.BackupLocation, volume, snapshot, repo.Spec.ResticIdentifier, "restic", pvc) if err := errorOnly(r.repoManager.veleroClient.VeleroV1().PodVolumeRestores(volumeRestore.Namespace).Create(context.TODO(), volumeRestore, metav1.CreateOptions{})); err != nil { errs = append(errs, errors.WithStack(err)) @@ -169,7 +169,7 @@ ForEachVolume: return errs } -func newPodVolumeRestore(restore *velerov1api.Restore, pod *corev1api.Pod, backupLocation, volume, snapshot, repoIdentifier string, pvc *corev1api.PersistentVolumeClaim) *velerov1api.PodVolumeRestore { +func newPodVolumeRestore(restore *velerov1api.Restore, pod *corev1api.Pod, backupLocation, volume, snapshot, repoIdentifier, uploaderType string, pvc *corev1api.PersistentVolumeClaim) *velerov1api.PodVolumeRestore { pvr := &velerov1api.PodVolumeRestore{ ObjectMeta: metav1.ObjectMeta{ Namespace: restore.Namespace,